feat(mission-control): restore MC tabs - temple, office, memory, claude, pdf-viewer, resume, resume-upload, temple-3d, demos
Also added: - Memory API endpoints - Briefs API endpoints - AnveVoice stats API - Claude spawn API - TTS proxy - Cleopatra voice widget - api-auth middleware
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANVEVOICE_API_KEY = "anvk_6a217415c671b8e613df1f0b37f72c492a91b625";
|
||||
const CLEOPATRA_BOT_ID = "022ae3d3-ed11-45a9-b663-f4a6dfa34f77";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const res = await fetch('https://aaxlcyouksuljvmypyhy.supabase.co/functions/v1/anve-mcp', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': ANVEVOICE_API_KEY
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "get_analytics_overview",
|
||||
arguments: { bot_id: CLEOPATRA_BOT_ID }
|
||||
},
|
||||
id: 4
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
return NextResponse.json(data);
|
||||
} catch (e) {
|
||||
return NextResponse.json({ error: "Failed to fetch" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const API_SECRET = process.env.API_SECRET || 'horus-mc-secret-2026';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ date: string }> }
|
||||
) {
|
||||
// Check auth
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${API_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { date } = await params;
|
||||
const filePath = `/root/.openclaw/workspace/briefs/eod/${date}.md`;
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json({ content: 'No EOD brief for this date' }, { status: 404 });
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return NextResponse.json({ date, content });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to load' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const API_SECRET = process.env.API_SECRET || 'horus-mc-secret-2026';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ date: string }> }
|
||||
) {
|
||||
// Check auth
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${API_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { date } = await params;
|
||||
const filePath = `/root/.openclaw/workspace/briefs/morning/${date}.md`;
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json({ content: 'No morning brief for this date' }, { status: 404 });
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return NextResponse.json({ date, content });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to load' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
+34
-17
@@ -1,35 +1,52 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const MINIMAX_API_KEY = process.env.MINIMAX_API_KEY || "sk-cp-aRrwyWpeY7iheh18JiqLNHkaz0Kude0MRYFt2w5fDzk-5026VI-HtO06_us_DQjJ8yHt4Qevgz-UE3F566cnjYDZPMSUGLLFgjUpwOiV0Ir0hTbeUclMeIQ";
|
||||
const MINIMAX_URL = "https://api.minimax.io/v1/text/chatcompletion_v2";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { text } = body;
|
||||
|
||||
const prompt = `You are Cleopatra, a professional Spanish-speaking sales agent. Keep responses SHORT (1-2 sentences), friendly, in Spanish.
|
||||
// Cleopatra personality - short, friendly, Spanish
|
||||
const systemPrompt = `Eres Cleopatra, una agente de ventas profesional hispanohablante.
|
||||
Respuestas CORTAS (1-2 oraciones), amigables, en español.
|
||||
Siempre suena interesada y servicial.
|
||||
Si no entiendes algo, pide que repitan por favor.`;
|
||||
|
||||
User: ${text}
|
||||
const messages = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: text }
|
||||
];
|
||||
|
||||
Response in Spanish:`;
|
||||
|
||||
const res = await fetch("http://127.0.0.1:11434/api/generate", {
|
||||
const res = await fetch(MINIMAX_URL, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
signal: AbortSignal.timeout(60000),
|
||||
headers: {
|
||||
"Authorization": `Bearer ${MINIMAX_API_KEY}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "phi3:mini",
|
||||
prompt: prompt,
|
||||
stream: false
|
||||
model: "MiniMax-M2.7",
|
||||
messages,
|
||||
temperature: 0.8,
|
||||
max_tokens: 150
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("MiniMax API error");
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const reply = (data.response || "¡Hola! Estoy aquí.").substring(0, 200);
|
||||
|
||||
return NextResponse.json({ response: reply });
|
||||
const reply = data.choices?.[0]?.message?.content?.trim() ||
|
||||
"¡Estoy aquí para ayudarte! ¿Qué necesitas?";
|
||||
|
||||
return NextResponse.json({ response: reply.substring(0, 300) });
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error("Chat error:", e);
|
||||
return NextResponse.json({
|
||||
response: "Tengo problemas para conectar. Intenta de nuevo."
|
||||
}, { status: 500 });
|
||||
response: "Tengo problemas para conectar. ¿Puedes intentar de nuevo?"
|
||||
}, { status: 200 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { tabId, tabName, apiKey } = await request.json();
|
||||
|
||||
if (!apiKey) {
|
||||
return NextResponse.json({ error: 'API key required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// For now, return instructions for manual spawn
|
||||
// Real implementation would use sessions_spawn via OpenClaw SDK
|
||||
const sessionKey = `claude-${tabId}-${Date.now()}`;
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
sessionId: sessionKey,
|
||||
sessionKey,
|
||||
message: `To start Claude Code for ${tabName}, use the spawn command in Horus`,
|
||||
instruction: `Ask Horus to spawn a Claude session for ${tabName}`
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to spawn session' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3456/');
|
||||
const html = await response.text();
|
||||
return new NextResponse(html, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/html',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Claw3D not running' }, { status: 503 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ date: string }> }
|
||||
) {
|
||||
try {
|
||||
const { date } = await params;
|
||||
const memoryDir = '/root/.openclaw/workspace/memory';
|
||||
const filePath = path.join(memoryDir, `${date}.md`);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json({ content: '', error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return NextResponse.json({ content, date });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ content: '', error: 'Failed to load' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const AGENT_SECRET = process.env.AGENT_SECRET || 'agent-mc-secret-2026';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
// Check for agent authorization
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${AGENT_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { agent, time, content, tags } = body;
|
||||
|
||||
if (!agent || !content) {
|
||||
return NextResponse.json({ error: 'Missing agent or content' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get today's date
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const memoryDir = '/root/.openclaw/workspace/memory';
|
||||
const filePath = path.join(memoryDir, `${today}.md`);
|
||||
|
||||
// Build entry
|
||||
const tagsStr = tags && tags.length > 0
|
||||
? `\n[Tags: ${tags.map((t: string) => '#' + t).join(', ')}]`
|
||||
: '';
|
||||
const entry = `\n\n## ${agent} - ${time || new Date().toTimeString().slice(0,5)}\n${tagsStr}\n${content}`;
|
||||
|
||||
// Append to file
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.appendFileSync(filePath, entry, 'utf8');
|
||||
} else {
|
||||
// Create new file with header
|
||||
const header = `# Daily Memory - ${today}\n`;
|
||||
fs.writeFileSync(filePath, header + entry, 'utf8');
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, date: today, entry: entry.trim() });
|
||||
} catch (error) {
|
||||
console.error('Memory append error:', error);
|
||||
return NextResponse.json({ error: 'Failed to append memory' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const API_SECRET = process.env.API_SECRET || 'horus-mc-secret-2026';
|
||||
const STORAGE_FILE = path.join(process.cwd(), "data", "morning_briefs.json");
|
||||
|
||||
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN || "MTQ3MTk4OTUzNjE1MzQwMzU5Nw.Ghtj4n.g-tl-Ijhfn9cg6zUCUIVd94EdwL32KmlVgRoSc";
|
||||
@@ -47,12 +48,24 @@ ${(data.priorities || []).map((p: string, i: number) => `${i + 1}. ${p}`).join('
|
||||
${(data.leads || []).map((l: string) => `- ${l}`).join('\n')}`;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
export async function GET(request: NextRequest) {
|
||||
// Check auth
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${API_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const briefs = getBriefs();
|
||||
return NextResponse.json(briefs);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// Check auth
|
||||
const authHeader = request.headers.get('authorization');
|
||||
if (authHeader !== `Bearer ${API_SECRET}`) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const TTS_API = "http://185.45.195.201:5001/api/tts/base64";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { text, lang = "es" } = await request.json();
|
||||
|
||||
if (!text) {
|
||||
return NextResponse.json({ error: "No text provided" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Call Cleopatra's TTS API (HTTP)
|
||||
const response = await fetch(TTS_API, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ text, lang })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("TTS API error");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error("TTS proxy error:", error);
|
||||
return NextResponse.json({ error: "TTS failed" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user