zot-client.js RawHistoryFolder
1// Minimal Node client for the `zot rpc` JSON protocol.
2//
3// Usage:
4// node zot-client.js "fix the failing test"
5//
6// Spawns `zot rpc`, sends one prompt, prints assistant text as it
7// streams, and exits when the turn finishes. Pure stdlib — no
8// dependencies. See docs/rpc.md for the full schema.
9"use strict";
10
11const { spawn } = require("node:child_process");
12const readline = require("node:readline");
13const { randomBytes } = require("node:crypto");
14
15class ZotClient {
16 constructor(...flags) {
17 this.proc = spawn("zot", ["rpc", ...flags], {
18 stdio: ["pipe", "pipe", "inherit"],
19 env: process.env,
20 });
21 this.rl = readline.createInterface({
22 input: this.proc.stdout,
23 crlfDelay: Infinity,
24 });
25 }
26
27 send(command) {
28 if (!command.id) command.id = randomBytes(4).toString("hex");
29 this.proc.stdin.write(JSON.stringify(command) + "\n");
30 return command.id;
31 }
32
33 async *events() {
34 for await (const line of this.rl) {
35 const trimmed = line.trim();
36 if (!trimmed) continue;
37 try {
38 yield JSON.parse(trimmed);
39 } catch {
40 // ignore garbled lines
41 }
42 }
43 }
44
45 close() {
46 try {
47 this.proc.stdin.end();
48 } catch {}
49 }
50}
51
52async function main() {
53 const prompt = process.argv.slice(2).join(" ");
54 if (!prompt) {
55 console.error("usage: node zot-client.js <prompt>");
56 process.exit(2);
57 }
58
59 const client = new ZotClient();
60
61 const token = process.env.ZOTCORE_RPC_TOKEN;
62 if (token) client.send({ type: "hello", token });
63
64 client.send({ type: "prompt", message: prompt });
65
66 let exitCode = 0;
67 try {
68 for await (const ev of client.events()) {
69 switch (ev.type) {
70 case "text_delta":
71 process.stdout.write(ev.delta || "");
72 break;
73 case "tool_call":
74 process.stderr.write(
75 `\n[tool] ${ev.name}(${JSON.stringify(ev.args || {})})`,
76 );
77 break;
78 case "tool_result":
79 if (ev.is_error) process.stderr.write("\n[tool error]");
80 break;
81 case "usage": {
82 const cum = ev.cumulative || {};
83 process.stderr.write(
84 `\n[usage] cum input=${cum.input} output=${cum.output} cost=$${(cum.cost_usd || 0).toFixed(4)}`,
85 );
86 break;
87 }
88 case "error":
89 process.stderr.write(`\n[error] ${ev.message}\n`);
90 exitCode = 1;
91 break;
92 case "done":
93 process.stdout.write("\n");
94 client.close();
95 process.exit(exitCode);
96 }
97 }
98 } finally {
99 client.close();
100 }
101}
102
103main().catch((e) => {
104 console.error(e);
105 process.exit(1);
106});