codecane 1.0.412 → 1.0.420-beta.1

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.
Files changed (3) hide show
  1. package/README.md +20 -16
  2. package/index.js +215 -85
  3. package/package.json +5 -2
package/README.md CHANGED
@@ -1,49 +1,51 @@
1
- # The most powerful coding agent
1
+ # 🚀 Codecane - The most powerful coding agent (STAGING)
2
2
 
3
- Codebuff is a CLI tool that writes code for you.
3
+ **⚠️ This is a staging/beta release for testing purposes.**
4
4
 
5
- 1. Run `codebuff` from your project directory
5
+ Codecane is a CLI tool that writes code for you.
6
+
7
+ 1. Run `codecane` from your project directory
6
8
  2. Tell it what to do
7
9
  3. It will read and write to files and run commands to produce the code you want
8
10
 
9
- Note: Codebuff will run commands in your terminal as it deems necessary to fulfill your request.
11
+ Note: Codecane will run commands in your terminal as it deems necessary to fulfill your request.
10
12
 
11
13
  ## Installation
12
14
 
13
- To install Codebuff, run:
15
+ To install Codecane (staging), run:
14
16
 
15
17
  ```bash
16
- npm install -g codebuff
18
+ npm install -g codecane@beta
17
19
  ```
18
20
 
19
21
  (Use `sudo` if you get a permission error.)
20
22
 
21
23
  ## Usage
22
24
 
23
- After installation, you can start Codebuff by running:
25
+ After installation, you can start Codecane by running:
24
26
 
25
27
  ```bash
26
- codebuff [project-directory]
28
+ codecane [project-directory]
27
29
  ```
28
30
 
29
- If no project directory is specified, Codebuff will use the current directory.
31
+ If no project directory is specified, Codecane will use the current directory.
30
32
 
31
- Once running, simply chat with Codebuff to say what coding task you want done.
33
+ Once running, simply chat with Codecane to say what coding task you want done.
32
34
 
33
35
  ## Features
34
36
 
35
37
  - Understands your whole codebase
36
38
  - Creates and edits multiple files based on your request
37
39
  - Can run your tests or type checker or linter; can install packages
38
- - It's powerful: ask Codebuff to keep working until it reaches a condition and it will.
40
+ - It's powerful: ask Codecane to keep working until it reaches a condition and it will.
39
41
 
40
- Our users regularly use Codebuff 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.
41
43
 
42
44
  ## Knowledge Files
43
45
 
44
46
  To unlock the full benefits of modern LLMs, we recommend storing knowledge alongside your code. Add a `knowledge.md` file anywhere in your project to provide helpful context, guidance, and tips for the LLM as it performs tasks for you.
45
47
 
46
- Codebuff can fluently read and write files, so it will add knowledge as it goes. You don't need to write knowledge manually!
48
+ Codecane can fluently read and write files, so it will add knowledge as it goes. You don't need to write knowledge manually!
47
49
 
48
50
  Some have said every change should be paired with a unit test. In 2024, every change should come with a knowledge update!
49
51
 
@@ -52,18 +54,20 @@ Some have said every change should be paired with a unit test. In 2024, every ch
52
54
  1. Type '/help' or just '/' to see available commands.
53
55
  2. Create a `knowledge.md` file and collect specific points of advice. The assistant will use this knowledge to improve its responses.
54
56
  3. Type `undo` or `redo` to revert or reapply file changes from the conversation.
55
- 4. Press `Esc` or `Ctrl+C` while Codebuff is generating a response to stop it.
57
+ 4. Press `Esc` or `Ctrl+C` while Codecane is generating a response to stop it.
56
58
 
57
59
  ## Troubleshooting
58
60
 
59
61
  If you are getting permission errors during installation, try using sudo:
60
62
 
61
63
  ```
62
- sudo npm install -g codebuff
64
+ sudo npm install -g codecane@beta
63
65
  ```
64
66
 
65
67
  If you still have errors, it's a good idea to [reinstall Node](https://nodejs.org/en/download).
66
68
 
67
69
  ## Feedback
68
70
 
69
- We value your input! Please email your feedback to `founders@codebuff.com`. Thank you for using Codebuff!
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
@@ -3,31 +3,40 @@
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
5
  const os = require('os')
6
- const { spawn, execSync } = require('child_process')
6
+ const { spawn } = require('child_process')
7
7
  const https = require('https')
