feat: add dev mode with DEV_ env prefix
Support dual configuration: PROXY_TOKEN/SERVER_URL for production, DEV_PROXY_TOKEN/DEV_SERVER_URL for local development. - npm start → production - npm run start:dev → dev (PROXY_MODE=dev) - npm run dev → dev with hot-reload - Restore PTY auto-creation on sync_state for existing chats Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
11
.env.example
11
.env.example
@ -1,8 +1,7 @@
|
|||||||
# Токен устройства (получить в simple-chat: Настройки → Устройство)
|
# === Production ===
|
||||||
PROXY_TOKEN=
|
PROXY_TOKEN=
|
||||||
|
SERVER_URL=wss://ai-chat.vigdorov.ru/ws/agent-proxy
|
||||||
|
|
||||||
# WebSocket URL бэкенда simple-chat
|
# === Dev (используется при npm run start:dev / npm run dev) ===
|
||||||
# Локальная разработка:
|
DEV_PROXY_TOKEN=
|
||||||
SERVER_URL=ws://localhost:3000/ws/agent-proxy
|
DEV_SERVER_URL=ws://localhost:3000/ws/agent-proxy
|
||||||
# Продакшен:
|
|
||||||
# SERVER_URL=wss://ai-chat.vigdorov.ru/ws/agent-proxy
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "tsx src/main.ts",
|
"start": "tsx src/main.ts",
|
||||||
"dev": "tsx watch src/main.ts",
|
"start:dev": "PROXY_MODE=dev tsx src/main.ts",
|
||||||
|
"dev": "PROXY_MODE=dev tsx watch src/main.ts",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { homedir } from 'os';
|
|||||||
export interface ProxyConfig {
|
export interface ProxyConfig {
|
||||||
proxyToken: string;
|
proxyToken: string;
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
|
isDev: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadConfig(): ProxyConfig {
|
export function loadConfig(): ProxyConfig {
|
||||||
@ -16,12 +17,15 @@ export function loadConfig(): ProxyConfig {
|
|||||||
}
|
}
|
||||||
config(); // local .env (won't override existing vars)
|
config(); // local .env (won't override existing vars)
|
||||||
|
|
||||||
const proxyToken = process.env.PROXY_TOKEN;
|
const isDev = process.env.PROXY_MODE === 'dev';
|
||||||
|
const prefix = isDev ? 'DEV_' : '';
|
||||||
|
|
||||||
|
const proxyToken = process.env[`${prefix}PROXY_TOKEN`];
|
||||||
if (!proxyToken) {
|
if (!proxyToken) {
|
||||||
throw new Error('PROXY_TOKEN is required. Set it in .env or ~/.claude-proxy/.env');
|
throw new Error(`${prefix}PROXY_TOKEN is required. Set it in .env`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverUrl = process.env.SERVER_URL || 'wss://ai-chat.vigdorov.ru/ws/agent-proxy';
|
const serverUrl = process.env[`${prefix}SERVER_URL`] || 'wss://ai-chat.vigdorov.ru/ws/agent-proxy';
|
||||||
|
|
||||||
return { proxyToken, serverUrl };
|
return { proxyToken, serverUrl, isDev };
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/main.ts
22
src/main.ts
@ -23,15 +23,16 @@ import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|||||||
class ClaudeCliProxy {
|
class ClaudeCliProxy {
|
||||||
private ws: WsClient;
|
private ws: WsClient;
|
||||||
private pty: PtyManager;
|
private pty: PtyManager;
|
||||||
|
private config: ReturnType<typeof loadConfig>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const config = loadConfig();
|
this.config = loadConfig();
|
||||||
|
|
||||||
this.pty = new PtyManager();
|
this.pty = new PtyManager();
|
||||||
|
|
||||||
this.ws = new WsClient({
|
this.ws = new WsClient({
|
||||||
url: config.serverUrl,
|
url: this.config.serverUrl,
|
||||||
token: config.proxyToken,
|
token: this.config.proxyToken,
|
||||||
onMessage: (msg) => this.handleMessage(msg),
|
onMessage: (msg) => this.handleMessage(msg),
|
||||||
onBinary: (data) => this.handleBinaryFrame(data),
|
onBinary: (data) => this.handleBinaryFrame(data),
|
||||||
onConnected: () => this.onConnected(),
|
onConnected: () => this.onConnected(),
|
||||||
@ -40,7 +41,8 @@ class ClaudeCliProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
console.log('[proxy] Starting claude-cli-proxy (xterm mode)...');
|
const mode = this.config.isDev ? 'DEV' : 'PROD';
|
||||||
|
console.log(`[proxy] Starting claude-cli-proxy (${mode}) → ${this.config.serverUrl}`);
|
||||||
|
|
||||||
this.ws.connect();
|
this.ws.connect();
|
||||||
|
|
||||||
@ -114,15 +116,23 @@ class ClaudeCliProxy {
|
|||||||
const activePtys = new Set(this.pty.listPtys());
|
const activePtys = new Set(this.pty.listPtys());
|
||||||
const chatIds = new Set(payload.chats.map((c) => c.id));
|
const chatIds = new Set(payload.chats.map((c) => c.id));
|
||||||
|
|
||||||
// Only send replay + pty_ready for PTYs that already exist
|
|
||||||
// Do NOT auto-create PTYs — they should only be created via explicit create_pty
|
|
||||||
for (const chat of payload.chats) {
|
for (const chat of payload.chats) {
|
||||||
if (activePtys.has(chat.id)) {
|
if (activePtys.has(chat.id)) {
|
||||||
|
// PTY exists — send replay + ready
|
||||||
const replay = this.pty.getReplayBuffer(chat.id);
|
const replay = this.pty.getReplayBuffer(chat.id);
|
||||||
if (replay) {
|
if (replay) {
|
||||||
this.ws.sendBinary(encodeBinaryFrame(DIR_PTY_OUTPUT, chat.id, replay));
|
this.ws.sendBinary(encodeBinaryFrame(DIR_PTY_OUTPUT, chat.id, replay));
|
||||||
}
|
}
|
||||||
this.ws.send({ type: 'pty_ready', payload: { chatId: chat.id } });
|
this.ws.send({ type: 'pty_ready', payload: { chatId: chat.id } });
|
||||||
|
} else if (chat.workDir) {
|
||||||
|
// PTY not running — create it
|
||||||
|
this.handleCreatePty({
|
||||||
|
chatId: chat.id,
|
||||||
|
dir: chat.workDir,
|
||||||
|
resumeSessionId: chat.sessionId ?? undefined,
|
||||||
|
cols: chat.cols ?? 120,
|
||||||
|
rows: chat.rows ?? 40,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user