@abide/abide 0.119.0 → 0.121.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.
- package/ABIDE.md +152 -0
- package/CHANGELOG.md +17 -0
- package/docs/REGISTRY.md +4 -4
- package/docs/RULEBOOK.md +21 -4
- package/docs/abide.md +26 -10
- package/package.json +2 -1
- package/scaffold/AGENTS.md +5 -72
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/index.abide +3 -3
- package/scaffold/src/layout.abide +4 -0
- package/scaffold/src/lib/app.css +1 -0
- package/src/compiler/scaffold.ts +4 -2
- package/src/server/BRIDGE_SCHEME.ts +3 -0
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/claudeCode.ts +29 -14
package/ABIDE.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Working on an abide app
|
|
2
|
+
|
|
3
|
+
These instructions ship with `@abide/abide` and update with it — do not edit this file.
|
|
4
|
+
|
|
5
|
+
abide is newer than any model's training data, so read its documentation before writing code,
|
|
6
|
+
rather than guessing from Svelte, Vue or React. It installs with the package:
|
|
7
|
+
|
|
8
|
+
- `node_modules/@abide/abide/docs/abide.md` — every documentation page in reading order, with a
|
|
9
|
+
working example for each task. It is long: read its Contents, then search for the page's
|
|
10
|
+
address, which heads each page as a comment — `<!-- values/show-a-value-that-changes.md -->`.
|
|
11
|
+
- `node_modules/@abide/abide/docs/REGISTRY.md` — every name an app can import or write, with its
|
|
12
|
+
signature and the rules that govern it.
|
|
13
|
+
- `node_modules/@abide/abide/docs/RULEBOOK.md` — every rule, numbered. A compiler or server
|
|
14
|
+
message that ends in `RULEBOOK 23.16.` names a clause in this file — search it for `23.16`.
|
|
15
|
+
|
|
16
|
+
When abide itself misbehaves, do not code around it: leave a `TODO(abide):` comment above the
|
|
17
|
+
affected code saying what it did, and tell the user.
|
|
18
|
+
|
|
19
|
+
## Checking your work
|
|
20
|
+
|
|
21
|
+
- `bun run check` — runs `abide check`, the type checker over `.abide` files. `tsc` and an editor's
|
|
22
|
+
TypeScript service do not read `.abide` files, so a clean `tsc` says nothing about them. Run
|
|
23
|
+
`bun run check` after every change, and treat its warnings as bugs: several of them name a read
|
|
24
|
+
that will not update when its value changes.
|
|
25
|
+
Where the read is meant, `// abide-ignore` and the clause the warning cites, on the line above
|
|
26
|
+
(`// abide-ignore 14.15`), silences that one (RULEBOOK 14.19); a read hoisted out of its place
|
|
27
|
+
only to quiet a warning is the bug it named.
|
|
28
|
+
- `bun run test` — runs `bun test`. Every handler file has a test that imports the handler and
|
|
29
|
+
calls it. Every component has a test that `mount`s it and counts the work an
|
|
30
|
+
interaction did with `measure`, both from `@abide/harness/abide` (RULEBOOK 38.119, 43.23) — a
|
|
31
|
+
test that only reads the text back passes against a component doing ten times the work.
|
|
32
|
+
A test waits on a value landing — `await` the handler call, or the `Reactive` the component
|
|
33
|
+
reads, which is thenable (RULEBOOK 1.4) — never on `Bun.sleep` or a timer. Break the code once and
|
|
34
|
+
watch a new test fail; a test that has never failed has not been shown to test anything.
|
|
35
|
+
Called outside a request, a `GET` keeps its answer for the whole test process: a test that changes
|
|
36
|
+
the data behind one calls its `invalidate()` in `beforeEach`. A handler that reads `config()` or
|
|
37
|
+
`principal` runs inside `app.run(…)` of `await createApp()`, where the app's `onConfig` has run.
|
|
38
|
+
`mount` sets no route, so a page stays thin: it reads the route and hands what it needs to a
|
|
39
|
+
component in `lib/`, and the test mounts that component.
|
|
40
|
+
- `bun run bench` — runs `harness-bench`: every `*.bench.ts` under `src/`, kept beside the code it
|
|
41
|
+
times. Each export is a function returning `batch()` from `@abide/harness/report`, an op at two
|
|
42
|
+
sizes; a `CEILINGS` export, `{ [bench]: { [arm]: milliseconds } }`, names the most each arm may
|
|
43
|
+
take, and an arm over it, or a ceiling naming no bench or arm, fails the run (RULEBOOK 43.29,
|
|
44
|
+
43.31, 43.32, 43.33).
|
|
45
|
+
- `bun run format` — `abide format` over every `.abide`, then `bun fmt` over the rest. Run it once
|
|
46
|
+
your changes are done.
|
|
47
|
+
- `bun run dev` — serves the app on a local port and rebuilds when a file changes.
|
|
48
|
+
- `bun run build` then `bun run start` — the production build, and the app served from it.
|
|
49
|
+
|
|
50
|
+
## Layout
|
|
51
|
+
|
|
52
|
+
- A file's path below `src/` is its address. `src/invoices/index.abide` answers `/invoices`,
|
|
53
|
+
`src/invoices/[id].abide` answers `/invoices/123`, and `layout.abide` wraps the pages below it;
|
|
54
|
+
the outermost one writes `<html>`. Run `bun run routes` to see every address and the file behind it.
|
|
55
|
+
- A `<name>.handler.ts` beside the pages is a handler: `src/invoices/getInvoice.handler.ts` holds
|
|
56
|
+
`export default GET(…)` and answers `/invoices/getInvoice`. It is the app's public API, for pages,
|
|
57
|
+
scripts, the CLI and MCP alike.
|
|
58
|
+
- A `.abide` cannot export. A `<script server>` in one holds that file's private handlers
|
|
59
|
+
(`const save = POST(…)`), callable by that file alone, and its hooks (`onStart`, `middleware`, …).
|
|
60
|
+
- A directory holds addresses, tests, benches, `.d.ts` declarations and two fenced directories
|
|
61
|
+
alone: `lib/` for components and modules, and `public/` for files served as they are. Anything
|
|
62
|
+
else beside the pages is a build error.
|
|
63
|
+
- Either fence may sit in any directory, at the root or beside the pages that use it.
|
|
64
|
+
`src/lib/` and `src/invoices/lib/` import alike (`#src/…`); the one difference is a lifecycle
|
|
65
|
+
hook a `lib/` file calls, filed at the route of the directory holding that `lib/` (RULEBOOK 37.1).
|
|
66
|
+
A `public/` file answers at the route of the directory holding it: `src/public/logo.png` answers
|
|
67
|
+
`/logo.png`, and `src/blog/public/cover.png` answers `/blog/cover.png`.
|
|
68
|
+
- Put a module in the `lib/` beside the pages that use it, and in `src/lib/` only once pages in
|
|
69
|
+
different directories share it. Within a `lib/`, group by what the code is about (`cart/`,
|
|
70
|
+
`catalog/`), not by which side runs it — the `.server.ts` name already says that.
|
|
71
|
+
- A test is a `.test.ts`, and a bench a `.bench.ts`, kept beside the file it tests or times — a
|
|
72
|
+
page, a handler, or a module in `lib/` (RULEBOOK 23.22). There is no `tests/` folder.
|
|
73
|
+
A browser test (playwright) is a `.test.ts` too, under `e2e/` beside `src/`: `bun test` reads
|
|
74
|
+
`src/` alone (`root` in `bunfig.toml`), so it never runs one.
|
|
75
|
+
- A module the browser never loads is named `<name>.server.ts` and lives in `lib/`
|
|
76
|
+
(`src/invoices/lib/db.server.ts`); importing one from browser code is a compile error. A module it
|
|
77
|
+
imports is not fenced by it.
|
|
78
|
+
- Import the app's own modules as `#src/…` (see `imports` in `package.json`).
|
|
79
|
+
- A test imports `.abide` files directly: `bunfig.toml` preloads `@abide/harness/abide/preload`,
|
|
80
|
+
a DOM to mount into and then `@abide/abide/test`.
|
|
81
|
+
|
|
82
|
+
## Reactive values
|
|
83
|
+
|
|
84
|
+
- Export a `Reactive` itself, never wrapped in an object — one per file, even when several belong
|
|
85
|
+
together: an `.abide` file reads and writes it by name (RULEBOOK 31.1), and one reached through a
|
|
86
|
+
property is left to `x()` and `x.set(v)` (31.7).
|
|
87
|
+
- Change part of a value by writing through its path — `edits.kind = kind`, `items.push(item)`
|
|
88
|
+
(RULEBOOK 31.8, 31.9) — not by rebuilding it (`edits = { ...edits, kind }`,
|
|
89
|
+
`items = [...items, item]`). Bind through the path too: `bind:value={edits.kind}` (32.5), not a
|
|
90
|
+
`{ get, set }` pair. In a `.ts` file the same write is `edits.patch((value) => { value.kind = kind })`,
|
|
91
|
+
not `edits.set({ ...edits(), kind })`.
|
|
92
|
+
- Derive a value with `reactive(() => …)`. `watch` is only for reaching outside the reactive graph —
|
|
93
|
+
the DOM, a timer, an imperative API — and never assigns state; a user action's effect belongs in
|
|
94
|
+
its event handler. A `watch` runs during a server render too (RULEBOOK 12.5), so one that starts a
|
|
95
|
+
timer or touches a browser global returns a `Disposer`.
|
|
96
|
+
- A preference the page renders — a currency, a theme, a panel left open — lives in a cookie:
|
|
97
|
+
`const theme = reactive(() => cookies().get('theme') ?? 'light')`, written with
|
|
98
|
+
`cookies().set('theme', next)`. `cookies()` is the request's on the server and the document's in
|
|
99
|
+
the browser, and a read by name is tracked (RULEBOOK 22.2, 22.4), so the first paint is already
|
|
100
|
+
right and a write repaints. The browser sends it, so check what it holds before trusting it.
|
|
101
|
+
`localStorage` is for a working set the browser alone needs: the server cannot read it, and
|
|
102
|
+
renders the initial value.
|
|
103
|
+
- A value that has not landed leaves its place in markup as it was — empty before it first lands,
|
|
104
|
+
the last value while it reloads — and fills it when it lands (RULEBOOK 32.23), so markup needs no
|
|
105
|
+
guard. Do not paper over it with `?? []` or `x.ready() ? x : undefined`
|
|
106
|
+
— that makes "not loaded yet" read as "empty". Branch on `x.ready()` or `x.busy()` only where a
|
|
107
|
+
placeholder is actually wanted.
|
|
108
|
+
|
|
109
|
+
## Settings and untrusted input
|
|
110
|
+
|
|
111
|
+
- Settings come from `onConfig` and `config` (RULEBOOK 27.2, 27.9), and cookies from `cookies()`
|
|
112
|
+
(22.2) — never `Bun.env`, `process.env` or a parsed `cookie` header in `src/`, and never a branch on
|
|
113
|
+
`NODE_ENV`. The environment overrides a default `onConfig` returns (27.2, 27.8), and `bun test`
|
|
114
|
+
reads `.env.test`, so a test's database or data directory is a line in `.env.test`, not a check in
|
|
115
|
+
the app's code.
|
|
116
|
+
- Model output, markdown and fetched HTML are untrusted. Never hand them to `raw()` (RULEBOOK 32.2)
|
|
117
|
+
unsanitized. Render markdown on the server — a component in the browser calls a private `POST` in
|
|
118
|
+
its `<script server>` for it. `Bun.markdown.html(text, { noHtmlBlocks: true, noHtmlSpans: true })`
|
|
119
|
+
escapes HTML but keeps a `javascript:` URL, so pass its output through `HTMLRewriter` and drop any
|
|
120
|
+
`href` that is not `http:`, `https:` or `mailto:`, and any `src` that is not `http:` or `https:`.
|
|
121
|
+
|
|
122
|
+
## Measuring
|
|
123
|
+
|
|
124
|
+
- Measure with `@abide/harness`, not a hand-written timer, bench runner or Chrome DevTools Protocol
|
|
125
|
+
script: `measure` in a test and `batch` in a bench (above), and `traceOp` and `shares` from
|
|
126
|
+
`@abide/harness/engine` for what Chromium did, from a playwright test. The page
|
|
127
|
+
`guide/test-measure-and-ship.md` in `abide.md`, and "The measurement harness" in `REGISTRY.md`,
|
|
128
|
+
document it.
|
|
129
|
+
|
|
130
|
+
## Code
|
|
131
|
+
|
|
132
|
+
- One export per file, and the file is named after it: `SCREAMING_SNAKE_CASE.ts` for a constant,
|
|
133
|
+
`TitleCase.abide` / `TitleCase.ts` for a component, a class or a type, `camelCase.ts` for a
|
|
134
|
+
function. No barrel files. Before writing a helper, search `lib/` for one to reuse or widen.
|
|
135
|
+
macOS disks ignore case, so two files in one directory never differ by case alone
|
|
136
|
+
(`RecipeStore.ts` beside `recipeStore.ts`): one silently replaces the other.
|
|
137
|
+
- Names say what they hold — `selectedInvoice`, not `sel`, `x` or `S`.
|
|
138
|
+
- A file past a few hundred lines is a component or a module waiting to be split out; split it
|
|
139
|
+
before adding to it.
|
|
140
|
+
- Use Bun's APIs (`Bun.file`, `Bun.write`, `Bun.$`, `bun:sqlite`) before `node:` ones.
|
|
141
|
+
- Style with tailwind classes. `style=` is for a value computed at runtime. Tailwind is wired
|
|
142
|
+
already (RULEBOOK 38.131): `src/lib/app.css`, imported by the root `layout.abide`, is where a theme
|
|
143
|
+
or a plugin goes.
|
|
144
|
+
- In code that runs per row, per frame or per chunk, write a `for` loop rather than a chain of
|
|
145
|
+
`map` / `filter` / `reduce`, which allocates an array per step. Elsewhere, write what reads best.
|
|
146
|
+
- Comment only where the reason is not obvious from the code, in a line or two. A comment is not a
|
|
147
|
+
log: no history ("used to…", "now…") and no file paths that go stale.
|
|
148
|
+
|
|
149
|
+
## The app's own notes
|
|
150
|
+
|
|
151
|
+
The app's `AGENTS.md` holds instructions — commands, conventions, where things live — and stays
|
|
152
|
+
short. What the product does, how a feature behaves and why it was built that way goes in `docs/`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.121.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 980d729: the agent instructions ship in the package and a scaffolded CLAUDE.md imports them, and the starter comes with tailwind wired (RULEBOOK 38.104, 38.106, 38.131) ([`c95852e`](https://github.com/briancray/abide/commit/c95852e7bc195cb9f5ef53f0623e31700362b302))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 980d729: the shipped agent instructions are ABIDE.md, so no agent working on abide reads them as its own, and a gate holds their citations and the starter's tailwind to what resolves (RULEBOOK 38.104, 38.106, 38.131) ([`2880551`](https://github.com/briancray/abide/commit/2880551b777247a88e7042fa5bdd462fab3e40b2))
|
|
12
|
+
- 980d729: one dangling-citation scan and one built-stylesheet read in the tests, and the shipped agent instructions state each rule once ([`602cb8f`](https://github.com/briancray/abide/commit/602cb8fd87f136ea23a58dd006bdac43ca65ebe4))
|
|
13
|
+
|
|
14
|
+
## 0.120.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- 2af648d: a pairing link beside the command — abide-bridge install registers a macOS app that opens it and asks before it pairs (RULEBOOK 44.40-44.43) ([`409d5ac`](https://github.com/briancray/abide/commit/409d5ac7f95338cb3f44a21cc76cd977387eab42))
|
|
19
|
+
|
|
3
20
|
## 0.119.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/docs/REGISTRY.md
CHANGED
|
@@ -351,9 +351,9 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
351
351
|
| --- | --- | --- | --- |
|
|
352
352
|
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | 44.5, 44.13, 44.17, 44.18, 44.19, 44.24, 44.25, 44.27, 44.30, 44.33 |
|
|
353
353
|
| `ClaudeCodeConfig` | `{ model?: string; fallbackModel?: string; systemPrompt?: string; appendSystemPrompt?: string; effort?: 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'; maxBudgetUsd?: number; betas?: string[]; disallowedTools?: string[]; tools?: string[] }` | The Claude Code options a site may set for a turn, and the built-in tools it asks the visitor for. | 44.16, 44.29 |
|
|
354
|
-
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; expires: number }` | The command that
|
|
355
|
-
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | 44.20, 44.22, 44.32, 44.33, 44.34 |
|
|
356
|
-
| `BridgeStatus` | `{ paired: boolean; command: string \| undefined }` | Whether a bridge is paired, and the command that
|
|
354
|
+
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | 44.14, 44.32, 44.33, 44.40, 44.41, 44.42, 44.43 |
|
|
355
|
+
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | 44.20, 44.22, 44.32, 44.33, 44.34, 44.40 |
|
|
356
|
+
| `BridgeStatus` | `{ paired: boolean; command: string \| undefined; link: string \| undefined }` | Whether a bridge is paired, and the command and the link that pair one where none is. | 44.20, 44.40 |
|
|
357
357
|
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | 36.7, 44.14, 44.15, 44.19, 44.23, 44.26, 16.57, 16.78 |
|
|
358
358
|
|
|
359
359
|
# Ambient values
|
|
@@ -858,7 +858,7 @@ rows a runner prints, and an adapter for an abide app.
|
|
|
858
858
|
|
|
859
859
|
| Command | Meaning | Rules |
|
|
860
860
|
| --- | --- | --- |
|
|
861
|
-
| `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112, 38.122, 38.124 |
|
|
861
|
+
| `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112, 38.122, 38.124, 38.131 |
|
|
862
862
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | 38.2, 38.57 |
|
|
863
863
|
| `abide check [dir…]` | The type-checker over a `.abide` tree. | 38.3, 38.31, 38.32, 38.33, 38.40, 38.105, 38.123 |
|
|
864
864
|
| `abide format [--check] [dir…]` | The formatter over a `.abide` tree. | 38.25, 38.125, 38.126, 38.127, 38.128, 38.129 |
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -2755,14 +2755,15 @@ open on the refusal, and the `connect` menu role MUST show the connect dialog op
|
|
|
2755
2755
|
|
|
2756
2756
|
38.103 The project `abide scaffold` writes MUST carry a `check` script that runs `abide check`.
|
|
2757
2757
|
|
|
2758
|
-
38.104 The project `abide scaffold` writes MUST carry instructions for a coding agent that
|
|
2759
|
-
the installed package holds
|
|
2758
|
+
38.104 The project `abide scaffold` writes MUST carry instructions for a coding agent that import the
|
|
2759
|
+
ones the installed package holds, and those MUST name where the installed package holds the
|
|
2760
|
+
documentation, the registry and the rulebook.
|
|
2760
2761
|
|
|
2761
2762
|
38.105 Every clause an `abide check` diagnostic or a refusal cites MUST resolve in a rulebook the
|
|
2762
2763
|
published package holds.
|
|
2763
2764
|
|
|
2764
|
-
38.106 The documentation the instructions `abide scaffold` writes
|
|
2765
|
-
as one markdown file, in reading order.
|
|
2765
|
+
38.106 The documentation named by the instructions a project `abide scaffold` writes imports (38.104)
|
|
2766
|
+
MUST be every page as one markdown file, in reading order.
|
|
2766
2767
|
|
|
2767
2768
|
38.107 `onLauncher` called anywhere but `src/launcher.abide` MUST be a build error naming that file.
|
|
2768
2769
|
|
|
@@ -2847,6 +2848,9 @@ line, MUST go on formatting the rest, and MUST exit non-zero where it reports on
|
|
|
2847
2848
|
38.130 `abide lsp` MUST answer a formatting request with what `abide format` makes of the text the
|
|
2848
2849
|
editor holds.
|
|
2849
2850
|
|
|
2851
|
+
38.131 The project `abide scaffold` writes MUST carry Tailwind wired: `bun-plugin-tailwind` declared
|
|
2852
|
+
in its `bunfig.toml` (38.43), and a stylesheet importing `tailwindcss` that its root layout imports.
|
|
2853
|
+
|
|
2850
2854
|
# 39. Head and view transitions
|
|
2851
2855
|
|
|
2852
2856
|
39.1 A `<head>` element in a `.abide` file MUST contribute its children to the document head.
|
|
@@ -3398,6 +3402,19 @@ its type, in its place in its turn.
|
|
|
3398
3402
|
44.39 `assistant` MUST hand a `Provider` each turn as its `AssistantContent` pieces, a turn's string
|
|
3399
3403
|
as one text piece.
|
|
3400
3404
|
|
|
3405
|
+
44.40 A pairing `claudeCode.bridgeCommand` or `claudeCode.bridge` mints MUST carry, beside its command,
|
|
3406
|
+
a link in the `abide-bridge` scheme naming the same site, code, bridge release and built-in tools.
|
|
3407
|
+
|
|
3408
|
+
44.41 A bridge MUST open only a link of the `abide-bridge` scheme whose site is https or loopback and
|
|
3409
|
+
whose release is a version alone, and MUST run the release it names.
|
|
3410
|
+
|
|
3411
|
+
44.42 A bridge opening an `abide-bridge` link MUST ask the visitor before it pairs, every time, naming
|
|
3412
|
+
the site, the built-in tools and the bridge release the link asks for.
|
|
3413
|
+
|
|
3414
|
+
44.43 `abide-bridge install` MUST write the app that opens links under the visitor's own Application
|
|
3415
|
+
Support and register it there, and `abide-bridge uninstall` MUST unregister it and remove all
|
|
3416
|
+
install wrote.
|
|
3417
|
+
|
|
3401
3418
|
# 45. Offline
|
|
3402
3419
|
|
|
3403
3420
|
45.1 Where `ABIDE_OFFLINE` is on, a page `abide build` built MUST register the worker
|
package/docs/abide.md
CHANGED
|
@@ -5900,7 +5900,7 @@ Leave `baseURL` out and the request goes to Anthropic. `claudeApi` reads no key
|
|
|
5900
5900
|
|
|
5901
5901
|
## `claudeCode` runs on the visitor's own Claude Code
|
|
5902
5902
|
|
|
5903
|
-
**`claudeCode(config)` — a turn on the reader's own Claude Code** — The assistant on whatever Claude Code is paired with this browser. Ask first and it is refused; connect, run the command in a terminal, and ask again.
|
|
5903
|
+
**`claudeCode(config)` — a turn on the reader's own Claude Code** — The assistant on whatever Claude Code is paired with this browser. Ask first and it is refused; connect, run the command in a terminal or open the link, and ask again.
|
|
5904
5904
|
|
|
5905
5905
|
```abide src/assistant/lib/ClaudeCodeBridge.abide
|
|
5906
5906
|
<script>
|
|
@@ -5935,6 +5935,11 @@ const answer = reactive(() => {
|
|
|
5935
5935
|
{:else if pairing.at(-1)}
|
|
5936
5936
|
<pre class="rounded-md border border-rule bg-raised p-snug
|
|
5937
5937
|
text-chrome">{pairing.at(-1)?.command}</pre>
|
|
5938
|
+
<div class="row">
|
|
5939
|
+
<a href={pairing.at(-1)?.link}>Open in abide bridge</a>
|
|
5940
|
+
<span class="status">on macOS, after
|
|
5941
|
+
<code>bunx @abide/bridge install</code></span>
|
|
5942
|
+
</div>
|
|
5938
5943
|
{:else}
|
|
5939
5944
|
<div class="row">
|
|
5940
5945
|
<button onclick={() => {
|
|
@@ -6010,7 +6015,7 @@ function fromPage(
|
|
|
6010
6015
|
})
|
|
6011
6016
|
}
|
|
6012
6017
|
|
|
6013
|
-
test('unpaired:
|
|
6018
|
+
test('unpaired: refused, with a command and a link', async () => {
|
|
6014
6019
|
await using app = await createApp()
|
|
6015
6020
|
const refused = await app.fetch(
|
|
6016
6021
|
fromPage('/assistant/askClaudeCode', {
|
|
@@ -6031,11 +6036,14 @@ test('unpaired: asking is refused, the bridge shows a command', async () => {
|
|
|
6031
6036
|
const reader = (status.body as ReadableStream<Uint8Array>).getReader()
|
|
6032
6037
|
const first = new TextDecoder().decode((await reader.read()).value)
|
|
6033
6038
|
watching.abort()
|
|
6034
|
-
const { paired, command } = JSON.parse(first.split('\n')[0] ?? '')
|
|
6039
|
+
const { paired, command, link } = JSON.parse(first.split('\n')[0] ?? '')
|
|
6035
6040
|
expect(paired).toBe(false)
|
|
6036
6041
|
expect(command).toMatch(
|
|
6037
6042
|
/^bunx @abide\/bridge@\S+ http:\/\/crm.invalid \S+$/,
|
|
6038
6043
|
)
|
|
6044
|
+
expect(new URL(link).searchParams.get('code')).toBe(
|
|
6045
|
+
command.split(' ').at(-1),
|
|
6046
|
+
)
|
|
6039
6047
|
})
|
|
6040
6048
|
```
|
|
6041
6049
|
|
|
@@ -6051,13 +6059,21 @@ Pass the same `tools` to `claudeCode.bridge` or `claudeCode.bridgeCommand` and t
|
|
|
6051
6059
|
|
|
6052
6060
|
The visitor pairs once per visit by running a command, which dials out to the site. Asking with nothing paired is refused 409.
|
|
6053
6061
|
|
|
6062
|
+
On a Mac the visitor can pair with a click instead. Each status carries a `link` beside its command, the same pairing in the `abide-bridge` scheme, and a visitor who has run this once opens it from the page:
|
|
6063
|
+
|
|
6064
|
+
```
|
|
6065
|
+
bunx @abide/bridge install
|
|
6066
|
+
```
|
|
6067
|
+
|
|
6068
|
+
That builds a small app under their own Application Support folder, nowhere in Applications, and registers it for the scheme. Opening a link asks them every time, naming the site, the tools and the bridge release it asks for, and then runs that release, as the command would. A link only proves the site minted its code, never that this visitor asked for it, so no answer is kept for a site. `bunx @abide/bridge uninstall` takes it away.
|
|
6069
|
+
|
|
6054
6070
|
The tools the conversation calls run as the visitor, on the sign-in their ask carried, so a tool reads `principal` as any handler does. The pairing belongs to that sign-in too. If someone else signs in at the same browser, their asks are refused 409 and the status offers them a command of their own, so nobody's turn runs on another member's Claude Code.
|
|
6055
6071
|
|
|
6056
6072
|
| Name | What it does for the CRM |
|
|
6057
6073
|
| --- | --- |
|
|
6058
|
-
| `claudeCode.bridge` | Streams this browser's pairing: unpaired with the command to run, then paired. |
|
|
6059
|
-
| `BridgeStatus` | One status in that stream, `{ paired, command }`. |
|
|
6060
|
-
| `claudeCode.bridgeCommand` | Mints the command alone, for a page that wants
|
|
6074
|
+
| `claudeCode.bridge` | Streams this browser's pairing: unpaired with the command to run and the link to open, then paired. |
|
|
6075
|
+
| `BridgeStatus` | One status in that stream, `{ paired, command, link }`. |
|
|
6076
|
+
| `claudeCode.bridgeCommand` | Mints the command and the link alone, for a page that wants them without the stream. Its code expires unused after ten minutes. |
|
|
6061
6077
|
| `/__abide/bridge` | Where the visitor's bridge connects. It opens the first time a command is minted. |
|
|
6062
6078
|
|
|
6063
6079
|
## `abide call` runs a handler from the terminal
|
|
@@ -8403,8 +8419,8 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8403
8419
|
| Name | Signature | Meaning | Rules |
|
|
8404
8420
|
| --- | --- | --- | --- |
|
|
8405
8421
|
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.27](/rulebook#44.27), [44.30](/rulebook#44.30), [44.33](/rulebook#44.33) |
|
|
8406
|
-
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; expires: number }` | The command that
|
|
8407
|
-
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34) |
|
|
8422
|
+
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.40](/rulebook#44.40), [44.41](/rulebook#44.41), [44.42](/rulebook#44.42), [44.43](/rulebook#44.43) |
|
|
8423
|
+
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34), [44.40](/rulebook#44.40) |
|
|
8408
8424
|
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | [36.7](/rulebook#36.7), [44.14](/rulebook#44.14), [44.15](/rulebook#44.15), [44.19](/rulebook#44.19), [44.23](/rulebook#44.23), [44.26](/rulebook#44.26), [16.57](/rulebook#16.57), [16.78](/rulebook#16.78) |
|
|
8409
8425
|
|
|
8410
8426
|
## Taught in
|
|
@@ -8441,7 +8457,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8441
8457
|
|
|
8442
8458
|
| Name | Signature | Meaning | Rules |
|
|
8443
8459
|
| --- | --- | --- | --- |
|
|
8444
|
-
| `BridgeStatus` | `{ paired: boolean; command: string \| undefined }` | Whether a bridge is paired, and the command that
|
|
8460
|
+
| `BridgeStatus` | `{ paired: boolean; command: string \| undefined; link: string \| undefined }` | Whether a bridge is paired, and the command and the link that pair one where none is. | [44.20](/rulebook#44.20), [44.40](/rulebook#44.40) |
|
|
8445
8461
|
|
|
8446
8462
|
## Taught in
|
|
8447
8463
|
|
|
@@ -10720,7 +10736,7 @@ CLI: what is written in place rather than imported.
|
|
|
10720
10736
|
|
|
10721
10737
|
| Command | Meaning | Rules |
|
|
10722
10738
|
| --- | --- | --- |
|
|
10723
|
-
| `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122), [38.124](/rulebook#38.124) |
|
|
10739
|
+
| `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122), [38.124](/rulebook#38.124), [38.131](/rulebook#38.131) |
|
|
10724
10740
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10725
10741
|
| `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105), [38.123](/rulebook#38.123) |
|
|
10726
10742
|
| `abide format [--check] [dir…]` | The formatter over a `.abide` tree. | [38.25](/rulebook#38.25), [38.125](/rulebook#38.125), [38.126](/rulebook#38.126), [38.127](/rulebook#38.127), [38.128](/rulebook#38.128), [38.129](/rulebook#38.129) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abide/abide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.121.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"prepack": "bun ../../scripts/packDocs.ts"
|
|
73
73
|
},
|
|
74
74
|
"files": [
|
|
75
|
+
"ABIDE.md",
|
|
75
76
|
"src",
|
|
76
77
|
"bin",
|
|
77
78
|
"scaffold",
|
package/scaffold/AGENTS.md
CHANGED
|
@@ -1,76 +1,9 @@
|
|
|
1
1
|
# Working on this app
|
|
2
2
|
|
|
3
|
-
This is an abide app.
|
|
4
|
-
|
|
3
|
+
This is an abide app. Before writing code, read `node_modules/@abide/abide/ABIDE.md`: how an abide
|
|
4
|
+
app is laid out, checked and tested, and what this file is for.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
working example for each task. It is long: read its Contents, then search for the page's
|
|
8
|
-
address, which heads each page as a comment — `<!-- values/show-a-value-that-changes.md -->`.
|
|
9
|
-
- `node_modules/@abide/abide/docs/REGISTRY.md` — every name an app can import or write, with its
|
|
10
|
-
signature and the rules that govern it.
|
|
11
|
-
- `node_modules/@abide/abide/docs/RULEBOOK.md` — every rule, numbered. A compiler or server
|
|
12
|
-
message that ends in `RULEBOOK 23.16.` names a clause in this file — search it for `23.16`.
|
|
6
|
+
## This app
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- `bun run check` — runs `abide check`, the type checker over `.abide` files. `tsc` and an editor's
|
|
17
|
-
TypeScript service do not read `.abide` files, so a clean `tsc` says nothing about them. Run
|
|
18
|
-
`bun run check` after every change, and treat its warnings as bugs: several of them name a read
|
|
19
|
-
that will not update when its value changes.
|
|
20
|
-
Where the read is meant, `// abide-ignore` and the clause the warning cites, on the line above
|
|
21
|
-
(`// abide-ignore 14.15`), silences that one (RULEBOOK 14.19); a read hoisted out of its place
|
|
22
|
-
only to quiet a warning is the bug it named.
|
|
23
|
-
- `bun run test` — runs `bun test`. `src/getGreeting.test.ts` is the pattern for a handler: a test
|
|
24
|
-
imports it and calls it. `src/index.test.ts` is the pattern for a page: `mount` it, then count
|
|
25
|
-
the work a click did with `measure`, both from `@abide/harness/abide` (RULEBOOK 38.119, 43.23).
|
|
26
|
-
- `bun run bench` — runs `harness-bench`: every `*.bench.ts` under `src/`, kept beside the code it
|
|
27
|
-
times. Each export is a function returning `batch()` from `@abide/harness/report`, an op at two
|
|
28
|
-
sizes; a `CEILINGS` export, `{ [bench]: { [arm]: milliseconds } }`, names the most each arm may
|
|
29
|
-
take, and an arm over it, or a ceiling naming no bench or arm, fails the run (RULEBOOK 43.29,
|
|
30
|
-
43.31, 43.32, 43.33).
|
|
31
|
-
- `bun run dev` — serves the app on a local port and rebuilds when a file changes.
|
|
32
|
-
- `bun run build` then `bun run start` — the production build, and the app served from it.
|
|
33
|
-
|
|
34
|
-
## Layout
|
|
35
|
-
|
|
36
|
-
- A file's path below `src/` is its address. `src/invoices/index.abide` answers `/invoices`,
|
|
37
|
-
`src/invoices/[id].abide` answers `/invoices/123`, and `layout.abide` wraps the pages below it;
|
|
38
|
-
the outermost one writes `<html>`. Run `bun run routes` to see every address and the file behind it.
|
|
39
|
-
- A `<name>.handler.ts` beside the pages is a handler: `src/invoices/getInvoice.handler.ts` holds
|
|
40
|
-
`export default GET(…)` and answers `/invoices/getInvoice`. It is the app's public API, for pages,
|
|
41
|
-
scripts, the CLI and MCP alike.
|
|
42
|
-
- A `.abide` cannot export. A `<script server>` in one holds that file's private handlers
|
|
43
|
-
(`const save = POST(…)`), callable by that file alone, and its hooks (`onStart`, `middleware`, …).
|
|
44
|
-
- A directory holds addresses, tests, benches, `.d.ts` declarations and two fenced directories alone: `lib/` for components and
|
|
45
|
-
modules, and `public/` for files served as they are. Anything else beside the pages is a build error.
|
|
46
|
-
- Either fence may sit in any directory, at the root or beside the pages that use it.
|
|
47
|
-
`src/lib/` and `src/invoices/lib/` import alike (`#src/…`); the one difference is a lifecycle
|
|
48
|
-
hook a `lib/` file calls, filed at the route of the directory holding that `lib/` (RULEBOOK 37.1).
|
|
49
|
-
A `public/` file answers at the route of the directory holding it: `src/public/logo.png` answers
|
|
50
|
-
`/logo.png`, and `src/blog/public/cover.png` answers `/blog/cover.png`.
|
|
51
|
-
- A test is a `.test.ts`, and a bench a `.bench.ts`, kept beside the file it tests or times — a
|
|
52
|
-
page, a handler, or a module in `lib/` (RULEBOOK 23.22).
|
|
53
|
-
A browser test (playwright) is a `.test.ts` too, under `e2e/` beside `src/`: `bun test` reads
|
|
54
|
-
`src/` alone (`root` in `bunfig.toml`), so it never runs one.
|
|
55
|
-
- A module the browser never loads is named `<name>.server.ts` and lives in `lib/`
|
|
56
|
-
(`src/invoices/lib/db.server.ts`); importing one from browser code is a compile error. A module it
|
|
57
|
-
imports is not fenced by it.
|
|
58
|
-
- Import the app's own modules as `#src/…` (see `imports` in `package.json`).
|
|
59
|
-
- A test imports `.abide` files directly: `bunfig.toml` preloads `@abide/harness/abide/preload`,
|
|
60
|
-
a DOM to mount into and then `@abide/abide/test`.
|
|
61
|
-
|
|
62
|
-
## Habits
|
|
63
|
-
|
|
64
|
-
- Measure with `@abide/harness`, not a hand-written timer, bench runner or Chrome DevTools Protocol
|
|
65
|
-
script. Its three lanes: `measure` from `@abide/harness/abide` is the DOM work and wakes an op
|
|
66
|
-
did, under `bun test`; `batch` from `@abide/harness/report` is time, in a bench; `traceOp` and
|
|
67
|
-
`shares` from `@abide/harness/engine` are what Chromium did, from a playwright test. The page
|
|
68
|
-
`guide/test-measure-and-ship.md` in `abide.md`, and "The measurement harness" in `REGISTRY.md`,
|
|
69
|
-
document it.
|
|
70
|
-
|
|
71
|
-
- Export a `Reactive` itself, never wrapped in an object: an `.abide` file reads and writes it by
|
|
72
|
-
name (RULEBOOK 31.1), and one reached through a property is left to `x()` and `x.set(v)` (31.7).
|
|
73
|
-
- Model output and fetched HTML are untrusted. Never hand them to `raw()` (RULEBOOK 32.2)
|
|
74
|
-
unsanitized.
|
|
75
|
-
- Settings come from `onConfig` and `config` (RULEBOOK 27.2, 27.9), which already read the
|
|
76
|
-
environment, and cookies from `cookies()` (22.2) — not `Bun.env` or a parsed `cookie` header.
|
|
8
|
+
- `src/getGreeting.handler.ts` and its test are the pattern for a handler and the test that calls
|
|
9
|
+
it; `src/index.test.ts` is the pattern for a page test that counts the work a click did.
|
package/scaffold/CLAUDE.md
CHANGED
package/scaffold/src/index.abide
CHANGED
|
@@ -9,10 +9,10 @@ const greeting = reactive(() => getGreeting({ name: 'world' }))
|
|
|
9
9
|
|
|
10
10
|
<head><title>Hello</title></head>
|
|
11
11
|
|
|
12
|
-
<main>
|
|
13
|
-
<h1>{greeting}</h1>
|
|
12
|
+
<main class="mx-auto flex max-w-md flex-col gap-4 p-8">
|
|
13
|
+
<h1 class="text-2xl font-semibold">{greeting}</h1>
|
|
14
14
|
<p>Clicked {count} times.</p>
|
|
15
|
-
<button onclick={() => {
|
|
15
|
+
<button class="self-start rounded bg-black px-4 py-2 text-white" onclick={() => {
|
|
16
16
|
count = count + 1
|
|
17
17
|
}}>Click</button>
|
|
18
18
|
</main>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss" source("..");
|
package/src/compiler/scaffold.ts
CHANGED
|
@@ -40,7 +40,7 @@ const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
|
|
|
40
40
|
* lands first on the PATH inside `bun run`, shadowing the bun that runs everything else.
|
|
41
41
|
*/
|
|
42
42
|
export const BUNFIG =
|
|
43
|
-
'# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n\n# What `bun fmt` and `abide format` print.\n[fmt]\ntabWidth = 4\nsemi = false\nsingleQuote = true\nsortImports = true\n'
|
|
43
|
+
'# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.43, 38.131 — the plugin that builds the stylesheet `src/lib/app.css` opens.\n[serve.static]\nplugins = ["bun-plugin-tailwind"]\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n\n# What `bun fmt` and `abide format` print.\n[fmt]\ntabWidth = 4\nsemi = false\nsingleQuote = true\nsortImports = true\n'
|
|
44
44
|
|
|
45
45
|
// WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
|
|
46
46
|
// the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file.
|
|
@@ -80,10 +80,12 @@ function filesFor(name: string): Record<string, string> {
|
|
|
80
80
|
// `Bun`, `bun:sqlite` and the rest of the runtime a server module reaches for are
|
|
81
81
|
// typed by these, and `abide check` reports every one untyped without them. The
|
|
82
82
|
// harness is what the starter's tests measure the work a click does with (38.119), and
|
|
83
|
-
// what `bench` runs (38.124).
|
|
83
|
+
// what `bench` runs (38.124). The stylesheet is tailwind's, built by its bun plugin (38.131).
|
|
84
84
|
devDependencies: {
|
|
85
85
|
'@abide/harness': HARNESS_DEPENDENCY,
|
|
86
86
|
'@types/bun': 'latest',
|
|
87
|
+
'bun-plugin-tailwind': '^0.1.2',
|
|
88
|
+
tailwindcss: '^4.3.3',
|
|
87
89
|
},
|
|
88
90
|
},
|
|
89
91
|
null,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The `abide-bridge` release a pairing command names, so a visitor's `bunx` runs the relay this
|
|
2
2
|
// server speaks. `packages/abide/tests/claudeCode.test.ts` holds it to the bridge package's own.
|
|
3
|
-
export const BRIDGE_VERSION = '0.
|
|
3
|
+
export const BRIDGE_VERSION = '0.4.0'
|
package/src/server/claudeCode.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
forwardOf,
|
|
20
20
|
type Provider,
|
|
21
21
|
} from './assistant.ts'
|
|
22
|
+
import { BRIDGE_SCHEME } from './BRIDGE_SCHEME.ts'
|
|
22
23
|
import { BRIDGE_VERSION } from './BRIDGE_VERSION.ts'
|
|
23
24
|
import {
|
|
24
25
|
followSignIn,
|
|
@@ -50,8 +51,15 @@ export type ClaudeCodeConfig = {
|
|
|
50
51
|
/** The built-in tools a pairing command grants, as the visitor would after its `--`. 44.32. */
|
|
51
52
|
type PairingOptions = Pick<ClaudeCodeConfig, 'tools'>
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Whether this browser's bridge is connected, and the command and the link that pair one where it is
|
|
56
|
+
* not.
|
|
57
|
+
*/
|
|
58
|
+
export type BridgeStatus = {
|
|
59
|
+
paired: boolean
|
|
60
|
+
command: string | undefined
|
|
61
|
+
link: string | undefined
|
|
62
|
+
}
|
|
55
63
|
|
|
56
64
|
/** The visitor's own Claude Code, reached through the bridge paired to their browser. */
|
|
57
65
|
function claudeCodeProvider(config: ClaudeCodeConfig = {}): Provider {
|
|
@@ -149,35 +157,40 @@ function askingCaller(): string {
|
|
|
149
157
|
return caller
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
/** One pairing code, as the command a visitor runs and the link they open, and when it stops standing. */
|
|
161
|
+
type Pairing = { command: string; link: string; expires: number }
|
|
162
|
+
|
|
152
163
|
/**
|
|
153
|
-
* The command that
|
|
154
|
-
* `tools` it is given are written after
|
|
164
|
+
* The command and the link that pair a bridge with this browser, and when their code stops standing.
|
|
165
|
+
* 44.14. The `tools` it is given are written after the command's `--` and in the link, so the visitor
|
|
166
|
+
* running either grants them (44.29, 44.32, 44.40).
|
|
155
167
|
*/
|
|
156
|
-
function bridgeCommand(options: PairingOptions = {}): {
|
|
157
|
-
command: string
|
|
158
|
-
expires: number
|
|
159
|
-
} {
|
|
168
|
+
function bridgeCommand(options: PairingOptions = {}): Pairing {
|
|
160
169
|
return commandFor(askingCaller(), signedInAs(), options)
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
/** The command pairing a bridge with `caller` signed in as `signedIn` (44.33). */
|
|
172
|
+
/** The command and the link pairing a bridge with `caller` signed in as `signedIn` (44.33). */
|
|
164
173
|
function commandFor(
|
|
165
174
|
caller: string,
|
|
166
175
|
signedIn: string,
|
|
167
176
|
{ tools }: PairingOptions,
|
|
168
|
-
):
|
|
177
|
+
): Pairing {
|
|
169
178
|
const { code, expires } = mintCode(caller, signedIn)
|
|
170
179
|
const site = `${appOrigin() ?? new URL(request().url).origin}${mounted('')}`
|
|
171
|
-
const
|
|
180
|
+
const granted = tools?.length ? tools.join(',') : undefined
|
|
181
|
+
const query = new URLSearchParams({ site, code, version: BRIDGE_VERSION })
|
|
182
|
+
if (granted !== undefined) query.set('tools', granted)
|
|
183
|
+
const grant = granted === undefined ? '' : ` -- --tools ${granted}`
|
|
172
184
|
return {
|
|
173
185
|
command: `bunx @abide/bridge@${BRIDGE_VERSION} ${site} ${code}${grant}`,
|
|
186
|
+
link: `${BRIDGE_SCHEME}://pair?${query}`,
|
|
174
187
|
expires,
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
/**
|
|
179
|
-
* This browser's pairing as it changes: unpaired with the command to run
|
|
180
|
-
* bridge connects, and unpaired again if it goes. 44.20.
|
|
192
|
+
* This browser's pairing as it changes: unpaired with the command to run and the link to open, then
|
|
193
|
+
* paired the moment a bridge connects, and unpaired again if it goes. 44.20.
|
|
181
194
|
*
|
|
182
195
|
* 44.34 — IT FOLLOWS WHOEVER SIGNS IN AT THE BROWSER, though its own request carries the cookies it
|
|
183
196
|
* opened with: a sign-in or a sign-out made since is the pairing it watches from then on, and the
|
|
@@ -203,7 +216,7 @@ async function* bridge(
|
|
|
203
216
|
rouse()
|
|
204
217
|
}
|
|
205
218
|
signal.addEventListener('abort', rouse)
|
|
206
|
-
let pairing:
|
|
219
|
+
let pairing: Pairing | undefined
|
|
207
220
|
let said = ''
|
|
208
221
|
try {
|
|
209
222
|
while (!signal.aborted) {
|
|
@@ -225,7 +238,9 @@ async function* bridge(
|
|
|
225
238
|
const status: BridgeStatus = {
|
|
226
239
|
paired,
|
|
227
240
|
command: paired ? undefined : pairing?.command,
|
|
241
|
+
link: paired ? undefined : pairing?.link,
|
|
228
242
|
}
|
|
243
|
+
// The link is minted with the command, so the command alone tells a new pairing.
|
|
229
244
|
const saying = `${status.paired} ${status.command}`
|
|
230
245
|
if (saying !== said) {
|
|
231
246
|
said = saying
|