@everme/cli 0.25.0 → 0.26.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/package.json +3 -2
- package/scripts/install.js +9 -0
- package/scripts/upgrade-plugins.js +201 -0
- package/sha256sums.txt +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everme/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "node scripts/install.js",
|
|
11
|
-
"test": "node --test tests/install.test.js"
|
|
11
|
+
"test": "node --test tests/install.test.js tests/upgrade-plugins.test.js"
|
|
12
12
|
},
|
|
13
13
|
"os": [
|
|
14
14
|
"darwin",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"scripts/install.js",
|
|
27
27
|
"scripts/install-wizard.js",
|
|
28
28
|
"scripts/run.js",
|
|
29
|
+
"scripts/upgrade-plugins.js",
|
|
29
30
|
"sha256sums.txt",
|
|
30
31
|
"LICENSE",
|
|
31
32
|
"README.md"
|
package/scripts/install.js
CHANGED
|
@@ -24,6 +24,7 @@ const path = require("path");
|
|
|
24
24
|
const os = require("os");
|
|
25
25
|
const crypto = require("crypto");
|
|
26
26
|
const { execFileSync } = require("child_process");
|
|
27
|
+
const { refreshPlugins } = require("./upgrade-plugins.js");
|
|
27
28
|
|
|
28
29
|
const VERSION = require("../package.json").version;
|
|
29
30
|
const REPO = "EverMind-AI/EverMe";
|
|
@@ -359,6 +360,14 @@ if (require.main === module) {
|
|
|
359
360
|
|
|
360
361
|
try {
|
|
361
362
|
install();
|
|
363
|
+
// One-shot plugin refresh: bring already-installed hosts to the latest
|
|
364
|
+
// plugin line. Never fatal — guards + per-host try/catch live in the
|
|
365
|
+
// module; we belt-and-suspenders wrap here so nothing fails `npm i -g`.
|
|
366
|
+
try {
|
|
367
|
+
refreshPlugins({ binPath: dest, env: process.env });
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.error(`[@everme/cli] plugin refresh skipped: ${e.message}`);
|
|
370
|
+
}
|
|
362
371
|
} catch (err) {
|
|
363
372
|
console.error(`Failed to install ${NAME}:`, err.message);
|
|
364
373
|
console.error(
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Copyright 2026 Evermind AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plugin refresh invoked from @everme/cli postinstall. After the new binary
|
|
6
|
+
* is on disk, bring already-installed plugin hosts up to the latest plugin
|
|
7
|
+
* line — without any new evercli subcommand. Discovery reuses the read-only
|
|
8
|
+
* `plugin list`; refresh is per host class (see spec
|
|
9
|
+
* .work_context/specs/2026-06-23-cli-postinstall-one-shot-upgrade-design.md).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const { execFileSync } = require("child_process");
|
|
16
|
+
|
|
17
|
+
// Quote-delimited bare/latest spec pairs. JSON host configs (cursor, gemini,
|
|
18
|
+
// claude-desktop, opencode) use double quotes; codex's config.toml uses single
|
|
19
|
+
// quotes. Each pair is inherently idempotent — once `@latest` is present the
|
|
20
|
+
// bare quoted token no longer exists (the closing quote sits after `@latest`).
|
|
21
|
+
const MCP_SPEC_PAIRS = [
|
|
22
|
+
['"@everme/memory-mcp"', '"@everme/memory-mcp@latest"'],
|
|
23
|
+
["'@everme/memory-mcp'", "'@everme/memory-mcp@latest'"],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const NPX_PLATFORMS = new Set(["cursor", "claude-desktop", "codex", "gemini", "opencode"]);
|
|
27
|
+
|
|
28
|
+
const LIST_TIMEOUT_MS = 15000;
|
|
29
|
+
const REFRESH_TIMEOUT_MS = 60000;
|
|
30
|
+
|
|
31
|
+
function npmCommand() {
|
|
32
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function defaultRunner(file, args, opts) {
|
|
36
|
+
return execFileSync(file, args, opts);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// discoverHosts runs the read-only `plugin list --format json` against the
|
|
40
|
+
// freshly-downloaded binary and returns the platforms array. Throws if the
|
|
41
|
+
// envelope is missing or not ok so the caller treats discovery as failed.
|
|
42
|
+
function discoverHosts(binPath, runner) {
|
|
43
|
+
const run = runner || defaultRunner;
|
|
44
|
+
const out = run(binPath, ["plugin", "list", "--format", "json"], {
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
timeout: LIST_TIMEOUT_MS,
|
|
47
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
48
|
+
});
|
|
49
|
+
const env = JSON.parse(out);
|
|
50
|
+
if (!env || env.ok !== true || !env.data || !Array.isArray(env.data.platforms)) {
|
|
51
|
+
throw new Error("unexpected `plugin list` envelope shape");
|
|
52
|
+
}
|
|
53
|
+
return env.data.platforms;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// patchNpxSpec adds @latest to the memory-mcp spec in a host config file by a
|
|
57
|
+
// literal, format-agnostic replace of the quoted bare token. Idempotent: the
|
|
58
|
+
// quoted bare token no longer exists once @latest is present. Touches only the
|
|
59
|
+
// package spec — never credential fields.
|
|
60
|
+
//
|
|
61
|
+
// The replace is atomic: write a sibling temp file, fsync, then rename over the
|
|
62
|
+
// original. These configs carry credentials, so an interrupted in-place
|
|
63
|
+
// truncate/write (crash, disk full) must never leave a corrupted file. The
|
|
64
|
+
// original mode is preserved (host configs are 0600).
|
|
65
|
+
function patchNpxSpec(configPath) {
|
|
66
|
+
const before = fs.readFileSync(configPath, "utf8");
|
|
67
|
+
let after = before;
|
|
68
|
+
for (const [bare, latest] of MCP_SPEC_PAIRS) {
|
|
69
|
+
if (after.includes(bare)) after = after.split(bare).join(latest);
|
|
70
|
+
}
|
|
71
|
+
if (after === before) return false;
|
|
72
|
+
const mode = fs.statSync(configPath).mode & 0o777;
|
|
73
|
+
const tmp = `${configPath}.everme-tmp-${process.pid}`;
|
|
74
|
+
try {
|
|
75
|
+
const fd = fs.openSync(tmp, "w", mode);
|
|
76
|
+
try {
|
|
77
|
+
fs.writeFileSync(fd, after);
|
|
78
|
+
fs.fsyncSync(fd);
|
|
79
|
+
} finally {
|
|
80
|
+
fs.closeSync(fd);
|
|
81
|
+
}
|
|
82
|
+
fs.chmodSync(tmp, mode);
|
|
83
|
+
fs.renameSync(tmp, configPath);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
try {
|
|
86
|
+
fs.unlinkSync(tmp);
|
|
87
|
+
} catch (_) {
|
|
88
|
+
// temp file may not exist if openSync failed — ignore.
|
|
89
|
+
}
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// refreshClaudeCode brings Claude Code to the latest plugin payload. Two steps
|
|
96
|
+
// are required: `evercli plugin install claude-code` reuses an already-present
|
|
97
|
+
// global npm package without upgrading it (pluginSourceSpec short-circuits on
|
|
98
|
+
// the existing `npm root -g` path), so the @latest bump must happen first; the
|
|
99
|
+
// re-install then runs `claude plugin marketplace add` + `claude plugin install`
|
|
100
|
+
// so Claude Code actually loads the fresh hooks/commands/MCP. Step 2 rotates the
|
|
101
|
+
// local agent token (per-(platform, fingerprint) — other devices unaffected).
|
|
102
|
+
function refreshClaudeCode(binPath, runner) {
|
|
103
|
+
const run = runner || defaultRunner;
|
|
104
|
+
run(npmCommand(), ["install", "-g", "@everme/claude-code@latest"], {
|
|
105
|
+
encoding: "utf8",
|
|
106
|
+
timeout: REFRESH_TIMEOUT_MS,
|
|
107
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
108
|
+
});
|
|
109
|
+
run(binPath, ["plugin", "install", "claude-code"], {
|
|
110
|
+
encoding: "utf8",
|
|
111
|
+
timeout: REFRESH_TIMEOUT_MS,
|
|
112
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// `plugins update <id>` is the refresh verb: it bumps an already-tracked plugin
|
|
117
|
+
// to latest by id (not an npm spec) and leaves openclaw.json alone, unlike
|
|
118
|
+
// `install --force`, which reruns the full install-time config scaffolding and
|
|
119
|
+
// rewrites the credential-bearing config. The host is already installed in the
|
|
120
|
+
// refresh context; if it somehow isn't tracked, update errors and the caller
|
|
121
|
+
// degrades it to a warning.
|
|
122
|
+
function refreshOpenClaw(runner) {
|
|
123
|
+
const run = runner || defaultRunner;
|
|
124
|
+
run("openclaw", ["plugins", "update", "@everme/openclaw"], {
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
timeout: REFRESH_TIMEOUT_MS,
|
|
127
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function refreshHermes(binPath, runner) {
|
|
132
|
+
const run = runner || defaultRunner;
|
|
133
|
+
run(binPath, ["plugin", "install", "hermes"], {
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
timeout: REFRESH_TIMEOUT_MS,
|
|
136
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function skipReason(env) {
|
|
141
|
+
if (env.EVERME_SKIP_PLUGIN_UPGRADE) return "EVERME_SKIP_PLUGIN_UPGRADE is set";
|
|
142
|
+
if (env.CI) return "CI environment";
|
|
143
|
+
if (typeof process.getuid === "function" && process.getuid() === 0) return "running as root";
|
|
144
|
+
if (env.SUDO_USER) return "running under sudo";
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// refreshPlugins is the postinstall entrypoint. It NEVER throws: every failure
|
|
149
|
+
// degrades to a stderr warning so `npm i -g` always succeeds.
|
|
150
|
+
function refreshPlugins(opts) {
|
|
151
|
+
const { binPath, env, runner } = opts;
|
|
152
|
+
const log = opts.logger || ((m) => console.error(m));
|
|
153
|
+
|
|
154
|
+
const skip = skipReason(env);
|
|
155
|
+
if (skip) {
|
|
156
|
+
log(`[@everme/cli] Skipping plugin refresh (${skip}). ` +
|
|
157
|
+
`Re-run \`npm i -g @everme/cli@latest\` as your normal user to refresh plugins.`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let hosts;
|
|
162
|
+
try {
|
|
163
|
+
hosts = discoverHosts(binPath, runner);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
log(`[@everme/cli] Could not enumerate plugin hosts; skipping plugin refresh: ${e.message}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const h of hosts) {
|
|
170
|
+
if (!h || h.hasEverMeEntry !== true) continue;
|
|
171
|
+
try {
|
|
172
|
+
if (NPX_PLATFORMS.has(h.platform)) {
|
|
173
|
+
if (h.configPath && patchNpxSpec(h.configPath)) {
|
|
174
|
+
log(`[@everme/cli] ${h.platform}: pinned memory-mcp to @latest`);
|
|
175
|
+
}
|
|
176
|
+
} else if (h.platform === "claude-code") {
|
|
177
|
+
refreshClaudeCode(binPath, runner);
|
|
178
|
+
} else if (h.platform === "openclaw") {
|
|
179
|
+
refreshOpenClaw(runner);
|
|
180
|
+
} else if (h.platform === "hermes") {
|
|
181
|
+
refreshHermes(binPath, runner);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
log(`[@everme/cli] ${h.platform}: refresh failed (continuing): ${e.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
NPX_PLATFORMS,
|
|
191
|
+
LIST_TIMEOUT_MS,
|
|
192
|
+
REFRESH_TIMEOUT_MS,
|
|
193
|
+
npmCommand,
|
|
194
|
+
discoverHosts,
|
|
195
|
+
patchNpxSpec,
|
|
196
|
+
refreshClaudeCode,
|
|
197
|
+
refreshOpenClaw,
|
|
198
|
+
refreshHermes,
|
|
199
|
+
skipReason,
|
|
200
|
+
refreshPlugins,
|
|
201
|
+
};
|
package/sha256sums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
3f8e635e9a9a666fcfc3ed3a7cd3a765bc7a89d45de02e001b41d6b98461987d evercli_darwin_amd64.tar.gz
|
|
2
|
+
c600f0fff31c8fb69fa6288c97ef7f554501e626105c07f5ed0dfa72e9dae244 evercli_darwin_arm64.tar.gz
|
|
3
|
+
29b4a45d8d968d8a1eac53bebbae592312fb0ca7f8a2bb6d5641b22cc1e8aa91 evercli_linux_amd64.tar.gz
|
|
4
|
+
623bb61d4ec06015b872b936f221b8debef50a3d10b61aca74c0fd876ffaa3a7 evercli_linux_arm64.tar.gz
|
|
5
|
+
d8acabbdee29bf28a456e2c62bd460df71a7d82469e2f70eaf139a18bc6a0a88 evercli_windows_amd64.zip
|
|
6
|
+
bca20616dd634969a1b928f3179fc1d5e94840064a4572c5a6ef0e93a54be795 evercli_windows_arm64.zip
|