From b1e081b64f3874b29cc9f15b8c15c2101f0fcf84 Mon Sep 17 00:00:00 2001 From: aedes Date: Mon, 20 Apr 2026 11:06:24 +0300 Subject: [PATCH] feat: initial Claude chat (sechenov) --- .env.example | 1 + .gitignore | 2 + .omc/project-memory.json | 65 ++++++ .omc/state/idle-notif-cooldown.json | 3 + .omc/state/last-tool-error.json | 7 + CLAUDE.md | 20 ++ package.json | 10 + public/index.html | 315 ++++++++++++++++++++++++++++ server.js | 67 ++++++ 9 files changed, 490 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .omc/project-memory.json create mode 100644 .omc/state/idle-notif-cooldown.json create mode 100644 .omc/state/last-tool-error.json create mode 100644 CLAUDE.md create mode 100644 package.json create mode 100644 public/index.html create mode 100644 server.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..53c1030 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +ANTHROPIC_API_KEY=sk-ant-... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..713d500 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env diff --git a/.omc/project-memory.json b/.omc/project-memory.json new file mode 100644 index 0000000..8245a23 --- /dev/null +++ b/.omc/project-memory.json @@ -0,0 +1,65 @@ +{ + "version": "1.0.0", + "lastScanned": 1776671657588, + "projectRoot": "C:\\Users\\aedes\\REUNION\\sync\\scriptsAedes\\sechenov", + "techStack": { + "languages": [], + "frameworks": [], + "packageManager": null, + "runtime": null + }, + "build": { + "buildCommand": null, + "testCommand": null, + "lintCommand": null, + "devCommand": null, + "scripts": {} + }, + "conventions": { + "namingStyle": null, + "importStyle": null, + "testPattern": null, + "fileOrganization": null + }, + "structure": { + "isMonorepo": false, + "workspaces": [], + "mainDirectories": [], + "gitBranches": null + }, + "customNotes": [], + "directoryMap": {}, + "hotPaths": [ + { + "path": "server.js", + "accessCount": 3, + "lastAccessed": 1776672164219, + "type": "file" + }, + { + "path": "package.json", + "accessCount": 2, + "lastAccessed": 1776672170754, + "type": "file" + }, + { + "path": "CLAUDE.md", + "accessCount": 1, + "lastAccessed": 1776671784032, + "type": "file" + }, + { + "path": ".env.example", + "accessCount": 1, + "lastAccessed": 1776671815334, + "type": "file" + }, + { + "path": "public\\index.html", + "accessCount": 1, + "lastAccessed": 1776672194456, + "type": "file" + } + ], + "userDirectives": [] +} \ No newline at end of file diff --git a/.omc/state/idle-notif-cooldown.json b/.omc/state/idle-notif-cooldown.json new file mode 100644 index 0000000..5821e6c --- /dev/null +++ b/.omc/state/idle-notif-cooldown.json @@ -0,0 +1,3 @@ +{ + "lastSentAt": "2026-04-20T07:57:42.926Z" +} \ No newline at end of file diff --git a/.omc/state/last-tool-error.json b/.omc/state/last-tool-error.json new file mode 100644 index 0000000..350deec --- /dev/null +++ b/.omc/state/last-tool-error.json @@ -0,0 +1,7 @@ +{ + "tool_name": "Bash", + "tool_input_preview": "{\"command\":\"cd \\\"/c/Users/aedes/REUNION/sync/scriptsAedes/sechenov\\\" && git init && git remote add origin ssh://git@git.akyra.ru:2222/aedes/sechenov.git && git add . && git commit -m \\\"feat: initial C...", + "error": "Exit code 128\nReinitialized existing Git repository in C:/Users/aedes/REUNION/sync/scriptsAedes/sechenov/.git/\nwarning: in the working copy of 'CLAUDE.md', LF will be replaced by CRLF the next time Git touches it\nwarning: in the working copy of '.env.example', LF will be replaced by CRLF the next time Git touches it\nwarning: in the working copy of '.gitignore', LF will be replaced by CRLF the next time Git touches it\nwarning: in the working copy of '.omc/project-memory.json', LF will be replaced...", + "timestamp": "2026-04-20T08:06:16.903Z", + "retry_count": 3 +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e103e55 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,20 @@ +# sechenov - + +## Stack +- TODO + +## Structure +sechenov/ +- TODO + +## Local (machine B) +- Path: C:\Users\aedes\REUNION\sync\scriptsAedes\sechenov + +## Remote (Debian V) +- Path: /opt/stack/sechenov/ +- URL: https://cc-sechenov +- Port: - +- Deploy: git push -> ssh aedes@192.168.1.20 "cd /opt/stack/sechenov && git pull && docker compose restart sechenov" + +## Update this file +After any changes to structure, stack or configs - update this CLAUDE.md. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..0f57120 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "sechenov", + "version": "1.0.0", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.19.2" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..5d62983 --- /dev/null +++ b/public/index.html @@ -0,0 +1,315 @@ + + + + + + Sechenov — Claude Chat + + + +
+

Claude Chat

+ + +
+ +
+
+
Запрос
+ +
+
+
Ответ
+
Ответ появится здесь...
+
+
+ + + + + + diff --git a/server.js b/server.js new file mode 100644 index 0000000..475baa7 --- /dev/null +++ b/server.js @@ -0,0 +1,67 @@ +const express = require('express'); +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const app = express(); +app.use(express.json({ limit: '10mb' })); +app.use(express.static(path.join(__dirname, 'public'))); + +// Resolve claude binary +let CLAUDE_BIN = 'claude'; +{ + const local = '/app/claude-bin/node_modules/.bin/claude'; + if (fs.existsSync(local)) { + CLAUDE_BIN = local; + } else { + try { + CLAUDE_BIN = execSync('which claude 2>/dev/null || echo claude', { encoding: 'utf8' }).trim(); + } catch (e) {} + } +} +console.log(`✓ Claude binary: ${CLAUDE_BIN}`); + +const MODELS = { + sonnet: 'claude-sonnet-4-6', + haiku: 'claude-haiku-4-5-20251001', +}; + +app.post('/api/chat', (req, res) => { + const { text, model = 'sonnet' } = req.body; + if (!text?.trim()) return res.status(400).json({ error: 'text required' }); + + const modelId = MODELS[model] || MODELS.sonnet; + const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'); + + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + + const env = { ...process.env, CLAUDE_CONFIG_DIR: configDir }; + + const proc = spawn(CLAUDE_BIN, ['-p', text, '--model', modelId, '--output-format', 'text'], { + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + proc.stdout.on('data', (chunk) => { + res.write(`data: ${JSON.stringify({ text: chunk.toString() })}\n\n`); + }); + + proc.stderr.on('data', (chunk) => { + console.error('[claude stderr]', chunk.toString().substring(0, 300)); + }); + + proc.on('close', (code) => { + if (code !== 0) { + res.write(`data: ${JSON.stringify({ error: `Ошибка (code ${code}). Проверь auth: claude --version` })}\n\n`); + } + res.write('data: [DONE]\n\n'); + res.end(); + }); + + req.on('close', () => { try { proc.kill(); } catch (e) {} }); +}); + +app.listen(8108, () => console.log('sechenov :8108'));