@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/cli",
3
- "version": "0.25.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"
@@ -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
- 768f6897ab2dc8ea7436505c32d122c3eaa555b2ce20647422d835f69bffefed evercli_darwin_amd64.tar.gz
2
- b9e5daddac0b9e746891304bfbde3fea1ea392d5aeb45b2d7700a32be58f0291 evercli_darwin_arm64.tar.gz
3
- 1d8f16039f036ae1bc71ccfd28159f42ccd744a42f73e2f240650e5331bde242 evercli_linux_amd64.tar.gz
4
- c8128ea80bfd5d4d20e140d5435dc9750689a3e5510130cb168f76d3a5f33d15 evercli_linux_arm64.tar.gz
5
- 768b6f32e3a1a62a2d9f41cc85a7f58b3e94ae760fe96a621bd23c0367fea302 evercli_windows_amd64.zip
6
- 871bf7299398e11d56d45f24a38ebdaa973d2a3d010fce05dbe1ec36e9299e09 evercli_windows_arm64.zip
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