45af56d9cf
Also added: - Memory API endpoints - Briefs API endpoints - AnveVoice stats API - Claude spawn API - TTS proxy - Cleopatra voice widget - api-auth middleware
142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
import { NextResponse } from 'next/server';
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
|
||
const API_SECRET = process.env.API_SECRET || 'horus-mc-secret-2026';
|
||
|
||
function checkAuth(request: Request): boolean {
|
||
const authHeader = request.headers.get('authorization');
|
||
return authHeader === `Bearer ${API_SECRET}`;
|
||
}
|
||
|
||
const MAIN_AGENTS = ["horus", "cleopatra", "amun"];
|
||
|
||
const AGENTS = [
|
||
{ id: "horus", name: "Horus", icon: "👁️", color: "#3b82f6" },
|
||
{ id: "cleopatra", name: "Cleopatra", icon: "👸", color: "#a855f7" },
|
||
{ id: "amun", name: "Amun", icon: "👑", color: "#f59e0b" },
|
||
{ id: "anubis", name: "Anubis", icon: "🐕", color: "#22c55e" },
|
||
{ id: "thoth", name: "Thoth", icon: "📚", color: "#06b6d4" },
|
||
{ id: "ptah", name: "Ptah", icon: "🎨", color: "#f97316" },
|
||
{ id: "seshat", name: "Seshat", icon: "📝", color: "#ec4899" },
|
||
{ id: "hathor", name: "Hathor", icon: "💕", color: "#ef4444" },
|
||
{ id: "sekhmet", name: "Sekhmet", icon: "⚔️", color: "#94a3b8" },
|
||
{ id: "maat", name: "Maat", icon: "⚖️", color: "#64748b" },
|
||
];
|
||
|
||
interface AgentInfo {
|
||
id: string;
|
||
name: string;
|
||
icon: string;
|
||
color: string;
|
||
}
|
||
|
||
function getAgentInfo(agentName: string): AgentInfo {
|
||
const lower = agentName.toLowerCase();
|
||
const found = AGENTS.find(a => lower.includes(a.id) || lower.includes(a.name.toLowerCase()));
|
||
return found || { id: "unknown", name: agentName, icon: "🤖", color: "#64748b" };
|
||
}
|
||
|
||
export async function GET(request: Request) {
|
||
// Check auth
|
||
if (!checkAuth(request)) {
|
||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||
}
|
||
|
||
try {
|
||
const memoryDir = '/root/.openclaw/workspace/memory';
|
||
const briefsMorningDir = '/root/.openclaw/workspace/briefs/morning';
|
||
const briefsEodDir = '/root/.openclaw/workspace/briefs/eod';
|
||
|
||
const files = fs.readdirSync(memoryDir)
|
||
.filter(f => f.endsWith('.md'))
|
||
.filter(f => /^\d{4}-\d{2}-\d{2}$/.test(f.replace('.md', '')));
|
||
|
||
const days: any[] = [];
|
||
const allTags: string[] = [];
|
||
|
||
for (const file of files) {
|
||
const date = file.replace('.md', '');
|
||
const filePath = path.join(memoryDir, file);
|
||
const content = fs.readFileSync(filePath, 'utf8');
|
||
|
||
const entries: any[] = [];
|
||
const lines = content.split('\n');
|
||
let currentEntry: any = null;
|
||
|
||
for (const line of lines) {
|
||
const agentMatch = line.match(/^##\s*(\w+)\s*[-–]\s*(\d{2}:\d{2})/);
|
||
if (agentMatch) {
|
||
if (currentEntry?.content?.trim()) {
|
||
entries.push(currentEntry);
|
||
}
|
||
const agentInfo = getAgentInfo(agentMatch[1]);
|
||
currentEntry = {
|
||
agent: agentInfo.name,
|
||
agentIcon: agentInfo.icon,
|
||
agentColor: agentInfo.color,
|
||
time: agentMatch[2],
|
||
content: '',
|
||
tags: [],
|
||
isMainAgent: MAIN_AGENTS.includes(agentInfo.id),
|
||
};
|
||
} else if (currentEntry) {
|
||
const trimmed = line.trim();
|
||
if (!trimmed) continue;
|
||
|
||
const standaloneTagsMatch = trimmed.match(/^\[Tags:\s*(.+?)\]$/);
|
||
if (standaloneTagsMatch) {
|
||
const tags = standaloneTagsMatch[1]
|
||
.split(',')
|
||
.map((t: string) => t.trim().replace(/^#/, '').trim())
|
||
.filter(Boolean);
|
||
currentEntry.tags = [...new Set([...currentEntry.tags, ...tags])];
|
||
tags.forEach((t: string) => {
|
||
if (!allTags.includes(t)) allTags.push(t);
|
||
});
|
||
} else {
|
||
const inlineTagsMatch = trimmed.match(/\[Tags:\s*(.+?)\]/);
|
||
if (inlineTagsMatch) {
|
||
const tags = inlineTagsMatch[1]
|
||
.split(',')
|
||
.map((t: string) => t.trim().replace(/^#/, '').trim())
|
||
.filter(Boolean);
|
||
currentEntry.tags = [...new Set([...currentEntry.tags, ...tags])];
|
||
tags.forEach((t: string) => {
|
||
if (!allTags.includes(t)) allTags.push(t);
|
||
});
|
||
}
|
||
const cleanLine = trimmed.replace(/\[Tags:.*?\]/g, '').trim();
|
||
if (cleanLine) {
|
||
currentEntry.content += (currentEntry.content ? '\n' : '') + cleanLine;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (currentEntry?.content?.trim()) {
|
||
entries.push(currentEntry);
|
||
}
|
||
|
||
const hasMorning = fs.existsSync(path.join(briefsMorningDir, `${date}.md`));
|
||
const hasEod = fs.existsSync(path.join(briefsEodDir, `${date}.md`));
|
||
|
||
days.push({
|
||
date,
|
||
entries,
|
||
hasBriefs: { morning: hasMorning, eod: hasEod },
|
||
});
|
||
}
|
||
|
||
days.sort((a, b) => b.date.localeCompare(a.date));
|
||
|
||
return NextResponse.json({
|
||
days,
|
||
allTags,
|
||
totalDays: days.length,
|
||
});
|
||
} catch (error) {
|
||
console.error('Memory list error:', error);
|
||
return NextResponse.json({ days: [], allTags: [], error: 'Failed to load memory' }, { status: 500 });
|
||
}
|
||
}
|