Files
archy/packages/app/server/claude-proxy.ts
T
Dorian ece4b8256f feat(app): enhance theme support and improve PWA integration
- Updated the app to support light and dark themes with appropriate CSS classes.
- Enhanced PWA configuration with manifest details and caching strategies.
- Improved the chat UI with dynamic theme adjustments for various components.
- Added new meta tags for better mobile web app experience.
- Refactored environment variables to include new Anthropic token.
- Updated package dependencies for better compatibility and performance.

Made-with: Cursor
2026-03-02 16:34:44 +00:00

140 lines
4.2 KiB
TypeScript

import { spawn } from 'child_process'
import { createServer } from 'http'
import { resolve } from 'path'
const PORT = 3141
const CLAUDE_BIN = resolve(process.env.HOME ?? '', '.local/bin/claude')
const server = createServer((req, res) => {
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
})
res.end()
return
}
if (req.method !== 'POST' || req.url !== '/v1/messages') {
res.writeHead(404, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Not found' }))
return
}
let body = ''
req.on('data', (chunk) => { body += chunk })
req.on('end', () => {
try {
const { model, messages, system } = JSON.parse(body)
const modelFlag = model?.includes('opus') ? 'opus'
: model?.includes('haiku') ? 'haiku'
: 'sonnet'
const history = (messages ?? []) as { role: string; content: string }[]
const userMessages = history.filter((m) => m.role === 'user')
const lastUserMsg = userMessages[userMessages.length - 1]?.content ?? ''
const contextParts: string[] = []
if (system) contextParts.push(system)
const prior = history.slice(0, -1)
if (prior.length > 0) {
contextParts.push(
'Conversation so far:\n' +
prior.map((m) => `${m.role}: ${m.content}`).join('\n')
)
}
const systemPrompt = contextParts.length > 0 ? contextParts.join('\n\n') : undefined
const args = ['-p', '--model', modelFlag]
if (systemPrompt) args.push('--system-prompt', systemPrompt)
args.push('--', lastUserMsg)
console.log(`[proxy] → claude -p --model ${modelFlag} "${lastUserMsg.slice(0, 60)}..."`)
const proc = spawn(CLAUDE_BIN, args, {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, NO_COLOR: '1', TERM: 'dumb' },
detached: false,
})
proc.stdin.end('')
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no',
})
let fullOutput = ''
let clientDisconnected = false
proc.stdout.on('data', (chunk: Buffer) => {
const text = chunk.toString()
fullOutput += text
console.log(`[proxy] stdout +${text.length}b total=${fullOutput.length}b`)
if (!clientDisconnected) {
const sseData = {
type: 'content_block_delta',
delta: { type: 'text_delta', text },
}
res.write(`data: ${JSON.stringify(sseData)}\n\n`)
}
})
proc.stderr.on('data', (chunk: Buffer) => {
const msg = chunk.toString().trim()
if (msg) console.error('[proxy] stderr:', msg)
})
proc.on('error', (err) => {
console.error('[proxy] spawn error:', err)
if (!clientDisconnected) {
const errData = {
type: 'error',
error: { message: `Spawn error: ${err.message}` },
}
res.write(`data: ${JSON.stringify(errData)}\n\n`)
res.write('data: [DONE]\n\n')
res.end()
}
})
proc.on('close', (code, signal) => {
console.log(`[proxy] ← exit code=${code} signal=${signal} output=${fullOutput.length}b`)
if (!clientDisconnected) {
res.write('data: [DONE]\n\n')
res.end()
}
})
res.on('close', () => {
clientDisconnected = true
if (proc.exitCode === null && !proc.killed) {
console.log('[proxy] Client disconnected, killing process')
proc.kill('SIGTERM')
}
})
} catch (err) {
console.error('[proxy] Parse error:', err)
res.writeHead(400, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
})
res.end(JSON.stringify({ error: String(err) }))
}
})
})
server.listen(PORT, () => {
console.log(`\n Claude proxy → http://localhost:${PORT}`)
console.log(` Binary: ${CLAUDE_BIN}`)
console.log(` Using your Max subscription\n`)
})