codecane 1.0.403 → 1.0.405

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecane",
3
- "version": "1.0.403",
3
+ "version": "1.0.405",
4
4
  "description": "AI coding agent",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -30,6 +30,23 @@ const PLATFORM_TARGETS = {
30
30
  'win32-x64': 'codebuff-win32-x64.zip',
31
31
  }
32
32
 
33
+ // Terminal utilities
34
+ const term = {
35
+ clearLine: () => {
36
+ if (process.stderr.isTTY) {
37
+ process.stderr.write('\r\x1b[K')
38
+ }
39
+ },
40
+ write: (text) => {
41
+ term.clearLine()
42
+ process.stderr.write(text)
43
+ },
44
+ writeLine: (text) => {
45
+ term.clearLine()
46
+ process.stderr.write(text + '\n')
47
+ },
48
+ }
49
+
33
50
  // Utility functions
34
51
  function httpGet(url, options = {}) {
35
52
  return new Promise((resolve, reject) => {
@@ -112,14 +129,22 @@ function compareVersions(v1, v2) {
112
129
  return 0
113
130
  }
114
131
 
115
- function showProgress(downloaded, total) {
116
- if (total > 0) {
117
- const percentage = Math.round((downloaded / total) * 100)
118
- process.stderr.write(`\r${percentage}%`)
119
- } else {
120
- const downloadedMB = (downloaded / 1024 / 1024).toFixed(1)
121
- process.stderr.write(`\r${downloadedMB} MB`)
122
- }
132
+ function formatBytes(bytes) {
133
+ if (bytes === 0) return '0 B'
134
+ const k = 1024
135
+ const sizes = ['B', 'KB', 'MB', 'GB']
136
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
137
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
138
+ }
139
+
140
+ function formatSpeed(bytesPerSecond) {
141
+ return formatBytes(bytesPerSecond) + '/s'
142
+ }
143
+
144
+ function createProgressBar(percentage, width = 30) {
145
+ const filled = Math.round((width * percentage) / 100)
146
+ const empty = width - filled
147
+ return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']'
123
148
  }
124
149
 
125
150
  async function downloadBinary(version) {
@@ -135,7 +160,7 @@ async function downloadBinary(version) {
135
160
  // Ensure config directory exists
136
161
  fs.mkdirSync(CONFIG.configDir, { recursive: true })
137
162
 
138
- console.log(`Downloading codebuff v${version}...`)
163
+ term.write(`Downloading codebuff v${version}...`)
139
164
 
140
165
  const res = await httpGet(downloadUrl)
141
166
 
@@ -146,6 +171,7 @@ async function downloadBinary(version) {
146
171
  const totalSize = parseInt(res.headers['content-length'] || '0', 10)
147
172
  let downloadedSize = 0
148
173
  let lastProgressTime = Date.now()
174
+ let lastDownloadedSize = 0
149
175
 
150
176
  const chunks = []
151
177
 
@@ -155,13 +181,27 @@ async function downloadBinary(version) {
155
181
 
156
182
  const now = Date.now()
157
183
  if (now - lastProgressTime >= 100 || downloadedSize === totalSize) {
184
+ const elapsedSeconds = (now - lastProgressTime) / 1000
185
+ const bytesThisInterval = downloadedSize - lastDownloadedSize
186
+ const speed = bytesThisInterval / elapsedSeconds
187
+
158
188
  lastProgressTime = now
159
- showProgress(downloadedSize, totalSize)
189
+ lastDownloadedSize = downloadedSize
190
+
191
+ if (totalSize > 0) {
192
+ const percentage = Math.round((downloadedSize / totalSize) * 100)
193
+ const progressBar = createProgressBar(percentage)
194
+ // const sizeInfo = `${formatBytes(downloadedSize)}/${formatBytes(totalSize)}`
195
+ const speedInfo = speed > 0 ? formatSpeed(speed) : ''
196
+
197
+ term.write(`Downloading... ${progressBar} ${percentage}% ${speedInfo}`)
198
+ } else {
199
+ term.write(`Downloading... ${formatBytes(downloadedSize)}`)
200
+ }
160
201
  }
161
202
  }
162
203
 
163
- process.stderr.write('\n')
164
- console.log('Extracting...')
204
+ term.write('Extracting...')
165
205
 
166
206
  const buffer = Buffer.concat(chunks)
167
207
 
@@ -200,6 +240,9 @@ async function downloadBinary(version) {
200
240
  } else {
201
241
  throw new Error(`Binary not found after extraction`)
202
242
  }
243
+
244
+ // Clear the line after successful download
245
+ term.clearLine()
203
246
  }
204
247
 
205
248
  async function ensureBinaryExists() {
@@ -214,6 +257,7 @@ async function ensureBinaryExists() {
214
257
  try {
215
258
  await downloadBinary(version)
216
259
  } catch (error) {
260
+ term.clearLine()
217
261
  console.error('❌ Failed to download codebuff:', error.message)
218
262
  console.error('Please try again later.')
219
263
  process.exit(1)
@@ -232,7 +276,7 @@ async function ensureBinaryExists() {
232
276
  }
233
277
  }
234
278
 
235
- async function checkForUpdates(runningProcess) {
279
+ async function checkForUpdates(runningProcess, exitListener) {
236
280
  try {
237
281
  const currentVersion = getCurrentVersion()
238
282
  if (!currentVersion) return
@@ -241,25 +285,43 @@ async function checkForUpdates(runningProcess) {
241
285
  if (!latestVersion) return
242
286
 
243
287
  if (compareVersions(currentVersion, latestVersion) < 0) {
244
- console.log(`Updating...\n`)
288
+ term.clearLine()
289
+ console.log(`\nUpdate available: ${currentVersion} → ${latestVersion}`)
290
+
291
+ // Remove the specific exit listener to prevent it from interfering with the update
292
+ runningProcess.removeListener('exit', exitListener)
245
293
 
246
294
  // Kill the running process
247
295
  runningProcess.kill('SIGTERM')
248
296
 
249
- // Wait for graceful shutdown
250
- await new Promise((resolve) => setTimeout(resolve, 500))
297
+ // Wait for the process to actually exit
298
+ await new Promise((resolve) => {
299
+ runningProcess.on('exit', resolve)
300
+ // Fallback timeout in case the process doesn't exit gracefully
301
+ setTimeout(() => {
302
+ if (!runningProcess.killed) {
303
+ runningProcess.kill('SIGKILL')
304
+ }
305
+ resolve()
306
+ }, 5000)
307
+ })
251
308
 
252
309
  await downloadBinary(latestVersion)
253
310
 
254
- // Restart with new binary
311
+ // Restart with new binary - this replaces the current process
255
312
  const newChild = spawn(CONFIG.binaryPath, process.argv.slice(2), {
256
313
  stdio: 'inherit',
257
314
  cwd: process.cwd(),
315
+ detached: false,
258
316
  })
259
317
 
318
+ // Set up exit handler for the new process
260
319
  newChild.on('exit', (code) => {
261
320
  process.exit(code || 0)
262
321
  })
322
+
323
+ // Don't return - keep this function running to maintain the wrapper
324
+ return new Promise(() => {}) // Never resolves, keeps wrapper alive
263
325
  }
264
326
  } catch (error) {
265
327
  // Silently ignore update check errors
@@ -267,7 +329,6 @@ async function checkForUpdates(runningProcess) {
267
329
  }
268
330
 
269
331
  async function main() {
270
- // Ensure binary exists
271
332
  await ensureBinaryExists()
272
333
 
273
334
  // Start codebuff
@@ -276,14 +337,17 @@ async function main() {
276
337
  cwd: process.cwd(),
277
338
  })
278
339
 
340
+ // Store reference to the exit listener so we can remove it during updates
341
+ const exitListener = (code) => {
342
+ process.exit(code || 0)
343
+ }
344
+
345
+ child.on('exit', exitListener)
346
+
279
347
  // Check for updates in background
280
348
  setTimeout(() => {
281
- checkForUpdates(child)
349
+ checkForUpdates(child, exitListener)
282
350
  }, 100)
283
-
284
- child.on('exit', (code) => {
285
- process.exit(code || 0)
286
- })
287
351
  }
288
352
 
289
353
  // Run the main function