codecane 1.0.420-beta.26 → 1.0.420-beta.261

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
@@ -39,7 +39,7 @@ Once running, simply chat with Codecane to say what coding task you want done.
39
39
  - Can run your tests or type checker or linter; can install packages
40
40
  - It's powerful: ask Codecane to keep working until it reaches a condition and it will.
41
41
 
42
- Our users regularly use Codecane to implement new features, write unit tests, refactor code,write scripts, or give advice.
42
+ Our users regularly use Codecane to implement new features, write unit tests, refactor code, write scripts, or give advice.
43
43
 
44
44
  ## Knowledge Files
45
45
 
@@ -69,5 +69,3 @@ If you still have errors, it's a good idea to [reinstall Node](https://nodejs.or
69
69
  ## Feedback
70
70
 
71
71
  We value your input! Please email your feedback to `founders@codebuff.com`. Thank you for using Codecane!
72
-
73
- <!-- Test comment for staging workflow -->
package/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs')
4
- const path = require('path')
5
- const os = require('os')
6
3
  const { spawn } = require('child_process')
4
+ const fs = require('fs')
7
5
  const https = require('https')
6
+ const os = require('os')
7
+ const path = require('path')
8
8
  const zlib = require('zlib')
9
+
9
10
  const tar = require('tar')
10
11
 
11
- // Hardcoded package name for codecane
12
12
  const packageName = 'codecane'
13
13
 
14
14
  function createConfig(packageName) {
@@ -22,16 +22,15 @@ function createConfig(packageName) {
22
22
  configDir,
23
23
  binaryName,
24
24
  binaryPath: path.join(configDir, binaryName),
25
- githubRepo: 'CodebuffAI/codebuff-community',
25
+ metadataPath: path.join(configDir, 'codecane-metadata.json'),
26
+ tempDownloadDir: path.join(configDir, '.download-temp-staging'),
26
27
  userAgent: `${packageName}-cli`,
27
28
  requestTimeout: 20000,
28
- isPrerelease: true, // codecane always looks for prereleases
29
29
  }
30
30
  }
31
31
 
32
32
  const CONFIG = createConfig(packageName)
33
33
 
