@log0u7/opencode-mesh 0.2.0 → 0.4.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 (57) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.md +46 -13
  3. package/dist/lib/client.d.ts +2 -0
  4. package/dist/lib/client.d.ts.map +1 -1
  5. package/dist/lib/client.js +6 -1
  6. package/dist/lib/client.js.map +1 -1
  7. package/dist/lib/db.d.ts.map +1 -1
  8. package/dist/lib/db.js +26 -1
  9. package/dist/lib/db.js.map +1 -1
  10. package/dist/lib/discovery.d.ts +3 -0
  11. package/dist/lib/discovery.d.ts.map +1 -1
  12. package/dist/lib/discovery.js +33 -7
  13. package/dist/lib/discovery.js.map +1 -1
  14. package/dist/lib/identity.d.ts +10 -8
  15. package/dist/lib/identity.d.ts.map +1 -1
  16. package/dist/lib/identity.js +132 -32
  17. package/dist/lib/identity.js.map +1 -1
  18. package/dist/lib/locks.d.ts.map +1 -1
  19. package/dist/lib/locks.js +20 -12
  20. package/dist/lib/locks.js.map +1 -1
  21. package/dist/lib/peers.d.ts +13 -1
  22. package/dist/lib/peers.d.ts.map +1 -1
  23. package/dist/lib/peers.js +114 -18
  24. package/dist/lib/peers.js.map +1 -1
  25. package/dist/lib/serve.d.ts +2 -0
  26. package/dist/lib/serve.d.ts.map +1 -1
  27. package/dist/lib/serve.js +62 -71
  28. package/dist/lib/serve.js.map +1 -1
  29. package/dist/lib/spawn.d.ts +14 -4
  30. package/dist/lib/spawn.d.ts.map +1 -1
  31. package/dist/lib/spawn.js +210 -21
  32. package/dist/lib/spawn.js.map +1 -1
  33. package/dist/lib/sqlite.d.ts.map +1 -1
  34. package/dist/lib/sqlite.js +15 -4
  35. package/dist/lib/sqlite.js.map +1 -1
  36. package/dist/lib/workers.d.ts +2 -0
  37. package/dist/lib/workers.d.ts.map +1 -1
  38. package/dist/lib/workers.js +45 -12
  39. package/dist/lib/workers.js.map +1 -1
  40. package/dist/lib/worktree.d.ts +22 -4
  41. package/dist/lib/worktree.d.ts.map +1 -1
  42. package/dist/lib/worktree.js +54 -6
  43. package/dist/lib/worktree.js.map +1 -1
  44. package/dist/plugin-v2.d.ts.map +1 -1
  45. package/dist/plugin-v2.js +65 -337
  46. package/dist/plugin-v2.js.map +1 -1
  47. package/dist/plugin.d.ts +2 -6
  48. package/dist/plugin.d.ts.map +1 -1
  49. package/dist/plugin.js +119 -265
  50. package/dist/plugin.js.map +1 -1
  51. package/dist/tools.d.ts +30 -0
  52. package/dist/tools.d.ts.map +1 -0
  53. package/dist/tools.js +315 -0
  54. package/dist/tools.js.map +1 -0
  55. package/dist/types/peer.d.ts +2 -0
  56. package/dist/types/peer.d.ts.map +1 -1
  57. package/package.json +10 -8
