promptkit-os 1.10.0 → 1.11.0
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/README.md +3 -1
- package/bin/promptkit-os.js +71 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# `promptkit-os` (npm)
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The open-source engineering control plane for AI coding agents (Claude Code, Cursor, Copilot, Antigravity, Gemini CLI) — managing task ceremony levels (L0–L3), context economy, JIT stack playbooks, and evidence-gated verification.
|
|
4
|
+
|
|
5
|
+
> **Note**: This npm package is the **courier**, not the product. PromptKit OS itself is pure repository-native markdown with no runtime, no daemon, and no model lock-in — it lives at [lowqualityloey/promptkit-os](https://github.com/lowqualityloey/promptkit-os).
|
|
4
6
|
|
|
5
7
|
This package does one thing: fetch the GitHub release tarball that matches its own version, extract it into `./.promptkit/`, and run the canonical `init.sh` (POSIX) or `init.ps1` (Windows). All installation logic stays in the fetched repository; nothing is reimplemented here.
|
|
6
8
|
|
package/bin/promptkit-os.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"use strict";
|
|
10
10
|
|
|
11
11
|
const { spawnSync } = require("node:child_process");
|
|
12
|
+
const crypto = require("node:crypto");
|
|
12
13
|
const fs = require("node:fs");
|
|
13
14
|
const path = require("node:path");
|
|
14
15
|
const os = require("node:os");
|
|
@@ -17,6 +18,32 @@ const PKG = require("../package.json");
|
|
|
17
18
|
const REPO = "lowqualityloey/promptkit-os";
|
|
18
19
|
const KIT_DIR = ".promptkit";
|
|
19
20
|
|
|
21
|
+
// Pinned SHA-256 (hex) of the GitHub release tarball per courier version.
|
|
22
|
+
// This lives here — not in a sidecar file — because release-npm.yml enforces an
|
|
23
|
+
// npm-pack allowlist of exactly { package.json, README.md, bin/promptkit-os.js };
|
|
24
|
+
// a sidecar would either be excluded from the published package (useless) or
|
|
25
|
+
// require a workflow change to allowlist.
|
|
26
|
+
//
|
|
27
|
+
// UPDATE INSTRUCTION FOR RELEASES: after pushing tag vX.Y.Z (and before the npm
|
|
28
|
+
// publish), download the immutable release tarball, hash it, and add the entry:
|
|
29
|
+
// curl -sL "https://github.com/lowqualityloey/promptkit-os/archive/refs/tags/vX.Y.Z.tar.gz" -o /tmp/release.tar.gz
|
|
30
|
+
// sha256sum /tmp/release.tar.gz
|
|
31
|
+
// then add `"X.Y.Z": "<64-hex-chars>",` below so the courier verifies that exact
|
|
32
|
+
// tarball before extracting or executing anything from it. If no pin exists for
|
|
33
|
+
// the running version the courier warns loudly and continues (pins can only be
|
|
34
|
+
// minted after the tag exists); set PROMPTKIT_REQUIRE_INTEGRITY_PIN=1 to fail
|
|
35
|
+
// closed instead. PROMPTKIT_TARBALL_SHA256 overrides the map (testing / rotation).
|
|
36
|
+
//
|
|
37
|
+
// CAVEAT: these digests pin GitHub's *generated* archive for a tag. That archive
|
|
38
|
+
// is reproducible today (verified by double download when each pin was minted)
|
|
39
|
+
// but is not contractually immutable — a future GitHub-side change to archive
|
|
40
|
+
// generation would change the bytes and make this pin reject an untampered
|
|
41
|
+
// download. On mismatch, confirm the release tag and the release evidence
|
|
42
|
+
// record before treating it as tampering; rotate with PROMPTKIT_TARBALL_SHA256.
|
|
43
|
+
const TARBALL_SHA256_BY_VERSION = {
|
|
44
|
+
"1.10.1": "1e55459914a1c46aa71df8f9cb8ce4c9ff37d1c02d67f70e5607b3bfd4f48d4b",
|
|
45
|
+
};
|
|
46
|
+
|
|
20
47
|
function die(msg) {
|
|
21
48
|
process.stderr.write(`[promptkit-os] ${msg}\n`);
|
|
22
49
|
process.exit(1);
|
|
@@ -78,6 +105,30 @@ function projectRootFrom(args) {
|
|
|
78
105
|
return path.resolve(positional[0] || process.cwd());
|
|
79
106
|
}
|
|
80
107
|
|
|
108
|
+
function expectedTarballDigest(version) {
|
|
109
|
+
const override = process.env.PROMPTKIT_TARBALL_SHA256;
|
|
110
|
+
if (override !== undefined && override !== "") return override;
|
|
111
|
+
return TARBALL_SHA256_BY_VERSION[version];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Aborts (non-zero exit, no extract, no exec) unless `tarball` hashes to
|
|
115
|
+
// `expectedHex`. Comparison is constant-time so a MITM learns nothing about the
|
|
116
|
+
// pin from timing. Must be called after download() and before extract().
|
|
117
|
+
function verifyTarballIntegrity(tarball, expectedHex, version) {
|
|
118
|
+
const actual = crypto.createHash("sha256").update(tarball).digest();
|
|
119
|
+
const expected = Buffer.from(String(expectedHex), "hex");
|
|
120
|
+
// Buffer.from(hex) never throws on bad input — it truncates — so the length
|
|
121
|
+
// check below is what turns a malformed pin into a mismatch (fail closed).
|
|
122
|
+
if (expected.length !== actual.length || !crypto.timingSafeEqual(actual, expected)) {
|
|
123
|
+
die(
|
|
124
|
+
`tarball integrity mismatch for v${version} ` +
|
|
125
|
+
`(expected sha256 ${expectedHex}, got ${actual.toString("hex")}): ` +
|
|
126
|
+
"refusing to extract or execute — the download may have been tampered with. " +
|
|
127
|
+
"If this version was just released, the pin in TARBALL_SHA256_BY_VERSION needs updating (see above)."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
81
132
|
async function download(url) {
|
|
82
133
|
process.stderr.write(`[promptkit-os] fetching ${url}\n`);
|
|
83
134
|
const res = await fetch(url, { redirect: "follow" });
|
|
@@ -150,10 +201,29 @@ async function main() {
|
|
|
150
201
|
fs.mkdirSync(kitDir, { recursive: true });
|
|
151
202
|
|
|
152
203
|
const tarball = await download(url);
|
|
204
|
+
const expectedDigest = expectedTarballDigest(version);
|
|
205
|
+
if (expectedDigest) {
|
|
206
|
+
verifyTarballIntegrity(tarball, expectedDigest, version);
|
|
207
|
+
process.stderr.write(`[promptkit-os] verified release tarball integrity (sha256) for v${version}\n`);
|
|
208
|
+
} else if (process.env.PROMPTKIT_REQUIRE_INTEGRITY_PIN === "1") {
|
|
209
|
+
die(
|
|
210
|
+
`no integrity pin for v${version} — refusing to extract or execute. ` +
|
|
211
|
+
"Add the sha256 of the release tarball to TARBALL_SHA256_BY_VERSION (see above)."
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
process.stderr.write(
|
|
215
|
+
`[promptkit-os] warning: no integrity pin for v${version}; ` +
|
|
216
|
+
"proceeding unverified — set PROMPTKIT_REQUIRE_INTEGRITY_PIN=1 to fail closed.\n"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
153
219
|
await extract(tarball, kitDir);
|
|
154
220
|
process.stderr.write(`[promptkit-os] extracted release v${version} into ${KIT_DIR}/\n`);
|
|
155
221
|
|
|
156
222
|
runInstaller(kitDir, args);
|
|
157
223
|
}
|
|
158
224
|
|
|
159
|
-
|
|
225
|
+
if (require.main === module) {
|
|
226
|
+
main().catch((err) => die(err && err.message ? err.message : String(err)));
|
|
227
|
+
} else {
|
|
228
|
+
module.exports = { verifyTarballIntegrity, expectedTarballDigest, TARBALL_SHA256_BY_VERSION };
|
|
229
|
+
}
|
package/package.json
CHANGED