thinkpool-pair 0.7.24 → 0.7.25
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 +8 -0
- package/package.json +2 -1
- package/provider.mjs +120 -0
package/bridge.mjs
CHANGED
|
@@ -143,8 +143,16 @@ if (argv[0] === 'install-service' || argv[0] === 'uninstall-service') {
|
|
|
143
143
|
// ACCOUNT, not per room. `login` links this device; `bind <ROOM> <dir>` maps a
|
|
144
144
|
// session to a working dir on this machine; a bare invocation discovers + serves
|
|
145
145
|
// every session you're in (a headless child bridge per room, run in its dir).
|
|
146
|
+
// Apply the per-machine LLM provider choice (GLM via Z.ai, or default Anthropic)
|
|
147
|
+
// BEFORE any child is spawned. The launchd service doesn't source ~/.zshrc, so a
|
|
148
|
+
// shell export can't reach a serviced bridge — provider.json is its own source
|
|
149
|
+
// of truth. Idempotent; a no-op for the default Anthropic provider. See provider.mjs.
|
|
150
|
+
const { applyProviderEnv } = await import('./provider.mjs')
|
|
151
|
+
applyProviderEnv()
|
|
152
|
+
|
|
146
153
|
if (argv[0] === 'login') { const { runLogin } = await import('./account.mjs'); await runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) }
|
|
147
154
|
if (argv[0] === 'bind') { const { runBind } = await import('./account.mjs'); runBind(argv[1], argv[2]) }
|
|
155
|
+
if (argv[0] === 'provider') { const { runProvider } = await import('./provider.mjs'); await runProvider(argv.slice(1)); process.exit(0) }
|
|
148
156
|
if (!argv[0] || argv[0].startsWith('-')) { const { runAccount } = await import('./account.mjs'); await runAccount(SUPABASE_URL, SUPABASE_ANON) }
|
|
149
157
|
|
|
150
158
|
const room = (argv[0] || '').toUpperCase().trim()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.25",
|
|
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": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"service.mjs",
|
|
14
14
|
"account.mjs",
|
|
15
15
|
"auth-store.mjs",
|
|
16
|
+
"provider.mjs",
|
|
16
17
|
"README.md"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
package/provider.mjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* ─────────────────────────────────────────────────────────────
|
|
2
|
+
provider.mjs — per-machine LLM provider choice for the ThinkPool
|
|
3
|
+
bridge. Lets a Code room run on GLM (Z.ai's Anthropic-compatible
|
|
4
|
+
endpoint) instead of plain Anthropic, INDEPENDENT of the launching
|
|
5
|
+
shell. Why this exists: the launchd service does NOT source ~/.zshrc,
|
|
6
|
+
so a shell `export ANTHROPIC_*` can never reach a serviced bridge —
|
|
7
|
+
its spawned Claude children fall back to the host's claude.ai login
|
|
8
|
+
and hit the Anthropic spend wall. provider.json is the bridge's OWN
|
|
9
|
+
source of truth.
|
|
10
|
+
|
|
11
|
+
applyProviderEnv() runs once at the top of bridge.mjs (the universal
|
|
12
|
+
entry point), BEFORE any child is spawned. It sets ANTHROPIC_BASE_URL
|
|
13
|
+
/ ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL on process.env. Because the
|
|
14
|
+
account-supervisor spawn (account.mjs) and the Agent-SDK env
|
|
15
|
+
(claude-session.mjs) both spread `...process.env`, the choice reaches
|
|
16
|
+
every code session with no other change. Default (no file, or
|
|
17
|
+
provider:"anthropic") = the host's regular Claude login, untouched.
|
|
18
|
+
|
|
19
|
+
CLI (dispatched from bridge.mjs):
|
|
20
|
+
npx thinkpool-pair provider # show current
|
|
21
|
+
npx thinkpool-pair provider glm --token <zai_…> # use GLM (Z.ai)
|
|
22
|
+
npx thinkpool-pair provider custom --base <url> --token <key>
|
|
23
|
+
npx thinkpool-pair provider anthropic # reset to Claude
|
|
24
|
+
───────────────────────────────────────────────────────────── */
|
|
25
|
+
import os from 'node:os'
|
|
26
|
+
import fs from 'node:fs'
|
|
27
|
+
import path from 'node:path'
|
|
28
|
+
|
|
29
|
+
const DIR = path.join(os.homedir(), '.thinkpool-pair')
|
|
30
|
+
const FILE = path.join(DIR, 'provider.json')
|
|
31
|
+
|
|
32
|
+
// The pre-baked GLM/Z.ai preset. --token is always required (never bake a key
|
|
33
|
+
// into source); --base / --model override these if the endpoint ever moves.
|
|
34
|
+
const GLM = { baseUrl: 'https://api.z.ai/api/anthropic', model: 'glm-5.2' }
|
|
35
|
+
|
|
36
|
+
function ensureDir() { try { fs.mkdirSync(DIR, { recursive: true }) } catch { /* noop */ } }
|
|
37
|
+
function opt(args, flag) { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined }
|
|
38
|
+
|
|
39
|
+
export function loadProvider() {
|
|
40
|
+
try { return JSON.parse(fs.readFileSync(FILE, 'utf8')) } catch { return null }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function saveProvider(p) {
|
|
44
|
+
ensureDir()
|
|
45
|
+
fs.writeFileSync(FILE, JSON.stringify(p, null, 2), { mode: 0o600 })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Merge the persisted provider choice (or TP_PROVIDER* env overrides) into
|
|
50
|
+
* process.env. Call once at startup, before spawning any child. Idempotent.
|
|
51
|
+
* A no-op for the default Anthropic provider — we only ever SET the GLM/custom
|
|
52
|
+
* vars, never clobber a host's real Claude login env. Env vars win over the
|
|
53
|
+
* file so a one-off launch can flip providers without writing anything.
|
|
54
|
+
* @returns {{provider:string,baseUrl?:string,model?:string,source:string}}
|
|
55
|
+
*/
|
|
56
|
+
export function applyProviderEnv() {
|
|
57
|
+
const cfg = loadProvider() || {}
|
|
58
|
+
const provider = (process.env.TP_PROVIDER || cfg.provider || 'anthropic').toLowerCase()
|
|
59
|
+
if (provider === 'anthropic' || !provider) return { provider: 'anthropic', source: 'default' }
|
|
60
|
+
|
|
61
|
+
const baseUrl = process.env.TP_ANTHROPIC_BASE_URL || cfg.baseUrl
|
|
62
|
+
const authToken = process.env.TP_ANTHROPIC_AUTH_TOKEN || cfg.authToken
|
|
63
|
+
const model = process.env.TP_ANTHROPIC_MODEL || cfg.model
|
|
64
|
+
if (!baseUrl || !authToken) {
|
|
65
|
+
process.stderr.write(`\n ◇ provider "${provider}" is set but is missing baseUrl/authToken.\n Finish setup: npx thinkpool-pair provider ${provider} --token <key>\n Falling back to regular Claude for now.\n`)
|
|
66
|
+
return { provider: 'anthropic', source: 'incomplete' }
|
|
67
|
+
}
|
|
68
|
+
process.env.ANTHROPIC_BASE_URL = baseUrl
|
|
69
|
+
process.env.ANTHROPIC_AUTH_TOKEN = authToken
|
|
70
|
+
if (model) process.env.ANTHROPIC_MODEL = model
|
|
71
|
+
return { provider, baseUrl, model, source: cfg.provider ? 'file' : 'env' }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** CLI handler for `thinkpool-pair provider [...]`. */
|
|
75
|
+
export async function runProvider(args) {
|
|
76
|
+
const sub = (args[0] || '').toLowerCase()
|
|
77
|
+
|
|
78
|
+
// ── show ──
|
|
79
|
+
if (!sub || sub === 'show' || sub === 'status') {
|
|
80
|
+
const cfg = loadProvider()
|
|
81
|
+
if (!cfg || cfg.provider === 'anthropic' || !cfg.provider) {
|
|
82
|
+
console.error('\n ◆ provider: anthropic (regular Claude login) — the default.\n')
|
|
83
|
+
} else {
|
|
84
|
+
console.error(`\n ◆ provider: ${cfg.provider}\n base url: ${cfg.baseUrl || '(unset)'}\n model: ${cfg.model || '(unset)'}\n token: ${cfg.authToken ? cfg.authToken.slice(0, 8) + '…' : '(unset)'}\n`)
|
|
85
|
+
}
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── reset to Anthropic ──
|
|
90
|
+
if (sub === 'anthropic' || sub === 'clear' || sub === 'reset') {
|
|
91
|
+
try { fs.unlinkSync(FILE) } catch { /* noop */ }
|
|
92
|
+
console.error('\n ✓ provider reset to anthropic (regular Claude login).\n')
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── GLM (Z.ai) preset ──
|
|
97
|
+
if (sub === 'glm') {
|
|
98
|
+
const token = opt(args, '--token') || opt(args, '--key')
|
|
99
|
+
const baseUrl = opt(args, '--base') || opt(args, '--base-url') || GLM.baseUrl
|
|
100
|
+
const model = opt(args, '--model') || GLM.model
|
|
101
|
+
if (!token) { console.error('\n ✗ glm needs --token <zai_…>. Get one at https://z.ai/\n'); process.exit(1) }
|
|
102
|
+
saveProvider({ provider: 'glm', baseUrl, authToken: token, model, savedAt: Date.now() })
|
|
103
|
+
console.error(`\n ✓ provider set to glm (Z.ai).\n base url: ${baseUrl}\n model: ${model}\n token: ${token.slice(0, 8)}…\n Restart the bridge (or its launchd service) to apply.\n`)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── arbitrary Anthropic-compatible endpoint ──
|
|
108
|
+
if (sub === 'custom') {
|
|
109
|
+
const token = opt(args, '--token') || opt(args, '--key')
|
|
110
|
+
const baseUrl = opt(args, '--base') || opt(args, '--base-url')
|
|
111
|
+
const model = opt(args, '--model')
|
|
112
|
+
if (!token || !baseUrl) { console.error('\n ✗ custom needs --base <url> and --token <key>.\n'); process.exit(1) }
|
|
113
|
+
saveProvider({ provider: 'custom', baseUrl, authToken: token, model: model || undefined, savedAt: Date.now() })
|
|
114
|
+
console.error(`\n ✓ provider set to custom (${baseUrl}).\n Restart the bridge to apply.\n`)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.error('\n usage:\n npx thinkpool-pair provider # show current\n npx thinkpool-pair provider glm --token <zai_…> [--model glm-5.2]\n npx thinkpool-pair provider custom --base <url> --token <key> [--model <m>]\n npx thinkpool-pair provider anthropic # reset to Claude\n')
|
|
119
|
+
process.exit(1)
|
|
120
|
+
}
|