package/CHANGELOG.md CHANGED
@@ -7,6 +7,156 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-10-14
11
+
12
+ ### Added
13
+
14
+ - Signed pairing offers close the pending-token rebind window (Closes #30).
15
+ Each node keeps an Ed25519 keypair in `node.json` (generated on first run or
16
+ on migration; the `node_id` is preserved). Pairing offers carry the emitter's
17
+ public key (`pk`), completing a pairing pins it, mDNS TXT advertises it, and
18
+ every outgoing mail carries a pairing proof (`<pk>.<signature>` over
19
+ `opencode-mesh-pair:<node_id>:<token>`). A pending token now binds (or
20
+ rotates) a peer row only with a valid proof from the pinned key: a leaked
21
+ offer alone can no longer take over a paired peer's credential. Peers paired
22
+ without a pinned key (0.3.0-era rows, or completions without `pk`) refuse
23
+ remote rebinding: re-run `mesh_pair` to rotate them. `peers` gains a
24
+ `public_key` column (schema version 2, migrated in place).
25
+
26
+ ### Fixed
27
+
28
+ - Peer addresses in the IPv4-mapped IPv6 form (`[::ffff:192.168.1.10]:4399`)
29
+ are accepted, so a dual-stack resolver's reported address no longer leaves
30
+ its peer unreachable ("no known address with a port"). Portless and
31
+ empty-zone forms stay rejected.
32
+
33
+ ## [0.3.0] - 2026-10-14
34
+
35
+ ### Added
36
+
37
+ - Documentation: `docs/CONFIGURATION.md` (the full options reference), and new
38
+ architecture and wire protocol docs detailing the process model, worker
39
+ lifecycle, data-dir layout, and the HTTP/mDNS/pairing protocol. The README
40
+ links them and documents the plugin options inline.
41
+ - Plugin options are validated at setup. A `port` outside `[0, 65535]` or not an
42
+ integer, a `bind` other than `loopback`/`lan`, a non-boolean `advertise`, or an
43
+ empty/non-string `dataDir` now fails fast with a clear message instead of
44
+ surfacing later as `EADDRINUSE`, a bind on the wrong interface, or a data dir
45
+ at the wrong path. `tsconfig.test.json` and a `pnpm typecheck:tests` step
46
+ typecheck the test suite, which `tsconfig.json` (src only) never did.
47
+
48
+ ### Changed
49
+
50
+ - `engines.node` is now `>=22.5.0` (the first release with `node:sqlite`), and
51
+ `@types/node` is pinned to the max Node major tested in CI (24) instead of 26.
52
+ `scripts/smoke-dist.mjs` now asserts the v2 (`setup`) entrypoint, and
53
+ `biome.json` is migrated to the 2.5.x schema.
54
+ - `mesh_lock` is documented and advertised as local to this machine: it is
55
+ enforced between sessions sharing this host's data directory and is not part
56
+ of the wire protocol. A guard test fails the build if a lock route reappears
57
+ on the server.
58
+ - Removed the Ed25519 keypair ceremony in node identity: nothing signed or
59
+ verified with it, so the short fingerprint is derived from `node_id`.
60
+ Existing node ids are preserved on upgrade; the fingerprint value changes,
61
+ which may require re-pairing peers.
62
+ - Workers spawned by `mesh_spawn` run with `OPENCODE_MESH_CHILD=1` and skip the
63
+ server, mDNS and all mesh tools, so a pool of N workers no longer costs N
64
+ mesh nodes.
65
+ - `mesh_worker_logs` reads only the tail of a worker log instead of loading the
66
+ whole file on the host's event loop; `mesh_status` caches `tailscale status`
67
+ for 60s and bounds the subprocess with a 2s timeout.
68
+
69
+ ### Fixed
70
+
71
+ - The docs no longer claim security behaviour the code does not implement:
72
+ `SECURITY.md` drops the "refuses entries that look like API-key formats"
73
+ promise (no such filtering exists) and the umask-077 claim (the data
74
+ *directory* is created `0700`; the SQLite file itself is not `chmod`ed), and
75
+ the README drops the non-existent `"allow_tailnet"` option while updating its
76
+ stale install pin.
77
+ - `mesh_pair` now rejects an empty `node_id` or `token` instead of completing a
78
+ pairing with a credential nobody can present (or that clobbers a peer record).
79
+ A partially-supplied pair (`node_id` without `token`, or vice versa) is an
80
+ error rather than being silently ignored.
81
+ - The mesh server reports a body read failure as `400` (malformed request) rather
82
+ than `413` (too large): a client that drops the connection mid-body no longer
83
+ looks like an oversized payload.
84
+ - Peer addresses that carry an IPv6 zone id (`[fe80::1%eth0]:4399`) are accepted,
85
+ so a link-local peer is no longer silently unreachable.
86
+ - `defaultDataDir()` no longer resolves to a filesystem-root path
87
+ (`/.local/share/opencode-mesh`) when `HOME` is unset or blank: it falls back to
88
+ `os.homedir()`, then `os.tmpdir()`.
89
+ - The node identity migration now finishes on disk: a `node.json` written by
90
+ <= 0.2.0 is rewritten without its dead Ed25519 keypair, so the stale key
91
+ material no longer lingers after the id is loaded. A corrupt or `node_id`-less
92
+ `node.json` now fails loudly with a recovery path instead of silently minting a
93
+ new node id, which would break every peer that recognised the node. Closes #31.
94
+ - `mesh_lock` no longer throws a raw `UNIQUE constraint failed` error when two
95
+ opencode sessions sharing the same data directory race for the same path. The
96
+ lock claim is a single atomic `INSERT ... ON CONFLICT DO NOTHING`, and the
97
+ worker status transition is a single guarded `UPDATE`, so a stale
98
+ read-then-write can neither raise nor overwrite a concurrently finalized row.
99
+ - The mesh database now records a schema version (`PRAGMA user_version`) and
100
+ creates indexes for its hot lookups (unread messages, pending pairing tokens,
101
+ peer tokens, running workers, expired locks).
102
+ - On OpenCode 2.x hosts, `mesh_spawn` and `mesh_worker_remove` no longer hand the
103
+ raw `ctx.worktree` domain to the REST-shaped worktree client. The in-process
104
+ domain (which resolves payloads directly, without the v1 `{ data, error }`
105
+ envelope) is wrapped back into that shape, and an injected
106
+ `options.worktreeClient` now takes precedence. Closes #29.
107
+ - Worktree create/remove calls on the 1.x REST client are bounded by a 15s
108
+ timeout. A create response with no directory, or a remove response that is
109
+ not a boolean, now raises a clear error instead of persisting an `undefined`
110
+ directory. When the 2.x domain reports only a directory (no branch), the
111
+ worker's branch falls back to that directory's name.
112
+ - A spawn that fails before the child runs no longer leaks its worktree:
113
+ `mesh_spawn` removes the worktree and the dead registry row when the child
114
+ never starts or `runWorker` throws. `mesh_worker_remove` keeps the registry
115
+ row when the worktree was not actually removed, instead of orphaning it.
116
+ - Node runtime support: the published package no longer calls `require()` from
117
+ ESM modules. The Node fallback paths (`node:sqlite` engine, `node:crypto`)
118
+ now use static and dynamic imports and actually run. Vitest's module runner
119
+ injects a `require`, which hid the `ReferenceError` from the suite, so
120
+ `scripts/smoke-dist.mjs` loads the built package under plain Node and is part
121
+ of `pnpm verify`.
122
+ - `mesh_send` no longer derives the peer's port from the local node. Discovered
123
+ addresses now carry the port from the mDNS SRV record (`[ipv6]:port` for IPv6
124
+ literals), and a portless address is refused instead of resolving to a wrong
125
+ socket. Peers that fall back to an ephemeral port are now reachable.
126
+ - Workers killed through `mesh_worker_stop` no longer orphan their own child
127
+ processes: workers are detached process-group leaders and a stop takes the
128
+ group down. The late `exit` event after a deliberate stop no longer throws;
129
+ a host restart reconciles `running` rows whose pid is gone.
130
+ - A worker that fails to start (binary missing, `error` event with no `exit`)
131
+ is now finalized as `failed` instead of staying `running` forever with a
132
+ leaked live entry; a `-1` pid is never persisted. `mesh_spawn` reports the
133
+ failure instead of a success envelope.
134
+ - A worker's stderr is drained into its log; a child writing more than the pipe
135
+ buffer (~64 KB) to stderr no longer blocks. `mesh_worker_stop` escalates
136
+ `SIGTERM` to `SIGKILL` after 3s so a worker ignoring the signal cannot linger.
137
+ - Worker logs are capped at 512 KB (rotated to `<id>.log.1`) and deleted by
138
+ `mesh_worker_remove`, so a chatty worker cannot fill the disk.
139
+ - A JSON line split across two stdout chunks is now reassembled before parsing,
140
+ so a worker's `session_id` is captured instead of silently dropping to null.
141
+ A session id arriving after the row is terminal is ignored.
142
+
143
+ ### Security
144
+
145
+ - The mesh HTTP server binds to `127.0.0.1` by default. `"bind": "lan"` opts
146
+ into listening on all interfaces, and `mesh_status` / the `mesh_pair` offer
147
+ report the address actually bound instead of a hardcoded loopback string.
148
+ - Pairing offers expire after 5 minutes, are pruned on check, and a pending
149
+ token alone no longer installs a permanent peer: the claimed node must
150
+ already be known to this host (mDNS discovery or explicit `mesh_pair`).
151
+ Auto-pairing a previously unknown node id is gone.
152
+ - Discovered peers keep their hostname and fingerprint through pairing instead
153
+ of collapsing to `"pending"`, so `mesh_status` still shows a real identity.
154
+
155
+ ### Removed
156
+
157
+ - `/announce` endpoint, its parser and `touchAddress` (no client ever called
158
+ the route).
159
+
10
160
  ## [0.2.0] - 2026-10-12
11
161
 
12
162
  ### Added
package/README.md CHANGED
@@ -10,6 +10,8 @@ Coordination for [OpenCode](https://opencode.ai): multiple sessions working toge
10
10
 
11
11
  Each node advertises itself over mDNS, pairs with explicit operator approval (per-peer bearer tokens, deny by default), then sessions can exchange messages (`mesh_send` / `mesh_inbox`) and coordinate on shared work (`mesh_lock` / `mesh_unlock`). Tailscale IPs are picked up automatically when `tailscale status` works, for cross-subnet pools.
12
12
 
13
+ `mesh_lock` is **local to your machine**: it is enforced between OpenCode sessions sharing this host's data directory (`~/.local/share/opencode-mesh/`). Locks are not part of the wire protocol, so a peer on another node does not see or respect them. Remote nodes coordinate through messages (`mesh_send`), not locks.
14
+
13
15
  ## Install
14
16
 
15
17
  Register the plugin in `opencode.json` (project or global `~/.config/opencode/opencode.json`):
@@ -24,12 +26,36 @@ Restart OpenCode. The package is installed automatically at startup into opencod
24
26
 
25
27
  ```json
26
28
  {
27
- "plugin": ["@log0u7/opencode-mesh@0.1.1"]
29
+ "plugin": ["@log0u7/opencode-mesh@0.4.0"]
30
+ }
31
+ ```
32
+
33
+ ### Configuration
34
+
35
+ All options are optional; see [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for the reference.
36
+
37
+ On OpenCode 1.x hosts, options travel in a package-and-options tuple:
38
+
39
+ ```json
40
+ {
41
+ "plugin": [["@log0u7/opencode-mesh", { "port": 4399, "bind": "loopback", "advertise": true }]]
42
+ }
43
+ ```
44
+
45
+ On 2.x hosts the key is `plugins` and each entry is an object:
46
+
47
+ ```json
48
+ {
49
+ "plugins": [
50
+ { "package": "@log0u7/opencode-mesh", "options": { "port": 4399, "bind": "lan" } }
51
+ ]
28
52
  }
29
53
  ```
30
54
 
31
55
  Node identity and mailbox live under `~/.local/share/opencode-mesh/` (honors `XDG_DATA_HOME`), created with restrictive permissions.
32
56
 
57
+ Workers spawned with `mesh_spawn` run with `OPENCODE_MESH_CHILD=1`: they skip the mesh server, mDNS and all mesh tools, so a pool of N workers costs N worktrees, not N mesh nodes.
58
+
33
59
  Note (npm 12): the package ships no lifecycle install scripts and needs no `allowScripts` / allowlist on install (its runtime dependency `bonjour-service` has none either).
34
60
 
35
61
  ## Usage
@@ -38,10 +64,10 @@ Same machine: start two OpenCode instances in the same repository.
38
64
 
39
65
  ```
40
66
  mesh_status() # local sessions + paired peers + active locks
41
- mesh_pair() # list discovered peers, approve pairing
67
+ mesh_pair() # emit a pairing offer (5 min) / complete the peer's offer
42
68
  mesh_send(peer="laptop", subject="handoff", body="...")
43
69
  mesh_inbox() # pull new messages, ack
44
- mesh_lock(path="src/api.ts", ttl=300) # claim a file before editing
70
+ mesh_lock(path="src/api.ts", ttl=300) # claim a file before editing (this machine only)
45
71
  mesh_unlock(path="src/api.ts")
46
72
  ```
47
73
 
@@ -50,10 +76,10 @@ mesh_unlock(path="src/api.ts")
50
76
  | Tool | Args | Description |
51
77
  |---|---|---|
52
78
  | `mesh_status` | : local session registry, own address, paired peers, active locks. |
53
- | `mesh_pair` | `query?` | List discovered peers; operator approves by hostname/fingerprint. |
79
+ | `mesh_pair` | `node_id?`, `token?`, `address?` | No args: emit a pairing offer (5 min ttl). With args: complete the peer's offer. |
54
80
  | `mesh_send` | `peer`, `subject`, `body` | Deliver a message to a peer's mailbox. |
55
81
  | `mesh_inbox` | `ack?` | Fetch unread messages; ack marks them read. |
56
- | `mesh_lock` | `path`, `ttl?` | Exclusive claim on a file or worktree path (default ttl 600s). |
82
+ | `mesh_lock` | `path`, `ttl?` | Exclusive claim on a file or path, enforced only between sessions on this machine (default ttl 600s). Not visible to remote peers. |
57
83
  | `mesh_unlock` | `path` | Release a claim. |
58
84
 
59
85
  ### Orchestrator
@@ -72,22 +98,29 @@ Workers run `opencode run --dir <worktree> --format json` headless with `--auto`
72
98
 
73
99
  ### Security model
74
100
 
75
- - Every HTTP endpoint requires a per-peer bearer token; nothing is open by default.
76
- - All peers, same machine or remote, pair through `mesh_pair`: one side emits a one-time token, the other completes the pairing with it.
77
- - mDNS TXT records carry node id + key fingerprint only, never secrets.
78
- - LAN-only by default; tailnet addresses are surfaced by `mesh_status` when `tailscale` runs, for cross-subnet pools (`"allow_tailnet": true` plugin option, planned).
101
+ - The HTTP mesh server binds to `127.0.0.1` by default. Set `"bind": "lan"` in the plugin options to listen on all interfaces for a LAN pool.
102
+ - Every endpoint requires a per-peer bearer token; nothing is open by default.
103
+ - Pairing offers expire after 5 minutes. A pending token alone grants nothing: the claimed node must already be known to this host (discovered over mDNS, or paired out of band with `mesh_pair`). Both sides run `mesh_pair` to complete a pairing, so a leaked offer cannot install a permanent peer on its own.
104
+ - Peer addresses always carry the advertised port; a portless address is refused instead of being completed with the local port.
105
+ - mDNS TXT records carry node id + fingerprint only, never secrets.
106
+ - Tailnet addresses are surfaced by `mesh_status` when `tailscale` runs, for cross-subnet pools.
107
+ - Messages travel over plain HTTP: there is no TLS layer. Loopback and trusted networks only.
79
108
 
80
109
  ## Development
81
110
 
82
- Requires Node >= 22 (mise: `mise install`) and pnpm (corepack).
111
+ Requires Node >= 22.5 (`mise install`) and pnpm (corepack).
83
112
 
84
113
  ```sh
85
114
  pnpm install
86
- pnpm verify # biome + typecheck + vitest
87
- pnpm build:check # tsc build + pack dry-run
115
+ pnpm verify # biome + typecheck + vitest + Node smoke of dist/
116
+ pnpm typecheck:tests # typecheck the test suite (also enforced in CI)
117
+ pnpm build:check # tsc build + pack dry-run
118
+ pnpm smoke:dist # build, then load dist/ under plain Node (no vitest shims)
88
119
  ```
89
120
 
90
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow.
121
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow, and the [docs/](docs/) folder for
122
+ [configuration](docs/CONFIGURATION.md), [architecture](docs/ARCHITECTURE.md), and the
123
+ wire [protocol](docs/PROTOCOL.md).
91
124
 
92
125
  ## License
93
126
 
@@ -4,6 +4,8 @@ export type SendTarget = {
4
4
  from: string;
5
5
  subject: string;
6
6
  body: string;
7
+ /** `<base64 spki>.<base64 ed25519 signature>` over node_id:token. */
8
+ proof?: string;
7
9
  };
8
10
  export type SendResult = "ok" | "unreachable" | "unauthorized" | "error";
9
11
  export declare function sendMail(target: SendTarget): Promise<SendResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;AAIzE,wBAAsB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAqBtE"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;AAIzE,wBAAsB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BtE"}
@@ -8,7 +8,12 @@ export async function sendMail(target) {
8
8
  "content-type": "application/json",
9
9
  authorization: `Bearer ${target.token}`,
10
10
  },
11
- body: JSON.stringify({ from: target.from, subject: target.subject, body: target.body }),
11
+ body: JSON.stringify({
12
+ from: target.from,
13
+ subject: target.subject,
14
+ body: target.body,
15
+ ...(target.proof !== undefined ? { proof: target.proof } : {}),
16
+ }),
12
17
  signal: AbortSignal.timeout(5000),
13
18
  });