8
8
  const zlib = require('zlib')
9
9
  const tar = require('tar')
10
10
 
11
- // Configuration
12
- const CONFIG = {
13
- homeDir: os.homedir(),
14
- configDir: path.join(os.homedir(), '.config', 'manicode'),
15
- binaryName: process.platform === 'win32' ? 'codebuff.exe' : 'codebuff',
16
- githubRepo: 'CodebuffAI/codebuff-community',
17
- userAgent: 'codebuff-cli',
18
- requestTimeout: 10000,
19
- updateCheckTimeout: 5000,
11
+ // Hardcoded package name for codecane
12
+ const packageName = 'codecane'
13
+
14
+ function createConfig(packageName) {
15
+ const homeDir = os.homedir()
16
+ const configDir = path.join(homeDir, '.config', 'manicode')
17
+ const binaryName = process.platform === 'win32' ? `${packageName}.exe` : packageName
18
+
19
+ return {
20
+ homeDir,
21
+ configDir,
22
+ binaryName,
23
+ binaryPath: path.join(configDir, binaryName),
24
+ githubRepo: 'CodebuffAI/codebuff-community',
25
+ userAgent: `${packageName}-cli`,
26
+ requestTimeout: 20000,
27
+ isPrerelease: true, // codecane always looks for prereleases
28
+ }
20
29
  }
21
30
 
22
- CONFIG.binaryPath = path.join(CONFIG.configDir, CONFIG.binaryName)
31
+ const CONFIG = createConfig(packageName)
23
32
 
24
33
  // Platform target mapping
25
34
  const PLATFORM_TARGETS = {
26
- 'linux-x64': 'codebuff-linux-x64.tar.gz',
27
- 'linux-arm64': 'codebuff-linux-arm64.tar.gz',
28
- 'darwin-x64': 'codebuff-darwin-x64.tar.gz',
29
- 'darwin-arm64': 'codebuff-darwin-arm64.tar.gz',
30
- 'win32-x64': 'codebuff-win32-x64.tar.gz',
35
+ 'linux-x64': `${packageName}-linux-x64.tar.gz`,
36
+ 'linux-arm64': `${packageName}-linux-arm64.tar.gz`,
37
+ 'darwin-x64': `${packageName}-darwin-x64.tar.gz`,
38
+ 'darwin-arm64': `${packageName}-darwin-arm64.tar.gz`,
39
+ 'win32-x64': `${packageName}-win32-x64.tar.gz`,
31
40
  }
32
41
 
33
42
  // Terminal utilities
@@ -60,9 +69,16 @@ function httpGet(url, options = {}) {
60
69
  },
61
70
  }
62
71
 
72
+ // Add GitHub token if available
73
+ const token = process.env.GITHUB_TOKEN
74
+ if (token) {
75
+ console.log('Using your GITHUB_TOKEN to download the latest version.')
76
+ reqOptions.headers.Authorization = `Bearer ${token}`
77
+ }
78
+
63
79
  const req = https.get(reqOptions, (res) => {
64
80
  if (res.statusCode === 302 || res.statusCode === 301) {
65
- return httpGet(res.headers.location, options)
81
+ return httpGet(new URL(res.headers.location, url).href, options)
66
82
  .then(resolve)
67
83
  .catch(reject)
68
84
  }
@@ -74,7 +90,7 @@ function httpGet(url, options = {}) {
74
90
  const timeout = options.timeout || CONFIG.requestTimeout
75
91
  req.setTimeout(timeout, () => {
76
92
  req.destroy()
77
- reject(new Error('Request timeout'))
93
+ reject(new Error('Request timeout.'))
78
94
  })
79
95
  })
80
96
  }
