/** * Search-box.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/agent/src/cli.ts * Description: CLI test harness — runs one research session and prints live events. */ import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { migrate, events } from "@search-box/db"; import { createSession, runSession } from "./run.js"; // Minimal .env loader (repo root), no dependency. function loadEnv(): void { const here = dirname(fileURLToPath(import.meta.url)); const envPath = join(here, "..", "..", "..", ".env"); try { for (const line of readFileSync(envPath, "utf8").split("\n")) { const m = line.match(/^([A-Z0-9_]+)=(.*)$/); if (m && m[1] && process.env[m[1]] === undefined) process.env[m[1]] = m[2] ?? ""; } } catch { // .env optional if vars already exported } } async function main(): Promise { loadEnv(); const question = process.argv.slice(2).join(" ").trim(); if (!question) { console.error('usage: pnpm research "your research question"'); process.exit(1); } await migrate(); const sessionId = await createSession(question); console.log(`session: ${sessionId}\n`); // Tail events live while the session runs. let lastSeq = 0; let done = false; const tail = (async () => { while (!done) { const batch = await events.listAfter(sessionId, lastSeq); for (const ev of batch) { lastSeq = ev.seq; printEvent(ev.payload); } await new Promise((r) => setTimeout(r, 400)); } const batch = await events.listAfter(sessionId, lastSeq); for (const ev of batch) printEvent(ev.payload); })(); try { await runSession(sessionId); } finally { done = true; await tail; } process.exit(0); } function printEvent(p: { type: string } & Record): void { switch (p.type) { case "plan.updated": console.log(`\n◆ PLAN: ${(p.objectives as string[]).join(" | ")}`); break; case "thought": console.log(`\n… ${p.publicReason}`); break; case "action.started": { const kind = p.kind as string; console.log(`→ ${kind}: ${p.label}`); break; } case "action.completed": console.log(` ${p.ok ? "✓" : "✗"} ${p.summary} (${p.latencyMs}ms)`); break; case "claim.added": { const claim = p.claim as { id: string; text: string }; console.log(`\n★ CLAIM ${claim.id}: ${claim.text}`); break; } case "claim.updated": { const claim = p.claim as { id: string; status: string; confidence: number }; console.log(` ↺ ${claim.id} → ${claim.status} (${Math.round(claim.confidence * 100)}%)`); break; } case "evidence.added": { const ev = p.evidence as { stance: string; quote: string }; console.log(` ❝ [${ev.stance}] ${ev.quote.slice(0, 120)}…`); break; } case "contradiction.added": { const c = p.contradiction as { description: string }; console.log(`\n⚡ CONTRADICTION: ${c.description}`); break; } case "answer.delta": process.stdout.write(p.delta as string); break; case "synthesis.started": console.log(`\n\n========== ANSWER ==========\n`); break; case "session.completed": console.log(`\n\n========== DONE ==========`); break; case "session.failed": console.log(`\nFAILED: ${p.error}`); break; default: break; } } main().catch((err) => { console.error(err); process.exit(1); });