← back to Govarbitrage

src/agents/run.ts

81 lines

// CLI runner for the agent layer.
//   npm run agents -- appraiser [N]   re-appraise up to N listings via local AI (default 25)
//   npm run agents -- skeptic [N]     adversarial audit of the top N ranked (default 30)
//   npm run agents -- scout           hot deals closing within 48h
//   npm run agents -- all             skeptic → scout → appraiser (small batch)

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import type { AgentRunResult } from "./framework";

// Minimal .env loader (same pattern as scripts/send-digest.ts — don't rely on
// Prisma's dotenv side-effect when invoked via tsx).
function loadEnv() {
  const root = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
  try {
    const raw = readFileSync(join(root, ".env"), "utf8");
    for (const line of raw.split("\n")) {
      const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/i);
      if (!m) continue;
      const key = m[1];
      let val = m[2].trim();
      if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
        val = val.slice(1, -1);
      }
      if (process.env[key] === undefined) process.env[key] = val;
    }
  } catch {
    /* no .env — rely on ambient env */
  }
}
loadEnv();

const { prisma } = await import("../lib/db");
const { runAppraiser } = await import("./appraiser");
const { runSkeptic } = await import("./skeptic");
const { runScout } = await import("./scout");

function report(r: AgentRunResult) {
  console.log(
    `[${r.agent}] ${r.status} — run ${r.runId} · ${r.itemsProcessed} items · ${r.findings} findings\n  ${r.summary ?? ""}`,
  );
}

async function main() {
  const [agent, nArg] = process.argv.slice(2);
  const n = nArg != null ? Number(nArg) : undefined;
  if (nArg != null && (!Number.isFinite(n) || n! <= 0)) {
    throw new Error(`Invalid count "${nArg}" — expected a positive number.`);
  }

  switch ((agent || "").toLowerCase()) {
    case "appraiser":
      report(await runAppraiser(n ?? 25));
      break;
    case "skeptic":
      report(await runSkeptic(n ?? 30));
      break;
    case "scout":
      report(await runScout());
      break;
    case "all":
      report(await runSkeptic(n ?? 30));
      report(await runScout());
      report(await runAppraiser(n ?? 25));
      break;
    default:
      console.log("Usage: npm run agents -- <appraiser [N] | skeptic [N] | scout | all>");
      process.exitCode = 2;
  }
}

main()
  .catch((e) => {
    console.error("[agents] ERROR:", e instanceof Error ? e.message : e);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });