thinkpool-pair 0.7.327 → 0.7.328
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 +236 -199
- package/package.json +1 -1
- package/terminal-name.mjs +15 -4
package/README.md
CHANGED
|
@@ -1,235 +1,272 @@
|
|
|
1
|
-
# thinkpool-pair
|
|
1
|
+
# `thinkpool-pair`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`thinkpool-pair` connects coding agents running on a machine you control to a
|
|
4
|
+
Thinkpool Code room. Both people in the room can watch and steer the visible
|
|
5
|
+
agent lanes from a browser or phone.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
The bridge makes outbound connections to Thinkpool’s Supabase project. It does
|
|
8
|
+
not require an inbound port, tunnel, or public IP.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
> you control (a cheap VM, a home server, a Raspberry Pi) so the agent stays live
|
|
12
|
-
> with your laptop closed. It's outbound-only — no ports, no public IP. See
|
|
13
|
-
> [**Run it in the cloud**](#run-it-in-the-cloud-remote-host--vm--container) below.
|
|
10
|
+
## Requirements
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
- Node.js 18 or newer
|
|
13
|
+
- A Thinkpool account and Code session
|
|
14
|
+
- At least one supported runtime available on the host:
|
|
15
|
+
- Claude
|
|
16
|
+
- Codex
|
|
17
|
+
- Hermes
|
|
18
|
+
- The provider login or API credentials required by that runtime
|
|
19
|
+
|
|
20
|
+
## Start the bridge
|
|
21
|
+
|
|
22
|
+
Run the launcher from the project directory the agents should use:
|
|
16
23
|
|
|
17
24
|
```bash
|
|
18
|
-
|
|
19
|
-
node bridge.mjs <ROOM> # picks an installed agent, shared into room <ROOM>
|
|
25
|
+
npx thinkpool-pair@latest
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
The
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **Boot-persistent service** — survive reboot/logout too. Installs the right
|
|
37
|
-
native service for your OS (launchd on macOS, systemd `--user` on Linux, a
|
|
38
|
-
Startup-folder script on Windows):
|
|
39
|
-
```bash
|
|
40
|
-
npx thinkpool-pair@latest install-service <ROOM> -- claude # set and forget
|
|
41
|
-
npx thinkpool-pair@latest uninstall-service <ROOM> # remove it
|
|
42
|
-
```
|
|
43
|
-
The service runs `npx thinkpool-pair@latest`, so it **auto-updates** — new
|
|
44
|
-
versions apply on the next restart, no re-install. (Linux: run
|
|
45
|
-
`loginctl enable-linger $USER` once to keep it running after logout.)
|
|
46
|
-
- **Watchdog** — if the realtime channel wedges for >60s, the bridge exits so the
|
|
47
|
-
supervisor/service restarts a clean process. Brief network blips reconnect on
|
|
48
|
-
their own.
|
|
49
|
-
|
|
50
|
-
Structured Claude sessions persist their scrollback + session id to
|
|
51
|
-
`~/.thinkpool-pair/<ROOM>/` and **resume on restart** (live context if recent),
|
|
52
|
-
so none of the above loses your place.
|
|
53
|
-
|
|
54
|
-
### Codex App Server driver
|
|
55
|
-
|
|
56
|
-
Structured Codex lanes use the long-lived App Server transport (streaming prose,
|
|
57
|
-
mid-turn steering, shared approval and question cards, and native image inputs)
|
|
58
|
-
when the installed Codex CLI is in the bridge's tested protocol range. To force
|
|
59
|
-
the stable `codex exec --json` fallback, set the kill switch before starting the
|
|
60
|
-
bridge:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
export TP_CODEX_APP_SERVER=0
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The bridge performs the initialize handshake before creating a thread and falls
|
|
67
|
-
back to `codex exec` when App Server is disabled, outside the tested version
|
|
68
|
-
range, or cannot initialize before a turn. It never retries a prompt via the
|
|
69
|
-
fallback after `turn/start` has been accepted, preventing duplicate work.
|
|
70
|
-
|
|
71
|
-
Your first agent runs **attached** — use it exactly like your normal terminal,
|
|
72
|
-
every byte mirrors to the web. The web's **"+ New terminal"** spawns additional
|
|
73
|
-
**headless** terminals here (same directory, same env), driven entirely from
|
|
74
|
-
the room. One bridge, many terminals.
|
|
75
|
-
|
|
76
|
-
### Built-in viewport capture
|
|
77
|
-
|
|
78
|
-
Structured Claude and Codex lanes have bridge-owned visual QA tools, even when
|
|
79
|
-
their own sandbox cannot bind localhost or launch Chrome:
|
|
80
|
-
|
|
81
|
-
- `preview_start` serves a built directory inside that lane's workspace
|
|
82
|
-
(`dist` by default; it must contain `index.html`).
|
|
83
|
-
- `preview_capture` returns exact desktop (1440×900) and mobile (390×844)
|
|
84
|
-
screenshots to the agent as verification evidence. It creates no transcript
|
|
85
|
-
artifact by default. Pass `card: true` only for an intentional user-facing
|
|
86
|
-
mockup/Design deliverable; a settled complete pair is then queued after the
|
|
87
|
-
agent's final response. Loading, empty, and public-auth fallback shells are
|
|
88
|
-
rejected before a card can be created.
|
|
89
|
-
- `preview_inspect` returns rendered DOM text, document size, and optional
|
|
90
|
-
selector geometry at either viewport.
|
|
91
|
-
- `preview_stop` releases the preview port.
|
|
92
|
-
|
|
93
|
-
The bridge launches the host's Chrome/Chromium lazily. Set `TP_BROWSER_PATH`
|
|
94
|
-
if it is installed somewhere non-standard. Preview files are read-only, roots
|
|
95
|
-
cannot escape the lane workspace (including through symlinks), and page network
|
|
96
|
-
requests are restricted to the preview server's exact loopback origin. The
|
|
97
|
-
tools never execute a caller-supplied command or open an arbitrary URL.
|
|
98
|
-
|
|
99
|
-
## Run it in the cloud (remote host / VM / container)
|
|
100
|
-
|
|
101
|
-
The bridge connects **outbound** to Supabase — no inbound ports, no public IP, no
|
|
102
|
-
tunnel. So a cloud VM, container, devcontainer, or remote dev box can stream its
|
|
103
|
-
Claude Code into the room exactly like a laptop. Run the same command there:
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
export ANTHROPIC_API_KEY=sk-ant-... # headless auth (no Keychain login)
|
|
107
|
-
cd /path/to/your/repo
|
|
108
|
-
npx thinkpool-pair@latest <ROOM> -- claude # structured Claude, no TTY needed
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
- **Auth** — two ways to sign in on a box with no browser. Both are your own
|
|
112
|
-
account on your own hardware, so both are fine:
|
|
113
|
-
- **API key** — `export ANTHROPIC_API_KEY=sk-ant-...` from your Anthropic
|
|
114
|
-
Console. Simplest, pay-per-token, cleanest cost tracking.
|
|
115
|
-
- **Your Claude subscription** — a server has no browser, so mint a long-lived
|
|
116
|
-
token *on a machine that does*: run `claude setup-token` there (prints a
|
|
117
|
-
~1-year `CLAUDE_CODE_OAUTH_TOKEN`), then `export CLAUDE_CODE_OAUTH_TOKEN=...`
|
|
118
|
-
on the box. Uses your existing Pro/Max plan — no per-token bill.
|
|
119
|
-
|
|
120
|
-
The Agent SDK reads either one automatically and runs its bundled `claude`, so
|
|
121
|
-
you don't need Claude Code installed separately. (Note: this is *you* running
|
|
122
|
-
*your* login on *your* machine. Handing a subscription token to a third-party
|
|
123
|
-
host to run for you is what Anthropic's terms forbid — that's why a future
|
|
124
|
-
"we host it for you" option would require an API key, not your subscription.)
|
|
125
|
-
- **Use `@latest`**: a bare `npx thinkpool-pair` reuses npx's local cache and can
|
|
126
|
-
run a stale version across restarts. `@latest` (and the service below) always
|
|
127
|
-
re-resolve the newest publish.
|
|
128
|
-
- **No TTY required**: the agent picker auto-selects and raw-mode is skipped when
|
|
129
|
-
there's no terminal, so it runs fine under CI / a service / `nohup`.
|
|
130
|
-
- **Always-on + auto-update**: `npx thinkpool-pair@latest install-service <ROOM> -- claude`
|
|
131
|
-
installs a systemd `--user` unit (auto-restart + boot-persistent). The bridge
|
|
132
|
-
polls npm for new publishes and **self-restarts at the next idle moment** into
|
|
133
|
-
the new version — the structured session resumes, so an away/unattended box
|
|
134
|
-
upgrades itself with nobody losing their place. On a server, also run
|
|
135
|
-
`loginctl enable-linger $USER` so it survives logout.
|
|
136
|
-
Tune with `THINKPOOL_PAIR_UPDATE_INTERVAL` (poll seconds, default 1800) and
|
|
137
|
-
`THINKPOOL_PAIR_UPDATE_IDLE` (idle seconds before restart, default 90). Live
|
|
138
|
-
auto-update applies to the launchd/systemd service tiers; bare `--supervise`
|
|
139
|
-
respawns the same version, so it updates only on the next manual/boot restart.
|
|
140
|
-
- **Security**: anyone with the room code can drive the agent (shell-trust by
|
|
141
|
-
design) — on a server that's a real shell, so treat the room code like a secret.
|
|
142
|
-
|
|
143
|
-
## How it works
|
|
144
|
-
|
|
145
|
-
`bridge.mjs` ⇄ **Supabase realtime** (`tpcode:<ROOM>`) ⇄ web `xterm`:
|
|
146
|
-
- `bridge` — announce: installed agents + live terminals.
|
|
147
|
-
- `pty-out` — terminal bytes (base64, per-terminal) → web clients render them.
|
|
148
|
-
- `pty-in` — keystrokes/prompts from the web → written to that terminal's PTY.
|
|
149
|
-
- `term-open` / `term-close` / `term-exit` — web-driven terminal lifecycle.
|
|
150
|
-
- `replay-request` / `pty-replay` — each terminal keeps a rolling ~120 KB
|
|
151
|
-
scrollback buffer for live recovery. Thinkpool also stores the cleaned reader
|
|
152
|
-
transcript and room events so members can reopen the room. It does not store
|
|
153
|
-
a copy of the repository or the raw PTY byte stream.
|
|
154
|
-
- `resize` — web viewport size → headless PTYs only (the attached terminal
|
|
155
|
-
follows your own TTY).
|
|
156
|
-
|
|
157
|
-
## Inspect the privacy boundary
|
|
158
|
-
|
|
159
|
-
Run a local report before pairing a repository:
|
|
28
|
+
The launcher shows the linked account, detected runtimes, current directory,
|
|
29
|
+
and background-service state. On first use it asks to link the machine in a
|
|
30
|
+
browser. Then choose one of:
|
|
31
|
+
|
|
32
|
+
- **Serve all my sessions** — runs in the current terminal until `Ctrl-C`.
|
|
33
|
+
- **Always-on background service** — starts at login/reboot and restarts after a
|
|
34
|
+
crash. The normal install is pinned to the confirmed package version; use the
|
|
35
|
+
launcher’s **Restart & update bridge** action when a new version should land.
|
|
36
|
+
|
|
37
|
+
Back in the room, open the runtime/model you want for each terminal.
|
|
38
|
+
|
|
39
|
+
## Account and service commands
|
|
40
|
+
|
|
41
|
+
Link the machine without opening the full launcher:
|
|
160
42
|
|
|
161
43
|
```bash
|
|
162
|
-
npx thinkpool-pair@latest
|
|
44
|
+
npx thinkpool-pair@latest login
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Install or remove the account-wide background service:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx thinkpool-pair@latest install-service
|
|
51
|
+
npx thinkpool-pair@latest restart-service
|
|
52
|
+
npx thinkpool-pair@latest uninstall-service
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`install-service` is pinned by default so a later bad npm publish cannot
|
|
56
|
+
silently replace a working unattended bridge. Opt into tracking `@latest` only
|
|
57
|
+
when that trade-off is intentional:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx thinkpool-pair@latest install-service --auto-update
|
|
163
61
|
```
|
|
164
62
|
|
|
165
|
-
|
|
166
|
-
present under `~/.thinkpool-pair`, known outbound service domains, what
|
|
167
|
-
Thinkpool stores remotely, and the limits of the report. It never prints a
|
|
168
|
-
provider key, refresh token, or bridge private key.
|
|
63
|
+
Service implementation by platform:
|
|
169
64
|
|
|
170
|
-
|
|
65
|
+
- macOS: LaunchAgent
|
|
66
|
+
- Linux: systemd user service; run `loginctl enable-linger "$USER"` once if it
|
|
67
|
+
must survive logout
|
|
68
|
+
- Windows: Startup-folder command file
|
|
171
69
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
that format:
|
|
70
|
+
On Windows the startup entry launches at login; update it from the launcher and
|
|
71
|
+
relaunch the bridge window to apply the new version.
|
|
175
72
|
|
|
176
|
-
|
|
177
|
-
- **Any other model via a translating gateway** — put **LiteLLM** (or a proxy of
|
|
178
|
-
your own) in front and run **GPT, Gemini, Llama, DeepSeek, or a local model**;
|
|
179
|
-
it presents the Anthropic Messages API while calling whatever you pick.
|
|
73
|
+
## Project directories
|
|
180
74
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
> Code expects Anthropic request semantics, so non-Anthropic models aren't
|
|
184
|
-
> supported through the native endpoint" — and this bridge spawns the `claude`
|
|
185
|
-
> CLI. To reach a non-Anthropic model, use a translating gateway (LiteLLM),
|
|
186
|
-
> not OpenRouter's Anthropic endpoint.
|
|
75
|
+
The account bridge serves sessions from their bound project directories. Set a
|
|
76
|
+
default for new/unbound sessions:
|
|
187
77
|
|
|
188
78
|
```bash
|
|
189
|
-
npx thinkpool-pair@latest
|
|
79
|
+
npx thinkpool-pair@latest set-default-dir /path/to/project
|
|
190
80
|
```
|
|
191
81
|
|
|
192
|
-
|
|
82
|
+
Bind one room explicitly when needed:
|
|
193
83
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
84
|
+
```bash
|
|
85
|
+
npx thinkpool-pair@latest bind <ROOM> /path/to/project
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Room codes are treated as secrets because room members can drive agents that
|
|
89
|
+
have access to the bound project and the host permissions granted to that lane.
|
|
200
90
|
|
|
201
|
-
|
|
202
|
-
> OpenAI-style `/v1/chat/completions` won't work directly — that's exactly what a
|
|
203
|
-
> gateway is for. Avoid LiteLLM 1.82.7 / 1.82.8 (compromised releases); use a
|
|
204
|
-
> current version.
|
|
91
|
+
## Runtimes and providers
|
|
205
92
|
|
|
206
|
-
|
|
207
|
-
|
|
93
|
+
| Runtime | Authentication/provider behavior |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Claude | Uses the host’s normal Anthropic login by default. A registered Anthropic-compatible provider can be selected for a lane. |
|
|
96
|
+
| Codex | Uses the host’s Codex/OpenAI login. Custom bridge providers are not wired to Codex lanes. |
|
|
97
|
+
| Hermes | Uses an isolated Thinkpool ACP profile. Set it up explicitly before the first Hermes lane. |
|
|
98
|
+
|
|
99
|
+
The launcher’s **Settings → Provider** flow configures the host default for
|
|
100
|
+
Claude-compatible lanes. The equivalent CLI commands are:
|
|
208
101
|
|
|
209
102
|
```bash
|
|
103
|
+
npx thinkpool-pair@latest provider
|
|
210
104
|
npx thinkpool-pair@latest provider anthropic
|
|
105
|
+
npx thinkpool-pair@latest provider custom --base <url> --token <key> --model <id>
|
|
211
106
|
```
|
|
212
107
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
108
|
+
The custom endpoint must implement the Anthropic Messages API. A raw
|
|
109
|
+
OpenAI-compatible chat-completions endpoint is not enough; place a translating
|
|
110
|
+
gateway in front of it or use the runtime that natively owns that provider.
|
|
216
111
|
|
|
217
|
-
|
|
112
|
+
Set up an isolated Hermes profile with one of:
|
|
218
113
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
114
|
+
```bash
|
|
115
|
+
npx thinkpool-pair@latest setup-hermes --clone
|
|
116
|
+
npx thinkpool-pair@latest setup-hermes --clean
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`--clone` copies the active Hermes provider/configuration into the isolated
|
|
120
|
+
profile. `--clean` creates the isolation boundary without copying credentials.
|
|
121
|
+
|
|
122
|
+
## Direct single-room mode
|
|
123
|
+
|
|
124
|
+
The account-wide launcher is the normal path. A direct room process is still
|
|
125
|
+
available for debugging or a deliberately single-room host:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx thinkpool-pair@latest <ROOM> -- claude
|
|
129
|
+
npx thinkpool-pair@latest <ROOM> -- codex
|
|
130
|
+
npx thinkpool-pair@latest <ROOM> -- hermes
|
|
131
|
+
```
|
|
224
132
|
|
|
225
|
-
|
|
133
|
+
Add `--headless` when the process should be driven only from the room. Direct
|
|
134
|
+
room services use the same command with the room code:
|
|
226
135
|
|
|
227
136
|
```bash
|
|
228
|
-
|
|
137
|
+
npx thinkpool-pair@latest install-service <ROOM> -- claude
|
|
138
|
+
npx thinkpool-pair@latest uninstall-service <ROOM>
|
|
229
139
|
```
|
|
230
140
|
|
|
231
|
-
|
|
232
|
-
|
|
141
|
+
## Cloud or always-on host
|
|
142
|
+
|
|
143
|
+
The bridge can run on a VM, dev box, home server, or container because its room
|
|
144
|
+
connection is outbound-only. Use the same launcher/service commands and provide
|
|
145
|
+
the runtime’s headless credentials through the host’s secret manager.
|
|
146
|
+
|
|
147
|
+
For Claude, either an Anthropic API key or a valid Claude Code OAuth token can
|
|
148
|
+
authenticate the runtime. Treat subscription/OAuth tokens as personal secrets;
|
|
149
|
+
do not hand them to a third-party host.
|
|
150
|
+
|
|
151
|
+
## What crosses the boundary
|
|
152
|
+
|
|
153
|
+
The bridge and web app deliberately split responsibility:
|
|
154
|
+
|
|
155
|
+
- Agent processes, repository access, provider credentials, and raw terminal
|
|
156
|
+
bytes remain on the bridge machine.
|
|
157
|
+
- Thinkpool relays room events and terminal output to authorized room members.
|
|
158
|
+
- Thinkpool stores the cleaned reader transcript and room events so members can
|
|
159
|
+
reopen the room.
|
|
160
|
+
- Thinkpool does not store a copy of the repository or a second copy of the raw
|
|
161
|
+
PTY byte stream.
|
|
162
|
+
- Voice dictation is an application API path and uses the configured
|
|
163
|
+
transcription service; it is not a local-only bridge operation.
|
|
164
|
+
|
|
165
|
+
Inspect the host-side boundary before pairing a sensitive repository:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npx thinkpool-pair@latest privacy-report
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The report lists configured project directories, local bridge records, known
|
|
172
|
+
outbound domains, remote storage categories, and the report’s limits. It does
|
|
173
|
+
not print provider keys, refresh tokens, or bridge private keys.
|
|
174
|
+
|
|
175
|
+
Structured session state is stored below `~/.thinkpool-pair/` so an agent lane
|
|
176
|
+
can resume after a bridge restart. Protect that directory like other local
|
|
177
|
+
developer credentials and session state.
|
|
178
|
+
|
|
179
|
+
## Visual verification tools
|
|
180
|
+
|
|
181
|
+
Structured Claude and Codex lanes can use bridge-owned preview tools even when
|
|
182
|
+
their own sandbox cannot bind a port or launch a browser:
|
|
183
|
+
|
|
184
|
+
- `preview_start` serves a built directory inside the lane worktree.
|
|
185
|
+
- `preview_capture` records exact desktop and mobile renders.
|
|
186
|
+
- `preview_inspect` reads rendered DOM text and geometry.
|
|
187
|
+
- `preview_stop` releases the preview server.
|
|
188
|
+
|
|
189
|
+
Preview roots are read-only, cannot escape the lane worktree, and accept only
|
|
190
|
+
the preview server’s loopback origin for page requests.
|
|
191
|
+
|
|
192
|
+
## Optional bridge environment variables
|
|
193
|
+
|
|
194
|
+
| Variable | Purpose |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `TP_NAME` | Label this bridge in the room |
|
|
197
|
+
| `TP_PAIR_ROOT` | Override the local state directory |
|
|
198
|
+
| `TP_SUPABASE_URL` / `TP_SUPABASE_ANON` | Override the built-in Thinkpool project endpoint/public key |
|
|
199
|
+
| `TP_ANTHROPIC_BASE_URL` / `TP_ANTHROPIC_AUTH_TOKEN` / `TP_ANTHROPIC_MODEL` | Override the Claude-compatible provider without writing provider config |
|
|
200
|
+
| `TP_CONTEXT_MAX` | Override the context-window size used by the room meter |
|
|
201
|
+
| `TP_BROWSER_PATH` | Point preview capture at a non-standard Chrome/Chromium binary |
|
|
202
|
+
| `TP_FLOW_OFF=1` | Disable Flow dispatch on this bridge |
|
|
203
|
+
| `TP_SPAWN_OFF=1` | Disable agent-spawned worker lanes |
|
|
204
|
+
| `TP_PAIRBUS_OFF=1` | Disable the paired-room bus |
|
|
205
|
+
| `TP_CROSSROOM_OFF=1` | Disable cross-session reach |
|
|
206
|
+
|
|
207
|
+
Provider and account configuration written by the launcher is preferred for a
|
|
208
|
+
managed service because shell startup files are not guaranteed to be sourced.
|
|
209
|
+
|
|
210
|
+
## Troubleshooting
|
|
211
|
+
|
|
212
|
+
### The room cannot see the bridge
|
|
213
|
+
|
|
214
|
+
Run the launcher and check its account/runtime/service header. If the account
|
|
215
|
+
link is missing or expired, rerun:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npx thinkpool-pair@latest login
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### A service is installed but not serving
|
|
222
|
+
|
|
223
|
+
Use the launcher’s restart/update action or:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npx thinkpool-pair@latest restart-service
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The install command prints the platform-specific log directory. Inspect that
|
|
230
|
+
log before reinstalling; repeated installation can hide the original failure.
|
|
231
|
+
|
|
232
|
+
### No runtimes are available
|
|
233
|
+
|
|
234
|
+
Install/sign in to Claude, Codex, or Hermes on the host, then restart the
|
|
235
|
+
launcher. Runtime availability is detected from the host; the web room cannot
|
|
236
|
+
install a missing CLI for you.
|
|
237
|
+
|
|
238
|
+
### A custom model fails immediately
|
|
239
|
+
|
|
240
|
+
Check that the endpoint implements Anthropic Messages semantics and that the
|
|
241
|
+
model ID exists on that provider. Codex lanes ignore the bridge’s custom Claude
|
|
242
|
+
provider configuration and use the Codex/OpenAI login.
|
|
243
|
+
|
|
244
|
+
### Preview capture cannot find Chrome
|
|
245
|
+
|
|
246
|
+
Install Chrome/Chromium on the bridge host or set `TP_BROWSER_PATH` to its
|
|
247
|
+
executable.
|
|
248
|
+
|
|
249
|
+
## Develop the bridge package
|
|
250
|
+
|
|
251
|
+
From the repository root:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
npm --prefix bridge ci
|
|
255
|
+
npm run test:bridge
|
|
256
|
+
node bridge/test-packed-artifact.mjs
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Run the checked-out launcher with:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
node bridge/bridge.mjs
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Publishing is separate from the web-app deploy:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm --prefix bridge run release
|
|
269
|
+
```
|
|
233
270
|
|
|
234
|
-
|
|
235
|
-
|
|
271
|
+
The release command has external side effects and requires npm publish
|
|
272
|
+
authority. Do not use it as a local verification command.
|
package/package.json
CHANGED
package/terminal-name.mjs
CHANGED
|
@@ -7,7 +7,8 @@ const SKIP = new Set([
|
|
|
7
7
|
'do', 'much', 'better', 'really', 'need', 'want', 'your', 'their', 'its',
|
|
8
8
|
'also', 'all', 'away', 'before', 'cause', 'does', 'dont', "don't", 'fully', 'has',
|
|
9
9
|
'have', 'now', 'occasionally', 'return', 'second', 'so', 'sometimes', 'still',
|
|
10
|
-
'than', 'then', 'there', '
|
|
10
|
+
'than', 'then', 'there', 'what', 'when', 'where', 'which', 'who', 'whenever',
|
|
11
|
+
'why', 'working',
|
|
11
12
|
])
|
|
12
13
|
|
|
13
14
|
const GENERIC = /^(?:new )?(?:agent |coding )?(?:terminal|task|lane|session|work)$/i
|
|
@@ -16,6 +17,8 @@ const ANY_HEADING = /^(?:#{1,6}\s+|(?:context|background|constraints?|inputs?|ou
|
|
|
16
17
|
const NOISE = /^(?:context|background|for reference|here(?:'s| is)|note|current(?:ly)?|example|environment|room now|constraints?|acceptance|success criteria)\b/i
|
|
17
18
|
const EXPLANATION = /^(?:because|cause|since|so that|this is because)\b/i
|
|
18
19
|
const CONSTRAINT = /^(?:users? can still|keep|must|never|should|without)\b/i
|
|
20
|
+
const INFORMATION_REQUEST = /^(?:which|what|who|where|when|why|how)\b/i
|
|
21
|
+
const NEGATIVE_PREFERENCE = /^(?:(?:i|we)\s+)?(?:do not|don't|dont|would not|wouldn't|won't|wont)\s+(?:(?:want|wanna|need)(?:\s+to)?|use|include|choose)\b|^(?:(?:i|we)\s+)?(?:want|wanna|need)(?:\s+to)?\s+avoid\b/i
|
|
19
22
|
const ISSUE = /\b(?:broken|buggy|crash(?:es|ed|ing)?|duplicate|error|fail(?:s|ed|ing|ure)?|flash(?:es|ed|ing)?|missing|no animation|not working|out of (?:scrollable )?view|stuck|wrong)\b/i
|
|
20
23
|
const REQUEST = /^(?:please\s+)?(?:can|could|would|will)\s+(?:we|you)\b|^(?:please\s+)?(?:how about|let's|let us|we need to|i want you to)\b/i
|
|
21
24
|
const SECRET = /(?<![\p{L}\p{N}])(?:sk-(?:proj-)?[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|xox[baprs]-[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9_-]{8,}|glpat-[a-z0-9_-]{8,}|npm_[a-z0-9]{24,}|(?:sk|rk)_(?:live|test)_[a-z0-9]{8,}|whsec_[a-z0-9]{8,}|AIza[a-z0-9_-]{8,}|AKIA[A-Z0-9]{12,}|bearer\s+[a-z0-9._-]{8,}|eyJ[a-z0-9_-]{8,}\.[a-z0-9_-]{8,}\.[a-z0-9_-]{8,})(?![\p{L}\p{N}])/giu
|
|
@@ -125,12 +128,14 @@ const bestIntentClause = (text) => {
|
|
|
125
128
|
const action = bestAction(clause)
|
|
126
129
|
let score = action?.score || 0
|
|
127
130
|
if (REQUEST.test(clause)) score += 18
|
|
131
|
+
if (INFORMATION_REQUEST.test(clause)) score += 36
|
|
128
132
|
if (ISSUE.test(clause)) score += /\b(?:crash|fail|flash|stuck|wrong)\w*\b/i.test(clause) ? 36 : 26
|
|
129
133
|
if (words.length >= 3 && words.length <= 24) score += 10
|
|
130
134
|
else if (words.length > 40) score -= 14
|
|
131
135
|
if (NOISE.test(clause)) score -= 30
|
|
132
136
|
if (EXPLANATION.test(clause)) score -= 34
|
|
133
137
|
if (CONSTRAINT.test(clause)) score -= 45
|
|
138
|
+
if (NEGATIVE_PREFERENCE.test(clause)) score -= 70
|
|
134
139
|
score += Math.round((i / Math.max(1, clauses.length - 1)) * 4)
|
|
135
140
|
if (!best || score > best.score) best = { clause, score }
|
|
136
141
|
}
|
|
@@ -163,7 +168,7 @@ export function cleanTerminalName(value) {
|
|
|
163
168
|
|
|
164
169
|
const titleWord = (word) => {
|
|
165
170
|
const lower = word.toLowerCase()
|
|
166
|
-
if (lower === 'thinkpool') return 'ThinkPool'
|
|
171
|
+
if (lower === 'thinkpool' || lower === 'thinkpool.io') return 'ThinkPool'
|
|
167
172
|
if (lower === 'serverside') return 'Server-Side'
|
|
168
173
|
if (/^(?:api|ci|css|db|html|llm|mcp|npm|sdk|ui|ux)$/i.test(word)) return lower.toUpperCase()
|
|
169
174
|
if (/^[A-Z\d+#.-]{2,}$/.test(word) || /\d/.test(word)) return word
|
|
@@ -194,6 +199,9 @@ const taskTitle = (value) => {
|
|
|
194
199
|
.replace(/^\s*(?:how about|i (?:do not|don't) know|i guess|i want you to|we need to|your task is to|let(?:'s| us))\s+/i, '')
|
|
195
200
|
.trim()
|
|
196
201
|
if (!body) return null
|
|
202
|
+
if (NEGATIVE_PREFERENCE.test(body)) return null
|
|
203
|
+
const informationRequest = INFORMATION_REQUEST.test(body)
|
|
204
|
+
if (informationRequest) body = body.replace(/^\s*(?:which|what)\s+/i, '').trim()
|
|
197
205
|
const signalText = body
|
|
198
206
|
body = body
|
|
199
207
|
.replace(/\s*,?\s+(?:so (?:that|you|we|it)\b|because\b|cause\b|if you know what i mean\b)[\s\S]*$/i, '')
|
|
@@ -205,7 +213,6 @@ const taskTitle = (value) => {
|
|
|
205
213
|
let verb = issue ? 'Fix' : action?.title
|
|
206
214
|
if (!verb && /\bclos(?:e|es|ed|ing)\b/i.test(body)) verb = 'Close'
|
|
207
215
|
if (!verb && /\bopen(?:s|ed|ing)?\b/i.test(body)) verb = 'Open'
|
|
208
|
-
if (!verb) verb = 'Work On'
|
|
209
216
|
|
|
210
217
|
if (/\bautomatic(?:ally)?\b[\s\S]{0,24}\bnam(?:e|ing)\b/i.test(signalText) && /\bterminals?\b/i.test(signalText)) {
|
|
211
218
|
if (/\bcontext\b[\s\S]{0,24}\broom\b|\broom\b[\s\S]{0,24}\bcontext\b/i.test(signalText)) return 'Auto-Name Terminals From Room Context'
|
|
@@ -226,9 +233,13 @@ const taskTitle = (value) => {
|
|
|
226
233
|
}
|
|
227
234
|
let object = contentWords(objectText)
|
|
228
235
|
if (!object.length) object = contentWords(body)
|
|
236
|
+
if (informationRequest) {
|
|
237
|
+
const available = object.findIndex((word) => /^available$/i.test(word))
|
|
238
|
+
if (available > 0) object = [object[available], ...object.slice(0, available), ...object.slice(available + 1)]
|
|
239
|
+
}
|
|
229
240
|
const picked = [...domain, ...object].filter((word, index, all) => all.findIndex((other) => other.toLowerCase() === word.toLowerCase()) === index).slice(0, 4)
|
|
230
241
|
if (!picked.length) return null
|
|
231
|
-
return cleanTerminalName([verb, ...picked.map(titleWord)].join(' '))
|
|
242
|
+
return cleanTerminalName([verb, ...picked.map(titleWord)].filter(Boolean).join(' '))
|
|
232
243
|
}
|
|
233
244
|
|
|
234
245
|
export function fallbackTerminalName(text) {
|