tachograph 0.1.1 → 0.2.1

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 (3) hide show
  1. package/asset.js +22 -3
  2. package/install.js +18 -1
  3. package/package.json +1 -1
package/asset.js CHANGED
@@ -23,10 +23,29 @@ function assetFor(platform, arch) {
23
23
  };
24
24
  }
25
25
 
26
+ // tagFor normalizes a package version to its release tag (v-prefixed).
27
+ function tagFor(version) {
28
+ return version.startsWith("v") ? version : `v${version}`;
29
+ }
30
+
26
31
  // downloadURL builds the GitHub release download URL for a version + asset.
27
32
  function downloadURL(version, asset) {
28
- const tag = version.startsWith("v") ? version : `v${version}`;
29
- return `https://github.com/kosako/tachograph/releases/download/${tag}/${asset}`;
33
+ return `https://github.com/kosako/tachograph/releases/download/${tagFor(version)}/${asset}`;
34
+ }
35
+
36
+ // checksumsURL is the GoReleaser checksums.txt for the matching release.
37
+ function checksumsURL(version) {
38
+ return `https://github.com/kosako/tachograph/releases/download/${tagFor(version)}/checksums.txt`;
39
+ }
40
+
41
+ // checksumFor parses GoReleaser's "<sha256> <filename>" lines and returns the
42
+ // hex digest for asset, or null when it isn't listed.
43
+ function checksumFor(text, asset) {
44
+ for (const line of text.split("\n")) {
45
+ const [hash, name] = line.trim().split(/\s+/);
46
+ if (name === asset && hash) return hash;
47
+ }
48
+ return null;
30
49
  }
31
50
 
32
- module.exports = { assetFor, downloadURL, PLATFORMS, ARCHES };
51
+ module.exports = { assetFor, downloadURL, checksumsURL, checksumFor, PLATFORMS, ARCHES };
package/install.js CHANGED
@@ -9,8 +9,9 @@
9
9
  const fs = require("fs");
10
10
  const os = require("os");
11
11
  const path = require("path");
12
+ const crypto = require("crypto");
12
13
  const { execFileSync } = require("child_process");
13
- const { assetFor, downloadURL } = require("./asset");
14
+ const { assetFor, downloadURL, checksumsURL, checksumFor } = require("./asset");
14
15
 
15
16
  const { version } = require("./package.json");
16
17
  const binDir = path.join(__dirname, "bin");
@@ -25,6 +26,22 @@ async function main() {
25
26
  }
26
27
  const buf = Buffer.from(await res.arrayBuffer());
27
28
 
29
+ // Verify the archive against GoReleaser's published checksums.txt before
30
+ // extracting and running it, so a corrupted/tampered/partial download is
31
+ // caught rather than executed.
32
+ const sums = await fetch(checksumsURL(version), { redirect: "follow" });
33
+ if (!sums.ok) {
34
+ throw new Error(`checksums download failed: ${sums.status} ${sums.statusText}`);
35
+ }
36
+ const want = checksumFor(await sums.text(), asset);
37
+ if (!want) {
38
+ throw new Error(`no checksum listed for ${asset}`);
39
+ }
40
+ const got = crypto.createHash("sha256").update(buf).digest("hex");
41
+ if (got !== want) {
42
+ throw new Error(`checksum mismatch for ${asset}: expected ${want}, got ${got}`);
43
+ }
44
+
28
45
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "tacho-"));
29
46
  const archive = path.join(tmp, asset);
30
47
  fs.writeFileSync(archive, buf);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tachograph",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "A compact instrument cluster for your coding agents (Claude Code / Codex). Shows model, rate-limit usage, and context at a glance.",
5
5
  "keywords": [
6
6
  "claude-code",