@earntori/cli 0.2.15 → 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.
- package/dist/index.js +93 -9
- 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";
|
|
@@ -243,8 +243,14 @@ const HARNESSES = [
|
|
|
243
243
|
];
|
|
244
244
|
async function init() {
|
|
245
245
|
const pin = flag("base");
|
|
246
|
-
if (pin)
|
|
247
|
-
|
|
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
|
|
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
|
-
|
|
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");
|
|
@@ -336,7 +384,7 @@ function formatRun(r, n) {
|
|
|
336
384
|
return `\u2713 Tori checks clean \u2014 ${n} file(s), no open findings.`;
|
|
337
385
|
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
386
|
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);
|
|
387
|
+
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
388
|
if (r.waived?.length)
|
|
341
389
|
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
390
|
if (r.reward?.issued)
|
|
@@ -539,7 +587,8 @@ async function standards() {
|
|
|
539
587
|
return;
|
|
540
588
|
}
|
|
541
589
|
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)"}
|
|
590
|
+
const line = (s) => ` ${s.enabled ? "on " : "off"} ${s.id.padEnd(20)} ${s.name}${s.rules ? ` (${s.rules} checks)` : " (no checks yet)"}` +
|
|
591
|
+
(s.areas?.length ? `\n ${" ".repeat(20)} ${s.areas.map((a) => `${a.name} ${a.rules}`).join(" · ")}` : "");
|
|
543
592
|
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
593
|
}
|
|
545
594
|
async function rules() {
|
|
@@ -592,13 +641,48 @@ function logout() {
|
|
|
592
641
|
unlinkSync(join(SESSIONS_DIR, f));
|
|
593
642
|
out({ ok: true }, "\u2713 Signed out (all sessions removed)");
|
|
594
643
|
}
|
|
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`); }
|
|
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
|
+
}
|
|
596
679
|
(async () => {
|
|
597
680
|
switch (cmd) {
|
|
598
681
|
case "login": return login();
|
|
599
682
|
case "status": return status();
|
|
600
683
|
case "init": return init();
|
|
601
684
|
case "check": return check();
|
|
685
|
+
case "ignore": return ignoreCmd();
|
|
602
686
|
case "ledger": return ledger();
|
|
603
687
|
case "receipt": return receipt();
|
|
604
688
|
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.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",
|