@pellux/goodvibes-tui 1.14.0 → 1.15.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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.15.0] — 2026-07-11
8
+
9
+ ### Changes
10
+ - e95c369d fix(install): treat a release without a sqlite-vec manifest entry as pre-addon
11
+ - 86c1cd9c feat(install): ship the sqlite-vec native addon to pure-binary installs
12
+ - b5590ef3 install.sh: install goodvibes-agent as a checksum-verified compiled binary; restart a running agent post-install
13
+ - f0860827 install.sh: restart a running daemon after the binary swap so the curl one-liner works as an upgrade
14
+
7
15
  ## [1.14.0] — 2026-07-11
8
16
 
9
17
  ### Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-1.14.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-1.15.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -131,10 +131,10 @@
131
131
  "typescript": "^5.9.3"
132
132
  },
133
133
  "optionalDependencies": {
134
- "@pellux/goodvibes-tui-linux-x64": "1.14.0",
135
- "@pellux/goodvibes-tui-linux-arm64": "1.14.0",
136
- "@pellux/goodvibes-tui-darwin-x64": "1.14.0",
137
- "@pellux/goodvibes-tui-darwin-arm64": "1.14.0"
134
+ "@pellux/goodvibes-tui-linux-x64": "1.15.0",
135
+ "@pellux/goodvibes-tui-linux-arm64": "1.15.0",
136
+ "@pellux/goodvibes-tui-darwin-x64": "1.15.0",
137
+ "@pellux/goodvibes-tui-darwin-arm64": "1.15.0"
138
138
  },
139
139
  "trustedDependencies": [],
140
140
  "overrides": {
@@ -9,10 +9,12 @@
9
9
  * whether this build is already current.
10
10
  * /update apply — for a binary install (scripts/install.sh),
11
11
  * download + verify + atomically swap the
12
- * app and daemon binaries. For any other
13
- * install kind, prints the exact command to
14
- * run instead — it never attempts a swap it
15
- * can't do safely.
12
+ * app and daemon binaries, and refresh the
13
+ * sqlite-vec native addon in lockstep so the
14
+ * vector index never goes stale beside a new
15
+ * binary. For any other install kind, prints the
16
+ * exact command to run instead — it never
17
+ * attempts a swap it can't do safely.
16
18
  * /update review — install/subscription/sandbox posture,
17
19
  * unrelated to the update mechanics above.
18
20
  * /update bundle export|inspect <path> — portable posture bundle, as before.
@@ -34,6 +36,7 @@ import {
34
36
  CHECKSUM_MANIFEST_NAME,
35
37
  parseChecksumFile,
36
38
  resolveArtifactNames,
39
+ resolveSqliteVecAsset,
37
40
  sha256,
38
41
  verifyChecksum,
39
42
  } from '../../runtime/release-artifacts.ts';
@@ -87,6 +90,24 @@ function swapBinaryAtomically(targetPath: string, buffer: Buffer): void {
87
90
  renameSync(tempPath, targetPath);
88
91
  }
89
92
 
93
+ /**
94
+ * Writes the sqlite-vec native addon into place with the same temp-write +
95
+ * atomic-rename discipline as the binaries, so a running process never
96
+ * dlopen()s a half-written extension. The addon's parent directory
97
+ * (`<execDir>/lib/sqlite-vec-<os>-<arch>/`) is created first because a fresh
98
+ * install may not have a `lib/` tree yet. A shared library needs no execute
99
+ * bit, so this uses 0o644 rather than the binaries' 0o755.
100
+ */
101
+ function writeAddonAtomically(targetPath: string, buffer: Buffer): void {
102
+ mkdirSync(dirname(targetPath), { recursive: true });
103
+ const tempPath = `${targetPath}.update-download`;
104
+ writeFileSync(tempPath, buffer);
105
+ if (process.platform !== 'win32') {
106
+ chmodSync(tempPath, 0o644);
107
+ }
108
+ renameSync(tempPath, targetPath);
109
+ }
110
+
90
111
  export interface DaemonServiceRestartInfo {
91
112
  readonly managed: boolean;
92
113
  readonly unitName: string;
@@ -150,6 +171,8 @@ export interface ApplyUpdateOptions {
150
171
  readonly runCommand?: RunCommand;
151
172
  /** Injectable so tests can observe/skip the actual filesystem swap. */
152
173
  readonly swap?: (targetPath: string, buffer: Buffer) => void;
174
+ /** Injectable so tests can observe/skip writing the sqlite-vec addon. */
175
+ readonly writeAddon?: (targetPath: string, buffer: Buffer) => void;
153
176
  readonly fileExists?: (path: string) => boolean;
154
177
  }
155
178
 
@@ -205,12 +228,37 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
205
228
  verifyChecksum(artifacts.daemon, sha256(daemonBuffer), checksums.get(artifacts.daemon));
206
229
  }
207
230
 
208
- // Both downloads verified before either swap — an update must not apply partially.
231
+ // The sqlite-vec native addon travels with the binaries: refresh it in the
232
+ // same download-verify-swap pass so /update never leaves a new binary beside a
233
+ // stale addon. It lands at <execDir>/lib/sqlite-vec-<os>-<arch>/vec0.<suffix>,
234
+ // exactly where the SDK's loader resolves it. The manifest entry decides
235
+ // whether the target release ships it — an entry that IS present makes the
236
+ // download and checksum mandatory (a mismatch is fatal, verified before any
237
+ // swap), while an absent entry means the target predates the addon and is
238
+ // skipped rather than blocking an otherwise-valid binary update. On macOS the
239
+ // file is refreshed for consistency even though the platform blocks extension
240
+ // loading.
241
+ const addon = resolveSqliteVecAsset(options.platform, options.arch);
242
+ const addonExpected = addon ? checksums.get(addon.assetName) : undefined;
243
+ let addonBuffer: Buffer | null = null;
244
+ if (addon && addonExpected !== undefined) {
245
+ addonBuffer = await downloadBuffer(options.fetchImpl, `${baseUrl}/${addon.assetName}`);
246
+ verifyChecksum(addon.assetName, sha256(addonBuffer), addonExpected);
247
+ }
248
+
249
+ // All downloads verified before any write — an update must not apply partially.
209
250
  const swap = options.swap ?? swapBinaryAtomically;
210
251
  swap(appBinaryPath, appBuffer);
211
252
  if (daemonBuffer) {
212
253
  swap(daemonBinaryPath, daemonBuffer);
213
254
  }
255
+ const addonTargetPath = addon && addonBuffer
256
+ ? join(dirname(appBinaryPath), 'lib', addon.dirName, addon.fileName)
257
+ : null;
258
+ if (addonBuffer && addonTargetPath) {
259
+ const writeAddon = options.writeAddon ?? writeAddonAtomically;
260
+ writeAddon(addonTargetPath, addonBuffer);
261
+ }
214
262
 
215
263
  const serviceInfo = detectDaemonServiceManaged(options.platform, options.configManager, options.runCommand);
216
264
 
@@ -221,6 +269,7 @@ export async function applyUpdate(options: ApplyUpdateOptions): Promise<void> {
221
269
  daemonBinaryPresent
222
270
  ? ` daemon binary: ${daemonBinaryPath}`
223
271
  : ` daemon binary: not found at ${daemonBinaryPath} — left untouched`,
272
+ ...(addonTargetPath ? [` vector addon: ${addonTargetPath}`] : []),
224
273
  '',
225
274
  'Restart goodvibes to run the new version.',
226
275
  serviceInfo.managed
@@ -42,6 +42,36 @@ export function resolveArtifactNames(platform: string, arch: string): ReleaseArt
42
42
  };
43
43
  }
