Initial: JSONL→SQLite Exporter + Cost Analytics Web UI
This commit is contained in:
commit
9803f8c727
14 files changed
+1449
No files matched your search
+81
@@ -0,0 +1,81 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const SESSIONS_DIR = '/home/node/.openclaw/agents/main/sessions';
|
||||
const DB_FILE = path.join(__dirname, 'sessions.db');
|
||||
|
||||
// DB öffnen + Schema anlegen
|
||||
const db = new Database(DB_FILE);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS entries (
|
||||
id TEXT PRIMARY KEY,
|
||||
session TEXT NOT NULL,
|
||||
type TEXT,
|
||||
timestamp TEXT,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_session ON entries (session);
|
||||
CREATE INDEX IF NOT EXISTS idx_type ON entries (type);
|
||||
CREATE INDEX IF NOT EXISTS idx_timestamp ON entries (timestamp);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS state (
|
||||
file TEXT PRIMARY KEY,
|
||||
lines INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
`);
|
||||
|
||||
const getState = db.prepare('SELECT lines FROM state WHERE file = ?');
|
||||
const setState = db.prepare('INSERT OR REPLACE INTO state (file, lines) VALUES (?, ?)');
|
||||
const insertEntry = db.prepare(`
|
||||
INSERT OR IGNORE INTO entries (id, session, type, timestamp, data)
|
||||
VALUES (@id, @session, @type, @timestamp, @data)
|
||||
`);
|
||||
|
||||
// Find all JSONL files
|
||||
const jsonlFiles = fs.readdirSync(SESSIONS_DIR)
|
||||
.filter(f => f.endsWith('.jsonl') && !f.includes('.deleted'));
|
||||
|
||||
let exported = 0;
|
||||
|
||||
const runImport = db.transaction(() => {
|
||||
for (const file of jsonlFiles) {
|
||||
const filePath = path.join(SESSIONS_DIR, file);
|
||||
const sessionName = file.replace('.jsonl', '');
|
||||
const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(l => l.trim());
|
||||
|
||||
const lastProcessed = getState.get(file)?.lines ?? 0;
|
||||
|
||||
for (let i = lastProcessed; i < lines.length; i++) {
|
||||
try {
|
||||
const entry = JSON.parse(lines[i]);
|
||||
if (!entry.timestamp) continue;
|
||||
|
||||
const id = entry.id || `${sessionName}-${i}`;
|
||||
|
||||
insertEntry.run({
|
||||
id,
|
||||
session: sessionName,
|
||||
type: entry.type ?? 'unknown',
|
||||
timestamp: entry.timestamp,
|
||||
data: JSON.stringify(entry),
|
||||
});
|
||||
|
||||
exported++;
|
||||
} catch {
|
||||
// skip malformed lines
|
||||
}
|
||||
}
|
||||
|
||||
setState.run(file, lines.length);
|
||||
}
|
||||
});
|
||||
|
||||
runImport();
|
||||
db.close();
|
||||
|
||||
console.log(`Exportiert: ${exported} neue Einträge → ${DB_FILE}`);
|
||||
Reference in new issue
Block a user