@earntori/cli 0.2.15 → 0.2.17
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 +83 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -163,7 +163,7 @@ function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true });
|
|
|
163
163
|
// `install` writes that harness's files and returns their paths. Add a harness = add a row.
|
|
164
164
|
// Pinned package versions written into agent config (B1b). CLI version = this package's own;
|
|
165
165
|
// MCP version is a constant: bump it in the same commit as a @earntori/mcp release.
|
|
166
|
-
const TORI_MCP_VERSION = "0.1.
|
|
166
|
+
const TORI_MCP_VERSION = "0.1.9";
|
|
167
167
|
function cliVersion(cliBin) {
|
|
168
168
|
try {
|
|
169
169
|
return JSON.parse(readFileSync(resolve(dirname(realpathSync(cliBin)), "..", "package.json"), "utf8")).version ?? "latest";
|
|
@@ -305,11 +305,51 @@ function projectId() {
|
|
|
305
305
|
catch { }
|
|
306
306
|
return "local:" + createHash("sha256").update(process.cwd()).digest("hex").slice(0, 12);
|
|
307
307
|
}
|
|
308
|
+
/** Spec rev 86: paths never sent to Tori. `ignore` in .tori/project.json is a list of glob patterns
|
|
309
|
+
* (`fixtures/`, `seeds/**`, `*.snap`, `scratch`). A pattern with no slash matches at any depth. */
|
|
310
|
+
function ignorePatterns(root) {
|
|
311
|
+
try {
|
|
312
|
+
const j = JSON.parse(readFileSync(join(root, ".tori", "project.json"), "utf8"));
|
|
313
|
+
return Array.isArray(j?.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function globToRe(g) {
|
|
320
|
+
let p = g.trim().replace(/^\.\//, "").replace(/\/+$/, "");
|
|
321
|
+
let re = "";
|
|
322
|
+
for (let i = 0; i < p.length; i++) {
|
|
323
|
+
const c = p[i];
|
|
324
|
+
if (c === "*") {
|
|
325
|
+
if (p[i + 1] === "*") {
|
|
326
|
+
re += ".*";
|
|
327
|
+
i++;
|
|
328
|
+
if (p[i + 1] === "/")
|
|
329
|
+
i++;
|
|
330
|
+
}
|
|
331
|
+
else
|
|
332
|
+
re += "[^/]*";
|
|
333
|
+
}
|
|
334
|
+
else if (c === "?")
|
|
335
|
+
re += "[^/]";
|
|
336
|
+
else
|
|
337
|
+
re += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
338
|
+
}
|
|
339
|
+
return new RegExp((p.includes("/") ? "^" : "(^|/)") + re + "(/|$)");
|
|
340
|
+
}
|
|
341
|
+
function isIgnored(rel, pats) {
|
|
342
|
+
if (!pats.length)
|
|
343
|
+
return false;
|
|
344
|
+
const r = rel.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
345
|
+
return pats.some((g) => globToRe(g).test(r));
|
|
346
|
+
}
|
|
308
347
|
function gitChanged() {
|
|
309
348
|
try {
|
|
310
349
|
const o = execSync("git status --porcelain --untracked-files=all", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
311
350
|
const SKIP = /(^|\/)(node_modules|dist|build|out|\.next|\.git|coverage|vendor|\.venv|venv|__pycache__|\.turbo|\.cache)(\/|$)/;
|
|
312
|
-
|
|
351
|
+
const pats = ignorePatterns(process.cwd());
|
|
352
|
+
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
353
|
}
|
|
314
354
|
catch {
|
|
315
355
|
return [];
|
|
@@ -317,10 +357,11 @@ function gitChanged() {
|
|
|
317
357
|
}
|
|
318
358
|
function readCode(paths) {
|
|
319
359
|
const files = [];
|
|
360
|
+
const pats = ignorePatterns(process.cwd());
|
|
320
361
|
for (const p of paths) {
|
|
321
362
|
const abs = isAbsolute(p) ? p : resolve(p);
|
|
322
363
|
const rel = relative(process.cwd(), abs);
|
|
323
|
-
if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs))
|
|
364
|
+
if (rel.startsWith("..") || rel.includes("node_modules/") || !CODE_EXT.test(rel) || !existsSync(abs) || isIgnored(rel, pats))
|
|
324
365
|
continue;
|
|
325
366
|
try {
|
|
326
367
|
const c = readFileSync(abs, "utf8");
|
|
@@ -336,7 +377,7 @@ function formatRun(r, n) {
|
|
|
336
377
|
return `\u2713 Tori checks clean \u2014 ${n} file(s), no open findings.`;
|
|
337
378
|
const L = [`Tori checks: ${r.findings.length} finding(s) in ${n} file(s). Fix each one, then call the checks_resolve tool with the finding_id and file path so the fix is verified.`];
|
|
338
379
|
for (const f of r.findings)
|
|
339
|
-
L.push("", `[${f.severity.toUpperCase()}] ${f.path}${f.line ? ":" + f.line : ""} (${f.rule_id})`, `finding_id: ${f.id}`, f.message, "", "How to fix:", f.fix_hint);
|
|
380
|
+
L.push("", `[${f.severity.toUpperCase()}] ${f.path}${f.line ? ":" + f.line : ""} (${f.rule_id}${f.area ? " · " + f.area : ""})`, `finding_id: ${f.id}`, f.message, "", "How to fix:", f.fix_hint);
|
|
340
381
|
if (r.waived?.length)
|
|
341
382
|
L.push("", `Waived (${r.waived.length}, not blocking): ` + r.waived.map((w) => `${w.path}${w.line ? ":" + w.line : ""} ${w.rule_id} \u2014 ${w.reason ?? "no reason"}`).join("; "));
|
|
342
383
|
if (r.reward?.issued)
|
|
@@ -539,7 +580,8 @@ async function standards() {
|
|
|
539
580
|
return;
|
|
540
581
|
}
|
|
541
582
|
const r = await api(cfg, "GET", `/api/v1/standards?project=${encodeURIComponent(project)}`);
|
|
542
|
-
const line = (s) => ` ${s.enabled ? "on " : "off"} ${s.id.padEnd(20)} ${s.name}${s.rules ? ` (${s.rules} checks)` : " (no checks yet)"}
|
|
583
|
+
const line = (s) => ` ${s.enabled ? "on " : "off"} ${s.id.padEnd(20)} ${s.name}${s.rules ? ` (${s.rules} checks)` : " (no checks yet)"}` +
|
|
584
|
+
(s.areas?.length ? `\n ${" ".repeat(20)} ${s.areas.map((a) => `${a.name} ${a.rules}`).join(" · ")}` : "");
|
|
543
585
|
out(r, `${project.startsWith("connector:") ? `Project: ${project}\n\n` : ""}Coding standards \u2014 always on:\n${r.standards.filter((s) => s.tier === "coding").map(line).join("\n")}\n\nDomain standards \u2014 choose per project:\n${r.standards.filter((s) => s.tier === "domain").map(line).join("\n")}\n\nEnable: tori standards enable <id…> Disable: tori standards disable <id…>`);
|
|
544
586
|
}
|
|
545
587
|
async function rules() {
|
|
@@ -592,13 +634,48 @@ function logout() {
|
|
|
592
634
|
unlinkSync(join(SESSIONS_DIR, f));
|
|
593
635
|
out({ ok: true }, "\u2713 Signed out (all sessions removed)");
|
|
594
636
|
}
|
|
595
|
-
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`); }
|
|
637
|
+
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`); }
|
|
638
|
+
function ignoreCmd() {
|
|
639
|
+
const sub = args[1];
|
|
640
|
+
const pfile = join(".tori", "project.json");
|
|
641
|
+
let j = {};
|
|
642
|
+
try {
|
|
643
|
+
j = JSON.parse(readFileSync(pfile, "utf8"));
|
|
644
|
+
}
|
|
645
|
+
catch { }
|
|
646
|
+
const cur = Array.isArray(j.ignore) ? j.ignore.filter((x) => typeof x === "string") : [];
|
|
647
|
+
if (sub === "list" || !sub) {
|
|
648
|
+
if (has("json"))
|
|
649
|
+
return out({ ignore: cur }, cur.join("\n"));
|
|
650
|
+
console.log(cur.length ? cur.join("\n") : "No ignore patterns. Add one: tori ignore add fixtures/");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const pats = args.slice(2).filter((a) => !a.startsWith("--")).map((p) => p.trim()).filter(Boolean);
|
|
654
|
+
if (sub === "add") {
|
|
655
|
+
if (!pats.length)
|
|
656
|
+
die("Usage: tori ignore add <glob> [<glob>…]");
|
|
657
|
+
const next = Array.from(new Set([...cur, ...pats]));
|
|
658
|
+
writeJson(pfile, { ...j, ignore: next });
|
|
659
|
+
console.log(`Ignoring ${next.length} pattern(s) in this project; never sent to Tori:\n` + next.join("\n"));
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (sub === "remove") {
|
|
663
|
+
if (!pats.length)
|
|
664
|
+
die("Usage: tori ignore remove <glob>");
|
|
665
|
+
const next = cur.filter((p) => !pats.includes(p));
|
|
666
|
+
writeJson(pfile, { ...j, ignore: next });
|
|
667
|
+
console.log(next.length ? `Ignoring ${next.length} pattern(s):\n` + next.join("\n") : "No ignore patterns.");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
die("Usage: tori ignore add <glob…> | list | remove <glob>");
|
|
671
|
+
}
|
|
596
672
|
(async () => {
|
|
597
673
|
switch (cmd) {
|
|
598
674
|
case "login": return login();
|
|
599
675
|
case "status": return status();
|
|
600
676
|
case "init": return init();
|
|
601
677
|
case "check": return check();
|
|
678
|
+
case "ignore": return ignoreCmd();
|
|
602
679
|
case "ledger": return ledger();
|
|
603
680
|
case "receipt": return receipt();
|
|
604
681
|
case "connector": return connector();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earntori/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
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",
|