@pellux/goodvibes-tui 0.19.90 → 0.19.91

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,11 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.19.91] — 2026-05-10
8
+
9
+ ### Changes
10
+ - 8f70c74e fix: install release binaries from global launchers
11
+
7
12
  ## [0.19.90] — 2026-05-10
8
13
 
9
14
  ### 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-0.19.90-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.91-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/bin/goodvibes CHANGED
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env bun
2
- import { accessSync, constants } from 'node:fs';
3
2
  import { dirname, join } from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
5
4
  import { spawnSync } from 'node:child_process';
5
+ import {
6
+ ensureVendoredBinary,
7
+ isExecutable,
8
+ isSourceCheckout,
9
+ resolveArtifactName,
10
+ run,
11
+ supportedTargetsText,
12
+ } from './launcher-support.js';
6
13
 
7
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
15
  const packageRoot = join(__dirname, '..');
@@ -13,32 +20,7 @@ if (process.platform === 'win32') {
13
20
  process.exit(1);
14
21
  }
15
22
 
16
- function resolveArtifactName(platform, arch) {
17
- if (platform === 'linux' && arch === 'x64') return 'goodvibes-linux-x64';
18
- if (platform === 'linux' && arch === 'arm64') return 'goodvibes-linux-arm64';
19
- if (platform === 'darwin' && arch === 'x64') return 'goodvibes-macos-x64';
20
- if (platform === 'darwin' && arch === 'arm64') return 'goodvibes-macos-arm64';
21
- return null;
22
- }
23
-
24
- function isExecutable(path) {
25
- try {
26
- accessSync(path, constants.X_OK);
27
- return true;
28
- } catch {
29
- return false;
30
- }
31
- }
32
-
33
- function run(command, args) {
34
- const child = spawnSync(command, args, { stdio: 'inherit' });
35
- if (child.error) {
36
- throw child.error;
37
- }
38
- process.exit(child.status ?? 1);
39
- }
40
-
41
- const artifactName = resolveArtifactName(process.platform, process.arch);
23
+ const artifactName = resolveArtifactName('app', process.platform, process.arch);
42
24
  const localPlatformBuild = artifactName ? join(packageRoot, 'dist', artifactName) : null;
43
25
  const localBuild = join(packageRoot, 'dist', 'goodvibes');
44
26
  const vendoredBinary = artifactName ? join(packageRoot, 'vendor', artifactName) : null;
@@ -55,22 +37,24 @@ if (vendoredBinary && isExecutable(vendoredBinary)) {
55
37
  run(vendoredBinary, process.argv.slice(2));
56
38
  }
57
39
 
