@clemensschartmueller/jev-guard 0.0.0-stage → 0.2.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/LICENSE.md +21 -0
- package/README.md +19 -2
- package/bin/jev-guard.js +96 -0
- package/package.json +28 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jev-guard contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# jev-guard installer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run `npx --yes @clemensschartmueller/jev-guard@latest` from the project you want to protect. The launcher downloads the jev-guard GitHub Release matching its npm package version, checks the binary against that release's SHA256 manifest, installs it under `~/.jevguard/bin`, and runs `jev-guard init`.
|
|
4
|
+
|
|
5
|
+
Options after the package name go to `jev-guard init`. For example:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx --yes @clemensschartmueller/jev-guard@latest --agent codex --scope project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See the [project README](https://github.com/ClemensSchartmueller/jev-guard#readme) for supported agents and other installation methods.
|
|
12
|
+
|
|
13
|
+
## Releasing
|
|
14
|
+
|
|
15
|
+
The `release.yml` workflow publishes this package only after the matching GitHub Release and its checksum file are uploaded. The npm package version must equal the Git tag without its leading `v`. The package configuration and workflow are ready for public publishing, but npm account setup remains with the owner:
|
|
16
|
+
|
|
17
|
+
1. Confirm that the npm user or organization scope `@clemensschartmueller` exists and that you can publish within it.
|
|
18
|
+
2. Before merging a version bump, bootstrap `@clemensschartmueller/jev-guard@0.2.0` from `npm/jev-guard` in an owner-authenticated environment with `npm publish --access public`. First review it with `npm pack --dry-run`. The first npm publication must use a version whose GitHub Release already contains `checksums.txt` and matching binaries; `v0.2.0` has those assets. Otherwise the launcher installs but cannot download its binary.
|
|
19
|
+
3. In the new package's npm settings, configure a [GitHub Actions trusted publisher](https://docs.npmjs.com/trusted-publishers/) with organization/user `ClemensSchartmueller`, repository `jev-guard`, workflow filename `release.yml`, and permission to run `npm publish`. Configure it just before the next release: npm requires its first successful trusted publish within two days.
|
|
20
|
+
4. After the other release changes and version bump are merged, tag the next version (for example, `v0.3.0`) and let `release.yml` publish it via OIDC. Verify the npm package is public and installable. A published name/version cannot be reused, so bump the version for each later release.
|
package/bin/jev-guard.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { createHash } = require('node:crypto');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
6
|
+
const fs = require('node:fs/promises');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { version } = require('../package.json');
|
|
10
|
+
|
|
11
|
+
const RELEASES = 'https://github.com/ClemensSchartmueller/jev-guard/releases/download';
|
|
12
|
+
const MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
function assetName(platform = process.platform, arch = process.arch) {
|
|
15
|
+
const osName = { linux: 'linux', darwin: 'darwin', win32: 'windows' }[platform];
|
|
16
|
+
const cpu = { x64: 'amd64', arm64: 'arm64' }[arch];
|
|
17
|
+
if (!osName || !cpu || (platform === 'win32' && arch !== 'x64')) {
|
|
18
|
+
throw new Error(`Unsupported platform: ${platform}/${arch}. Use a source build for this platform.`);
|
|
19
|
+
}
|
|
20
|
+
return `jev-guard-${osName}-${cpu}${platform === 'win32' ? '.exe' : ''}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function expectedDigest(checksums, filename) {
|
|
24
|
+
const line = checksums.split(/\r?\n/).find((entry) => entry.endsWith(` ${filename}`) || entry.endsWith(` *${filename}`));
|
|
25
|
+
const digest = line && /^([a-fA-F0-9]{64})\s+[*]?\S+$/.exec(line)?.[1];
|
|
26
|
+
if (!digest) throw new Error(`No SHA256 checksum for ${filename} in release checksums.txt`);
|
|
27
|
+
return digest.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function download(url, fetchImpl = fetch) {
|
|
31
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(60000) });
|
|
32
|
+
if (!response.ok) throw new Error(`Download failed (${response.status}): ${url}`);
|
|
33
|
+
if (!response.body) throw new Error(`Empty response: ${url}`);
|
|
34
|
+
const chunks = [];
|
|
35
|
+
let bytes = 0;
|
|
36
|
+
for await (const chunk of response.body) {
|
|
37
|
+
bytes += chunk.length;
|
|
38
|
+
if (bytes > MAX_DOWNLOAD_BYTES) throw new Error(`Download exceeds ${MAX_DOWNLOAD_BYTES} bytes: ${url}`);
|
|
39
|
+
chunks.push(chunk);
|
|
40
|
+
}
|
|
41
|
+
return Buffer.concat(chunks);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function install({ platform = process.platform, arch = process.arch, home = os.homedir(), fetchImpl = fetch } = {}) {
|
|
45
|
+
const filename = assetName(platform, arch);
|
|
46
|
+
const base = `${RELEASES}/v${version}`;
|
|
47
|
+
const checksums = (await download(`${base}/checksums.txt`, fetchImpl)).toString('utf8');
|
|
48
|
+
const expected = expectedDigest(checksums, filename);
|
|
49
|
+
const installDir = path.join(home, '.jevguard', 'bin');
|
|
50
|
+
const target = path.join(installDir, platform === 'win32' ? 'jev-guard.exe' : 'jev-guard');
|
|
51
|
+
|
|
52
|
+
// A repeat invocation checks the release checksum, but avoids a binary download.
|
|
53
|
+
try {
|
|
54
|
+
const existing = await fs.readFile(target);
|
|
55
|
+
if (createHash('sha256').update(existing).digest('hex') === expected) return target;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.code !== 'ENOENT') throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const binary = await download(`${base}/${filename}`, fetchImpl);
|
|
61
|
+
if (createHash('sha256').update(binary).digest('hex') !== expected) {
|
|
62
|
+
throw new Error(`SHA256 mismatch for ${filename}; existing installation was kept`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await fs.mkdir(installDir, { recursive: true });
|
|
66
|
+
const staged = path.join(installDir, `.${path.basename(target)}-${process.pid}-${Date.now()}.tmp`);
|
|
67
|
+
try {
|
|
68
|
+
await fs.writeFile(staged, binary, { mode: 0o755, flag: 'wx' });
|
|
69
|
+
if (platform !== 'win32') await fs.chmod(staged, 0o755);
|
|
70
|
+
await fs.rename(staged, target);
|
|
71
|
+
} finally {
|
|
72
|
+
await fs.rm(staged, { force: true });
|
|
73
|
+
}
|
|
74
|
+
return target;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main(args = process.argv.slice(2)) {
|
|
78
|
+
const binary = await install();
|
|
79
|
+
const child = spawn(binary, ['init', ...args], { stdio: 'inherit' });
|
|
80
|
+
child.on('error', (error) => {
|
|
81
|
+
console.error(`jev-guard: ${error.message}`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
});
|
|
84
|
+
child.on('exit', (code, signal) => {
|
|
85
|
+
process.exitCode = code ?? (signal ? 1 : 0);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (require.main === module) {
|
|
90
|
+
main().catch((error) => {
|
|
91
|
+
console.error(`jev-guard: ${error.message}`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { assetName, expectedDigest, install };
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clemensschartmueller/jev-guard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Install and initialize jev-guard for your coding agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"jev-guard": "bin/jev-guard.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/jev-guard.js",
|
|
10
|
+
"LICENSE.md",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"type": "commonjs",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ClemensSchartmueller/jev-guard.git",
|
|
20
|
+
"directory": "npm/jev-guard"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --test"
|
|
29
|
+
}
|
|
30
|
+
}
|