34
- // Platform target mapping
35
34
  const PLATFORM_TARGETS = {
36
35
  'linux-x64': `${packageName}-linux-x64.tar.gz`,
37
36
  'linux-arm64': `${packageName}-linux-arm64.tar.gz`,
@@ -40,7 +39,6 @@ const PLATFORM_TARGETS = {
40
39
  'win32-x64': `${packageName}-win32-x64.tar.gz`,
41
40
  }
42
41
 
43
- // Terminal utilities
44
42
  const term = {
45
43
  clearLine: () => {
46
44
  if (process.stderr.isTTY) {
@@ -57,7 +55,6 @@ const term = {
57
55
  },
58
56
  }
59
57
 
60
- // Utility functions
61
58
  function httpGet(url, options = {}) {
62
59
  return new Promise((resolve, reject) => {
63
60
  const parsedUrl = new URL(url)
@@ -70,13 +67,6 @@ function httpGet(url, options = {}) {
70
67
  },
71
68
  }
72
69
 
73
- // Add GitHub token if available
74
- const token = process.env.GITHUB_TOKEN
75
- if (token) {
76
- console.log('Using your GITHUB_TOKEN to download the latest version.')
77
- reqOptions.headers.Authorization = `Bearer ${token}`
78
- }
79
-
80
70
  const req = https.get(reqOptions, (res) => {
81
71
  if (res.statusCode === 302 || res.statusCode === 301) {
82
72
  return httpGet(new URL(res.headers.location, url).href, options)
@@ -99,43 +89,15 @@ function httpGet(url, options = {}) {
99
89
  async function getLatestVersion() {
100
90
  try {
101
91
  const res = await httpGet(
102
- `https://github.com/${CONFIG.githubRepo}/releases.atom`
92
+ `https://registry.npmjs.org/${packageName}/latest`,
103
93
  )
104
94
 
105
95
  if (res.statusCode !== 200) return null
106
96
 
107
97
  const body = await streamToString(res)
98
+ const packageData = JSON.parse(body)
108
99
 
109
- // Parse the Atom XML to extract releases
110
- const tagMatches = body.match(
111
- /<id>tag:github\.com,2008:Repository\/\d+\/([^<]+)<\/id>/g
112
- )
113
-
114
- if (!tagMatches) return null
115
-
116
- // Extract all version tags
117
- const versions = tagMatches
118
- .map((match) => {
119
- const tagMatch = match.match(
120
- /<id>tag:github\.com,2008:Repository\/\d+\/([^<]+)<\/id>/
121
- )
122
- return tagMatch ? tagMatch[1].replace(/^v/, '') : null
123
- })
124
- .filter(Boolean)
125
-
126
- if (versions.length === 0) return null
127
-
128
- // Filter versions based on whether we want prereleases or stable releases
129
- const filteredVersions = versions.filter((version) => {
130
- const isPrerelease = version.includes('-')
131
- return CONFIG.isPrerelease === isPrerelease
132
- })
133
-
134
- if (filteredVersions.length === 0) return null
135
-
136
- // Sort and return the latest version
137
- filteredVersions.sort(compareVersions)
138
- return filteredVersions[filteredVersions.length - 1]
100
+ return packageData.version || null
139
101
  } catch (error) {
140
102
  return null
141
103
  }
@@ -151,58 +113,75 @@ function streamToString(stream) {
151
113
  }
152
114
 
153
115
  function getCurrentVersion() {
154
- if (!fs.existsSync(CONFIG.binaryPath)) return null
155
-
156
116
  try {
157
- return new Promise((resolve, reject) => {
158
- const child = spawn(CONFIG.binaryPath, ['--version'], {
159
- cwd: os.homedir(),
160
- stdio: 'pipe',
161
- })
117
+ if (!fs.existsSync(CONFIG.metadataPath)) {
118
+ return null
119
+ }
120
+ const metadata = JSON.parse(fs.readFileSync(CONFIG.metadataPath, 'utf8'))
121
+ // Also verify the binary still exists
122
+ if (!fs.existsSync(CONFIG.binaryPath)) {
123
+ return null
124
+ }
125
+ return metadata.version || null
126
+ } catch (error) {
127
+ return null
128
+ }
129
+ }
162
130
 
163
- let output = ''
164
- let errorOutput = ''
131
+ function runSmokeTest(binaryPath) {
132
+ return new Promise((resolve) => {
133
+ if (!fs.existsSync(binaryPath)) {
134
+ resolve(false)
135
+ return
136
+ }
165
137
 
166
- child.stdout.on('data', (data) => {
167
- output += data.toString()
168
- })
138
+ const child = spawn(binaryPath, ['--version'], {
139
+ cwd: os.homedir(),
140
+ stdio: 'pipe',
141
+ })
169
142
 
170
- child.stderr.on('data', (data) => {
171
- errorOutput += data.toString()
172
- })
143
+ let output = ''
173
144
 
174
- const timeout = setTimeout(() => {
175
- child.kill('SIGTERM')
176
- setTimeout(() => {
177
- if (!child.killed) {
178
- child.kill('SIGKILL')
179
- }
180
- }, 1000)
181
- resolve('error')
182
- }, 1000)
145
+ child.stdout.on('data', (data) => {
146
+ output += data.toString()
147
+ })
183
148
 
184
- child.on('exit', (code) => {
185
- clearTimeout(timeout)
186
- if (code === 0) {
187
- resolve(output.trim())
188
- } else {
189
- resolve('error')
149
+ const timeout = setTimeout(() => {
150
+ child.kill('SIGTERM')
151
+ setTimeout(() => {
152
+ if (!child.killed) {
153
+ child.kill('SIGKILL')
190
154
  }
191
- })
155
+ }, 1000)
156
+ resolve(false)
157
+ }, 5000)
158
+
159
+ child.on('exit', (code) => {
160
+ clearTimeout(timeout)
161
+ // Check that it exits successfully and outputs something that looks like a version
162
+ if (code === 0 && output.trim().match(/^\d+(\.\d+)*(-beta\.\d+)?$/)) {
163
+ resolve(true)
164
+ } else {
165
+ resolve(false)
166
+ }
167
+ })
192
168
 
193
- child.on('error', () => {
194
- clearTimeout(timeout)
195
- resolve('error')
196
- })
169
+ child.on('error', () => {
170
+ clearTimeout(timeout)
171
+ resolve(false)
197
172
  })
198
- } catch (error) {
199
- return 'error'
200
- }
173
+ })
201
174
  }
202
175
 
203
176
  function compareVersions(v1, v2) {
204
177
  if (!v1 || !v2) return 0
205
178
 
179
+ // Always update if the current version is not a valid semver
180
+ // e.g. 1.0.420-beta.1
181
+ if (!v1.match(/^\d+(\.\d+)*$/)) {
182
+ return -1
183
+ }
184
+
206
185
  const parseVersion = (version) => {
207
186
  const parts = version.split('-')
208
187
  const mainParts = parts[0].split('.').map(Number)
@@ -213,7 +192,6 @@ function compareVersions(v1, v2) {
213
192
  const p1 = parseVersion(v1)
214
193
  const p2 = parseVersion(v2)
215
194
 
216
- // Compare main version parts
217
195
  for (let i = 0; i < Math.max(p1.main.length, p2.main.length); i++) {
218
196
  const n1 = p1.main[i] || 0
219
197
  const n2 = p2.main[i] || 0
@@ -222,15 +200,13 @@ function compareVersions(v1, v2) {
222
200
  if (n1 > n2) return 1
223
201
  }
224
202
 
225
- // If main versions are equal, compare prerelease parts
226
203
  if (p1.prerelease.length === 0 && p2.prerelease.length === 0) {
227
- return 0 // No prerelease, versions are equal
204
+ return 0
228
205
  } else if (p1.prerelease.length === 0) {
229
- return 1 // v1 is a release, v2 is prerelease, so v1 > v2
206
+ return 1
230
207
  } else if (p2.prerelease.length === 0) {
231
- return -1 // v2 is a release, v1 is prerelease, so v1 < v2
208
+ return -1
232
209
  } else {
233
- // Both have prerelease parts, compare them
234
210
  for (
235
211
  let i = 0;
236
212
  i < Math.max(p1.prerelease.length, p2.prerelease.length);
@@ -239,7 +215,6 @@ function compareVersions(v1, v2) {
239
215
  const pr1 = p1.prerelease[i] || ''
240
216
  const pr2 = p2.prerelease[i] || ''
241
217
 
242
- // Handle numeric vs. string parts
243
218
  const isNum1 = !isNaN(parseInt(pr1))
244
219
  const isNum2 = !isNaN(parseInt(pr2))
245
220
 
@@ -249,16 +224,16 @@ function compareVersions(v1, v2) {
249
224
  if (num1 < num2) return -1
250
225
  if (num1 > num2) return 1
251
226
  } else if (isNum1 && !isNum2) {
252
- return 1 // Numeric prerelease is generally higher than alpha/beta
227
+ return 1
253
228
  } else if (!isNum1 && isNum2) {
254
229
  return -1
255
- } else {
256
- // Lexicographical comparison for string parts
257
- if (pr1 < pr2) return -1
258
- if (pr1 > pr2) return 1
230
+ } else if (pr1 < pr2) {
231
+ return -1
232
+ } else if (pr1 > pr2) {
233
+ return 1
259
234
  }
260
235
  }
261
- return 0 // Prerelease parts are equal
236
+ return 0
262
237
  }
263
238
  }
264
239
 
@@ -284,20 +259,25 @@ async function downloadBinary(version) {
284
259
  throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`)
285
260
  }
286
261
 
287
- const downloadUrl = `https://github.com/${CONFIG.githubRepo}/releases/download/v${version}/${fileName}`
262
+ const downloadUrl = `${
263
+ process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
264
+ }/api/releases/download/${version}/${fileName}`
288
265
 
289
266
  // Ensure config directory exists
290
267
  fs.mkdirSync(CONFIG.configDir, { recursive: true })
291
268
 
292
- if (fs.existsSync(CONFIG.binaryPath)) {
293
- fs.unlinkSync(CONFIG.binaryPath)
269
+ // Clean up any previous temp download directory
270
+ if (fs.existsSync(CONFIG.tempDownloadDir)) {
271
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
294
272
  }
273
+ fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
295
274
 
296
275
  term.write('Downloading...')
297
276
 
298
277
  const res = await httpGet(downloadUrl)
299
278
 
300
279
  if (res.statusCode !== 200) {
280
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
301
281
  throw new Error(`Download failed: HTTP ${res.statusCode}`)
302
282
  }
303
283
 
@@ -314,8 +294,8 @@ async function downloadBinary(version) {
314
294
  const pct = Math.round((downloadedSize / totalSize) * 100)
315
295
  term.write(
316
296
  `Downloading... ${createProgressBar(pct)} ${pct}% of ${formatBytes(
317
- totalSize
318
- )}`
297
+ totalSize,
298
+ )}`,
319
299
  )
320
300
  } else {
321
301
  term.write(`Downloading... ${formatBytes(downloadedSize)}`)
@@ -323,32 +303,71 @@ async function downloadBinary(version) {
323
303
  }
324
304
  })
325
305
 
306
+ // Extract to temp directory
326
307
  await new Promise((resolve, reject) => {
327
308
  res
328
309
  .pipe(zlib.createGunzip())
329
- .pipe(tar.x({ cwd: CONFIG.configDir }))
310
+ .pipe(tar.x({ cwd: CONFIG.tempDownloadDir }))
330
311
  .on('finish', resolve)
331
312
  .on('error', reject)
332
313
  })
333
314
 
334
- try {
335
- // Find the extracted binary - it should be named "codebuff" or "codebuff.exe"
336
- const files = fs.readdirSync(CONFIG.configDir)
337
- const extractedPath = path.join(CONFIG.configDir, CONFIG.binaryName)
315
+ const tempBinaryPath = path.join(CONFIG.tempDownloadDir, CONFIG.binaryName)
316
+
317
+ // Verify the binary was extracted
318
+ if (!fs.existsSync(tempBinaryPath)) {
319
+ const files = fs.readdirSync(CONFIG.tempDownloadDir)
320
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
321
+ throw new Error(
322
+ `Binary not found after extraction. Expected: ${CONFIG.binaryName}, Available files: ${files.join(', ')}`,
323
+ )
324
+ }
338
325
 
339
- if (fs.existsSync(extractedPath)) {
340
- if (process.platform !== 'win32') {
341
- fs.chmodSync(extractedPath, 0o755)
326
+ // Set executable permissions
327
+ if (process.platform !== 'win32') {
328
+ fs.chmodSync(tempBinaryPath, 0o755)
329
+ }
330
+
331
+ // Run smoke test on the downloaded binary
332
+ term.write('Verifying download...')
333
+ const smokeTestPassed = await runSmokeTest(tempBinaryPath)
334
+
335
+ if (!smokeTestPassed) {
336
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
337
+ throw new Error('Downloaded binary failed smoke test (--version check)')
338
+ }
339
+
340
+ // Smoke test passed - move binary to final location
341
+ try {
342
+ if (fs.existsSync(CONFIG.binaryPath)) {
343
+ try {
344
+ fs.unlinkSync(CONFIG.binaryPath)
345
+ } catch (err) {
346
+ // Fallback: try renaming the locked/undeletable binary (Windows)
347
+ const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
348
+ try {
349
+ fs.renameSync(CONFIG.binaryPath, backupPath)
350
+ } catch (renameErr) {
351
+ throw new Error(
352
+ `Failed to replace existing binary. ` +
353
+ `unlink error: ${err.code || err.message}, ` +
354
+ `rename error: ${renameErr.code || renameErr.message}`,
355
+ )
356
+ }
342
357
  }
343
- } else {
344
- throw new Error(
345
- `Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`
346
- )
347
358
  }
348
- } catch (error) {
349
- term.clearLine()
350
- console.error(`Extraction failed: ${error.message}`)
351
- process.exit(1)
359
+ fs.renameSync(tempBinaryPath, CONFIG.binaryPath)
360
+
361
+ // Save version metadata for fast version checking
362
+ fs.writeFileSync(
363
+ CONFIG.metadataPath,
364
+ JSON.stringify({ version }, null, 2),
365
+ )
366
+ } finally {
367
+ // Clean up temp directory even if rename fails
368
+ if (fs.existsSync(CONFIG.tempDownloadDir)) {
369
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
370
+ }
352
371
  }
353
372
 
354
373
  term.clearLine()
@@ -356,50 +375,47 @@ async function downloadBinary(version) {
356
375
  }
357
376
 
358
377
  async function ensureBinaryExists() {
359
- if (!fs.existsSync(CONFIG.binaryPath)) {
360
- const version = await getLatestVersion()
361
- if (!version) {
362
- console.error('❌ Failed to determine latest version')
363
- console.error('Please check your internet connection and try again')
364
- process.exit(1)
365
- }
378
+ const currentVersion = getCurrentVersion()
379
+ if (currentVersion !== null) {
380
+ return
381
+ }
366
382
 
367
- try {
368
- await downloadBinary(version)
369
- } catch (error) {
370
- term.clearLine()
371
- console.error('❌ Failed to download codebuff:', error.message)
372
- console.error('Please check your internet connection and try again')
373
- process.exit(1)
374
- }
383
+ const version = await getLatestVersion()
384
+ if (!version) {
385
+ console.error('❌ Failed to determine latest version')
386
+ console.error('Please check your internet connection and try again')
387
+ process.exit(1)
388
+ }
389
+
390
+ try {
391
+ await downloadBinary(version)
392
+ } catch (error) {
393
+ term.clearLine()
394
+ console.error('❌ Failed to download codecane:', error.message)
395
+ console.error('Please check your internet connection and try again')
396
+ process.exit(1)
375
397
  }
376
398
  }
377
399
 
378
400
  async function checkForUpdates(runningProcess, exitListener) {
379
401
  try {
380
- const currentVersion = await getCurrentVersion()
381
- if (!currentVersion) return
402
+ const currentVersion = getCurrentVersion()
382
403
 
383
404
  const latestVersion = await getLatestVersion()
384
405
  if (!latestVersion) return
385
406
 
386
407
  if (
387
- // Download new version if current binary errors.
388
- currentVersion === 'error' ||
408
+ // Download new version if current version is unknown or outdated.
409
+ currentVersion === null ||
389
410
  compareVersions(currentVersion, latestVersion) < 0
390
411
  ) {
391
412
  term.clearLine()
392
413
 
393
- // Remove the specific exit listener to prevent it from interfering with the update
394
414
  runningProcess.removeListener('exit', exitListener)
395
-
396
- // Kill the running process
397
415
  runningProcess.kill('SIGTERM')
398
416
 
399
- // Wait for the process to actually exit
400
417
  await new Promise((resolve) => {
401
418
  runningProcess.on('exit', resolve)
402
- // Fallback timeout in case the process doesn't exit gracefully
403
419
  setTimeout(() => {
404
420
  if (!runningProcess.killed) {
405
421
  runningProcess.kill('SIGKILL')
@@ -412,64 +428,48 @@ async function checkForUpdates(runningProcess, exitListener) {
412
428
 
413
429
  await downloadBinary(latestVersion)
414
430
 
415
- // Restart with new binary - this replaces the current process
416
- const newChild = spawn(
417
- CONFIG.binaryPath,
418
- [packageName, ...process.argv.slice(2)],
419
- {
420
- stdio: 'inherit',
421
- detached: false,
422
- }
423
- )
431
+ const newChild = spawn(CONFIG.binaryPath, process.argv.slice(2), {
432
+ stdio: 'inherit',
433
+ detached: false,
434
+ })
424
435
 
425
- // Set up exit handler for the new process
426
436
  newChild.on('exit', (code) => {
427
437
  process.exit(code || 0)
428
438
  })
429
439
 
430
- // Don't return - keep this function running to maintain the wrapper
431
- return new Promise(() => {}) // Never resolves, keeps wrapper alive
440
+ return new Promise(() => {})
432
441
  }
433
442
  } catch (error) {
434
- // Silently ignore update check errors
443
+ // Ignore update failures
435
444
  }
436
445
  }
437
446
 
438
447
  async function main() {
439
- // Bold, bright warning for staging environment
440
448
  console.log('\x1b[1m\x1b[91m' + '='.repeat(60) + '\x1b[0m')
441
449
  console.log('\x1b[1m\x1b[93m❄️ CODECANE STAGING ENVIRONMENT ❄️\x1b[0m')
442
450
  console.log(
443
- '\x1b[1m\x1b[91mFOR TESTING PURPOSES ONLY - NOT FOR PRODUCTION USE\x1b[0m'
451
+ '\x1b[1m\x1b[91mFOR TESTING PURPOSES ONLY - NOT FOR PRODUCTION USE\x1b[0m',
444
452
  )
445
453
  console.log('\x1b[1m\x1b[91m' + '='.repeat(60) + '\x1b[0m')
446
454
  console.log('')
447
455
 
448
456
  await ensureBinaryExists()
449
457
 
450
- // Start the binary with codecane argument
451
- const child = spawn(
452
- CONFIG.binaryPath,
453
- [packageName, ...process.argv.slice(2)],
454
- {
455
- stdio: 'inherit',
456
- }
457
- )
458
+ const child = spawn(CONFIG.binaryPath, process.argv.slice(2), {
459
+ stdio: 'inherit',
460
+ })
458
461
 
459
- // Store reference to the exit listener so we can remove it during updates
460
462
  const exitListener = (code) => {
461
463
  process.exit(code || 0)
462
464
  }
463
465
 
464
466
  child.on('exit', exitListener)
465
467
 
466
- // Check for updates in background
467
468
  setTimeout(() => {
468
469
  checkForUpdates(child, exitListener)
469
470
  }, 100)
470
471
  }
471
472
 
472
- // Run the main function
473
473
  main().catch((error) => {
474
474
  console.error('❌ Unexpected error:', error.message)
475
475
  process.exit(1)
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "codecane",
3
- "version": "1.0.420-beta.26",
4
- "description": "AI coding agent (staging)",
3
+ "version": "1.0.420-beta.261",
4
+ "description": "AI coding agent CLI (staging)",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "codecane": "index.js"
8
8
  },
9
9
  "scripts": {
10
+ "postinstall": "node postinstall.js",
10
11
  "preuninstall": "node -e \"const fs = require('fs'); const path = require('path'); const os = require('os'); const binaryPath = path.join(os.homedir(), '.config', 'manicode', process.platform === 'win32' ? 'codecane.exe' : 'codecane'); try { fs.unlinkSync(binaryPath) } catch (e) { /* ignore if file doesn't exist */ }\""
11
12
  },
12
13
  "files": [
13
14
  "index.js",
15
+ "postinstall.js",
14
16
  "README.md"
15
17
  ],
16
18
  "os": [
@@ -30,7 +32,7 @@
30
32
  },
31
33
  "repository": {
32
34
  "type": "git",
33
- "url": "https://github.com/CodebuffAI/codebuff-community.git"
35
+ "url": "https://github.com/CodebuffAI/codebuff.git"
34
36
  },
35
37
  "homepage": "https://codebuff.com",
36
38
  "publishConfig": {
package/postinstall.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ // Clean up old binary
8
+ const binaryPath = path.join(
9
+ os.homedir(),
10
+ '.config',
11
+ 'manicode',
12
+ process.platform === 'win32' ? 'codecane.exe' : 'codecane'
13
+ );
14
+
15
+ try {
16
+ fs.unlinkSync(binaryPath);
17
+ } catch (e) {
18
+ /* ignore if file doesn't exist */
19
+ }
20
+
21
+ // Print welcome message
22
+ console.log('\n');
23
+ console.log('🧪 Welcome to Codecane (Staging)!');
24
+ console.log('\n');
25
+ console.log('⚠️ This is a staging/beta release for testing purposes.');
26
+ console.log('\n');
27
+ console.log('To get started:');
28
+ console.log(' 1. cd to your project directory');
29
+ console.log(' 2. Run: codecane');
30
+ console.log('\n');
31
+ console.log('Example:');
32
+ console.log(' $ cd ~/my-project');
33
+ console.log(' $ codecane');
34
+ console.log('\n');
35
+ console.log('For more information, visit: https://codebuff.com/docs');
36
+ console.log('\n');