yaver-cli 1.99.458 → 1.99.460

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": "yaver-cli",
3
- "version": "1.99.458",
3
+ "version": "1.99.460",
4
4
  "mcpName": "io.github.yaver-io/yaver",
5
5
  "description": "Unified npm bootstrap for the Yaver agent, SDK injection, and local-first developer runtime",
6
6
  "bin": {
@@ -32,7 +32,17 @@ const EXECUTABLE_MAGICS = [
32
32
  Buffer.from([0x4d, 0x5a]), // PE (Windows) — MZ
33
33
  ];
34
34
 
35
- async function ensureAgentBinary({ quiet = false } = {}) {
35
+ async function ensureAgentBinary({
36
+ quiet = false,
37
+ resolveBinary = resolveAgentBinary,
38
+ refreshLink = refreshCurrentSymlink,
39
+ } = {}) {
40
+ const binaryPath = await resolveBinary({ quiet });
41
+ refreshLink(binaryPath, { quiet });
42
+ return binaryPath;
43
+ }
44
+
45
+ async function resolveAgentBinary({ quiet = false } = {}) {
36
46
  // Try cache first — when the user already has any version of yaver
37
47
  // installed locally, we shouldn't fail just because GitHub's API
38
48
  // rate-limited the unauthenticated /releases call. Falling back to
@@ -96,6 +106,69 @@ async function ensureAgentBinary({ quiet = false } = {}) {
96
106
  }
97
107
  }
98
108
 
109
+ // Keep the supervisor's stable path aligned with the binary the wrapper has
110
+ // actually selected. Postinstall also does this, but agent releases can be
111
+ // discovered and downloaded lazily on any later CLI invocation. Before this
112
+ // guard, that path ran the new binary for the command while launchd/systemd
113
+ // continued restarting `current`, which could still name an older version
114
+ // (observed 2026-09-08: wrapper 1.99.459, live launchd agent 1.99.452).
115
+ //
116
+ // Use an atomic rename so a concurrent supervisor restart never observes a
117
+ // missing `current` entry. The function is deliberately best-effort: resolving
118
+ // a usable agent must not fail because a local symlink cannot be repaired.
119
+ function refreshCurrentSymlink(binaryPath, {
120
+ quiet = false,
121
+ platform = process.platform,
122
+ cacheRoot = CACHE_ROOT,
123
+ fsImpl = fs,
124
+ } = {}) {
125
+ let temporaryLink = '';
126
+ try {
127
+ if (platform === 'win32' || !binaryPath) return 'skipped';
128
+
129
+ const resolvedCacheRoot = path.resolve(cacheRoot);
130
+ const versionDir = path.resolve(path.dirname(path.dirname(binaryPath)));
131
+ const relativeVersionDir = path.relative(resolvedCacheRoot, versionDir);
132
+ if (
133
+ !relativeVersionDir ||
134
+ relativeVersionDir.startsWith(`..${path.sep}`) ||
135
+ path.isAbsolute(relativeVersionDir) ||
136
+ !semver.valid(path.basename(versionDir)) ||
137
+ !fsImpl.existsSync(versionDir)
138
+ ) {
139
+ return 'skipped';
140
+ }
141
+
142
+ const currentLink = path.join(resolvedCacheRoot, 'current');
143
+ try {
144
+ const existingTarget = fsImpl.readlinkSync(currentLink);
145
+ const existingResolved = path.resolve(path.dirname(currentLink), existingTarget);
146
+ if (existingResolved === versionDir) return 'already-current';
147
+ } catch (_) {}
148
+
149
+ temporaryLink = path.join(
150
+ resolvedCacheRoot,
151
+ `.current-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
152
+ );
153
+ fsImpl.symlinkSync(versionDir, temporaryLink);
154
+ fsImpl.renameSync(temporaryLink, currentLink);
155
+ temporaryLink = '';
156
+ if (!quiet) {
157
+ console.error(`[yaver] Repointed ~/.yaver/bin/current -> ${path.basename(versionDir)}`);
158
+ }
159
+ return 'repointed';
160
+ } catch (err) {
161
+ if (!quiet) {
162
+ console.error(`[yaver] Could not refresh the current agent link: ${err.message}`);
163
+ }
164
+ return 'failed';
165
+ } finally {
166
+ if (temporaryLink) {
167
+ try { fsImpl.unlinkSync(temporaryLink); } catch (_) {}
168
+ }
169
+ }
170
+ }
171
+
99
172
  /** A cached agent binary is only usable if it actually RUNS.
100
173
  *
101
174
  * `fs.existsSync` answers "is there a file here", which is a proxy for the
@@ -730,6 +803,7 @@ async function extractTarball(archivePath, destDir) {
730
803
  module.exports = {
731
804
  ensureAgentBinary,
732
805
  ensureValidMacSignature,
806
+ refreshCurrentSymlink,
733
807
  resolveAgentInfo,
734
808
  runAgentCommand,
735
809
  };
@@ -400,39 +400,6 @@ function ensureZshenvRescue() {
400
400
  }
401
401
  }
402
402
 
403
- // Update ~/.yaver/bin/current → versioned dir for the binary just
404
- // installed. Without this, the symlink can lag behind npm — every time
405
- // I bumped CLI on the user's Mac mini, `~/.yaver/bin/current` kept
406
- // pointing at an older version, so `~/.yaver/bin/current/<arch>/yaver`
407
- // silently ran the wrong binary even though `npm install -g yaver-cli@
408
- // <new>` had succeeded. The auto-update watchdogs (heartbeat_watcher
409
- // systemctl/launchctl kickstart paths) trust `current` too — so a
410
- // stale symlink keeps the old binary running across a "restart"
411
- // without anyone noticing. This belongs in postinstall, runs every
412
- // global install, never throws.
413
- function refreshCurrentSymlink(binaryPath) {
414
- try {
415
- if (process.platform === "win32") return;
416
- if (!binaryPath) return;
417
- // binaryPath is like ~/.yaver/bin/<version>/<cacheKey>/yaver — walk
418
- // up two levels to the version dir, point `current` at it.
419
- const versionDir = path.dirname(path.dirname(binaryPath));
420
- if (!versionDir.startsWith(path.join(os.homedir(), ".yaver", "bin") + path.sep)) {
421
- return;
422
- }
423
- if (!fs.existsSync(versionDir)) return;
424
- const symlink = path.join(os.homedir(), ".yaver", "bin", "current");
425
- let already = "";
426
- try { already = fs.readlinkSync(symlink); } catch (_) {}
427
- if (already === versionDir) return;
428
- try { fs.unlinkSync(symlink); } catch (_) {}
429
- fs.symlinkSync(versionDir, symlink);
430
- log(`Repointed ~/.yaver/bin/current → ${path.basename(versionDir)}`);
431
- } catch (err) {
432
- log(`Skipping current symlink refresh: ${err.message}`);
433
- }
434
- }
435
-
436
403
  // Restart whichever service supervisor the user has registered. Without
437
404
  // this, a fresh `npm install -g yaver-cli@latest` updates the binary on
438
405
  // disk but leaves the still-running agent process on the OLD binary
@@ -497,10 +464,8 @@ async function main() {
497
464
  log(`Skipping agent prefetch: ${error.message}`);
498
465
  return;
499
466
  }
500
- // Repoint the canonical `current` symlink + bounce any registered
501
- // service so a fresh `npm install -g yaver-cli@latest` actually
502
- // takes effect without a manual `yaver restart`.
503
- refreshCurrentSymlink(installedBinary);
467
+ // ensureAgentBinary has atomically reconciled the canonical `current`
468
+ // symlink. Bounce any registered service so the selected binary is live.
504
469
  bounceRunningAgent();
505
470
 
506
471
  if (isIotEdgeInstall()) {