14
19
  if (response.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAUA,8EAA8E;AAC9E,gCAAgC;AAChC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAkB;IAC/C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,OAAO,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,MAAM,CAAC,KAAK,EAAE;aACxC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;YACvF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAYA,8EAA8E;AAC9E,gCAAgC;AAChC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAkB;IAC/C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,GAAG,OAAO,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,MAAM,CAAC,KAAK,EAAE;aACxC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/D,CAAC;YACF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AA4CF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW/C"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAuEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW/C"}
package/dist/lib/db.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { openSqlite } from "./sqlite.js";
4
+ // Bump when a schema change needs a migration step; the version anchors future
5
+ // migrations that cannot be expressed as idempotent `CREATE ... IF NOT EXISTS`.
6
+ const SCHEMA_VERSION = 2;
4
7
  const SCHEMA = `
5
8
  CREATE TABLE IF NOT EXISTS peers (
6
9
  node_id TEXT PRIMARY KEY,
@@ -9,6 +12,7 @@ CREATE TABLE IF NOT EXISTS peers (
9
12
  addresses TEXT NOT NULL DEFAULT '[]',
10
13
  paired INTEGER NOT NULL DEFAULT 0,
11
14
  token TEXT,
15
+ public_key TEXT,
12
16
  last_seen INTEGER NOT NULL
13
17
  );
14
18
  CREATE TABLE IF NOT EXISTS messages (
@@ -41,7 +45,28 @@ CREATE TABLE IF NOT EXISTS workers (
41
45
  started_at INTEGER NOT NULL,
42
46
  finished_at INTEGER
43
47
  );
48
+ CREATE INDEX IF NOT EXISTS idx_messages_status_created ON messages(status, created_at);
49
+ CREATE INDEX IF NOT EXISTS idx_pending_tokens_created ON pending_tokens(created_at);
50
+ CREATE INDEX IF NOT EXISTS idx_peers_token ON peers(token);
51
+ CREATE INDEX IF NOT EXISTS idx_workers_status ON workers(status);
52
+ CREATE INDEX IF NOT EXISTS idx_locks_expires ON locks(expires_at);
44
53
  `;
