@geohar/un-bien 0.7.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 (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1,614 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { mkdir, readFile, writeFile, chmod, unlink } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { generateEd25519Keypair } from "./crypto.js";
5
+ import { canonicalizeEd25519PublicKey } from "../mesh/encoding.js";
6
+ import { unbienStateHome } from "../paths.js";
7
+ /**
8
+ * Pi-secret storage (plan/27 Wave E1).
9
+ *
10
+ * The Ed25519 long-term identity of this Pi lives in the platform keyring
11
+ * via `@napi-rs/keyring` (Keychain on macOS, libsecret on Linux desktop,
12
+ * Credential Manager on Windows — DPAPI-backed). When the keyring is
13
+ * unavailable (headless Linux without a D-Bus session, Docker containers,
14
+ * VPS without GNOME Keyring/KWallet running) we fall back to a
15
+ * file-backed store at `~/.pi/un-bien/identity.json` with `0o600`
16
+ * permissions and the parent dir at `0o700`.
17
+ *
18
+ * **Migration**: previous builds used `keytar` against service
19
+ * `dev.unbien.mac`. This module reads from the old service if the new
20
+ * service is empty, copies the entry to the new service `dev.unbien.pi`,
21
+ * and deletes the old one. Both keytar and `@napi-rs/keyring` address the
22
+ * same OS-level credential store on every supported platform, so the read
23
+ * succeeds without keeping the deprecated `keytar` dependency.
24
+ */
25
+ const NEW_SERVICE = "dev.unbien.pi"; // platform-neutral
26
+ const OLD_SERVICE = "dev.unbien.mac"; // legacy keytar service (pre-2026-05-25)
27
+ const ACCOUNT = "longterm-ed25519";
28
+ /**
29
+ * The keyring read can THROW transiently rather than permanently — most
30
+ * notably a macOS Keychain that's still locked right after login/wake (the
31
+ * machine sat idle for days). Treating that throw as "backend unavailable"
32
+ * and minting a fresh identity silently orphans the paired key (the
33
+ * "lost pairing after a week idle" failure). So we retry the read a few times
34
+ * before ever concluding the keyring is truly unavailable. Overridable for
35
+ * tests via `_setKeyringRetryForTest`. */
36
+ let _keyringReadAttempts = 3;
37
+ let _keyringRetryDelayMs = 300;
38
+ /** Raised when the keyring is unreadable on a platform where it's a core OS
39
+ * service (macOS Keychain, Windows Credential Manager) AND no prior file
40
+ * identity exists. We refuse to generate a NEW identity here because that
41
+ * would break existing pairing — the caller surfaces this so the user can
42
+ * unlock the keychain and retry instead of silently re-pairing. */
43
+ export class KeyringUnavailableError extends Error {
44
+ constructor(cause) {
45
+ super("Platform keyring is unreadable and no file-backed identity exists. " +
46
+ "Refusing to generate a NEW identity (that would break existing " +
47
+ "pairing). Unlock your keychain / start your secret service and retry. " +
48
+ "Set UNBIEN_ALLOW_FILE_IDENTITY=1 to force a file-backed identity. " +
49
+ `Cause: ${String(cause)}`);
50
+ this.name = "KeyringUnavailableError";
51
+ }
52
+ }
53
+ /** Raised when no identity can be resolved (keyring unreadable, no identity
54
+ * file) BUT `peers.json` already lists paired devices. Minting a fresh key
55
+ * here would make SelfRevoke wipe those pairings — see issues #95 / #69. */
56
+ export class PairedIdentityMissingError extends Error {
57
+ constructor(pairedCount, cause) {
58
+ super(`No identity could be read, but ${pairedCount} device(s) are already ` +
59
+ "paired. Refusing to generate a NEW identity — that would revoke every " +
60
+ "paired device. This usually means this process cannot reach the same " +
61
+ "keyring as the session that paired (e.g. a systemd --user daemon vs. " +
62
+ "your desktop session). Fix the service's keyring access, or pin the " +
63
+ "identity by copying the paired keypair to ~/.pi/un-bien/identity.json " +
64
+ "(0600), which both contexts read first. " +
65
+ `Cause: ${String(cause)}`);
66
+ this.name = "PairedIdentityMissingError";
67
+ }
68
+ }
69
+ const PI_DIR = unbienStateHome();
70
+ const IDENTITY_FILE = join(PI_DIR, "identity.json");
71
+ const PEERS_PATH = join(PI_DIR, "peers.json");
72
+ /**
73
+ * Per-operation timeout for native keyring calls (gnome-keyring / libsecret
74
+ * via @napi-rs/keyring). A healthy secret service settles in milliseconds; 3s
75
+ * is generous. The point is NOT speed — it is converting a HANG into a thrown
76
+ * error. The native getPassword()/setPassword() can block indefinitely when
77
+ * gnome-keyring waits on a GUI authorization prompt that cannot be shown in
78
+ * a headless / tmux / non-interactive context. A hang never settles, so
79
+ * without this guard the promise never rejects and the retry + file-fallback
80
+ * logic in getOrCreateEd25519Keypair() is unreachable — freezing the entire
81
+ * /unbien pair bootstrap. Raising a real error here lets that fallback
82
+ * chain run as designed.
83
+ */
84
+ const KEYRING_OP_TIMEOUT_MS = 3_000;
85
+ function _withTimeout(p, op, ms = KEYRING_OP_TIMEOUT_MS) {
86
+ return Promise.race([
87
+ p,
88
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`keyring ${op} timed out after ${ms}ms`)), ms)),
89
+ ]);
90
+ }
91
+ /**
92
+ * Lazily loaded `@napi-rs/keyring` binding — issue #113.
93
+ *
94
+ * A STATIC `import { AsyncEntry } from "@napi-rs/keyring"` is evaluated when
95
+ * the extension module is loaded, so a native binding that cannot be resolved
96
+ * takes the WHOLE extension down at load time ("Failed to load extension …:
97
+ * Cannot find native binding"). That happens on a Bun-compiled `pi`: the
98
+ * loader's first branch (`require("./keyring.<triple>.node")`) is fine, but its
99
+ * fallback (`require("@napi-rs/keyring-<triple>")`, a bare package whose `main`
100
+ * IS the .node file) resolves under Node and not under Bun — and the message it
101
+ * prints then blames npm's optional-dependency bug, sending users off to delete
102
+ * node_modules and losing their other pi packages.
103
+ *
104
+ * Loading on first use turns that fatal load error into an ordinary backend
105
+ * failure, which the existing retry + file-identity fallback already handles.
106
+ */
107
+ let _asyncEntryCtor = null;
108
+ let _nativeBindingError = null;
109
+ async function _loadAsyncEntry() {
110
+ if (_asyncEntryCtor)
111
+ return _asyncEntryCtor;
112
+ if (_nativeBindingError)
113
+ throw _nativeBindingError;
114
+ try {
115
+ const mod = await import("@napi-rs/keyring");
116
+ _asyncEntryCtor = mod.AsyncEntry;
117
+ return _asyncEntryCtor;
118
+ }
119
+ catch (err) {
120
+ _nativeBindingError = err;
121
+ throw err;
122
+ }
123
+ }
124
+ /**
125
+ * Did the native binding fail to LOAD (as opposed to an operation failing)?
126
+ *
127
+ * A load failure is deterministic and platform-wide: no retry, no unlock, no
128
+ * amount of waiting brings the keyring back in this process. So it must NOT be
129
+ * treated like a transiently locked Keychain — on macOS/Windows that would
130
+ * throw `KeyringUnavailableError` and leave the user with no working path at
131
+ * all. The file-identity fallback (with its loud warning) is the only usable
132
+ * route here, exactly as on headless Linux.
133
+ */
134
+ function _nativeBindingUnavailable() {
135
+ return _nativeBindingError !== null;
136
+ }
137
+ /** Test-only: force (or clear with `null`) a memoized binding-load failure, so
138
+ * the Bun/no-native-binding branch is reachable without a Bun host. */
139
+ export function _setNativeBindingErrorForTest(err) {
140
+ _asyncEntryCtor = null;
141
+ _nativeBindingError = err;
142
+ }
143
+ class NapiKeyringBackend {
144
+ async read(service, account) {
145
+ const AsyncEntry = await _loadAsyncEntry();
146
+ const entry = new AsyncEntry(service, account);
147
+ return _withTimeout(entry.getPassword(), `read(${service})`); // undefined on no-entry
148
+ }
149
+ async write(service, account, value) {
150
+ const AsyncEntry = await _loadAsyncEntry();
151
+ const entry = new AsyncEntry(service, account);
152
+ await _withTimeout(entry.setPassword(value), `write(${service})`);
153
+ }
154
+ async delete(service, account) {
155
+ let entry;
156
+ try {
157
+ const AsyncEntry = await _loadAsyncEntry();
158
+ entry = new AsyncEntry(service, account);
159
+ }
160
+ catch {
161
+ return false;
162
+ }
163
+ try {
164
+ return await _withTimeout(entry.deleteCredential(), `delete(${service})`);
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
170
+ }
171
+ let _backend = null;
172
+ function _getBackend() {
173
+ if (!_backend)
174
+ _backend = new NapiKeyringBackend();
175
+ return _backend;
176
+ }
177
+ /** Test-only: swap (or clear with `null`) the keyring backend. */
178
+ export function _setKeyStoreBackendForTest(backend) {
179
+ _backend = backend;
180
+ }
181
+ /**
182
+ * Is the platform keyring a CORE OS service we should expect to be present?
183
+ * macOS (Keychain) and Windows (Credential Manager) always have one, so a read
184
+ * that throws there is transient/locked, NOT "headless" — we must not mint a
185
+ * new identity. On Linux/other the secret service may be genuinely absent
186
+ * (headless, no D-Bus), so the documented file fallback applies. Overridable
187
+ * for tests via `_setKeyringExpectedForTest`. */
188
+ let _keyringExpectedOverride = null;
189
+ function _keyringExpectedAvailable() {
190
+ if (_keyringExpectedOverride !== null)
191
+ return _keyringExpectedOverride;
192
+ // Binding never loaded (Bun-built pi, issue #113) → there is no keyring on
193
+ // this platform *for this process*, whatever the OS normally offers.
194
+ if (_nativeBindingUnavailable())
195
+ return false;
196
+ return process.platform === "darwin" || process.platform === "win32";
197
+ }
198
+ /** Test-only: force `_keyringExpectedAvailable()` (so a darwin test host can
199
+ * exercise the Linux/headless branch and vice-versa). `null` restores the
200
+ * real platform check. */
201
+ export function _setKeyringExpectedForTest(value) {
202
+ _keyringExpectedOverride = value;
203
+ }
204
+ /** Test-only: shrink retry attempts/delay so the persistent-failure path is
205
+ * fast. `null`/omitted restores defaults. */
206
+ export function _setKeyringRetryForTest(attempts, delayMs) {
207
+ _keyringReadAttempts = attempts ?? 3;
208
+ _keyringRetryDelayMs = delayMs ?? 300;
209
+ }
210
+ function _sleep(ms) {
211
+ return ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
212
+ }
213
+ function _serialize(kp) {
214
+ const payload = {
215
+ pk: Buffer.from(kp.publicKey).toString("base64"),
216
+ sk: Buffer.from(kp.secretKey).toString("base64"),
217
+ };
218
+ return JSON.stringify(payload);
219
+ }
220
+ function _deserialize(stored) {
221
+ const parsed = JSON.parse(stored);
222
+ return {
223
+ publicKey: Buffer.from(parsed.pk, "base64"),
224
+ secretKey: Buffer.from(parsed.sk, "base64"),
225
+ };
226
+ }
227
+ // ── File fallback (headless Linux) ──────────────────────────────────────────
228
+ async function _readKeypairFromFile() {
229
+ try {
230
+ const raw = await readFile(IDENTITY_FILE, "utf8");
231
+ return _deserialize(raw);
232
+ }
233
+ catch {
234
+ return null;
235
+ }
236
+ }
237
+ async function _writeKeypairToFile(kp) {
238
+ await mkdir(PI_DIR, { recursive: true, mode: 0o700 });
239
+ // Best-effort tighten of the dir in case it pre-existed with looser
240
+ // permissions (mkdir's mode is only applied to NEW dirs).
241
+ try {
242
+ await chmod(PI_DIR, 0o700);
243
+ }
244
+ catch {
245
+ /* not fatal */
246
+ }
247
+ await writeFile(IDENTITY_FILE, _serialize(kp), { mode: 0o600 });
248
+ try {
249
+ await chmod(IDENTITY_FILE, 0o600);
250
+ }
251
+ catch {
252
+ /* not fatal */
253
+ }
254
+ }
255
+ // ── Public API ──────────────────────────────────────────────────────────────
256
+ /**
257
+ * Returns the Pi-secret Ed25519 keypair, generating + persisting one on
258
+ * first call. Resolution order:
259
+ * 1. Existing file `~/.pi/un-bien/identity.json`, if present — it WINS over
260
+ * the keyring. A file identity is only ever written by the headless/
261
+ * degraded fallback (step 4) or an explicit `UNBIEN_ALLOW_FILE_IDENTITY`
262
+ * opt-in, so its mere presence means this machine established its identity
263
+ * as a file and the mobile device paired against THAT pubkey. If the
264
+ * platform keyring later becomes readable (D-Bus/libsecret installed, a
265
+ * desktop session, or a stale/other entry from another install), reading
266
+ * it first would mask the file identity — returning a DIFFERENT key, or
267
+ * (when the keyring is empty) minting a fresh one and persisting it —
268
+ * silently breaking the existing pairing. So when both exist, file wins.
269
+ * 2. New keyring service `dev.unbien.pi` (read retried — a transiently
270
+ * locked Keychain throws; we don't treat that as "no key")
271
+ * 3. Old keyring service `dev.unbien.mac` (migrate → step 2, delete old)
272
+ * 4. Generate a fresh keypair, BUT only when it's safe to: either both
273
+ * keyring reads succeeded and returned nothing (genuine first run), or
274
+ * the keyring is genuinely unavailable on a platform without a core one
275
+ * (headless Linux → a file identity is minted here). On macOS/Windows a
276
+ * persistent read failure with no file identity throws
277
+ * `KeyringUnavailableError` instead of minting a new key — generating
278
+ * there silently breaks existing pairing (the "lost pairing after idle"
279
+ * bug). `UNBIEN_ALLOW_FILE_IDENTITY=1` opts back into a file identity
280
+ * for headless macOS/Windows hosts.
281
+ *
282
+ * Idempotent: subsequent calls return the same identity. The migration
283
+ * runs at most once per machine (the old entry is deleted after copy).
284
+ */
285
+ export async function getOrCreateEd25519Keypair() {
286
+ const backend = _getBackend();
287
+ // ── Path 0: an existing file-backed identity wins ──────────────────────
288
+ // `~/.pi/un-bien/identity.json` is only ever written by the headless/degraded
289
+ // fallback below (or an operator who set UNBIEN_ALLOW_FILE_IDENTITY=1) —
290
+ // never on a keyring-backed install. So its presence means THIS machine
291
+ // paired against the file key, and the keyring (readable or not, matching or
292
+ // not) must not be allowed to mask it. Short-circuit before touching the
293
+ // keyring so a keyring that later comes online can't return a different key,
294
+ // nor mint a fresh one over the file identity. No file → normal keyring
295
+ // resolution below; a headless first run still reaches Path B and mints one.
296
+ const existingFile = await _readKeypairFromFile();
297
+ if (existingFile)
298
+ return existingFile;
299
+ // ── Path A: keyring (retried) ──────────────────────────────────────────
300
+ // A throw here means the keyring op FAILED — but on macOS/Windows that is
301
+ // almost always a transiently locked Keychain (idle/just-woke machine), not
302
+ // a missing backend. `read` returns `undefined` for "no such entry" (the
303
+ // genuine first-run signal). So we retry on throw, and only a throw that
304
+ // SURVIVES every attempt drops us to Path B.
305
+ let keyringError;
306
+ for (let attempt = 0; attempt < _keyringReadAttempts; attempt++) {
307
+ try {
308
+ const existing = await backend.read(NEW_SERVICE, ACCOUNT);
309
+ if (existing)
310
+ return _deserialize(existing);
311
+ const legacy = await backend.read(OLD_SERVICE, ACCOUNT);
312
+ if (legacy) {
313
+ const kp = _deserialize(legacy);
314
+ await backend.write(NEW_SERVICE, ACCOUNT, legacy);
315
+ await backend.delete(OLD_SERVICE, ACCOUNT);
316
+ // Silent migration: writing the chat surface would be premature
317
+ // (Pi SDK isn't bound yet at this point in boot) and console
318
+ // output bleeds outside the TUI. The presence of an entry under
319
+ // NEW_SERVICE is itself the audit signal — re-running migration
320
+ // is idempotent and harmless.
321
+ return kp;
322
+ }
323
+ // Both reads SUCCEEDED and returned nothing → genuine first run on a
324
+ // working keyring. Generate and save to the new service.
325
+ const fresh = generateEd25519Keypair();
326
+ await backend.write(NEW_SERVICE, ACCOUNT, _serialize(fresh));
327
+ return fresh;
328
+ }
329
+ catch (err) {
330
+ keyringError = err;
331
+ if (attempt < _keyringReadAttempts - 1) {
332
+ // Linear backoff — a locked Keychain usually frees within seconds.
333
+ await _sleep(_keyringRetryDelayMs * (attempt + 1));
334
+ }
335
+ }
336
+ }
337
+ // ── Path B: keyring threw on every attempt ─────────────────────────────
338
+ // Path 0 already returned any pre-existing file identity; this defensive
339
+ // re-check catches a file written concurrently by another Pi process during
340
+ // the keyring-retry window (still: use it, never regenerate).
341
+ const fromFile = await _readKeypairFromFile();
342
+ if (fromFile)
343
+ return fromFile;
344
+ // No file identity AND the keyring is unreadable. CRITICAL FORK:
345
+ //
346
+ // - On a platform without a guaranteed keyring (headless Linux, no D-Bus),
347
+ // minting a file-backed identity is the documented, correct first-run
348
+ // behavior.
349
+ // - On macOS/Windows the keyring is a core OS service, so a persistent read
350
+ // failure means it's LOCKED/denied — NOT that we're a fresh install.
351
+ // Generating a new key here is exactly what silently broke pairing after
352
+ // a week idle, and the new key then masks the real Keychain identity via
353
+ // the file. So we FAIL LOUD instead, unless the operator explicitly
354
+ // opts into a file identity.
355
+ const forceFile = process.env.UNBIEN_ALLOW_FILE_IDENTITY === "1";
356
+ if (_keyringExpectedAvailable() && !forceFile) {
357
+ throw new KeyringUnavailableError(keyringError);
358
+ }
359
+ // Issues #95 / #69 — minting a NEW identity when devices are already paired
360
+ // is destructive, on every platform.
361
+ //
362
+ // A daemon started by `systemd --user` resolves a different secret-service
363
+ // store than the desktop session that ran the pairing, so the keyring read
364
+ // fails, this path mints a fresh Ed25519 key, and the SelfRevoke poller then
365
+ // finds that key absent from the relay-side owner envelope, concludes it was
366
+ // revoked, and wipes `peers.json` — the phone silently goes offline a few
367
+ // seconds after every `daemon start`. `peers.json` being non-empty is proof
368
+ // that a working identity existed, so "no key found" here is a broken
369
+ // environment, not a first run. Fail loud and keep the pairing intact; the
370
+ // operator can pin the identity to a file (the confirmed workaround) or fix
371
+ // the service's keyring access.
372
+ if (!forceFile) {
373
+ const paired = await listPeers();
374
+ if (paired.length > 0) {
375
+ throw new PairedIdentityMissingError(paired.length, keyringError);
376
+ }
377
+ }
378
+ console.warn(_nativeBindingUnavailable()
379
+ ? // Issue #113: the @napi-rs/keyring native binding could not be loaded at
380
+ // all (typically a Bun-compiled pi). Name it explicitly — the error the
381
+ // loader prints blames npm's optional-dependency bug and sends people off
382
+ // to delete node_modules, which takes their other pi packages with it.
383
+ "[un-bien] @napi-rs/keyring native binding could not be loaded in this " +
384
+ `runtime; using file-backed identity at ${IDENTITY_FILE} (0600) instead. ` +
385
+ "This is expected on a Bun-built pi. Paired devices keyed to a previous " +
386
+ `keyring identity must be re-paired. ${String(keyringError)}`
387
+ : "[un-bien] keyring unavailable; using file-backed identity at " +
388
+ `${IDENTITY_FILE}. ${String(keyringError)}`);
389
+ const fresh = generateEd25519Keypair();
390
+ await _writeKeypairToFile(fresh);
391
+ return fresh;
392
+ }
393
+ export async function listPeers() {
394
+ try {
395
+ const raw = await readFile(PEERS_PATH, "utf8");
396
+ const parsed = JSON.parse(raw);
397
+ return Array.isArray(parsed.peers) ? parsed.peers : [];
398
+ }
399
+ catch {
400
+ return [];
401
+ }
402
+ }
403
+ /**
404
+ * Authoritative container read for SelfRevoke's token path. Public readers
405
+ * intentionally remain best-effort; only a missing file is proof of emptiness
406
+ * here. Valid array elements are returned verbatim for corruption isolation.
407
+ */
408
+ async function _readPeerContainerStrict() {
409
+ let raw;
410
+ try {
411
+ raw = await readFile(PEERS_PATH, "utf8");
412
+ }
413
+ catch (error) {
414
+ if (typeof error === "object" &&
415
+ error !== null &&
416
+ "code" in error &&
417
+ error.code === "ENOENT") {
418
+ return [];
419
+ }
420
+ throw error;
421
+ }
422
+ const parsed = JSON.parse(raw);
423
+ if (!Array.isArray(parsed.peers)) {
424
+ throw new Error("Invalid peers.json container");
425
+ }
426
+ return parsed.peers;
427
+ }
428
+ let _peerMutationQueue = Promise.resolve();
429
+ const _ownerSlotTokens = new Map();
430
+ const _ownerStorageTokenBrand = Symbol("owner-storage-token");
431
+ function _ownerSlotKey(rawOwnerPubkey) {
432
+ if (typeof rawOwnerPubkey !== "string") {
433
+ // Invalid non-string records remain in snapshots, but SelfRevoke skips
434
+ // them before conditional removal; quarantine-key collisions cannot
435
+ // authorize a removal.
436
+ return `raw:quarantine:${typeof rawOwnerPubkey}`;
437
+ }
438
+ try {
439
+ return `owner:${canonicalizeEd25519PublicKey(rawOwnerPubkey, "Owner record")}`;
440
+ }
441
+ catch {
442
+ return `raw:string:${rawOwnerPubkey}`;
443
+ }
444
+ }
445
+ function _tokenForSlot(slot) {
446
+ const existing = _ownerSlotTokens.get(slot);
447
+ if (existing)
448
+ return existing;
449
+ const token = Object.freeze({
450
+ [_ownerStorageTokenBrand]: true,
451
+ });
452
+ _ownerSlotTokens.set(slot, token);
453
+ return token;
454
+ }
455
+ function _invalidateOwnerSlot(rawOwnerPubkey) {
456
+ const slot = _ownerSlotKey(rawOwnerPubkey);
457
+ const token = Object.freeze({
458
+ [_ownerStorageTokenBrand]: true,
459
+ });
460
+ _ownerSlotTokens.set(slot, token);
461
+ return token;
462
+ }
463
+ function _serializePeerMutation(mutation) {
464
+ const result = _peerMutationQueue.then(mutation, mutation);
465
+ _peerMutationQueue = result.then(() => undefined, () => undefined);
466
+ return result;
467
+ }
468
+ export function addPeer(record) {
469
+ return _serializePeerMutation(async () => {
470
+ const peers = (await listPeers());
471
+ const idx = peers.findIndex((peer) => !!peer &&
472
+ typeof peer === "object" &&
473
+ peer.remote_epk === record.remote_epk);
474
+ if (idx >= 0) {
475
+ peers[idx] = record; // idempotent re-pair
476
+ }
477
+ else {
478
+ peers.push(record);
479
+ }
480
+ await mkdir(dirname(PEERS_PATH), { recursive: true });
481
+ await writeFile(PEERS_PATH, JSON.stringify({ peers }, null, 2));
482
+ // A successful re-pair is a new storage provenance event even when the
483
+ // record bytes happen to be identical.
484
+ _invalidateOwnerSlot(record.remote_epk);
485
+ });
486
+ }
487
+ /**
488
+ * Returns the set of distinct `remote_epk` values in peers.json.
489
+ *
490
+ * In the current pairing model (plan/23 + plan/24), each `remote_epk` is the
491
+ * Owner's Ed25519 pubkey — and we treat each as a distinct Owner the Pi has
492
+ * been paired with. Used by the mesh self-revoke poller (plan/24 Wave 3) to
493
+ * know which Owners' mesh blobs to fetch.
494
+ */
495
+ export async function listOwnerPubkeys() {
496
+ const peers = (await listPeers());
497
+ const seen = new Set();
498
+ for (const peer of peers) {
499
+ if (!peer || typeof peer !== "object") {
500
+ seen.add(peer);
501
+ continue;
502
+ }
503
+ seen.add(peer.remote_epk);
504
+ }
505
+ return [...seen];
506
+ }
507
+ /**
508
+ * Atomically snapshots raw Owner handles and their canonical-slot provenance.
509
+ * The token is deliberately process-local and opaque to callers.
510
+ */
511
+ export function snapshotOwnerPubkeys() {
512
+ return _serializePeerMutation(async () => {
513
+ const peers = await _readPeerContainerStrict();
514
+ const rawOwners = new Set();
515
+ for (const peer of peers) {
516
+ if (!peer || typeof peer !== "object") {
517
+ rawOwners.add(peer);
518
+ }
519
+ else {
520
+ rawOwners.add(peer.remote_epk);
521
+ }
522
+ }
523
+ return [...rawOwners].map((rawOwnerPubkey) => ({
524
+ rawOwnerPubkey,
525
+ token: _tokenForSlot(_ownerSlotKey(rawOwnerPubkey)),
526
+ }));
527
+ });
528
+ }
529
+ /**
530
+ * Removes one exact raw handle only when its snapshot provenance still owns
531
+ * the target canonical Owner slot. The final authority/token checks and sync
532
+ * write share the existing serialized mutation lane.
533
+ */
534
+ export function conditionalRemovePeer(remoteEpk, expectedToken, canCommit) {
535
+ return _serializePeerMutation(async () => {
536
+ const slot = _ownerSlotKey(remoteEpk);
537
+ // Provenance belongs to the canonical Owner slot, not the exact raw
538
+ // spelling. A stale slot must therefore win over an absent old spelling.
539
+ if (_tokenForSlot(slot) !== expectedToken)
540
+ return { outcome: "stale" };
541
+ const peers = await _readPeerContainerStrict();
542
+ const filtered = peers.filter((peer) => !peer ||
543
+ typeof peer !== "object" ||
544
+ peer.remote_epk !== remoteEpk);
545
+ if (filtered.length === peers.length)
546
+ return { outcome: "not_found" };
547
+ await mkdir(dirname(PEERS_PATH), { recursive: true });
548
+ // No await may intervene between the final token/authority checks and
549
+ // synchronous write, preserving the lane's fail-closed commit boundary.
550
+ if (_tokenForSlot(slot) !== expectedToken)
551
+ return { outcome: "stale" };
552
+ if (canCommit) {
553
+ let authorized = false;
554
+ try {
555
+ authorized = canCommit();
556
+ }
557
+ catch {
558
+ return { outcome: "no_authority" };
559
+ }
560
+ if (!authorized)
561
+ return { outcome: "no_authority" };
562
+ }
563
+ writeFileSync(PEERS_PATH, JSON.stringify({ peers: filtered }, null, 2));
564
+ return { outcome: "removed", nextToken: _invalidateOwnerSlot(remoteEpk) };
565
+ });
566
+ }
567
+ export function removePeer(remoteEpk, canCommit) {
568
+ return _serializePeerMutation(async () => {
569
+ const peers = (await listPeers());
570
+ const filtered = peers.filter((peer) => !peer ||
571
+ typeof peer !== "object" ||
572
+ peer.remote_epk !== remoteEpk);
573
+ if (filtered.length === peers.length)
574
+ return false;
575
+ await mkdir(dirname(PEERS_PATH), { recursive: true });
576
+ const serialized = JSON.stringify({ peers: filtered }, null, 2);
577
+ if (canCommit) {
578
+ // Guarded SelfRevoke commits must be atomic with their final authority
579
+ // check at the JavaScript level: fail closed on false/throw, then perform
580
+ // the tiny JSON rewrite synchronously with no interruptible await between.
581
+ let authorized = false;
582
+ try {
583
+ authorized = canCommit();
584
+ }
585
+ catch {
586
+ return false;
587
+ }
588
+ if (!authorized)
589
+ return false;
590
+ writeFileSync(PEERS_PATH, serialized);
591
+ }
592
+ else {
593
+ // Manual removals keep the established asynchronous storage behavior.
594
+ await writeFile(PEERS_PATH, serialized);
595
+ }
596
+ const removed = true;
597
+ if (removed)
598
+ _invalidateOwnerSlot(remoteEpk);
599
+ return removed;
600
+ });
601
+ }
602
+ // ── Test-only helpers ────────────────────────────────────────────────────────
603
+ /** Test-only: expose the identity-file path so tests can clean it. */
604
+ export const _IDENTITY_FILE_FOR_TEST = IDENTITY_FILE;
605
+ /** Test-only: expose unlink for cleanup. */
606
+ export const _unlinkIdentityFileForTest = async () => {
607
+ try {
608
+ await unlink(IDENTITY_FILE);
609
+ }
610
+ catch {
611
+ /* fine if missing */
612
+ }
613
+ };
614
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/pairing/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { ServerMessage } from "./protocol/types.js";
3
+ export interface PanelBridge {
4
+ /** Current panels as `panel_update` frames, for `session_sync` to replay to a
5
+ * late-joining peer (mirrors the ui bridge's `pendingRequests`). */
6
+ pendingPanels(): ServerMessage[];
7
+ /** Drop subscriptions, timers, and state. */
8
+ dispose(): void;
9
+ }
10
+ /**
11
+ * Subscribe the plan + subagents buses and broadcast `panel_update` frames.
12
+ * Returns `null` when the SDK exposes no usable events bus (defensive — modern
13
+ * Pi always has one); callers stay null-safe.
14
+ */
15
+ export declare function createPanelBridge(pi: ExtensionAPI, broadcast: (msg: ServerMessage) => void): PanelBridge | null;