openvibe-desktop 1.2.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/dist/cli.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import { getInstallDir, checkForUpdate, downloadAndVerify } from './updater.js';
7
+ const VERSION_FILE = join(getInstallDir(), '.version');
8
+ const CACHE_FILE = join(getInstallDir(), '.cache.json');
9
+ async function readCache() {
10
+ try {
11
+ const data = await readFile(CACHE_FILE, 'utf-8');
12
+ return JSON.parse(data);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ async function writeCache(data) {
19
+ await mkdir(getInstallDir(), { recursive: true });
20
+ await writeFile(CACHE_FILE, JSON.stringify(data, null, 2));
21
+ }
22
+ async function getOrDownloadBinary() {
23
+ // Check cache first
24
+ const cache = await readCache();
25
+ if (cache && existsSync(cache.binaryPath)) {
26
+ return cache.binaryPath;
27
+ }
28
+ // Check version file
29
+ let currentVersion = '';
30
+ try {
31
+ currentVersion = (await readFile(VERSION_FILE, 'utf-8')).trim();
32
+ }
33
+ catch {
34
+ currentVersion = '';
35
+ }
36
+ const info = await checkForUpdate(VERSION_FILE);
37
+ if (!info) {
38
+ throw new Error('failed to fetch latest release info');
39
+ }
40
+ const binaryPath = await downloadAndVerify(info);
41
+ await writeCache({ version: info.version, binaryPath });
42
+ return binaryPath;
43
+ }
44
+ async function main() {
45
+ const args = process.argv.slice(2);
46
+ if (args.includes('--version') || args.includes('-v')) {
47
+ const cache = await readCache();
48
+ console.log(cache?.version || 'unknown');
49
+ process.exit(0);
50
+ }
51
+ if (args.includes('--help') || args.includes('-h')) {
52
+ console.log(`OpenVibe Desktop — open-source agentic coding environment
53
+
54
+ Usage:
55
+ openvibe [options]
56
+
57
+ Options:
58
+ --version, -v Show version
59
+ --help, -h Show this help
60
+ --update Force update check`);
61
+ process.exit(0);
62
+ }
63
+ if (args.includes('--update')) {
64
+ console.log('Checking for updates...');
65
+ const info = await checkForUpdate(VERSION_FILE);
66
+ if (!info) {
67
+ console.log('No update available or failed to check');
68
+ process.exit(1);
69
+ }
70
+ const cache = await readCache();
71
+ if (cache?.version === info.version) {
72
+ console.log(`Already up-to-date (v${info.version})`);
73
+ process.exit(0);
74
+ }
75
+ console.log(`Updating to v${info.version}...`);
76
+ const binaryPath = await downloadAndVerify(info);
77
+ console.log(`Updated to v${info.version}`);
78
+ process.exit(0);
79
+ }
80
+ try {
81
+ const binaryPath = await getOrDownloadBinary();
82
+ const child = spawn(binaryPath, args, {
83
+ stdio: 'inherit',
84
+ env: { ...process.env },
85
+ });
86
+ child.on('exit', (code) => {
87
+ process.exit(code ?? 0);
88
+ });
89
+ child.on('error', (err) => {
90
+ console.error(`Failed to launch OpenVibe: ${err.message}`);
91
+ process.exit(1);
92
+ });
93
+ }
94
+ catch (err) {
95
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
96
+ process.exit(1);
97
+ }
98
+ }
99
+ main();
@@ -0,0 +1,217 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { writeFile, mkdir, unlink } from 'node:fs/promises';
3
+ import { existsSync, createReadStream } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { get } from 'node:https';
6
+ import { request as httpGet } from 'node:http';
7
+ import { exec as execCb } from 'node:child_process';
8
+ const GITHUB_REPO = 'nihmadev/OpenVibe';
9
+ const API_BASE = process.env.OPENVIBE_API_URL || `https://api.github.com/repos/${GITHUB_REPO}`;
10
+ export function getPlatform() {
11
+ const archMap = {
12
+ x64: 'x64',
13
+ arm64: 'arm64',
14
+ ia32: 'x86',
15
+ };
16
+ const platformMap = {
17
+ linux: 'linux',
18
+ darwin: 'macos',
19
+ win32: 'windows',
20
+ };
21
+ const platform = platformMap[process.platform];
22
+ const arch = archMap[process.arch];
23
+ if (!platform)
24
+ throw new Error(`unsupported platform: ${process.platform}`);
25
+ if (!arch)
26
+ throw new Error(`unsupported arch: ${process.arch}`);
27
+ return { platform, arch };
28
+ }
29
+ export function getInstallDir() {
30
+ const home = process.env.HOME || process.env.USERPROFILE || '';
31
+ return join(home, '.openvibe');
32
+ }
33
+ export function getBinDir() {
34
+ return join(getInstallDir(), 'bin');
35
+ }
36
+ export function getVersionDir(version) {
37
+ return join(getInstallDir(), 'versions', version);
38
+ }
39
+ function fetchJSON(url) {
40
+ return new Promise((resolve, reject) => {
41
+ const protocol = url.startsWith('https') ? get : httpGet;
42
+ const req = protocol(url, { headers: { 'Accept': 'application/json', 'User-Agent': 'openvibe-desktop' } }, (res) => {
43
+ let data = '';
44
+ res.on('data', (chunk) => data += chunk);
45
+ res.on('end', () => {
46
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
47
+ try {
48
+ resolve(JSON.parse(data));
49
+ }
50
+ catch (e) {
51
+ reject(new Error(`invalid JSON: ${data.slice(0, 200)}`));
52
+ }
53
+ }
54
+ else {
55
+ reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
56
+ }
57
+ });
58
+ });
59
+ req.on('error', reject);
60
+ req.end();
61
+ });
62
+ }
63
+ function downloadFile(url, dest) {
64
+ return new Promise((resolve, reject) => {
65
+ const protocol = url.startsWith('https') ? get : httpGet;
66
+ const req = protocol(url, { headers: { 'User-Agent': 'openvibe-desktop' } }, async (res) => {
67
+ if (!res.statusCode || res.statusCode >= 300) {
68
+ reject(new Error(`download failed: HTTP ${res.statusCode}`));
69
+ return;
70
+ }
71
+ const chunks = [];
72
+ for await (const chunk of res)
73
+ chunks.push(chunk);
74
+ await mkdir(join(dest, '..'), { recursive: true });
75
+ await writeFile(dest, Buffer.concat(chunks));
76
+ resolve();
77
+ });
78
+ req.on('error', reject);
79
+ req.end();
80
+ });
81
+ }
82
+ export async function checkForUpdate(versionFile) {
83
+ const { platform, arch } = getPlatform();
84
+ try {
85
+ const baseURL = API_BASE.replace(/\/+$/, '');
86
+ const url = `${baseURL}/releases/latest`;
87
+ // Try the Go API first
88
+ if (!process.env.OPENVIBE_API_URL && baseURL.includes('api.github.com')) {
89
+ return await checkViaGitHub(platform, arch);
90
+ }
91
+ const data = await fetchJSON(`${baseURL}/updates/latest?platform=${platform}&arch=${arch}`);
92
+ return data;
93
+ }
94
+ catch {
95
+ // Fallback to GitHub API
96
+ return await checkViaGitHub(platform, arch);
97
+ }
98
+ }
99
+ async function checkViaGitHub(platform, arch) {
100
+ const data = await fetchJSON(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`);
101
+ const version = data.tag_name.replace(/^v/, '');
102
+ const extMap = {
103
+ linux: '.AppImage',
104
+ macos: '.dmg',
105
+ windows: '.exe',
106
+ };
107
+ const ext = extMap[platform];
108
+ if (!ext)
109
+ return null;
110
+ const asset = data.assets.find(a => a.name.endsWith(ext) && a.name.includes(platform));
111
+ if (!asset)
112
+ return null;
113
+ return {
114
+ version,
115
+ releaseUrl: `https://github.com/${GITHUB_REPO}/releases/tag/${data.tag_name}`,
116
+ platform,
117
+ arch,
118
+ url: asset.browser_download_url,
119
+ };
120
+ }
121
+ export async function downloadAndVerify(info) {
122
+ const versionDir = getVersionDir(info.version);
123
+ const binDir = getBinDir();
124
+ await mkdir(versionDir, { recursive: true });
125
+ await mkdir(binDir, { recursive: true });
126
+ const fileName = info.url.split('/').pop() || `openvibe-${info.platform}-${info.arch}`;
127
+ const filePath = join(versionDir, fileName);
128
+ if (existsSync(filePath)) {
129
+ if (info.sha256) {
130
+ const hash = await sha256File(filePath);
131
+ if (hash === info.sha256) {
132
+ return makeExecutableAndLink(filePath, binDir, info);
133
+ }
134
+ }
135
+ else {
136
+ return makeExecutableAndLink(filePath, binDir, info);
137
+ }
138
+ }
139
+ console.log(`Downloading OpenVibe ${info.version} (${info.platform}-${info.arch})...`);
140
+ await downloadFile(info.url, filePath);
141
+ if (info.sha256) {
142
+ const hash = await sha256File(filePath);
143
+ if (hash !== info.sha256) {
144
+ await unlink(filePath);
145
+ throw new Error(`SHA256 mismatch: expected ${info.sha256}, got ${hash}`);
146
+ }
147
+ console.log('SHA256 verified');
148
+ }
149
+ return makeExecutableAndLink(filePath, binDir, info);
150
+ }
151
+ async function makeExecutableAndLink(filePath, binDir, info) {
152
+ // Get the actual binary path (for dmg we need to extract)
153
+ const { platform } = info;
154
+ if (platform === 'linux') {
155
+ await exec(`chmod +x "${filePath}"`);
156
+ const link = join(binDir, 'openvibe');
157
+ await writeFile(link.replace(/openvibe$/, '.version'), info.version);
158
+ // Symlink the binary
159
+ try {
160
+ await unlink(link);
161
+ }
162
+ catch { /* ignore */ }
163
+ await exec(`ln -sf "${filePath}" "${link}"`);
164
+ return link;
165
+ }
166
+ if (platform === 'macos') {
167
+ // Mount dmg, copy .app, detach
168
+ const mountPoint = `/tmp/openvibe-mount-${Date.now()}`;
169
+ await exec(`mkdir -p "${mountPoint}"`);
170
+ await exec(`hdiutil attach "${filePath}" -mountpoint "${mountPoint}" -nobrowse -quiet`);
171
+ const appPath = join(binDir, 'OpenVibe.app');
172
+ if (existsSync(appPath)) {
173
+ await exec(`rm -rf "${appPath}"`);
174
+ }
175
+ // Find .app in mounted volume
176
+ const { stdout } = await exec(`find "${mountPoint}" -name "*.app" -maxdepth 2 -type d | head -1`);
177
+ const srcApp = stdout.trim();
178
+ if (srcApp) {
179
+ await exec(`cp -R "${srcApp}" "${appPath}"`);
180
+ }
181
+ await exec(`hdiutil detach "${mountPoint}" -quiet -force`);
182
+ await exec(`rm -rf "${mountPoint}"`);
183
+ await writeFile(join(binDir, '.version'), info.version);
184
+ return join(appPath, 'Contents/MacOS/openvibe');
185
+ }
186
+ if (platform === 'windows') {
187
+ const link = join(binDir, 'openvibe.exe');
188
+ await writeFile(link.replace(/openvibe\.exe$/, '.version'), info.version);
189
+ // For exe installer, just symlink
190
+ try {
191
+ await unlink(link);
192
+ }
193
+ catch { /* ignore */ }
194
+ await exec(`mklink "${link}" "${filePath}"`, true);
195
+ return link;
196
+ }
197
+ return filePath;
198
+ }
199
+ function sha256File(filePath) {
200
+ return new Promise((resolve, reject) => {
201
+ const hash = createHash('sha256');
202
+ const stream = createReadStream(filePath);
203
+ stream.on('data', (chunk) => hash.update(chunk));
204
+ stream.on('end', () => resolve(hash.digest('hex')));
205
+ stream.on('error', reject);
206
+ });
207
+ }
208
+ function exec(cmd, ignoreError = false) {
209
+ return new Promise((resolve, reject) => {
210
+ execCb(cmd, (err, stdout, stderr) => {
211
+ if (err && !ignoreError)
212
+ reject(err);
213
+ else
214
+ resolve({ stdout, stderr });
215
+ });
216
+ });
217
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "openvibe-desktop",
3
+ "version": "1.2.0",
4
+ "description": "OpenVibe Desktop — open-source agentic coding environment. Bring your own AI model.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "mt-studio <lolz@nihmadev.fun>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/nihmadev/OpenVibe"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "openvibe": "dist/cli.js"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build",
18
+ "watch": "tsc --watch"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "files": [
24
+ "dist/",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "keywords": [
29
+ "openvibe",
30
+ "ai",
31
+ "coding",
32
+ "agent",
33
+ "desktop",
34
+ "tauri"
35
+ ],
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "typescript": "^5.4.0"
39
+ }
40
+ }