vesper-code 1.0.0 → 1.0.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/README.md CHANGED
@@ -24,8 +24,6 @@ After installation, you can start Vesper Code by running:
24
24
 
25
25
  ```bash
26
26
  vesper [project-directory]
27
- # or
28
- vc [project-directory]
29
27
  ```
30
28
 
31
29
  If no project directory is specified, Vesper Code will use the current directory.
package/index.js CHANGED
@@ -2,484 +2,116 @@
2
2
 
3
3
  const { spawn } = require('child_process')
4
4
  const fs = require('fs')
5
- const http = require('http')
6
- const https = require('https')
7
5
  const os = require('os')
8
6
  const path = require('path')
9
- const zlib = require('zlib')
10
7
 
11
- const tar = require('tar')
12
-
13
- const packageName = 'vesper-code'
14
- const binaryBaseName = 'vesper'
15
-
16
- function createConfig() {
17
- const homeDir = os.homedir()
18
- const configDir = path.join(homeDir, '.config', 'vesper-code')
19
- const binaryName =
20
- process.platform === 'win32' ? `${binaryBaseName}.exe` : binaryBaseName
21
-
22
- return {
23
- homeDir,
24
- configDir,
25
- binaryName,
26
- binaryPath: path.join(configDir, binaryName),
27
- metadataPath: path.join(configDir, 'vesper-metadata.json'),
28
- tempDownloadDir: path.join(configDir, '.download-temp'),
29
- userAgent: `${packageName}-cli`,
30
- requestTimeout: 20000,
31
- }
32
- }
33
-
34
- const CONFIG = createConfig()
35
-
36
- function getPostHogConfig() {
37
- const apiKey =
38
- process.env.VESPER_POSTHOG_API_KEY ||
39
- process.env.NEXT_PUBLIC_POSTHOG_API_KEY
40
- const host =
41
- process.env.VESPER_POSTHOG_HOST ||
42
- process.env.NEXT_PUBLIC_POSTHOG_HOST_URL
43
-
44
- if (!apiKey || !host) {
45
- return null
46
- }
47
-
48
- return { apiKey, host }
49
- }
50
-
51
- /**
52
- * Track update failure event to PostHog.
53
- * Fire-and-forget - errors are silently ignored.
54
- */
55
- function trackUpdateFailed(errorMessage, version, context = {}) {
56
- try {
57
- const posthogConfig = getPostHogConfig()
58
- if (!posthogConfig) {
59
- return
60
- }
61
-
62
- const payload = JSON.stringify({
63
- api_key: posthogConfig.apiKey,
64
- event: 'cli.update_vesper_failed',
65
- properties: {
66
- distinct_id: `anonymous-${CONFIG.homeDir}`,
67
- error: errorMessage,
68
- version: version || 'unknown',
69
- platform: process.platform,
70
- arch: process.arch,
71
- ...context,
72
- },
73
- timestamp: new Date().toISOString(),
74
- })
75
-
76
- const parsedUrl = new URL(`${posthogConfig.host}/capture/`)
77
- const isHttps = parsedUrl.protocol === 'https:'
78
- const options = {
79
- hostname: parsedUrl.hostname,
80
- port: parsedUrl.port || (isHttps ? 443 : 80),
81
- path: parsedUrl.pathname + parsedUrl.search,
82
- method: 'POST',
83
- headers: {
84
- 'Content-Type': 'application/json',
85
- 'Content-Length': Buffer.byteLength(payload),
86
- },
87
- }
88
-
89
- const transport = isHttps ? https : http
90
- const req = transport.request(options)
91
- req.on('error', () => {}) // Silently ignore errors
92
- req.write(payload)
93
- req.end()
94
- } catch (e) {
95
- // Silently ignore any tracking errors
96
- }
97
- }
98
-
99
- const PLATFORM_TARGETS = {
100
- 'linux-x64': `${binaryBaseName}-linux-x64.tar.gz`,
101
- 'linux-arm64': `${binaryBaseName}-linux-arm64.tar.gz`,
102
- 'darwin-x64': `${binaryBaseName}-darwin-x64.tar.gz`,
103
- 'darwin-arm64': `${binaryBaseName}-darwin-arm64.tar.gz`,
104
- 'win32-x64': `${binaryBaseName}-win32-x64.tar.gz`,
105
- }
106
-
107
- const term = {
108
- clearLine: () => {
109
- if (process.stderr.isTTY) {
110
- process.stderr.write('\r\x1b[K')
111
- }
8
+ const TARGET_PACKAGES = {
9
+ 'darwin-arm64': {
10
+ packageName: 'vesper-code-darwin-arm64',
11
+ binaryName: 'vesper',
12
+ },
13
+ 'darwin-x64': {
14
+ packageName: 'vesper-code-darwin-x64',
15
+ binaryName: 'vesper',
16
+ },
17
+ 'linux-x64': {
18
+ packageName: 'vesper-code-linux-x64',
19
+ binaryName: 'vesper',
112
20
  },
113
- write: (text) => {
114
- term.clearLine()
115
- process.stderr.write(text)
21
+ 'linux-arm64': {
22
+ packageName: 'vesper-code-linux-arm64',
23
+ binaryName: 'vesper',
116
24
  },
117
- writeLine: (text) => {
118
- term.clearLine()
119
- process.stderr.write(text + '\n')
25
+ 'win32-x64': {
26
+ packageName: 'vesper-code-windows-x64',
27
+ binaryName: 'vesper.exe',
120
28
  },
121
29
  }
122
30
 
123
- function httpGet(url, options = {}) {
124
- return new Promise((resolve, reject) => {
125
- const parsedUrl = new URL(url)
126
- const reqOptions = {
127
- hostname: parsedUrl.hostname,
128
- path: parsedUrl.pathname + parsedUrl.search,
129
- headers: {
130
- 'User-Agent': CONFIG.userAgent,
131
- ...options.headers,
132
- },
133
- }
134
-
135
- const req = https.get(reqOptions, (res) => {
136
- if (res.statusCode === 302 || res.statusCode === 301) {
137
- return httpGet(new URL(res.headers.location, url).href, options)
138
- .then(resolve)
139
- .catch(reject)
140
- }
141
- resolve(res)
142
- })
143
-
144
- req.on('error', reject)
145
-
146
- const timeout = options.timeout || CONFIG.requestTimeout
147
- req.setTimeout(timeout, () => {
148
- req.destroy()
149
- reject(new Error('Request timeout.'))
150
- })
151
- })
31
+ function getTarget() {
32
+ return `${process.platform}-${process.arch}`
152
33
  }
153
34
 
154
- async function getLatestVersion() {
155
- try {
156
- const res = await httpGet(
157
- `https://registry.npmjs.org/${packageName}/latest`,
158
- )
159
-
160
- if (res.statusCode !== 200) return null
161
-
162
- const body = await streamToString(res)
163
- const packageData = JSON.parse(body)
35
+ function getPlatformPackage() {
36
+ const target = getTarget()
37
+ const platformPackage = TARGET_PACKAGES[target]
164
38
 
165
- return packageData.version || null
166
- } catch (error) {
167
- return null
39
+ if (!platformPackage) {
40
+ throw new Error(
41
+ `Unsupported platform: ${process.platform} ${process.arch}. ` +
42
+ 'Supported targets: darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64.',
43
+ )
168
44
  }
169
- }
170
45
 
171
- function streamToString(stream) {
172
- return new Promise((resolve, reject) => {
173
- let data = ''
174
- stream.on('data', (chunk) => (data += chunk))
175
- stream.on('end', () => resolve(data))
176
- stream.on('error', reject)
177
- })
46
+ return platformPackage
178
47
  }
179
48
 
180
- function getCurrentVersion() {
49
+ function resolveBinaryPath() {
50
+ const platformPackage = getPlatformPackage()
51
+
52
+ let packageJsonPath
181
53
  try {
182
- if (!fs.existsSync(CONFIG.metadataPath)) {
183
- return null
184
- }
185
- const metadata = JSON.parse(fs.readFileSync(CONFIG.metadataPath, 'utf8'))
186
- // Also verify the binary still exists
187
- if (!fs.existsSync(CONFIG.binaryPath)) {
188
- return null
189
- }
190
- return metadata.version || null
54
+ packageJsonPath = require.resolve(`${platformPackage.packageName}/package.json`)
191
55
  } catch (error) {
192
- return null
193
- }
194
- }
195
-
196
- function compareVersions(v1, v2) {
197
- if (!v1 || !v2) return 0
198
-
199
- // Always update if the current version is not a valid semver
200
- // e.g. 1.0.420-beta.1
201
- if (!v1.match(/^\d+(\.\d+)*$/)) {
202
- return -1
203
- }
204
-
205
- const parseVersion = (version) => {
206
- const parts = version.split('-')
207
- const mainParts = parts[0].split('.').map(Number)
208
- const prereleaseParts = parts[1] ? parts[1].split('.') : []
209
- return { main: mainParts, prerelease: prereleaseParts }
210
- }
211
-
212
- const p1 = parseVersion(v1)
213
- const p2 = parseVersion(v2)
214
-
215
- for (let i = 0; i < Math.max(p1.main.length, p2.main.length); i++) {
216
- const n1 = p1.main[i] || 0
217
- const n2 = p2.main[i] || 0
218
-
219
- if (n1 < n2) return -1
220
- if (n1 > n2) return 1
221
- }
222
-
223
- if (p1.prerelease.length === 0 && p2.prerelease.length === 0) {
224
- return 0
225
- } else if (p1.prerelease.length === 0) {
226
- return 1
227
- } else if (p2.prerelease.length === 0) {
228
- return -1
229
- } else {
230
- for (
231
- let i = 0;
232
- i < Math.max(p1.prerelease.length, p2.prerelease.length);
233
- i++
234
- ) {
235
- const pr1 = p1.prerelease[i] || ''
236
- const pr2 = p2.prerelease[i] || ''
237
-
238
- const isNum1 = !isNaN(parseInt(pr1))
239
- const isNum2 = !isNaN(parseInt(pr2))
240
-
241
- if (isNum1 && isNum2) {
242
- const num1 = parseInt(pr1)
243
- const num2 = parseInt(pr2)
244
- if (num1 < num2) return -1
245
- if (num1 > num2) return 1
246
- } else if (isNum1 && !isNum2) {
247
- return 1
248
- } else if (!isNum1 && isNum2) {
249
- return -1
250
- } else if (pr1 < pr2) {
251
- return -1
252
- } else if (pr1 > pr2) {
253
- return 1
254
- }
255
- }
256
- return 0
257
- }
258
- }
259
-
260
- function formatBytes(bytes) {
261
- if (bytes === 0) return '0 B'
262
- const k = 1024
263
- const sizes = ['B', 'KB', 'MB', 'GB']
264
- const i = Math.floor(Math.log(bytes) / Math.log(k))
265
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
266
- }
267
-
268
- function createProgressBar(percentage, width = 30) {
269
- const filled = Math.round((width * percentage) / 100)
270
- const empty = width - filled
271
- return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']'
272
- }
273
-
274
- async function downloadBinary(version) {
275
- const platformKey = `${process.platform}-${process.arch}`
276
- const fileName = PLATFORM_TARGETS[platformKey]
277
-
278
- if (!fileName) {
279
- const error = new Error(`Unsupported platform: ${process.platform} ${process.arch}`)
280
- trackUpdateFailed(error.message, version, { stage: 'platform_check' })
281
- throw error
282
- }
283
-
284
- const downloadUrl = `${
285
- process.env.NEXT_PUBLIC_VESPER_APP_URL || 'https://vesper-code-production.up.railway.app'
286
- }/api/releases/download/${version}/${fileName}`
287
-
288
- // Ensure config directory exists
289
- fs.mkdirSync(CONFIG.configDir, { recursive: true })
290
-
291
- // Clean up any previous temp download directory
292
- if (fs.existsSync(CONFIG.tempDownloadDir)) {
293
- fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
294
- }
295
- fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
296
-
297
- term.write('Downloading...')
298
-
299
- const res = await httpGet(downloadUrl)
300
-
301
- if (res.statusCode !== 200) {
302
- fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
303
- const error = new Error(`Download failed: HTTP ${res.statusCode}`)
304
- trackUpdateFailed(error.message, version, { stage: 'http_download', statusCode: res.statusCode })
305
- throw error
56
+ throw new Error(
57
+ `Missing platform package ${platformPackage.packageName}. ` +
58
+ 'Reinstall Vesper with optional dependencies enabled: npm install -g vesper-code',
59
+ )
306
60
  }
307
61
 
308
- const totalSize = parseInt(res.headers['content-length'] || '0', 10)
309
- let downloadedSize = 0
310
- let lastProgressTime = Date.now()
311
-
312
- res.on('data', (chunk) => {
313
- downloadedSize += chunk.length
314
- const now = Date.now()
315
- if (now - lastProgressTime >= 100 || downloadedSize === totalSize) {
316
- lastProgressTime = now
317
- if (totalSize > 0) {
318
- const pct = Math.round((downloadedSize / totalSize) * 100)
319
- term.write(
320
- `Downloading... ${createProgressBar(pct)} ${pct}% of ${formatBytes(
321
- totalSize,
322
- )}`,
323
- )
324
- } else {
325
- term.write(`Downloading... ${formatBytes(downloadedSize)}`)
326
- }
327
- }
328
- })
329
-
330
- // Extract to temp directory
331
- await new Promise((resolve, reject) => {
332
- res
333
- .pipe(zlib.createGunzip())
334
- .pipe(tar.x({ cwd: CONFIG.tempDownloadDir }))
335
- .on('finish', resolve)
336
- .on('error', reject)
337
- })
338
-
339
- const tempBinaryPath = path.join(CONFIG.tempDownloadDir, CONFIG.binaryName)
340
-
341
- // Verify the binary was extracted
342
- if (!fs.existsSync(tempBinaryPath)) {
343
- const files = fs.readdirSync(CONFIG.tempDownloadDir)
344
- fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
345
- const error = new Error(
346
- `Binary not found after extraction. Expected: ${CONFIG.binaryName}, Available files: ${files.join(', ')}`,
62
+ const binaryPath = path.join(path.dirname(packageJsonPath), platformPackage.binaryName)
63
+ if (!fs.existsSync(binaryPath)) {
64
+ throw new Error(
65
+ `Platform package ${platformPackage.packageName} is installed, ` +
66
+ `but ${platformPackage.binaryName} is missing.`,
347
67
  )
348
- trackUpdateFailed(error.message, version, { stage: 'extraction' })
349
- throw error
350
68
  }
351
69
 
352
- // Set executable permissions
353
70
  if (process.platform !== 'win32') {
354
- fs.chmodSync(tempBinaryPath, 0o755)
355
- }
356
-
357
- // Move binary to final location
358
- try {
359
- if (fs.existsSync(CONFIG.binaryPath)) {
360
- try {
361
- fs.unlinkSync(CONFIG.binaryPath)
362
- } catch (err) {
363
- // Fallback: try renaming the locked/undeletable binary (Windows)
364
- const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
365
- try {
366
- fs.renameSync(CONFIG.binaryPath, backupPath)
367
- } catch (renameErr) {
368
- throw new Error(
369
- `Failed to replace existing binary. ` +
370
- `unlink error: ${err.code || err.message}, ` +
371
- `rename error: ${renameErr.code || renameErr.message}`,
372
- )
373
- }
374
- }
375
- }
376
- fs.renameSync(tempBinaryPath, CONFIG.binaryPath)
377
-
378
- // Save version metadata for fast version checking
379
- fs.writeFileSync(
380
- CONFIG.metadataPath,
381
- JSON.stringify({ version }, null, 2),
382
- )
383
- } finally {
384
- // Clean up temp directory even if rename fails
385
- if (fs.existsSync(CONFIG.tempDownloadDir)) {
386
- fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
71
+ try {
72
+ fs.chmodSync(binaryPath, 0o755)
73
+ } catch {
74
+ // Best effort. Spawn will surface a real permission error if this fails.
387
75
  }
388
76
  }
389
77
 
390
- term.clearLine()
391
- console.log('Download complete! Starting Vesper Code...')
78
+ return binaryPath
392
79
  }
393
80
 
394
- async function ensureBinaryExists() {
395
- const currentVersion = getCurrentVersion()
396
- if (currentVersion !== null) {
397
- return
398
- }
399
-
400
- const version = await getLatestVersion()
401
- if (!version) {
402
- console.error('❌ Failed to determine latest version')
403
- console.error('Please check your internet connection and try again')
404
- process.exit(1)
405
- }
406
-
81
+ function main() {
82
+ let binaryPath
407
83
  try {
408
- await downloadBinary(version)
84
+ binaryPath = resolveBinaryPath()
409
85
  } catch (error) {
410
- term.clearLine()
411
- console.error('❌ Failed to download vesper:', error.message)
412
- console.error('Please check your internet connection and try again')
86
+ console.error('Failed to start Vesper Code.')
87
+ console.error(error instanceof Error ? error.message : String(error))
88
+ console.error('')
89
+ console.error('Debug info:')
90
+ console.error(` platform: ${process.platform}`)
91
+ console.error(` arch: ${process.arch}`)
92
+ console.error(` node: ${process.version}`)
93
+ console.error(` home: ${os.homedir()}`)
413
94
  process.exit(1)
414
95
  }
415
- }
416
-
417
- async function checkForUpdates(runningProcess, exitListener) {
418
- try {
419
- const currentVersion = getCurrentVersion()
420
-
421
- const latestVersion = await getLatestVersion()
422
- if (!latestVersion) return
423
-
424
- if (
425
- // Download new version if current version is unknown or outdated.
426
- currentVersion === null ||
427
- compareVersions(currentVersion, latestVersion) < 0
428
- ) {
429
- term.clearLine()
430
96
 
431
- runningProcess.removeListener('exit', exitListener)
432
- runningProcess.kill('SIGTERM')
433
-
434
- await new Promise((resolve) => {
435
- runningProcess.on('exit', resolve)
436
- setTimeout(() => {
437
- if (!runningProcess.killed) {
438
- runningProcess.kill('SIGKILL')
439
- }
440
- resolve()
441
- }, 5000)
442
- })
443
-
444
- console.log(`Update available: ${currentVersion} → ${latestVersion}`)
445
-
446
- await downloadBinary(latestVersion)
447
-
448
- const newChild = spawn(CONFIG.binaryPath, process.argv.slice(2), {
449
- stdio: 'inherit',
450
- detached: false,
451
- })
452
-
453
- newChild.on('exit', (code) => {
454
- process.exit(code || 0)
455
- })
456
-
457
- return new Promise(() => {})
458
- }
459
- } catch (error) {
460
- // Ignore update failures
461
- }
462
- }
463
-
464
- async function main() {
465
- await ensureBinaryExists()
466
-
467
- const child = spawn(CONFIG.binaryPath, process.argv.slice(2), {
97
+ const child = spawn(binaryPath, process.argv.slice(2), {
468
98
  stdio: 'inherit',
99
+ env: process.env,
469
100
  })
470
101
 
471
- const exitListener = (code) => {
472
- process.exit(code || 0)
473
- }
474
-
475
- child.on('exit', exitListener)
102
+ child.on('error', (error) => {
103
+ console.error('Failed to start Vesper Code.')
104
+ console.error(error.message)
105
+ process.exit(1)
106
+ })
476
107
 
477
- setTimeout(() => {
478
- checkForUpdates(child, exitListener)
479
- }, 100)
108
+ child.on('exit', (code, signal) => {
109
+ if (signal) {
110
+ process.kill(process.pid, signal)
111
+ return
112
+ }
113
+ process.exit(code || 0)
114
+ })
480
115
  }
481
116
 
482
- main().catch((error) => {
483
- console.error('❌ Unexpected error:', error.message)
484
- process.exit(1)
485
- })
117
+ main()
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "vesper-code",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Vesper Code — AI coding agent",
5
5
  "license": "MIT",
6
6
  "bin": {
7
- "vesper": "index.js",
8
- "vc": "index.js"
7
+ "vesper": "index.js"
9
8
  },
10
9
  "scripts": {
11
10
  "postinstall": "node postinstall.js",
@@ -28,14 +27,18 @@
28
27
  "engines": {
29
28
  "node": ">=16"
30
29
  },
31
- "dependencies": {
32
- "tar": "^7.0.0"
30
+ "optionalDependencies": {
31
+ "vesper-code-darwin-arm64": "1.0.1",
32
+ "vesper-code-darwin-x64": "1.0.1",
33
+ "vesper-code-linux-arm64": "1.0.1",
34
+ "vesper-code-linux-x64": "1.0.1",
35
+ "vesper-code-windows-x64": "1.0.1"
33
36
  },
34
37
  "repository": {
35
38
  "type": "git",
36
- "url": "https://github.com/andreygoldman13/vesper-code.git"
39
+ "url": "git+https://github.com/andreygoldman13/vesper-code.git"
37
40
  },
38
- "homepage": "https://vesper-code-production.up.railway.app",
41
+ "homepage": "https://vespercode.com",
39
42
  "publishConfig": {
40
43
  "access": "public"
41
44
  }
package/postinstall.js CHANGED
@@ -20,7 +20,7 @@ try {
20
20
 
21
21
  // Print welcome message
22
22
  console.log('\n');
23
- console.log('🎉 Welcome to Vesper Code!');
23
+ console.log('Welcome to Vesper Code.');
24
24
  console.log('\n');
25
25
  console.log('To get started:');
26
26
  console.log(' 1. cd to your project directory');