@earntori/cli 0.2.5 → 0.2.6
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/dist/index.js +67 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ function out(o, human) { console.log(json ? JSON.stringify(o, null, 2) : human);
|
|
|
55
55
|
function die(msg) { console.error(json ? JSON.stringify({ error: msg }) : `\u2717 ${msg}`); process.exit(1); }
|
|
56
56
|
const usd = (c) => `$${(c / 100).toFixed(2)}`;
|
|
57
57
|
async function api(cfg, method, path, body) {
|
|
58
|
-
const r = await fetch(`${cfg.base}${path}`, { method, headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": "@earntori/cli/0.2.
|
|
58
|
+
const r = await fetch(`${cfg.base}${path}`, { method, headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": "@earntori/cli/0.2.6" }, body: body ? JSON.stringify(body) : undefined });
|
|
59
59
|
const t = await r.text();
|
|
60
60
|
let j = null;
|
|
61
61
|
try {
|
|
@@ -157,6 +157,63 @@ catch {
|
|
|
157
157
|
return null;
|
|
158
158
|
} }
|
|
159
159
|
function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true }); writeFileSync(p, JSON.stringify(o, null, 2) + "\n"); }
|
|
160
|
+
const withHarness = (mcp, id) => ({ ...mcp, env: { TORI_HARNESS: id } });
|
|
161
|
+
function mergeMcpJson(file, mcp) { const j = readJson(file) ?? {}; j.mcpServers = { ...(j.mcpServers ?? {}), tori: mcp }; writeJson(file, j); return file; }
|
|
162
|
+
function hookPresent(h, c) { return typeof h?.command === "string" && (h.command.includes("@earntori/cli check") || (c.local && h.command.includes(c.cliBin))); }
|
|
163
|
+
function ensureHook(hooks, event, entry, c) {
|
|
164
|
+
const arr = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
165
|
+
if (!arr.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => hookPresent(h, c))))
|
|
166
|
+
arr.push(entry);
|
|
167
|
+
hooks[event] = arr;
|
|
168
|
+
}
|
|
169
|
+
const HARNESSES = [
|
|
170
|
+
{
|
|
171
|
+
id: "claude-code", label: "Claude Code", detect: () => true,
|
|
172
|
+
install: (c) => {
|
|
173
|
+
const wrote = [mergeMcpJson(".mcp.json", withHarness(c.mcp, "claude-code"))];
|
|
174
|
+
const sfile = join(".claude", "settings.json");
|
|
175
|
+
const s = readJson(sfile) ?? {};
|
|
176
|
+
s.hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : {};
|
|
177
|
+
ensureHook(s.hooks, "Stop", { hooks: [{ type: "command", command: `${c.cli} check --hook` }] }, c);
|
|
178
|
+
ensureHook(s.hooks, "SessionStart", { hooks: [{ type: "command", command: `${c.cli} check --reminder` }] }, c);
|
|
179
|
+
writeJson(sfile, s);
|
|
180
|
+
wrote.push(sfile);
|
|
181
|
+
return wrote;
|
|
182
|
+
},
|
|
183
|
+
after: "Claude Code: restart the session (hooks and .mcp.json load at start).",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "cursor", label: "Cursor", detect: () => true,
|
|
187
|
+
install: (c) => [mergeMcpJson(join(".cursor", "mcp.json"), withHarness(c.mcp, "cursor"))],
|
|
188
|
+
after: "Cursor: reload the window.",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "codex", label: "Codex",
|
|
192
|
+
detect: () => !has("no-codex") && (has("codex") || existsSync(".codex") || existsSync(join(homedir(), ".codex"))),
|
|
193
|
+
install: (c) => {
|
|
194
|
+
const wrote = [];
|
|
195
|
+
// .codex/config.toml — append the [mcp_servers.tori] table if it is not already there.
|
|
196
|
+
const tfile = join(".codex", "config.toml");
|
|
197
|
+
const toml = existsSync(tfile) ? readFileSync(tfile, "utf8") : "";
|
|
198
|
+
if (!/\[mcp_servers\.tori\]/.test(toml)) {
|
|
199
|
+
const block = `\n[mcp_servers.tori]\ncommand = ${JSON.stringify(c.mcp.command)}\nargs = ${JSON.stringify(c.mcp.args)}\n\n[mcp_servers.tori.env]\nTORI_HARNESS = "codex"\n`;
|
|
200
|
+
mkdirSync(".codex", { recursive: true });
|
|
201
|
+
writeFileSync(tfile, (toml.trimEnd() + "\n" + block).replace(/^\n/, ""));
|
|
202
|
+
}
|
|
203
|
+
wrote.push(tfile);
|
|
204
|
+
// .codex/hooks.json — same two hooks; Stop uses exit 2 + stderr to continue the turn, SessionStart prints to stdout.
|
|
205
|
+
const hfile = join(".codex", "hooks.json");
|
|
206
|
+
const h = readJson(hfile) ?? {};
|
|
207
|
+
h.hooks = h.hooks && typeof h.hooks === "object" ? h.hooks : {};
|
|
208
|
+
ensureHook(h.hooks, "SessionStart", { matcher: "startup|resume", hooks: [{ type: "command", command: `${c.cli} check --reminder`, timeout: 30 }] }, c);
|
|
209
|
+
ensureHook(h.hooks, "Stop", { hooks: [{ type: "command", command: `${c.cli} check --hook`, timeout: 120 }] }, c);
|
|
210
|
+
writeJson(hfile, h);
|
|
211
|
+
wrote.push(hfile);
|
|
212
|
+
return wrote;
|
|
213
|
+
},
|
|
214
|
+
after: "Codex: trust the project, then /mcp to confirm tori and /hooks once to trust the two hooks.",
|
|
215
|
+
},
|
|
216
|
+
];
|
|
160
217
|
function init() {
|
|
161
218
|
const pin = flag("base");
|
|
162
219
|
if (pin)
|
|
@@ -169,29 +226,17 @@ function init() {
|
|
|
169
226
|
const mcpBin = resolve(dirname(cliBin), "..", "..", "mcp", "dist", "index.js");
|
|
170
227
|
if (local && !existsSync(mcpBin))
|
|
171
228
|
die(`--local: MCP build not found at ${mcpBin} (run: pnpm --filter @earntori/mcp build)`);
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
const s = readJson(sfile) ?? {};
|
|
178
|
-
s.hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : {};
|
|
179
|
-
const ensure = (event, command) => {
|
|
180
|
-
const arr = Array.isArray(s.hooks[event]) ? s.hooks[event] : [];
|
|
181
|
-
const present = arr.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => typeof h?.command === "string" && h.command.includes("@earntori/cli check") || (local && typeof h?.command === "string" && h.command.includes(cliBin))));
|
|
182
|
-
if (!present)
|
|
183
|
-
arr.push({ hooks: [{ type: "command", command }] });
|
|
184
|
-
s.hooks[event] = arr;
|
|
185
|
-
};
|
|
186
|
-
ensure("Stop", `${cli} check --hook`);
|
|
187
|
-
ensure("SessionStart", `${cli} check --reminder`);
|
|
188
|
-
writeJson(sfile, s);
|
|
189
|
-
wrote.push(sfile);
|
|
229
|
+
const ctx = { mcp: local ? { command: "node", args: [mcpBin] } : { command: "npx", args: ["-y", "@earntori/mcp"] }, cli: local ? `node ${JSON.stringify(cliBin)}` : "npx -y @earntori/cli", local, cliBin };
|
|
230
|
+
const set = HARNESSES.filter((h) => h.detect());
|
|
231
|
+
const wrote = [];
|
|
232
|
+
for (const h of set)
|
|
233
|
+
wrote.push(...h.install(ctx));
|
|
190
234
|
if (pin)
|
|
191
235
|
wrote.push(join(".tori", "project.json"));
|
|
192
|
-
out({ ok: true, wrote, local }, `\u2713 Tori installed in this project${local ? " (local builds)" : ""}${pin ? ` \u2014 pinned to ${pin}` : ""}\n` +
|
|
236
|
+
out({ ok: true, harnesses: set.map((h) => h.id), wrote, local }, `\u2713 Tori installed in this project for ${set.map((h) => h.label).join(", ")}${local ? " (local builds)" : ""}${pin ? ` \u2014 pinned to ${pin}` : ""}\n` +
|
|
193
237
|
wrote.map((w) => ` ${w}`).join("\n") +
|
|
194
|
-
`\n\
|
|
238
|
+
`\n\n${set.map((h) => h.after).join(" ")}\nTori's checks now run when the agent finishes a turn; findings are shown to the agent before it stops.` +
|
|
239
|
+
(set.some((h) => h.id === "codex") ? "" : "\n(Codex not detected \u2014 run `tori init --codex` to set it up too.)"));
|
|
195
240
|
}
|
|
196
241
|
const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|php|java|kt|cs)$/i;
|
|
197
242
|
function projectId() {
|
|
@@ -393,7 +438,7 @@ async function connector() {
|
|
|
393
438
|
}
|
|
394
439
|
function logout() { if (existsSync(CONFIG))
|
|
395
440
|
unlinkSync(CONFIG); out({ ok: true }, "\u2713 Signed out"); }
|
|
396
|
-
function help() { console.log(`tori \u2014 Tori CLI\n\n tori login [--base URL] sign in with GitHub (device flow)\n tori status [--json] balance + your AI keys\n tori key --limit 5 [--write env] [--file .env] [--json]\n tori stats [--json] your integration + developer program numbers\n tori init [--local]
|
|
441
|
+
function help() { console.log(`tori \u2014 Tori CLI\n\n tori login [--base URL] sign in with GitHub (device flow)\n tori status [--json] balance + your AI keys\n tori key --limit 5 [--write env] [--file .env] [--json]\n tori stats [--json] your integration + developer program numbers\n tori init [--local] [--codex|--no-codex] wire Tori into this project (Claude Code, Cursor, Codex)\n tori check [paths...] [--json] run Tori's checks on changed files\n tori ledger [--project X|all] [--type a,b] [--since ISO] [--limit N] [--json]\n tori receipt <event_id|seq> fetch a receipt and verify it locally\n tori connector new [--label X] | list | revoke <id> Tori as a remote MCP connector (Claude Cowork etc.)\n tori init --base https://sandbox.earntori.com pin this repo to a Tori environment (.tori/project.json)\n tori logout\n`); }
|
|
397
442
|
(async () => {
|
|
398
443
|
switch (cmd) {
|
|
399
444
|
case "login": return login();
|