@@ -82,51 +98,167 @@ function httpGet(url, options = {}) {
82
98
  async function getLatestVersion() {
83
99
  try {
84
100
  const res = await httpGet(
85
- `https://api.github.com/repos/${CONFIG.githubRepo}/releases/latest`
101
+ `https://github.com/${CONFIG.githubRepo}/releases.atom`
86
102
  )
87
103
 
88
- let data = ''
89
- for await (const chunk of res) {
90
- data += chunk
91
- }
104
+ if (res.statusCode !== 200) return null
105
+
106
+ const body = await streamToString(res)
107
+
108
+ // Parse the Atom XML to extract releases
109
+ const tagMatches = body.match(
110
+ /<id>tag:github\.com,2008:Repository\/\d+\/([^<]+)<\/id>/g
111
+ )
112
+
113
+ if (!tagMatches) return null
92
114
 
93
- const release = JSON.parse(data)
94
- return release.tag_name?.replace(/^v/, '') || null
115
+ // Extract all version tags
116
+ const versions = tagMatches
117
+ .map((match) => {
118
+ const tagMatch = match.match(
119
+ /<id>tag:github\.com,2008:Repository\/\d+\/([^<]+)<\/id>/
120
+ )
121
+ return tagMatch ? tagMatch[1].replace(/^v/, '') : null
122
+ })
123
+ .filter(Boolean)
124
+
125
+ if (versions.length === 0) return null
126
+
127
+ // Filter versions based on whether we want prereleases or stable releases
128
+ const filteredVersions = versions.filter((version) => {
129
+ const isPrerelease = version.includes('-')
130
+ return CONFIG.isPrerelease === isPrerelease
131
+ })
132
+
133
+ if (filteredVersions.length === 0) return null
134
+
135
+ // Sort and return the latest version
136
+ filteredVersions.sort(compareVersions)
137
+ return filteredVersions[filteredVersions.length - 1]
95
138
  } catch (error) {
96
139
  return null
97
140
  }
98
141
  }
99
142
 
143
+ function streamToString(stream) {
144
+ return new Promise((resolve, reject) => {
145
+ let data = ''
146
+ stream.on('data', (chunk) => (data += chunk))
147
+ stream.on('end', () => resolve(data))
148
+ stream.on('error', reject)
149
+ })
150
+ }
151
+
100
152
  function getCurrentVersion() {
101
153
  if (!fs.existsSync(CONFIG.binaryPath)) return null
102
154
 
103
155
  try {
104
- const result = execSync(`"${CONFIG.binaryPath}" --version`, {
105
- encoding: 'utf-8',
106
- stdio: 'pipe',
107
- timeout: 1000,
156
+ return new Promise((resolve, reject) => {
157
+ const child = spawn(CONFIG.binaryPath, ['--version'], {
158
+ cwd: os.homedir(),
159
+ stdio: 'pipe',
160
+ })
161
+
162
+ let output = ''
163
+ let errorOutput = ''
164
+
165
+ child.stdout.on('data', (data) => {
166
+ output += data.toString()
167
+ })
168
+
169
+ child.stderr.on('data', (data) => {
170
+ errorOutput += data.toString()
171
+ })
172
+
173
+ const timeout = setTimeout(() => {
174
+ child.kill('SIGTERM')
175
+ setTimeout(() => {
176
+ if (!child.killed) {
177
+ child.kill('SIGKILL')
178
+ }
179
+ }, 1000)
180
+ resolve('error')
181
+ }, 1000)
182
+
183
+ child.on('exit', (code) => {
184
+ clearTimeout(timeout)
185
+ if (code === 0) {
186
+ resolve(output.trim())
187
+ } else {
188
+ resolve('error')
189
+ }
190
+ })
191
+
192
+ child.on('error', () => {
193
+ clearTimeout(timeout)
194
+ resolve('error')
195
+ })
108
196
  })
109
- return result.trim()
110
197
  } catch (error) {
111
- return null
198
+ return 'error'
112
199
  }
113
200
  }
114
201
 
115
202
  function compareVersions(v1, v2) {
116
203
  if (!v1 || !v2) return 0
117
204
 
118
- const parts1 = v1.split('.').map(Number)
119
- const parts2 = v2.split('.').map(Number)
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)
120
214
 
121
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
122
- const p1 = parts1[i] || 0
123
- const p2 = parts2[i] || 0
215
+ // Compare main version parts
216
+ for (let i = 0; i < Math.max(p1.main.length, p2.main.length); i++) {
217
+ const n1 = p1.main[i] || 0
218
+ const n2 = p2.main[i] || 0
124
219
 
125
- if (p1 < p2) return -1
126
- if (p1 > p2) return 1
220
+ if (n1 < n2) return -1
221
+ if (n1 > n2) return 1
127
222
  }
128
223
 
129
- return 0
224
+ // If main versions are equal, compare prerelease parts
225
+ if (p1.prerelease.length === 0 && p2.prerelease.length === 0) {
226
+ return 0 // No prerelease, versions are equal
227
+ } else if (p1.prerelease.length === 0) {
228
+ return 1 // v1 is a release, v2 is prerelease, so v1 > v2
229
+ } else if (p2.prerelease.length === 0) {
230
+ return -1 // v2 is a release, v1 is prerelease, so v1 < v2
231
+ } else {
232
+ // Both have prerelease parts, compare them
233
+ for (
234
+ let i = 0;
235
+ i < Math.max(p1.prerelease.length, p2.prerelease.length);
236
+ i++
237
+ ) {
238
+ const pr1 = p1.prerelease[i] || ''
239
+ const pr2 = p2.prerelease[i] || ''
240
+
241
+ // Handle numeric vs. string parts
242
+ const isNum1 = !isNaN(parseInt(pr1))
243
+ const isNum2 = !isNaN(parseInt(pr2))
244
+
245
+ if (isNum1 && isNum2) {
246
+ const num1 = parseInt(pr1)
247
+ const num2 = parseInt(pr2)
248
+ if (num1 < num2) return -1
249
+ if (num1 > num2) return 1
250
+ } else if (isNum1 && !isNum2) {
251
+ return 1 // Numeric prerelease is generally higher than alpha/beta
252
+ } else if (!isNum1 && isNum2) {
253
+ return -1
254
+ } else {
255
+ // Lexicographical comparison for string parts
256
+ if (pr1 < pr2) return -1
257
+ if (pr1 > pr2) return 1
258
+ }
259
+ }
260
+ return 0 // Prerelease parts are equal
261
+ }
130
262
  }
131
263
 
132
264
  function formatBytes(bytes) {
@@ -137,10 +269,6 @@ function formatBytes(bytes) {
137
269
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
138
270
  }
139
271
 
140
- function formatSpeed(bytesPerSecond) {
141
- return formatBytes(bytesPerSecond) + '/s'
142
- }
143
-
144
272
  function createProgressBar(percentage, width = 30) {
145
273
  const filled = Math.round((width * percentage) / 100)
146
274
  const empty = width - filled
@@ -160,7 +288,11 @@ async function downloadBinary(version) {
160
288
  // Ensure config directory exists
161
289
  fs.mkdirSync(CONFIG.configDir, { recursive: true })
162
290
 
163
- term.write(`Downloading codebuff v${version}...`)
291
+ if (fs.existsSync(CONFIG.binaryPath)) {
292
+ fs.unlinkSync(CONFIG.binaryPath)
293
+ }
294
+
295
+ term.write('Downloading...')
164
296
 
165
297
  const res = await httpGet(downloadUrl)
166
298
 
@@ -172,46 +304,33 @@ async function downloadBinary(version) {
172
304
  let downloadedSize = 0
173
305
  let lastProgressTime = Date.now()
174
306
 
175
- const chunks = []
176
-
177
- for await (const chunk of res) {
178
- chunks.push(chunk)
307
+ res.on('data', (chunk) => {
179
308
  downloadedSize += chunk.length
180
-
181
309
  const now = Date.now()
182
310
  if (now - lastProgressTime >= 100 || downloadedSize === totalSize) {
183
311
  lastProgressTime = now
184
-
185
312
  if (totalSize > 0) {
186
- const percentage = Math.round((downloadedSize / totalSize) * 100)
187
- const progressBar = createProgressBar(percentage)
188
-
313
+ const pct = Math.round((downloadedSize / totalSize) * 100)
189
314
  term.write(
190
- `Downloading... ${progressBar} ${percentage}% of ${formatBytes(totalSize)}`
315
+ `Downloading... ${createProgressBar(pct)} ${pct}% of ${formatBytes(
316
+ totalSize
317
+ )}`
191
318
  )
192
319
  } else {
193
320
  term.write(`Downloading... ${formatBytes(downloadedSize)}`)
194
321
  }
195
322
  }
196
- }
197
- term.clearLine()
198
- console.log('Download complete!')
199
-
200
- term.write('Extracting...')
323
+ })
201
324
 
202
- const buffer = Buffer.concat(chunks)
325
+ await new Promise((resolve, reject) => {
326
+ res
327
+ .pipe(zlib.createGunzip())
328
+ .pipe(tar.x({ cwd: CONFIG.configDir }))
329
+ .on('finish', resolve)
330
+ .on('error', reject)
331
+ })
203
332
 
204
333
  try {
205
- // Unix tar.gz extraction for all platforms
206
- await new Promise((resolve, reject) => {
207
- const gunzip = zlib.createGunzip()
208
- const extract = tar.extract({ cwd: CONFIG.configDir })
209
-
210
- gunzip.pipe(extract).on('finish', resolve).on('error', reject)
211
-
212
- gunzip.end(buffer)
213
- })
214
-
215
334
  // Find the extracted binary - it should be named "codebuff" or "codebuff.exe"
216
335
  const files = fs.readdirSync(CONFIG.configDir)
217
336
  const extractedPath = path.join(CONFIG.configDir, CONFIG.binaryName)
@@ -225,13 +344,14 @@ async function downloadBinary(version) {
225
344
  `Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`
226
345
  )
227
346
  }
228
-
229
- term.write('Starting Codebuff...')
230
347
  } catch (error) {
231
348
  term.clearLine()
232
349
  console.error(`Extraction failed: ${error.message}`)
233
350
  process.exit(1)
234
351
  }
352
+
353
+ term.clearLine()
354
+ console.log('Download complete! Starting Codecane...')
235
355
  }
236
356
 
237
357
  async function ensureBinaryExists() {
@@ -248,7 +368,7 @@ async function ensureBinaryExists() {
248
368
  } catch (error) {
249
369
  term.clearLine()
250
370
  console.error('❌ Failed to download codebuff:', error.message)
251
- console.error('Please try again later.')
371
+ console.error('Please check your internet connection and try again')
252
372
  process.exit(1)
253
373
  }
254
374
  }
@@ -256,13 +376,17 @@ async function ensureBinaryExists() {
256
376
 
257
377
  async function checkForUpdates(runningProcess, exitListener) {
258
378
  try {
259
- const currentVersion = getCurrentVersion()
379
+ const currentVersion = await getCurrentVersion()
260
380
  if (!currentVersion) return
261
381
 
262
382
  const latestVersion = await getLatestVersion()
263
383
  if (!latestVersion) return
264
384
 
265
- if (compareVersions(currentVersion, latestVersion) < 0) {
385
+ if (
386
+ // Download new version if current binary errors.
387
+ currentVersion === 'error' ||
388
+ compareVersions(currentVersion, latestVersion) < 0
389
+ ) {
266
390
  term.clearLine()
267
391
 
268
392
  // Remove the specific exit listener to prevent it from interfering with the update
@@ -288,11 +412,14 @@ async function checkForUpdates(runningProcess, exitListener) {
288
412
  await downloadBinary(latestVersion)
289
413
 
290
414
  // Restart with new binary - this replaces the current process
291
- const newChild = spawn(CONFIG.binaryPath, process.argv.slice(2), {
292
- stdio: 'inherit',
293
- cwd: process.cwd(),
294
- detached: false,
295
- })
415
+ const newChild = spawn(
416
+ CONFIG.binaryPath,
417
+ [packageName, ...process.argv.slice(2)],
418
+ {
419
+ stdio: 'inherit',
420
+ detached: false,
421
+ }
422
+ )
296
423
 
297
424
  // Set up exit handler for the new process
298
425
  newChild.on('exit', (code) => {
@@ -310,11 +437,14 @@ async function checkForUpdates(runningProcess, exitListener) {
310
437
  async function main() {
311
438
  await ensureBinaryExists()
312
439
 
313
- // Start codebuff
314
- const child = spawn(CONFIG.binaryPath, process.argv.slice(2), {
315
- stdio: 'inherit',
316
- cwd: process.cwd(),
317
- })
440
+ // Start the binary with codecane argument
441
+ const child = spawn(
442
+ CONFIG.binaryPath,
443
+ [packageName, ...process.argv.slice(2)],
444
+ {
445
+ stdio: 'inherit',
446
+ }
447
+ )
318
448
 
319
449
  // Store reference to the exit listener so we can remove it during updates
320
450
  const exitListener = (code) => {
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "codecane",
3
- "version": "1.0.412",
4
- "description": "AI coding agent",
3
+ "version": "1.0.420-beta.1",
4
+ "description": "AI coding agent (staging)",
5
5
  "license": "MIT",
6
6
  "bin": {
7
7
  "codecane": "index.js"
8
8
  },
9
+ "scripts": {
10
+ "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
+ },
9
12
  "files": [
10
13
  "index.js",
11
14
  "README.md"