claude-discover 1.9.2 → 1.9.4

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.
@@ -1,13 +1,7 @@
1
1
  #!/usr/bin/env node
2
- //
3
- // Claude Code status line. Reads the hook JSON from stdin and prints one colored line
4
- // summarizing the model, context-window usage, token totals, and rate-limit windows.
5
- // Installed as the `statusLine` command from the app: StatusBar → Status line → Activate
6
- // (see src/main/services/switchers/StatuslineSwitch.js).
7
- //
8
- // Example output:
9
- // [Opus 4.8] Context: ▓▓▓░░░░░░░ 32% used (45.2k, 87% cached) | Tokens: 1.2M total (+45.2k, 3 turns) | Usage limit: 42% used (resets in 2h 30m) | Weekly limit: 18% used (resets in 3d 4h)
10
- //
2
+ // Claude Code status line, installed by StatuslineSwitch. Reads the hook JSON from stdin and
3
+ // prints one colored line — model, context usage, token totals, rate-limit windows:
4
+ // [Opus 4.8] Context: ▓▓▓░░░░░░░ 32% used (45.2k, 87% cached) | Tokens: 1.2M total (+45.2k, 3 turns) | Usage limit: 42% used (resets in 2h 30m)
11
5
 
12
6
  import { readFileSync } from 'node:fs'
13
7
  import { styleText } from 'node:util'
package/bin/proxy.mjs CHANGED
@@ -1,15 +1,13 @@
1
1
  // Local logging proxy for Claude Code's API traffic — captures what transcripts never record
2
2
  // (system prompt, tool definitions, injected reminders, request params) plus the raw responses.
3
- // Claude Code is pointed at it via env.ANTHROPIC_BASE_URL (installed by the app's StatusBar
4
- // Activate button); started manually with `npm run proxy`. Capture must never fail or delay a request — errors go
5
- // to ~/.claude-discover/proxy.error.log instead of the client.
3
+ // Capture must never fail or delay a request: errors go to ~/.claude-discover/proxy.error.log.
6
4
  //
7
- // Usage: node bin/proxy.mjs [--restart] — port/upstream/dir come from proxy.config.js ($CLAUDE_DISCOVER_* env overrides used only by tests)
5
+ // Usage: node bin/proxy.mjs [--restart] — config in proxy.config.js. ProxySwitch runs it as a
6
+ // login service (LoginService) and points Claude Code here via env.ANTHROPIC_BASE_URL.
8
7
 
9
8
  import fs from 'node:fs'
10
9
  import path from 'node:path'
11
10
  import http from 'node:http'
12
- import { spawn } from 'node:child_process'
13
11
  import https from 'node:https'
14
12
  import zlib from 'node:zlib'
15
13
  import crypto from 'node:crypto'
@@ -56,34 +54,42 @@ function createProxy({ upstream, onExchange, onError, errorBody }) {
56
54
  delete headers['accept-encoding']
57
55
  }
58
56
 
