@pellux/goodvibes-tui 0.19.61 → 0.19.63

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
@@ -8,6 +8,33 @@ All notable changes to GoodVibes TUI.
8
8
 
9
9
  ---
10
10
 
11
+ ## [0.19.63] — 2026-05-05
12
+
13
+ ### Changed
14
+ - Made the npm registry install path explicitly Bun-first: `bun add -g @pellux/goodvibes-tui` is now the documented global install command, while `npm install -g` requires Bun to already be installed.
15
+ - Switched the published `goodvibes`, `goodvibes-daemon`, and postinstall entrypoints to Bun.
16
+ - Added a preinstall Bun availability check so npm users get a clear error before postinstall runs.
17
+ - Added package verification coverage for Bun shebangs and the Bun preinstall script.
18
+ - Updated service management so daemon services use the global daemon state directory and resolve packaged/vendored daemon binaries before falling back to source or PATH wrappers.
19
+ - Added mouse-wheel routing for scrollable panels such as the file explorer without stealing normal transcript scrolling.
20
+
21
+ ### Verified
22
+ - `bun test src/test/cli/package-verification.test.ts src/test/cli/service-posture.test.ts src/test/daemon/service-manager.test.ts src/test/cli-flags.test.ts --timeout 30000`
23
+ - `bun run tsc --noEmit --pretty false`
24
+ - `bun run publish:check`
25
+ - `bun run package:install-check`
26
+ - `bun run build`
27
+ - `git diff --check`
28
+
29
+ ---
30
+
31
+ ## [0.19.62] — 2026-05-05
32
+
33
+ ### Changed
34
+ - Recut the SDK 0.33.4 TUI migration release so the GitHub Release workflow owns npm publication and can finish green after `0.19.61` was already published manually.
35
+
36
+ ---
37
+
11
38
  ## [0.19.61] — 2026-05-05
12
39
 
13
40
  ### Changed
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-0.19.61-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.63-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
 
@@ -14,13 +14,15 @@ A terminal-native AI coding, operations, automation, knowledge, and integration
14
14
 
15
15
  ## Start Here
16
16
 
17
- Install from npm:
17
+ GoodVibes is a Bun program. Install Bun first and make sure `bun` is on `PATH`, then install GoodVibes from the npm registry:
18
18
 
19
19
  ```sh
20
- npm install -g @pellux/goodvibes-tui
20
+ bun add -g @pellux/goodvibes-tui
21
21
  goodvibes
22
22
  ```
23
23
 
24
+ `npm install -g @pellux/goodvibes-tui` is also supported, but it does not install Bun for you. The package preinstall check fails with a clear message if `bun` is missing from `PATH`.
25
+
24
26
  Or run from source:
25
27
 