54
+ function migrate(conn) {
55
+ const current = conn.get("PRAGMA user_version")?.user_version ?? 0;
56
+ if (current >= SCHEMA_VERSION) {
57
+ return;
58
+ }
59
+ // Every statement in SCHEMA is idempotent, so a version-0 database (fresh or
60
+ // pre-versioning) is brought up to date by re-running it.
61
+ conn.exec(SCHEMA);
62
+ // v2: pairing proofs are verified against a pinned public key, so peers gain
63
+ // the column. ALTER TABLE is not idempotent: guard it on the live schema.
64
+ const peerColumns = conn.all("PRAGMA table_info(peers)").map((row) => row.name);
65
+ if (!peerColumns.includes("public_key")) {
66
+ conn.exec("ALTER TABLE peers ADD COLUMN public_key TEXT");
67
+ }
68
+ conn.run(`PRAGMA user_version = ${SCHEMA_VERSION}`);
69
+ }
45
70
  export function openMeshDb(path) {
46
71
  if (path !== ":memory:") {
47
72
  mkdirSync(dirname(path), { recursive: true });
@@ -49,7 +74,7 @@ export function openMeshDb(path) {
49
74
  const conn = openSqlite(path);
50
75
  conn.run("PRAGMA journal_mode = WAL");
51
76
  conn.run("PRAGMA busy_timeout = 5000");
52
- conn.exec(SCHEMA);
77
+ migrate(conn);
53
78
  return { conn, close: () => conn.close() };
54
79
  }
55
80
  //# sourceMappingURL=db.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAO1D,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAElB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAO1D,+EAA+E;AAC/E,gFAAgF;AAChF,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Cd,CAAC;AAEF,SAAS,OAAO,CAAC,IAAgB;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAA2B,qBAAqB,CAAC,EAAE,YAAY,IAAI,CAAC,CAAC;IAC7F,IAAI,OAAO,IAAI,cAAc,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,6EAA6E;IAC7E,0DAA0D;IAC1D,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClB,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAmB,0BAA0B,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,yBAAyB,cAAc,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACvC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;AAC7C,CAAC"}
@@ -8,12 +8,14 @@ export declare function advertiseMesh(options: {
8
8
  port: number;
9
9
  node_id: string;
10
10
  fingerprint: string;
11
+ public_key: string;
11
12
  }): AdvertiseHandle;
12
13
  export type DiscoveredPeer = {
13
14
  node_id: string;
14
15
  hostname: string;
15
16
  fingerprint: string;
16
17
  addresses: string[];
18
+ public_key?: string;
17
19
  };
18
20
  export declare function browseMesh(options: {
19
21
  bonjour: Bonjour;
@@ -21,5 +23,6 @@ export declare function browseMesh(options: {
21
23
  }): {
22
24
  close(): void;
23
25
  };
26
+ export declare function resetTailnetCache(): void;
24
27
  export declare function tailnetAddresses(): Promise<string[]>;
25
28
  //# sourceMappingURL=discovery.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAIF,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,eAAe,CAalB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C,GAAG;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAoBpB;AAID,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAkB1D"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAIF,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,eAAe,CAalB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;CAC9C,GAAG;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAuBpB;AAsBD,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAgC1D"}
@@ -1,12 +1,12 @@
1
1
  export const MESH_SERVICE_TYPE = "opencode-mesh";
2
- // Advertise this node on the LAN. TXT carries only the node id and key
3
- // fingerprint: never secrets.
2
+ // Advertise this node on the LAN. TXT carries the node id, key fingerprint and
3
+ // the pairing public key (pk): never secrets.
4
4
  export function advertiseMesh(options) {
5
5
  const service = options.bonjour.publish({
6
6
  name: options.node_id,
7
7
  type: MESH_SERVICE_TYPE,
8
8
  port: options.port,
9
- txt: { id: options.node_id, fp: options.fingerprint },
9
+ txt: { id: options.node_id, fp: options.fingerprint, pk: options.public_key },
10
10
  });
11
11
  return {
12
12
  close() {
@@ -25,7 +25,10 @@ export function browseMesh(options) {
25
25
  node_id: id,
26
26
  hostname: service.host ?? service.name,
27
27
  fingerprint: fp,
28
- addresses: service.addresses ?? [],
28
+ addresses: socketAddresses(service.addresses, service.port),
29
+ ...(typeof service.txt?.pk === "string" && service.txt.pk.length > 0
30
+ ? { public_key: service.txt.pk }
31
+ : {}),
29
32
  });
30
33
  });
31
34
  return {
@@ -34,12 +37,33 @@ export function browseMesh(options) {
34
37
  },
35
38
  };
36
39
  }
40
+ // An mDNS A/AAAA record is a bare IP: the peer's listening port comes from the
41
+ // SRV record in the same service. Guessing the port from the local node is
42
+ // wrong whenever the peer fell back to an ephemeral port, so a record without
43
+ // a port yields no address at all rather than a wrong one.
44
+ function socketAddresses(ips, port) {
45
+ if (port === undefined) {
46
+ return [];
47
+ }
48
+ return (ips ?? []).map((ip) => (ip.includes(":") ? `[${ip}]:${port}` : `${ip}:${port}`));
49
+ }
37
50
  // Tailnet addresses let peers connect across subnets when tailscale runs.
38
- // Absent CLI = empty list, never an error.
51
+ // Absent CLI = empty list, never an error. The result is cached: mesh_status is
52
+ // a hot tool call and `tailscale status --json` is a subprocess we must not pay
53
+ // on every invocation.
54
+ const TAILNET_TTL_MS = 60_000;
55
+ const TAILNET_TIMEOUT_MS = 2000;
56
+ let tailnetCache = null;
57
+ export function resetTailnetCache() {
58
+ tailnetCache = null;
59
+ }
39
60
  export async function tailnetAddresses() {
61
+ if (tailnetCache !== null && tailnetCache.expiresAt > Date.now()) {
62
+ return tailnetCache.addresses;
63
+ }
40
64
  const { execFile } = await import("node:child_process");
41
- return new Promise((resolve) => {
42
- execFile("tailscale", ["status", "--json"], (error, stdout) => {
65
+ const addresses = await new Promise((resolve) => {
66
+ execFile("tailscale", ["status", "--json"], { timeout: TAILNET_TIMEOUT_MS }, (error, stdout) => {
43
67
  if (error) {
44
68
  resolve([]);
45
69
  return;
@@ -54,5 +78,7 @@ export async function tailnetAddresses() {
54
78
  }
55
79
  });
56
80
  });
81
+ tailnetCache = { addresses, expiresAt: Date.now() + TAILNET_TTL_MS };
82
+ return addresses;
57
83
  }
58
84
  //# sourceMappingURL=discovery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAMjD,uEAAuE;AACvE,8BAA8B;AAC9B,MAAM,UAAU,aAAa,CAAC,OAK7B;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QACtC,IAAI,EAAE,OAAO,CAAC,OAAO;QACrB,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE;KACtD,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;YACH,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AASD,MAAM,UAAU,UAAU,CAAC,OAG1B;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE;QAC5E,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxE,OAAO;QACT,CAAC;QACD,OAAO,CAAC,YAAY,CAAC;YACnB,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;YACtC,WAAW,EAAE,EAAE;YACf,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;YACH,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,2CAA2C;AAC3C,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAExD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAmB,EAAE,MAAc,EAAE,EAAE;YAClF,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,EAAE,CAAC,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA2C,CAAC;gBAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;gBACtC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9F,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/lib/discovery.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAMjD,+EAA+E;AAC/E,8CAA8C;AAC9C,MAAM,UAAU,aAAa,CAAC,OAM7B;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QACtC,IAAI,EAAE,OAAO,CAAC,OAAO;QACrB,IAAI,EAAE,iBAAiB;QACvB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,OAAO,CAAC,UAAU,EAAE;KAC9E,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;YACH,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAUD,MAAM,UAAU,UAAU,CAAC,OAG1B;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,CAAC,OAAO,EAAE,EAAE;QAC5E,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3B,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxE,OAAO;QACT,CAAC;QACD,OAAO,CAAC,YAAY,CAAC;YACnB,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;YACtC,WAAW,EAAE,EAAE;YACf,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC;YAC3D,GAAG,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC;gBAClE,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE;gBAChC,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;YACH,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,2DAA2D;AAC3D,SAAS,eAAe,CAAC,GAAyB,EAAE,IAAwB;IAC1E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,0EAA0E;AAC1E,gFAAgF;AAChF,gFAAgF;AAChF,uBAAuB;AACvB,MAAM,cAAc,GAAG,MAAM,CAAC;AAC9B,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,IAAI,YAAY,GAAsD,IAAI,CAAC;AAE3E,MAAM,UAAU,iBAAiB;IAC/B,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACjE,OAAO,YAAY,CAAC,SAAS,CAAC;IAChC,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAExD,MAAM,SAAS,GAAG,MAAM,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,EAAE;QACxD,QAAQ,CACN,WAAW,EACX,CAAC,QAAQ,EAAE,QAAQ,CAAC,EACpB,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAC/B,CAAC,KAAmB,EAAE,MAAc,EAAE,EAAE;YACtC,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,EAAE,CAAC,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA2C,CAAC;gBAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;gBACtC,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CACnF,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,YAAY,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IACrE,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1,15 +1,17 @@
1
- type Jwk = {
2
- kty?: string;
3
- x?: string;
4
- [key: string]: unknown;
5
- };
6
1
  export type NodeIdentity = {
7
2
  node_id: string;
8
3
  hostname: string;
4
+ public_key: string;
5
+ private_key: string;
9
6
  fingerprint: string;
10
- public_key: Jwk;
11
- private_key: Jwk;
12
7
  };
13
8
  export declare function loadOrCreateIdentity(dataDir: string): NodeIdentity;
14
- export {};
9
+ export declare function pairingProofPayload(nodeId: string, token: string): string;
10
+ export declare function signPairingProof(privateKey: string, payload: string): string;
11
+ export declare function pairingProof(identity: {
12
+ node_id: string;
13
+ public_key: string;
14
+ private_key: string;
15
+ }, token: string): string;
16
+ export declare function verifyPairingProof(publicKey: string, payload: string, proof: string): boolean;
15
17
  //# sourceMappingURL=identity.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/lib/identity.ts"],"names":[],"mappings":"AAKA,KAAK,GAAG,GAAG;IACT,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC;IAChB,WAAW,EAAE,GAAG,CAAC;CAClB,CAAC;AASF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CASlE"}
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/lib/identity.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAoBlE;AA+FD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAO5E;AAGD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EACtE,KAAK,EAAE,MAAM,GACZ,MAAM,CAKR;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAW7F"}