@everme/cli 0.1.0-beta.4 → 0.1.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.
- package/README.md +5 -10
- package/package.json +3 -3
- package/scripts/install.js +60 -13
- package/scripts/run.js +13 -1
- package/sha256sums.txt +6 -6
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# @everme/cli
|
|
2
2
|
|
|
3
|
-
npm-distributed installer for [EverMe CLI](https://github.com/
|
|
3
|
+
npm-distributed installer for [EverMe CLI](https://github.com/EverMind-AI/EverMe-CLI). Detects your platform on install, downloads the matching pre-built binary from GitHub Releases, verifies its SHA-256 checksum, and exposes it as the `evercli` command.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install -g @everme/cli
|
|
6
|
+
npm install -g @everme/cli
|
|
7
7
|
evercli --help
|
|
8
8
|
|
|
9
9
|
# or, no install
|
|
10
|
-
npx @everme/cli
|
|
10
|
+
npx @everme/cli --version
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## What this package does
|
|
@@ -15,7 +15,7 @@ npx @everme/cli@beta --version
|
|
|
15
15
|
`@everme/cli` is a thin Node-side installer + runner; the actual CLI is a pre-compiled binary downloaded on first install. On `npm install`:
|
|
16
16
|
|
|
17
17
|
1. Detect platform / arch (darwin/linux/windows × amd64/arm64)
|
|
18
|
-
2. Download the matching archive from `https://github.com/
|
|
18
|
+
2. Download the matching archive from `https://github.com/EverMind-AI/EverMe-CLI/releases/download/v<version>/evercli_<os>_<arch>.{tar.gz|zip}`
|
|
19
19
|
3. Verify SHA-256 against the `sha256sums.txt` shipped inside this npm package
|
|
20
20
|
4. Extract and place the binary under the package's `bin/` directory
|
|
21
21
|
5. The `evercli` shim (`scripts/run.js`) execs that binary with your args
|
|
@@ -26,7 +26,7 @@ If `postinstall` was skipped (some `npx` flows, restricted CI), the installer ru
|
|
|
26
26
|
|
|
27
27
|
The installer tries download sources in this order:
|
|
28
28
|
|
|
29
|
-
1. `https://github.com/
|
|
29
|
+
1. `https://github.com/EverMind-AI/EverMe-CLI/releases/...` (canonical)
|
|
30
30
|
2. The npm registry's binary mirror path (`<registry>/-/binary/everme-cli/...`), if your `npm_config_registry` is non-default
|
|
31
31
|
3. `https://registry.npmmirror.com/-/binary/everme-cli/...` (always tried as final fallback)
|
|
32
32
|
|
|
@@ -41,11 +41,6 @@ export EVERCLI_API_BASE_URL=https://memory.acme-internal.com
|
|
|
41
41
|
evercli auth login
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
## Release channels
|
|
45
|
-
|
|
46
|
-
- `@everme/cli@beta` — pre-release builds
|
|
47
|
-
- `@everme/cli@latest` — stable releases
|
|
48
|
-
|
|
49
44
|
## License
|
|
50
45
|
|
|
51
46
|
Apache-2.0.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everme/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "EverMe CLI — npm wrapper that downloads and runs the platform-native evercli binary on first use.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"homepage": "https://everme.evermind.ai",
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/
|
|
44
|
+
"url": "git+https://github.com/EverMind-AI/EverMe-CLI.git",
|
|
45
45
|
"directory": "plugins/cli"
|
|
46
46
|
},
|
|
47
47
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/
|
|
48
|
+
"url": "https://github.com/EverMind-AI/EverMe-CLI/issues"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public",
|
package/scripts/install.js
CHANGED
|
@@ -25,12 +25,8 @@ const os = require("os");
|
|
|
25
25
|
const crypto = require("crypto");
|
|
26
26
|
const { execFileSync } = require("child_process");
|
|
27
27
|
|
|
28
|
-
// Keep prerelease suffix (e.g. "0.1.0-beta.1") intact — GoReleaser tags
|
|
29
|
-
// match package.json version verbatim, so stripping after `-` would build
|
|
30
|
-
// TAG = cli/v0.1.0 for a 0.1.0-beta.1 publish and 404 against the real
|
|
31
|
-
// cli/v0.1.0-beta.1 release.
|
|
32
28
|
const VERSION = require("../package.json").version;
|
|
33
|
-
const REPO = "
|
|
29
|
+
const REPO = "EverMind-AI/EverMe-CLI";
|
|
34
30
|
const NAME = "evercli";
|
|
35
31
|
|
|
36
32
|
// Allowlist gates the *initial* request URL. curl --location follows redirects
|
|
@@ -182,16 +178,24 @@ function extractZipWindows(archivePath, destDir) {
|
|
|
182
178
|
|
|
183
179
|
/**
|
|
184
180
|
* Look up the SHA256 checksum for the archive from sha256sums.txt that ships
|
|
185
|
-
* inside this npm package.
|
|
186
|
-
*
|
|
187
|
-
*
|
|
181
|
+
* inside this npm package. sha256sums.txt is listed in package.json::files,
|
|
182
|
+
* so a real `npm install` will always include it. A missing file is either
|
|
183
|
+
* a tampered tarball or a broken publish — in both cases skipping the check
|
|
184
|
+
* defeats the integrity model, so we hard-fail instead of warning.
|
|
185
|
+
*
|
|
186
|
+
* The workspace dev-install path is already short-circuited above
|
|
187
|
+
* (`__dirname.includes("/node_modules/")`), so this branch is only ever
|
|
188
|
+
* reached for installs that MUST verify.
|
|
188
189
|
*/
|
|
189
190
|
function getExpectedChecksum(archive, checksumsDir) {
|
|
190
191
|
const dir = checksumsDir || path.join(__dirname, "..");
|
|
191
192
|
const checksumsPath = path.join(dir, "sha256sums.txt");
|
|
192
193
|
if (!fs.existsSync(checksumsPath)) {
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
throw new Error(
|
|
195
|
+
`[SECURITY] sha256sums.txt missing at ${checksumsPath}; refusing to install ` +
|
|
196
|
+
`an unverified binary. The file ships with the npm package — its absence ` +
|
|
197
|
+
`indicates tampering or a broken release.`,
|
|
198
|
+
);
|
|
195
199
|
}
|
|
196
200
|
const content = fs.readFileSync(checksumsPath, "utf8");
|
|
197
201
|
for (const line of content.split("\n")) {
|
|
@@ -207,8 +211,34 @@ function getExpectedChecksum(archive, checksumsDir) {
|
|
|
207
211
|
throw new Error(`Checksum entry not found for ${archive}`);
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Defends against zip-slip: an archive can ship `evercli` as a symlink to
|
|
216
|
+
* `/etc/passwd` (or any path outside tmpDir). copyFileSync follows symlinks,
|
|
217
|
+
* so without this check the extracted "binary" we ship to <pkg>/bin could
|
|
218
|
+
* be sourced from anywhere. lstat catches the symlink case (we reject non-
|
|
219
|
+
* regular files); realpath resolves what the path actually points at and we
|
|
220
|
+
* assert it stays within tmpDir.
|
|
221
|
+
*/
|
|
222
|
+
function assertInsideDir(dir, candidate) {
|
|
223
|
+
const stat = fs.lstatSync(candidate);
|
|
224
|
+
if (!stat.isFile()) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`[SECURITY] Extracted entry is not a regular file (mode=${stat.mode.toString(8)}); ` +
|
|
227
|
+
`refusing to install. Path: ${candidate}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const realDir = fs.realpathSync(dir);
|
|
231
|
+
const realCandidate = fs.realpathSync(candidate);
|
|
232
|
+
const sep = path.sep;
|
|
233
|
+
if (realCandidate !== realDir && !realCandidate.startsWith(realDir + sep)) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`[SECURITY] Extracted path escapes archive root: ${realCandidate} not under ${realDir}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return realCandidate;
|
|
239
|
+
}
|
|
240
|
+
|
|
210
241
|
function verifyChecksum(archivePath, expectedHash) {
|
|
211
|
-
if (expectedHash === null) return;
|
|
212
242
|
// Stream to keep RSS constant; archives can be ~10MB+.
|
|
213
243
|
const hash = crypto.createHash("sha256");
|
|
214
244
|
const fd = fs.openSync(archivePath, "r");
|
|
@@ -260,11 +290,27 @@ function install() {
|
|
|
260
290
|
if (isWindows) {
|
|
261
291
|
extractZipWindows(archivePath, tmpDir);
|
|
262
292
|
} else {
|
|
263
|
-
|
|
293
|
+
// --no-same-owner / --no-same-permissions matter when postinstall
|
|
294
|
+
// runs as root (e.g. system-wide npm install) — without them, a
|
|
295
|
+
// hostile archive could set setuid/world-writable modes on the
|
|
296
|
+
// extracted binary. The flags are accepted by both GNU tar (Linux)
|
|
297
|
+
// and BSD tar (macOS). Modern tar already rejects ".." paths by
|
|
298
|
+
// default; the post-extraction realpath check below is the second
|
|
299
|
+
// line of defense against zip-slip via symlinks.
|
|
300
|
+
execFileSync(
|
|
301
|
+
"tar",
|
|
302
|
+
[
|
|
303
|
+
"-xzf", archivePath,
|
|
304
|
+
"-C", tmpDir,
|
|
305
|
+
"--no-same-owner",
|
|
306
|
+
"--no-same-permissions",
|
|
307
|
+
],
|
|
308
|
+
{ stdio: "ignore" },
|
|
309
|
+
);
|
|
264
310
|
}
|
|
265
311
|
|
|
266
312
|
const binaryName = NAME + (isWindows ? ".exe" : "");
|
|
267
|
-
const extractedBinary = path.join(tmpDir, binaryName);
|
|
313
|
+
const extractedBinary = assertInsideDir(tmpDir, path.join(tmpDir, binaryName));
|
|
268
314
|
fs.copyFileSync(extractedBinary, dest);
|
|
269
315
|
fs.chmodSync(dest, 0o755);
|
|
270
316
|
console.log(`${NAME} v${VERSION} installed successfully`);
|
|
@@ -331,5 +377,6 @@ module.exports = {
|
|
|
331
377
|
getExpectedChecksum,
|
|
332
378
|
verifyChecksum,
|
|
333
379
|
assertAllowedHost,
|
|
380
|
+
assertInsideDir,
|
|
334
381
|
resolveMirrorUrls,
|
|
335
382
|
};
|
package/scripts/run.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
const { execFileSync } = require("child_process");
|
|
24
24
|
const fs = require("fs");
|
|
25
|
+
const os = require("os");
|
|
25
26
|
const path = require("path");
|
|
26
27
|
|
|
27
28
|
const NAME = "evercli";
|
|
@@ -83,6 +84,17 @@ if (args[0] === "install") {
|
|
|
83
84
|
try {
|
|
84
85
|
execFileSync(bin, args, { stdio: "inherit" });
|
|
85
86
|
} catch (e) {
|
|
86
|
-
|
|
87
|
+
// Preserve the child's exit semantics. execFileSync throws an Error
|
|
88
|
+
// whose `.status` is the exit code OR null when the child was killed
|
|
89
|
+
// by a signal (`.signal` is set instead). Mapping signal → 128+N
|
|
90
|
+
// matches POSIX shell convention so callers (CI, parent shells) can
|
|
91
|
+
// distinguish SIGINT (130) from a clean exit 1.
|
|
92
|
+
if (typeof e.status === "number") {
|
|
93
|
+
process.exit(e.status);
|
|
94
|
+
}
|
|
95
|
+
if (e.signal && os.constants.signals && os.constants.signals[e.signal]) {
|
|
96
|
+
process.exit(128 + os.constants.signals[e.signal]);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
87
99
|
}
|
|
88
100
|
}
|
package/sha256sums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
ebe4459a6efeaeb7fab2d210208ff64b3d787338fad66690add284eff0442047 evercli_darwin_amd64.tar.gz
|
|
2
|
+
285c4d77d69b4850a1b84156b4196a0800af8ddb16428558414c6ceb9f91dda6 evercli_darwin_arm64.tar.gz
|
|
3
|
+
8ac26b56ed87875764c7cf84567e8123596565d33459a2278f95e00070db8251 evercli_linux_amd64.tar.gz
|
|
4
|
+
8f2d75be861849f1c5bb8bbfb8437b3106a4ed48c2f40d268b4b21d5ea277cb7 evercli_linux_arm64.tar.gz
|
|
5
|
+
6477aa4da68d15d8a2bba1bcdb6e39f3122215319cba4e44c60ac91e644fcbe2 evercli_windows_amd64.zip
|
|
6
|
+
dc17458b21b1e26958d9946506deeae40d8f4b4885beea926a95fd9487a26b58 evercli_windows_arm64.zip
|