26
28
  ```sh
@@ -41,7 +43,8 @@ Common entrypoints:
41
43
  Release distribution:
42
44
 
43
45
  - GitHub Releases are the primary distribution path for compiled binaries
44
- - `npm install -g @pellux/goodvibes-tui` is supported on Linux, macOS, and WSL; the install script downloads the matching TUI and daemon binaries for the current platform
46
+ - `bun add -g @pellux/goodvibes-tui` is the recommended global install path; the package is hosted on the npm registry and Bun installs from that registry directly
47
+ - `npm install -g @pellux/goodvibes-tui` is supported on Linux, macOS, and WSL when Bun is already installed; the preinstall check verifies Bun, and the install script downloads the matching TUI and daemon binaries for the current platform
45
48
  - native Windows is not supported; use WSL on Windows
46
49
 
47
50
  Common paths:
package/bin/goodvibes CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { accessSync, constants } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { accessSync, constants } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.61",
3
+ "version": "0.19.63",
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",
@@ -17,6 +17,7 @@
17
17
  "!src/test",
18
18
  "!src/**/*.test.ts",
19
19
  "!src/**/__tests__",
20
+ "scripts/check-bun.sh",
20
21
  "scripts/postinstall.js",
21
22
  "README.md",
22
23
  "CHANGELOG.md",
@@ -41,8 +42,9 @@
41
42
  "build:all-shell": "bun run build:linux-x64 && bun run build:linux-arm64 && bun run build:macos-x64 && bun run build:macos-arm64 && bun run build:windows",
42
43
  "test": "bun run scripts/run-tests.ts",
43
44
  "version": "bun run scripts/prebuild.ts",
44
- "postinstall": "node scripts/postinstall.js",
45
- "postbuild": "node scripts/postinstall.js --no-download",
45
+ "preinstall": "sh scripts/check-bun.sh",
46
+ "postinstall": "bun scripts/postinstall.js",
47
+ "postbuild": "bun scripts/postinstall.js --no-download",
46
48
  "release": "bun run scripts/release.ts",
47
49
  "release:dry": "bun run scripts/release.ts --dry-run",
48
50
  "publish:package": "bun run scripts/publish-package.ts",
@@ -54,7 +56,10 @@
54
56
  "build:all": "bun run scripts/build.ts --all",
55
57
  "perf:check": "bun run scripts/perf-check.ts",
56
58
  "architecture:check": "bun run scripts/check-architecture.ts",
59
+ "audit:home": "bun run scripts/audit-goodvibes-home.ts",
57
60
  "foundation:artifacts": "bun run scripts/export-foundation-artifacts.ts",
61
+ "verification:ledger": "bun run scripts/verification-ledger.ts",
62
+ "verification:live": "bun run scripts/verify-live.ts",
58
63
  "eval:gate": "bun run scripts/eval-gate.ts",
59
64
  "eval:gate:verbose": "bun run scripts/eval-gate.ts --verbose",
60
65
  "eval:baseline": "bun run scripts/eval-gate.ts --save-baseline"
@@ -0,0 +1,20 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ if ! command -v bun >/dev/null 2>&1; then
5
+ cat >&2 <<'EOF'
6
+ goodvibes-tui requires Bun.
7
+
8
+ Install Bun first, then install GoodVibes from the npm registry with:
9
+
10
+ bun add -g @pellux/goodvibes-tui
11
+
12
+ npm install -g @pellux/goodvibes-tui also works, but Bun must already be installed and available on PATH.
13
+ EOF
14
+ exit 1
15
+ fi
16
+
17
+ if ! bun --version >/dev/null 2>&1; then
18
+ echo "goodvibes-tui requires a working Bun executable on PATH." >&2
19
+ exit 1
20
+ fi
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { homedir } from 'node:os';
@@ -7,6 +7,7 @@ export interface PackageCliBinVerification {
7
7
  readonly target: string;
8
8
  readonly exists: boolean;
9
9
  readonly executable: boolean;
10
+ readonly usesBunShebang: boolean;
10
11
  readonly hasLocalPlatformBuildFallback: boolean;
11
12
  readonly hasLocalBuildFallback: boolean;
12
13
  readonly hasVendoredBinaryFallback: boolean;
@@ -35,6 +36,7 @@ const REQUIRED_TARBALL_PATHS = [
35
36
  'src/daemon/cli.ts',
36
37
  'bin/goodvibes',
37
38
  'bin/goodvibes-daemon',
39
+ 'scripts/check-bun.sh',
38
40
  'scripts/postinstall.js',
39
41
  '.goodvibes/GOODVIBES.md',
40
42
  ] as const;
@@ -58,6 +60,7 @@ function verifyBin(root: string, command: typeof REQUIRED_BIN_COMMANDS[number],
58
60
  target: target ?? '',
59
61
  exists: Boolean(target) && existsSync(binPath),
60
62
  executable: Boolean(target) && hasExecutableBit(binPath),
63
+ usesBunShebang: source.startsWith('#!/usr/bin/env bun'),
61
64
  hasLocalPlatformBuildFallback: source.includes("dist', artifactName"),
62
65
  hasLocalBuildFallback: source.includes(expectedLocalBuild),
63
66
  hasVendoredBinaryFallback: source.includes('vendor'),
@@ -92,6 +95,7 @@ export function verifyPackageCliInstall(root: string): PackageCliVerificationRep
92
95
  if (!item.target) issues.push(`package.json bin is missing ${item.command}.`);
93
96
  if (!item.exists) issues.push(`bin target does not exist: ${item.command} -> ${item.target}`);
94
97
  if (!item.executable) issues.push(`bin target is not executable: ${item.command} -> ${item.target}`);
98
+ if (!item.usesBunShebang) issues.push(`bin target does not use Bun shebang: ${item.command} -> ${item.target}`);
95
99
  if (!item.hasLocalPlatformBuildFallback) issues.push(`bin target lacks local platform dist fallback: ${item.command}`);
96
100
  if (!item.hasLocalBuildFallback) issues.push(`bin target lacks local dist fallback: ${item.command}`);
97
101
  if (!item.hasVendoredBinaryFallback) issues.push(`bin target lacks vendored binary fallback: ${item.command}`);
@@ -1,5 +1,6 @@
1
+ import { mkdirSync } from 'node:fs';
1
2
  import type { CliCommandRuntime } from './management.ts';
2
- import { buildCliServicePosture, createPlatformServiceManager, formatCliServicePosture } from './service-posture.ts';
3
+ import { buildCliServicePosture, createPlatformServiceManager, formatCliServicePosture, getServiceStateRoot } from './service-posture.ts';
3
4
  import type { CliCommandOutput } from './types.ts';
4
5
 
5
6
  function enableServicePosture(runtime: CliCommandRuntime): void {
@@ -21,6 +22,9 @@ export async function handleServiceCommand(runtime: CliCommandRuntime): Promise<
21
22
  if (sub === 'install' || sub === 'start' || sub === 'restart' || sub === 'stop' || sub === 'uninstall') {
22
23
  const manager = createPlatformServiceManager(runtime);
23
24
  if (sub === 'install' || sub === 'start' || sub === 'restart') enableServicePosture(runtime);
25
+ if (sub === 'install' || sub === 'start' || sub === 'restart') {
26
+ mkdirSync(getServiceStateRoot(runtime), { recursive: true });
27
+ }
24
28
  const result =
25
29
  sub === 'install' ? manager.install()
26
30
  : sub === 'start' ? manager.start()
@@ -1,6 +1,7 @@
1
- import { closeSync, existsSync, openSync, readSync, statSync } from 'node:fs';
1
+ import { accessSync, closeSync, constants, existsSync, openSync, readSync, realpathSync, statSync } from 'node:fs';
2
2
  import net from 'node:net';
3
- import { join } from 'node:path';
3
+ import { basename, delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import { PlatformServiceManager } from '@pellux/goodvibes-sdk/platform/daemon';
5
6
  import type { ManagedServiceStatus } from '@pellux/goodvibes-sdk/platform/daemon';
6
7
  import type { ConfigManager } from '../config/index.ts';
@@ -56,6 +57,31 @@ const ENDPOINTS: readonly { readonly id: RuntimeEndpointId; readonly label: stri
56
57
  { id: 'web', label: 'web surface', enabledKey: 'web.enabled' },
57
58
  ];
58
59
 
60
+ const SOURCE_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
61
+
62
+ export interface DaemonExecutableResolutionOptions {
63
+ readonly env?: NodeJS.ProcessEnv;
64
+ readonly argv?: readonly string[];
65
+ readonly execPath?: string;
66
+ readonly packageRoot?: string;
67
+ }
68
+
69
+ export interface DaemonExecutableResolution {
70
+ readonly command: string;
71
+ readonly source: 'env' | 'sibling' | 'package' | 'path' | 'fallback';
72
+ readonly absolute: boolean;
73
+ }
74
+
75
+ interface ServiceDefinitionOverride {
76
+ readonly name: string;
77
+ readonly description: string;
78
+ readonly workingDirectory: string;
79
+ readonly command: string;
80
+ readonly args: readonly string[];
81
+ readonly env: Readonly<Record<string, string>>;
82
+ readonly restartOnFailure: boolean;
83
+ }
84
+
59
85
  function connectHostForBindHost(host: string): string {
60
86
  if (host === '0.0.0.0' || host === '::') return '127.0.0.1';
61
87
  return host || '127.0.0.1';
@@ -76,8 +102,129 @@ async function probeTcp(host: string, port: number, timeoutMs = 750): Promise<bo
76
102
  });
77
103
  }
78
104
 
105
+ function isExecutable(path: string): boolean {
106
+ try {
107
+ accessSync(path, constants.X_OK);
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ function platformDaemonArtifactName(platform = process.platform, arch = process.arch): string {
115
+ if (platform === 'linux' && arch === 'x64') return 'goodvibes-daemon-linux-x64';
116
+ if (platform === 'linux' && arch === 'arm64') return 'goodvibes-daemon-linux-arm64';
117
+ if (platform === 'darwin' && arch === 'x64') return 'goodvibes-daemon-macos-x64';
118
+ if (platform === 'darwin' && arch === 'arm64') return 'goodvibes-daemon-macos-arm64';
119
+ if (platform === 'win32') return 'goodvibes-daemon-windows.exe';
120
+ return 'goodvibes-daemon';
121
+ }
122
+
123
+ function executablePathCandidates(path: string): string[] {
124
+ if (!path.trim() || !isAbsolute(path)) return [];
125
+ const dir = dirname(path);
126
+ const name = basename(path);
127
+ const artifact = platformDaemonArtifactName();
128
+ const candidates = [
129
+ join(dir, 'goodvibes-daemon'),
130
+ join(dir, artifact),
131
+ ];
132
+ if (name.startsWith('goodvibes-') && !name.startsWith('goodvibes-daemon-')) {
133
+ candidates.push(join(dir, name.replace(/^goodvibes-/, 'goodvibes-daemon-')));
134
+ }
135
+ if (name === 'goodvibes') candidates.push(join(dir, 'goodvibes-daemon'));
136
+ return [...new Set(candidates)];
137
+ }
138
+
139
+ function resolveCommandFromPath(command: string, pathValue: string | undefined): string | null {
140
+ const pathEntries = (pathValue ?? '').split(delimiter).filter(Boolean);
141
+ const extensions = process.platform === 'win32'
142
+ ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
143
+ : [''];
144
+ for (const entry of pathEntries) {
145
+ for (const extension of extensions) {
146
+ const candidate = join(entry, `${command}${extension}`);
147
+ if (isExecutable(candidate)) return candidate;
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+
153
+ function firstExecutable(paths: readonly string[]): string | null {
154
+ for (const path of paths) {
155
+ if (isExecutable(path)) return path;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ function packageArtifactForBinWrapper(path: string): string | null {
161
+ let realPath = path;
162
+ try {
163
+ realPath = realpathSync(path);
164
+ } catch {
165
+ // Keep the original path if resolving a symlink fails.
166
+ }
167
+ if (basename(realPath) !== 'goodvibes-daemon') return null;
168
+ const binDir = dirname(realPath);
169
+ if (basename(binDir) !== 'bin') return null;
170
+ const packageRoot = dirname(binDir);
171
+ return firstExecutable([
172
+ join(packageRoot, 'vendor', platformDaemonArtifactName()),
173
+ join(packageRoot, 'dist', platformDaemonArtifactName()),
174
+ join(packageRoot, 'dist', 'goodvibes-daemon'),
175
+ ]);
176
+ }
177
+
178
+ export function resolveGoodVibesDaemonExecutable(
179
+ options: DaemonExecutableResolutionOptions = {},
180
+ ): DaemonExecutableResolution {
181
+ const env = options.env ?? process.env;
182
+ const override = env.GOODVIBES_DAEMON_COMMAND?.trim();
183
+ if (override) {
184
+ return { command: override, source: 'env', absolute: isAbsolute(override) };
185
+ }
186
+
187
+ const packageRoot = options.packageRoot ?? SOURCE_PACKAGE_ROOT;
188
+ const packaged = firstExecutable([
189
+ join(packageRoot, 'dist', platformDaemonArtifactName()),
190
+ join(packageRoot, 'dist', 'goodvibes-daemon'),
191
+ join(packageRoot, 'vendor', platformDaemonArtifactName()),
192
+ join(packageRoot, 'bin', 'goodvibes-daemon'),
193
+ ]);
194
+ if (packaged) return { command: packaged, source: 'package', absolute: true };
195
+
196
+ const argv = options.argv ?? process.argv;
197
+ const execPath = options.execPath ?? process.execPath;
198
+ const sibling = firstExecutable([
199
+ ...executablePathCandidates(execPath),
200
+ ...executablePathCandidates(argv[1] ?? ''),
201
+ ]);
202
+ if (sibling) {
203
+ return {
204
+ command: packageArtifactForBinWrapper(sibling) ?? sibling,
205
+ source: 'sibling',
206
+ absolute: true,
207
+ };
208
+ }
209
+
210
+ const pathResolved = resolveCommandFromPath('goodvibes-daemon', env.PATH);
211
+ if (pathResolved) {
212
+ return {
213
+ command: packageArtifactForBinWrapper(pathResolved) ?? pathResolved,
214
+ source: 'path',
215
+ absolute: true,
216
+ };
217
+ }
218
+
219
+ return { command: 'goodvibes-daemon', source: 'fallback', absolute: false };
220
+ }
221
+
222
+ export function getServiceStateRoot(runtime: CliServiceRuntime): string {
223
+ return join(runtime.homeDirectory, '.goodvibes', 'daemon');
224
+ }
225
+
79
226
  function pidFilePath(runtime: CliServiceRuntime, platform: ManagedServiceStatus['platform']): string {
80
- return join(runtime.workingDirectory, '.goodvibes', 'tui', 'service', `${platform}.pid`);
227
+ return join(getServiceStateRoot(runtime), 'service', `${platform}.pid`);
81
228
  }
82
229
 
83
230
  function readLogPosture(path: string | undefined, tailBytes: number): CliServiceLogPosture {
@@ -122,11 +269,28 @@ function endpointsConflict(a: CliServiceEndpointPosture, b: CliServiceEndpointPo
122
269
  }
123
270
 
124
271
  export function createPlatformServiceManager(runtime: CliServiceRuntime): PlatformServiceManager {
272
+ const daemonHomeDir = getServiceStateRoot(runtime);
273
+ const serviceName = String(runtime.configManager.get('service.serviceName') ?? 'goodvibes').trim() || 'goodvibes';
274
+ const daemonExecutable = resolveGoodVibesDaemonExecutable();
275
+ const definition: ServiceDefinitionOverride = {
276
+ name: serviceName,
277
+ description: 'GoodVibes daemon, control-plane, listener, and web host',
278
+ workingDirectory: daemonHomeDir,
279
+ command: daemonExecutable.command,
280
+ args: [],
281
+ env: {
282
+ GOODVIBES_DAEMON_HOME: daemonHomeDir,
283
+ GOODVIBES_DAEMON_TOKEN: process.env.GOODVIBES_DAEMON_TOKEN ?? '',
284
+ GOODVIBES_HTTP_TOKEN: process.env.GOODVIBES_HTTP_TOKEN ?? '',
285
+ NODE_ENV: process.env.NODE_ENV ?? 'production',
286
+ },
287
+ restartOnFailure: runtime.configManager.get('service.restartOnFailure') === true,
288
+ };
125
289
  return new PlatformServiceManager(runtime.configManager, {
126
- workingDirectory: runtime.workingDirectory,
290
+ workingDirectory: runtime.homeDirectory,
127
291
  homeDirectory: runtime.homeDirectory,
128
- surfaceRoot: 'tui',
129
- binaryBaseName: 'goodvibes-daemon',
292
+ surfaceRoot: 'daemon',
293
+ definitionOverride: definition,
130
294
  defaultServiceName: 'goodvibes',
131
295
  defaultServiceDescription: 'GoodVibes daemon, control-plane, listener, and web host',
132
296
  });