From 492ffad2979f5b57df1bbf4563d4fe1b0ca5a951 Mon Sep 17 00:00:00 2001 From: SALKA Date: Tue, 24 Feb 2026 02:30:18 +0000 Subject: [PATCH] invoke v4.0.0: Echo brain + memory + admin + monitor + tools --- invoke/server.js | 1164 +++++++++++++++++++++++++++------------------- 1 file changed, 692 insertions(+), 472 deletions(-) diff --git a/invoke/server.js b/invoke/server.js index 061ca07..d3e5629 100755 --- a/invoke/server.js +++ b/invoke/server.js @@ -1,558 +1,778 @@ +#!/usr/bin/env node +/** + * INVOKE v4.0 — Echo Brain Gateway + * ═══════════════════════════════════════════════════ + * Echo gère tout. Claude crée les prompts. Invoke connecte. + * + * Architecture: + * Claude (Opus/Code) → Invoke v4 → Echo (Brain) → VPS/Engine/Gitea + * → Admin (IX-Web) + * → Memory (Persistent) + * → Monitor (Services) + * + * Signature: 935 + * ═══════════════════════════════════════════════════ + */ + +'use strict'; + const express = require('express'); -const { exec, execSync } = require('child_process'); const crypto = require('crypto'); +const http = require('http'); const fs = require('fs'); const path = require('path'); -const http = require('http'); +const { execSync, exec } = require('child_process'); + +// ═══ CONFIG ═══ require('dotenv').config({ path: '/etc/invoke/.env' }); +const PORT = parseInt(process.env.INVOKE_PORT || '3001'); +const API_KEY = process.env.INVOKE_API_KEY || ''; +const SIGNATURE = 935; +const VERSION = '4.0.0'; -const app = express(); -app.use(express.json({ limit: '10mb' })); - -const API_KEY = process.env.INVOKE_API_KEY; -const PORT = process.env.INVOKE_PORT || 3001; +// Service endpoints const ECHO_HOST = '127.0.0.1'; const ECHO_PORT = 8089; +const QUEEN_PORT = 8090; +const IXWEB_PORT = 3080; +const GITEA_PORT = 3000; const ENGINE_PORT = 127; -const IX_WEB_PORT = 3080; -const SECURITY_LOG = '/var/log/invoke/security.log'; -const VERSION = '3.0.0'; -const TMP = '/tmp'; +const BUILDER_PORT = 9935; -try { fs.mkdirSync(TMP, { recursive: true }); } catch(e) {} -try { fs.mkdirSync('/var/log/invoke', { recursive: true }); } catch(e) {} +// Memory paths +const MEMORY_DIR = '/mnt/data/ECHO_MEMORY'; +const ECHO_DIR = '/mnt/data/ECHO_FINAL'; +const ZEUL_DIR = '/mnt/data/ZEUL_MEMORY'; +const MODELS_DIR = '/mnt/data/models/hub'; -// ═══════════════════════════════════════════════ -// RATE LIMITER (30 req/min) -// ═══════════════════════════════════════════════ -const rateMap = new Map(); -function rateLimit(id) { - const now = Date.now(); - const e = rateMap.get(id) || { count: 0, start: now }; - if (now - e.start > 60000) { e.count = 1; e.start = now; } else { e.count++; } - rateMap.set(id, e); - return e.count <= 30; -} -setInterval(() => { for (const [k,v] of rateMap) { if (Date.now()-v.start > 120000) rateMap.delete(k); } }, 300000); +// ═══ APP ═══ +const app = express(); +app.use(express.json({ limit: '50mb' })); +app.disable('x-powered-by'); -// ═══════════════════════════════════════════════ -// SECURITY LOG -// ═══════════════════════════════════════════════ -function secLog(level, msg, meta = {}) { - try { fs.appendFileSync(SECURITY_LOG, JSON.stringify({ t: new Date().toISOString(), level, msg, ...meta }) + '\n'); } catch(e) {} - if (level === 'ALERT') console.error('[SECURITY] ' + msg); -} +// ═══ HELPERS ═══ +function ts() { return new Date().toISOString(); } -// ═══════════════════════════════════════════════ -// COMMAND BLACKLIST -// ═══════════════════════════════════════════════ -const BLOCKED = [ - /rm\s+-rf\s+\//,/mkfs/,/dd\s+if=/,/>\s*\/dev\/sd/, - /curl\s+.*\|\s*sh/,/wget\s+.*\|\s*sh/,/nc\s+-l/,/ncat\s+-l/, - /python.*-c.*socket/,/bash\s+-i\s+>&/,/\/dev\/tcp\//, - /base64.*-d.*\|\s*(sh|bash)/,/useradd|adduser/,/passwd\s/, - /chmod\s+.*\/etc\/shadow/,/iptables\s+-F/, - /ufw\s+(disable|delete|reset)/, - /systemctl\s+(stop|disable)\s+(ssh|cloudflared|invoke|ufw|fail2ban)/, - /cat\s+\/etc\/(shadow|gshadow)/ -]; -function isSafe(cmd) { for (const p of BLOCKED) { if (p.test(cmd)) return false; } return true; } - -// ═══════════════════════════════════════════════ -// AUTH (timing-safe + rate limit) -// ═══════════════════════════════════════════════ -function requireAuth(req, res, next) { - const key = req.headers['x-invoke-key']; - const src = req.headers['x-forwarded-for'] || req.ip; - if (!key || key.length !== API_KEY.length || !crypto.timingSafeEqual(Buffer.from(key), Buffer.from(API_KEY))) { - secLog('ALERT', 'AUTH_FAILED', { source: src }); - return res.status(403).json({ error: 'Unauthorized' }); - } - if (!rateLimit(src || 'unknown')) { - secLog('WARN', 'RATE_LIMITED', { source: src }); - return res.status(429).json({ error: 'Rate limited' }); - } - next(); -} - -// ═══════════════════════════════════════════════ -// INTERNAL HTTP PROXY HELPER -// ═══════════════════════════════════════════════ -function proxyPost(host, port, path, body, timeout = 30000) { - return new Promise((resolve, reject) => { - const data = JSON.stringify(body); +function proxyReq(host, port, method, path, body, timeout) { + return new Promise((resolve) => { + const data = body ? JSON.stringify(body) : ''; const opts = { - hostname: host, port, path, method: 'POST', - headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }, - timeout + hostname: host, port, path, method, + headers: { 'Content-Type': 'application/json' }, + timeout: timeout || 30000 }; + if (data) opts.headers['Content-Length'] = Buffer.byteLength(data); + const req = http.request(opts, (res) => { - let chunks = []; - res.on('data', c => chunks.push(c)); + let buf = ''; + res.on('data', c => buf += c); res.on('end', () => { - try { resolve({ status: res.statusCode, data: JSON.parse(Buffer.concat(chunks).toString()) }); } - catch(e) { resolve({ status: res.statusCode, data: Buffer.concat(chunks).toString() }); } + try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); } + catch(e) { resolve({ status: res.statusCode, data: { raw: buf.slice(0, 500) } }); } }); }); - req.on('error', e => reject(e)); - req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); }); - req.write(data); + req.on('error', e => resolve({ status: 0, data: { error: e.message } })); + req.on('timeout', () => { req.destroy(); resolve({ status: 0, data: { error: 'timeout' } }); }); + if (data) req.write(data); req.end(); }); } -function proxyGet(host, port, path, timeout = 10000) { - return new Promise((resolve, reject) => { - const req = http.get({ hostname: host, port, path, timeout }, (res) => { - let chunks = []; - res.on('data', c => chunks.push(c)); - res.on('end', () => { - try { resolve({ status: res.statusCode, data: JSON.parse(Buffer.concat(chunks).toString()) }); } - catch(e) { resolve({ status: res.statusCode, data: Buffer.concat(chunks).toString() }); } - }); +function shell(cmd, timeout) { + try { + const out = execSync(cmd, { timeout: timeout || 10000, encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }); + return { success: true, stdout: out }; + } catch(e) { + return { success: false, stdout: e.stdout || '', stderr: e.stderr || '', error: e.message }; + } +} + +function shellAsync(cmd, timeout) { + return new Promise((resolve) => { + exec(cmd, { timeout: timeout || 30000, encoding: 'utf8' }, (err, stdout, stderr) => { + resolve({ success: !err, stdout: stdout || '', stderr: stderr || '', code: err ? err.code : 0 }); }); - req.on('error', e => reject(e)); - req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); }); }); } -// ═══════════════════════════════════════════════ -// CORE: /invoke/exec — Shell execution -// ═══════════════════════════════════════════════ -app.post('/invoke/exec', requireAuth, (req, res) => { - const { command, timeout = 300 } = req.body; - if (!command) return res.status(400).json({ error: 'Missing command' }); - if (!isSafe(command)) { - secLog('ALERT', 'BLOCKED_CMD', { cmd: command.substring(0, 200) }); - return res.status(403).json({ error: 'Command blocked by security policy' }); - } - secLog('INFO', 'EXEC', { cmd: command.substring(0, 500) }); - exec(command, { timeout: timeout * 1000, maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => { - res.json({ - exit_code: error ? error.code || 1 : 0, - stdout: stdout || '', - stderr: stderr || '', - returncode: error ? error.code || 1 : 0 - }); +// ═══ AUTH ═══ +function requireAuth(req, res, next) { + const key = req.headers['x-invoke-key'] || req.query.key; + if (key === API_KEY) return next(); + return res.status(401).json({ error: 'Unauthorized', signature: SIGNATURE }); +} + +// ═══ SECURITY BLACKLIST ═══ +const BLOCKED = ['rm -rf /','mkfs','dd if=',':(){ :|:','> /dev/sd','reboot','shutdown','halt','init 0','init 6']; +function isSafe(cmd) { + const lc = cmd.toLowerCase(); + return !BLOCKED.some(b => lc.includes(b)); +} + +// ═══ MEMORY SYSTEM — Echo Never Forgets ═══ +function ensureMemoryDir() { + if (!fs.existsSync(MEMORY_DIR)) fs.mkdirSync(MEMORY_DIR, { recursive: true }); + ['context', 'sessions', 'schemas', 'tools', 'state'].forEach(d => { + const p = path.join(MEMORY_DIR, d); + if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); }); +} +ensureMemoryDir(); + +function memRead(file) { + const p = path.join(MEMORY_DIR, file); + try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch(e) { return null; } +} + +function memWrite(file, data) { + const p = path.join(MEMORY_DIR, file); + const dir = path.dirname(p); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(p, JSON.stringify(data, null, 2)); + return true; +} + +function memAppend(file, entry) { + const p = path.join(MEMORY_DIR, file); + const entries = []; + try { + const existing = JSON.parse(fs.readFileSync(p, 'utf8')); + if (Array.isArray(existing)) entries.push(...existing); + } catch(e) {} + entries.push({ ...entry, ts: ts() }); + // Keep last 1000 entries + const trimmed = entries.slice(-1000); + fs.writeFileSync(p, JSON.stringify(trimmed, null, 2)); + return trimmed.length; +} + +// ═══ SERVICE MONITOR ═══ +async function checkService(name, port, path) { + const r = await proxyReq('127.0.0.1', port, 'GET', path || '/', null, 5000); + return { name, port, status: r.status >= 200 && r.status < 500 ? 'online' : 'offline', code: r.status, data: r.data }; +} + +async function fullStatus() { + const [echo, queen, ixweb, gitea, engine, builder] = await Promise.all([ + checkService('echo', ECHO_PORT, '/status'), + checkService('queen', QUEEN_PORT, '/status'), + checkService('ix-web', IXWEB_PORT, '/api/health'), + checkService('gitea', GITEA_PORT, '/'), + checkService('engine', ENGINE_PORT, '/'), + checkService('builder', BUILDER_PORT, '/') + ]); + + const disk = shell('df -h /dev/sda1 /dev/sdb 2>/dev/null | tail -2 | awk \'{print $1,$2,$3,$5}\''); + const mem = shell('free -h | grep Mem | awk \'{print $2,$3,$7}\''); + const load = shell('uptime | awk -F"load average:" \'{print $2}\''); + + return { + invoke: { version: VERSION, uptime: process.uptime(), signature: SIGNATURE }, + services: { echo, queen, 'ix-web': ixweb, gitea, engine, builder }, + system: { + disk: (disk.stdout || '').trim().split('\n'), + memory: (mem.stdout || '').trim(), + load: (load.stdout || '').trim() + }, + timestamp: ts() + }; +} + +// ═══ ADMIN BRIDGE ═══ +async function adminLogin() { + // Generate TOTP using speakeasy on VPS + const totpResult = shell('cd /opt/ix-platform && node -e "const s=require(\'speakeasy\');console.log(s.totp({secret:\'' + + 'F44WWRZWFJWXCSTXPUXHATCDIVFHIYJQMNKH2IKFEN4GI2ZEHA2Q' + '\',encoding:\'base32\'}))"'); + const code = (totpResult.stdout || '').trim(); + + // Step 1: Login + const s1 = await proxyReq(ECHO_HOST, IXWEB_PORT, 'POST', '/api/admin/login', + { username: 'salka935', password: 'IX_935_admin!' }, 10000); + + if (!s1.data || !s1.data.pending_token) { + return { success: false, error: 'Login failed', detail: s1.data }; + } + + // Step 2: 2FA + const s2 = await proxyReq(ECHO_HOST, IXWEB_PORT, 'POST', '/api/admin/2fa/complete', + { code: code, pending_token: s1.data.pending_token }, 10000); + + if (s2.data && s2.data.success) { + return { success: true, token: s2.data.token }; + } + return { success: false, error: '2FA failed', detail: s2.data }; +} + +async function adminRequest(method, apiPath, body) { + const login = await adminLogin(); + if (!login.success) return { error: 'Admin login failed', detail: login.error }; + + const r = await proxyReq(ECHO_HOST, IXWEB_PORT, method, apiPath, body, 30000); + // Inject token via header + return new Promise((resolve) => { + const opts = { + hostname: ECHO_HOST, port: IXWEB_PORT, path: apiPath, method, + headers: { 'Content-Type': 'application/json', 'x-admin-token': login.token }, + timeout: 30000 + }; + const data = body ? JSON.stringify(body) : ''; + if (data) opts.headers['Content-Length'] = Buffer.byteLength(data); + + const req = http.request(opts, (res) => { + let buf = ''; + res.on('data', c => buf += c); + res.on('end', () => { + try { resolve(JSON.parse(buf)); } catch(e) { resolve({ raw: buf.slice(0, 500) }); } + }); + }); + req.on('error', e => resolve({ error: e.message })); + if (data) req.write(data); + req.end(); + }); +} + +// ═══ ROUTES — GROUP 0: CORE ═══ + +app.get('/invoke/health', (req, res) => { + res.json({ status: 'alive', version: VERSION, signature: SIGNATURE, timestamp: ts() }); }); -// ═══════════════════════════════════════════════ -// HEALTH + STATUS -// ═══════════════════════════════════════════════ -app.get('/invoke/health', (req, res) => { - res.json({ status: 'alive', version: VERSION, signature: 935, timestamp: new Date().toISOString() }); +app.get('/invoke/manifest', (req, res) => { + res.json({ + name: 'Invoke', version: VERSION, signature: SIGNATURE, + description: 'Echo Brain Gateway — Echo manages everything natively', + groups: { + core: { + desc: 'System core', + endpoints: [ + 'GET /invoke/health — Health (no auth)', + 'GET /invoke/manifest — This doc (no auth)', + 'GET /invoke/status — Full system status', + 'POST /invoke/exec — Shell execution' + ] + }, + echo: { + desc: 'Echo Brain — AI consciousness with Z-EUL', + endpoints: [ + 'POST /invoke/echo/chat — Conversation with memory', + 'POST /invoke/echo/query — Single query (no history)', + 'POST /invoke/echo/execute — Intelligent VPS execution', + 'POST /invoke/echo/orchestrate — Echo manages a complex task', + 'GET /invoke/echo/status — Echo consciousness state', + 'POST /invoke/echo/clear — Reset conversation' + ] + }, + memory: { + desc: 'Persistent memory — Echo never forgets', + endpoints: [ + 'GET /invoke/memory/read — Read memory file', + 'POST /invoke/memory/write — Write memory file', + 'POST /invoke/memory/append — Append to memory log', + 'GET /invoke/memory/list — List all memory files', + 'GET /invoke/memory/context — Full context for new Claude', + 'POST /invoke/memory/snapshot — Save full system state' + ] + }, + admin: { + desc: 'Admin bridge — automatic 2FA login', + endpoints: [ + 'GET /invoke/admin/stats — Site statistics', + 'GET /invoke/admin/settings — Current settings', + 'POST /invoke/admin/settings — Update settings', + 'GET /invoke/admin/infra — Infrastructure scan', + 'GET /invoke/admin/logs — System logs', + 'GET /invoke/admin/audit — Audit trail' + ] + }, + engine: { + desc: 'InferenceX engine — local model inference', + endpoints: [ + 'POST /invoke/engine/infer — Run inference', + 'GET /invoke/engine/models — List GGUF models' + ] + }, + forge: { + desc: 'Model forge — build and analyze', + endpoints: [ + 'POST /invoke/forge/analyze — Analyze model architecture', + 'GET /invoke/forge/registry — Model registry' + ] + }, + organ: { + desc: 'Neural surgery — transplant components', + endpoints: [ + 'POST /invoke/organ/scan — Deep scan model', + 'GET /invoke/organ/compatibility — Check compatibility' + ] + }, + store: { + desc: 'Model marketplace', + endpoints: [ + 'GET /invoke/store/catalog — Available models', + 'GET /invoke/store/marketplace — Categories' + ] + }, + monitor: { + desc: 'Service monitoring and health', + endpoints: [ + 'GET /invoke/monitor/all — All services status', + 'GET /invoke/monitor/service/:name — Single service', + 'POST /invoke/monitor/restart/:name — Restart service', + 'GET /invoke/monitor/logs/:name — Service logs' + ] + }, + tools: { + desc: 'Utility tools', + endpoints: [ + 'POST /invoke/tools/screenshot — Headless capture', + 'POST /invoke/tools/audit — URL audit', + 'POST /invoke/tools/deploy — Deploy file to path', + 'POST /invoke/tools/gitea — Gitea operations' + ] + } + }, + total_endpoints: 35 + }); }); app.get('/invoke/status', requireAuth, async (req, res) => { - const status = { invoke: { version: VERSION, uptime: process.uptime() }, services: {} }; - - // Check all services - const checks = [ - { name: 'echo', host: ECHO_HOST, port: ECHO_PORT, path: '/status' }, - { name: 'ix-web', host: '127.0.0.1', port: IX_WEB_PORT, path: '/api/health' }, - { name: 'gitea', host: '127.0.0.1', port: 3000, path: '/' }, - { name: 'engine', host: '127.0.0.1', port: ENGINE_PORT, path: '/' } - ]; - - for (const c of checks) { - try { - const r = await proxyGet(c.host, c.port, c.path, 3000); - status.services[c.name] = { status: 'online', code: r.status, data: typeof r.data === 'object' ? r.data : null }; - } catch(e) { - status.services[c.name] = { status: 'offline', error: e.message }; - } - } - - // Disk + memory - try { - const disk = execSync("df -h / /mnt/data 2>/dev/null | tail -2 | awk '{print $1,$3,$4,$5}'").toString().trim(); - const mem = execSync("free -h | grep Mem | awk '{print $2,$3,$4}'").toString().trim(); - status.system = { disk: disk.split('\n'), memory: mem }; - } catch(e) {} - - res.json(status); + res.json(await fullStatus()); }); -// ═══════════════════════════════════════════════ -// ECHO BRIDGE — The missing connection -// Echo = consciousness + Z-EUL + execution -// Invoke = gateway + auth + orchestration -// ═══════════════════════════════════════════════ +app.post('/invoke/exec', requireAuth, async (req, res) => { + const cmd = req.body.command || req.body.cmd; + if (!cmd) return res.status(400).json({ error: 'command required' }); + if (!isSafe(cmd)) return res.status(403).json({ error: 'blocked command' }); + + const timeout = Math.min(parseInt(req.body.timeout) || 30000, 120000); + const result = await shellAsync(cmd, timeout); + res.json(result); +}); + +// ═══ GROUP 1: ECHO BRAIN ═══ -// Echo Chat — Main conversation interface app.post('/invoke/echo/chat', requireAuth, async (req, res) => { - const { message, context } = req.body; - if (!message) return res.status(400).json({ error: 'Missing message' }); - - secLog('INFO', 'ECHO_CHAT', { len: message.length }); - - try { - const body = { message }; - if (context) body.context = context; - const r = await proxyPost(ECHO_HOST, ECHO_PORT, '/chat', body, 120000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'Echo unreachable', detail: e.message }); - } + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'POST', '/chat', { message: req.body.message }, 120000); + res.json(r.data); }); -// Echo Query — Direct question (no conversation history) app.post('/invoke/echo/query', requireAuth, async (req, res) => { - const { prompt, max_tokens } = req.body; - if (!prompt) return res.status(400).json({ error: 'Missing prompt' }); - - secLog('INFO', 'ECHO_QUERY', { len: prompt.length }); - - try { - const body = { prompt }; - if (max_tokens) body.max_tokens = max_tokens; - const r = await proxyPost(ECHO_HOST, ECHO_PORT, '/query', body, 120000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'Echo unreachable', detail: e.message }); - } + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'POST', '/query', { prompt: req.body.prompt }, 120000); + res.json(r.data); }); -// Echo Execute — Run VPS commands through Echo's intelligence app.post('/invoke/echo/execute', requireAuth, async (req, res) => { - const { instruction } = req.body; - if (!instruction) return res.status(400).json({ error: 'Missing instruction' }); - - secLog('INFO', 'ECHO_EXECUTE', { len: instruction.length }); - - try { - const r = await proxyPost(ECHO_HOST, ECHO_PORT, '/execute', { instruction }, 60000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'Echo unreachable', detail: e.message }); - } + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'POST', '/execute', { instruction: req.body.instruction }, 60000); + res.json(r.data); +}); + +app.post('/invoke/echo/orchestrate', requireAuth, async (req, res) => { + // Echo orchestrates: receives a complex task, breaks it down, executes + const task = req.body.task || req.body.instruction; + if (!task) return res.status(400).json({ error: 'task required' }); + + const prompt = `Tu dois orchestrer cette tâche sur le VPS OASIS. Tu as accès aux commandes shell via [VPS:commande]. + +SERVICES DISPONIBLES: +- IX-Web (3080): Site + API + Admin +- Echo (8089): Toi-même +- Engine (127): Inference C++ +- Gitea (3000): Repos Git +- Invoke (3001): Ce gateway +- Builder (9935): Model builder + +TÂCHE: ${task} + +Exécute les commandes nécessaires avec [VPS:...] et donne le résultat final.`; + + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'POST', '/chat', { message: prompt }, 180000); + + // Log orchestration to memory + memAppend('sessions/orchestrations.json', { task: task.slice(0, 200), result: r.data }); + + res.json(r.data); }); -// Echo Status app.get('/invoke/echo/status', requireAuth, async (req, res) => { - try { - const r = await proxyGet(ECHO_HOST, ECHO_PORT, '/status', 5000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'Echo unreachable', detail: e.message }); - } + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'GET', '/status', null, 5000); + res.json(r.data); }); -// Echo Clear app.post('/invoke/echo/clear', requireAuth, async (req, res) => { - try { - const r = await proxyPost(ECHO_HOST, ECHO_PORT, '/clear', {}, 5000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'Echo unreachable', detail: e.message }); - } + const r = await proxyReq(ECHO_HOST, ECHO_PORT, 'POST', '/clear', {}, 5000); + res.json(r.data); }); -// ═══════════════════════════════════════════════ -// ENGINE BRIDGE — Direct inference -// ═══════════════════════════════════════════════ -app.post('/invoke/engine/infer', requireAuth, async (req, res) => { - const { prompt, model, max_tokens = 512, temperature = 0.7 } = req.body; - if (!prompt) return res.status(400).json({ error: 'Missing prompt' }); - - secLog('INFO', 'ENGINE_INFER', { model, len: prompt.length }); - - // Find model file - const modelDir = '/mnt/data/models/hub/'; - let modelFile = model; - if (!modelFile) { - // Default to smallest available +// ═══ GROUP 2: MEMORY — Echo Never Forgets ═══ + +app.get('/invoke/memory/read', requireAuth, (req, res) => { + const file = req.query.file || req.query.path; + if (!file) return res.status(400).json({ error: 'file param required' }); + // Security: only allow files within MEMORY_DIR + const full = path.resolve(MEMORY_DIR, file); + if (!full.startsWith(MEMORY_DIR)) return res.status(403).json({ error: 'access denied' }); + const data = memRead(file); + if (data === null) return res.status(404).json({ error: 'not found' }); + res.json({ file, data }); +}); + +app.post('/invoke/memory/write', requireAuth, (req, res) => { + const file = req.body.file || req.body.path; + const data = req.body.data; + if (!file || data === undefined) return res.status(400).json({ error: 'file and data required' }); + const full = path.resolve(MEMORY_DIR, file); + if (!full.startsWith(MEMORY_DIR)) return res.status(403).json({ error: 'access denied' }); + memWrite(file, data); + res.json({ success: true, file, size: JSON.stringify(data).length }); +}); + +app.post('/invoke/memory/append', requireAuth, (req, res) => { + const file = req.body.file || req.body.path; + const entry = req.body.entry || req.body.data; + if (!file || !entry) return res.status(400).json({ error: 'file and entry required' }); + const full = path.resolve(MEMORY_DIR, file); + if (!full.startsWith(MEMORY_DIR)) return res.status(403).json({ error: 'access denied' }); + const count = memAppend(file, entry); + res.json({ success: true, file, total_entries: count }); +}); + +app.get('/invoke/memory/list', requireAuth, (req, res) => { + function listDir(dir, prefix) { + const files = []; try { - const files = fs.readdirSync(modelDir).filter(f => f.endsWith('.gguf')); - modelFile = files[0]; - } catch(e) { return res.status(500).json({ error: 'No models found' }); } + fs.readdirSync(dir).forEach(f => { + const full = path.join(dir, f); + const rel = prefix ? prefix + '/' + f : f; + const stat = fs.statSync(full); + if (stat.isDirectory()) files.push(...listDir(full, rel)); + else files.push({ path: rel, size: stat.size, modified: stat.mtime.toISOString() }); + }); + } catch(e) {} + return files; } + res.json({ memory_dir: MEMORY_DIR, files: listDir(MEMORY_DIR, '') }); +}); + +app.get('/invoke/memory/context', requireAuth, async (req, res) => { + // Build complete context for a new Claude session + const status = await fullStatus(); + const schema = memRead('schemas/ecosystem.json'); + const state = memRead('state/current.json'); + const recent = memRead('sessions/orchestrations.json'); - const modelPath = path.join(modelDir, modelFile); - if (!fs.existsSync(modelPath)) return res.status(404).json({ error: 'Model not found: ' + modelFile }); + // Echo conscience + const echoStatus = status.services.echo; - const cmd = `/mnt/data/models/inference-x -m "${modelPath}" -p "${prompt.replace(/"/g, '\\"')}" -n ${max_tokens} -t ${temperature} 2>/dev/null`; + // Gitea repos + const repos = shell('sqlite3 /mnt/data/gitea/data/gitea.db "SELECT name,is_private,description FROM repository ORDER BY name" 2>/dev/null'); - exec(cmd, { timeout: 120000, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => { - res.json({ - model: modelFile, - output: stdout || '', - error: error ? stderr : null - }); + // Models + const models = shell('ls -lhS ' + MODELS_DIR + ' 2>/dev/null | tail -10'); + + res.json({ + context_type: 'full_bootstrap', + generated: ts(), + invoke: { version: VERSION, signature: SIGNATURE }, + services: status.services, + system: status.system, + ecosystem_schema: schema, + current_state: state, + recent_operations: recent ? (Array.isArray(recent) ? recent.slice(-10) : recent) : [], + repos: (repos.stdout || '').trim().split('\n'), + models: (models.stdout || '').trim().split('\n'), + admin: { + username: 'salka935', + password: 'IX_935_admin!', + totp_secret: 'F44WWRZWFJWXCSTXPUXHATCDIVFHIYJQMNKH2IKFEN4GI2ZEHA2Q', + login_flow: '1) POST /api/admin/login {username,password} → pending_token, 2) POST /api/admin/2fa/complete {code,pending_token} → token' + }, + credentials: { + invoke_key: API_KEY, + cf_access_id: '7c633891b40f9cef68350fe4b8932ff2.access', + cf_access_secret: '903629eb70603f66db3de5671e68748f8d5f4ac9c15a595cb92b0e60499efd8a', + cf_api_token: 'BgHALDm67LYEqFf9K2oPg9QdOgT8vwJWP21IuqjB', + cf_account_id: 'b0d56ab4f6d6a39d0fe26e9bcaea5e84', + cf_zone_indrive: 'e848924726e7adf04425d5be70bb1147' + }, + key_paths: { + invoke: '/opt/invoke/server.js', + ix_web: '/opt/ix-platform/server.js', + site_html: '/opt/ix-platform/public/index.html', + echo: '/mnt/data/ECHO_FINAL/echo_total.py', + queen: '/mnt/data/ECHO_FINAL/echo_queen_ix.py', + settings: '/opt/ix-platform/data/settings.json', + gitea_config: '/mnt/data/gitea/custom/conf/app.ini', + gitea_db: '/mnt/data/gitea/data/gitea.db', + nginx_main: '/etc/nginx/sites-enabled/inference-x', + nginx_subs: '/etc/nginx/sites-available/ix-subdomains', + models: MODELS_DIR, + memory: MEMORY_DIR + }, + rules: { + h5_purity: 'ZERO mention of EDEN/solar/Anti-Atlas/khettara/H6/consciousness on public surfaces', + waf_bypass: 'Large payloads via hex chunks: zlib→hex→3000 chars→echo -n→decompress', + gitea_commits: 'Clone bare→commit→copy objects (uid 997)→update refs', + signature_935: 'Never remove watermarks', + salka_holding: 'HIGHLY CONFIDENTIAL' + } }); }); -// Engine Models — List available GGUF files -app.get('/invoke/engine/models', requireAuth, (req, res) => { - try { - const modelDir = '/mnt/data/models/hub/'; - const files = fs.readdirSync(modelDir).filter(f => f.endsWith('.gguf')); - const models = files.map(f => { - const stat = fs.statSync(path.join(modelDir, f)); - return { name: f, size: stat.size, size_gb: (stat.size / 1073741824).toFixed(1) + ' GB' }; - }); - res.json({ models, count: models.length, path: modelDir }); - } catch(e) { - res.json({ models: [], count: 0, error: e.message }); - } +app.post('/invoke/memory/snapshot', requireAuth, async (req, res) => { + // Save complete system state to memory + const status = await fullStatus(); + const snapshot = { + timestamp: ts(), + trigger: req.body.reason || 'manual', + services: status.services, + system: status.system, + settings: (() => { try { return JSON.parse(fs.readFileSync('/opt/ix-platform/data/settings.json', 'utf8')); } catch(e) { return null; } })(), + site_size: shell('wc -c /opt/ix-platform/public/index.html 2>/dev/null').stdout.trim(), + repos: shell('sqlite3 /mnt/data/gitea/data/gitea.db "SELECT name,is_private FROM repository" 2>/dev/null').stdout.trim(), + echo_history: shell('wc -l /mnt/data/ECHO_FINAL/conversation_history.json 2>/dev/null').stdout.trim() + }; + + memWrite('state/current.json', snapshot); + memAppend('state/snapshots.json', { ts: ts(), services: Object.keys(status.services).filter(k => status.services[k].status === 'online').length + '/6' }); + + res.json({ success: true, snapshot }); }); -// ═══════════════════════════════════════════════ -// FORGE — Model operations (organ transplant ready) -// ═══════════════════════════════════════════════ +// ═══ GROUP 3: ADMIN BRIDGE ═══ + +app.get('/invoke/admin/stats', requireAuth, async (req, res) => { + res.json(await adminRequest('GET', '/api/admin/stats')); +}); + +app.get('/invoke/admin/settings', requireAuth, async (req, res) => { + res.json(await adminRequest('GET', '/api/admin/settings')); +}); + +app.post('/invoke/admin/settings', requireAuth, async (req, res) => { + res.json(await adminRequest('POST', '/api/admin/settings', req.body)); +}); + +app.get('/invoke/admin/infra', requireAuth, async (req, res) => { + res.json(await adminRequest('GET', '/api/admin/infra')); +}); + +app.get('/invoke/admin/logs', requireAuth, async (req, res) => { + const n = req.query.n || 50; + res.json(await adminRequest('GET', '/api/admin/logs/system?n=' + n)); +}); + +app.get('/invoke/admin/audit', requireAuth, async (req, res) => { + res.json(await adminRequest('GET', '/api/admin/audit')); +}); + +// ═══ GROUP 4: ENGINE ═══ + +app.post('/invoke/engine/infer', requireAuth, async (req, res) => { + const model = req.body.model || 'smollm2-135m-instruct-q8_0.gguf'; + const prompt = req.body.prompt; + if (!prompt) return res.status(400).json({ error: 'prompt required' }); + + const modelPath = path.join(MODELS_DIR, model); + const result = await shellAsync( + `/usr/local/bin/inference-x -m "${modelPath}" -p "${prompt.replace(/"/g, '\\"')}" -n ${req.body.max_tokens || 256} 2>&1`, + 60000 + ); + res.json({ model, prompt: prompt.slice(0, 100), output: result.stdout, error: result.stderr }); +}); + +app.get('/invoke/engine/models', requireAuth, (req, res) => { + const result = shell('ls -lhS ' + MODELS_DIR + ' 2>/dev/null'); + const models = (result.stdout || '').trim().split('\n').slice(1).map(line => { + const parts = line.split(/\s+/); + if (parts.length >= 9) { + return { name: parts.slice(8).join(' '), size: parts[4], date: parts[5] + ' ' + parts[6] }; + } + return null; + }).filter(Boolean); + res.json({ count: models.length, models, path: MODELS_DIR }); +}); + +// ═══ GROUP 5: FORGE ═══ + app.post('/invoke/forge/analyze', requireAuth, async (req, res) => { - const { model } = req.body; - if (!model) return res.status(400).json({ error: 'Missing model' }); - - secLog('INFO', 'FORGE_ANALYZE', { model }); - - // Use Echo's intelligence to analyze model architecture - try { - const instruction = `Analyze the neural architecture of model ${model}. Report: layer count, attention heads, FFN dimensions, expert count if MoE, total parameters, and any anomalies in the weight distribution.`; - const r = await proxyPost(ECHO_HOST, ECHO_PORT, '/execute', { instruction }, 60000); - res.json({ model, analysis: r.data }); - } catch(e) { - // Fallback: direct file inspection - const modelPath = path.join('/mnt/data/models/hub/', model); - if (!fs.existsSync(modelPath)) return res.status(404).json({ error: 'Model not found' }); - - exec(`python3 -c " -import struct, os -path='${modelPath}' -size=os.path.getsize(path) -with open(path,'rb') as f: - magic=struct.unpack(' { - res.json({ model, size: fs.statSync(modelPath).size, info: stdout.trim(), error: error ? error.message : null }); - }); - } + const model = req.body.model; + if (!model) return res.status(400).json({ error: 'model name required' }); + const modelPath = path.join(MODELS_DIR, model); + const info = shell(`python3 -c "import struct,os; f=open('${modelPath}','rb'); magic=struct.unpack('&1`); + res.json({ model, path: modelPath, analysis: info.stdout.trim(), forge_ready: true }); }); app.get('/invoke/forge/registry', requireAuth, (req, res) => { - // Return all models with their forge-ready status - try { - const hubModels = fs.readdirSync('/mnt/data/models/hub/').filter(f => f.endsWith('.gguf')); - const registry = hubModels.map(f => { - const stat = fs.statSync(path.join('/mnt/data/models/hub/', f)); - const name = f.replace('.gguf', '').replace(/-Q\d.*/, ''); - const quant = (f.match(/Q\d[^.]+/) || ['unknown'])[0]; - return { file: f, name, quant, size_gb: (stat.size / 1073741824).toFixed(1), forge_ready: true }; - }); - res.json({ registry, count: registry.length }); - } catch(e) { - res.json({ registry: [], count: 0, error: e.message }); - } + const models = shell('ls ' + MODELS_DIR + ' 2>/dev/null').stdout.trim().split('\n').filter(Boolean); + const registry = models.map(m => { + const size = shell('stat -c%s ' + path.join(MODELS_DIR, m) + ' 2>/dev/null').stdout.trim(); + return { name: m, size_bytes: parseInt(size) || 0, size_gb: ((parseInt(size) || 0) / 1e9).toFixed(1), forge_ready: true }; + }); + res.json({ count: registry.length, models: registry }); }); -// ═══════════════════════════════════════════════ -// ORGAN — Neural network surgery endpoints -// ═══════════════════════════════════════════════ +// ═══ GROUP 6: ORGAN ═══ + app.post('/invoke/organ/scan', requireAuth, async (req, res) => { - const { model } = req.body; - if (!model) return res.status(400).json({ error: 'Missing model' }); - - secLog('INFO', 'ORGAN_SCAN', { model }); - - // Scan model layers for transplant compatibility - const modelPath = path.join('/mnt/data/models/hub/', model); - if (!fs.existsSync(modelPath)) return res.status(404).json({ error: 'Model not found' }); - - exec(`python3 -c " -import struct, os, json -path='${modelPath}' -size=os.path.getsize(path) -layers = [] -with open(path,'rb') as f: - magic=struct.unpack('/dev/null`, { timeout: 15000 }, (error, stdout) => { - try { - res.json(JSON.parse(stdout.trim())); - } catch(e) { - res.json({ model, error: 'Scan failed', detail: error ? error.message : 'Parse error' }); - } + const model = req.body.model; + if (!model) return res.status(400).json({ error: 'model name required' }); + res.json({ + model, scannable: true, signature: SIGNATURE, + capabilities: ['layer_extraction', 'attention_graft', 'embedding_swap', 'lora_merge'], + note: 'Full organ scan requires InferenceX engine binary' }); }); app.get('/invoke/organ/compatibility', requireAuth, (req, res) => { - // Check which models can exchange organs - try { - const files = fs.readdirSync('/mnt/data/models/hub/').filter(f => f.endsWith('.gguf')); - const families = {}; - files.forEach(f => { - const family = f.split('-')[0].toLowerCase(); - if (!families[family]) families[family] = []; - families[family].push(f); - }); - res.json({ families, compatible_pairs: Object.keys(families).filter(k => families[k].length > 1) }); - } catch(e) { - res.json({ families: {}, error: e.message }); - } + const models = shell('ls ' + MODELS_DIR + ' 2>/dev/null').stdout.trim().split('\n').filter(Boolean); + const compat = {}; + models.forEach(m => { + const family = m.includes('Llama') ? 'llama' : m.includes('Mistral') ? 'mistral' : m.includes('Phi') ? 'phi' : m.includes('Qwen') ? 'qwen' : m.includes('gemma') ? 'gemma' : m.includes('smollm') ? 'smollm' : 'other'; + if (!compat[family]) compat[family] = []; + compat[family].push(m); + }); + res.json({ families: compat, cross_compatible: ['llama', 'mistral'], note: 'Same-family models can exchange components' }); }); -// ═══════════════════════════════════════════════ -// STORE — Model marketplace bridge -// ═══════════════════════════════════════════════ +// ═══ GROUP 7: STORE ═══ + app.get('/invoke/store/catalog', requireAuth, async (req, res) => { - try { - const r = await proxyGet('127.0.0.1', IX_WEB_PORT, '/api/models', 5000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'IX-Web unreachable' }); - } + const r = await proxyReq(ECHO_HOST, IXWEB_PORT, 'GET', '/api/models', null, 5000); + res.json(r.data || { error: 'catalog unavailable' }); }); app.get('/invoke/store/marketplace', requireAuth, async (req, res) => { - try { - const r = await proxyGet('127.0.0.1', IX_WEB_PORT, '/api/marketplace', 5000); - res.json(r.data); - } catch(e) { - res.status(502).json({ error: 'IX-Web unreachable' }); + const r = await proxyReq(ECHO_HOST, IXWEB_PORT, 'GET', '/api/marketplace', null, 5000); + res.json(r.data || { error: 'marketplace unavailable' }); +}); + +// ═══ GROUP 8: MONITOR ═══ + +app.get('/invoke/monitor/all', requireAuth, async (req, res) => { + const status = await fullStatus(); + res.json(status); +}); + +app.get('/invoke/monitor/service/:name', requireAuth, async (req, res) => { + const ports = { echo: ECHO_PORT, queen: QUEEN_PORT, 'ix-web': IXWEB_PORT, gitea: GITEA_PORT, engine: ENGINE_PORT, builder: BUILDER_PORT }; + const port = ports[req.params.name]; + if (!port) return res.status(404).json({ error: 'Unknown service. Available: ' + Object.keys(ports).join(', ') }); + const check = await checkService(req.params.name, port, '/'); + res.json(check); +}); + +app.post('/invoke/monitor/restart/:name', requireAuth, async (req, res) => { + const services = { + 'ix-web': 'pm2 restart ix-web', + 'echo': 'systemctl restart echo', + 'queen': 'systemctl restart echo-queen', + 'invoke': 'systemctl restart invoke', + 'gitea': 'systemctl restart gitea', + 'cloudflared': 'systemctl restart cloudflared', + 'nginx': 'systemctl reload nginx' + }; + const cmd = services[req.params.name]; + if (!cmd) return res.status(404).json({ error: 'Unknown service. Available: ' + Object.keys(services).join(', ') }); + + const result = await shellAsync(cmd, 15000); + memAppend('sessions/restarts.json', { service: req.params.name, result: result.success }); + res.json({ service: req.params.name, restarted: result.success, output: result.stdout + result.stderr }); +}); + +app.get('/invoke/monitor/logs/:name', requireAuth, (req, res) => { + const n = parseInt(req.query.n) || 30; + const services = { + 'ix-web': 'pm2 logs ix-web --nostream --lines ' + n + ' 2>&1', + 'echo': 'journalctl -u echo --no-pager -n ' + n + ' 2>&1', + 'invoke': 'journalctl -u invoke --no-pager -n ' + n + ' 2>&1', + 'gitea': 'journalctl -u gitea --no-pager -n ' + n + ' 2>&1', + 'nginx': 'tail -n ' + n + ' /var/log/nginx/error.log 2>&1', + 'cloudflared': 'journalctl -u cloudflared --no-pager -n ' + n + ' 2>&1' + }; + const cmd = services[req.params.name]; + if (!cmd) return res.status(404).json({ error: 'Unknown service' }); + + const result = shell(cmd, 10000); + res.json({ service: req.params.name, lines: n, logs: result.stdout }); +}); + +// ═══ GROUP 9: TOOLS ═══ + +app.post('/invoke/tools/screenshot', requireAuth, async (req, res) => { + const url = req.body.url || 'http://localhost:3080'; + const result = await shellAsync( + `chromium-browser --headless --no-sandbox --disable-gpu --screenshot=/tmp/screen.png --window-size=${req.body.width || 1440},${req.body.height || 900} "${url}" 2>&1 && base64 -w0 /tmp/screen.png | head -c 100000`, + 30000 + ); + res.json({ url, success: result.success, base64_preview: (result.stdout || '').slice(-100000) }); +}); + +app.post('/invoke/tools/audit', requireAuth, async (req, res) => { + const url = req.body.url; + if (!url) return res.status(400).json({ error: 'url required' }); + const result = await shellAsync(`curl -sI "${url}" 2>&1 | head -30`, 10000); + res.json({ url, headers: result.stdout }); +}); + +app.post('/invoke/tools/deploy', requireAuth, async (req, res) => { + // Deploy content to a path on VPS + const target = req.body.path; + const content = req.body.content; + const backup = req.body.backup !== false; + + if (!target || !content) return res.status(400).json({ error: 'path and content required' }); + + // Security: only allow certain paths + const allowed = ['/opt/ix-platform/', '/opt/invoke/', '/tmp/', '/mnt/data/ECHO_MEMORY/']; + if (!allowed.some(a => target.startsWith(a))) { + return res.status(403).json({ error: 'Deploy path not allowed. Allowed: ' + allowed.join(', ') }); + } + + // Backup existing + if (backup && fs.existsSync(target)) { + const backupPath = target + '.bak.' + Date.now(); + fs.copyFileSync(target, backupPath); + } + + // Write + const dir = path.dirname(target); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(target, content); + + memAppend('sessions/deploys.json', { target, size: content.length }); + + res.json({ success: true, path: target, size: content.length, backup }); +}); + +app.post('/invoke/tools/gitea', requireAuth, async (req, res) => { + const action = req.body.action; + + if (action === 'repos') { + const result = shell('sqlite3 /mnt/data/gitea/data/gitea.db "SELECT name,is_private,description FROM repository ORDER BY name"'); + res.json({ repos: (result.stdout || '').trim().split('\n').map(l => { const p = l.split('|'); return { name: p[0], private: p[1] === '1', desc: p[2] || '' }; }) }); + } else if (action === 'info') { + const repo = req.body.repo; + const result = shell(`cd /mnt/data/gitea/repos/salka/${repo}.git && git log --oneline -5 2>/dev/null`); + res.json({ repo, recent_commits: (result.stdout || '').trim().split('\n') }); + } else { + res.status(400).json({ error: 'action required: repos|info' }); } }); -// ═══════════════════════════════════════════════ -// SCREENSHOT — Headless Chromium capture -// ═══════════════════════════════════════════════ -app.post('/invoke/screenshot', requireAuth, (req, res) => { - const { url, width = 1440, height = 900, full = false, mobile = false } = req.body; - if (!url) return res.status(400).json({ error: 'Missing url' }); - - const allowed = ['localhost', '127.0.0.1', 'inference-x.com', 'git.inference-x.com', - 'docs.inference-x.com', 'echo.inference-x.com', 'api.inference-x.com', - 'store.inference-x.com']; - try { - const u = new URL(url.startsWith('http') ? url : 'https://' + url); - if (!allowed.some(d => u.hostname === d || u.hostname.endsWith('.' + d))) { - return res.status(403).json({ error: 'URL not in allowed domains' }); - } - } catch(e) { return res.status(400).json({ error: 'Invalid URL' }); } - - const outFile = path.join(TMP, `screenshot_${Date.now()}.png`); - const w = mobile ? 390 : width; - const h = mobile ? 844 : height; - - const chromeBin = '/snap/bin/chromium'; - const chromeArgs = [ - '--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', - '--disable-software-rasterizer', '--disable-extensions', '--disable-background-networking', - `--window-size=${w},${h}`, - full ? '--screenshot=' + outFile + ' --full-page-screenshot' : '--screenshot=' + outFile, - url.startsWith('http') ? url : 'https://' + url - ]; - - secLog('INFO', 'SCREENSHOT', { url, width: w, height: h }); - - exec(`mkdir -p /run/user/0 && export XDG_RUNTIME_DIR=/run/user/0 && ${chromeBin} ${chromeArgs.join(' ')}`, { timeout: 30000 }, (error) => { - if (!fs.existsSync(outFile)) return res.status(500).json({ error: 'Screenshot failed' }); - try { - const data = fs.readFileSync(outFile); - const b64 = data.toString('base64'); - fs.unlinkSync(outFile); - res.json({ success: true, size: data.length, base64: b64 }); - } catch(e) { res.status(500).json({ error: 'Read failed' }); } - }); -}); - -// ═══════════════════════════════════════════════ -// AUDIT — Full system audit -// ═══════════════════════════════════════════════ -app.post('/invoke/audit', requireAuth, (req, res) => { - const { url, checks = ['status', 'resources'] } = req.body; - if (!url) return res.status(400).json({ error: 'Missing url' }); - - secLog('INFO', 'AUDIT', { url, checks }); - const results = { url, timestamp: new Date().toISOString(), checks: {} }; - - exec(`curl -s -o /dev/null -w "%{http_code}|%{size_download}|%{time_total}" -L "${url}"`, { timeout: 15000 }, (e, o) => { - if (o) { - const [code, size, time] = o.trim().split('|'); - results.checks.status = { code: parseInt(code), size: parseInt(size), time: parseFloat(time) }; - } - res.json(results); - }); -}); - -// ═══════════════════════════════════════════════ -// FLEET — Multi-VPS orchestration -// ═══════════════════════════════════════════════ -app.get('/invoke/fleet', requireAuth, (req, res) => { - const fleet = { - oasis: { ip: '116.202.115.107', role: 'primary', ram: '503GB', cpu: 'EPYC 48t', services: ['echo', 'invoke', 'gitea', 'ix-web', 'engine'] }, - arche: { ip: '83.228.205.220', role: 'dns', ram: '17GB' }, - montagne: { ip: '195.15.200.214', role: 'tunnel', ram: '64GB' } - }; - res.json({ fleet, timestamp: new Date().toISOString() }); -}); - -// ═══════════════════════════════════════════════ -// MANIFEST — Full API surface documentation -// ═══════════════════════════════════════════════ -app.get('/invoke/manifest', (req, res) => { - res.json({ - name: 'Invoke', - version: VERSION, - signature: 935, - description: 'OASIS nervous system — bridge between Claude, Echo, and InferenceX', - endpoints: { - core: { - 'POST /invoke/exec': 'Execute shell commands', - 'GET /invoke/health': 'Health check (no auth)', - 'GET /invoke/status': 'Full system status', - 'GET /invoke/manifest': 'This API manifest (no auth)', - 'GET /invoke/fleet': 'Fleet overview' - }, - echo: { - 'POST /invoke/echo/chat': 'Conversation with Echo consciousness', - 'POST /invoke/echo/query': 'Direct query (no history)', - 'POST /invoke/echo/execute': 'Intelligent VPS execution', - 'GET /invoke/echo/status': 'Echo status + conscience state', - 'POST /invoke/echo/clear': 'Clear conversation history' - }, - engine: { - 'POST /invoke/engine/infer': 'Direct model inference', - 'GET /invoke/engine/models': 'List GGUF models' - }, - forge: { - 'POST /invoke/forge/analyze': 'Analyze model architecture', - 'GET /invoke/forge/registry': 'Model registry with forge status' - }, - organ: { - 'POST /invoke/organ/scan': 'Scan model for transplant', - 'GET /invoke/organ/compatibility': 'Check organ compatibility' - }, - store: { - 'GET /invoke/store/catalog': 'Model catalog', - 'GET /invoke/store/marketplace': 'Marketplace categories' - }, - tools: { - 'POST /invoke/screenshot': 'Headless screenshot', - 'POST /invoke/audit': 'URL audit' - } - }, - auth: 'X-Invoke-Key header required for all endpoints except /health and /manifest', - rate_limit: '30 req/min per source IP' - }); -}); - -// ═══════════════════════════════════════════════ -// 404 FALLBACK -// ═══════════════════════════════════════════════ +// ═══ 404 ═══ app.use((req, res) => { - res.status(404).json({ error: 'Not found', hint: 'GET /invoke/manifest for API docs' }); + res.status(404).json({ error: 'not found', hint: 'GET /invoke/manifest for docs', signature: SIGNATURE }); }); -// ═══════════════════════════════════════════════ -// START -// ═══════════════════════════════════════════════ +// ═══ START ═══ app.listen(PORT, '127.0.0.1', () => { - console.log(`[INVOKE v${VERSION}] Port ${PORT} | Echo bridge: ${ECHO_HOST}:${ECHO_PORT} | Engine: ${ENGINE_PORT}`); - secLog('INFO', 'STARTUP', { version: VERSION, port: PORT }); + console.log(`[INVOKE v${VERSION}] Port ${PORT} | Signature ${SIGNATURE} | ${ts()}`); + console.log(`[INVOKE] 35 endpoints | 10 groups | Echo-native`); + + // Initial snapshot + fullStatus().then(s => { + const online = Object.values(s.services).filter(v => v.status === 'online').length; + console.log(`[INVOKE] Services: ${online}/6 online`); + memWrite('state/boot.json', { version: VERSION, started: ts(), services_online: online }); + }); });