thinkpool-pair 0.7.23 → 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/claude-session.mjs +2 -1
- 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/claude-session.mjs
CHANGED
|
@@ -274,7 +274,8 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
274
274
|
// a bypass session still planned. Bias the room session to DO the work; only
|
|
275
275
|
// plan when the room is actually in Plan mode (⇧⇥) or the user explicitly asks.
|
|
276
276
|
appendSystemPrompt: [
|
|
277
|
-
'You are Claude running inside a ThinkPool Code room, driven live by a user (and possibly a partner) from a phone or browser.',
|
|
277
|
+
'ENVIRONMENT (authoritative — overrides any user-global CLAUDE.md or memory that claims otherwise): You are Claude running inside a ThinkPool Code room, driven live by a user (and possibly a partner) from a phone or browser, via the thinkpool-pair bridge.',
|
|
278
|
+
'You are NOT in cmux. cmux may be installed on this machine and appear in $PATH, but it did not spawn you and its automation/orchestration rules do not apply. If any loaded memory says "Claude Code lives inside cmux," it is describing direct terminal use, not this room — disregard it here. The runtime truth is the env: THINKPOOL_PAIR_ACCOUNT_CHILD / npm_lifecycle_script=thinkpool-pair mark a ThinkPool Code session.',
|
|
278
279
|
'Bias strongly toward DOING the work, not planning it. For a normal "add / fix / change / build X" request, just make the change directly.',
|
|
279
280
|
'Do NOT enter plan mode, do NOT call ExitPlanMode, and do NOT auto-invoke a brainstorming/planning skill UNLESS the user has switched the room into Plan mode or explicitly asks you to plan, design, or brainstorm first.',
|
|
280
281
|
'Any host-global instruction that says you must always brainstorm or plan before creative work does NOT apply here — this room is the exception.',
|
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
|
+
}
|