44
44
 
45
+ export interface SqliteVecAsset {
46
+ /** Release asset filename, e.g. `sqlite-vec-linux-x64.so`. */
47
+ readonly assetName: string;
48
+ /** Directory name the SDK loader resolves, e.g. `sqlite-vec-linux-x64`. */
49
+ readonly dirName: string;
50
+ /** File the loader opens inside that directory, e.g. `vec0.so`. */
51
+ readonly fileName: string;
52
+ }
53
+
54
+ /**
55
+ * Names the sqlite-vec native addon for a platform/arch. Unlike the binaries
56
+ * (whose release tag maps darwin→"macos"), the addon keeps the Node-style
57
+ * platform tag ("linux" | "darwin") because that is exactly what the SDK's
58
+ * loader resolves at `<execDir>/lib/sqlite-vec-<process.platform>-<process.arch>/vec0.<suffix>`.
59
+ * The scripts/install.sh installer and the release workflow name the asset the
60
+ * same way; all three must stay in lockstep.
61
+ */
62
+ export function resolveSqliteVecAsset(platform: string, arch: string): SqliteVecAsset | null {
63
+ if ((platform !== 'linux' && platform !== 'darwin') || (arch !== 'x64' && arch !== 'arm64')) {
64
+ return null;
65
+ }
66
+ const suffix = platform === 'darwin' ? 'dylib' : 'so';
67
+ const dirName = `sqlite-vec-${platform}-${arch}`;
68
+ return {
69
+ assetName: `${dirName}.${suffix}`,
70
+ dirName,
71
+ fileName: `vec0.${suffix}`,
72
+ };
73
+ }
74
+
45
75
  export function sha256(buffer: Buffer | Uint8Array): string {
46
76
  return createHash('sha256').update(buffer).digest('hex');
47
77
  }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.14.0';
9
+ let _version = '1.15.0';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;