@earntori/cli 0.2.16 → 0.2.18

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 +89 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -243,8 +243,14 @@ const HARNESSES = [
243
243
  ];
244
244
  async function init() {
245
245
  const pin = flag("base");
246
- if (pin)
247
- writeJson(join(".tori", "project.json"), { base: pin.replace(/\/$/, "") });
246
+ if (pin) {
247
+ let prev = {};
248
+ try {
249
+ prev = JSON.parse(readFileSync(join(".tori", "project.json"), "utf8"));
250
+ }
251
+ catch { }
252
+ writeJson(join(".tori", "project.json"), { ...prev, base: pin.replace(/\/$/, "") });
253
+ }
248
254
  let cfg = loadCfg();
249
255
  if (!cfg) {
250
256
  console.log(pin ? `Not signed in for ${pin} \u2014 signing in first` : "Not signed in \u2014 signing in first");
@@ -283,7 +289,8 @@ async function init() {
283
289
  (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("; ") + ".)") + next);
284
290
  }
285
291
  function repoIndex() { try {
286
- const out = execSync("git ls-files", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split("\n").filter(Boolean);
292
+ const pats = ignorePatterns(process.cwd());
293
+ const out = execSync("git ls-files", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split("\n").filter(Boolean).filter((p) => !isIgnored(p, pats));
287
294
  return out.length ? out.slice(0, 5000) : undefined;
288
295
  }
289
296
  catch {
@@ -305,11 +312,51 @@ function projectId() {
305
312
  catch { }
306
313
  return "local:" + createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12);
307
314
  }
315
+ /** Spec rev 86: paths never sent to Tori. `ignore` in .tori/project.json is a list of glob patterns
316
+ * (`fixtures/`, `seeds/**`, `*.snap`, `scratch`). A pattern with no slash matches at any depth. */
317
+ function ignorePatterns(root) {
318
+ try {
319
+ const j = JSON.parse(readFileSync(join(root, ".tori", "project.json"), "utf8"));
320
+ return Array.isArray(j?.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
321
+ }
322
+ catch {
323
+ return [];
324
+ }
325
+ }
326
+ function globToRe(g) {
327
+ let p = g.trim().replace(/^\.\//, "").replace(/\/+$/, "");
328
+ let re = "";
329
+ for (let i = 0; i < p.length; i++) {
330
+ const c = p[i];
331
+ if (c === "*") {
332
+ if (p[i + 1] === "*") {
333
+ re += ".*";
334
+ i++;
335
+ if (p[i + 1] === "/")
336
+ i++;
337
+ }
338
+ else
339
+ re += "[^/]*";
340
+ }
341
+ else if (c === "?")
342
+ re += "[^/]";
343
+ else
344
+ re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
345
+ }
346
+ return new RegExp((p.includes("/") ? "^" : "(^|/)") + re + "(/|$)");
347
+ }
348
+ function isIgnored(rel, pats) {
349
+ if (!pats.length)
350
+ return false;
351
+ const r = rel.replace(/\\/g, "/").replace(/^\.\//, "");
352
+ return pats.some((g) => globToRe(g).test(r));
353
+ }
308
354
  function gitChanged() {
309
355
  try {
310
356
  const o = execSync("git status --porcelain --untracked-files=all", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
311
357
  const SKIP = /(^|\/)(node_modules|dist|build|out|\.next|\.git|coverage|vendor|\.venv|venv|__pycache__|\.turbo|\.cache)(\/|$)/;
312
- return o.split("\n").map((l) => l.slice(3).trim()).filter(Boolean).map((p) => (p.includes(" -> ") ? p.split(" -> ")[1] : p)).filter((p) => !SKIP.test(p)).slice(0, 200);
358
+ const pats = ignorePatterns(process.cwd());
359
+ return o.split("\n").map((l) => l.slice(3).trim()).filter(Boolean).map((p) => (p.includes(" -> ") ? p.split(" -> ")[1] : p)).filter((p) => !SKIP.test(p) && !isIgnored(p, pats)).slice(0, 200);
313
360
  }
314
361
  catch {
315
362
  return [];
@@ -317,10 +364,11 @@ function gitChanged() {
317
364
  }
318
365
  function readCode(paths) {
319
366
  const files = [];
367
+ const pats = ignorePatterns(process.cwd());
320
368
  for (const p of paths) {
321
369
  const abs = isAbsolute(p) ? p : resolve(p);
322
370
  const rel = relative(process.cwd(), abs);
323
- if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs))
371
+ if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs) || isIgnored(rel, pats))
324
372
  continue;
325
373
  try {
326
374
  const c = readFileSync(abs, "utf8");
@@ -593,13 +641,48 @@ function logout() {
593
641
  unlinkSync(join(SESSIONS_DIR, f));
594
642
  out({ ok: true }, "\u2713 Signed out (all sessions removed)");
595
643
  }
596
- 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`); }
644
+ 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 ignore add <glob…> | list | remove <glob> paths never sent to Tori (fixtures/, seeds/**, scratch); in .tori/project.json\n tori init --base https://sandbox.earntori.com pin this repo to a Tori environment (.tori/project.json)\n tori logout\n`); }
645
+ function ignoreCmd() {
646
+ const sub = args[1];
647
+ const pfile = join(".tori", "project.json");
648
+ let j = {};
649
+ try {
650
+ j = JSON.parse(readFileSync(pfile, "utf8"));
651
+ }
652
+ catch { }
653
+ const cur = Array.isArray(j.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
654
+ if (sub === "list" || !sub) {
655
+ if (has("json"))
656
+ return out({ ignore: cur }, cur.join("\n"));
657
+ console.log(cur.length ? cur.join("\n") : "No ignore patterns. Add one: tori ignore add fixtures/");
658
+ return;
659
+ }
660
+ const pats = args.slice(2).filter((a) => !a.startsWith("--")).map((p) => p.trim()).filter(Boolean);
661
+ if (sub === "add") {
662
+ if (!pats.length)
663
+ die("Usage: tori ignore add <glob> [<glob>…]");
664
+ const next = Array.from(new Set([...cur, ...pats]));
665
+ writeJson(pfile, { ...j, ignore: next });
666
+ console.log(`Ignoring ${next.length} pattern(s) in this project; never sent to Tori:\n` + next.join("\n"));
667
+ return;
668
+ }
669
+ if (sub === "remove") {
670
+ if (!pats.length)
671
+ die("Usage: tori ignore remove <glob>");
672
+ const next = cur.filter((p) => !pats.includes(p));
673
+ writeJson(pfile, { ...j, ignore: next });
674
+ console.log(next.length ? `Ignoring ${next.length} pattern(s):\n` + next.join("\n") : "No ignore patterns.");
675
+ return;
676
+ }
677
+ die("Usage: tori ignore add <glob…> | list | remove <glob>");
678
+ }
597
679
  (async () => {
598
680
  switch (cmd) {
599
681
  case "login": return login();
600
682
  case "status": return status();
601
683
  case "init": return init();
602
684
  case "check": return check();
685
+ case "ignore": return ignoreCmd();
603
686
  case "ledger": return ledger();
604
687
  case "receipt": return receipt();
605
688
  case "connector": return connector();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earntori/cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Tori CLI — holds your coding agent to a standard: checks before it calls a task done, each finding with its fix, on a signed record you can verify offline.",
5
5
  "license": "MIT",
6
6
  "type": "module",