05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
299 lines
9.8 KiB
JavaScript
299 lines
9.8 KiB
JavaScript
#!/usr/bin/env node
|
||
// ═══════════════════════════════════════════════════════════════════════════
|
||
// SIL Observer - Kontinuerlig systemobservation
|
||
// Skannar kodbas, databaser, och infrastruktur för att uppdatera grafen
|
||
// ═══════════════════════════════════════════════════════════════════════════
|
||
|
||
import { execSync } from 'child_process';
|
||
import { readFileSync, existsSync } from 'fs';
|
||
import { globSync } from 'glob';
|
||
|
||
class SILObserver {
|
||
constructor(graphPath) {
|
||
this.graph = JSON.parse(readFileSync(graphPath, 'utf8'));
|
||
this.changes = [];
|
||
}
|
||
|
||
// ── Git-observation ─────────────────────────────────────────────────────
|
||
|
||
observeGit(repoPath) {
|
||
console.log(`🔍 Skannar git-repo: ${repoPath}`);
|
||
|
||
try {
|
||
// Senaste commit
|
||
const lastCommit = execSync('git log -1 --format="%H|%an|%ae|%at|%s"', {
|
||
cwd: repoPath,
|
||
encoding: 'utf8'
|
||
}).trim();
|
||
|
||
const [hash, author, email, timestamp, message] = lastCommit.split('|');
|
||
|
||
// Ändrade filer
|
||
const changedFiles = execSync('git diff-tree --no-commit-id --name-only -r HEAD', {
|
||
cwd: repoPath,
|
||
encoding: 'utf8'
|
||
}).trim().split('\n').filter(f => f);
|
||
|
||
// Nya endpoints från ändrade filer
|
||
const newEndpoints = this.extractEndpointsFromFiles(changedFiles, repoPath);
|
||
|
||
// Nya tabeller från migrationer
|
||
const newTables = this.extractTablesFromMigrations(changedFiles, repoPath);
|
||
|
||
this.changes.push({
|
||
type: 'git_commit',
|
||
source: 'git',
|
||
confidence: 0.99,
|
||
data: {
|
||
hash,
|
||
author,
|
||
timestamp: new Date(parseInt(timestamp) * 1000).toISOString(),
|
||
message,
|
||
changedFiles,
|
||
newEndpoints,
|
||
newTables
|
||
}
|
||
});
|
||
|
||
console.log(` ✅ Hittade ${changedFiles.length} ändrade filer`);
|
||
console.log(` ✅ ${newEndpoints.length} nya endpoints`);
|
||
console.log(` ✅ ${newTables.length} nya tabeller`);
|
||
|
||
} catch (e) {
|
||
console.error(` ❌ Git-fel: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
extractEndpointsFromFiles(files, repoPath) {
|
||
const endpoints = [];
|
||
|
||
for (const file of files) {
|
||
if (!file.match(/\.(ts|js|tsx|jsx|rs)$/)) continue;
|
||
|
||
const fullPath = `${repoPath}/${file}`;
|
||
if (!existsSync(fullPath)) continue;
|
||
|
||
const content = readFileSync(fullPath, 'utf8');
|
||
|
||
// Hitta API-endpoints (Express/Fastify/Axum)
|
||
const endpointMatches = content.matchAll(/\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)/g);
|
||
for (const match of endpointMatches) {
|
||
endpoints.push({
|
||
method: match[1].toUpperCase(),
|
||
path: match[2],
|
||
file,
|
||
confidence: 0.98
|
||
});
|
||
}
|
||
|
||
// Hitta fetch/anrop till externa API:er
|
||
const apiMatches = content.matchAll(/fetch\s*\(\s*['"`]([^'"`]+)/g);
|
||
for (const match of apiMatches) {
|
||
if (match[1].includes('api.quixzoom.com') || match[1].includes('http')) {
|
||
endpoints.push({
|
||
type: 'external_call',
|
||
url: match[1],
|
||
file,
|
||
confidence: 0.95
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return endpoints;
|
||
}
|
||
|
||
extractTablesFromMigrations(files, repoPath) {
|
||
const tables = [];
|
||
|
||
for (const file of files) {
|
||
if (!file.includes('migration') && !file.includes('schema')) continue;
|
||
|
||
const fullPath = `${repoPath}/${file}`;
|
||
if (!existsSync(fullPath)) continue;
|
||
|
||
const content = readFileSync(fullPath, 'utf8');
|
||
|
||
// Hitta CREATE TABLE
|
||
const tableMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)[`"']?/gi);
|
||
for (const match of tableMatches) {
|
||
tables.push({
|
||
name: match[1],
|
||
operation: 'create',
|
||
file,
|
||
confidence: 0.97
|
||
});
|
||
}
|
||
|
||
// Hitta ALTER TABLE
|
||
const alterMatches = content.matchAll(/ALTER\s+TABLE\s+[`"']?(\w+)[`"']?/gi);
|
||
for (const match of alterMatches) {
|
||
tables.push({
|
||
name: match[1],
|
||
operation: 'alter',
|
||
file,
|
||
confidence: 0.97
|
||
});
|
||
}
|
||
}
|
||
|
||
return tables;
|
||
}
|
||
|
||
// ── AST-observation ─────────────────────────────────────────────────────
|
||
|
||
observeAST(repoPath) {
|
||
console.log(`🔍 Skannar AST: ${repoPath}`);
|
||
|
||
// Hitta alla TypeScript/JavaScript-filer
|
||
const files = globSync('**/*.{ts,tsx,js,jsx}', { cwd: repoPath });
|
||
|
||
const imports = [];
|
||
const functionCalls = [];
|
||
|
||
for (const file of files.slice(0, 100)) { // Begränsa för prestanda
|
||
const fullPath = `${repoPath}/${file}`;
|
||
if (!existsSync(fullPath)) continue;
|
||
|
||
const content = readFileSync(fullPath, 'utf8');
|
||
|
||
// Hitta imports
|
||
const importMatches = content.matchAll(/import\s+.*?\s+from\s+['"`]([^'"`]+)['"`]/g);
|
||
for (const match of importMatches) {
|
||
imports.push({
|
||
source: match[1],
|
||
file,
|
||
confidence: 0.99
|
||
});
|
||
}
|
||
|
||
// Hitta funktionsanrop till externa tjänster
|
||
const callMatches = content.matchAll(/(\w+)\s*\(\s*['"`]([^'"`]*(?:api|http|stripe|ses)[^'"`]*)['"`]/gi);
|
||
for (const match of callMatches) {
|
||
functionCalls.push({
|
||
function: match[1],
|
||
argument: match[2],
|
||
file,
|
||
confidence: 0.90
|
||
});
|
||
}
|
||
}
|
||
|
||
this.changes.push({
|
||
type: 'ast_analysis',
|
||
source: 'ast',
|
||
confidence: 0.98,
|
||
data: {
|
||
filesScanned: files.length,
|
||
imports: imports.slice(0, 50),
|
||
functionCalls: functionCalls.slice(0, 50)
|
||
}
|
||
});
|
||
|
||
console.log(` ✅ Skannade ${files.length} filer`);
|
||
console.log(` ✅ Hittade ${imports.length} imports`);
|
||
console.log(` ✅ Hittade ${functionCalls.length} funktionsanrop`);
|
||
}
|
||
|
||
// ── Miljövariabler ──────────────────────────────────────────────────────
|
||
|
||
observeEnvironment() {
|
||
console.log(`🔍 Skannar miljövariabler`);
|
||
|
||
const envVars = {};
|
||
|
||
// Hitta .env-filer
|
||
const envFiles = globSync('**/.env*', { cwd: '/home/bernt' });
|
||
|
||
for (const file of envFiles) {
|
||
try {
|
||
const content = readFileSync(`/home/bernt/${file}`, 'utf8');
|
||
const vars = content.match(/^\w+=[^\n]+/gm) || [];
|
||
envVars[file] = vars.map(v => v.split('=')[0]);
|
||
} catch (e) {
|
||
// Ignorera filer vi inte kan läsa
|
||
}
|
||
}
|
||
|
||
this.changes.push({
|
||
type: 'environment',
|
||
source: 'env_files',
|
||
confidence: 0.85,
|
||
data: {
|
||
files: envFiles,
|
||
variables: envVars
|
||
}
|
||
});
|
||
|
||
console.log(` ✅ Hittade ${envFiles.length} .env-filer`);
|
||
}
|
||
|
||
// ── Sammanställning ─────────────────────────────────────────────────────
|
||
|
||
generateReport() {
|
||
console.log(`\n📊 OBSERVER-RAPPORT\n`);
|
||
|
||
console.log(`Händelser: ${this.changes.length}`);
|
||
console.log(`Källor: ${[...new Set(this.changes.map(c => c.source))].join(', ')}`);
|
||
console.log(`Genomsnittlig confidence: ${(this.changes.reduce((a, c) => a + c.confidence, 0) / this.changes.length).toFixed(2)}`);
|
||
|
||
// Föreslagna graf-uppdateringar
|
||
console.log(`\n📝 Föreslagna uppdateringar av kunskapsgrafen:\n`);
|
||
|
||
for (const change of this.changes) {
|
||
switch (change.type) {
|
||
case 'git_commit':
|
||
if (change.data.newEndpoints.length > 0) {
|
||
console.log(` ➕ Nya endpoints:`);
|
||
change.data.newEndpoints.forEach(e => {
|
||
console.log(` ${e.method} ${e.path} (${e.confidence})`);
|
||
});
|
||
}
|
||
if (change.data.newTables.length > 0) {
|
||
console.log(` ➕ Nya/ändrade tabeller:`);
|
||
change.data.newTables.forEach(t => {
|
||
console.log(` ${t.operation.toUpperCase()} ${t.name} (${t.confidence})`);
|
||
});
|
||
}
|
||
break;
|
||
|
||
case 'ast_analysis':
|
||
console.log(` 🔍 AST-analys visar ${change.data.imports.length} imports`);
|
||
break;
|
||
|
||
case 'environment':
|
||
console.log(` 🔧 Miljövariabler från ${Object.keys(change.data.variables).length} filer`);
|
||
break;
|
||
}
|
||
}
|
||
|
||
return this.changes;
|
||
}
|
||
}
|
||
|
||
// ── Main ──────────────────────────────────────────────────────────────────
|
||
|
||
const observer = new SILObserver('/home/bernt/.openclaw/workspace/SYSTEM_GRAPH.json');
|
||
|
||
console.log('🚀 SIL Observer startar...\n');
|
||
|
||
// Observera quiXzoom frontend
|
||
if (existsSync('/home/bernt/repos/quixzoom.com')) {
|
||
observer.observeGit('/home/bernt/repos/quixzoom.com');
|
||
observer.observeAST('/home/bernt/repos/quixzoom.com');
|
||
}
|
||
|
||
// Observera ouroboros-identity
|
||
if (existsSync('/home/bernt/rust/ouroboros-identity')) {
|
||
observer.observeGit('/home/bernt/rust/ouroboros-identity');
|
||
}
|
||
|
||
// Miljövariabler
|
||
observer.observeEnvironment();
|
||
|
||
// Generera rapport
|
||
const changes = observer.generateReport();
|
||
|
||
console.log(`\n✅ Observer klar. ${changes.length} händelser identifierade.`);
|
||
console.log(` Kör graph-engine.mjs för att se påverkan.`);
|