thinkpool-pair 0.6.10 → 0.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +26 -0
  2. package/package.json +1 -1
  3. package/service.mjs +19 -8
package/README.md CHANGED
@@ -35,6 +35,9 @@ The bridge self-heals at three levels — all cross-platform (macOS / Linux / Wi
35
35
  npx thinkpool-pair install-service <ROOM> -- claude # set and forget
36
36
  npx thinkpool-pair uninstall-service <ROOM> # remove it
37
37
  ```
38
+ The service runs `npx thinkpool-pair@latest`, so it **auto-updates** — new
39
+ versions apply on the next restart, no re-install. (Linux: run
40
+ `loginctl enable-linger $USER` once to keep it running after logout.)
38
41
  - **Watchdog** — if the realtime channel wedges for >60s, the bridge exits so the
39
42
  supervisor/service restarts a clean process. Brief network blips reconnect on
40
43
  their own.
@@ -48,6 +51,29 @@ every byte mirrors to the web. The web's **"+ New terminal"** spawns additional
48
51
  **headless** terminals here (same directory, same env), driven entirely from
49
52
  the room. One bridge, many terminals.
50
53
 
54
+ ## Run it in the cloud (remote host / VM / container)
55
+
56
+ The bridge connects **outbound** to Supabase — no inbound ports, no public IP, no
57
+ tunnel. So a cloud VM, container, devcontainer, or remote dev box can stream its
58
+ Claude Code into the room exactly like a laptop. Run the same command there:
59
+
60
+ ```bash
61
+ export ANTHROPIC_API_KEY=sk-ant-... # headless auth (no Keychain login)
62
+ cd /path/to/your/repo
63
+ npx thinkpool-pair <ROOM> -- claude # structured Claude, no TTY needed
64
+ ```
65
+
66
+ - **Auth**: set `ANTHROPIC_API_KEY` (or `CLAUDE_CODE_OAUTH_TOKEN`) in the box's
67
+ env — the Agent SDK reads it automatically. The structured path runs the SDK's
68
+ bundled `claude`, so you don't need Claude Code separately installed.
69
+ - **No TTY required**: the agent picker auto-selects and raw-mode is skipped when
70
+ there's no terminal, so it runs fine under CI / a service / `nohup`.
71
+ - **Always-on**: `npx thinkpool-pair install-service <ROOM> -- claude` installs a
72
+ systemd `--user` unit (auto-restart + boot-persistent + auto-update). On a
73
+ server, also run `loginctl enable-linger $USER` so it survives logout.
74
+ - **Security**: anyone with the room code can drive the agent (shell-trust by
75
+ design) — on a server that's a real shell, so treat the room code like a secret.
76
+
51
77
  ## How it works
52
78
 
53
79
  `bridge.mjs` ⇄ **Supabase realtime** (`tpcode:<ROOM>`) ⇄ web `xterm`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {
package/service.mjs CHANGED
@@ -12,24 +12,34 @@
12
12
  import os from 'node:os'
13
13
  import fs from 'node:fs'
14
14
  import path from 'node:path'
15
- import { fileURLToPath } from 'node:url'
16
15
  import { execSync } from 'node:child_process'
17
16
 
18
- const BRIDGE = fileURLToPath(new URL('./bridge.mjs', import.meta.url))
19
17
  const label = (room) => `io.thinkpool.pair.${room.toLowerCase()}`
20
18
  const xml = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
21
19
 
20
+ // Resolve the real npx next to the current node, so the service stays seamless:
21
+ // it runs `npx -y thinkpool-pair@latest …`, always picking up the newest publish
22
+ // with no re-install. Absolute path + injected PATH so it works under launchd /
23
+ // systemd's bare environment (which don't search PATH for argv[0]).
24
+ function npxPath(platform) {
25
+ const bin = path.dirname(process.execPath)
26
+ const cand = path.join(bin, platform === 'win32' ? 'npx.cmd' : 'npx')
27
+ try { fs.accessSync(cand); return cand } catch { return platform === 'win32' ? 'npx.cmd' : 'npx' }
28
+ }
29
+
22
30
  // Pure artifact builder — returned shape is testable without side effects.
23
- // `mode:'service'` lets the OS supervise (no --supervise); 'supervise' wraps it.
24
31
  export function buildArtifact(platform, { room, cmdArgs = [] }) {
25
- const node = process.execPath
32
+ const npx = npxPath(platform)
26
33
  const cwd = process.cwd()
27
34
  const logDir = path.join(os.homedir(), '.thinkpool-pair')
28
35
  const log = path.join(logDir, `${room}.log`)
29
36
  const tail = cmdArgs.length ? ['--', ...cmdArgs] : []
37
+ // OS-supervised tiers (launchd KeepAlive / systemd Restart) don't need
38
+ // --supervise; the latest published bridge is fetched by npx each (re)start.
39
+ const pkgArgs = ['-y', 'thinkpool-pair@latest', room]
30
40
 
31
41
  if (platform === 'darwin') {
32
- const args = [node, BRIDGE, room, ...tail] // launchd KeepAlive supervises
42
+ const args = [npx, ...pkgArgs, ...tail] // launchd KeepAlive supervises
33
43
  const file = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label(room)}.plist`)
