contextecf-codeledger 0.10.7
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 +61 -0
- package/README.md +31 -0
- package/bin/codeledger.js +67 -0
- package/package.json +43 -0
- package/scripts/postinstall.js +285 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
CodeLedger Core Engine License
|
|
2
|
+
Version 1.0, 2025
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 Intelligent Context AI, Inc.
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
GRANT OF LICENSE
|
|
8
|
+
|
|
9
|
+
Subject to the terms of this license, Intelligent Context AI, Inc. ("Licensor")
|
|
10
|
+
grants you a non-exclusive, non-transferable, revocable license to use the
|
|
11
|
+
CodeLedger Core Engine binary ("Software") under the following conditions:
|
|
12
|
+
|
|
13
|
+
1. INDIVIDUAL USE (FREE)
|
|
14
|
+
You may use the Software for personal, non-commercial development work
|
|
15
|
+
at no cost. This includes individual developers using the Software on
|
|
16
|
+
their own projects or contributing to open-source projects.
|
|
17
|
+
|
|
18
|
+
2. OPEN-SOURCE PROJECTS (FREE)
|
|
19
|
+
You may use the Software in open-source projects licensed under
|
|
20
|
+
OSI-approved licenses at no cost.
|
|
21
|
+
|
|
22
|
+
3. COMMERCIAL USE (LICENSE REQUIRED)
|
|
23
|
+
Use of the Software in commercial settings, including but not limited to:
|
|
24
|
+
- CI/CD pipelines
|
|
25
|
+
- Team development environments (>1 developer)
|
|
26
|
+
- Enterprise deployments
|
|
27
|
+
- Products or services that incorporate the Software
|
|
28
|
+
requires a CodeLedger Pro license. Contact team@codeledger.dev for pricing.
|
|
29
|
+
|
|
30
|
+
RESTRICTIONS
|
|
31
|
+
|
|
32
|
+
You may NOT:
|
|
33
|
+
- Reverse engineer, decompile, or disassemble the Software
|
|
34
|
+
- Redistribute the Software as a standalone product
|
|
35
|
+
- Remove or modify any license notices or proprietary markings
|
|
36
|
+
- Use the Software to build a competing product or service
|
|
37
|
+
|
|
38
|
+
PRIVACY GUARANTEE
|
|
39
|
+
|
|
40
|
+
The Software:
|
|
41
|
+
- Runs entirely on your local machine
|
|
42
|
+
- Makes no network calls
|
|
43
|
+
- Collects no telemetry, analytics, or usage data
|
|
44
|
+
- Does not transmit your source code or any data to external services
|
|
45
|
+
|
|
46
|
+
DISCLAIMER
|
|
47
|
+
|
|
48
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
49
|
+
IMPLIED. IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR
|
|
50
|
+
OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
|
|
51
|
+
|
|
52
|
+
TERMINATION
|
|
53
|
+
|
|
54
|
+
This license terminates automatically if you breach any of its terms.
|
|
55
|
+
Upon termination, you must destroy all copies of the Software.
|
|
56
|
+
|
|
57
|
+
CONTACT
|
|
58
|
+
|
|
59
|
+
Intelligent Context AI, Inc.
|
|
60
|
+
team@codeledger.dev
|
|
61
|
+
https://codeledger.dev/license
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# contextecf-codeledger — CodeLedger by ContextECF
|
|
2
|
+
|
|
3
|
+
**Deterministic, bounded context selection for AI coding agents.**
|
|
4
|
+
Published by [ContextECF](https://codeledger.dev) (Intelligent Context AI, Inc.).
|
|
5
|
+
|
|
6
|
+
This is the **unscoped mirror** of [`@codeledger/cli`](https://www.npmjs.com/package/@codeledger/cli). Both packages install the exact same binary. Use whichever you prefer:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g contextecf-codeledger # this package
|
|
10
|
+
npm install -g @codeledger/cli # scoped original
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why two packages?
|
|
14
|
+
|
|
15
|
+
An unrelated package named `codeledger` (unscoped, no `@`) exists on npm by a different author. AI coding agents often guess `npm install codeledger` and install the wrong thing. This unscoped `contextecf-codeledger` package gives agents (and humans) a discoverable name that works on the first try.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
codeledger init
|
|
21
|
+
codeledger activate --task "describe your task"
|
|
22
|
+
codeledger mcp start # for Claude Desktop / Cursor MCP integration
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Links
|
|
26
|
+
|
|
27
|
+
- Website: https://codeledger.dev
|
|
28
|
+
- Demo: https://demo.codeledger.dev
|
|
29
|
+
- Source: https://github.com/codeledgerECF/codeledger
|
|
30
|
+
- npm (scoped): https://www.npmjs.com/package/@codeledger/cli
|
|
31
|
+
- npm (unscoped): https://www.npmjs.com/package/contextecf-codeledger
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* CodeLedger npm launcher — thin shim (unscoped mirror).
|
|
4
|
+
*
|
|
5
|
+
* This is the unscoped mirror of @codeledger/cli. Both packages install
|
|
6
|
+
* the exact same hardened binary. You can install either:
|
|
7
|
+
*
|
|
8
|
+
* npm install -g contextecf-codeledger (this package)
|
|
9
|
+
* npm install -g @codeledger/cli (scoped original)
|
|
10
|
+
*
|
|
11
|
+
* This file contains no engine code. All CodeLedger functionality lives
|
|
12
|
+
* inside the hardened binary downloaded during postinstall.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { spawnSync } = require('node:child_process');
|
|
18
|
+
const { existsSync } = require('node:fs');
|
|
19
|
+
const { join } = require('node:path');
|
|
20
|
+
const { homedir, platform } = require('node:os');
|
|
21
|
+
|
|
22
|
+
const { version } = require('../package.json');
|
|
23
|
+
|
|
24
|
+
const CACHE_DIR =
|
|
25
|
+
process.env.CODELEDGER_CACHE_DIR ||
|
|
26
|
+
join(homedir(), '.cache', 'codeledger', 'bin');
|
|
27
|
+
|
|
28
|
+
const BINARY_NAME =
|
|
29
|
+
platform() === 'win32'
|
|
30
|
+
? `codeledger-hardened-v${version}.exe`
|
|
31
|
+
: `codeledger-hardened-v${version}`;
|
|
32
|
+
|
|
33
|
+
const BINARY_PATH = join(CACHE_DIR, BINARY_NAME);
|
|
34
|
+
|
|
35
|
+
function die(msg, code = 1) {
|
|
36
|
+
process.stderr.write(`codeledger: ${msg}\n`);
|
|
37
|
+
process.exit(code);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!existsSync(BINARY_PATH)) {
|
|
41
|
+
const installerPath = join(__dirname, '..', 'scripts', 'postinstall.js');
|
|
42
|
+
die(
|
|
43
|
+
[
|
|
44
|
+
`hardened binary for v${version} not found at ${BINARY_PATH}.`,
|
|
45
|
+
'',
|
|
46
|
+
'The postinstall step may have been skipped (e.g. --ignore-scripts).',
|
|
47
|
+
'Recover with one of:',
|
|
48
|
+
` 1. npm rebuild contextecf-codeledger`,
|
|
49
|
+
` 2. node "${installerPath}"`,
|
|
50
|
+
` 3. Re-run the install without --ignore-scripts`,
|
|
51
|
+
'',
|
|
52
|
+
'Or download the binary manually from:',
|
|
53
|
+
` https://github.com/codeledgerECF/codeledger/releases/tag/v${version}`,
|
|
54
|
+
].join('\n'),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = spawnSync(BINARY_PATH, process.argv.slice(2), {
|
|
59
|
+
stdio: 'inherit',
|
|
60
|
+
env: process.env,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.error) {
|
|
64
|
+
die(`failed to spawn binary: ${result.error.message}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process.exit(result.status ?? 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contextecf-codeledger",
|
|
3
|
+
"version": "0.10.7",
|
|
4
|
+
"mcpName": "io.github.codeledgerECF/codeledger",
|
|
5
|
+
"description": "CodeLedger by ContextECF — deterministic context selection for AI coding agents. This is the unscoped mirror of @codeledger/cli; both install the same binary.",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"codeledger": "./bin/codeledger.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "node scripts/postinstall.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"scripts/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "github:codeledgerECF/codeledger-blackbox"
|
|
26
|
+
},
|
|
27
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"codeledger",
|
|
30
|
+
"contextecf",
|
|
31
|
+
"context",
|
|
32
|
+
"ai",
|
|
33
|
+
"claude",
|
|
34
|
+
"cursor",
|
|
35
|
+
"windsurf",
|
|
36
|
+
"codex",
|
|
37
|
+
"mcp",
|
|
38
|
+
"mcp-server"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* CodeLedger postinstall — download + verify the hardened binary.
|
|
4
|
+
*
|
|
5
|
+
* Runs automatically on `npm install -g @codeledger/cli`. Downloads the
|
|
6
|
+
* platform-specific hardened binary from the GitHub Release for this
|
|
7
|
+
* exact version, verifies its SHA256 against SHA256SUMS-v<version>.txt
|
|
8
|
+
* (also downloaded from the release), and writes it to the cache dir.
|
|
9
|
+
*
|
|
10
|
+
* Environment variables (all optional):
|
|
11
|
+
* CODELEDGER_SKIP_POSTINSTALL=1 — skip entirely (air-gapped installs)
|
|
12
|
+
* CODELEDGER_BINARY_URL=<url> — override the release download base URL
|
|
13
|
+
* (for corporate mirrors / internal CDNs).
|
|
14
|
+
* Must serve the same filenames and a
|
|
15
|
+
* SHA256SUMS-v<version>.txt sibling file.
|
|
16
|
+
* CODELEDGER_CACHE_DIR=<path> — override the cache directory
|
|
17
|
+
* (default: ~/.cache/codeledger/bin)
|
|
18
|
+
*
|
|
19
|
+
* This file contains no engine logic. It only fetches, verifies, and writes
|
|
20
|
+
* bytes. Safe to inspect, safe to mirror, safe to audit.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const https = require('node:https');
|
|
26
|
+
const { URL } = require('node:url');
|
|
27
|
+
const { createHash } = require('node:crypto');
|
|
28
|
+
const {
|
|
29
|
+
mkdirSync,
|
|
30
|
+
writeFileSync,
|
|
31
|
+
chmodSync,
|
|
32
|
+
existsSync,
|
|
33
|
+
unlinkSync,
|
|
34
|
+
} = require('node:fs');
|
|
35
|
+
const { join } = require('node:path');
|
|
36
|
+
const { homedir, platform, arch } = require('node:os');
|
|
37
|
+
|
|
38
|
+
const { version } = require('../package.json');
|
|
39
|
+
|
|
40
|
+
// ─── config ──────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
if (process.env.CODELEDGER_SKIP_POSTINSTALL === '1') {
|
|
43
|
+
console.log(
|
|
44
|
+
'codeledger: CODELEDGER_SKIP_POSTINSTALL=1 set, skipping hardened binary download.',
|
|
45
|
+
);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Skip when running inside the blackbox monorepo itself (dev mode). The
|
|
50
|
+
// presence of pnpm-workspace.yaml two directories up is a reliable signal
|
|
51
|
+
// that we're not a real end-user install — we're running during `pnpm
|
|
52
|
+
// install` from the workspace root, and the release binary for this
|
|
53
|
+
// version may not even exist yet.
|
|
54
|
+
try {
|
|
55
|
+
const path = require('node:path');
|
|
56
|
+
const fs = require('node:fs');
|
|
57
|
+
const workspaceRoot = path.resolve(__dirname, '..', '..', '..');
|
|
58
|
+
if (fs.existsSync(path.join(workspaceRoot, 'pnpm-workspace.yaml'))) {
|
|
59
|
+
console.log(
|
|
60
|
+
'codeledger: in-workspace dev install detected, skipping hardened binary download.',
|
|
61
|
+
);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// fall through to normal install
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// darwin-x64 is temporarily absent from v0.10.6 — no working Intel
|
|
69
|
+
// Mac runner on the release CI plan as of 2026-04-09. Intel Mac
|
|
70
|
+
// users hit the "unsupported platform" soft-fail below, which tells
|
|
71
|
+
// them to download manually from the GitHub Release. Re-add when a
|
|
72
|
+
// working Intel Mac runner label exists (see release-dispatch.yml
|
|
73
|
+
// matrix comment).
|
|
74
|
+
const PLATFORM_MAP = {
|
|
75
|
+
'darwin-arm64': 'darwin-arm64',
|
|
76
|
+
'linux-x64': 'linux-x64',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const platformKey = `${platform()}-${arch()}`;
|
|
80
|
+
const platformSuffix = PLATFORM_MAP[platformKey];
|
|
81
|
+
|
|
82
|
+
if (!platformSuffix) {
|
|
83
|
+
console.error(
|
|
84
|
+
`codeledger: unsupported platform ${platformKey}. Supported: ${Object.keys(
|
|
85
|
+
PLATFORM_MAP,
|
|
86
|
+
).join(', ')}`,
|
|
87
|
+
);
|
|
88
|
+
console.error(
|
|
89
|
+
`codeledger: v${version} does not provide a hardened binary for ${platformKey}.`,
|
|
90
|
+
);
|
|
91
|
+
console.error(
|
|
92
|
+
'codeledger: you can still run from source by cloning the blackbox repo.',
|
|
93
|
+
);
|
|
94
|
+
// Fail softly: don't block `npm install` for unsupported platforms. The
|
|
95
|
+
// bin shim will tell the user what happened when they first run `codeledger`.
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Binaries are hosted on the PUBLIC codeledgerECF/codeledger repo
|
|
100
|
+
// releases (not the private codeledger-blackbox repo — its release
|
|
101
|
+
// assets are not anonymously downloadable). The release pipeline in
|
|
102
|
+
// codeledger-blackbox builds the binaries, then a promote_binaries
|
|
103
|
+
// job uploads them cross-repo to the public release using a PAT.
|
|
104
|
+
// Users' `npm install -g @codeledger/cli` fetches from the public
|
|
105
|
+
// URL with no auth required.
|
|
106
|
+
const RELEASE_BASE =
|
|
107
|
+
process.env.CODELEDGER_BINARY_URL ||
|
|
108
|
+
`https://github.com/codeledgerECF/codeledger/releases/download/v${version}`;
|
|
109
|
+
|
|
110
|
+
const BINARY_REMOTE_NAME = `codeledger-hardened-${platformSuffix}-v${version}`;
|
|
111
|
+
const BINARY_URL = `${RELEASE_BASE}/${BINARY_REMOTE_NAME}`;
|
|
112
|
+
const CHECKSUMS_URL = `${RELEASE_BASE}/SHA256SUMS-v${version}.txt`;
|
|
113
|
+
|
|
114
|
+
const CACHE_DIR =
|
|
115
|
+
process.env.CODELEDGER_CACHE_DIR ||
|
|
116
|
+
join(homedir(), '.cache', 'codeledger', 'bin');
|
|
117
|
+
const BINARY_LOCAL_NAME = `codeledger-hardened-v${version}`;
|
|
118
|
+
const BINARY_LOCAL_PATH = join(CACHE_DIR, BINARY_LOCAL_NAME);
|
|
119
|
+
|
|
120
|
+
// ─── http ────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function fetchBufferOnce(url, redirectDepth = 0) {
|
|
123
|
+
if (redirectDepth > 5) {
|
|
124
|
+
return Promise.reject(new Error(`too many redirects fetching ${url}`));
|
|
125
|
+
}
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const req = https.get(
|
|
128
|
+
url,
|
|
129
|
+
{
|
|
130
|
+
headers: {
|
|
131
|
+
'User-Agent': `codeledger-installer/${version}`,
|
|
132
|
+
Accept: 'application/octet-stream',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
(res) => {
|
|
136
|
+
if (
|
|
137
|
+
(res.statusCode === 301 ||
|
|
138
|
+
res.statusCode === 302 ||
|
|
139
|
+
res.statusCode === 307 ||
|
|
140
|
+
res.statusCode === 308) &&
|
|
141
|
+
res.headers.location
|
|
142
|
+
) {
|
|
143
|
+
res.resume();
|
|
144
|
+
const next = new URL(res.headers.location, url).toString();
|
|
145
|
+
fetchBufferOnce(next, redirectDepth + 1).then(resolve, reject);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (res.statusCode !== 200) {
|
|
149
|
+
res.resume();
|
|
150
|
+
const err = new Error(`HTTP ${res.statusCode} fetching ${url}`);
|
|
151
|
+
err.statusCode = res.statusCode;
|
|
152
|
+
reject(err);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const chunks = [];
|
|
156
|
+
res.on('data', (c) => chunks.push(c));
|
|
157
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
158
|
+
res.on('error', reject);
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
req.on('error', reject);
|
|
162
|
+
req.setTimeout(60_000, () => {
|
|
163
|
+
req.destroy(new Error(`timeout fetching ${url}`));
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Retry wrapper that tolerates the post-publish race window.
|
|
169
|
+
//
|
|
170
|
+
// The release pipeline publishes the npm wrapper BEFORE the hardened
|
|
171
|
+
// binaries land on the public GitHub release (see the Phase 1d comment
|
|
172
|
+
// in release-dispatch.yml). For up to ~10 minutes after a user first
|
|
173
|
+
// sees @codeledger/cli@<version> on npm, the hardened binary URL may
|
|
174
|
+
// 404. This wrapper retries 404s with exponential backoff (10s, 20s,
|
|
175
|
+
// 40s, 60s, 60s, 60s, 60s, 60s, 60s, 60s → 450s total) so installs
|
|
176
|
+
// during the race window eventually succeed instead of hard-failing.
|
|
177
|
+
//
|
|
178
|
+
// Non-404 errors (DNS, network, timeouts) fail fast — retrying a DNS
|
|
179
|
+
// failure won't help.
|
|
180
|
+
async function fetchBuffer(url) {
|
|
181
|
+
const backoffs = [10, 20, 40, 60, 60, 60, 60, 60, 60, 60]; // seconds
|
|
182
|
+
let lastErr;
|
|
183
|
+
for (let i = 0; i <= backoffs.length; i++) {
|
|
184
|
+
try {
|
|
185
|
+
return await fetchBufferOnce(url);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
lastErr = err;
|
|
188
|
+
// Only retry on 404 — everything else is a hard failure
|
|
189
|
+
if (err.statusCode !== 404) throw err;
|
|
190
|
+
if (i === backoffs.length) break;
|
|
191
|
+
const wait = backoffs[i];
|
|
192
|
+
console.log(
|
|
193
|
+
`codeledger: 404 fetching ${url.split('/').pop()} — retrying in ${wait}s ` +
|
|
194
|
+
`(attempt ${i + 1}/${backoffs.length + 1}, ride race window)`,
|
|
195
|
+
);
|
|
196
|
+
await new Promise((r) => setTimeout(r, wait * 1000));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw lastErr;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── checksum parsing ───────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
function findExpectedSha(checksumsText, filename) {
|
|
205
|
+
// SHA256SUMS format: "<hex> <filename>\n" per line (coreutils sha256sum)
|
|
206
|
+
const lines = checksumsText.split('\n');
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
210
|
+
const parts = trimmed.split(/\s+/);
|
|
211
|
+
if (parts.length < 2) continue;
|
|
212
|
+
const [hex, ...nameParts] = parts;
|
|
213
|
+
const name = nameParts.join(' ').replace(/^\*/, ''); // strip binary-mode prefix
|
|
214
|
+
if (name === filename) {
|
|
215
|
+
return hex.toLowerCase();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── main ────────────────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
async function main() {
|
|
224
|
+
console.log(`codeledger: installing v${version} hardened binary for ${platformKey}`);
|
|
225
|
+
console.log(` source: ${BINARY_URL}`);
|
|
226
|
+
|
|
227
|
+
const [checksumsBuf, binary] = await Promise.all([
|
|
228
|
+
fetchBuffer(CHECKSUMS_URL),
|
|
229
|
+
fetchBuffer(BINARY_URL),
|
|
230
|
+
]);
|
|
231
|
+
const checksums = checksumsBuf.toString('utf8');
|
|
232
|
+
|
|
233
|
+
const expectedSha = findExpectedSha(checksums, BINARY_REMOTE_NAME);
|
|
234
|
+
if (!expectedSha) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`${BINARY_REMOTE_NAME} not listed in SHA256SUMS-v${version}.txt — cannot verify binary integrity`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const actualSha = createHash('sha256').update(binary).digest('hex');
|
|
241
|
+
if (actualSha !== expectedSha) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`SHA256 mismatch for ${BINARY_REMOTE_NAME}:\n expected ${expectedSha}\n got ${actualSha}`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
248
|
+
|
|
249
|
+
// Atomic-ish write: write to tmp, rename
|
|
250
|
+
const tmpPath = `${BINARY_LOCAL_PATH}.tmp`;
|
|
251
|
+
if (existsSync(tmpPath)) unlinkSync(tmpPath);
|
|
252
|
+
writeFileSync(tmpPath, binary);
|
|
253
|
+
chmodSync(tmpPath, 0o755);
|
|
254
|
+
if (existsSync(BINARY_LOCAL_PATH)) unlinkSync(BINARY_LOCAL_PATH);
|
|
255
|
+
// rename via fs for portability
|
|
256
|
+
require('node:fs').renameSync(tmpPath, BINARY_LOCAL_PATH);
|
|
257
|
+
|
|
258
|
+
console.log(`codeledger: installed ${BINARY_LOCAL_PATH}`);
|
|
259
|
+
console.log(` sha256: ${actualSha}`);
|
|
260
|
+
console.log('codeledger: run `codeledger --version` to verify');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Only run main() when invoked directly (npm postinstall, node scripts/postinstall.js).
|
|
264
|
+
// When require()'d from tests, just export the helper functions.
|
|
265
|
+
if (require.main === module) {
|
|
266
|
+
main().catch((err) => {
|
|
267
|
+
console.error(`codeledger: postinstall failed — ${err.message}`);
|
|
268
|
+
console.error('');
|
|
269
|
+
console.error('Recovery options:');
|
|
270
|
+
console.error(' 1. Retry: npm rebuild @codeledger/cli');
|
|
271
|
+
console.error(' 2. Air-gapped install:');
|
|
272
|
+
console.error(' CODELEDGER_SKIP_POSTINSTALL=1 npm install -g @codeledger/cli');
|
|
273
|
+
console.error(' then manually place the binary at:');
|
|
274
|
+
console.error(` ${BINARY_LOCAL_PATH}`);
|
|
275
|
+
console.error(' 3. Use a mirror:');
|
|
276
|
+
console.error(' CODELEDGER_BINARY_URL=https://your-mirror/path npm install -g @codeledger/cli');
|
|
277
|
+
console.error('');
|
|
278
|
+
console.error(
|
|
279
|
+
`Manual download: https://github.com/codeledgerECF/codeledger-blackbox/releases/tag/v${version}`,
|
|
280
|
+
);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = { findExpectedSha, PLATFORM_MAP };
|