59
- const up = mod.request(new URL(req.url, upstream), { method: req.method, headers }, upRes => {
60
- const resHeaders = { ...upRes.headers }
61
- delete resHeaders['transfer-encoding'] // node re-frames the piped body itself
62
- delete resHeaders.connection
63
- res.writeHead(upRes.statusCode, resHeaders)
64
- const chunks = []
65
- upRes.on('data', c => chunks.push(c))
66
- upRes.pipe(res)
67
- // 'close' fires after 'end' and also on an aborted stream — a truncated body is still captured
68
- upRes.on('close', () => {
69
- finish(upRes.statusCode, upRes.headers, decode(upRes.headers['content-encoding'], Buffer.concat(chunks)))
70
- if (!upRes.complete) res.destroy() // upstream died mid-body — surface the reset so the client can retry
57
+ let up, resent = false
58
+ const send = () => {
59
+ up = mod.request(new URL(req.url, upstream), { method: req.method, headers }, upRes => {
60
+ const resHeaders = { ...upRes.headers }
61
+ delete resHeaders['transfer-encoding'] // node re-frames the piped body itself
62
+ delete resHeaders.connection
63
+ res.writeHead(upRes.statusCode, resHeaders)
64
+ const chunks = []
65
+ upRes.on('data', c => chunks.push(c))
66
+ upRes.pipe(res)
67
+ // 'close' fires after 'end' and also on an aborted stream — a truncated body is still captured
68
+ upRes.on('close', () => {
69
+ finish(upRes.statusCode, upRes.headers, decode(upRes.headers['content-encoding'], Buffer.concat(chunks)))
70
+ if (!upRes.complete) res.destroy() // upstream died mid-body — surface the reset so the client can retry
71
+ })
71
72
  })
72
- })
73
- // Client gone mid-exchange — tear down upstream too; headersSent means upstream responded and its 'close' captures the partial body, otherwise log the attempt here (up.destroy() emits no 'error')
73
+ const cur = up
74
+ cur.on('error', err => {
75
+ if (done) return // our own up.destroy() below surfaces here as a 'socket hang up' — the client left, upstream is fine
76
+ // A pooled keep-alive socket the server closed just before we picked it: nothing reached upstream, so re-sending once is safe (Node's documented reusedSocket case)
77
+ if (cur.reusedSocket && err.code === 'ECONNRESET' && !resent) { resent = true; return send() }
78
+ onError(err, `${req.method} ${req.url} → ${upstream.host}`)
79
+ finish()
80
+ if (res.headersSent) return res.destroy() // mid-stream failure — never append an error body to a partially-piped response
81
+ res.writeHead(502, { 'content-type': 'application/json' })
82
+ res.end(errorBody(err))
83
+ })
84
+ cur.end(requestBody)
85
+ }
86
+ // Client gone mid-exchange — tear down upstream too; headersSent means upstream responded and its 'close' captures the partial body, otherwise log the attempt here
74
87
  res.on('close', () => {
75
88
  if (done) return
76
- up.destroy()
77
89
  if (!res.headersSent) finish()
90
+ up.destroy()
78
91
  })
79
- up.on('error', err => {
80
- onError(err, `${req.method} ${req.url} → ${upstream.host}`)
81
- finish()
82
- if (res.headersSent) return res.destroy() // mid-stream failure — never append an error body to a partially-piped response
83
- res.writeHead(502, { 'content-type': 'application/json' })
84
- res.end(errorBody(err))
85
- })
86
- up.end(requestBody)
92
+ send()
87
93
  })
88
94
  }
89
95
 
@@ -228,35 +234,13 @@ function logError(err, context) {
228
234
  }
229
235
 
230
236
  const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
231
- if (process.parentPort) {
232
- // Forked as an Electron utility process (ProxySwitch): Chromium gives those a clean handle
233
- // table, unlike the app's main process whose children inherit the dev CDP server's socket on
234
- // Windows and keep its port bound after the app quits. Utility processes die with the app,
235
- // so re-spawn detached from here (as node — no system `node` required) and relay an early
236
- // exit code back as a message until the app kills this relay.
237
- const child = spawn(process.execPath, process.argv.slice(1), {
238
- detached: true, stdio: 'ignore', windowsHide: true,
239
- env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
240
- })
241
- child.once('exit', code => { process.parentPort.postMessage(code); process.exit(0) })
242
- }
243
- else if (isMain) {
237
+ if (isMain) {
244
238
  const args = parseArgs({
245
239
  options: {
246
240
  restart: { type: 'boolean' }, // replace an already-running instance instead of exiting
247
241
  }
248
242
  })
249
243
  const upstream = new URL(UPSTREAM)
250
- // Fail fast when the upstream is unreachable — a clear exit beats a proxy that 502s every request.
251
- // Runs before --restart so a working instance is never replaced by a broken one. Exit code 2 is
252
- // recognized by the app's ProxySwitch ("cannot reach upstream").
253
- try {
254
- await fetch(new URL('/v1/models', upstream), { signal: AbortSignal.timeout(5000) })
255
- } catch (err) {
256
- console.error(`Cannot reach ${upstream.origin} (${err?.cause?.code || err?.cause?.message || err?.name || err})`)
257
- logError(err, `startup check → ${upstream.origin}`)
258
- process.exit(2)
259
- }
260
244
  const server = createProxy({
261
245
  upstream: upstream,
262
246
  onExchange: logRequest,
@@ -277,5 +261,8 @@ else if (isMain) {
277
261
  logError(err)
278
262
  process.exit(1)
279
263
  })
264
+ // Never die on a stray exception — log it and keep serving
265
+ process.on('uncaughtException', err => logError(err, 'uncaught exception'))
266
+ process.on('unhandledRejection', err => logError(err, 'unhandled rejection'))
280
267
  server.listen(PORT, HOST, () => console.log(`Capture proxy listening on ${PROXY_URL} → ${upstream.origin}\nLogging requests to ${REQUESTS_DIR}`))
281
268
  }