thinkpool-pair 0.6.19 → 0.6.21

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/bridge.mjs CHANGED
@@ -211,6 +211,32 @@ if (dashIdx >= 0) {
211
211
  if (continuing) attachedArgs = [...agent.resume.args]
212
212
  }
213
213
  }
214
+
215
+ // First-run offer: install as a background service (auto-updating, survives
216
+ // reboot/logout) instead of this foreground run. Asked once per room on an
217
+ // interactive TTY, AFTER the agent is chosen so the service runs that exact
218
+ // command non-interactively. Skipped when running AS the service
219
+ // (AUTOUPDATE=1), headless, or non-interactive. Choice is remembered.
220
+ if (process.stdin.isTTY && !headless && process.env.THINKPOOL_PAIR_AUTOUPDATE !== '1') {
221
+ const svc = await import('./service.mjs')
222
+ if (svc.isServiceInstalled(room)) {
223
+ process.stderr.write(`\n ◆ a background service is already running room ${room} (auto-updating).\n ◆ remove it with: npx thinkpool-pair@latest uninstall-service ${room}\n\n`)
224
+ process.exit(0)
225
+ }
226
+ const declineFile = path.join(os.homedir(), '.thinkpool-pair', `${room}.svc-declined`)
227
+ let declined = false
228
+ try { declined = fs.existsSync(declineFile) } catch { /* noop */ }
229
+ if (!declined) {
230
+ const yes = await askYesNo(`\n Keep this bridge running with auto-updates (survives reboot, no terminal to babysit)?\n Install it as a background service? [Y/n] `)
231
+ if (yes) {
232
+ svc.installService(room, attachedCmd ? [attachedCmd, ...attachedArgs] : [])
233
+ process.stderr.write(` ◆ the background service is handling room ${room} now — you can close this terminal.\n\n`)
234
+ process.exit(0) // hand off to the supervised service (no double bridge)
235
+ }
236
+ try { fs.mkdirSync(path.dirname(declineFile), { recursive: true }); fs.writeFileSync(declineFile, new Date().toISOString()) } catch { /* noop */ }
237
+ process.stderr.write(` ◆ running in this terminal. (Auto-update service anytime: npx thinkpool-pair@latest install-service ${room})\n`)
238
+ }
239
+ }
214
240
  const name = process.env.TP_NAME || os.userInfo().username || 'host'
215
241
 
216
242
  // Repo awareness — the room shows which project this machine is sharing.
@@ -730,6 +756,23 @@ if (process.env.THINKPOOL_PAIR_AUTOUPDATE === '1' && VERSION) {
730
756
  setInterval(check, POLL_MS).unref() // poll the registry
731
757
  setInterval(applyIfIdle, 15000).unref() // once pending, land it as soon as idle
732
758
  setTimeout(check, 60000).unref() // first check after the session settles
759
+ } else if (VERSION) {
760
+ // Foreground run (not the supervised service): it can't self-restart, but
761
+ // nudge once if a newer version is out so a relaunch with @latest picks it up.
762
+ setTimeout(async () => {
763
+ try {
764
+ const ctrl = new AbortController(); const to = setTimeout(() => ctrl.abort(), 8000)
765
+ const res = await fetch('https://registry.npmjs.org/thinkpool-pair/latest', { signal: ctrl.signal, headers: { accept: 'application/json' } }).finally(() => clearTimeout(to))
766
+ if (!res.ok) return
767
+ const j = await res.json(); const latest = typeof j?.version === 'string' ? j.version : null
768
+ if (!latest) return
769
+ const core = (v) => String(v).split('-')[0].split('.').map((n) => parseInt(n, 10) || 0)
770
+ const a = core(latest), b = core(VERSION)
771
+ let newer = false
772
+ for (let i = 0; i < 3; i++) { if ((a[i] || 0) > (b[i] || 0)) { newer = true; break } if ((a[i] || 0) < (b[i] || 0)) break }
773
+ if (newer) process.stderr.write(`\n ◆ thinkpool-pair ${latest} is out (you're on ${VERSION}) — restart with: npx thinkpool-pair@latest ${room}\n`)
774
+ } catch { /* offline — never block the session */ }
775
+ }, 60000).unref()
733
776
  }
734
777
 
735
778
  function shutdown() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.6.19",
3
+ "version": "0.6.21",
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
@@ -108,6 +108,13 @@ export function installService(room, cmdArgs = []) {
108
108
  process.stderr.write(` ◆ ${a.note}\n ◆ ${autoUpdate}\n ◆ logs: ${path.join(a.logDir, `${room}.log`)}\n ◆ remove with: thinkpool-pair uninstall-service ${room}\n\n`)
109
109
  }
110
110
 
111
+ // Is a boot-persistent service already installed for this room? (artifact file
112
+ // present.) Lets the main `npx thinkpool-pair <room>` run skip the offer + avoid
113
+ // starting a second bridge that would race the service on the same room.
114
+ export function isServiceInstalled(room) {
115
+ try { return fs.existsSync(buildArtifact(process.platform, { room }).file) } catch { return false }
116
+ }
117
+
111
118
  export function uninstallService(room) {
112
119
  const plat = process.platform
113
120
  let a