thinkpool-pair 0.7.37 → 0.7.40

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/bridge.mjs +4 -23
  2. package/launcher.mjs +18 -42
  3. package/package.json +1 -1
package/bridge.mjs CHANGED
@@ -151,9 +151,7 @@ if (argv[0] === 'install-service' || argv[0] === 'uninstall-service') {
151
151
  // shell export can't reach a serviced bridge — provider.json is its own source
152
152
  // of truth. Idempotent; a no-op for the default Anthropic provider. See provider.mjs.
153
153
  const { applyProviderEnv } = await import('./provider.mjs')
154
- // Captured so the room announce can report the active LLM provider (anthropic,
155
- // or a custom Anthropic-compatible endpoint) to the web welcome banner.
156
- const PROVIDER_INFO = applyProviderEnv()
154
+ applyProviderEnv()
157
155
 
158
156
  if (argv[0] === 'login') { const { runLogin } = await import('./account.mjs'); await runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) }
159
157
  if (argv[0] === 'bind') { const { runBind } = await import('./account.mjs'); runBind(argv[1], argv[2]) }
@@ -166,7 +164,7 @@ if (argv[0] === 'provider') { const { runProvider } = await import('./provider.m
166
164
  // so launchd / systemd / CI / pipes are byte-for-byte unchanged.
167
165
  // Spec: docs/specs/2026-06-19-bridge-unified-launcher.md (Part 1).
168
166
  if ((!argv[0] || argv[0] === 'setup') && process.stdin.isTTY) {
169
- const { runLauncher, detectState } = await import('./launcher.mjs')
167
+ const { runLauncher } = await import('./launcher.mjs')
170
168
  const svc = await import('./service.mjs')
171
169
  const BRIDGE = fileURLToPath(import.meta.url)
172
170
  const io = {
@@ -188,18 +186,9 @@ if ((!argv[0] || argv[0] === 'setup') && process.stdin.isTTY) {
188
186
  installService: ({ room = null, agentCmd } = {}) => { svc.installService(room, agentCmd ? [agentCmd] : []); process.exit(0) },
189
187
  uninstallService: ({ room = null } = {}) => { svc.uninstallService(room); process.exit(0) },
190
188
  login: async () => { const { runLogin } = await import('./account.mjs'); await runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) },
191
- // Provider config is NOT terminal — it writes provider.json and returns to the
192
- // menu so you can pick a model, switch back, or do something else. (It used to
193
- // process.exit(0), which dumped you to the shell mid-setup.)
194
- setProvider: async ({ kind, base, token, model }) => {
195
- const { runProvider } = await import('./provider.mjs')
196
- const args = kind === 'custom'
197
- ? ['custom', '--base', base, '--token', token, ...(model ? ['--model', model] : [])]
198
- : ['anthropic']
199
- await runProvider(args)
200
- },
189
+ setProvider: async ({ kind, base, token }) => { const { runProvider } = await import('./provider.mjs'); await runProvider(kind === 'custom' ? ['custom', '--base', base, '--token', token] : ['anthropic']); process.exit(0) },
201
190
  }
202
- await runLauncher({ actions, io, refresh: detectState })
191
+ await runLauncher({ actions, io })
203
192
  process.exit(0)
204
193
  }
205
194
 
@@ -417,14 +406,6 @@ const announce = () =>
417
406
  // cwd + version: the host's working dir + thinkpool-pair version, shown in
418
407
  // the room's welcome banner. Re-sent per announce so late joiners get them.
419
408
  cwd, version: VERSION,
420
- // provider: the LLM endpoint this bridge drives Claude Code through —
421
- // 'anthropic' (the default, the host's regular Claude login) or 'custom'
422
- // (any Anthropic-compatible base url: GLM/Z.ai, OpenRouter, a proxy). Shown
423
- // in the room banner so both drivers see whose model they're on.
424
- // providerBaseUrl rides along only for custom so the banner can show the
425
- // host (e.g. openrouter.ai). Older web clients ignore unknown fields.
426
- provider: PROVIDER_INFO.provider,
427
- providerBaseUrl: PROVIDER_INFO.baseUrl || undefined,
428
409
  // pendingUpdate: a newer thinkpool-pair is out — drives the room's "update ready"
429
410
  // chip; re-sent per announce so reloads/late-joiners see it (Slice 3).
430
411
  pendingUpdate,
package/launcher.mjs CHANGED
@@ -51,14 +51,10 @@ function serviceFile(room) {
51
51
  export function detectState() {
52
52
  const auth = readJson(path.join(CFG_DIR, 'auth.json'))
53
53
  const provider = readJson(path.join(CFG_DIR, 'provider.json'))
54
- // provider.json is written with { provider, baseUrl, model } (see provider.mjs).
55
- // Anything whose `provider` field is set and not "anthropic" is a custom endpoint.
56
- const isCustom = !!provider?.provider && provider.provider !== 'anthropic'
57
54
  return {
58
55
  loggedIn: !!auth?.refresh_token,
59
56
  email: auth?.email || auth?.user?.email || null,
60
- provider: isCustom ? `Custom (${provider.baseUrl || 'endpoint'})` : 'Anthropic (default)',
61
- providerModel: isCustom ? (provider.model || null) : null,
57
+ provider: provider?.kind === 'custom' ? `Custom (${provider.base || 'endpoint'})` : 'Anthropic (default)',
62
58
  agents: KNOWN_AGENTS.filter(a => onPath(a.cmd)),
63
59
  accountSvc: fs.existsSync(serviceFile(null)),
64
60
  cwd: process.cwd(),
@@ -72,11 +68,7 @@ const C = {
72
68
  }
73
69
 
74
70
  // io: { ask(prompt)->Promise<string>, print(s) }. actions: real | dry-run | mock.
75
- // refresh: optional () => state — re-read disk after a mutating action so the header
76
- // reflects the change (real bridge passes detectState; tests omit it to keep the
77
- // injected state stable).
78
- export async function runLauncher({ actions, io, state = detectState(), refresh = null }) {
79
- const resync = () => { if (refresh) state = refresh() }
71
+ export async function runLauncher({ actions, io, state = detectState() }) {
80
72
  const askChoice = async (prompt, options, def = 1) => {
81
73
  options.forEach((o, i) => io.print(` ${C.cyan(String(i + 1))}) ${o.label}${o.hint ? ' ' + C.dim(o.hint) : ''}`))
82
74
  const a = await io.ask(`\n ${prompt} ${C.dim(`[${def}]`)} ${C.cyan('▸')} `)
@@ -145,41 +137,25 @@ export async function runLauncher({ actions, io, state = detectState(), refresh
145
137
  await actions.installService({ room, agentCmd: agent.cmd })
146
138
  }
147
139
 
148
- const providerMenu = async () => {
149
- const cur = state.provider + (state.providerModel ? `, model ${state.providerModel}` : '')
150
- const p = await askChoice('\n provider', [
151
- { label: 'Anthropic (default)', hint: 'regular Claude login' },
152
- { label: 'Custom endpoint', hint: 'Z.ai GLM, OpenRouter, your own proxy' },
153
- { label: '← Back', hint: `current: ${cur}` },
154
- ], 3) // Enter = Back
155
- if (p === 2) return
156
- if (p === 0) { await actions.setProvider({ kind: 'anthropic' }); resync(); io.print('\n ' + C.green('✓ back on regular Claude.')); return }
157
- // custom — every step can be aborted with a blank line, which backs out cleanly.
158
- const base = (await io.ask(`\n Base url ${C.dim('(blank to cancel · SDK appends /v1/messages)')} ${C.cyan('▸')} `)).trim()
159
- if (!base) { io.print(' ' + C.yellow('cancelled — provider unchanged')); return }
160
- const token = (await io.ask(` API token ${C.dim('(blank to cancel)')} ${C.cyan('▸')} `)).trim()
161
- if (!token) { io.print(' ' + C.yellow('cancelled — provider unchanged')); return }
162
- const model = (await io.ask(` Model ${C.dim('(blank = endpoint default · e.g. glm-4.6)')} ${C.cyan('▸')} `)).trim()
163
- await actions.setProvider({ kind: 'custom', base, token, model: model || undefined })
164
- resync()
165
- io.print(`\n ${C.green('✓ provider set to custom.')}\n base url: ${state.provider}\n model: ${state.providerModel || '(endpoint default)'}`)
166
- }
167
-
168
140
  const settingsMenu = async () => {
169
- for (;;) {
170
- const pick = await askChoice('\n change', [
171
- { label: 'LLM provider', hint: `current: ${state.provider}` },
172
- { label: 'Account', hint: state.loggedIn ? `linked: ${state.email || 'yes'}` : 'not linked' },
173
- { label: 'Remove the background service', hint: state.accountSvc ? '' : 'none installed' },
174
- { label: '← Back to main menu', hint: '' },
175
- ], 4) // Enter = Back
176
- if (pick === 3) return
177
- if (pick === 0) await providerMenu()
178
- else if (pick === 1) { await actions.login(); resync(); return }
141
+ const pick = await askChoice('\n change', [
142
+ { label: 'LLM provider', hint: `current: ${state.provider}` },
143
+ { label: 'Account', hint: state.loggedIn ? `linked: ${state.email || 'yes'}` : 'not linked' },
144
+ { label: 'Remove the background service', hint: state.accountSvc ? '' : 'none installed' },
145
+ ])
146
+ if (pick === 0) {
147
+ const p = await askChoice('\n provider', [{ label: 'Anthropic (default)' }, { label: 'Custom endpoint', hint: 'Z.ai GLM, OpenRouter, your own proxy' }])
148
+ if (p === 0) await actions.setProvider({ kind: 'anthropic' })
179
149
  else {
180
- if (state.accountSvc) await actions.uninstallService({ room: null })
181
- else io.print(' ' + C.yellow('no background service installed'))
150
+ const base = (await io.ask(`\n Base url ${C.dim('(SDK appends /v1/messages)')} ${C.cyan('▸')} `)).trim()
151
+ const token = (await io.ask(` API token ${C.cyan('▸')} `)).trim()
152
+ await actions.setProvider({ kind: 'custom', base, token })
182
153
  }
154
+ } else if (pick === 1) {
155
+ await actions.login()
156
+ } else {
157
+ if (state.accountSvc) await actions.uninstallService({ room: null })
158
+ else io.print(' ' + C.yellow('no background service installed'))
183
159
  }
184
160
  }
185
161
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.37",
3
+ "version": "0.7.40",
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": {