@abide/abide 0.119.0 → 0.120.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @abide/abide
2
2
 
3
+ ## 0.120.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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))
8
+
3
9
  ## 0.119.0
4
10
 
5
11
  ### 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 pairs a bridge with this browser, granting the built-in tools it is given. | 44.14, 44.32, 44.33 |
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 pairs one where none is. | 44.20 |
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
package/docs/RULEBOOK.md CHANGED
@@ -3398,6 +3398,19 @@ its type, in its place in its turn.
3398
3398
  44.39 `assistant` MUST hand a `Provider` each turn as its `AssistantContent` pieces, a turn's string
3399
3399
  as one text piece.
3400
3400
 
3401
+ 44.40 A pairing `claudeCode.bridgeCommand` or `claudeCode.bridge` mints MUST carry, beside its command,
3402
+ a link in the `abide-bridge` scheme naming the same site, code, bridge release and built-in tools.
3403
+
3404
+ 44.41 A bridge MUST open only a link of the `abide-bridge` scheme whose site is https or loopback and
3405
+ whose release is a version alone, and MUST run the release it names.
3406
+
3407
+ 44.42 A bridge opening an `abide-bridge` link MUST ask the visitor before it pairs, every time, naming
3408
+ the site, the built-in tools and the bridge release the link asks for.
3409
+
3410
+ 44.43 `abide-bridge install` MUST write the app that opens links under the visitor's own Application
3411
+ Support and register it there, and `abide-bridge uninstall` MUST unregister it and remove all
3412
+ install wrote.
3413
+
3401
3414
  # 45. Offline
3402
3415
 
3403
3416
  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: asking is refused, the bridge shows a command', async () => {
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 the line without the stream. Its code expires unused after ten minutes. |
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 pairs 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) |
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 pairs one where none is. | [44.20](/rulebook#44.20) |
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.119.0",
3
+ "version": "0.120.0",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
@@ -0,0 +1,3 @@
1
+ // 44.40 — the scheme a pairing link is written in, which the handler `abide-bridge install` registers
2
+ // opens. The bridge spells it out as `PAIRING_SCHEME`, and a test holds the two equal.
3
+ export const BRIDGE_SCHEME = 'abide-bridge'
@@ -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.5'
3
+ export const BRIDGE_VERSION = '0.4.0'
@@ -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
- /** Whether this browser's bridge is connected, and the command that pairs one where it is not. */
54
- export type BridgeStatus = { paired: boolean; command: string | undefined }
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 pairs a bridge with this browser, and when its code stops standing. 44.14. The
154
- * `tools` it is given are written after its `--`, so the visitor running it grants them (44.29, 44.32).
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
- ): { command: string; expires: number } {
177
+ ): Pairing {
169
178
  const { code, expires } = mintCode(caller, signedIn)
170
179
  const site = `${appOrigin() ?? new URL(request().url).origin}${mounted('')}`
171
- const grant = tools?.length ? ` -- --tools ${tools.join(',')}` : ''
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, then paired the moment a
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: { command: string; expires: number } | undefined
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