@frod.io/bridge 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 (3) hide show
  1. package/README.md +269 -0
  2. package/dist/cli.js +8257 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # Frod.io bridge — your computer is a desk
2
+
3
+ You already have Claude Code on your computer, with your own sign-in, skills,
4
+ memory and tools. The bridge makes that Claude a **session of the platform**:
5
+ agents you choose run on your computer, take `@mentions` from Teams and
6
+ Telegram, read their Inbox, sit in the Office, run blueprint steps and file
7
+ into the map — governed and metered like every other agent — and nothing of
8
+ yours leaves your machine.
9
+
10
+ ## One command to join
11
+
12
+ On an agent's page in Mission Control, **Join from your computer** gives you a
13
+ one-time code and shows the command to run (the code lasts 15 minutes and
14
+ works once). The bridge is a public package, `@frod.io/bridge` — nobody needs a
15
+ checkout of the platform repository:
16
+
17
+ ```
18
+ npx @frod.io/bridge@latest join <code> --api https://api.agentic.frod.io [--name "Wafeek's Mac"] [--workdir ~/frod-work] [--max-sessions 2] [--no-install]
19
+ ```
20
+
21
+ `npx` only bootstraps the command. `join` itself:
22
+
23
+ - checks that `claude` is installed and signed in (`claude auth login`);
24
+ - exchanges the code for a token and keeps it in your OS keychain — macOS's
25
+ `security`, set up so the item trusts `/usr/bin/security` and never pops a
26
+ dialog; Linux's `secret-tool`; anywhere else a 0600 file, and it says so;
27
+ - writes `~/.frod/bridge.json` (no secret in it);
28
+ - **installs the very package it just ran** into
29
+ `~/.frod/bridge/versions/<version>/`, with `~/.frod/bridge/current` a
30
+ symlink to it (a junction on Windows) — the npx cache is never where the
31
+ bridge actually runs from;
32
+ - writes the run-at-login entry so it points at
33
+ `node ~/.frod/bridge/current/dist/cli.js run` (macOS launchd agent
34
+ `io.frod.bridge`, Linux systemd user unit `frod-bridge.service`, Windows
35
+ scheduled task *Frod.io bridge*);
36
+ - and starts it.
37
+
38
+ Pass `--no-install` to join without any of the install/run-at-login steps —
39
+ useful if you'd rather run `install` or `run` by hand afterwards.
40
+
41
+ **You join once.** A computer is *yours* on the platform, not an agent's.
42
+ Every further agent is attached in Mission Control — on that agent's page,
43
+ **Join from your computer → Attach an existing computer** — and the change
44
+ reaches a running bridge at once, no restart. `status` lists the agents this
45
+ computer hosts and how each runs.
46
+
47
+ ## Other commands
48
+
49
+ All of these work either as `npx @frod.io/bridge@latest <command>` or, once
50
+ you've joined, as `node ~/.frod/bridge/current/dist/cli.js <command>`:
51
+
52
+ - `install` — on a computer already joined (from an old checkout, or an
53
+ earlier package), stages this package into `versions/`, rewrites the
54
+ run-at-login entry to point at it, leaves the keychain item exactly as it
55
+ is, and restarts the bridge — any session running here ends, so do it
56
+ when the desk is clear.
57
+ - `run` — runs the bridge in the foreground.
58
+ - `status` — what the platform sees, and which agents run here.
59
+ - `leave` — see below. `uninstall` — removes the run-at-login entry only
60
+ (the installed versions under `~/.frod/bridge` stay; delete the folder if
61
+ you like). `--version` — prints the version this command is.
62
+
63
+ ## What the platform decides, per agent (in Mission Control)
64
+
65
+ For every agent on your computer the workspace owner chooses:
66
+
67
+ - **Model** — the computer's default, Opus, Sonnet or Haiku (an alias Claude
68
+ Code understands), **Effort** — low, medium, high.
69
+ - **Ask / Never ask.** *Ask* (the default): a shell command, the web, a file
70
+ outside the session folder, a connector that sends or changes something —
71
+ each becomes a decision on the owner's phone (Approve / Reject) before it
72
+ runs. *Never ask — acts on its own*: the same calls run without waiting, and
73
+ **every one of them is recorded** — an activity line the owner can read and a
74
+ notice in the session's transcript with the exact words the gate would have
75
+ used. Only the workspace owner can set it; an agent can never set it for
76
+ itself; it is never the default.
77
+
78
+ ## What the platform sees
79
+
80
+ - The **transcript** of every session it opens here (what the agent said, the
81
+ tools it called and their results), its **cost** at the model's list rate
82
+ from the token counts Claude Code reports (the ledger says *billed to
83
+ subscription (list rate)*), and its **working folder** (`<workdir>/sessions/<id>`,
84
+ or the run checkout for a repository step).
85
+ - That this computer is **connected or offline**, its name, OS and Claude Code
86
+ version, and how many sessions it is running (up to `--max-sessions`, across
87
+ all its agents).
88
+ - Every permission decision, and — under *Never ask* — every call that would
89
+ have asked.
90
+
91
+ ## What it never sees
92
+
93
+ - Your Claude sign-in, your `~/.claude` settings, skills, plugins and MCP
94
+ servers — they load as they always do; the platform only *adds* its own
95
+ system prompt and its own tools on top.
96
+ - Any file outside the session folder unless the agent asks and a person
97
+ approves (or the owner chose *Never ask*).
98
+ - Your git credentials: repository steps clone and push **with your own
99
+ access**, only ever the run branch, and the platform verifies the commit on
100
+ its side before it counts.
101
+ - Any platform secret: the session's tools reach the Knowledge Hub, the
102
+ Library and every connector through doors on the platform, on a per-session
103
+ token that dies with the session. No database credential, no connector
104
+ credential and no API key is ever on this computer (the platform walks every
105
+ spec for them before it is sent).
106
+
107
+ ## It updates itself
108
+
109
+ Every time the bridge connects, the platform announces the current version,
110
+ signed. If this computer is behind, the bridge downloads the new tarball and
111
+ checks it two ways before trusting it — its sha512 against the hash the
112
+ platform announced, and an ed25519 signature over `version|integrity` against
113
+ a public key built into the bridge. Both have to pass: a compromised registry
114
+ alone can't push a version, and neither can a compromised platform alone. A
115
+ tarball that fails either check is deleted, the old version keeps running,
116
+ the log says `bridge.update_refused` with the reason, and the computer card
117
+ says "Update to `<v>` refused: `<reason>`."
118
+
119
+ A tarball that passes is extracted to `versions/<new>/`, and then the bridge
120
+ **waits** — never in the middle of a turn, never with a prompt — until this
121
+ computer hosts no live session, logging
122
+ `update ready, waiting for the desk to clear` once an hour (the computer card
123
+ says the same). When the desk is clear, it swaps `current` atomically, logs
124
+ `bridge.updated {from, to}`, and restarts through the supervisor: it exits
125
+ with code 75, a non-zero exit that launchd's `KeepAlive SuccessfulExit=false`
126
+ and systemd's `Restart=on-failure` both bring straight back up — a clean exit
127
+ (code 0) is deliberately a final goodbye, not a restart signal. Windows'
128
+ scheduled task has no restart-on-failure, so there the bridge relaunches the
129
+ new version itself, detached; if that fails, the next logon starts it.
130
+
131
+ The keychain item is never touched by an update. You'll see `Updated to <v>`
132
+ in `~/.frod/bridge.log` and the new version on the computer card. Two
133
+ versions are kept under `versions/`; older ones are pruned.
134
+
135
+ A bridge still run from a checkout — the old way,
136
+ `npx tsx apps/bridge/src/cli.ts run` — never updates itself; its card says
137
+ "update it by hand: `npx @frod.io/bridge@latest install`."
138
+
139
+ ## The compatibility contract
140
+
141
+ The platform decides what a computer's bridge can be trusted with by the
142
+ version it announces when it says hello — for example
143
+ `0.7.0/claude 2.1.261/codex 0.153.2/grok 1.0.13`. Three lines are drawn on the
144
+ platform side (`apps/orchestration-api/src/services/session-doors.ts`):
145
+
146
+ - below bridge `0.2.0` — connectors are never attached (an older bridge
147
+ couldn't gate them, so it isn't offered the choice);
148
+ - below `0.6.0` — no repository tools;
149
+ - below `0.7.0` — the Inbox and orchestration tools run from a checkout of the
150
+ platform as separate processes on your computer; at or above, they're
151
+ served by the platform the same way the Knowledge Hub and the Library are,
152
+ which is exactly why the package needs no checkout at all.
153
+
154
+ The rule behind these lines: one is only ever raised in the same commit as
155
+ the feature that needs it, and a computer running an older bridge is told, in
156
+ its own session's prompt, plainly, what it doesn't have — it is never handed
157
+ a door it can't work.
158
+
159
+ ## Leaving
160
+
161
+ ```
162
+ npx @frod.io/bridge@latest leave
163
+ ```
164
+
165
+ revokes the token on the platform, forgets the computer there (each agent goes
166
+ back to running where it did before — on the platform, or from its desk),
167
+ removes the token from the keychain and the run-at-login entry. The workspace
168
+ owner can do the same from Mission Control (**Settings → Your computers →
169
+ Disconnect**), which takes the computer off the floor at once.
170
+
171
+ ## Files on this computer
172
+
173
+ | Path | What |
174
+ |---|---|
175
+ | `~/.frod/bridge.json` | settings; no secret |
176
+ | the OS keychain, item `io.frod.bridge` | the host token |
177
+ | `~/.frod/bridge/versions/<version>/` | one installed copy of the package |
178
+ | `~/.frod/bridge/current` | a symlink (junction on Windows) to the version that's running |
179
+ | `~/.frod/sessions.json` | which Claude Code session each platform session ran as (for resume) |
180
+ | `~/.frod/bridge.log` | the log — tokens are redacted before anything is written |
181
+ | `<workdir>/sessions/<id>` | a session's folder (the default) |
182
+ | the folder the owner chose | a session's folder when the agent's binding names a **working folder on this computer** (see below) |
183
+ | `<workdir>/runs/<run id>` | a repository step's checkout (pruned after a week) |
184
+
185
+ ## A working folder per agent (day block 2026-09-03)
186
+
187
+ The workspace owner may give an agent a **working folder on this computer** in
188
+ Mission Control (the agent's page → Your computer → Change). The platform stores
189
+ the path as typed and sends it with each session; **this computer decides**
190
+ whether it allows it (`src/workdir.ts`):
191
+
192
+ - allowed: the folder is, or is under, `workdir` in `bridge.json`
193
+ (`~/frod-work` by default) **or** one of the folders listed under
194
+ `"allowedRoots"` in `bridge.json` — a list you edit by hand; `~/` means your
195
+ home. A folder that does not exist yet is created (0700).
196
+ - refused: anything else — outside every root, a relative path, a climb out with
197
+ `..`, a symlink that escapes. The session fails at once with the words
198
+ *"Wafeek's Mac does not allow that folder — allowed: …"*; nothing runs in a
199
+ folder you did not choose.
200
+
201
+ The folder becomes the session's cwd and the free zone of its permission policy
202
+ (reads and edits there are free; outside it, they ask — and Claude Code's own
203
+ files and any `.git` **at any depth** under it ask too). A repository step
204
+ ignores it and works in `runs/<run id>` as before. `status` prints the allowed
205
+ folders.
206
+
207
+ Two quirks of the containment check, both fail-closed and deliberate:
208
+ `"allowedRoots": ["/"]` allows **nothing under it** (the check is `root + "/"`, and
209
+ `"//"` matches no path — list real folders); and the check is
210
+ case-sensitive, so on a case-insensitive disk (macOS default) a folder typed as
211
+ `~/Frod-Work/x` is refused when the root is `~/frod-work` — type it as the root
212
+ is spelled.
213
+
214
+ ## Adapters
215
+
216
+ The bridge talks to a tool through one small interface — `start`, `prompt`,
217
+ `cancel`, `stop` (`src/adapters/types.ts`). **`claude`**
218
+ (`src/adapters/claude.ts`) is required — `join` checks it's installed and
219
+ signed in, and it drives Claude Code's headless CLI with your subscription.
220
+ **`codex`** (`src/adapters/codex.ts`) and **`grok`** (`src/adapters/grok.ts`)
221
+ are optional: each is offered only when it's installed *and* signed in on
222
+ this computer. Which agent runs on which adapter is decided in Mission
223
+ Control, not here. Grok Build runs in its `workspace` sandbox, and the bridge
224
+ answers its permission questions by the same Ask / Never-ask policy as the
225
+ others. Each adapter is one file.
226
+
227
+ ## Running at login
228
+
229
+ - **macOS** — a launchd user agent `io.frod.bridge`, running
230
+ `node ~/.frod/bridge/current/dist/cli.js run` — the same entry shape that
231
+ has run from a checkout on Wafeek's Mac since 2026-09-03; the packaged
232
+ entry runs there when his Mac is upgraded last, by him.
233
+ - **Linux** — a systemd user unit `~/.config/systemd/user/frod-bridge.service`
234
+ running the same command (`systemctl --user enable --now
235
+ frod-bridge.service`; on a headless box `loginctl enable-linger $USER` once).
236
+ Written and enabled by `install`; proven in a container on 2026-09-06 — the
237
+ unit is written correctly, but `systemctl --user` doesn't exist in a
238
+ container, so the manual command is printed instead. Not yet proven on a
239
+ real Linux machine.
240
+ - **Windows** — a scheduled task *Frod.io bridge* at logon
241
+ (`schtasks /Create`), also pointing at `current`. If it refuses, the exact
242
+ command is printed. Not yet proven on a real Windows machine.
243
+
244
+ ## Publishing
245
+
246
+ `.github/workflows/deploy-bridge.yml` runs on every push to `main` that
247
+ touches `apps/bridge/**`: it runs the tests, the type check and the bundle,
248
+ then `npm pack` and a smoke test of the packed tarball in a clean directory,
249
+ writes release notes from the commit subjects since the last published
250
+ version, runs `npm publish --access public`, signs the release
251
+ with an ed25519 key held in the `BRIDGE_SIGNING_KEY` secret, and calls
252
+ `POST /api/bridge/releases` (with the `BRIDGE_RELEASE_TOKEN` secret) so the
253
+ platform starts announcing it to connecting bridges.
254
+
255
+ The version published is always the package's own: bump
256
+ `apps/bridge/package.json` in the commit that should ship. The workflow
257
+ refuses to publish a version already on the registry, and never bumps
258
+ anything itself. Three secrets gate the workflow's last three steps, and if
259
+ one is missing the workflow says in words which one.
260
+
261
+ ## For developers
262
+
263
+ - `npm test -w @frod.io/bridge`
264
+ - `npm run build -w @frod.io/bridge` — bundles `dist/cli.js` with esbuild and
265
+ runs `tsc` for types.
266
+ - `npm run pack:check` — packs the tarball and smoke-tests it in a clean
267
+ directory, the same check the publish workflow runs.
268
+ - The bundle test asserts that every adapter and every door starts correctly
269
+ from `dist/cli.js` alone, with no checkout of the platform on disk.