@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.
Files changed (2) hide show
  1. package/dist/index.js +48 -9
  2. 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" && (h.command.includes("@earntori/cli check") || (c.local && h.command.includes(c.cliBin))); }
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
- if (!arr.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => hookPresent(h, c))))
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", detect: () => true,
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", detect: () => true,
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 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 };
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
- (set.some((h) => h.id === "codex") ? "" : "\n(Codex not detected \u2014 run `tori init --codex` to set it up too.)"));
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() { if (existsSync(CONFIG))
542
- unlinkSync(CONFIG); out({ ok: true }, "\u2713 Signed out"); }
543
- 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 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`); }
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.10",
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/GAishZOEish/Tori-MCP.git"
32
+ "url": "git+https://github.com/usetori/tori.git"
33
33
  },
34
34
  "bugs": {
35
35
  "url": "https://github.com/GAishZOEish/Tori-MCP/issues"