@vibesdotdev/bin 1.0.2 → 1.0.3-rc.2

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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VIBES DEV LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,37 +1,18 @@
1
- # Vibes CLI
1
+ # @vibesdotdev/bin
2
2
 
3
- Published npm wrapper for the public `vibes` binary.
3
+ The `vibes` command-line interface. It starts the public plugin host with no
4
+ setup step: the plugins it composes are this package's own `vibes.plugins`,
5
+ installed as its dependencies.
4
6
 
5
- ## Installation
7
+ ## Install
6
8
 
7
- ```bash
8
- npm install -g @vibesdotdev/bin
9
- ```
10
-
11
- Or with bun:
12
-
13
- ```bash
9
+ ```sh
14
10
  bun add -g @vibesdotdev/bin
11
+ vibes --help
15
12
  ```
16
13
 
17
- ## Usage
18
-
19
- The package downloads a platform-specific `vibes` binary during `postinstall` and exposes it through the package `bin` field.
20
-
21
- Set `VIBES_BINARY_MIRROR` to override the release download base URL. The default
22
- mirror is `https://assets.vibes.dev/downloads/cli`. The installer verifies the
23
- download against `SHA256SUMS.txt` from the same mirror before completing.
24
-
25
- To inspect the exact npm tarball before release:
26
-
27
- ```bash
28
- npm run pack:dry-run
29
- ```
30
-
31
- To publish from this package after npm auth is configured:
14
+ The `vibes` executable runs on [Bun](https://bun.sh).
32
15
 
33
- ```bash
34
- npm run publish:npm
35
- ```
16
+ ## License
36
17
 
37
- See [https://vibes.dev](https://vibes.dev) for documentation.
18
+ MIT. See `LICENSE`.
package/checkout.mjs ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bun
2
+ import { realpathSync, statSync } from 'node:fs';
3
+ import { isAbsolute, relative, sep } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ // The checkout command is the same public CLI rooted at the declared checkout:
6
+ // it declares the published package's version, `vibes.plugins` selection and
7
+ // `vibes.config` block, resolved in the checkout's own graph.
8
+ import manifest from './package.json' with { type: 'json' };
9
+
10
+ const declared = process.argv[2];
11
+ if (!declared || !isAbsolute(declared)) throw new Error('A checkout CLI requires an absolute declared root.');
12
+ const root = realpathSync(declared);
13
+ if (!statSync(root).isDirectory()) throw new Error('The declared checkout root is not a directory.');
14
+ // Same custody rule the host's capture applies, before loading any host
15
+ // module: a root that does not own this script cannot own its graph.
16
+ const location = relative(root, realpathSync(fileURLToPath(import.meta.url)));
17
+ if (!location || location === '..' || location.startsWith(`..${sep}`) || isAbsolute(location)) {
18
+ throw new Error('The checkout CLI must belong to the declared root.');
19
+ }
20
+
21
+ /**
22
+ * Resolve a host module from the declared checkout root, so a copy nested
23
+ * under `node_modules/@vibesdotdev/bin` never boots a second graph. An
24
+ * isolated linker may keep the host beside bin instead of at the root; the
25
+ * fallback loads that copy and the checkout's Kernel admission still refuses
26
+ * a second Kernel.
27
+ */
28
+ function resolveHostModule(specifier) {
29
+ try {
30
+ return pathToFileURL(Bun.resolveSync(specifier, root)).href;
31
+ } catch (error) {
32
+ if (error?.name !== 'ResolveMessage') throw error;
33
+ return import.meta.resolve(specifier);
34
+ }
35
+ }
36
+
37
+ const { captureCheckoutCliLaunch } = await import(resolveHostModule('@vibesdotdev/host-bun/cli-checkout'));
38
+ const launch = captureCheckoutCliLaunch(import.meta.url);
39
+ const { startHost } = await import(resolveHostModule('@vibesdotdev/plugins/host-start.mjs'));
40
+ await startHost({
41
+ ...launch,
42
+ host: { ...launch.host, version: manifest.version },
43
+ hostPluginSpecifiers: manifest.vibes.plugins,
44
+ hostConfig: manifest.vibes.config,
45
+ attestHostGraph: true,
46
+ globalPackagePlugins: false
47
+ });
package/package.json CHANGED
@@ -1,42 +1,145 @@
1
1
  {
2
2
  "name": "@vibesdotdev/bin",
3
- "version": "1.0.2",
4
- "description": "Vibes CLI - the command-line interface for the Vibes platform",
5
- "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/vibesdotdev/monorepo.git",
9
- "directory": "packages/cli-bin"
3
+ "version": "1.0.3-rc.2",
4
+ "dependencies": {
5
+ "@vibesdotdev/account": "^1.0.0-rc.0",
6
+ "@vibesdotdev/agent-execution": "^1.0.0-rc.0",
7
+ "@vibesdotdev/ai": "^1.0.0-rc.0",
8
+ "@vibesdotdev/ai-schemas": "^1.0.0-rc.0",
9
+ "@vibesdotdev/applets": "^1.0.0-rc.0",
10
+ "@vibesdotdev/auth": "^1.0.0-rc.0",
11
+ "@vibesdotdev/bridge": "^1.0.0-rc.0",
12
+ "@vibesdotdev/chat": "^1.0.0-rc.0",
13
+ "@vibesdotdev/cli": "^1.0.0-rc.0",
14
+ "@vibesdotdev/code-sessions": "^1.0.0-rc.0",
15
+ "@vibesdotdev/command-palette": "^1.0.0-rc.0",
16
+ "@vibesdotdev/control": "^1.0.0-rc.0",
17
+ "@vibesdotdev/execution-local": "^1.0.0-rc.0",
18
+ "@vibesdotdev/filesystem": "^1.0.0-rc.0",
19
+ "@vibesdotdev/git-operations": "^1.0.0-rc.0",
20
+ "@vibesdotdev/host-bun": "^1.0.0-rc.0",
21
+ "@vibesdotdev/hq": "^1.0.0-rc.0",
22
+ "@vibesdotdev/infra-core": "^1.0.0-rc.0",
23
+ "@vibesdotdev/infra-deploy": "^1.0.0-rc.0",
24
+ "@vibesdotdev/integrations": "^1.0.0-rc.0",
25
+ "@vibesdotdev/integrations-vibes": "^1.0.0-rc.0",
26
+ "@vibesdotdev/kernel": "^1.0.0-rc.0",
27
+ "@vibesdotdev/kernel-rpc": "^1.0.0-rc.0",
28
+ "@vibesdotdev/kit": "^1.0.0-rc.0",
29
+ "@vibesdotdev/knowledge": "^1.0.0-rc.0",
30
+ "@vibesdotdev/mcp": "^1.0.0-rc.0",
31
+ "@vibesdotdev/notifications": "^1.0.0-rc.0",
32
+ "@vibesdotdev/platform": "^1.0.0-rc.0",
33
+ "@vibesdotdev/plugins": "^1.0.0-rc.0",
34
+ "@vibesdotdev/pm": "^1.0.0-rc.0",
35
+ "@vibesdotdev/projects": "^1.0.0-rc.0",
36
+ "@vibesdotdev/prompts": "^1.0.0-rc.0",
37
+ "@vibesdotdev/registry": "^1.0.0-rc.0",
38
+ "@vibesdotdev/scope": "^1.0.0-rc.0",
39
+ "@vibesdotdev/secrets": "^1.0.0-rc.0",
40
+ "@vibesdotdev/session": "^1.0.0-rc.0",
41
+ "@vibesdotdev/storage": "^1.0.0-rc.0",
42
+ "@vibesdotdev/studio": "^1.0.0-rc.0",
43
+ "@vibesdotdev/surface-sveltekit": "^1.0.0-rc.0",
44
+ "@vibesdotdev/sync": "^1.0.0-rc.0",
45
+ "@vibesdotdev/terminal-client": "^1.0.0-rc.0",
46
+ "@vibesdotdev/testing": "^1.0.0-rc.0",
47
+ "@vibesdotdev/tui": "^1.0.0-rc.0",
48
+ "@vibesdotdev/vessel": "^1.0.0-rc.0",
49
+ "@vibesdotdev/workboard": "^1.0.0-rc.0",
50
+ "@vibesdotdev/workspace": "^1.0.0-rc.0"
10
51
  },
11
- "homepage": "https://vibes.dev",
12
- "bin": {
13
- "vibes": "bin/vibes"
52
+ "gitHead": "54ccfb6769c79c4d35361a7d27778a9ee991b9f9",
53
+ "_vibes_source": {
54
+ "repo": "ssh://git@git.vibes.dev/vibesdotdev/cli-bin.git",
55
+ "sha": "54ccfb6769c79c4d35361a7d27778a9ee991b9f9",
56
+ "workspaceDigest": "sha256:35a9fb041381b0f77619cd27067b9d3ab20be5dc63ebb27b5ae22040d4e587c8"
14
57
  },
15
58
  "files": [
16
- "bin/vibes",
17
- "scripts/postinstall.js",
18
- "README.md"
19
- ],
20
- "scripts": {
21
- "postinstall": "node scripts/postinstall.js",
22
- "pack:dry-run": "npm pack --dry-run --json --ignore-scripts",
23
- "publish:npm": "npm publish --access public --registry=https://registry.npmjs.org",
24
- "check": "bun --bun tsc -p tsconfig.json --noEmit"
25
- },
26
- "os": [
27
- "darwin",
28
- "linux",
29
- "win32"
30
- ],
31
- "cpu": [
32
- "x64",
33
- "arm64"
59
+ "vibes.mjs",
60
+ "checkout.mjs",
61
+ "!**/.turbo/**",
62
+ "!**/.svelte-kit/**",
63
+ "!**/.vibes/**",
64
+ "!**/__tests__/**",
65
+ "!**/__stubs__/**",
66
+ "!**/*.test.js",
67
+ "!**/*.test.mjs",
68
+ "!**/*.test.cjs",
69
+ "!**/*.test.jsx",
70
+ "!**/*.test.ts",
71
+ "!**/*.test.mts",
72
+ "!**/*.test.cts",
73
+ "!**/*.test.tsx",
74
+ "!**/*.spec.js",
75
+ "!**/*.spec.mjs",
76
+ "!**/*.spec.cjs",
77
+ "!**/*.spec.jsx",
78
+ "!**/*.spec.ts",
79
+ "!**/*.spec.mts",
80
+ "!**/*.spec.cts",
81
+ "!**/*.spec.tsx",
82
+ "!**/SPEC.md",
83
+ "!**/tsconfig.json",
84
+ "!**/tsconfig.*.json",
85
+ "!**/*.tsbuildinfo",
86
+ "!**/.build-ok.json",
87
+ "!**/*.tgz"
34
88
  ],
35
- "engines": {
36
- "node": ">=18"
89
+ "repository": {
90
+ "type": "git",
91
+ "url": "ssh://git@git.vibes.dev/vibesdotdev/cli-bin.git",
92
+ "directory": "npm-bin"
37
93
  },
38
94
  "publishConfig": {
39
- "registry": "https://registry.npmjs.org",
95
+ "registry": "https://packages.vibes.dev",
40
96
  "access": "public"
97
+ },
98
+ "vibes": {
99
+ "visibility": "public",
100
+ "registry": {
101
+ "repositoryId": "80ee5ed7-a88d-49f8-86e8-f867006d1b4d",
102
+ "directory": "npm-bin"
103
+ },
104
+ "tier": "public",
105
+ "compositionRoots": [
106
+ "vibes.mjs",
107
+ "checkout.mjs"
108
+ ],
109
+ "plugins": [
110
+ "@vibesdotdev/plugins/consumer.plugin",
111
+ "@vibesdotdev/infra-core/service",
112
+ "@vibesdotdev/integrations-vibes/services",
113
+ "@vibesdotdev/infra-core/plugin",
114
+ "@vibesdotdev/infra-core/generate/plugin",
115
+ "@vibesdotdev/infra-deploy/plugin",
116
+ "@vibesdotdev/infra-deploy/cli/plugin",
117
+ "@vibesdotdev/pm/plugin",
118
+ "@vibesdotdev/pm/cli/plugin",
119
+ "@vibesdotdev/platform/plugin",
120
+ "@vibesdotdev/platform/cli/plugin",
121
+ "@vibesdotdev/platform/fleets/cli/plugin",
122
+ "@vibesdotdev/workboard/marshal/cli/plugin",
123
+ "@vibesdotdev/sync/plugin",
124
+ "@vibesdotdev/sync/cli/plugin",
125
+ "@vibesdotdev/studio/cli/plugin",
126
+ "@vibesdotdev/projects/app/cli/plugin",
127
+ "@vibesdotdev/plugins/tui/plugin"
128
+ ],
129
+ "config": {
130
+ "runtime.host": {
131
+ "environmentTier": "local",
132
+ "hardware": "consumer"
133
+ },
134
+ "infra.services": {
135
+ "environment": "production"
136
+ }
137
+ }
138
+ },
139
+ "description": "Vibes CLI. Starts the public plugin host with no setup command.",
140
+ "type": "module",
141
+ "license": "MIT",
142
+ "bin": {
143
+ "vibes": "./vibes.mjs"
41
144
  }
42
145
  }
package/vibes.mjs ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bun
2
+ import { loadedCliHostDeclaration } from '@vibesdotdev/host-bun/resolution-root';
3
+ import { startHost } from '@vibesdotdev/plugins/host-start.mjs';
4
+ // The published package identity is the executable's declaration: its version
5
+ // is what `vibes --version` reports, and `vibes.plugins` is the public plugin
6
+ // selection this package installs as `dependencies` and composes at the graph
7
+ // it was installed into. `vibes.config` is the executable's own config block
8
+ // (the environment tier credentials resolve under, the services environment).
9
+ // Nothing here lists a plugin or a setting.
10
+ import manifest from './package.json' with { type: 'json' };
11
+
12
+ await startHost({
13
+ host: {
14
+ ...loadedCliHostDeclaration({
15
+ scriptUrl: import.meta.url,
16
+ hostEntryUrl: import.meta.resolve('@vibesdotdev/plugins/host-start.mjs')
17
+ }),
18
+ version: manifest.version
19
+ },
20
+ hostPluginSpecifiers: manifest.vibes.plugins,
21
+ hostConfig: manifest.vibes.config
22
+ });
package/bin/vibes DELETED
@@ -1,149 +0,0 @@
1
- #!/usr/bin/env node
2
- // Cross-platform launcher for the vibes CLI binary.
3
- //
4
- // npm's bin field is static, but the published package downloads a
5
- // platform-specific binary at install time (scripts/postinstall.js):
6
- // - Unix: overwrites THIS file (bin/vibes) with the binary itself,
7
- // so subsequent invocations skip Node entirely.
8
- // - Windows: writes bin/vibes.exe alongside this file. npm-on-Windows
9
- // generates a vibes.cmd that does `node bin/vibes`, so this
10
- // shim has to be a JS file that spawns the .exe. Without it,
11
- // Windows would try to execute a shell script and fail.
12
- //
13
- // If neither artifact is present, postinstall didn't complete — surface a
14
- // clear error rather than silently doing nothing.
15
-
16
- 'use strict';
17
-
18
- const { spawnSync } = require('child_process');
19
- const fs = require('fs');
20
- const os = require('os');
21
- const path = require('path');
22
-
23
- const binDir = __dirname;
24
- const installerPath = path.join(binDir, '..', 'scripts', 'postinstall.js');
25
- const exePath = path.join(binDir, 'vibes.exe');
26
-
27
- function getPersistentBinDir() {
28
- if (process.env.VIBES_INSTALL_DIR) return process.env.VIBES_INSTALL_DIR;
29
-
30
- const bunGlobalBin = readCommandOutput('bun', ['pm', 'bin', '-g']);
31
- if (bunGlobalBin) return bunGlobalBin;
32
-
33
- if (process.env.BUN_INSTALL) return path.join(process.env.BUN_INSTALL, 'bin');
34
-
35
- if (process.platform === 'win32') {
36
- const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
37
- return path.join(base, 'Programs', 'vibes', 'bin');
38
- }
39
-
40
- if (process.env.XDG_BIN_HOME) return process.env.XDG_BIN_HOME;
41
- return path.join(os.homedir(), '.local', 'bin');
42
- }
43
-
44
- function readCommandOutput(command, args) {
45
- const result = spawnSync(command, args, {
46
- encoding: 'utf8',
47
- stdio: ['ignore', 'pipe', 'ignore'],
48
- windowsHide: true
49
- });
50
- if (result.error || result.status !== 0) return undefined;
51
- const value = result.stdout.trim();
52
- return value.length > 0 ? value : undefined;
53
- }
54
-
55
- function getPersistentBinaryPath() {
56
- return path.join(getPersistentBinDir(), process.platform === 'win32' ? 'vibes.exe' : 'vibes');
57
- }
58
-
59
- function runInstaller() {
60
- const result = spawnSync(process.execPath, [installerPath], {
61
- stdio: 'inherit',
62
- windowsHide: false,
63
- env: { ...process.env, VIBES_INSTALL_PERSISTENT: '1' }
64
- });
65
- if (result.error) {
66
- process.stderr.write(`Failed to run installer: ${result.error.message}\n`);
67
- process.exit(1);
68
- }
69
- if (result.status !== 0) {
70
- process.exit(result.status === null ? 1 : result.status);
71
- }
72
- }
73
-
74
- function reportMissingBinary() {
75
- process.stderr.write('Error: vibes binary not installed.\n');
76
- process.stderr.write('\n');
77
- process.stderr.write('The installer could not place the platform binary.\n');
78
- process.stderr.write('\n');
79
- process.stderr.write('If the problem persists, see https://github.com/vibesdotdev/monorepo/issues\n');
80
- process.exit(1);
81
- }
82
-
83
- function isStillNodeShim(filePath) {
84
- try {
85
- const content = fs.readFileSync(filePath, 'utf8');
86
- return content.includes('Cross-platform launcher for the vibes CLI binary');
87
- } catch {
88
- return false;
89
- }
90
- }
91
-
92
- if (process.platform === 'win32') {
93
- const persistentPath = getPersistentBinaryPath();
94
- if (fs.existsSync(persistentPath)) {
95
- const result = spawnSync(persistentPath, process.argv.slice(2), {
96
- stdio: 'inherit',
97
- windowsHide: false
98
- });
99
- if (result.error) {
100
- process.stderr.write(`Failed to spawn ${persistentPath}: ${result.error.message}\n`);
101
- process.exit(1);
102
- }
103
- process.exit(result.status === null ? 1 : result.status);
104
- }
105
- if (!fs.existsSync(exePath)) {
106
- runInstaller();
107
- }
108
- if (!fs.existsSync(exePath)) {
109
- reportMissingBinary();
110
- }
111
- const result = spawnSync(exePath, process.argv.slice(2), {
112
- stdio: 'inherit',
113
- windowsHide: false
114
- });
115
- if (result.error) {
116
- process.stderr.write(`Failed to spawn vibes.exe: ${result.error.message}\n`);
117
- process.exit(1);
118
- }
119
- process.exit(result.status === null ? 1 : result.status);
120
- }
121
-
122
- const persistentPath = getPersistentBinaryPath();
123
- if (fs.existsSync(persistentPath) && !isStillNodeShim(persistentPath)) {
124
- const result = spawnSync(persistentPath, process.argv.slice(2), {
125
- stdio: 'inherit',
126
- windowsHide: false
127
- });
128
- if (result.error) {
129
- process.stderr.write(`Failed to spawn ${persistentPath}: ${result.error.message}\n`);
130
- process.exit(1);
131
- }
132
- process.exit(result.status === null ? 1 : result.status);
133
- }
134
-
135
- runInstaller();
136
-
137
- if (!fs.existsSync(persistentPath) || isStillNodeShim(persistentPath)) {
138
- reportMissingBinary();
139
- }
140
-
141
- const result = spawnSync(persistentPath, process.argv.slice(2), {
142
- stdio: 'inherit',
143
- windowsHide: false
144
- });
145
- if (result.error) {
146
- process.stderr.write(`Failed to spawn vibes: ${result.error.message}\n`);
147
- process.exit(1);
148
- }
149
- process.exit(result.status === null ? 1 : result.status);
@@ -1,310 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const https = require('https');
4
- const http = require('http');
5
- const crypto = require('crypto');
6
- const { spawnSync } = require('child_process');
7
- const fs = require('fs');
8
- const path = require('path');
9
- const os = require('os');
10
- const BASE_URL = process.env.VIBES_BINARY_MIRROR || 'https://assets.vibes.dev/downloads/cli';
11
-
12
- function getPlatform() {
13
- const platform = os.platform();
14
- if (platform === 'win32') return 'win32';
15
- if (platform === 'darwin') return 'darwin';
16
- return 'linux';
17
- }
18
-
19
- function getArch() {
20
- const arch = os.arch();
21
- if (arch === 'x64') return 'x64';
22
- if (arch === 'arm64') return 'arm64';
23
- // Node on macOS can report 'arm' for Apple Silicon
24
- if (arch === 'arm' && getPlatform() === 'darwin') return 'arm64';
25
- return 'x64';
26
- }
27
-
28
- function getBinaryName() {
29
- const platform = getPlatform();
30
- const arch = getArch();
31
- const baseName = `vibes-${platform}-${arch}`;
32
- return platform === 'win32' ? `${baseName}.exe` : baseName;
33
- }
34
-
35
- function getOutputPath() {
36
- const platform = getPlatform();
37
- if (process.env.VIBES_INSTALL_PERSISTENT === '1') {
38
- const dir = getPersistentBinDir();
39
- const outputName = platform === 'win32' ? 'vibes.exe' : 'vibes';
40
- return path.join(dir, outputName);
41
- }
42
- const dir = path.join(__dirname, '..', 'bin');
43
- const outputName = platform === 'win32' ? 'vibes.exe' : 'vibes';
44
- return path.join(dir, outputName);
45
- }
46
-
47
- function getPersistentBinDir() {
48
- if (process.env.VIBES_INSTALL_DIR) return process.env.VIBES_INSTALL_DIR;
49
-
50
- const bunGlobalBin = readCommandOutput('bun', ['pm', 'bin', '-g']);
51
- if (bunGlobalBin) return bunGlobalBin;
52
-
53
- if (process.env.BUN_INSTALL) return path.join(process.env.BUN_INSTALL, 'bin');
54
-
55
- if (process.platform === 'win32') {
56
- const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
57
- return path.join(base, 'Programs', 'vibes', 'bin');
58
- }
59
-
60
- if (process.env.XDG_BIN_HOME) return process.env.XDG_BIN_HOME;
61
-
62
- // POSIX fallback used by Linux distributions and many shell setups. If it is
63
- // not on PATH, the installer prints a warning with the exact directory.
64
- return path.join(os.homedir(), '.local', 'bin');
65
- }
66
-
67
- function readCommandOutput(command, args) {
68
- const result = spawnSync(command, args, {
69
- encoding: 'utf8',
70
- stdio: ['ignore', 'pipe', 'ignore'],
71
- windowsHide: true
72
- });
73
- if (result.error || result.status !== 0) return undefined;
74
- const value = result.stdout.trim();
75
- return value.length > 0 ? value : undefined;
76
- }
77
-
78
- function pathHasDir(dir) {
79
- const pathValue = process.env.PATH || '';
80
- const resolvedDir = path.resolve(dir);
81
- return pathValue
82
- .split(path.delimiter)
83
- .filter(Boolean)
84
- .some((entry) => path.resolve(entry) === resolvedDir);
85
- }
86
-
87
- function showProgress(total, current) {
88
- if (total > 0) {
89
- const percent = Math.round((current / total) * 100);
90
- const bar = '[' + '='.repeat(Math.floor(percent / 5)) + ' '.repeat(20 - Math.floor(percent / 5)) + ']';
91
- process.stdout.write(`\r${bar} ${percent}%`);
92
- } else {
93
- process.stdout.write('.');
94
- }
95
- }
96
-
97
- function download(url, dest, callback) {
98
- const protocol = url.startsWith('https') ? https : http;
99
-
100
- const request = protocol.get(url, { timeout: 30000 }, (response) => {
101
- // Handle redirects
102
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
103
- download(response.headers.location, dest, callback);
104
- return;
105
- }
106
-
107
- if (response.statusCode !== 200) {
108
- callback(new Error(`Failed to download: HTTP ${response.statusCode}`));
109
- return;
110
- }
111
-
112
- const total = parseInt(response.headers['content-length'], 10);
113
- let downloaded = 0;
114
-
115
- const fp = fs.openSync(dest, 'w');
116
- response.on('data', (chunk) => {
117
- downloaded += chunk.length;
118
- showProgress(total, downloaded);
119
- fs.writeSync(fp, chunk);
120
- });
121
-
122
- response.on('end', () => {
123
- fs.closeSync(fp);
124
- console.log();
125
- callback(null);
126
- });
127
-
128
- response.on('error', (err) => {
129
- fs.closeSync(fp);
130
- callback(err);
131
- });
132
- });
133
-
134
- request.on('error', (err) => {
135
- callback(err);
136
- });
137
-
138
- request.on('timeout', () => {
139
- request.destroy();
140
- callback(new Error('Download timed out'));
141
- });
142
- }
143
-
144
- function fetchText(url, callback) {
145
- const protocol = url.startsWith('https') ? https : http;
146
-
147
- const request = protocol.get(url, { timeout: 30000 }, (response) => {
148
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
149
- fetchText(response.headers.location, callback);
150
- return;
151
- }
152
-
153
- if (response.statusCode !== 200) {
154
- callback(new Error(`HTTP ${response.statusCode}`));
155
- return;
156
- }
157
-
158
- let body = '';
159
- response.setEncoding('utf8');
160
- response.on('data', (chunk) => {
161
- body += chunk;
162
- });
163
- response.on('end', () => callback(null, body));
164
- response.on('error', callback);
165
- });
166
-
167
- request.on('error', callback);
168
- request.on('timeout', () => {
169
- request.destroy();
170
- callback(new Error('Download timed out'));
171
- });
172
- }
173
-
174
- function verifyChecksum(binaryName, filePath) {
175
- return new Promise((resolve, reject) => {
176
- if (process.env.VIBES_SKIP_CHECKSUM === '1') {
177
- console.warn('Warning: VIBES_SKIP_CHECKSUM=1 set; skipping checksum verification.');
178
- resolve();
179
- return;
180
- }
181
-
182
- const checksumUrl = `${BASE_URL}/SHA256SUMS.txt`;
183
- fetchText(checksumUrl, (err, text) => {
184
- if (err) {
185
- reject(new Error(`Could not fetch checksum file from ${checksumUrl}: ${err.message}`));
186
- return;
187
- }
188
-
189
- const line = text
190
- .split(/\r?\n/)
191
- .find((entry) => entry.trim().endsWith(` ${binaryName}`));
192
- if (!line) {
193
- reject(new Error(`No checksum entry for ${binaryName} in ${checksumUrl}`));
194
- return;
195
- }
196
-
197
- const expected = line.trim().split(/\s+/)[0];
198
- const actual = crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
199
- if (actual !== expected) {
200
- reject(new Error(`Checksum mismatch for ${binaryName}`));
201
- return;
202
- }
203
-
204
- console.log('Verified release checksum');
205
- resolve();
206
- });
207
- });
208
- }
209
-
210
- function makeExecutable(filePath) {
211
- const platform = getPlatform();
212
- if (platform === 'win32') return;
213
-
214
- try {
215
- fs.chmodSync(filePath, 0o755);
216
- } catch (err) {
217
- console.error('Warning: Could not chmod +x:', err.message);
218
- }
219
- }
220
-
221
- function isWorkspaceInstall() {
222
- // Explicit opt-out (set in CI, the monorepo's bun install, or any env that
223
- // already has the binary built locally and doesn't want network on install).
224
- if (process.env.VIBES_SKIP_BINARY_DOWNLOAD === '1') return true;
225
-
226
- // When this package is installed from the npm registry, the realpath of the
227
- // script location includes `/node_modules/` (the unpacked tarball path). When
228
- // bun installs us via a workspace link, realpath dereferences the symlink to
229
- // the monorepo source — which lives outside any `node_modules` directory. We
230
- // use this asymmetry to skip downloads cleanly inside the workspace while
231
- // failing loudly for real end-user installs.
232
- try {
233
- const real = fs.realpathSync(__dirname);
234
- const sep = path.sep;
235
- return !real.includes(`${sep}node_modules${sep}`);
236
- } catch {
237
- return false;
238
- }
239
- }
240
-
241
- async function main() {
242
- if (isWorkspaceInstall()) {
243
- console.log('Vibes CLI installer: workspace install detected, skipping binary download.');
244
- return;
245
- }
246
-
247
- const binaryName = getBinaryName();
248
- const outputPath = getOutputPath();
249
- const url = `${BASE_URL}/${binaryName}`;
250
-
251
- console.log(`Vibes CLI installer`);
252
- console.log(`Platform: ${getPlatform()}, Arch: ${getArch()}`);
253
- console.log(`Downloading ${binaryName}...`);
254
-
255
- // Ensure bin directory exists
256
- const binDir = path.dirname(outputPath);
257
- if (!fs.existsSync(binDir)) {
258
- fs.mkdirSync(binDir, { recursive: true });
259
- }
260
-
261
- // Check if already exists and up-to-date
262
- if (fs.existsSync(outputPath)) {
263
- try {
264
- fs.unlinkSync(outputPath);
265
- } catch (err) {
266
- console.error('Could not remove existing binary:', err.message);
267
- }
268
- }
269
-
270
- return new Promise((resolve, reject) => {
271
- download(url, outputPath, (err) => {
272
- if (err) {
273
- console.error();
274
- console.error('Error:', err.message);
275
- console.error(`Could not download binary from ${url}`);
276
- console.error();
277
- console.error('To resolve:');
278
- console.error(` - Confirm a release exists at ${BASE_URL}`);
279
- console.error(` - Confirm ${BASE_URL}/SHA256SUMS.txt is reachable and contains ${binaryName}`);
280
- console.error(' - Or point VIBES_BINARY_MIRROR at an alternate download base URL');
281
- console.error(' - Or download the binary manually and place it at:');
282
- console.error(` ${outputPath}`);
283
- console.error(' - Or set VIBES_SKIP_BINARY_DOWNLOAD=1 to skip this step');
284
- console.error(' - Or set VIBES_SKIP_CHECKSUM=1 only if you trust the mirror and need to bypass checksum verification');
285
- reject(err);
286
- return;
287
- }
288
-
289
- verifyChecksum(binaryName, outputPath)
290
- .then(() => {
291
- makeExecutable(outputPath);
292
- console.log(`Installed to ${outputPath}`);
293
- const outputDir = path.dirname(outputPath);
294
- if (process.env.VIBES_INSTALL_PERSISTENT === '1' && !pathHasDir(outputDir)) {
295
- console.warn(`Warning: ${outputDir} is not on PATH. Add it to run \`vibes\` directly.`);
296
- }
297
- resolve();
298
- })
299
- .catch(reject);
300
- });
301
- });
302
- }
303
-
304
- main().catch((err) => {
305
- console.error('Installation failed:', err.message);
306
- // End-user installs reach here when isWorkspaceInstall() was false above
307
- // (i.e., the script lives under node_modules) and the download failed. Fail
308
- // loud so the user sees a broken install instead of a silently empty bin/.
309
- process.exit(1);
310
- });