@everme/cli 0.2.4 → 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.2.4",
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
- b4a36757fd0e48427e82be57863f091e590794a48be44cdbae5cd0d8e5ec7010 evercli_darwin_amd64.tar.gz
2
- 2b67e5feed77e12644240f0268b6f29ad1b77527b467d07f7f8bcb07fa526ad3 evercli_darwin_arm64.tar.gz
3
- 48c3b712ee28f59d10fc46a5f9d99ea700a610aef0bd78803ed670ec96518f2a evercli_linux_amd64.tar.gz
4
- f0f15e8e0412257dd62799137edf1dfcdb2bd9884dc825a5269965be8f4a1044 evercli_linux_arm64.tar.gz
5
- ab209d05674c924ea44d74a0d46e22699690e209b1ec9dac50a8e881c883124e evercli_windows_amd64.zip
6
- 021d3f19e033621b4392be9ef02bab7c15012eb8751f541a60747436ab1cecb8 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