thinkpool-pair 0.7.3 → 0.7.5
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/account.mjs +32 -27
- package/package.json +1 -1
package/account.mjs
CHANGED
|
@@ -19,27 +19,35 @@ const VERSION = (() => { try { return JSON.parse(fs.readFileSync(new URL('./pack
|
|
|
19
19
|
|
|
20
20
|
const BRIDGE = fileURLToPath(new URL('./bridge.mjs', import.meta.url))
|
|
21
21
|
|
|
22
|
-
// ── login:
|
|
23
|
-
// The bridge
|
|
24
|
-
//
|
|
25
|
-
//
|
|
22
|
+
// ── login: OAuth-style DEVICE-CODE flow (hardened) ──────────────────────────
|
|
23
|
+
// The bridge asks Postgres (start_device_code) for a secret device_code + a short
|
|
24
|
+
// human user_code, prints the user_code + URL, then POLLS claim_device_code with
|
|
25
|
+
// the secret until the signed-in web has approved (approve_device_code stores the
|
|
26
|
+
// browser's session). The session is delivered only over authenticated TLS RPCs
|
|
27
|
+
// + a single-use secret — never broadcast on a public channel.
|
|
26
28
|
export async function runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) {
|
|
27
29
|
const sb = createClient(SUPABASE_URL, SUPABASE_ANON, { auth: { persistSession: false } })
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
const { data, error } = await sb.rpc('start_device_code')
|
|
31
|
+
const row = Array.isArray(data) ? data[0] : data
|
|
32
|
+
if (error || !row?.device_code) { process.stderr.write(`\n ✗ couldn't start login: ${error?.message || 'no response'}\n`); process.exit(1) }
|
|
33
|
+
const { device_code, user_code } = row
|
|
34
|
+
const url = `${WEB_BASE}/code/link?c=${encodeURIComponent(user_code)}`
|
|
35
|
+
process.stderr.write(`\n ◆ Link this device to your ThinkPool account.\n Open (signed in) and approve:\n\n ${url}\n\n code: ${user_code}\n\n Waiting…\n`)
|
|
36
|
+
const started = Date.now()
|
|
37
|
+
const poll = async () => {
|
|
38
|
+
if (Date.now() - started > 5 * 60 * 1000) { process.stderr.write('\n ◇ link timed out — run `npx thinkpool-pair login` again.\n'); process.exit(1) }
|
|
39
|
+
try {
|
|
40
|
+
const { data: r } = await sb.rpc('claim_device_code', { p_device_code: device_code })
|
|
41
|
+
if (r?.status === 'approved' && r.session?.refresh_token) {
|
|
42
|
+
saveAuth(r.session)
|
|
43
|
+
process.stderr.write(`\n ◆ linked as ${r.session.email || 'your account'}. You can close the browser tab.\n Now run: npx thinkpool-pair\n`)
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
if (r?.status === 'expired' || r?.status === 'not_found') { process.stderr.write('\n ◇ link expired — run `npx thinkpool-pair login` again.\n'); process.exit(1) }
|
|
47
|
+
} catch { /* transient — keep polling */ }
|
|
48
|
+
setTimeout(poll, 3000)
|
|
49
|
+
}
|
|
50
|
+
poll()
|
|
43
51
|
await new Promise(() => {})
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -91,7 +99,8 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
|
|
|
91
99
|
process.exit(1)
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
process.
|
|
102
|
+
const DEFAULT_DIR = process.cwd() // unbound sessions auto-serve here; `bind` overrides per session
|
|
103
|
+
process.stderr.write(`\n ◆ thinkpool-pair — account mode · ${email}\n Auto-serving your sessions from ${DEFAULT_DIR}\n (point one at another repo: npx thinkpool-pair bind <code> <dir>)\n`)
|
|
95
104
|
|
|
96
105
|
const children = new Map() // room -> child process
|
|
97
106
|
const warned = new Set()
|
|
@@ -107,24 +116,20 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
|
|
|
107
116
|
const tick = async () => {
|
|
108
117
|
let rooms = []
|
|
109
118
|
try {
|
|
110
|
-
const { data } = await sb.from('code_sessions').select('code,name,last_active_at').order('last_active_at', { ascending: false })
|
|
119
|
+
const { data } = await sb.from('code_sessions').select('code,name,last_active_at').order('last_active_at', { ascending: false }).limit(20)
|
|
111
120
|
rooms = data || []
|
|
112
121
|
} catch { /* transient — keep existing children, retry next tick */ }
|
|
113
122
|
const dirs = loadDirs()
|
|
114
123
|
for (const r of rooms) {
|
|
115
124
|
const room = r.code
|
|
116
125
|
if (!room || children.has(room)) continue
|
|
117
|
-
const dir = dirs[room]
|
|
118
|
-
if (!dir) {
|
|
119
|
-
if (!warned.has(room)) { warned.add(room); process.stderr.write(`\n ◇ session ${r.name ? `"${r.name}" ` : ''}(${room}) needs a directory:\n npx thinkpool-pair bind ${room} <path>\n`) }
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
126
|
+
const dir = dirs[room] || DEFAULT_DIR // auto-serve unbound sessions in the launch dir
|
|
122
127
|
if (!fs.existsSync(dir)) {
|
|
123
128
|
if (!warned.has(room)) { warned.add(room); process.stderr.write(`\n ◇ ${room}: bound directory is gone (${dir}) — re-bind it.\n`) }
|
|
124
129
|
continue
|
|
125
130
|
}
|
|
126
131
|
warned.delete(room)
|
|
127
|
-
process.stderr.write(`\n ◆ serving ${room}${r.name ? ` "${r.name}"` : ''} → ${dir}\n`)
|
|
132
|
+
process.stderr.write(`\n ◆ serving ${room}${r.name ? ` "${r.name}"` : ''} → ${dir}${dirs[room] ? '' : ' (default)'}\n`)
|
|
128
133
|
const child = spawn(process.execPath, [BRIDGE, room, '--headless'], { cwd: dir, stdio: 'inherit' })
|
|
129
134
|
children.set(room, child)
|
|
130
135
|
child.on('exit', () => { children.delete(room) }) // re-served on the next tick
|