codecane 1.0.413 → 1.0.420-beta.10
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 +20 -16
- package/index.js +199 -62
- 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
|
-
|
|
3
|
+
**⚠️ This is a staging/beta release for testing purposes.**
|
|
4
4
|
|
|
5
|
-
|
|
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:
|
|
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
|
|
15
|
+
To install Codecane (staging), run:
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
|
-
npm install -g
|
|
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
|
|
25
|
+
After installation, you can start Codecane by running:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
28
|
+
codecane [project-directory]
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
If no project directory is specified,
|
|
31
|
+
If no project directory is specified, Codecane will use the current directory.
|
|
30
32
|
|
|
31
|
-
Once running, simply chat with
|
|
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
|
|
40
|
+
- It's powerful: ask Codecane to keep working until it reaches a condition and it will.
|
|
39
41
|
|
|
40
|
-
Our users regularly use
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,30 +3,41 @@
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const os = require('os')
|
|
6
|
-
const { spawn
|
|
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
|
-
//
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 =
|
|
18
|
+
process.platform === 'win32' ? `${packageName}.exe` : packageName
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
homeDir,
|
|
22
|
+
configDir,
|
|
23
|
+
binaryName,
|
|
24
|
+
binaryPath: path.join(configDir, binaryName),
|
|
25
|
+
githubRepo: 'CodebuffAI/codebuff-community',
|
|
26
|
+
userAgent: `${packageName}-cli`,
|
|
27
|
+
requestTimeout: 20000,
|
|
28
|
+
isPrerelease: true, // codecane always looks for prereleases
|
|
29
|
+
}
|
|
19
30
|
}
|
|
20
31
|
|
|
21
|
-
CONFIG
|
|
32
|
+
const CONFIG = createConfig(packageName)
|
|
22
33
|
|
|
23
34
|
// Platform target mapping
|
|
24
35
|
const PLATFORM_TARGETS = {
|
|
25
|
-
'linux-x64':
|
|
26
|
-
'linux-arm64':
|
|
27
|
-
'darwin-x64':
|
|
28
|
-
'darwin-arm64':
|
|
29
|
-
'win32-x64':
|
|
36
|
+
'linux-x64': `${packageName}-linux-x64.tar.gz`,
|
|
37
|
+
'linux-arm64': `${packageName}-linux-arm64.tar.gz`,
|
|
38
|
+
'darwin-x64': `${packageName}-darwin-x64.tar.gz`,
|
|
39
|
+
'darwin-arm64': `${packageName}-darwin-arm64.tar.gz`,
|
|
40
|
+
'win32-x64': `${packageName}-win32-x64.tar.gz`,
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
// Terminal utilities
|
|
@@ -80,37 +91,60 @@ function httpGet(url, options = {}) {
|
|
|
80
91
|
const timeout = options.timeout || CONFIG.requestTimeout
|
|
81
92
|
req.setTimeout(timeout, () => {
|
|
82
93
|
req.destroy()
|
|
83
|
-
reject(new Error('Request timeout'))
|
|
94
|
+
reject(new Error('Request timeout.'))
|
|
84
95
|
})
|
|
85
96
|
})
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
async function getLatestVersion() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
try {
|
|
101
|
+
const res = await httpGet(
|
|
102
|
+
`https://github.com/${CONFIG.githubRepo}/releases.atom`
|
|
103
|
+
)
|
|
92
104
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
if (res.statusCode !== 200) return null
|
|
106
|
+
|
|
107
|
+
const body = await streamToString(res)
|
|
108
|
+
|
|
109
|
+
// Parse the Atom XML to extract releases
|
|
110
|
+
const tagMatches = body.match(
|
|
111
|
+
/<id>tag:github\.com,2008:Repository\/\d+\/([^<]+)<\/id>/g
|
|
100
112
|
)
|
|
101
|
-
return null
|
|
102
|
-
}
|
|
103
113
|
|
|
104
|
-
|
|
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
|
|
105
135
|
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
// Sort and return the latest version
|
|
137
|
+
filteredVersions.sort(compareVersions)
|
|
138
|
+
return filteredVersions[filteredVersions.length - 1]
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
108
142
|
}
|
|
109
143
|
|
|
110
144
|
function streamToString(stream) {
|
|
111
145
|
return new Promise((resolve, reject) => {
|
|
112
146
|
let data = ''
|
|
113
|
-
stream.on('data', chunk => (data += chunk))
|
|
147
|
+
stream.on('data', (chunk) => (data += chunk))
|
|
114
148
|
stream.on('end', () => resolve(data))
|
|
115
149
|
stream.on('error', reject)
|
|
116
150
|
})
|
|
@@ -120,33 +154,112 @@ function getCurrentVersion() {
|
|
|
120
154
|
if (!fs.existsSync(CONFIG.binaryPath)) return null
|
|
121
155
|
|
|
122
156
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
const child = spawn(CONFIG.binaryPath, ['--version'], {
|
|
159
|
+
cwd: os.homedir(),
|
|
160
|
+
stdio: 'pipe',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
let output = ''
|
|
164
|
+
let errorOutput = ''
|
|
165
|
+
|
|
166
|
+
child.stdout.on('data', (data) => {
|
|
167
|
+
output += data.toString()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
child.stderr.on('data', (data) => {
|
|
171
|
+
errorOutput += data.toString()
|
|
172
|
+
})
|
|
173
|
+
|
|
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)
|
|
183
|
+
|
|
184
|
+
child.on('exit', (code) => {
|
|
185
|
+
clearTimeout(timeout)
|
|
186
|
+
if (code === 0) {
|
|
187
|
+
resolve(output.trim())
|
|
188
|
+
} else {
|
|
189
|
+
resolve('error')
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
child.on('error', () => {
|
|
194
|
+
clearTimeout(timeout)
|
|
195
|
+
resolve('error')
|
|
196
|
+
})
|
|
128
197
|
})
|
|
129
|
-
return result.trim()
|
|
130
198
|
} catch (error) {
|
|
131
|
-
return
|
|
199
|
+
return 'error'
|
|
132
200
|
}
|
|
133
201
|
}
|
|
134
202
|
|
|
135
203
|
function compareVersions(v1, v2) {
|
|
136
204
|
if (!v1 || !v2) return 0
|
|
137
205
|
|
|
138
|
-
const
|
|
139
|
-
|
|
206
|
+
const parseVersion = (version) => {
|
|
207
|
+
const parts = version.split('-')
|
|
208
|
+
const mainParts = parts[0].split('.').map(Number)
|
|
209
|
+
const prereleaseParts = parts[1] ? parts[1].split('.') : []
|
|
210
|
+
return { main: mainParts, prerelease: prereleaseParts }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const p1 = parseVersion(v1)
|
|
214
|
+
const p2 = parseVersion(v2)
|
|
140
215
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
216
|
+
// Compare main version parts
|
|
217
|
+
for (let i = 0; i < Math.max(p1.main.length, p2.main.length); i++) {
|
|
218
|
+
const n1 = p1.main[i] || 0
|
|
219
|
+
const n2 = p2.main[i] || 0
|
|
144
220
|
|
|
145
|
-
if (
|
|
146
|
-
if (
|
|
221
|
+
if (n1 < n2) return -1
|
|
222
|
+
if (n1 > n2) return 1
|
|
147
223
|
}
|
|
148
224
|
|
|
149
|
-
|
|
225
|
+
// If main versions are equal, compare prerelease parts
|
|
226
|
+
if (p1.prerelease.length === 0 && p2.prerelease.length === 0) {
|
|
227
|
+
return 0 // No prerelease, versions are equal
|
|
228
|
+
} else if (p1.prerelease.length === 0) {
|
|
229
|
+
return 1 // v1 is a release, v2 is prerelease, so v1 > v2
|
|
230
|
+
} else if (p2.prerelease.length === 0) {
|
|
231
|
+
return -1 // v2 is a release, v1 is prerelease, so v1 < v2
|
|
232
|
+
} else {
|
|
233
|
+
// Both have prerelease parts, compare them
|
|
234
|
+
for (
|
|
235
|
+
let i = 0;
|
|
236
|
+
i < Math.max(p1.prerelease.length, p2.prerelease.length);
|
|
237
|
+
i++
|
|
238
|
+
) {
|
|
239
|
+
const pr1 = p1.prerelease[i] || ''
|
|
240
|
+
const pr2 = p2.prerelease[i] || ''
|
|
241
|
+
|
|
242
|
+
// Handle numeric vs. string parts
|
|
243
|
+
const isNum1 = !isNaN(parseInt(pr1))
|
|
244
|
+
const isNum2 = !isNaN(parseInt(pr2))
|
|
245
|
+
|
|
246
|
+
if (isNum1 && isNum2) {
|
|
247
|
+
const num1 = parseInt(pr1)
|
|
248
|
+
const num2 = parseInt(pr2)
|
|
249
|
+
if (num1 < num2) return -1
|
|
250
|
+
if (num1 > num2) return 1
|
|
251
|
+
} else if (isNum1 && !isNum2) {
|
|
252
|
+
return 1 // Numeric prerelease is generally higher than alpha/beta
|
|
253
|
+
} else if (!isNum1 && isNum2) {
|
|
254
|
+
return -1
|
|
255
|
+
} else {
|
|
256
|
+
// Lexicographical comparison for string parts
|
|
257
|
+
if (pr1 < pr2) return -1
|
|
258
|
+
if (pr1 > pr2) return 1
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return 0 // Prerelease parts are equal
|
|
262
|
+
}
|
|
150
263
|
}
|
|
151
264
|
|
|
152
265
|
function formatBytes(bytes) {
|
|
@@ -176,6 +289,10 @@ async function downloadBinary(version) {
|
|
|
176
289
|
// Ensure config directory exists
|
|
177
290
|
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
178
291
|
|
|
292
|
+
if (fs.existsSync(CONFIG.binaryPath)) {
|
|
293
|
+
fs.unlinkSync(CONFIG.binaryPath)
|
|
294
|
+
}
|
|
295
|
+
|
|
179
296
|
term.write('Downloading...')
|
|
180
297
|
|
|
181
298
|
const res = await httpGet(downloadUrl)
|
|
@@ -213,8 +330,6 @@ async function downloadBinary(version) {
|
|
|
213
330
|
.on('finish', resolve)
|
|
214
331
|
.on('error', reject)
|
|
215
332
|
})
|
|
216
|
-
term.clearLine()
|
|
217
|
-
console.log('Download and extract complete!')
|
|
218
333
|
|
|
219
334
|
try {
|
|
220
335
|
// Find the extracted binary - it should be named "codebuff" or "codebuff.exe"
|
|
@@ -230,13 +345,14 @@ async function downloadBinary(version) {
|
|
|
230
345
|
`Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`
|
|
231
346
|
)
|
|
232
347
|
}
|
|
233
|
-
|
|
234
|
-
term.write('Starting Codebuff...')
|
|
235
348
|
} catch (error) {
|
|
236
349
|
term.clearLine()
|
|
237
350
|
console.error(`Extraction failed: ${error.message}`)
|
|
238
351
|
process.exit(1)
|
|
239
352
|
}
|
|
353
|
+
|
|
354
|
+
term.clearLine()
|
|
355
|
+
console.log('Download complete! Starting Codecane...')
|
|
240
356
|
}
|
|
241
357
|
|
|
242
358
|
async function ensureBinaryExists() {
|
|
@@ -253,7 +369,7 @@ async function ensureBinaryExists() {
|
|
|
253
369
|
} catch (error) {
|
|
254
370
|
term.clearLine()
|
|
255
371
|
console.error('❌ Failed to download codebuff:', error.message)
|
|
256
|
-
console.error('Please try again
|
|
372
|
+
console.error('Please check your internet connection and try again')
|
|
257
373
|
process.exit(1)
|
|
258
374
|
}
|
|
259
375
|
}
|
|
@@ -261,13 +377,17 @@ async function ensureBinaryExists() {
|
|
|
261
377
|
|
|
262
378
|
async function checkForUpdates(runningProcess, exitListener) {
|
|
263
379
|
try {
|
|
264
|
-
const currentVersion = getCurrentVersion()
|
|
380
|
+
const currentVersion = await getCurrentVersion()
|
|
265
381
|
if (!currentVersion) return
|
|
266
382
|
|
|
267
383
|
const latestVersion = await getLatestVersion()
|
|
268
384
|
if (!latestVersion) return
|
|
269
385
|
|
|
270
|
-
if (
|
|
386
|
+
if (
|
|
387
|
+
// Download new version if current binary errors.
|
|
388
|
+
currentVersion === 'error' ||
|
|
389
|
+
compareVersions(currentVersion, latestVersion) < 0
|
|
390
|
+
) {
|
|
271
391
|
term.clearLine()
|
|
272
392
|
|
|
273
393
|
// Remove the specific exit listener to prevent it from interfering with the update
|
|
@@ -293,10 +413,14 @@ async function checkForUpdates(runningProcess, exitListener) {
|
|
|
293
413
|
await downloadBinary(latestVersion)
|
|
294
414
|
|
|
295
415
|
// Restart with new binary - this replaces the current process
|
|
296
|
-
const newChild = spawn(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
416
|
+
const newChild = spawn(
|
|
417
|
+
CONFIG.binaryPath,
|
|
418
|
+
[packageName, ...process.argv.slice(2)],
|
|
419
|
+
{
|
|
420
|
+
stdio: 'inherit',
|
|
421
|
+
detached: false,
|
|
422
|
+
}
|
|
423
|
+
)
|
|
300
424
|
|
|
301
425
|
// Set up exit handler for the new process
|
|
302
426
|
newChild.on('exit', (code) => {
|
|
@@ -312,12 +436,25 @@ async function checkForUpdates(runningProcess, exitListener) {
|
|
|
312
436
|
}
|
|
313
437
|
|
|
314
438
|
async function main() {
|
|
439
|
+
// Bold, bright warning for staging environment
|
|
440
|
+
console.log('\x1b[1m\x1b[91m' + '='.repeat(60) + '\x1b[0m')
|
|
441
|
+
console.log('\x1b[1m\x1b[93m❄️ CODECANE STAGING ENVIRONMENT ❄️\x1b[0m')
|
|
442
|
+
console.log(
|
|
443
|
+
'\x1b[1m\x1b[91mFOR TESTING PURPOSES ONLY - NOT FOR PRODUCTION USE\x1b[0m'
|
|
444
|
+
)
|
|
445
|
+
console.log('\x1b[1m\x1b[91m' + '='.repeat(60) + '\x1b[0m')
|
|
446
|
+
console.log('')
|
|
447
|
+
|
|
315
448
|
await ensureBinaryExists()
|
|
316
449
|
|
|
317
|
-
// Start
|
|
318
|
-
const child = spawn(
|
|
319
|
-
|
|
320
|
-
|
|
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
|
+
)
|
|
321
458
|
|
|
322
459
|
// Store reference to the exit listener so we can remove it during updates
|
|
323
460
|
const exitListener = (code) => {
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecane",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "AI coding agent",
|
|
3
|
+
"version": "1.0.420-beta.10",
|
|
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"
|