create-bridgething 0.10.0 → 0.12.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/README.md +16 -12
- package/dist/index.mjs +86 -21
- package/package.json +9 -3
- package/template/CLAUDE.md +60 -84
- package/template/README.md +31 -44
- package/template/_claude/skills/bridgething/SKILL.md +12 -17
- package/template/_claude/skills/bridgething/reference/develop.md +84 -65
- package/template/_claude/skills/bridgething/reference/extension.md +183 -0
- package/template/_claude/skills/bridgething/reference/sdk.md +74 -106
- package/template/_claude/skills/bridgething/reference/ship.md +19 -38
- package/template/_gitignore +1 -0
- package/template/package.json +6 -5
- package/template/scripts/bridgething.ts +1086 -0
- package/template/scripts/push.ts +212 -193
- package/template/src/App.tsx +2 -5
- package/template/src/daemon.ts +14 -0
- package/template/vite.config.ts +5 -3
- package/template-extension/_claude_append.md +23 -0
- package/template-extension/extension/main.ts +20 -0
- package/template-launcher/_claude_append.md +14 -30
- package/template-launcher/src/App.tsx +2 -5
- package/template-overlay/_claude_append.md +31 -45
|
@@ -1,61 +1,38 @@
|
|
|
1
1
|
# The webapp SDK (`@bridgething/client`)
|
|
2
2
|
|
|
3
|
-
A webapp reaches the daemon only through this one client: a typed facade over a
|
|
4
|
-
local WebSocket. You never open sockets or build wire messages yourself.
|
|
5
|
-
|
|
6
|
-
## Contents
|
|
7
|
-
|
|
8
|
-
- Read the types (the `.d.ts` files are the source of truth)
|
|
9
|
-
- Connect
|
|
10
|
-
- The three call shapes (events / requests / commands)
|
|
11
|
-
- Cookbook (player, asset, store, config, net, library)
|
|
12
|
-
- Full surface index (all 18)
|
|
13
|
-
- Gotchas
|
|
14
|
-
|
|
15
3
|
## Read the types
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
method, event, and field carries a doc comment.
|
|
5
|
+
The shipped declarations carry a doc comment on every method, event, and field.
|
|
19
6
|
|
|
20
|
-
- `node_modules/@bridgething/client/dist/dispatch.generated.d.ts`
|
|
21
|
-
`client.<surface>` method and event
|
|
22
|
-
- `node_modules/@bridgething/lib/dist/bindings
|
|
23
|
-
|
|
24
|
-
per-field docs. `client.d.ts` is the webapp surface; `shared.ts` holds the
|
|
25
|
-
shared data types.
|
|
26
|
-
|
|
27
|
-
Useful greps:
|
|
7
|
+
- `node_modules/@bridgething/client/dist/dispatch.generated.d.ts` has every
|
|
8
|
+
`client.<surface>` method and event with its argument and return types.
|
|
9
|
+
- `node_modules/@bridgething/lib/dist/bindings/client.d.ts` has the request and
|
|
10
|
+
reply payloads. `shared.d.ts` has the data types.
|
|
28
11
|
|
|
29
12
|
```bash
|
|
30
13
|
grep -n 'class PlayerSurface' -A40 node_modules/@bridgething/client/dist/dispatch.generated.d.ts
|
|
31
|
-
grep -n 'export type PlayerState ' -A12 node_modules/@bridgething/lib/dist/bindings/shared.ts
|
|
14
|
+
grep -n 'export type PlayerState ' -A12 node_modules/@bridgething/lib/dist/bindings/shared.d.ts
|
|
32
15
|
```
|
|
33
16
|
|
|
34
|
-
The `.d.ts` is the source of truth; this file is the map.
|
|
35
|
-
|
|
36
17
|
## Connect
|
|
37
18
|
|
|
38
19
|
```ts
|
|
39
20
|
import { BridgethingClient } from '@bridgething/client';
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const client = new BridgethingClient({ url:
|
|
22
|
+
import { daemonUrl } from './daemon';
|
|
23
|
+
|
|
24
|
+
const client = new BridgethingClient({ url: daemonUrl() });
|
|
44
25
|
```
|
|
45
26
|
|
|
46
|
-
Construct once and reuse
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
peer; you never address anything.
|
|
27
|
+
Construct once and reuse, a `useMemo` in React. Watch the link with
|
|
28
|
+
`client.on(e => ...)`, where `e.type` is `open`, `close`, `connecting`, or
|
|
29
|
+
`message`, and read `client.connectionState`, one of
|
|
30
|
+
`'connecting' | 'open' | 'closing' | 'closed'`.
|
|
51
31
|
|
|
52
32
|
## The three call shapes
|
|
53
33
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
**1. Events** - the daemon pushes; you subscribe. `onXxx` returns an unsubscribe
|
|
58
|
-
function (call it in cleanup). Or `subscribe({...})` for several at once.
|
|
34
|
+
Events. `onXxx` returns an unsubscribe function to call in cleanup.
|
|
35
|
+
`subscribe({...})` takes several at once.
|
|
59
36
|
|
|
60
37
|
```ts
|
|
61
38
|
const off = client.player.onSnapshot(reply => setState(reply.state));
|
|
@@ -63,8 +40,7 @@ const off = client.player.onSnapshot(reply => setState(reply.state));
|
|
|
63
40
|
off();
|
|
64
41
|
```
|
|
65
42
|
|
|
66
|
-
|
|
67
|
-
check `.ok`.
|
|
43
|
+
Requests. Check `.ok` on every result.
|
|
68
44
|
|
|
69
45
|
```ts
|
|
70
46
|
const res = await client.player.stateGet();
|
|
@@ -72,8 +48,8 @@ if (res.ok) console.log(res.response.state);
|
|
|
72
48
|
else console.warn(res.kind, res.error); // kind: 'domain' | 'protocol'
|
|
73
49
|
```
|
|
74
50
|
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
Commands. The returned `Promise<void>` resolves when the daemon has taken the
|
|
52
|
+
message, not when the phone finished acting.
|
|
77
53
|
|
|
78
54
|
```ts
|
|
79
55
|
await client.player.skipNext();
|
|
@@ -81,31 +57,31 @@ await client.player.skipNext();
|
|
|
81
57
|
|
|
82
58
|
## Cookbook
|
|
83
59
|
|
|
84
|
-
|
|
60
|
+
`client.player`:
|
|
85
61
|
|
|
86
62
|
```ts
|
|
87
63
|
client.player.onSnapshot(r => setState(r.state)); // full PlayerState on every material change
|
|
88
64
|
client.player.stateGet().then(r => r.ok && setState(r.response.state)); // prime on mount
|
|
89
65
|
|
|
90
|
-
// PlayerState: { track
|
|
66
|
+
// PlayerState: { track: MediaItem | null, playback: Playback, queue, options, context }
|
|
91
67
|
// track.title / track.artist / track.album / track.artworkId / track.durationMs
|
|
92
68
|
// playback.state: 'stopped' | 'paused' | 'playing'; playback.positionMs; playback.shuffle
|
|
93
69
|
|
|
94
70
|
client.player.play({ uri: 'spotify:track:...' });
|
|
95
|
-
client.player.pause();
|
|
96
|
-
client.player.
|
|
71
|
+
client.player.pause();
|
|
72
|
+
client.player.resume();
|
|
73
|
+
client.player.skipNext();
|
|
74
|
+
client.player.skipPrev({ allowSeeking: true }); // true restarts the track if it is progressed
|
|
97
75
|
client.player.seekTo({ positionMs: 30_000 });
|
|
98
76
|
client.player.setShuffle({ on: true });
|
|
99
|
-
client.player.setRepeat({ mode: 'all' });
|
|
77
|
+
client.player.setRepeat({ mode: 'all' }); // 'off' | 'all' | 'one'
|
|
100
78
|
```
|
|
101
79
|
|
|
102
|
-
|
|
103
|
-
seek
|
|
104
|
-
|
|
80
|
+
`onSnapshot` fires on material changes such as a track change, play, pause, and
|
|
81
|
+
seek. For a smooth progress bar, extrapolate the playhead between snapshots from
|
|
82
|
+
`playback.state` and `playback.positionMs`.
|
|
105
83
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Player state gives opaque asset ids, never URLs. Fetch bytes and wrap a blob:
|
|
84
|
+
`client.asset`. Player state carries opaque asset ids:
|
|
109
85
|
|
|
110
86
|
```ts
|
|
111
87
|
const res = await client.asset.get({ id: track.artworkId, requestId: crypto.randomUUID() });
|
|
@@ -116,16 +92,16 @@ if (res.ok) {
|
|
|
116
92
|
}
|
|
117
93
|
```
|
|
118
94
|
|
|
119
|
-
|
|
95
|
+
`client.store`, per-webapp key and value that survives restarts:
|
|
120
96
|
|
|
121
97
|
```ts
|
|
122
98
|
await client.store.put({ key: 'theme', value: 'dark' });
|
|
123
|
-
const r = await client.store.get({ key: 'theme' });
|
|
99
|
+
const r = await client.store.get({ key: 'theme' }); // r.ok && r.response.value
|
|
124
100
|
await client.store.delete({ key: 'theme' });
|
|
125
101
|
```
|
|
126
102
|
|
|
127
|
-
|
|
128
|
-
|
|
103
|
+
`client.config`, the values declared in `manifest.json`. The companion app writes
|
|
104
|
+
them, the webapp reads them:
|
|
129
105
|
|
|
130
106
|
```ts
|
|
131
107
|
client.config.onChanged(c => applySetting(c.key, c.value));
|
|
@@ -133,63 +109,55 @@ const r = await client.config.get({ key: 'units' });
|
|
|
133
109
|
const all = await client.config.list();
|
|
134
110
|
```
|
|
135
111
|
|
|
136
|
-
The
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
NETWORK CAVEAT for the settings page: it runs on the phone with real internet,
|
|
143
|
-
but it loads from a `file://` origin and the webview enforces CORS on
|
|
144
|
-
fetch/XHR. WebSocket APIs work (the WS handshake is not CORS-gated); plain HTTP
|
|
145
|
-
APIs work only when the server sends permissive CORS headers (requests arrive
|
|
146
|
-
with `Origin: null`). Prefer the service's websocket API from the settings
|
|
147
|
-
page; for a CORS-strict HTTP-only service, fetch from the device webapp via
|
|
148
|
-
`client.net` (phone-tunneled, not origin-restricted) and keep the settings page
|
|
149
|
-
for typing and choosing.
|
|
112
|
+
The settings page under `settings/` reads and writes them through
|
|
113
|
+
`@bridgething/client/settings`, a `postMessage` bridge. It runs on the phone with
|
|
114
|
+
real internet, but loads from a `file://` origin and the webview enforces CORS on
|
|
115
|
+
fetch and XHR. WebSocket APIs work. Plain HTTP APIs work when the server sends
|
|
116
|
+
permissive CORS headers, since requests arrive with `Origin: null`. For a
|
|
117
|
+
CORS-strict HTTP-only service, fetch through `client.net` instead.
|
|
150
118
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`net.proxy`). Read the `NetSurface` block in the `.d.ts` for the request shapes.
|
|
119
|
+
`client.net` tunnels HTTP, websockets, and a SOCKS proxy through the phone. The
|
|
120
|
+
SOCKS proxy needs `"net.proxy"` in the manifest's `permissions`. Read the
|
|
121
|
+
`NetSurface` block in the `.d.ts` for the request shapes.
|
|
155
122
|
|
|
156
|
-
|
|
157
|
-
`favoritesList
|
|
158
|
-
|
|
123
|
+
`client.library` is Spotify-backed: `browse`, `search`, `recommendations`,
|
|
124
|
+
`favoritesList`, and `favoritesContains` are requests; `favoritesToggle` and
|
|
125
|
+
`favoritesSet` are commands; `onFavoriteChanged` is an event.
|
|
159
126
|
|
|
160
|
-
##
|
|
127
|
+
## Every surface
|
|
161
128
|
|
|
162
|
-
|
|
163
|
-
`dispatch.generated.d.ts` for its exact methods and types.
|
|
129
|
+
Read a surface's class in `dispatch.generated.d.ts` for its exact methods.
|
|
164
130
|
|
|
165
131
|
| Surface | What it does | Notable methods |
|
|
166
132
|
| --- | --- | --- |
|
|
167
|
-
| `player` | now-playing
|
|
168
|
-
| `asset` | fetch blobs by opaque id | get, preload, onReady
|
|
169
|
-
| `store` | per-webapp key
|
|
133
|
+
| `player` | now-playing and transport | onSnapshot, stateGet, play, pause, resume, skipNext, skipPrev, seekTo, setShuffle, setRepeat, queueGet |
|
|
134
|
+
| `asset` | fetch blobs by opaque id | get, preload, onReady, onCleared |
|
|
135
|
+
| `store` | per-webapp key and value | get, put, delete |
|
|
170
136
|
| `config` | user settings from the manifest | get, list, onChanged |
|
|
171
|
-
| `capabilities` | which
|
|
172
|
-
| `library` | Spotify browse
|
|
173
|
-
| `audio` | volume, mute, TTS, earcons | volumeUp
|
|
174
|
-
| `notifications` | phone notification actions | invokePositive
|
|
175
|
-
| `phone` | call control
|
|
176
|
-
| `geo` | location watches
|
|
177
|
-
| `net` | HTTP
|
|
178
|
-
| `hardware` | backlight
|
|
179
|
-
| `bluetooth` | adapter alias
|
|
180
|
-
| `system` | version, logs, power, diagnostics | versionRequest, logsTail
|
|
181
|
-
| `time` | wall clock
|
|
182
|
-
| `voice` | mic
|
|
183
|
-
| `webapp` |
|
|
184
|
-
| `forward` |
|
|
137
|
+
| `capabilities` | which companion features are live | get, onSnapshot |
|
|
138
|
+
| `library` | Spotify browse, search, favorites | browse, search, recommendations, favoritesList, favoritesContains, favoritesToggle, favoritesSet, onFavoriteChanged |
|
|
139
|
+
| `audio` | volume, mute, TTS, earcons | volumeUp, volumeDown, setVolume, muteToggle, tts, earcon |
|
|
140
|
+
| `notifications` | phone notification actions | invokePositive, invokeNegative, onPosted, onRemoved, onUpdated |
|
|
141
|
+
| `phone` | call control | stateGet, accept, end, initiate, mute, dtmf, onCallStarted, onCallUpdated, onCallEnded |
|
|
142
|
+
| `geo` | location watches, sourced from the phone | watch, unwatch, getOnce, onPosition |
|
|
143
|
+
| `net` | HTTP, WS, and SOCKS through the phone | fetch, wsOpen, wsSend, wsClose, streamOpen, streamCancel |
|
|
144
|
+
| `hardware` | backlight and ambient light sensor | displaySetMode, displaySetLevel, stateGet, onAmbientLightUpdate, onBrightnessChanged |
|
|
145
|
+
| `bluetooth` | adapter alias, bonds, discoverable | list, connect, forget, setAlias, enableDiscoverable |
|
|
146
|
+
| `system` | version, logs, power, diagnostics | versionRequest, logsTail, logsSubscribe, reboot, powerOff, factoryReset, onVersion, onLogEntry, onOtaProgress |
|
|
147
|
+
| `time` | wall clock | get, onSnapshot, onChanged |
|
|
148
|
+
| `voice` | mic and push-to-talk, capability-gated | pushToTalk, cancel, muteMic, stateGet |
|
|
149
|
+
| `webapp` | list and activate installed webapps | list, current, activate, icon |
|
|
150
|
+
| `forward` | passthrough to the desktop-side extension | text, json, binary, onText, onJson, onBinary |
|
|
151
|
+
| `doc` | per-webapp documents the companion also writes | get, list, set, delete, onChanged |
|
|
152
|
+
| `lyrics` | timed lyrics for the current track | get |
|
|
153
|
+
| `peer` | every known peer and its link state | onSnapshot |
|
|
185
154
|
|
|
186
155
|
## Gotchas
|
|
187
156
|
|
|
188
|
-
-
|
|
189
|
-
protocol error
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
`phone` on iOS). Check `client.capabilities` and handle absence.
|
|
157
|
+
- Check `.ok` on every request result. The daemon answers with a domain or
|
|
158
|
+
protocol error when it cannot serve the call, such as `player.play` with a uri
|
|
159
|
+
no companion claims.
|
|
160
|
+
- Write the app so it works with no phone connected. `player`, `library`, and
|
|
161
|
+
`net` all depend on the companion app.
|
|
162
|
+
- Treat asset ids as opaque. Pass them to `client.asset.get` as they arrive.
|
|
163
|
+
- Read `client.capabilities` before using `voice` and `phone`.
|
|
@@ -1,65 +1,46 @@
|
|
|
1
1
|
# Installing and sharing the app
|
|
2
2
|
|
|
3
|
-
Two outputs: install onto your own connected device, and a zip anyone can install
|
|
4
|
-
through the bridgething companion app.
|
|
5
|
-
|
|
6
3
|
## Install onto a connected Car Thing
|
|
7
4
|
|
|
8
5
|
```bash
|
|
9
|
-
bun run push # targets bridgething.local (device over USB)
|
|
10
|
-
bun run push <address> # or an explicit host
|
|
6
|
+
bun run push # targets bridgething.local (the device over USB)
|
|
7
|
+
bun run push <address> # or an explicit host or IP
|
|
11
8
|
```
|
|
12
9
|
|
|
13
|
-
`push` builds `dist/` itself,
|
|
14
|
-
|
|
15
|
-
must be reachable: connected over USB it answers to `bridgething.local`;
|
|
16
|
-
otherwise pass its address. If multiple devices are on the network, set
|
|
10
|
+
`push` builds `dist/` itself, copies it to the device, then switches the kiosk to
|
|
11
|
+
your app. With several devices on the network, set
|
|
17
12
|
`SUPERBIRD_HOST=bridgething-<serial>.local`.
|
|
18
13
|
|
|
19
|
-
Flags: `--skip-build`
|
|
20
|
-
activating.
|
|
21
|
-
the
|
|
22
|
-
save the device for final checks.
|
|
14
|
+
Flags: `--skip-build` copies whatever is already in `dist/`, and `--no-switch`
|
|
15
|
+
copies without activating. To iterate on hardware, use `bun run dev:device` from
|
|
16
|
+
the develop reference instead.
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
the matching
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`--release` gives the slots back to the built-in hub and overlay - the recovery
|
|
29
|
-
path when a build wedges the screen.
|
|
18
|
+
When `manifest.json` declares `role: launcher` or an `overlay`, `push` also
|
|
19
|
+
claims the matching slot. A launcher is switched to; an overlay is not, so pass
|
|
20
|
+
`--switch` to open its own page. `--no-slot` pushes without claiming a slot.
|
|
21
|
+
`--release` gives the slots back to the built-in hub and overlay.
|
|
30
22
|
|
|
31
23
|
## Make a shareable zip
|
|
32
24
|
|
|
33
25
|
```bash
|
|
34
|
-
bun run build # share
|
|
26
|
+
bun run build # share reads dist/, so produce it first
|
|
35
27
|
bun run share # writes <name>-<version>.zip from dist/
|
|
36
28
|
```
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
from the bridgething companion app (no rebuild, no dev tools). The zip is just
|
|
40
|
-
your built bundle with `manifest.json` at its root. `bun run build` also emits
|
|
41
|
-
`dist/settings.html` (the companion-side settings page); it ships in the same
|
|
42
|
-
bundle when `manifest.json` declares `settings`.
|
|
30
|
+
Anyone with a bridgething Car Thing installs that zip from the companion app.
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
tell builds apart. Never touch `manifest.json`'s `id` - it is the webapp's
|
|
46
|
-
identity and the install path on the device.
|
|
32
|
+
Raise `version` in `public/manifest.json` before you share an update. Keep `id`.
|
|
47
33
|
|
|
48
34
|
## Keep the device on the latest bridgething
|
|
49
35
|
|
|
50
36
|
```bash
|
|
51
|
-
bun run update # updates the connected Car Thing's daemon
|
|
37
|
+
bun run update # updates the connected Car Thing's daemon and image
|
|
52
38
|
```
|
|
53
39
|
|
|
54
|
-
|
|
55
|
-
uses, reads the release manifest, and brings the device to the latest `stable`
|
|
56
|
-
release.
|
|
57
|
-
|
|
58
|
-
Multiple devices? Target one explicitly:
|
|
40
|
+
Flags go after `--`:
|
|
59
41
|
|
|
60
42
|
```bash
|
|
61
|
-
bun run update -- --host ws://bridgething-<serial>.local:8892/
|
|
43
|
+
bun run update -- --host ws://bridgething-<serial>.local:8892/ # pick one of several devices
|
|
44
|
+
bun run update -- --channel <name> # track another channel
|
|
45
|
+
bun run update -- --version <composite-version> # install a specific release
|
|
62
46
|
```
|
|
63
|
-
|
|
64
|
-
Other flags: `-- --channel <name>` to track a non-stable channel,
|
|
65
|
-
`-- --daemon-only` to skip the image OTA.
|
package/template/_gitignore
CHANGED
package/template/package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
|
+
"dev:device": "vite --mode device",
|
|
8
9
|
"build": "vite build && vite build -c vite.settings.config.ts",
|
|
9
10
|
"preview": "vite preview",
|
|
10
11
|
"push": "bun scripts/push.ts",
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
"update": "bunx @bridgething/updater"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
|
-
"@bridgething/client": "^0.
|
|
16
|
+
"@bridgething/client": "^0.12.0",
|
|
16
17
|
"preact": "^10.29.7",
|
|
17
18
|
"react": "^19.2.0",
|
|
18
19
|
"react-dom": "^19.2.0"
|
|
@@ -20,14 +21,14 @@
|
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@msgpack/msgpack": "^3.1.3",
|
|
22
23
|
"@preact/preset-vite": "^2.10.5",
|
|
23
|
-
"@tailwindcss/vite": "^4.
|
|
24
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
24
25
|
"@types/react": "^19.2.10",
|
|
25
26
|
"@types/react-dom": "^19.2.3",
|
|
26
|
-
"@vitejs/plugin-react": "^
|
|
27
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
27
28
|
"fflate": "^0.8.2",
|
|
28
|
-
"tailwindcss": "^4.
|
|
29
|
+
"tailwindcss": "^4.3.3",
|
|
29
30
|
"typescript": "^6.0.3",
|
|
30
|
-
"vite": "^
|
|
31
|
+
"vite": "^8.2.2",
|
|
31
32
|
"vite-plugin-singlefile": "^2.3.3"
|
|
32
33
|
}
|
|
33
34
|
}
|