@earntori/cli 0.2.17 → 0.2.19

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 +35 -12
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ function out(o, human) { console.log(json ? JSON.stringify(o, null, 2) : human);
56
56
  function die(msg) { console.error(json ? JSON.stringify({ error: msg }) : `\u2717 ${msg}`); process.exit(1); }
57
57
  const usd = (c) => `$${(c / 100).toFixed(2)}`;
58
58
  async function api(cfg, method, path, body) {
59
- const r = await fetch(`${cfg.base}${path}`, { method, headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": "@earntori/cli/0.2.10" }, body: body ? JSON.stringify(body) : undefined });
59
+ const r = await fetch(`${cfg.base}${path}`, { method, headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": userAgent() }, body: body ? JSON.stringify(body) : undefined });
60
60
  const t = await r.text();
61
61
  let j = null;
62
62
  try {
@@ -162,8 +162,9 @@ function writeJson(p, o) { mkdirSync(dirname(resolve(p)), { recursive: true });
162
162
  // (Claude Code and Cursor: always; Codex: when it is installed or present in the repo, or --codex);
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
- // MCP version is a constant: bump it in the same commit as a @earntori/mcp release.
166
- const TORI_MCP_VERSION = "0.1.9";
165
+ // MCP version is a constant: bump it in the same commit as a @earntori/mcp release. Spec rev 97:
166
+ // scripts/check-pins.mjs fails `test` and `prepublishOnly` when it differs from packages/mcp's version.
167
+ const TORI_MCP_VERSION = "0.1.12";
167
168
  function cliVersion(cliBin) {
168
169
  try {
169
170
  return JSON.parse(readFileSync(resolve(dirname(realpathSync(cliBin)), "..", "package.json"), "utf8")).version ?? "latest";
@@ -172,6 +173,13 @@ function cliVersion(cliBin) {
172
173
  return "latest";
173
174
  }
174
175
  }
176
+ /** Spec rev 97: the User-Agent carries this package's own version, read once per process. */
177
+ let uaCache;
178
+ function userAgent() {
179
+ if (!uaCache)
180
+ uaCache = `@earntori/cli/${cliVersion(resolve(process.argv[1] ?? ""))}`;
181
+ return uaCache;
182
+ }
175
183
  const withHarness = (mcp, id) => ({ ...mcp, env: { TORI_HARNESS: id } });
176
184
  function mergeMcpJson(file, mcp) { const j = readJson(file) ?? {}; j.mcpServers = { ...(j.mcpServers ?? {}), tori: mcp }; writeJson(file, j); return file; }
177
185
  function hookPresent(h, c) { return typeof h?.command === "string" && (/@earntori\/cli(@[^\s]+)? check/.test(h.command) || (c.local && h.command.includes(c.cliBin))); }
@@ -243,8 +251,14 @@ const HARNESSES = [
243
251
  ];
244
252
  async function init() {
245
253
  const pin = flag("base");
246
- if (pin)
247
- writeJson(join(".tori", "project.json"), { base: pin.replace(/\/$/, "") });
254
+ if (pin) {
255
+ let prev = {};
256
+ try {
257
+ prev = JSON.parse(readFileSync(join(".tori", "project.json"), "utf8"));
258
+ }
259
+ catch { }
260
+ writeJson(join(".tori", "project.json"), { ...prev, base: pin.replace(/\/$/, "") });
261
+ }
248
262
  let cfg = loadCfg();
249
263
  if (!cfg) {
250
264
  console.log(pin ? `Not signed in for ${pin} \u2014 signing in first` : "Not signed in \u2014 signing in first");
@@ -283,18 +297,27 @@ async function init() {
283
297
  (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
298
  }
285
299
  function repoIndex() { try {
286
- const out = execSync("git ls-files", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split("\n").filter(Boolean);
300
+ const pats = ignorePatterns(process.cwd());
301
+ const out = execSync("git ls-files", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).split("\n").filter(Boolean).filter((p) => !isIgnored(p, pats));
287
302
  return out.length ? out.slice(0, 5000) : undefined;
288
303
  }
289
304
  catch {
290
305
  return undefined;
291
306
  } }
292
- function envExample() { try {
293
- return existsSync(".env.example") ? readFileSync(".env.example", "utf8").slice(0, 20000) : undefined;
307
+ /** Spec rev 97 (amends rev 86): `.env.example` is skipped when it matches an ignore pattern, and when it is
308
+ * read only variable names leave the machine, never values. Names are taken with the same pattern the one
309
+ * rule that uses them applies, so the check is unchanged. */
310
+ function envExample() {
311
+ try {
312
+ if (!existsSync(".env.example") || isIgnored(".env.example", ignorePatterns(process.cwd())))
313
+ return undefined;
314
+ const names = readFileSync(".env.example", "utf8").match(/^\s*[A-Z][A-Z0-9_]+\s*=/gm) ?? [];
315
+ return names.map((l) => l.replace(/\s*=.*$/, "").trim() + "=").join("\n").slice(0, 20000);
316
+ }
317
+ catch {
318
+ return undefined;
319
+ }
294
320
  }
295
- catch {
296
- return undefined;
297
- } }
298
321
  const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|php|java|kt|cs)$/i;
299
322
  function projectId() {
300
323
  try {
@@ -439,7 +462,7 @@ async function check() {
439
462
  try {
440
463
  const body0 = { files, project: projectId(), repo_index: repoIndex(), env_example: envExample() };
441
464
  dbg(`request: ${JSON.stringify(body0).length} bytes, index ${body0.repo_index?.length ?? 0} paths -> ${cfg.base}`);
442
- const res = await fetch(`${cfg.base}/api/v1/checks/run`, { method: "POST", headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": "@earntori/cli/check" }, body: JSON.stringify(body0) });
465
+ const res = await fetch(`${cfg.base}/api/v1/checks/run`, { method: "POST", headers: { Authorization: `Bearer ${cfg.token}`, "Content-Type": "application/json", "User-Agent": userAgent() }, body: JSON.stringify(body0) });
443
466
  dbg(`response: ${res.status}`);
444
467
  const t = await res.text();
445
468
  if (!res.ok)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earntori/cli",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
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",
@@ -17,7 +17,8 @@
17
17
  "scripts": {
18
18
  "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
19
19
  "typecheck": "tsc --noEmit -p tsconfig.json",
20
- "test": "echo no tests"
20
+ "test": "node scripts/check-pins.mjs",
21
+ "prepublishOnly": "node scripts/check-pins.mjs"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^20.19.43",