@everme/cli 0.25.0 → 0.26.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/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.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": {
|
|
@@ -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
|
+
8553ccaa870d067424c56df3c82a4ce91189b6f98d509455714f90a9f7bda4b9 evercli_darwin_amd64.tar.gz
|
|
2
|
+
2a31902a5cfeeb553588f6eb4f81be9b65cd87d3934ac8466933a8f4afae7110 evercli_darwin_arm64.tar.gz
|
|
3
|
+
4a0e7879a423733a7b1eaa59b908e3235bb04aeb2ef0c8b4433c16a3e6176c0f evercli_linux_amd64.tar.gz
|
|
4
|
+
916a0a0cf4018e77989e331fa4245d5bbf344020079ae1821944d748739f56f5 evercli_linux_arm64.tar.gz
|
|
5
|
+
154eb3cdfc568d39a5e2fdceef5cdef6ad24fa5a2e3d1292c6b8f9ff16b123e5 evercli_windows_amd64.zip
|
|
6
|
+
5dec906014eceba9fbccef052181e813ce06cef046729005581942a83af38867 evercli_windows_arm64.zip
|