40
+ if (artifactName) {
41
+ try {
42
+ const installedBinary = await ensureVendoredBinary({ packageRoot, artifactName });
43
+ run(installedBinary, process.argv.slice(2));
44
+ } catch (error) {
45
+ console.error(`goodvibes: failed to install release binary: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ }
48
+
58
49
  const bunProbe = spawnSync('bun', ['--version'], { stdio: 'ignore' });
59
- if (bunProbe.status === 0) {
50
+ if (bunProbe.status === 0 && isSourceCheckout(packageRoot)) {
60
51
  run('bun', [join(packageRoot, 'src', 'main.ts'), ...process.argv.slice(2)]);
61
52
  }
62
53
 
63
- const supported = [
64
- 'linux-x64',
65
- 'linux-arm64',
66
- 'darwin-x64',
67
- 'darwin-arm64',
68
- ].join(', ');
69
-
70
54
  console.error('goodvibes: no runnable binary is available.');
71
55
  console.error(`platform: ${process.platform}-${process.arch}`);
72
- console.error(`supported prebuilt targets: ${supported}`);
56
+ console.error(`supported prebuilt targets: ${supportedTargetsText()}`);
73
57
  console.error('Either:');
74
- console.error(' 1. install Bun and re-run the command, or');
75
- console.error(' 2. rerun the package postinstall step so the matching release binary is downloaded.');
58
+ console.error(' 1. check network access to the GitHub release assets, or');
59
+ console.error(' 2. build locally with `bun run build` in a source checkout.');
76
60
  process.exit(1);
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env bun
2
- import { accessSync, constants } from 'node:fs';
3
2
  import { dirname, join } from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
5
4
  import { spawnSync } from 'node:child_process';
5
+ import {
6
+ ensureVendoredBinary,
7
+ isExecutable,
8
+ isSourceCheckout,
9
+ resolveArtifactName,
10
+ run,
11
+ supportedTargetsText,
12
+ } from './launcher-support.js';
6
13
 
7
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
15
  const packageRoot = join(__dirname, '..');
@@ -13,32 +20,7 @@ if (process.platform === 'win32') {
13
20
  process.exit(1);
14
21
  }
15
22
 
16
- function resolveArtifactName(platform, arch) {
17
- if (platform === 'linux' && arch === 'x64') return 'goodvibes-daemon-linux-x64';
18
- if (platform === 'linux' && arch === 'arm64') return 'goodvibes-daemon-linux-arm64';
19
- if (platform === 'darwin' && arch === 'x64') return 'goodvibes-daemon-macos-x64';
20
- if (platform === 'darwin' && arch === 'arm64') return 'goodvibes-daemon-macos-arm64';
21
- return null;
22
- }
23
-
24
- function isExecutable(path) {
25
- try {
26
- accessSync(path, constants.X_OK);
27
- return true;
28
- } catch {
29
- return false;
30
- }
31
- }
32
-
33
- function run(command, args) {
34
- const child = spawnSync(command, args, { stdio: 'inherit' });
35
- if (child.error) {
36
- throw child.error;
37
- }
38
- process.exit(child.status ?? 1);
39
- }
40
-
41
- const artifactName = resolveArtifactName(process.platform, process.arch);
23
+ const artifactName = resolveArtifactName('daemon', process.platform, process.arch);
42
24
  const localPlatformBuild = artifactName ? join(packageRoot, 'dist', artifactName) : null;
43
25
  const localBuild = join(packageRoot, 'dist', 'goodvibes-daemon');
44
26
  const vendoredBinary = artifactName ? join(packageRoot, 'vendor', artifactName) : null;
@@ -55,22 +37,24 @@ if (vendoredBinary && isExecutable(vendoredBinary)) {
55
37
  run(vendoredBinary, process.argv.slice(2));
56
38
  }
57
39
 
40
+ if (artifactName) {
41
+ try {
42
+ const installedBinary = await ensureVendoredBinary({ packageRoot, artifactName });
43
+ run(installedBinary, process.argv.slice(2));
44
+ } catch (error) {
45
+ console.error(`goodvibes-daemon: failed to install release binary: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ }
48
+
58
49
  const bunProbe = spawnSync('bun', ['--version'], { stdio: 'ignore' });
59
- if (bunProbe.status === 0) {
50
+ if (bunProbe.status === 0 && isSourceCheckout(packageRoot)) {
60
51
  run('bun', [join(packageRoot, 'src', 'daemon', 'cli.ts'), ...process.argv.slice(2)]);
61
52
  }
62
53
 
63
- const supported = [
64
- 'linux-x64',
65
- 'linux-arm64',
66
- 'darwin-x64',
67
- 'darwin-arm64',
68
- ].join(', ');
69
-
70
54
  console.error('goodvibes-daemon: no runnable binary is available.');
71
55
  console.error(`platform: ${process.platform}-${process.arch}`);
72
- console.error(`supported prebuilt targets: ${supported}`);
56
+ console.error(`supported prebuilt targets: ${supportedTargetsText()}`);
73
57
  console.error('Either:');
74
- console.error(' 1. install Bun and re-run the command, or');
75
- console.error(' 2. rerun the package postinstall step so the matching daemon binary is downloaded.');
58
+ console.error(' 1. check network access to the GitHub release assets, or');
59
+ console.error(' 2. build locally with `bun run build:daemon:<platform>` in a source checkout.');
76
60
  process.exit(1);
@@ -0,0 +1,141 @@
1
+ import { accessSync, chmodSync, constants, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { join } from 'node:path';
5
+
6
+ const SUPPORTED_TARGETS = ['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64'];
7
+
8
+ export function isExecutable(path) {
9
+ try {
10
+ accessSync(path, constants.X_OK);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ export function run(command, args) {
18
+ const child = spawnSync(command, args, { stdio: 'inherit' });
19
+ if (child.error) {
20
+ throw child.error;
21
+ }
22
+ process.exit(child.status ?? 1);
23
+ }
24
+
25
+ export function isSourceCheckout(packageRoot) {
26
+ return isExecutable(join(packageRoot, 'node_modules', '.bin', 'bun')) ||
27
+ isExecutable(join(packageRoot, 'node_modules', '.bin', 'tsc')) ||
28
+ fileExists(join(packageRoot, 'tsconfig.json'));
29
+ }
30
+
31
+ export function supportedTargetsText() {
32
+ return SUPPORTED_TARGETS.join(', ');
33
+ }
34
+
35
+ export function resolveArtifactName(kind, platform, arch) {
36
+ const prefix = kind === 'daemon' ? 'goodvibes-daemon' : 'goodvibes';
37
+ if (platform === 'linux' && arch === 'x64') return `${prefix}-linux-x64`;
38
+ if (platform === 'linux' && arch === 'arm64') return `${prefix}-linux-arm64`;
39
+ if (platform === 'darwin' && arch === 'x64') return `${prefix}-macos-x64`;
40
+ if (platform === 'darwin' && arch === 'arm64') return `${prefix}-macos-arm64`;
41
+ return null;
42
+ }
43
+
44
+ export async function ensureVendoredBinary({ packageRoot, artifactName }) {
45
+ const vendorDir = join(packageRoot, 'vendor');
46
+ const destination = join(vendorDir, artifactName);
47
+ if (isExecutable(destination)) {
48
+ return destination;
49
+ }
50
+
51
+ const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
52
+ const releaseBaseUrl =
53
+ process.env.GOODVIBES_RELEASE_BASE_URL?.trim() ||
54
+ `${resolveRepositoryBaseUrl(pkg)}/releases/download/v${pkg.version}`;
55
+
56
+ mkdirSync(vendorDir, { recursive: true });
57
+
58
+ const checksumText = await downloadText(`${releaseBaseUrl}/SHA256SUMS.txt`);
59
+ writeFileSync(join(vendorDir, 'SHA256SUMS.txt'), checksumText);
60
+ const checksums = parseChecksumFile(checksumText);
61
+
62
+ const tempDestination = `${destination}.download`;
63
+ rmSync(tempDestination, { force: true });
64
+
65
+ try {
66
+ const binary = await downloadBuffer(`${releaseBaseUrl}/${artifactName}`);
67
+ const actual = sha256(binary);
68
+ const expected = checksums.get(artifactName);
69
+ if (expected && expected !== actual) {
70
+ throw new Error(`checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}`);
71
+ }
72
+ writeFileSync(tempDestination, binary);
73
+ prepareBinary(tempDestination);
74
+ rmSync(destination, { force: true });
75
+ writeFileSync(destination, readFileSync(tempDestination));
76
+ prepareBinary(destination);
77
+ } finally {
78
+ rmSync(tempDestination, { force: true });
79
+ }
80
+
81
+ return destination;
82
+ }
83
+
84
+ function fileExists(path) {
85
+ try {
86
+ accessSync(path, constants.F_OK);
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ function prepareBinary(path) {
94
+ if (process.platform !== 'win32') {
95
+ chmodSync(path, 0o755);
96
+ }
97
+ }
98
+
99
+ function resolveRepositoryBaseUrl(pkg) {
100
+ const repositoryUrl = typeof pkg.repository?.url === 'string' ? pkg.repository.url : '';
101
+ const normalized = repositoryUrl
102
+ .replace(/^git\+/, '')
103
+ .replace(/\.git$/, '')
104
+ .replace(/^git@github\.com:/, 'https://github.com/');
105
+ if (!normalized.startsWith('https://github.com/')) {
106
+ throw new Error(`unsupported repository URL for binary downloads: ${repositoryUrl || '(missing)'}`);
107
+ }
108
+ return normalized;
109
+ }
110
+
111
+ async function downloadText(url) {
112
+ const response = await fetch(url);
113
+ if (!response.ok) {
114
+ throw new Error(`download failed (${response.status}) for ${url}`);
115
+ }
116
+ return await response.text();
117
+ }
118
+
119
+ async function downloadBuffer(url) {
120
+ const response = await fetch(url);
121
+ if (!response.ok) {
122
+ throw new Error(`download failed (${response.status}) for ${url}`);
123
+ }
124
+ return Buffer.from(await response.arrayBuffer());
125
+ }
126
+
127
+ function sha256(buffer) {
128
+ return createHash('sha256').update(buffer).digest('hex');
129
+ }
130
+
131
+ function parseChecksumFile(contents) {
132
+ const checksums = new Map();
133
+ for (const rawLine of contents.split(/\r?\n/)) {
134
+ const line = rawLine.trim();
135
+ if (!line) continue;
136
+ const match = line.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
137
+ if (!match) continue;
138
+ checksums.set(match[2], match[1].toLowerCase());
139
+ }
140
+ return checksums;
141
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.90",
3
+ "version": "0.19.91",
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",
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 = '0.19.90';
9
+ let _version = '0.19.91';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;