codecane 1.0.0-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.
- package/README.md +73 -0
- package/index.js +468 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 🚀 Codecane - The most powerful coding agent (STAGING)
|
|
2
|
+
|
|
3
|
+
**⚠️ This is a staging/beta release for testing purposes.**
|
|
4
|
+
|
|
5
|
+
Codecane is a CLI tool that writes code for you.
|
|
6
|
+
|
|
7
|
+
1. Run `codecane` from your project directory
|
|
8
|
+
2. Tell it what to do
|
|
9
|
+
3. It will read and write to files and run commands to produce the code you want
|
|
10
|
+
|
|
11
|
+
Note: Codecane will run commands in your terminal as it deems necessary to fulfill your request.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
To install Codecane (staging), run:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g codecane@beta
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
(Use `sudo` if you get a permission error.)
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
After installation, you can start Codecane by running:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
codecane [project-directory]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If no project directory is specified, Codecane will use the current directory.
|
|
32
|
+
|
|
33
|
+
Once running, simply chat with Codecane to say what coding task you want done.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Understands your whole codebase
|
|
38
|
+
- Creates and edits multiple files based on your request
|
|
39
|
+
- Can run your tests or type checker or linter; can install packages
|
|
40
|
+
- It's powerful: ask Codecane to keep working until it reaches a condition and it will.
|
|
41
|
+
|
|
42
|
+
Our users regularly use Codecane to implement new features, write unit tests, refactor code,write scripts, or give advice.
|
|
43
|
+
|
|
44
|
+
## Knowledge Files
|
|
45
|
+
|
|
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.
|
|
47
|
+
|
|
48
|
+
Codecane can fluently read and write files, so it will add knowledge as it goes. You don't need to write knowledge manually!
|
|
49
|
+
|
|
50
|
+
Some have said every change should be paired with a unit test. In 2024, every change should come with a knowledge update!
|
|
51
|
+
|
|
52
|
+
## Tips
|
|
53
|
+
|
|
54
|
+
1. Type '/help' or just '/' to see available commands.
|
|
55
|
+
2. Create a `knowledge.md` file and collect specific points of advice. The assistant will use this knowledge to improve its responses.
|
|
56
|
+
3. Type `undo` or `redo` to revert or reapply file changes from the conversation.
|
|
57
|
+
4. Press `Esc` or `Ctrl+C` while Codecane is generating a response to stop it.
|
|
58
|
+
|
|
59
|
+
## Troubleshooting
|
|
60
|
+
|
|
61
|
+
If you are getting permission errors during installation, try using sudo:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
sudo npm install -g codecane@beta
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If you still have errors, it's a good idea to [reinstall Node](https://nodejs.org/en/download).
|
|
68
|
+
|
|
69
|
+
## Feedback
|
|
70
|
+
|
|
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
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const { spawn } = require('child_process')
|
|
7
|
+
const https = require('https')
|
|
8
|
+
const zlib = require('zlib')
|
|
9
|
+
const tar = require('tar')
|
|
10
|
+
|
|
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
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CONFIG = createConfig(packageName)
|
|
32
|
+
|
|
33
|
+
// Platform target mapping
|
|
34
|
+
const PLATFORM_TARGETS = {
|
|
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`,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Terminal utilities
|
|
43
|
+
const term = {
|
|
44
|
+
clearLine: () => {
|
|
45
|
+
if (process.stderr.isTTY) {
|
|
46
|
+
process.stderr.write('\r\x1b[K')
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
write: (text) => {
|
|
50
|
+
term.clearLine()
|
|
51
|
+
process.stderr.write(text)
|
|
52
|
+
},
|
|
53
|
+
writeLine: (text) => {
|
|
54
|
+
term.clearLine()
|
|
55
|
+
process.stderr.write(text + '\n')
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Utility functions
|
|
60
|
+
function httpGet(url, options = {}) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const parsedUrl = new URL(url)
|
|
63
|
+
const reqOptions = {
|
|
64
|
+
hostname: parsedUrl.hostname,
|
|
65
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
66
|
+
headers: {
|
|
67
|
+
'User-Agent': CONFIG.userAgent,
|
|
68
|
+
...options.headers,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
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
|
+
|
|
79
|
+
const req = https.get(reqOptions, (res) => {
|
|
80
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
81
|
+
return httpGet(new URL(res.headers.location, url).href, options)
|
|
82
|
+
.then(resolve)
|
|
83
|
+
.catch(reject)
|
|
84
|
+
}
|
|
85
|
+
resolve(res)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
req.on('error', reject)
|
|
89
|
+
|
|
90
|
+
const timeout = options.timeout || CONFIG.requestTimeout
|
|
91
|
+
req.setTimeout(timeout, () => {
|
|
92
|
+
req.destroy()
|
|
93
|
+
reject(new Error('Request timeout.'))
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function getLatestVersion() {
|
|
99
|
+
try {
|
|
100
|
+
const res = await httpGet(
|
|
101
|
+
`https://github.com/${CONFIG.githubRepo}/releases.atom`
|
|
102
|
+
)
|
|
103
|
+
|
|
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
|
|
114
|
+
|
|
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]
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
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
|
+
|
|
152
|
+
function getCurrentVersion() {
|
|
153
|
+
if (!fs.existsSync(CONFIG.binaryPath)) return null
|
|
154
|
+
|
|
155
|
+
try {
|
|
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
|
+
})
|
|
196
|
+
})
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return 'error'
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function compareVersions(v1, v2) {
|
|
203
|
+
if (!v1 || !v2) return 0
|
|
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
|
+
// 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
|
|
219
|
+
|
|
220
|
+
if (n1 < n2) return -1
|
|
221
|
+
if (n1 > n2) return 1
|
|
222
|
+
}
|
|
223
|
+
|
|
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
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function formatBytes(bytes) {
|
|
265
|
+
if (bytes === 0) return '0 B'
|
|
266
|
+
const k = 1024
|
|
267
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
268
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
269
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function createProgressBar(percentage, width = 30) {
|
|
273
|
+
const filled = Math.round((width * percentage) / 100)
|
|
274
|
+
const empty = width - filled
|
|
275
|
+
return '[' + '█'.repeat(filled) + '░'.repeat(empty) + ']'
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function downloadBinary(version) {
|
|
279
|
+
const platformKey = `${process.platform}-${process.arch}`
|
|
280
|
+
const fileName = PLATFORM_TARGETS[platformKey]
|
|
281
|
+
|
|
282
|
+
if (!fileName) {
|
|
283
|
+
throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const downloadUrl = `https://github.com/${CONFIG.githubRepo}/releases/download/v${version}/${fileName}`
|
|
287
|
+
|
|
288
|
+
// Ensure config directory exists
|
|
289
|
+
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
290
|
+
|
|
291
|
+
if (fs.existsSync(CONFIG.binaryPath)) {
|
|
292
|
+
fs.unlinkSync(CONFIG.binaryPath)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
term.write('Downloading...')
|
|
296
|
+
|
|
297
|
+
const res = await httpGet(downloadUrl)
|
|
298
|
+
|
|
299
|
+
if (res.statusCode !== 200) {
|
|
300
|
+
throw new Error(`Download failed: HTTP ${res.statusCode}`)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const totalSize = parseInt(res.headers['content-length'] || '0', 10)
|
|
304
|
+
let downloadedSize = 0
|
|
305
|
+
let lastProgressTime = Date.now()
|
|
306
|
+
|
|
307
|
+
res.on('data', (chunk) => {
|
|
308
|
+
downloadedSize += chunk.length
|
|
309
|
+
const now = Date.now()
|
|
310
|
+
if (now - lastProgressTime >= 100 || downloadedSize === totalSize) {
|
|
311
|
+
lastProgressTime = now
|
|
312
|
+
if (totalSize > 0) {
|
|
313
|
+
const pct = Math.round((downloadedSize / totalSize) * 100)
|
|
314
|
+
term.write(
|
|
315
|
+
`Downloading... ${createProgressBar(pct)} ${pct}% of ${formatBytes(
|
|
316
|
+
totalSize
|
|
317
|
+
)}`
|
|
318
|
+
)
|
|
319
|
+
} else {
|
|
320
|
+
term.write(`Downloading... ${formatBytes(downloadedSize)}`)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
|
|
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
|
+
})
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Find the extracted binary - it should be named "codebuff" or "codebuff.exe"
|
|
335
|
+
const files = fs.readdirSync(CONFIG.configDir)
|
|
336
|
+
const extractedPath = path.join(CONFIG.configDir, CONFIG.binaryName)
|
|
337
|
+
|
|
338
|
+
if (fs.existsSync(extractedPath)) {
|
|
339
|
+
if (process.platform !== 'win32') {
|
|
340
|
+
fs.chmodSync(extractedPath, 0o755)
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
term.clearLine()
|
|
349
|
+
console.error(`Extraction failed: ${error.message}`)
|
|
350
|
+
process.exit(1)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
term.clearLine()
|
|
354
|
+
console.log('Download complete! Starting Codecane...')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function ensureBinaryExists() {
|
|
358
|
+
if (!fs.existsSync(CONFIG.binaryPath)) {
|
|
359
|
+
const version = await getLatestVersion()
|
|
360
|
+
if (!version) {
|
|
361
|
+
console.error('❌ Failed to determine latest version')
|
|
362
|
+
console.error('Please check your internet connection and try again')
|
|
363
|
+
process.exit(1)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
await downloadBinary(version)
|
|
368
|
+
} catch (error) {
|
|
369
|
+
term.clearLine()
|
|
370
|
+
console.error('❌ Failed to download codebuff:', error.message)
|
|
371
|
+
console.error('Please check your internet connection and try again')
|
|
372
|
+
process.exit(1)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function checkForUpdates(runningProcess, exitListener) {
|
|
378
|
+
try {
|
|
379
|
+
const currentVersion = await getCurrentVersion()
|
|
380
|
+
if (!currentVersion) return
|
|
381
|
+
|
|
382
|
+
const latestVersion = await getLatestVersion()
|
|
383
|
+
if (!latestVersion) return
|
|
384
|
+
|
|
385
|
+
if (
|
|
386
|
+
// Download new version if current binary errors.
|
|
387
|
+
currentVersion === 'error' ||
|
|
388
|
+
compareVersions(currentVersion, latestVersion) < 0
|
|
389
|
+
) {
|
|
390
|
+
term.clearLine()
|
|
391
|
+
|
|
392
|
+
// Remove the specific exit listener to prevent it from interfering with the update
|
|
393
|
+
runningProcess.removeListener('exit', exitListener)
|
|
394
|
+
|
|
395
|
+
// Kill the running process
|
|
396
|
+
runningProcess.kill('SIGTERM')
|
|
397
|
+
|
|
398
|
+
// Wait for the process to actually exit
|
|
399
|
+
await new Promise((resolve) => {
|
|
400
|
+
runningProcess.on('exit', resolve)
|
|
401
|
+
// Fallback timeout in case the process doesn't exit gracefully
|
|
402
|
+
setTimeout(() => {
|
|
403
|
+
if (!runningProcess.killed) {
|
|
404
|
+
runningProcess.kill('SIGKILL')
|
|
405
|
+
}
|
|
406
|
+
resolve()
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// Find the extracted binary - it should be named "codecane" or "codecane.exe"
|
|
410
|
+
}, 5000)
|
|
411
|
+
console.log(`Update available: ${currentVersion} → ${latestVersion}`)
|
|
412
|
+
|
|
413
|
+
await downloadBinary(latestVersion)
|
|
414
|
+
|
|
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
|
+
)
|
|
424
|
+
|
|
425
|
+
// Set up exit handler for the new process
|
|
426
|
+
newChild.on('exit', (code) => {
|
|
427
|
+
process.exit(code || 0)
|
|
428
|
+
})
|
|
429
|
+
} catch (error) {
|
|
430
|
+
console.error('❌ Failed to download codecane:', error.message)
|
|
431
|
+
|
|
432
|
+
// Don't return - keep this function running to maintain the wrapper
|
|
433
|
+
return new Promise(() => {}) // Never resolves, keeps wrapper alive
|
|
434
|
+
}
|
|
435
|
+
// Silently ignore update check errors
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function main() {
|
|
440
|
+
await ensureBinaryExists()
|
|
441
|
+
|
|
442
|
+
// Start the binary with codecane argument
|
|
443
|
+
const child = spawn(
|
|
444
|
+
CONFIG.binaryPath,
|
|
445
|
+
[packageName, ...process.argv.slice(2)],
|
|
446
|
+
{
|
|
447
|
+
stdio: 'inherit',
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
// Store reference to the exit listener so we can remove it during updates
|
|
452
|
+
const exitListener = (code) => {
|
|
453
|
+
process.exit(code || 0)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
child.on('exit', exitListener)
|
|
457
|
+
|
|
458
|
+
// Check for updates in background
|
|
459
|
+
setTimeout(() => {
|
|
460
|
+
checkForUpdates(child, exitListener)
|
|
461
|
+
}, 100)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Run the main function
|
|
465
|
+
main().catch((error) => {
|
|
466
|
+
console.error('❌ Unexpected error:', error.message)
|
|
467
|
+
process.exit(1)
|
|
468
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codecane",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "AI coding agent (staging)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codecane": "index.js"
|
|
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
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"os": [
|
|
17
|
+
"darwin",
|
|
18
|
+
"linux",
|
|
19
|
+
"win32"
|
|
20
|
+
],
|
|
21
|
+
"cpu": [
|
|
22
|
+
"x64",
|
|
23
|
+
"arm64"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=16"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"tar": "^6.2.0"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/CodebuffAI/codebuff-community.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://codebuff.com",
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|