v0.1.28 955a9b7

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});

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close