import { type Action, circuit, connect } from "./circuit";
const app = process.argv[2] ?? "northwind";
const origins = process.argv[3] === "staging"
? { api: "https://api.staging.circuit.org", web: "https://app.staging.circuit.org" }
: { api: "https://api.circuit.org", web: "https://app.circuit.org" };
let permit: string | undefined;
const conversationId = crypto.randomUUID();
const json = (body: unknown, status = 200) => Response.json(body, { status });
const page = `<!doctype html><meta charset="utf-8"><title>Partner demo</title>
<style>body{font:15px system-ui;max-width:640px;margin:40px auto}button{margin:2px}li{margin:6px 0}</style>
<button id="connect">Connect Circuit</button><pre id="account"></pre>
<form id="ask"><input id="text" size="60" placeholder="Long $10 of BTC, start an agent on paper…"><button>Send</button></form>
<ul id="turns"></ul>
<script>
const post = (path, body) => fetch(path, { method: "POST", body: JSON.stringify(body ?? {}) }).then((r) => r.json());
const button = (label, run) => Object.assign(document.createElement("button"), { textContent: label, onclick: run });
async function refresh() {
const account = await fetch("/account").then((r) => r.json());
document.getElementById("account").textContent = account.error ?? account.wallets.map((w) => w.name + " $" + (w.valueUsd ?? "?")).join("\\n") +
"\\n" + account.agents.map((a) => a.agent + ": " + a.state).join("\\n");
for (const agent of account.agents ?? []) if (agent.state === "running") document.getElementById("account").append(button("Stop " + agent.agent, () => post("/stop", { id: agent.id }).then(refresh)));
}
function show(turn) {
const item = document.createElement("li");
item.textContent = (turn.text ?? "") + " → " + (turn.message ?? turn.state);
for (const action of turn.actions) {
const row = document.createElement("div");
row.textContent = action.title + " (" + action.state + ") ";
if (action.state === "review") row.append(button("Confirm", () => post("/decide", { id: action.id, decision: "confirm" }).then(refresh)), button("Dismiss", () => post("/decide", { id: action.id, decision: "dismiss" })));
item.append(row);
}
document.getElementById("turns").append(item);
}
document.getElementById("connect").onclick = async () => { window.open((await post("/connect")).url); };
document.getElementById("ask").onsubmit = async (event) => { event.preventDefault(); show(await post("/ask", { text: document.getElementById("text").value })); refresh(); };
refresh();
</script>`;
Bun.serve({
port: 4455,
async fetch(request) {
const path = new URL(request.url).pathname;
if (path === "/") return new Response(page, { headers: { "content-type": "text/html" } });
if (path === "/connect") {
const started = await connect(app, origins);
started.permit.then((approved) => (permit = approved));
return json({ url: started.url });
}
if (!permit) return json({ error: "Connect first" }, 401);
const user = circuit(permit, origins.api);
const body = request.method === "POST" ? await request.json() : {};
try {
if (path === "/account") return json(await user.account());
if (path === "/ask") {
const sent = await user.send(conversationId, body.text);
return json(await user.settled(conversationId, sent.id));
}
if (path === "/decide") return json((await user.decide(body.id, body.decision)) satisfies Action);
if (path === "/stop") return json(await user.control(body.id, "stop"));
} catch (error) {
return json({ error: error instanceof Error ? error.message : String(error) }, 502);
}
return json({ error: "Not found" }, 404);
},
});
console.log("Partner demo on http://localhost:4455");