z0gcode 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +41 -0
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/bin/z0g.mjs +1052 -0
- package/contracts/Z0gSession.json +424 -0
- package/contracts/Z0gSession.sol +106 -0
- package/package.json +60 -0
- package/skills/0g/CHAIN.md +229 -0
- package/skills/0g/COMPUTE.md +296 -0
- package/skills/0g/NETWORK_CONFIG.md +163 -0
- package/skills/0g/SECURITY.md +174 -0
- package/skills/0g/STORAGE.md +167 -0
- package/skills/0g/TESTING.md +263 -0
- package/src/agent.mjs +434 -0
- package/src/anchor.mjs +65 -0
- package/src/checkpoints.mjs +64 -0
- package/src/client.mjs +134 -0
- package/src/commands.mjs +93 -0
- package/src/config.mjs +74 -0
- package/src/context.mjs +55 -0
- package/src/crypto.mjs +47 -0
- package/src/env.mjs +47 -0
- package/src/goal.mjs +45 -0
- package/src/inft.mjs +79 -0
- package/src/mcp-server.mjs +38 -0
- package/src/mcp.mjs +91 -0
- package/src/media.mjs +52 -0
- package/src/models-info.mjs +97 -0
- package/src/plan.mjs +21 -0
- package/src/prompt.mjs +149 -0
- package/src/provenance.mjs +46 -0
- package/src/sessions.mjs +168 -0
- package/src/settings.mjs +37 -0
- package/src/skills.mjs +43 -0
- package/src/tools.mjs +481 -0
- package/src/ui.mjs +798 -0
- package/src/user-skills.mjs +111 -0
- package/src/worktree.mjs +61 -0
package/bin/z0g.mjs
ADDED
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// z0gcode CLI: a coding agent whose brain runs on 0G Compute.
|
|
3
|
+
import "../src/env.mjs"; // load .env before config reads process.env
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { exec } from "node:child_process";
|
|
8
|
+
import { CONFIG, normEffort, EFFORT_LEVELS, boolOf } from "../src/config.mjs";
|
|
9
|
+
import { makeClient } from "../src/client.mjs";
|
|
10
|
+
import { runAgent } from "../src/agent.mjs";
|
|
11
|
+
import { INIT_TASK } from "../src/context.mjs";
|
|
12
|
+
import { undoLastTurn, readCheckpointLog, activeTurns } from "../src/checkpoints.mjs";
|
|
13
|
+
import { loadCustomCommands, expandTemplate, loadHooks, runHooks, hasHooks } from "../src/commands.mjs";
|
|
14
|
+
import { runGoal } from "../src/goal.mjs";
|
|
15
|
+
import { loadProvenance } from "../src/provenance.mjs";
|
|
16
|
+
import {
|
|
17
|
+
sessionDir, listSessions, mostRecent, createSession,
|
|
18
|
+
readMessages, saveMessages, renameSession, deleteSession, migrateLegacy, ensureGitignore, pruneEmptySync,
|
|
19
|
+
} from "../src/sessions.mjs";
|
|
20
|
+
import { loadPlan } from "../src/plan.mjs";
|
|
21
|
+
import { loadMcp } from "../src/mcp.mjs";
|
|
22
|
+
import { saveSetting } from "../src/settings.mjs";
|
|
23
|
+
import { fetchModels, orderChatModels } from "../src/models-info.mjs";
|
|
24
|
+
import { discoverSkills, setSkillEnabled } from "../src/user-skills.mjs";
|
|
25
|
+
import { generateImage, transcribeAudio } from "../src/media.mjs";
|
|
26
|
+
import { uploadFileToStorage, anchorOnChain, downloadAndVerify } from "../src/anchor.mjs";
|
|
27
|
+
import { encryptEnvelope, decryptEnvelope } from "../src/crypto.mjs";
|
|
28
|
+
import { arrowSelect } from "../src/prompt.mjs";
|
|
29
|
+
import * as ui from "../src/ui.mjs";
|
|
30
|
+
|
|
31
|
+
function helpText() {
|
|
32
|
+
const groups = [
|
|
33
|
+
["Run", [
|
|
34
|
+
['z0g "<task>"', "Run a coding task (one-shot)"],
|
|
35
|
+
["z0g", "Interactive session (REPL, /help for commands)"],
|
|
36
|
+
['z0g goal "<obj>"', "Iterate until a verify command passes"],
|
|
37
|
+
["z0g init", "Analyze the project and write an AGENTS.md context file"],
|
|
38
|
+
]],
|
|
39
|
+
["Inspect", [
|
|
40
|
+
["z0g models", "List the 0G models on the Router (add --json)"],
|
|
41
|
+
["z0g skills", "List user/project skills (enable|disable <name>)"],
|
|
42
|
+
["z0g doctor", "Check your 0G setup (key, connectivity, model)"],
|
|
43
|
+
["z0g attest", "Show which 0G model wrote which change"],
|
|
44
|
+
["z0g undo", "Revert the file edits from the last turn"],
|
|
45
|
+
["z0g share", "Export a session to 0G Storage, encrypted (--anchor for 0G Chain)"],
|
|
46
|
+
["z0g pull <root>", "Fetch, verify, and decrypt a shared session (--import)"],
|
|
47
|
+
["z0g mint", "Mint a session as an NFT on 0G Chain (ERC-7857-inspired)"],
|
|
48
|
+
]],
|
|
49
|
+
["Media", [
|
|
50
|
+
['z0g image "<prompt>"', "Generate an image on 0G (z-image-turbo), saved as PNG"],
|
|
51
|
+
["z0g transcribe <file>", "Transcribe audio on 0G (whisper-large-v3)"],
|
|
52
|
+
]],
|
|
53
|
+
["Serve", [
|
|
54
|
+
["z0g serve --mcp", "Expose z0gcode's 0G tools as an MCP server"],
|
|
55
|
+
]],
|
|
56
|
+
["Options", [
|
|
57
|
+
["--auto", "Allow shell commands (run_bash)"],
|
|
58
|
+
["--onchain", "Allow gas-spending on-chain actions (off by default)"],
|
|
59
|
+
["--continue", "Continue the saved session in this directory"],
|
|
60
|
+
["--model <id>", "Override the model (default " + CONFIG.model + ")"],
|
|
61
|
+
["--effort <l>", "Reasoning effort: low, medium, high (default: model's own)"],
|
|
62
|
+
["--no-subagents", "Disable parallel subagents for this run"],
|
|
63
|
+
['--verify "<cmd>"', "Run, then verify and self-correct with this command"],
|
|
64
|
+
["--auto-verify", "Same, auto-detecting the verify command"],
|
|
65
|
+
["--max-steps <n>", "Max agent steps (default " + CONFIG.maxSteps + ")"],
|
|
66
|
+
["--cwd <dir>", "Working directory (default: current)"],
|
|
67
|
+
]],
|
|
68
|
+
["Setup", [
|
|
69
|
+
["ZOG_API_KEY", "Your 0G Router key (env or .env). Get one at https://pc.0g.ai"],
|
|
70
|
+
]],
|
|
71
|
+
];
|
|
72
|
+
const w = Math.max(...groups.flatMap(([, rows]) => rows.map(([c]) => c.length))) + 2;
|
|
73
|
+
const out = [ui.strong("z0gcode") + ui.muted(" · a coding agent whose brain runs on 0G Compute.")];
|
|
74
|
+
for (const [title, rows] of groups) {
|
|
75
|
+
out.push("");
|
|
76
|
+
out.push(" " + ui.muted(title));
|
|
77
|
+
for (const [c, d] of rows) out.push(" " + ui.accent(c.padEnd(w)) + ui.muted(d));
|
|
78
|
+
}
|
|
79
|
+
out.push("");
|
|
80
|
+
out.push(" " + ui.muted("0G is the default backend: no OpenAI or Anthropic key, no config file."));
|
|
81
|
+
return out.join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const SLASH_COMMANDS = [
|
|
85
|
+
["/help", "Show commands"],
|
|
86
|
+
["/clear", "Reset the conversation context"],
|
|
87
|
+
["/chats", "Switch chat (arrow-key picker, search, rename, delete)"],
|
|
88
|
+
["/new", "Start a new chat (/new [title])"],
|
|
89
|
+
["/rename", "Rename the current chat (/rename <title>)"],
|
|
90
|
+
["/init", "Analyze the project and write an AGENTS.md context file"],
|
|
91
|
+
["/model", "Pick the active 0G model (saved to settings)"],
|
|
92
|
+
["/effort", "Set reasoning effort (low|medium|high|default)"],
|
|
93
|
+
["/subagents", "Enable or disable parallel subagents (on|off)"],
|
|
94
|
+
["/onchain", "Enable or disable gas-spending on-chain actions (on|off)"],
|
|
95
|
+
["/skills", "List skills; /skills enable|disable <name>"],
|
|
96
|
+
["/commands", "List project custom commands (.z0g/commands/*.md)"],
|
|
97
|
+
["/attest", "Show the provenance manifest"],
|
|
98
|
+
["/undo", "Revert the file edits from the last turn"],
|
|
99
|
+
["/checkpoints", "List the turns you can undo"],
|
|
100
|
+
["/share", "Export this session to 0G Storage (/share anchor to anchor on-chain)"],
|
|
101
|
+
["/pull", "Fetch + verify + decrypt a shared session (/pull <root> [import])"],
|
|
102
|
+
["/mint", "Mint this session as an NFT on 0G Chain (records its Storage root)"],
|
|
103
|
+
["/plan", "Show the current task checklist"],
|
|
104
|
+
["/verify", "Run the project's verify command (npm test / .z0g/verify)"],
|
|
105
|
+
["/goal", "Run until the verify command passes"],
|
|
106
|
+
["/exit", "Quit"],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// Menu shown on /help or when you type "/" and hit Enter. `filter` narrows it.
|
|
110
|
+
function slashMenu(filter) {
|
|
111
|
+
let rows = SLASH_COMMANDS;
|
|
112
|
+
if (filter) {
|
|
113
|
+
const f = SLASH_COMMANDS.filter(([c]) => c.startsWith(filter));
|
|
114
|
+
if (f.length) rows = f;
|
|
115
|
+
}
|
|
116
|
+
const list = rows.map(([c, d]) => ` ${ui.accent(c.padEnd(9))} ${ui.muted(d)}`).join("\n");
|
|
117
|
+
return " " + ui.muted("Commands") + "\n" + list;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Project-local custom command names (e.g. /review), set by repl() at startup.
|
|
121
|
+
let extraSlash = [];
|
|
122
|
+
// Tab-completion for slash commands: "/" + Tab lists all, "/mo" + Tab -> "/model".
|
|
123
|
+
function slashCompleter(line) {
|
|
124
|
+
if (!line.startsWith("/")) return [[], line];
|
|
125
|
+
const names = [...SLASH_COMMANDS.map(([c]) => c), ...extraSlash];
|
|
126
|
+
const hits = names.filter((c) => c.startsWith(line));
|
|
127
|
+
return [hits.length ? hits : names, line];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fetch chat models ordered for the picker (default first, verifiable by price).
|
|
131
|
+
async function chatModelsFor(client, current) {
|
|
132
|
+
try {
|
|
133
|
+
const all = await fetchModels(client);
|
|
134
|
+
const chat = orderChatModels(all, current).filter((m) => m.tools);
|
|
135
|
+
return chat.length ? chat : orderChatModels(all, current);
|
|
136
|
+
} catch (e) {
|
|
137
|
+
ui.error("could not list models: " + e.message);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const interactiveTTY = () => ui.interactive && typeof process.stdin.setRawMode === "function";
|
|
143
|
+
|
|
144
|
+
// One-shot line prompt (used for rename/delete confirmation inside the picker).
|
|
145
|
+
// Isolate the outer (paused) REPL readline's keypress listeners while we read,
|
|
146
|
+
// or it buffers this input and replays it as a spurious task on resume.
|
|
147
|
+
function ask(question) {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const saved = process.stdin.listeners("keypress").slice();
|
|
150
|
+
for (const l of saved) process.stdin.removeListener("keypress", l);
|
|
151
|
+
const r = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
152
|
+
r.question(question, (a) => {
|
|
153
|
+
r.close();
|
|
154
|
+
for (const l of saved) process.stdin.on("keypress", l);
|
|
155
|
+
resolve(a);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The arrow-key session picker with search, rename (ctrl-r) and delete (ctrl-x).
|
|
161
|
+
// Returns a session id, "__new__", or "__cancel__".
|
|
162
|
+
async function sessionPickerLoop(cwd, currentId) {
|
|
163
|
+
while (true) {
|
|
164
|
+
const sessions = listSessions(cwd);
|
|
165
|
+
const items = [{ __new: true }, ...sessions];
|
|
166
|
+
let initial = 1 <= sessions.length ? 1 : 0;
|
|
167
|
+
if (currentId) {
|
|
168
|
+
const at = items.findIndex((it) => it.id === currentId);
|
|
169
|
+
if (at >= 0) initial = at;
|
|
170
|
+
}
|
|
171
|
+
const res = await arrowSelect({
|
|
172
|
+
items,
|
|
173
|
+
initialIndex: initial,
|
|
174
|
+
renderFrame: (its, i, c) => ui.sessionPickerFrame(its, i, c),
|
|
175
|
+
clearOnExit: true,
|
|
176
|
+
filterable: true,
|
|
177
|
+
filterText: (it) => (it.__new ? "" : it.title), // hide "New chat" while searching
|
|
178
|
+
onActionKey: (key) =>
|
|
179
|
+
key.ctrl && key.name === "r" ? "rename" : key.ctrl && key.name === "x" ? "delete" : null,
|
|
180
|
+
});
|
|
181
|
+
if (res === undefined) return "__cancel__";
|
|
182
|
+
if (res.__action === "rename") {
|
|
183
|
+
if (res.item && !res.item.__new) {
|
|
184
|
+
const t = (await ask(" new title: ")).trim();
|
|
185
|
+
if (t) await renameSession(cwd, res.item.id, t);
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (res.__action === "delete") {
|
|
190
|
+
if (res.item && !res.item.__new) {
|
|
191
|
+
const a = (await ask(` delete "${res.item.title}"? (y/N) `)).trim();
|
|
192
|
+
if (/^y/i.test(a)) await deleteSession(cwd, res.item.id);
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (res.__new) return "__new__";
|
|
197
|
+
return res.id;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Resolve which session to use, based on flags and whether a picker applies.
|
|
202
|
+
// Returns { id, dir, history } or null if the user cancelled the picker.
|
|
203
|
+
async function openSession(cwd, flags, { pickerOnOpen = false } = {}) {
|
|
204
|
+
await migrateLegacy(cwd);
|
|
205
|
+
await ensureGitignore(cwd);
|
|
206
|
+
const sessions = listSessions(cwd);
|
|
207
|
+
const load = async (id) => ({ id, dir: sessionDir(cwd, id), history: await readMessages(cwd, id) });
|
|
208
|
+
const fresh = async () => {
|
|
209
|
+
const s = await createSession(cwd, {});
|
|
210
|
+
return { id: s.id, dir: s.dir, history: null };
|
|
211
|
+
};
|
|
212
|
+
if (flags.new) return await fresh();
|
|
213
|
+
if (flags.cont) return sessions.length ? await load(sessions[0].id) : await fresh();
|
|
214
|
+
if ((flags.resume || pickerOnOpen) && sessions.length) {
|
|
215
|
+
if (interactiveTTY()) {
|
|
216
|
+
const chosen = await sessionPickerLoop(cwd, null);
|
|
217
|
+
if (chosen === "__cancel__") return null;
|
|
218
|
+
if (chosen === "__new__") return await fresh();
|
|
219
|
+
return await load(chosen);
|
|
220
|
+
}
|
|
221
|
+
return await load(sessions[0].id); // non-interactive: most recent
|
|
222
|
+
}
|
|
223
|
+
return await fresh();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parse(argv) {
|
|
227
|
+
const flags = { auto: false, cont: false };
|
|
228
|
+
const positional = [];
|
|
229
|
+
for (let i = 0; i < argv.length; i++) {
|
|
230
|
+
const a = argv[i];
|
|
231
|
+
if (a === "--auto") flags.auto = true;
|
|
232
|
+
else if (a === "--mcp") flags.mcp = true;
|
|
233
|
+
else if (a === "--json") flags.json = true;
|
|
234
|
+
else if (a === "--num") flags.num = Number(argv[++i]);
|
|
235
|
+
else if (a === "--anchor") flags.anchor = true;
|
|
236
|
+
else if (a === "--auto-verify") flags.autoVerify = true;
|
|
237
|
+
else if (a === "--continue") flags.cont = true;
|
|
238
|
+
else if (a === "--resume") flags.resume = true;
|
|
239
|
+
else if (a === "--new") flags.new = true;
|
|
240
|
+
else if (a === "--model") flags.model = argv[++i];
|
|
241
|
+
else if (a === "--effort") {
|
|
242
|
+
const v = String(argv[++i] || "").toLowerCase().trim();
|
|
243
|
+
if (EFFORT_LEVELS.includes(v)) flags.effort = v;
|
|
244
|
+
else if (["default", "off", "none", "unset", "model"].includes(v)) flags.effort = ""; // explicit unset
|
|
245
|
+
// otherwise leave undefined (falls back to the saved/env default)
|
|
246
|
+
}
|
|
247
|
+
else if (a === "--subagents") flags.subagents = boolOf(argv[++i]);
|
|
248
|
+
else if (a === "--no-subagents") flags.subagents = false;
|
|
249
|
+
else if (a === "--onchain") flags.onchain = true;
|
|
250
|
+
else if (a === "--no-onchain") flags.onchain = false;
|
|
251
|
+
else if (a === "--force") flags.force = true;
|
|
252
|
+
else if (a === "--import") flags.import = true;
|
|
253
|
+
else if (a === "--verify") flags.verify = argv[++i];
|
|
254
|
+
else if (a === "--max-steps") CONFIG.maxSteps = Number(argv[++i]) || CONFIG.maxSteps;
|
|
255
|
+
else if (a === "--cwd") flags.cwd = argv[++i];
|
|
256
|
+
else if (a === "-h" || a === "--help") flags.help = true;
|
|
257
|
+
else if (a === "-v" || a === "--version") flags.version = true;
|
|
258
|
+
else positional.push(a);
|
|
259
|
+
}
|
|
260
|
+
return { flags, positional };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolveCwd(flags) {
|
|
264
|
+
return flags.cwd ? path.resolve(process.cwd(), flags.cwd) : process.cwd();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function detectVerifyCmd(cwd) {
|
|
268
|
+
const zogVerify = path.join(cwd, ".z0g", "verify");
|
|
269
|
+
if (existsSync(zogVerify)) {
|
|
270
|
+
try {
|
|
271
|
+
const c = readFileSync(zogVerify, "utf8").trim();
|
|
272
|
+
if (c) return c;
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const pkg = JSON.parse(readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
277
|
+
const test = pkg.scripts?.test;
|
|
278
|
+
if (test && !/no test specified/i.test(test)) return "npm test";
|
|
279
|
+
} catch {}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sh(cmd, cwd) {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
exec(cmd, { cwd, timeout: 300_000, maxBuffer: 10 * 1024 * 1024 }, (err, so, se) => {
|
|
286
|
+
const code = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
|
|
287
|
+
resolve({ code, out: `${so || ""}${se || ""}` });
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function cmdModels(flags = {}) {
|
|
293
|
+
const client = makeClient();
|
|
294
|
+
const models = await fetchModels(client);
|
|
295
|
+
if (flags.json) {
|
|
296
|
+
const out = models.map((m) => ({
|
|
297
|
+
id: m.id, name: m.name, type: m.type, context_length: m.ctx, max_output: m.maxOut,
|
|
298
|
+
price_in_per_1m: m.inPerM, price_out_per_1m: m.outPerM,
|
|
299
|
+
tools: m.tools, vision: m.vision, verifiable: m.verifiable, private: m.private,
|
|
300
|
+
tee: m.tee, discount_pct: m.discount,
|
|
301
|
+
}));
|
|
302
|
+
console.log(JSON.stringify(out, null, 2));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
console.log(ui.renderModelsTable(models, { currentId: CONFIG.model }));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function cmdImage(prompt, out, flags) {
|
|
309
|
+
if (!prompt) { console.log(ui.warn('Usage: z0g image "<prompt>" [out.png] [--num 2]')); return; }
|
|
310
|
+
const cwd = resolveCwd(flags);
|
|
311
|
+
const n = Math.max(1, Math.min(2, Number(flags.num) || 1));
|
|
312
|
+
const base = path.resolve(cwd, out || "image.png").replace(/\.png$/i, "");
|
|
313
|
+
ui.info(`generating ${n} image(s) on 0G (${CONFIG.imageModel})`);
|
|
314
|
+
const { images, cost } = await generateImage(makeClient(), { prompt, n });
|
|
315
|
+
const written = [];
|
|
316
|
+
for (let i = 0; i < images.length; i++) {
|
|
317
|
+
const p = images.length > 1 ? `${base}-${i + 1}.png` : `${base}.png`;
|
|
318
|
+
mkdirSync(path.dirname(p), { recursive: true });
|
|
319
|
+
writeFileSync(p, Buffer.from(images[i], "base64"));
|
|
320
|
+
written.push(path.relative(cwd, p) || p);
|
|
321
|
+
}
|
|
322
|
+
const costStr = cost != null ? " · ~$" + cost.toFixed(4) : "";
|
|
323
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " wrote " + ui.strong(written.join(", ")) + ui.muted(" · " + CONFIG.imageModel + costStr + " · ") + ui.accent(ui.GLYPH.seal) + ui.muted(" 0G Compute (TEE)"));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function cmdTranscribe(file, flags) {
|
|
327
|
+
if (!file) { console.log(ui.warn("Usage: z0g transcribe <audio-file>")); return; }
|
|
328
|
+
const cwd = resolveCwd(flags);
|
|
329
|
+
const abs = path.resolve(cwd, file);
|
|
330
|
+
if (!existsSync(abs)) { console.log(ui.warn("File not found: " + file)); return; }
|
|
331
|
+
ui.info(`transcribing on 0G (${CONFIG.transcribeModel})`);
|
|
332
|
+
const { text, duration, cost } = await transcribeAudio(makeClient(), abs);
|
|
333
|
+
console.log("\n" + (text || ui.muted("(empty transcript)")) + "\n");
|
|
334
|
+
const meta = [CONFIG.transcribeModel];
|
|
335
|
+
if (duration) meta.push(duration.toFixed(1) + "s");
|
|
336
|
+
if (cost != null) meta.push("~$" + cost.toFixed(4));
|
|
337
|
+
console.log(ui.muted(" · " + meta.join(" · ") + " · ") + ui.accent(ui.GLYPH.seal) + ui.muted(" 0G Compute (TEE)"));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Generate an AGENTS.md by letting the agent analyze the project. The file is
|
|
341
|
+
// then auto-loaded into the agent's context on subsequent runs.
|
|
342
|
+
async function cmdInit(flags) {
|
|
343
|
+
const cwd = resolveCwd(flags);
|
|
344
|
+
const target = path.join(cwd, "AGENTS.md");
|
|
345
|
+
if (existsSync(target) && !flags.force) {
|
|
346
|
+
console.log(ui.warn("AGENTS.md already exists. Re-run `z0g init --force` to regenerate it."));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
console.log(ui.section("Init", "analyzing the project to write AGENTS.md"));
|
|
350
|
+
const res = await runAgent({
|
|
351
|
+
client: makeClient(), task: INIT_TASK, cwd,
|
|
352
|
+
sessionDir: path.join(cwd, ".z0g"), allowBash: false,
|
|
353
|
+
preferredModel: flags.model, preferredEffort: flags.effort,
|
|
354
|
+
preferredSubagents: false, preferredOnchain: false,
|
|
355
|
+
});
|
|
356
|
+
if (existsSync(target)) {
|
|
357
|
+
const lines = readFileSync(target, "utf8").split("\n").length;
|
|
358
|
+
console.log("\n " + ui.ok(ui.GLYPH.ok) + " " + ui.strong("AGENTS.md") + ui.muted(" written (" + lines + " lines). It is auto-loaded into the agent's context from now on."));
|
|
359
|
+
} else {
|
|
360
|
+
console.log(ui.warn("The agent did not create AGENTS.md. " + (res?.finalText ? "It said: " + res.finalText.slice(0, 200) : "Try again or write it by hand.")));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Revert the most recent turn's file edits (restore before-content, delete
|
|
365
|
+
// files the turn created), using the session's checkpoint log.
|
|
366
|
+
async function cmdUndo(flags, sessionId) {
|
|
367
|
+
const cwd = resolveCwd(flags);
|
|
368
|
+
await migrateLegacy(cwd);
|
|
369
|
+
const id = sessionId || mostRecent(cwd);
|
|
370
|
+
if (!id) { console.log(ui.warn("No session yet, nothing to undo.")); return; }
|
|
371
|
+
const rep = await undoLastTurn(cwd, sessionDir(cwd, id));
|
|
372
|
+
if (!rep) { console.log(ui.warn("Nothing to undo in this chat.")); return; }
|
|
373
|
+
console.log(ui.section("Undo", rep.task || rep.runId));
|
|
374
|
+
for (const f of rep.files) {
|
|
375
|
+
const g = f.action === "failed" ? ui.err(ui.GLYPH.no) : ui.ok(ui.GLYPH.ok);
|
|
376
|
+
const note = f.diverged ? ui.warn(" (had changed since; overwritten)") : "";
|
|
377
|
+
console.log(" " + g + " " + f.action + " " + ui.strong(f.path) + (f.error ? ui.err(" " + f.error) : note));
|
|
378
|
+
}
|
|
379
|
+
console.log("\n " + ui.muted("Reverted the last turn. Run undo again to step further back."));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// List the turns still available to undo (newest first).
|
|
383
|
+
async function cmdCheckpoints(flags, sessionId) {
|
|
384
|
+
const cwd = resolveCwd(flags);
|
|
385
|
+
await migrateLegacy(cwd);
|
|
386
|
+
const id = sessionId || mostRecent(cwd);
|
|
387
|
+
if (!id) { console.log(ui.warn("No session yet.")); return; }
|
|
388
|
+
const turns = activeTurns(await readCheckpointLog(sessionDir(cwd, id)));
|
|
389
|
+
if (!turns.length) { ui.info("no checkpoints yet (the agent has not edited files in this chat)"); return; }
|
|
390
|
+
console.log(ui.section("Checkpoints", turns.length + " turn(s) you can undo"));
|
|
391
|
+
turns.slice(-12).reverse().forEach((t, i) => {
|
|
392
|
+
const files = [...new Set(t.edits.map((e) => e.path))];
|
|
393
|
+
const tag = i === 0 ? ui.accent("next undo") : ui.muted(ui.relTime(t.ts));
|
|
394
|
+
console.log(" " + ui.strong(files.length + " file" + (files.length === 1 ? "" : "s")) + " " + ui.muted(files.join(", ")) + " " + tag);
|
|
395
|
+
if (t.task) console.log(" " + ui.muted(t.task));
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Build the shareable session bundle (title + transcript + provenance) and
|
|
400
|
+
// write it to share-bundle.json. Returns { bundlePath, title }.
|
|
401
|
+
function buildSessionBundle(cwd, id) {
|
|
402
|
+
const dir = sessionDir(cwd, id);
|
|
403
|
+
let sess = {};
|
|
404
|
+
try { sess = JSON.parse(readFileSync(path.join(dir, "session.json"), "utf8")); } catch {}
|
|
405
|
+
let provenance = null;
|
|
406
|
+
try { provenance = JSON.parse(readFileSync(path.join(dir, "provenance.json"), "utf8")); } catch {}
|
|
407
|
+
const bundle = {
|
|
408
|
+
tool: "z0gcode",
|
|
409
|
+
ts: new Date().toISOString(),
|
|
410
|
+
session: { id: sess.id || id, title: sess.title || "", created: sess.created, messages: sess.messages || [] },
|
|
411
|
+
provenance,
|
|
412
|
+
};
|
|
413
|
+
const bundlePath = path.join(dir, "share-bundle.json");
|
|
414
|
+
mkdirSync(dir, { recursive: true });
|
|
415
|
+
writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
|
|
416
|
+
return { bundlePath, title: bundle.session.title || id, ts: bundle.ts };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Build the bundle and encrypt it for the wallet, returning the path to upload.
|
|
420
|
+
// 0G Storage is public, so we only ever upload ciphertext.
|
|
421
|
+
function encryptedBundlePath(cwd, id) {
|
|
422
|
+
const { bundlePath, title, ts } = buildSessionBundle(cwd, id);
|
|
423
|
+
const enc = encryptEnvelope(readFileSync(bundlePath), process.env.ZOG_WALLET_KEY);
|
|
424
|
+
const encPath = path.join(sessionDir(cwd, id), "share-bundle.enc");
|
|
425
|
+
writeFileSync(encPath, enc);
|
|
426
|
+
return { encPath, title, ts };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Ensure the session has a 0G Storage root: reuse share.json or upload the
|
|
430
|
+
// bundle. Returns { root, storageTx, cached }.
|
|
431
|
+
async function ensureSessionRoot(cwd, id) {
|
|
432
|
+
const dir = sessionDir(cwd, id);
|
|
433
|
+
try {
|
|
434
|
+
const rec = JSON.parse(readFileSync(path.join(dir, "share.json"), "utf8"));
|
|
435
|
+
if (rec.root) return { root: rec.root, storageTx: rec.storageTx, cached: true };
|
|
436
|
+
} catch { /* not shared yet */ }
|
|
437
|
+
const { encPath, ts } = encryptedBundlePath(cwd, id);
|
|
438
|
+
const { rootHash, txHash } = await uploadFileToStorage(encPath);
|
|
439
|
+
writeFileSync(path.join(dir, "share.json"), JSON.stringify({ id, root: rootHash, storageTx: txHash, encrypted: true, ts }, null, 2));
|
|
440
|
+
return { root: rootHash, storageTx: txHash, cached: false };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Export a session (transcript + provenance) to 0G Storage, optionally anchoring
|
|
444
|
+
// the content hash on 0G Chain, for a verifiable, shareable snapshot.
|
|
445
|
+
async function cmdShare(flags, sessionId) {
|
|
446
|
+
const cwd = resolveCwd(flags);
|
|
447
|
+
await migrateLegacy(cwd);
|
|
448
|
+
const id = sessionId || mostRecent(cwd);
|
|
449
|
+
if (!id) { console.log(ui.warn("No session to share. Run a task first.")); return; }
|
|
450
|
+
const onchainOn = flags.onchain !== undefined ? flags.onchain : CONFIG.onchain;
|
|
451
|
+
if (!onchainOn) { console.log(ui.warn("On-chain is off. Enable it with --onchain, /onchain on, or ZOG_ONCHAIN=on.")); return; }
|
|
452
|
+
if (!process.env.ZOG_WALLET_KEY) { console.log(ui.warn("Set ZOG_WALLET_KEY (a funded 0G mainnet key) to share on 0G.")); return; }
|
|
453
|
+
const dir = sessionDir(cwd, id);
|
|
454
|
+
const { encPath, title, ts } = encryptedBundlePath(cwd, id);
|
|
455
|
+
|
|
456
|
+
console.log(ui.section("Share session", title));
|
|
457
|
+
ui.info("encrypting for your wallet and uploading to 0G Storage...");
|
|
458
|
+
let root, storageTx;
|
|
459
|
+
try {
|
|
460
|
+
({ rootHash: root, txHash: storageTx } = await uploadFileToStorage(encPath));
|
|
461
|
+
} catch (e) { console.log(ui.err(" upload failed: " + e.message)); return; }
|
|
462
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " 0G Storage root " + ui.strong(root) + ui.muted(" (encrypted)"));
|
|
463
|
+
console.log(" " + ui.muted(" tx " + storageTx + " · https://chainscan.0g.ai/tx/" + storageTx));
|
|
464
|
+
const record = { id, root, storageTx, encrypted: true, ts };
|
|
465
|
+
if (flags.anchor) {
|
|
466
|
+
ui.info("anchoring the hash on 0G Chain...");
|
|
467
|
+
try {
|
|
468
|
+
const a = await anchorOnChain(root);
|
|
469
|
+
record.anchorTx = a.txHash; record.block = a.block;
|
|
470
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " anchored on 0G Chain " + ui.strong(a.txHash));
|
|
471
|
+
console.log(" " + ui.muted(" block " + a.block + " · https://chainscan.0g.ai/tx/" + a.txHash));
|
|
472
|
+
} catch (e) { console.log(ui.err(" anchor failed: " + e.message)); }
|
|
473
|
+
}
|
|
474
|
+
writeFileSync(path.join(dir, "share.json"), JSON.stringify(record, null, 2));
|
|
475
|
+
console.log("\n " + ui.accent(ui.GLYPH.seal) + ui.muted(" Verifiable session snapshot on 0G. Saved to .z0g/sessions/" + id + "/share.json"));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Pull a shared session back: download by its 0G Storage root, verify the root,
|
|
479
|
+
// and decrypt with the wallet. Read-only (no gas); proves the round-trip and that
|
|
480
|
+
// only the owner's wallet can read the content.
|
|
481
|
+
async function cmdPull(flags, root) {
|
|
482
|
+
if (!root || !/^0x[0-9a-fA-F]{6,}$/.test(root)) { console.log(ui.warn("Usage: z0g pull <0G Storage content root> [--import]")); return; }
|
|
483
|
+
const cwd = resolveCwd(flags);
|
|
484
|
+
await migrateLegacy(cwd);
|
|
485
|
+
if (!process.env.ZOG_WALLET_KEY) { console.log(ui.warn("Set ZOG_WALLET_KEY (the wallet you shared with) to decrypt the session.")); return; }
|
|
486
|
+
console.log(ui.section("Pull session", root.slice(0, 18) + "…"));
|
|
487
|
+
const tmpDir = path.join(cwd, ".z0g", "tmp");
|
|
488
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
489
|
+
const tmp = path.join(tmpDir, "pull-" + root.slice(2, 14) + ".enc");
|
|
490
|
+
try { rmSync(tmp, { force: true }); } catch {}
|
|
491
|
+
ui.info("downloading from 0G Storage and verifying the content root...");
|
|
492
|
+
try {
|
|
493
|
+
await downloadAndVerify(root, tmp);
|
|
494
|
+
} catch (e) { console.log(ui.err(" download/verify failed: " + e.message)); return; }
|
|
495
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " content root verified against 0G Storage");
|
|
496
|
+
let plain;
|
|
497
|
+
try {
|
|
498
|
+
plain = decryptEnvelope(readFileSync(tmp), process.env.ZOG_WALLET_KEY);
|
|
499
|
+
} catch (e) {
|
|
500
|
+
console.log(" " + ui.err(ui.GLYPH.no) + " " + e.message);
|
|
501
|
+
console.log(" " + ui.muted("The bytes are authentic, but only the owner's wallet can read them."));
|
|
502
|
+
try { rmSync(tmp, { force: true }); } catch {}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " decrypted with your wallet");
|
|
506
|
+
let bundle;
|
|
507
|
+
try { bundle = JSON.parse(plain.toString("utf8")); } catch { console.log(ui.warn(" decrypted, but the bundle is not valid JSON")); try { rmSync(tmp, { force: true }); } catch {} return; }
|
|
508
|
+
const s = bundle.session || {};
|
|
509
|
+
const msgs = Array.isArray(s.messages) ? s.messages.length : 0;
|
|
510
|
+
const prov = (bundle.provenance && Array.isArray(bundle.provenance.entries)) ? bundle.provenance.entries.length : 0;
|
|
511
|
+
console.log("\n " + ui.strong(s.title || s.id || "session"));
|
|
512
|
+
console.log(" " + ui.muted(msgs + " message(s) · " + prov + " recorded change(s) · from " + (bundle.tool || "?")));
|
|
513
|
+
if (flags.import) {
|
|
514
|
+
const created = await createSession(cwd, { title: s.title ? s.title + " (pulled)" : "pulled session" });
|
|
515
|
+
await saveMessages(cwd, created.id, s.messages || []);
|
|
516
|
+
console.log("\n " + ui.ok(ui.GLYPH.ok) + " imported as a new chat: " + ui.strong(created.id));
|
|
517
|
+
} else {
|
|
518
|
+
console.log("\n " + ui.muted("Add --import to load it as a new chat here."));
|
|
519
|
+
}
|
|
520
|
+
try { rmSync(tmp, { force: true }); } catch {}
|
|
521
|
+
console.log(" " + ui.accent(ui.GLYPH.seal) + ui.muted(" Verifiable round-trip: fetched from 0G Storage, root-checked, decrypted."));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Mint the session as an NFT on 0G Chain: its token records the 0G Storage root
|
|
525
|
+
// of the session bundle, so an AI work session becomes an ownable, provable
|
|
526
|
+
// asset (ERC-721 based, ERC-7857-inspired).
|
|
527
|
+
async function cmdMint(flags, sessionId) {
|
|
528
|
+
const cwd = resolveCwd(flags);
|
|
529
|
+
await migrateLegacy(cwd);
|
|
530
|
+
const id = sessionId || mostRecent(cwd);
|
|
531
|
+
if (!id) { console.log(ui.warn("No session to mint. Run a task first.")); return; }
|
|
532
|
+
const onchainOn = flags.onchain !== undefined ? flags.onchain : CONFIG.onchain;
|
|
533
|
+
if (!onchainOn) { console.log(ui.warn("On-chain is off. Enable it with --onchain, /onchain on, or ZOG_ONCHAIN=on.")); return; }
|
|
534
|
+
if (!process.env.ZOG_WALLET_KEY) { console.log(ui.warn("Set ZOG_WALLET_KEY (a funded 0G mainnet key) to mint.")); return; }
|
|
535
|
+
const dir = sessionDir(cwd, id);
|
|
536
|
+
|
|
537
|
+
console.log(ui.section("Mint session INFT", id));
|
|
538
|
+
let root, storageTx;
|
|
539
|
+
ui.info("ensuring the session is on 0G Storage...");
|
|
540
|
+
try {
|
|
541
|
+
const r = await ensureSessionRoot(cwd, id);
|
|
542
|
+
root = r.root; storageTx = r.storageTx;
|
|
543
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + (r.cached ? " reusing" : " uploaded") + " 0G Storage root " + ui.strong(root));
|
|
544
|
+
} catch (e) { console.log(ui.err(" storage failed: " + e.message)); return; }
|
|
545
|
+
|
|
546
|
+
const meta = {
|
|
547
|
+
name: "z0gcode session " + root.slice(0, 10),
|
|
548
|
+
description: "A verifiable z0gcode session: transcript and provenance stored on 0G Storage, reasoning served by 0G Compute (TEE).",
|
|
549
|
+
session: id,
|
|
550
|
+
storage_root: root,
|
|
551
|
+
external_url: "https://chainscan.0g.ai/tx/" + storageTx,
|
|
552
|
+
attributes: [
|
|
553
|
+
{ trait_type: "tool", value: "z0gcode" },
|
|
554
|
+
{ trait_type: "backend", value: "0G Compute" },
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
const uri = "data:application/json;base64," + Buffer.from(JSON.stringify(meta)).toString("base64");
|
|
558
|
+
|
|
559
|
+
ui.info("minting on 0G Chain...");
|
|
560
|
+
try {
|
|
561
|
+
const { mintSession } = await import("../src/inft.mjs");
|
|
562
|
+
const r = await mintSession(cwd, { root, uri });
|
|
563
|
+
if (r.deployed) {
|
|
564
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " deployed Z0gSession " + ui.strong(r.contract));
|
|
565
|
+
console.log(" " + ui.muted(" tx " + r.deployTx + " · https://chainscan.0g.ai/address/" + r.contract));
|
|
566
|
+
}
|
|
567
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " minted token " + ui.strong("#" + r.tokenId) + ui.muted(" to " + r.owner));
|
|
568
|
+
console.log(" " + ui.muted(" tx " + r.txHash + " · https://chainscan.0g.ai/tx/" + r.txHash));
|
|
569
|
+
writeFileSync(path.join(dir, "mint.json"), JSON.stringify({ id, contract: r.contract, tokenId: r.tokenId, txHash: r.txHash, block: r.block, root, ts: new Date().toISOString() }, null, 2) + "\n");
|
|
570
|
+
console.log("\n " + ui.accent(ui.GLYPH.seal) + ui.muted(" This session is now an on-chain asset. Saved to .z0g/sessions/" + id + "/mint.json"));
|
|
571
|
+
} catch (e) { console.log(ui.err(" mint failed: " + e.message)); }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async function cmdDoctor() {
|
|
575
|
+
const warnGlyph = ui.uiTTY ? "▲" : "!";
|
|
576
|
+
const row = (state, label, value) => {
|
|
577
|
+
const g =
|
|
578
|
+
state === "ok" ? ui.ok(ui.GLYPH.ok) :
|
|
579
|
+
state === "err" ? ui.err(ui.GLYPH.no) :
|
|
580
|
+
state === "warn" ? ui.warn(warnGlyph) : ui.muted(ui.GLYPH.open);
|
|
581
|
+
console.log(" " + g + " " + ui.muted(String(label).padEnd(18)) + value);
|
|
582
|
+
};
|
|
583
|
+
const group = (t) => console.log(" " + ui.muted(t));
|
|
584
|
+
|
|
585
|
+
console.log(ui.section("Doctor", "0G Compute"));
|
|
586
|
+
const hasKey = !!CONFIG.apiKey;
|
|
587
|
+
group("Auth");
|
|
588
|
+
row(hasKey ? "ok" : "err", "ZOG_API_KEY", hasKey ? "set · value hidden" : ui.warn("missing"));
|
|
589
|
+
if (!hasKey) {
|
|
590
|
+
console.log("\n " + ui.err(ui.GLYPH.no) + " " + ui.strong("Not ready") + ui.muted(" · 1 issue"));
|
|
591
|
+
console.log(" " + ui.muted("Fix: export ZOG_API_KEY=<key> (or add it to .env) · get one at https://pc.0g.ai"));
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
group("Router");
|
|
596
|
+
row("ok", "Endpoint", ui.host(CONFIG.baseURL));
|
|
597
|
+
let models = null;
|
|
598
|
+
try {
|
|
599
|
+
models = await fetchModels(makeClient());
|
|
600
|
+
row("ok", "Reachable", "ok · " + models.length + " models");
|
|
601
|
+
} catch (e) {
|
|
602
|
+
row("err", "Reachable", ui.err("failed: " + e.message));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
group("Model");
|
|
606
|
+
const def = models ? models.find((m) => m.id === CONFIG.model) : null;
|
|
607
|
+
let chip = "";
|
|
608
|
+
if (def) {
|
|
609
|
+
const t = ui.trustTier(def);
|
|
610
|
+
const tee = def.private ? "TeeML" : def.verifiable ? "TeeTLS" : "";
|
|
611
|
+
chip = " " + t.role((t.glyph ? t.glyph + " " : "") + t.long) + (tee ? ui.muted(" (" + tee + ")") : "");
|
|
612
|
+
}
|
|
613
|
+
row("ok", "Default", CONFIG.model + chip);
|
|
614
|
+
if (models) row(def ? "ok" : "warn", "On router", def ? "available" : ui.warn("not found (will fall back)"));
|
|
615
|
+
row("open", "Fallbacks", ui.muted(CONFIG.fallbacks.join(", ")));
|
|
616
|
+
|
|
617
|
+
group("Runtime");
|
|
618
|
+
row("open", "Limits", ui.muted(CONFIG.maxSteps + " steps · " + CONFIG.maxTokens + " max tokens · temp " + CONFIG.temperature));
|
|
619
|
+
row("open", "Effort", ui.muted(CONFIG.effort || "unset (model default)"));
|
|
620
|
+
row("open", "Subagents", ui.muted(CONFIG.subagents ? "on · up to " + CONFIG.maxParallel + " parallel" : "off"));
|
|
621
|
+
row(CONFIG.onchain ? (process.env.ZOG_WALLET_KEY ? "ok" : "warn") : "open", "On-chain",
|
|
622
|
+
CONFIG.onchain ? (process.env.ZOG_WALLET_KEY ? ui.muted("on · wallet set") : ui.warn("on · set ZOG_WALLET_KEY")) : ui.muted("off (gas-spending, opt-in)"));
|
|
623
|
+
|
|
624
|
+
console.log("");
|
|
625
|
+
if (models && def) {
|
|
626
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " " + ui.strong("Ready.") + " " + ui.accent(ui.GLYPH.seal) + ui.muted(" 0G Compute (TEE)"));
|
|
627
|
+
} else {
|
|
628
|
+
console.log(" " + ui.warn(warnGlyph) + " " + ui.strong("Degraded") + ui.muted(models ? " · default model not on router" : " · router unreachable"));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async function printAttest(dir) {
|
|
633
|
+
const man = await loadProvenance(dir);
|
|
634
|
+
if (!man || !Array.isArray(man.entries) || man.entries.length === 0) {
|
|
635
|
+
console.log(ui.muted("No provenance yet. Run a task that edits files, then attest."));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const n = man.entries.length;
|
|
639
|
+
console.log(ui.section("Provenance", n + (n === 1 ? " change" : " changes") + " on 0G"));
|
|
640
|
+
const EMPTY = "e3b0c44298fc"; // sha256("") prefix: a new file
|
|
641
|
+
for (const e of man.entries) {
|
|
642
|
+
const g = e.response_id ? ui.ok(ui.GLYPH.ok) : ui.muted(ui.GLYPH.open);
|
|
643
|
+
console.log(" " + g + " " + ui.strong(e.path));
|
|
644
|
+
console.log(" " + ui.muted("model ") + ui.accent(e.model));
|
|
645
|
+
const before = String(e.sha256_before).slice(0, 12);
|
|
646
|
+
const after = String(e.sha256_after).slice(0, 12);
|
|
647
|
+
const from = before.startsWith(EMPTY) ? "(new file)" : before;
|
|
648
|
+
console.log(" " + ui.muted("hash ") + ui.muted(from) + " " + ui.accent(ui.GLYPH.chevron) + " " + ui.muted(after));
|
|
649
|
+
if (e.tee_trace && e.tee_trace.provider) {
|
|
650
|
+
console.log(" " + ui.muted("0G node") + " " + ui.accent(e.tee_trace.provider) + ui.muted(e.tee_trace.request_id ? " req " + e.tee_trace.request_id : ""));
|
|
651
|
+
}
|
|
652
|
+
console.log(" " + ui.muted("signed " + e.ts + " · " + (e.response_id || "no response id")));
|
|
653
|
+
}
|
|
654
|
+
console.log("");
|
|
655
|
+
const withNode = man.entries.filter((e) => e.tee_trace && e.tee_trace.provider).length;
|
|
656
|
+
console.log(" " + ui.accent(ui.GLYPH.seal) + ui.muted(" Model id, response id" + (withNode ? ", and the 0G provider node address" : "") + " captured from 0G Compute (TEE)."));
|
|
657
|
+
console.log(" " + ui.muted("Full TEE-quote verification: roadmap."));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function printSkills(cwd) {
|
|
661
|
+
const skills = discoverSkills(cwd);
|
|
662
|
+
console.log(ui.section("Skills", skills.length + (skills.length === 1 ? " skill" : " skills")));
|
|
663
|
+
if (!skills.length) {
|
|
664
|
+
console.log(" " + ui.muted("No user skills yet. Add a markdown file with name/description frontmatter to:"));
|
|
665
|
+
console.log(" " + ui.muted(" ~/.z0gcode/skills/<name>.md (global) or .z0g/skills/<name>.md (project)"));
|
|
666
|
+
console.log(" " + ui.muted("Enabled skills are offered to the agent; load one with the read_skill tool."));
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
for (const s of skills) {
|
|
670
|
+
const g = s.enabled ? ui.ok(ui.GLYPH.ok) : ui.muted(ui.GLYPH.open);
|
|
671
|
+
const state = s.enabled ? "" : ui.muted(" (disabled)");
|
|
672
|
+
console.log(" " + g + " " + ui.strong(s.name) + ui.muted(" · " + s.scope) + state);
|
|
673
|
+
if (s.description) console.log(" " + ui.muted(s.description));
|
|
674
|
+
}
|
|
675
|
+
console.log("");
|
|
676
|
+
console.log(" " + ui.muted("Toggle: /skills enable <name> · /skills disable <name>. The agent loads a skill with read_skill."));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function cmdSkills(cwd, arg) {
|
|
680
|
+
const parts = String(arg || "").trim().split(/\s+/).filter(Boolean);
|
|
681
|
+
const sub = parts[0];
|
|
682
|
+
if (sub === "enable" || sub === "disable") {
|
|
683
|
+
const name = parts.slice(1).join(" ");
|
|
684
|
+
if (!name) { console.log(ui.warn("Usage: /skills " + sub + " <name>")); return; }
|
|
685
|
+
if (!discoverSkills(cwd).some((s) => s.name === name)) { console.log(ui.warn("No skill named " + name)); return; }
|
|
686
|
+
setSkillEnabled(cwd, name, sub === "enable");
|
|
687
|
+
console.log(" " + ui.ok(ui.GLYPH.ok) + " skill " + ui.strong(name) + " " + (sub === "enable" ? "enabled" : "disabled"));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
printSkills(cwd);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function runVerify(cwd) {
|
|
694
|
+
const cmd = detectVerifyCmd(cwd);
|
|
695
|
+
if (!cmd) {
|
|
696
|
+
console.log(ui.warn("No verify command found (add a package.json test script or a .z0g/verify file)."));
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
console.log(ui.muted("running: " + cmd));
|
|
700
|
+
const r = await sh(cmd, cwd);
|
|
701
|
+
const head = r.code === 0 ? ui.ok(ui.GLYPH.ok + " passed") : ui.err(ui.GLYPH.no + " failed (exit " + r.code + ")");
|
|
702
|
+
console.log(head + "\n" + ui.muted(r.out.slice(0, 4000)));
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function runTask(task, flags) {
|
|
706
|
+
const cwd = resolveCwd(flags);
|
|
707
|
+
const opened = await openSession(cwd, flags);
|
|
708
|
+
if (!opened) return; // picker cancelled
|
|
709
|
+
const { id: sessionId, dir: sessionDirPath } = opened;
|
|
710
|
+
const onSig = () => { pruneEmptySync(cwd, sessionId); process.exit(130); };
|
|
711
|
+
process.once("SIGINT", onSig);
|
|
712
|
+
try {
|
|
713
|
+
// Auto-verify: a normal run becomes self-correcting when a verify command is present.
|
|
714
|
+
const verifyCmd = flags.verify || (flags.autoVerify ? detectVerifyCmd(cwd) : null);
|
|
715
|
+
if (verifyCmd) {
|
|
716
|
+
await runGoal({ client: makeClient(), objective: task, cwd, sessionId, sessionDir: sessionDirPath, allowBash: flags.auto, preferredModel: flags.model, preferredEffort: flags.effort, preferredSubagents: flags.subagents, preferredOnchain: flags.onchain, verifyCmd, maxIters: 3, history: opened.history });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const client = makeClient();
|
|
720
|
+
const mcp = await loadMcp(cwd);
|
|
721
|
+
if (mcp?.count) ui.info(`MCP: ${mcp.count} tool(s) from configured servers`);
|
|
722
|
+
const res = await runAgent({ client, task, cwd, sessionDir: sessionDirPath, allowBash: flags.auto, preferredModel: flags.model, preferredEffort: flags.effort, preferredSubagents: flags.subagents, preferredOnchain: flags.onchain, history: opened.history, mcp });
|
|
723
|
+
if (res?.messages) await saveMessages(cwd, sessionId, res.messages);
|
|
724
|
+
await mcp?.close();
|
|
725
|
+
} finally {
|
|
726
|
+
process.removeListener("SIGINT", onSig);
|
|
727
|
+
pruneEmptySync(cwd, sessionId); // drop a session that produced no messages
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async function cmdGoal(objective, flags) {
|
|
732
|
+
if (!objective) {
|
|
733
|
+
console.log(ui.warn('Usage: z0g goal "<objective>" [--verify "<cmd>"] [--auto]'));
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const client = makeClient();
|
|
737
|
+
const cwd = resolveCwd(flags);
|
|
738
|
+
const opened = await openSession(cwd, flags);
|
|
739
|
+
if (!opened) return;
|
|
740
|
+
const onSig = () => { pruneEmptySync(cwd, opened.id); process.exit(130); };
|
|
741
|
+
process.once("SIGINT", onSig);
|
|
742
|
+
try {
|
|
743
|
+
const verifyCmd = flags.verify || detectVerifyCmd(cwd);
|
|
744
|
+
if (!flags.auto) ui.info("tip: run goal with --auto so the agent can run and verify its own work.");
|
|
745
|
+
await runGoal({ client, objective, cwd, sessionId: opened.id, sessionDir: opened.dir, allowBash: flags.auto, preferredModel: flags.model, preferredEffort: flags.effort, preferredSubagents: flags.subagents, preferredOnchain: flags.onchain, verifyCmd, maxIters: 3, history: opened.history });
|
|
746
|
+
} finally {
|
|
747
|
+
process.removeListener("SIGINT", onSig);
|
|
748
|
+
pruneEmptySync(cwd, opened.id);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async function repl(flags) {
|
|
753
|
+
const client = makeClient();
|
|
754
|
+
const cwd = resolveCwd(flags);
|
|
755
|
+
const opened = await openSession(cwd, flags, { pickerOnOpen: true });
|
|
756
|
+
if (!opened) return; // cancelled the picker on open
|
|
757
|
+
let sessionId = opened.id;
|
|
758
|
+
let sessionDirPath = opened.dir;
|
|
759
|
+
let history = opened.history;
|
|
760
|
+
let model = flags.model;
|
|
761
|
+
let effort = flags.effort;
|
|
762
|
+
let subagents = flags.subagents;
|
|
763
|
+
let onchain = flags.onchain;
|
|
764
|
+
let sessTokens = { in: 0, out: 0 };
|
|
765
|
+
let priceMap = null;
|
|
766
|
+
fetchModels(client).then((all) => { priceMap = Object.fromEntries(all.map((m) => [m.id, m])); }).catch(() => {});
|
|
767
|
+
const mcp = await loadMcp(cwd);
|
|
768
|
+
if (mcp?.count) ui.info(`MCP: ${mcp.count} tool(s) from configured servers`);
|
|
769
|
+
|
|
770
|
+
// Delete a session that never persisted any messages (empty "New chat").
|
|
771
|
+
const pruneIfEmpty = async (id) => {
|
|
772
|
+
const s = listSessions(cwd).find((x) => x.id === id);
|
|
773
|
+
if (s && s.messageCount === 0) {
|
|
774
|
+
try { await deleteSession(cwd, id); } catch {}
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
const sessTitle = (id) => listSessions(cwd).find((s) => s.id === id)?.title || "New chat";
|
|
778
|
+
|
|
779
|
+
const promptStr = ui.strong("z0g") + ui.accent(" " + ui.GLYPH.chevron) + " ";
|
|
780
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: promptStr, completer: slashCompleter });
|
|
781
|
+
let pendingModels = null; // when set, the next line is a /model selection
|
|
782
|
+
const activeModel = () => model || CONFIG.model;
|
|
783
|
+
const activeEffort = () => (effort === "" ? null : (effort || CONFIG.effort));
|
|
784
|
+
const activeOnchain = () => (onchain !== undefined ? onchain : CONFIG.onchain);
|
|
785
|
+
const costOf = () => {
|
|
786
|
+
const m = priceMap && priceMap[activeModel()];
|
|
787
|
+
if (!m || m.inPerM == null) return null;
|
|
788
|
+
return (sessTokens.in / 1e6) * m.inPerM + (sessTokens.out / 1e6) * (m.outPerM || 0);
|
|
789
|
+
};
|
|
790
|
+
// Divider + session token/cost counter, then the z0g prompt.
|
|
791
|
+
const showPrompt = () => {
|
|
792
|
+
console.log(ui.sessionBar({ model: activeModel(), effort: activeEffort(), inTok: sessTokens.in, outTok: sessTokens.out, cost: costOf() }));
|
|
793
|
+
rl.prompt();
|
|
794
|
+
};
|
|
795
|
+
const customCmds = loadCustomCommands(cwd);
|
|
796
|
+
const customMap = Object.fromEntries(customCmds.map((c) => [c.name, c]));
|
|
797
|
+
extraSlash = customCmds.map((c) => "/" + c.name);
|
|
798
|
+
const hooks = loadHooks(cwd);
|
|
799
|
+
// Run one agent turn: hooks, the agent, then persist history + tokens.
|
|
800
|
+
const runTurn = async (task) => {
|
|
801
|
+
await runHooks(cwd, "preRun", hooks, flags.auto, task);
|
|
802
|
+
const res = await runAgent({ client, task, cwd, sessionDir: sessionDirPath, allowBash: flags.auto, preferredModel: model, preferredEffort: effort, preferredSubagents: subagents, preferredOnchain: activeOnchain(), history, mcp });
|
|
803
|
+
history = res.messages;
|
|
804
|
+
if (res.usageTotal) { sessTokens.in += res.usageTotal.prompt || 0; sessTokens.out += res.usageTotal.completion || 0; }
|
|
805
|
+
await saveMessages(cwd, sessionId, history);
|
|
806
|
+
await runHooks(cwd, "postRun", hooks, flags.auto, task);
|
|
807
|
+
};
|
|
808
|
+
ui.info("Interactive session. Type a task, or / then Tab for commands.");
|
|
809
|
+
if (customCmds.length) ui.info(customCmds.length + " custom command(s): " + customCmds.map((c) => "/" + c.name).join(", "));
|
|
810
|
+
if (hasHooks(hooks) && !flags.auto) ui.info("hooks are configured; run with --auto to enable them");
|
|
811
|
+
// Blinking block cursor at the prompt (matches the demo); restore on any exit.
|
|
812
|
+
ui.cursorBlink(true);
|
|
813
|
+
process.once("exit", () => ui.cursorBlink(false));
|
|
814
|
+
showPrompt();
|
|
815
|
+
for await (const line of rl) {
|
|
816
|
+
const input = line.trim();
|
|
817
|
+
if (!input) { pendingModels = null; showPrompt(); continue; }
|
|
818
|
+
|
|
819
|
+
// Resolve a pending /model pick (a slash command instead cancels it).
|
|
820
|
+
if (pendingModels && !input.startsWith("/")) {
|
|
821
|
+
const list = pendingModels;
|
|
822
|
+
pendingModels = null;
|
|
823
|
+
const n = Number.parseInt(input, 10);
|
|
824
|
+
let chosen = null;
|
|
825
|
+
if (!Number.isNaN(n) && n >= 1 && n <= list.length) chosen = list[n - 1];
|
|
826
|
+
else if (list.includes(input)) chosen = input;
|
|
827
|
+
if (chosen) { model = chosen; saveSetting("model", chosen); console.log(ui.pickConfirm(chosen)); }
|
|
828
|
+
else ui.info("model unchanged");
|
|
829
|
+
showPrompt();
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
if (pendingModels) pendingModels = null;
|
|
833
|
+
|
|
834
|
+
if (input.startsWith("/")) {
|
|
835
|
+
const [cmd, ...rest] = input.slice(1).split(/\s+/);
|
|
836
|
+
const arg = rest.join(" ");
|
|
837
|
+
if (cmd === "exit" || cmd === "quit") break;
|
|
838
|
+
else if (cmd === "help" || cmd === "") console.log(slashMenu());
|
|
839
|
+
else if (cmd === "clear") {
|
|
840
|
+
if (Array.isArray(history) && history.length) await saveMessages(cwd, sessionId, history);
|
|
841
|
+
await pruneIfEmpty(sessionId);
|
|
842
|
+
const s = await createSession(cwd, {});
|
|
843
|
+
sessionId = s.id; sessionDirPath = s.dir; history = null;
|
|
844
|
+
ui.info("context cleared");
|
|
845
|
+
}
|
|
846
|
+
else if (cmd === "model") {
|
|
847
|
+
if (arg) { model = arg; saveSetting("model", arg); console.log(ui.pickConfirm(arg)); }
|
|
848
|
+
else {
|
|
849
|
+
const cur = model || CONFIG.model;
|
|
850
|
+
const models = await chatModelsFor(client, cur);
|
|
851
|
+
if (models && models.length) {
|
|
852
|
+
if (interactiveTTY()) {
|
|
853
|
+
rl.pause();
|
|
854
|
+
const chosen = await arrowSelect({
|
|
855
|
+
items: models,
|
|
856
|
+
initialIndex: Math.max(0, models.findIndex((m) => m.id === cur)),
|
|
857
|
+
renderFrame: (its, i) => ui.modelPickerFrame(its, i, cur),
|
|
858
|
+
clearOnExit: true,
|
|
859
|
+
});
|
|
860
|
+
rl.resume();
|
|
861
|
+
if (chosen) { model = chosen.id; saveSetting("model", chosen.id); console.log(ui.pickConfirm(chosen.id)); }
|
|
862
|
+
else ui.info("model unchanged");
|
|
863
|
+
} else {
|
|
864
|
+
console.log(ui.renderModelsPickList(models, cur));
|
|
865
|
+
pendingModels = models.map((m) => m.id);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
else if (cmd === "effort") {
|
|
871
|
+
const a = arg.toLowerCase().trim();
|
|
872
|
+
if (!a) {
|
|
873
|
+
const cur = effort === "" ? null : (effort || CONFIG.effort);
|
|
874
|
+
ui.info("effort: " + (cur || "model default") + " · valid: " + EFFORT_LEVELS.join(", ") + ", default");
|
|
875
|
+
} else if (EFFORT_LEVELS.includes(a)) {
|
|
876
|
+
effort = a; saveSetting("effort", a); ui.info("effort set to " + a + " (saved)");
|
|
877
|
+
} else if (["default", "off", "none", "unset", "model"].includes(a)) {
|
|
878
|
+
effort = ""; saveSetting("effort", undefined); ui.info("effort: model default (saved)");
|
|
879
|
+
} else {
|
|
880
|
+
console.log(ui.warn("invalid effort. valid: " + EFFORT_LEVELS.join(", ") + ", default"));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else if (cmd === "subagents") {
|
|
884
|
+
const a = arg.toLowerCase().trim();
|
|
885
|
+
if (a === "on" || a === "off") {
|
|
886
|
+
subagents = a === "on"; saveSetting("subagents", subagents);
|
|
887
|
+
ui.info("subagents " + (subagents ? "on" : "off") + " (saved)");
|
|
888
|
+
} else if (!a) {
|
|
889
|
+
const cur = subagents !== undefined ? subagents : CONFIG.subagents;
|
|
890
|
+
ui.info("subagents: " + (cur ? "on" : "off") + " · usage: /subagents on|off");
|
|
891
|
+
} else {
|
|
892
|
+
console.log(ui.warn("usage: /subagents on|off"));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else if (cmd === "onchain") {
|
|
896
|
+
const a = arg.toLowerCase().trim();
|
|
897
|
+
if (a === "on" || a === "off") {
|
|
898
|
+
onchain = a === "on"; saveSetting("onchain", onchain);
|
|
899
|
+
ui.info("on-chain " + (onchain ? "on" : "off") + " (saved)" + (onchain && !process.env.ZOG_WALLET_KEY ? " · set ZOG_WALLET_KEY to a funded key" : ""));
|
|
900
|
+
} else if (!a) {
|
|
901
|
+
ui.info("on-chain: " + (activeOnchain() ? "on" : "off") + " · gas-spending (Storage/Chain/anchor) · usage: /onchain on|off");
|
|
902
|
+
} else {
|
|
903
|
+
console.log(ui.warn("usage: /onchain on|off"));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
else if (cmd === "skills") cmdSkills(cwd, arg);
|
|
907
|
+
else if (cmd === "chats") {
|
|
908
|
+
if (Array.isArray(history) && history.length) await saveMessages(cwd, sessionId, history);
|
|
909
|
+
if (interactiveTTY()) {
|
|
910
|
+
rl.pause();
|
|
911
|
+
const chosen = await sessionPickerLoop(cwd, sessionId);
|
|
912
|
+
rl.resume();
|
|
913
|
+
if (chosen && chosen !== "__cancel__") {
|
|
914
|
+
if (chosen !== sessionId) await pruneIfEmpty(sessionId); // never prune the one we keep
|
|
915
|
+
if (chosen === "__new__") {
|
|
916
|
+
const s = await createSession(cwd, {});
|
|
917
|
+
sessionId = s.id; sessionDirPath = s.dir; history = null;
|
|
918
|
+
ui.info("new chat");
|
|
919
|
+
} else {
|
|
920
|
+
sessionId = chosen; sessionDirPath = sessionDir(cwd, chosen);
|
|
921
|
+
history = await readMessages(cwd, chosen);
|
|
922
|
+
ui.info("switched to: " + sessTitle(sessionId));
|
|
923
|
+
}
|
|
924
|
+
} else if (!listSessions(cwd).some((s) => s.id === sessionId)) {
|
|
925
|
+
// Active session was deleted inside the picker, then cancelled: re-point.
|
|
926
|
+
const rid = mostRecent(cwd);
|
|
927
|
+
if (rid) {
|
|
928
|
+
sessionId = rid; sessionDirPath = sessionDir(cwd, rid);
|
|
929
|
+
history = await readMessages(cwd, rid);
|
|
930
|
+
ui.info("switched to: " + sessTitle(sessionId));
|
|
931
|
+
} else {
|
|
932
|
+
const s = await createSession(cwd, {});
|
|
933
|
+
sessionId = s.id; sessionDirPath = s.dir; history = null;
|
|
934
|
+
ui.info("new chat");
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} else ui.info("switching sessions needs an interactive terminal");
|
|
938
|
+
}
|
|
939
|
+
else if (cmd === "new") {
|
|
940
|
+
if (Array.isArray(history) && history.length) await saveMessages(cwd, sessionId, history);
|
|
941
|
+
await pruneIfEmpty(sessionId);
|
|
942
|
+
const s = await createSession(cwd, { title: arg || "" });
|
|
943
|
+
sessionId = s.id; sessionDirPath = s.dir; history = null;
|
|
944
|
+
ui.info("new chat" + (arg ? ": " + arg : ""));
|
|
945
|
+
}
|
|
946
|
+
else if (cmd === "rename") {
|
|
947
|
+
if (arg) { await renameSession(cwd, sessionId, arg); ui.info("renamed to: " + arg); }
|
|
948
|
+
else ui.info("usage: /rename <title>");
|
|
949
|
+
}
|
|
950
|
+
else if (cmd === "attest") await printAttest(sessionDirPath);
|
|
951
|
+
else if (cmd === "share") {
|
|
952
|
+
if (Array.isArray(history) && history.length) await saveMessages(cwd, sessionId, history);
|
|
953
|
+
await cmdShare({ ...flags, anchor: /anchor/.test(arg) || flags.anchor, onchain: activeOnchain() }, sessionId);
|
|
954
|
+
}
|
|
955
|
+
else if (cmd === "mint") {
|
|
956
|
+
if (Array.isArray(history) && history.length) await saveMessages(cwd, sessionId, history);
|
|
957
|
+
await cmdMint({ ...flags, onchain: activeOnchain() }, sessionId);
|
|
958
|
+
}
|
|
959
|
+
else if (cmd === "pull") await cmdPull({ ...flags, import: /import/.test(arg) }, rest[0]);
|
|
960
|
+
else if (cmd === "init") await cmdInit({ ...flags, force: /force|-f/.test(arg) });
|
|
961
|
+
else if (cmd === "undo") await cmdUndo(flags, sessionId);
|
|
962
|
+
else if (cmd === "checkpoints") await cmdCheckpoints(flags, sessionId);
|
|
963
|
+
else if (cmd === "plan") { const p = await loadPlan(sessionDirPath); if (p) ui.renderPlan(p); else ui.info("no plan yet"); }
|
|
964
|
+
else if (cmd === "verify") await runVerify(cwd);
|
|
965
|
+
else if (cmd === "commands") {
|
|
966
|
+
if (!customCmds.length) ui.info("no custom commands. Add .z0g/commands/<name>.md to create /<name>");
|
|
967
|
+
else { console.log(ui.section("Custom commands", customCmds.length + " loaded")); for (const c of customCmds) console.log(" " + ui.accent("/" + c.name) + " " + ui.muted(c.description)); }
|
|
968
|
+
}
|
|
969
|
+
else if (cmd === "goal") {
|
|
970
|
+
await runGoal({ client, objective: arg, cwd, sessionId, sessionDir: sessionDirPath, allowBash: flags.auto, preferredModel: model, preferredEffort: effort, preferredSubagents: subagents, preferredOnchain: activeOnchain(), verifyCmd: flags.verify || detectVerifyCmd(cwd), maxIters: 3, history });
|
|
971
|
+
history = await readMessages(cwd, sessionId) || history;
|
|
972
|
+
}
|
|
973
|
+
else if (customMap[cmd]) {
|
|
974
|
+
const task = expandTemplate(customMap[cmd].template, arg);
|
|
975
|
+
await runTurn(task);
|
|
976
|
+
}
|
|
977
|
+
else ui.info("unknown command; /help for the list");
|
|
978
|
+
showPrompt();
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
await runTurn(input);
|
|
983
|
+
showPrompt();
|
|
984
|
+
}
|
|
985
|
+
rl.close();
|
|
986
|
+
ui.cursorBlink(false);
|
|
987
|
+
await pruneIfEmpty(sessionId);
|
|
988
|
+
await mcp?.close();
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
async function main() {
|
|
992
|
+
const { flags, positional } = parse(process.argv.slice(2));
|
|
993
|
+
if (flags.help) { console.log(helpText()); return; }
|
|
994
|
+
if (flags.version) {
|
|
995
|
+
let v = "";
|
|
996
|
+
try { v = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version; } catch {}
|
|
997
|
+
console.log("z0gcode " + (v || "0.2.0"));
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const sub = positional[0];
|
|
1002
|
+
try {
|
|
1003
|
+
if (sub === "models") return await cmdModels(flags);
|
|
1004
|
+
if (sub === "image") return await cmdImage(positional[1], positional[2], flags);
|
|
1005
|
+
if (sub === "transcribe") return await cmdTranscribe(positional[1], flags);
|
|
1006
|
+
if (sub === "init") return await cmdInit(flags);
|
|
1007
|
+
if (sub === "undo") return await cmdUndo(flags);
|
|
1008
|
+
if (sub === "checkpoints") return await cmdCheckpoints(flags);
|
|
1009
|
+
if (sub === "share") return await cmdShare(flags);
|
|
1010
|
+
if (sub === "pull") return await cmdPull(flags, positional[1]);
|
|
1011
|
+
if (sub === "mint") return await cmdMint(flags);
|
|
1012
|
+
if (sub === "skills") return cmdSkills(resolveCwd(flags), positional.slice(1).join(" "));
|
|
1013
|
+
if (sub === "doctor") return await cmdDoctor();
|
|
1014
|
+
if (sub === "attest") {
|
|
1015
|
+
const acwd = resolveCwd(flags);
|
|
1016
|
+
await migrateLegacy(acwd);
|
|
1017
|
+
const id = mostRecent(acwd);
|
|
1018
|
+
return await printAttest(id ? sessionDir(acwd, id) : path.join(acwd, ".z0g"));
|
|
1019
|
+
}
|
|
1020
|
+
if (sub === "serve") {
|
|
1021
|
+
if (flags.mcp) {
|
|
1022
|
+
const { startMcpServer } = await import("../src/mcp-server.mjs");
|
|
1023
|
+
await startMcpServer({ cwd: resolveCwd(flags), allowBash: flags.auto });
|
|
1024
|
+
return; // stays alive serving the stdio MCP transport
|
|
1025
|
+
}
|
|
1026
|
+
console.log("Usage: z0g serve --mcp");
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (sub === "goal") {
|
|
1030
|
+
ui.banner(flags.model || CONFIG.model, CONFIG.baseURL);
|
|
1031
|
+
return await cmdGoal(positional.slice(1).join(" "), flags);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const task = sub === "run" ? positional.slice(1).join(" ") : positional.join(" ");
|
|
1035
|
+
if (!task) {
|
|
1036
|
+
if (process.stdin.isTTY) {
|
|
1037
|
+
await ui.bannerAnimated(flags.model || CONFIG.model, CONFIG.baseURL);
|
|
1038
|
+
return await repl(flags);
|
|
1039
|
+
}
|
|
1040
|
+
ui.banner(flags.model || CONFIG.model, CONFIG.baseURL);
|
|
1041
|
+
console.log(helpText());
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
ui.banner(flags.model || CONFIG.model, CONFIG.baseURL);
|
|
1045
|
+
await runTask(task, flags);
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
ui.error(e.message);
|
|
1048
|
+
process.exitCode = 1;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
main();
|