server
import crypto from "node:crypto"; import fs from "node:fs"; import http from "node:http"; import os from "node:os"; import path from "node:pat...
import crypto from "node:crypto"; import fs from "node:fs"; import http from "node:http"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import. meta.
import crypto from "node:crypto"; import fs from "node:fs"; import http from "node:http"; import os from "node:os"; import path from "node:path"; import { execFileSync } from "node:child_process"; import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename);
const PORT = Number(process.env.PORT || 4177); const SCREEN_ROOT = process.env.CHRONICLE_SCREEN_ROOT || path.join(os.tmpdir(), "chronicle", "screen_recording"); const MEMORY_ROOT = process.env.CHRONICLE_MEMORY_ROOT || path.join(os.homedir(), ".codex", "memories", "extensions", "chronicle", "resources"); const PID_PATH = path.join(os.tmpdir(), "codex_chronicle", "chronicle-started.pid"); const CACHE_ROOT = path.join(__dirname, ".cache"); const THUMB_ROOT = path.join(CACHE_ROOT, "thumbnails"); const PUBLIC_ROOT = path.join(__dirname, "public");
const PROJECT_RULES = [ ["hyphenomenon", /\bhyphenomenon\b/i], ["rps-etsy", /\b(rps[-\s]?etsy|rock paper scissors|rps catalog|rps creative)\b/i], ["family-shapes", /\b(family[-\s]?shapes|donor conception|donor[-\s]?gamete)\b/i], ["codex-skills", /\b(codex[-\s]?skills|skills marketplace|skill library|plugin package)\b/i], ["tmora", /\b(tmora|print center|photoshop|proof deck|postcard)\b/i], ["shopify", /\b(shopify|hydrogen|digital downloads|metafield)\b/i], ["gmail", /\b(gmail|support email|inbox|shipment email)\b/i], ["etsy", /\b(etsy|shop manager|listing media|seller)\b/i], ["maggie-todo", /\b(maggie todo|intake queue|iq-\d+)\b/i], ["openai", /\b(openai|agent builder|chat prompts|responses api|gpt[-_\s]?\d|sora)\b/i], ["google-drive", /\b(google drive|google sheets|google docs|drive migration)\b/i], ["vercel", /\bvercel\b/i], ["netlify", /\bnetlify\b/i] ];
let indexCache = null; let indexCacheAt = 0;
function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); }
function hashId(value) { return crypto.createHash("sha1").update(value).digest("hex").slice(0, 16); }
function walkFiles(root, predicate, out = []) { if (!fs.existsSync(root)) return out; for (const entry of fs.readdirSync(root, { withFileTypes: true })) { const fullPath = path.join(root, entry.name); if (entry.isDirectory()) { walkFiles(fullPath, predicate, out); } else if (!predicate || predicate(fullPath)) { out.push(fullPath); } } return out; }
function statMtimeMs(filePath) { try { return fs.statSync(filePath).mtimeMs; } catch { return 0; } }
function chronicleStatus() { let running = false; let latestFrameAt = null; let latestFrameAgeSeconds = null;
try { const pid = Number(fs.readFileSync(PID_PATH, "utf8").trim()); process.kill(pid, 0); running = true; } catch { running = false; }
const latestFrames = walkFiles(SCREEN_ROOT, (filePath) => filePath.endsWith("-latest.jpg")); let newestMtime = 0; for (const framePath of latestFrames) { newestMtime = Math.max(newestMtime, statMtimeMs(framePath)); } if (newestMtime) { latestFrameAt = new Date(newestMtime).toISOString(); latestFrameAgeSeconds = Math.max(0, Math.round((Date.now() - newestMtime) / 1000)); }
return { running, latestFrameAt, latestFrameAgeSeconds }; }
function parseFrameTimestamp(filePath) { const fileName = path.basename(filePath); const minuteMatch = fileName.match(/frame-\d+-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z).jpg$/); if (minuteMatch) return parseChronicleTimestamp(minuteMatch[1]);
const latestMatch = fileName.match(/^(.+)-display-\d+-latest.jpg$/); if (latestMatch) return parseChronicleTimestamp(latestMatch[1]);
return new Date(statMtimeMs(filePath)).toISOString(); }
function parseChronicleTimestamp(raw) { const normalized = raw .replace(/T(\d{2})-(\d{2})-(\d{2})/, "T$1:$2:$3") .replace(/+00-00$/, "Z"); const parsed = new Date(normalized); return Number.isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString(); }
function parseSegmentKey(filePath) { const dirName = path.basename(path.dirname(filePath)); if (dirName.includes("-display-")) return dirName; const fileName = path.basename(filePath); const latest = fileName.match(/^(.+-display-\d+)-latest.jpg$/); if (latest) return latest[1]; const sidecar = fileName.match(/^(.+-display-\d+).(ocr.jsonl|capture|capture.json)$/); if (sidecar) return sidecar[1]; return ""; }
function parseDisplay(filePath) { const match = filePath.match(/display-(\d+)/); return match ? match[1] : "unknown"; }
function extractStrings(value, out = []) { if (typeof value === "string") { if (value.trim()) out.push(value.trim()); return out; } if (Array.isArray(value)) { for (const item of value) extractStrings(item, out); return out; } if (value && typeof value === "object") { for (const item of Object.values(value)) extractStrings(item, out); } return out; }
function loadOcrBySegment() { const sidecars = walkFiles(SCREEN_ROOT, (filePath) => filePath.endsWith(".ocr.jsonl")); const bySegment = new Map();
for (const sidecarPath of sidecars) { const segmentKey = parseSegmentKey(sidecarPath); const snippets = []; try { const lines = fs.readFileSync(sidecarPath, "utf8").split(/\r?\n/).filter(Boolean); for (const line of lines.slice(-250)) { try { const strings = extractStrings(JSON.parse(line)); snippets.push(...strings); } catch { snippets.push(line); } } } catch { continue; } const text = Array.from(new Set(snippets)) .join(" ") .replace(/\s+/g, " ") .slice(0, 20000); bySegment.set(segmentKey, { sidecarPath, text }); }
return bySegment; }
function loadMemories() {
const files = walkFiles(MEMORY_ROOT, (filePath) => filePath.endsWith(".md"));
return files
.map((filePath) => {
const fileName = path.basename(filePath);
const stamp = fileName.match(/^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})/);
const timestamp = stamp ? parseChronicleTimestamp(${stamp[1]}Z) : new Date(statMtimeMs(filePath)).toISOString();
let text = "";
try {
text = fs.readFileSync(filePath, "utf8");
} catch {
text = "";
}
const cwdMatch = text.match(/\bcwd=([^\n,)]+)/i) || text.match(/\bcwd:\s*([^\n]+)/i);
const kind = fileName.includes("-6h-") ? "6h" : "10min";
return {
id: hashId(filePath),
fileName,
path: filePath,
timestamp,
timeMs: new Date(timestamp).getTime(),
kind,
cwd: cwdMatch ? cwdMatch[1].trim() : "",
text,
excerpt: text.replace(/^---[\s\S]*?---/, "").replace(/\s+/g, " ").trim().slice(0, 900)
};
})
.sort((a, b) => a.timeMs - b.timeMs);
}
function nearestMemory(frameMs, memories) { let best = null; let bestDelta = Infinity; for (const memory of memories) { const delta = Math.abs(memory.timeMs - frameMs); const windowMs = memory.kind === "6h" ? 6 * 60 * 60 * 1000 : 16 * 60 * 1000; if (delta <= windowMs && delta < bestDelta) { best = memory; bestDelta = delta; } } return best; }
function inferredProjects(text) { const matches = []; for (const [key, rule] of PROJECT_RULES) { if (rule.test(text)) matches.push(key); } return matches; }
function thumbPathFor(item) {
return path.join(THUMB_ROOT, ${item.id}.jpg);
}
function ensureThumbnail(item) { ensureDir(THUMB_ROOT); const destination = thumbPathFor(item); const sourceMtime = statMtimeMs(item.path); const thumbMtime = statMtimeMs(destination); if (thumbMtime && thumbMtime >= sourceMtime) return destination;
try { execFileSync("sips", ["-Z", "320", item.path, "--out", destination], { stdio: "ignore" }); } catch { fs.copyFileSync(item.path, destination); } return destination; }
function frameFiles() { const latest = walkFiles(SCREEN_ROOT, (filePath) => filePath.endsWith("-latest.jpg")); const historical = walkFiles(path.join(SCREEN_ROOT, "1min"), (filePath) => filePath.endsWith(".jpg")); return [...historical, ...latest]; }
function buildIndex({ withThumbs = false } = {}) { const status = chronicleStatus(); const ocrBySegment = loadOcrBySegment(); const memories = loadMemories(); const frames = frameFiles();
const items = frames
.map((filePath) => {
const timestamp = parseFrameTimestamp(filePath);
const timeMs = new Date(timestamp).getTime();
const segmentKey = parseSegmentKey(filePath);
const ocr = ocrBySegment.get(segmentKey) || null;
const memory = nearestMemory(timeMs, memories);
const id = hashId(filePath);
const fileName = path.basename(filePath);
const haystack = [
fileName,
filePath,
segmentKey,
ocr?.text || "",
memory?.fileName || "",
memory?.cwd || "",
memory?.text || ""
].join("\n");
const item = {
id,
fileName,
path: filePath,
timestamp,
timeMs,
day: timestamp.slice(0, 10),
display: parseDisplay(filePath),
segmentKey,
isLatest: fileName.endsWith("-latest.jpg"),
thumbUrl: /thumb/${id}.jpg,
frameUrl: /frame/${id}.jpg,
ocrSidecarPath: ocr?.sidecarPath || "",
ocrHint: ocr?.text ? ocr.text.slice(0, 900) : "",
memory: memory
? {
id: memory.id,
fileName: memory.fileName,
path: memory.path,
timestamp: memory.timestamp,
kind: memory.kind,
cwd: memory.cwd,
excerpt: memory.excerpt
}
: null,
projects: inferredProjects(haystack),
searchText: haystack.toLowerCase().slice(0, 50000)
};
if (withThumbs) ensureThumbnail(item);
return item;
})
.sort((a, b) => b.timeMs - a.timeMs);
const projectCounts = {}; for (const item of items) { for (const project of item.projects) { projectCounts[project] = (projectCounts[project] || 0) + 1; } }
return { generatedAt: new Date().toISOString(), screenRoot: SCREEN_ROOT, memoryRoot: MEMORY_ROOT, cacheRoot: CACHE_ROOT, status, counts: { frames: items.length, memories: memories.length, ocrSidecars: ocrBySegment.size }, projectCounts, items }; }
function getIndex(force = false) { const now = Date.now(); if (!force && indexCache && now - indexCacheAt < 15000) return indexCache; indexCache = buildIndex(); indexCacheAt = now; return indexCache; }
function itemById(id) { const index = getIndex(); return index.items.find((item) => item.id === id); }
function sendJson(res, payload, statusCode = 200) { const body = JSON.stringify(payload); res.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) }); res.end(body); }
function sendFile(res, filePath, contentType = "application/octet-stream") { if (!fs.existsSync(filePath)) { res.writeHead(404); res.end("Not found"); return; } res.writeHead(200, { "content-type": contentType, "cache-control": "no-store" }); fs.createReadStream(filePath).pipe(res); }
function contentTypeFor(filePath) { if (filePath.endsWith(".html")) return "text/html; charset=utf-8"; if (filePath.endsWith(".css")) return "text/css; charset=utf-8"; if (filePath.endsWith(".js")) return "text/javascript; charset=utf-8"; if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg"; if (filePath.endsWith(".png")) return "image/png"; if (filePath.endsWith(".svg")) return "image/svg+xml"; return "application/octet-stream"; }
function serveStatic(req, res, url) { const requested = url.pathname === "/" ? "/index.html" : url.pathname; const normalized = path.normal
...[truncated for intake]
Provenance
- Source file:
local-tools/chronicle-visualizer/server.js - Source URL: https://github.com/maggielerman/chronicle-visualizer/blob/main/local-tools/chronicle-visualizer/server.js
Metadata
- Created
- Not recorded
- Last updated
- Not recorded