@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/cli",
3
- "version": "0.25.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"
@@ -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
+ 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