34
44
  const content = `<?xml version="1.0" encoding="UTF-8"?>
35
45
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -47,7 +57,7 @@ export function buildArtifact(platform, { room, cmdArgs = [] }) {
47
57
  }
48
58
 
49
59
  if (platform === 'linux') {
50
- const args = [node, BRIDGE, room, ...tail] // systemd Restart=always supervises
60
+ const args = [npx, ...pkgArgs, ...tail] // systemd Restart=always supervises
51
61
  const file = path.join(os.homedir(), '.config', 'systemd', 'user', `${label(room)}.service`)
52
62
  const content = `[Unit]
53
63
  Description=ThinkPool Code bridge (${room})
@@ -73,7 +83,8 @@ WantedBy=default.target
73
83
  // running --supervise gives login-persistence + crash-restart, dependency-free.
74
84
  const startup = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup')
75
85
  const file = path.join(startup, `thinkpool-pair-${room}.cmd`)
76
- const inner = [`"${node}"`, `"${BRIDGE}"`, room, '--supervise', ...tail].join(' ')
86
+ // Startup folder isn't a supervisor → keep --supervise for crash-restart.
87
+ const inner = ['npx', '-y', 'thinkpool-pair@latest', room, '--supervise', ...tail].join(' ')
77
88
  const content = `@echo off\r\ntitle thinkpool-pair ${room}\r\n${inner}\r\n`
78
89
  return { file, content, logDir, post: [], note: 'Installed to the Startup folder — runs at login with --supervise (auto-restart on crash). Start it now without rebooting by double-clicking the .cmd, or run it from a terminal.' }
79
90
  }
@@ -90,7 +101,7 @@ export function installService(room, cmdArgs = []) {
90
101
  for (const cmd of a.post) {
91
102
  try { execSync(cmd, { stdio: 'inherit' }) } catch (e) { process.stderr.write(` ⚠ "${cmd}" failed: ${e.message}\n`) }
92
103
  }
93
- process.stderr.write(` ◆ ${a.note}\n ◆ logs: ${path.join(a.logDir, `${room}.log`)}\n ◆ remove with: thinkpool-pair uninstall-service ${room}\n\n`)
104
+ process.stderr.write(` ◆ ${a.note}\n ◆ tracks thinkpool-pair@latest — new versions apply on next restart, no re-install.\n ◆ logs: ${path.join(a.logDir, `${room}.log`)}\n ◆ remove with: thinkpool-pair uninstall-service ${room}\n\n`)
94
105
  }
95
106
 
96
107
  export function uninstallService(room) {