@earntori/cli 0.2.10 → 0.2.12
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 +48 -9
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readdirSync, realpathSync } from "node:fs";
|
|
2
3
|
// @earntori/cli — tori login | status | key --limit N [--write env] | stats | logout
|
|
3
4
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, appendFileSync, unlinkSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
@@ -157,18 +158,37 @@ catch {
|
|
|
157
158
|
return null;
|
|
158
159
|
} }
|
|
159
160
|
function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true }); writeFileSync(p, JSON.stringify(o, null, 2) + "\n"); }
|
|
161
|
+
// Harness registry: one entry per coding harness. `detect` decides whether `tori init` sets it up
|
|
162
|
+
// (Claude Code and Cursor: always; Codex: when it is installed or present in the repo, or --codex);
|
|
163
|
+
// `install` writes that harness's files and returns their paths. Add a harness = add a row.
|
|
164
|
+
// Pinned package versions written into agent config (B1b). CLI version = this package's own;
|
|
165
|
+
// MCP version is a constant: bump it in the same commit as a @earntori/mcp release.
|
|
166
|
+
const TORI_MCP_VERSION = "0.1.7";
|
|
167
|
+
function cliVersion(cliBin) {
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(readFileSync(resolve(dirname(realpathSync(cliBin)), "..", "package.json"), "utf8")).version ?? "latest";
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return "latest";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
160
175
|
const withHarness = (mcp, id) => ({ ...mcp, env: { TORI_HARNESS: id } });
|
|
161
176
|
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" && (
|
|
177
|
+
function hookPresent(h, c) { return typeof h?.command === "string" && (/@earntori\/cli(@[^\s]+)? check/.test(h.command) || (c.local && h.command.includes(c.cliBin))); }
|
|
178
|
+
// Re-running init replaces an existing Tori hook group (so a new pinned version lands) and never adds a second one.
|
|
163
179
|
function ensureHook(hooks, event, entry, c) {
|
|
164
180
|
const arr = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
165
|
-
|
|
181
|
+
const i = arr.findIndex((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => hookPresent(h, c)));
|
|
182
|
+
if (i >= 0)
|
|
183
|
+
arr[i] = entry;
|
|
184
|
+
else
|
|
166
185
|
arr.push(entry);
|
|
167
186
|
hooks[event] = arr;
|
|
168
187
|
}
|
|
169
188
|
const HARNESSES = [
|
|
170
189
|
{
|
|
171
|
-
id: "claude-code", label: "Claude Code",
|
|
190
|
+
id: "claude-code", label: "Claude Code",
|
|
191
|
+
detect: () => !has("no-claude") && (has("claude") || existsSync(".claude") || existsSync(".mcp.json") || existsSync(join(homedir(), ".claude")) || existsSync(join(homedir(), ".claude.json"))),
|
|
172
192
|
install: (c) => {
|
|
173
193
|
const wrote = [mergeMcpJson(".mcp.json", withHarness(c.mcp, "claude-code"))];
|
|
174
194
|
const sfile = join(".claude", "settings.json");
|
|
@@ -183,7 +203,8 @@ const HARNESSES = [
|
|
|
183
203
|
after: "Claude Code: restart the session (hooks and .mcp.json load at start).",
|
|
184
204
|
},
|
|
185
205
|
{
|
|
186
|
-
id: "cursor", label: "Cursor",
|
|
206
|
+
id: "cursor", label: "Cursor",
|
|
207
|
+
detect: () => !has("no-cursor") && (has("cursor") || existsSync(".cursor") || existsSync(join(homedir(), ".cursor"))),
|
|
187
208
|
install: (c) => [mergeMcpJson(join(".cursor", "mcp.json"), withHarness(c.mcp, "cursor"))],
|
|
188
209
|
after: "Cursor: reload the window.",
|
|
189
210
|
},
|
|
@@ -200,6 +221,12 @@ const HARNESSES = [
|
|
|
200
221
|
mkdirSync(".codex", { recursive: true });
|
|
201
222
|
writeFileSync(tfile, (toml.trimEnd() + "\n" + block).replace(/^\n/, ""));
|
|
202
223
|
}
|
|
224
|
+
else {
|
|
225
|
+
// Existing block: rewrite its command/args so the pinned version is current.
|
|
226
|
+
const updated = toml.replace(/(\[mcp_servers\.tori\][^\[]*?)command = [^\n]*\nargs = [^\n]*/, `$1command = ${JSON.stringify(c.mcp.command)}\nargs = ${JSON.stringify(c.mcp.args)}`);
|
|
227
|
+
if (updated !== toml)
|
|
228
|
+
writeFileSync(tfile, updated);
|
|
229
|
+
}
|
|
203
230
|
wrote.push(tfile);
|
|
204
231
|
// .codex/hooks.json — same two hooks; Stop uses exit 2 + stderr to continue the turn, SessionStart prints to stdout.
|
|
205
232
|
const hfile = join(".codex", "hooks.json");
|
|
@@ -226,8 +253,11 @@ function init() {
|
|
|
226
253
|
const mcpBin = resolve(dirname(cliBin), "..", "..", "mcp", "dist", "index.js");
|
|
227
254
|
if (local && !existsSync(mcpBin))
|
|
228
255
|
die(`--local: MCP build not found at ${mcpBin} (run: pnpm --filter @earntori/mcp build)`);
|
|
229
|
-
const
|
|
256
|
+
const ver = cliVersion(cliBin);
|
|
257
|
+
const ctx = { mcp: local ? { command: "node", args: [mcpBin] } : { command: "npx", args: ["-y", `@earntori/mcp@${TORI_MCP_VERSION}`] }, cli: local ? `node ${JSON.stringify(cliBin)}` : `npx -y @earntori/cli@${ver}`, local, cliBin };
|
|
230
258
|
const set = HARNESSES.filter((h) => h.detect());
|
|
259
|
+
if (!set.length)
|
|
260
|
+
die("No coding agent detected here. Run `tori init --claude`, `--cursor` or `--codex` to set one up anyway.");
|
|
231
261
|
const wrote = [];
|
|
232
262
|
for (const h of set)
|
|
233
263
|
wrote.push(...h.install(ctx));
|
|
@@ -236,7 +266,8 @@ function init() {
|
|
|
236
266
|
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` +
|
|
237
267
|
wrote.map((w) => ` ${w}`).join("\n") +
|
|
238
268
|
`\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
|
-
(
|
|
269
|
+
(local ? "" : `\nPinned to @earntori/cli@${ver} and @earntori/mcp@${TORI_MCP_VERSION}; re-run tori init after upgrading.`) +
|
|
270
|
+
(set.length === HARNESSES.length ? "" : "\n(Not detected: " + HARNESSES.filter((h) => !set.includes(h)).map((h) => `${h.label} \u2014 tori init --${h.id === "claude-code" ? "claude" : h.id}`).join("; ") + ".)"));
|
|
240
271
|
}
|
|
241
272
|
function repoIndex() { try {
|
|
242
273
|
const out = execSync("git ls-files", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split("\n").filter(Boolean);
|
|
@@ -538,9 +569,17 @@ async function rules() {
|
|
|
538
569
|
const r = await api(cfg, "GET", `/api/v1/rules?project=${encodeURIComponent(project)}`);
|
|
539
570
|
out(r, r.rules.length ? r.rules.map((x) => ` ${x.enforced ? "enforced" : "recorded"} ${x.id} ${x.text}`).join("\n") : 'No rules for this project. Add one: tori rules add "no console.log outside dev paths"');
|
|
540
571
|
}
|
|
541
|
-
function logout() {
|
|
542
|
-
|
|
543
|
-
|
|
572
|
+
function logout() {
|
|
573
|
+
if (existsSync(CONFIG))
|
|
574
|
+
unlinkSync(CONFIG);
|
|
575
|
+
// Pinned projects keep a per-base session; signing out must remove those too (B6).
|
|
576
|
+
if (existsSync(SESSIONS_DIR))
|
|
577
|
+
for (const f of readdirSync(SESSIONS_DIR))
|
|
578
|
+
if (f.endsWith(".json"))
|
|
579
|
+
unlinkSync(join(SESSIONS_DIR, f));
|
|
580
|
+
out({ ok: true }, "\u2713 Signed out (all sessions removed)");
|
|
581
|
+
}
|
|
582
|
+
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] [--claude|--no-claude] [--cursor|--no-cursor] [--codex|--no-codex] wire Tori into this project (agents detected unless forced)\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 standards [enable|disable <id…>] [--connector <label>] standards that apply to this project, or to a connector's\n tori rules [add "<rule>"|list|remove <id>] [--connector <label>] your own rules, in plain English\n tori init --base https://sandbox.earntori.com pin this repo to a Tori environment (.tori/project.json)\n tori logout\n`); }
|
|
544
583
|
(async () => {
|
|
545
584
|
switch (cmd) {
|
|
546
585
|
case "login": return login();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earntori/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Tori CLI \u2014 sign in with GitHub, check your credits, mint a capped AI key into your tools.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"homepage": "https://www.earntori.com/docs/mcp",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/
|
|
32
|
+
"url": "git+https://github.com/usetori/tori.git"
|
|
33
33
|
},
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/GAishZOEish/Tori-MCP/issues"
|