cortexcode 0.1.0 → 0.1.2
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 +6 -3
- package/bin/cortexcode.mjs +153 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,11 @@ npx cortexcode
|
|
|
7
7
|
Starts [CortexCode](https://github.com/agentbusiness/CortexCode), a local coding agent. The first
|
|
8
8
|
run downloads the app for this machine (Apple Silicon Macs; Linux x64 and arm64) from the official
|
|
9
9
|
releases, checks it against the checksum in the release catalog and unpacks it into `~/.cortexcode/app/`.
|
|
10
|
-
Later runs start
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Later runs start the installed app at once; the app updates its frame and plugins itself. It also
|
|
11
|
+
adds CortexCode to your applications (macOS: `/Applications`, or `~/Applications` when that is not
|
|
12
|
+
writable; Linux: `~/.local/share/applications`), which starts the newest installed version, so you
|
|
13
|
+
can open it without a terminal. The app
|
|
14
|
+
runs on its own: you can close the terminal (its start output goes to `~/.cortexcode/logs/start.log`);
|
|
15
|
+
running the command again while it runs only says so.
|
|
13
16
|
|
|
14
17
|
Arguments go to the app: `npx cortexcode --headless`, `npx cortexcode --safe`.
|
package/bin/cortexcode.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// `npx cortexcode`: start CortexCode. The first run downloads the app for this machine from the
|
|
3
3
|
// official releases (the catalog), checks its checksum and unpacks it into ~/.cortexcode/app/<version>/;
|
|
4
|
-
// later runs start
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// later runs start the installed app at once, without looking at the catalog: the app updates its
|
|
5
|
+
// frame and plugins itself, so a newer app release is only for machines that have none. The app runs
|
|
6
|
+
// on its own: closing the terminal does not quit it (its start output goes to ~/.cortexcode/logs/start.log).
|
|
7
|
+
// Every start also keeps a starter in the Applications folder (macOS) or the app list (Linux).
|
|
8
|
+
// Arguments go to the app (`--headless`, `--safe`).
|
|
7
9
|
|
|
8
10
|
import { spawn } from 'node:child_process';
|
|
9
11
|
import { createHash } from 'node:crypto';
|
|
10
|
-
import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { chmodSync, closeSync, copyFileSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
13
|
import http from 'node:http';
|
|
12
14
|
import https from 'node:https';
|
|
13
15
|
import { homedir } from 'node:os';
|
|
14
|
-
import { join } from 'node:path';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
15
17
|
import { pipeline } from 'node:stream/promises';
|
|
16
18
|
|
|
17
19
|
const REPO = process.env.CORTEXCODE_REPO || 'agentbusiness/CortexCode';
|
|
@@ -143,6 +145,134 @@ function running() {
|
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
// The starter: a small app in the Applications folder (macOS) or the desktop's app list (Linux) that
|
|
149
|
+
// starts the newest installed version, so the app opens without a terminal. It copies nothing of the
|
|
150
|
+
// app, so it keeps working when updates add versions and prune old ones.
|
|
151
|
+
const STARTER_ID = 'ai.cortexcode.starter';
|
|
152
|
+
|
|
153
|
+
/** `text` quoted for sh. */
|
|
154
|
+
const shq = (text) => `'${String(text).replace(/'/g, `'\\''`)}'`;
|
|
155
|
+
|
|
156
|
+
/** Write `text` to `path` unless it already holds it; true when it wrote. */
|
|
157
|
+
function writeIfChanged(path, text, mode = 0o644) {
|
|
158
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
159
|
+
if (existsSync(path) && readFileSync(path, 'utf8') === text) return false;
|
|
160
|
+
writeFileSync(path, text);
|
|
161
|
+
chmodSync(path, mode);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The starter's script: the newest ready version in APPS, started as this launcher starts it. */
|
|
166
|
+
function starterScript() {
|
|
167
|
+
const start =
|
|
168
|
+
process.platform === 'darwin'
|
|
169
|
+
? 'exec open "$apps/$version/CortexCode.app" --stdout "$log" --stderr "$log" --args "$@"'
|
|
170
|
+
: 'exec "$apps/$version/cortexcode/CortexCode" "$@" >>"$log" 2>&1';
|
|
171
|
+
const missing =
|
|
172
|
+
process.platform === 'darwin'
|
|
173
|
+
? `osascript -e 'display alert "CortexCode is not installed" message "Run npx cortexcode in a terminal to install it."' >/dev/null 2>&1`
|
|
174
|
+
: 'command -v notify-send >/dev/null 2>&1 && notify-send CortexCode "CortexCode is not installed: run npx cortexcode in a terminal."';
|
|
175
|
+
return `#!/bin/sh
|
|
176
|
+
# CortexCode starter, written by npx cortexcode: starts the newest app installed in the folder below.
|
|
177
|
+
apps=${shq(APPS)}
|
|
178
|
+
log=${shq(START_LOG)}
|
|
179
|
+
mkdir -p "$(dirname "$log")"
|
|
180
|
+
version="$(ls "$apps" 2>/dev/null | grep -E '^[0-9]+\\.[0-9]+\\.[0-9]+$' | sort -t. -k1,1n -k2,2n -k3,3n |
|
|
181
|
+
while read -r v; do [ -e "$apps/$v/${READY}" ] && echo "$v"; done | tail -n 1)"
|
|
182
|
+
if [ -z "$version" ]; then
|
|
183
|
+
${missing}
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
${start}
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** macOS: CortexCode.app in /Applications, or in ~/Applications when /Applications is not writable. */
|
|
191
|
+
function macStarter(appDir) {
|
|
192
|
+
const places = ['/Applications', join(homedir(), 'Applications')].map((place) => join(place, 'CortexCode.app'));
|
|
193
|
+
const ours = (bundle) => {
|
|
194
|
+
try {
|
|
195
|
+
return readFileSync(join(bundle, 'Contents', 'Info.plist'), 'utf8').includes(STARTER_ID);
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
// A CortexCode.app that is not a starter (the disk image's app) is the user's own: leave it.
|
|
201
|
+
if (places.some((bundle) => existsSync(join(bundle, 'Contents', 'Info.plist')) && !ours(bundle))) return;
|
|
202
|
+
let bundle = places.find(ours);
|
|
203
|
+
const fresh = !bundle;
|
|
204
|
+
if (!bundle) {
|
|
205
|
+
for (const place of places) {
|
|
206
|
+
try {
|
|
207
|
+
mkdirSync(join(place, 'Contents', 'MacOS'), { recursive: true });
|
|
208
|
+
bundle = place;
|
|
209
|
+
break;
|
|
210
|
+
} catch {
|
|
211
|
+
// not writable: the next place
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!bundle) throw new Error('neither /Applications nor ~/Applications is writable');
|
|
216
|
+
// LSUIElement: the starter only hands over to the app, so it never shows in the Dock itself.
|
|
217
|
+
writeIfChanged(
|
|
218
|
+
join(bundle, 'Contents', 'Info.plist'),
|
|
219
|
+
`<?xml version="1.0" encoding="UTF-8"?>
|
|
220
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
221
|
+
<plist version="1.0">
|
|
222
|
+
<dict>
|
|
223
|
+
<key>CFBundleName</key>
|
|
224
|
+
<string>CortexCode</string>
|
|
225
|
+
<key>CFBundleDisplayName</key>
|
|
226
|
+
<string>CortexCode</string>
|
|
227
|
+
<key>CFBundleIdentifier</key>
|
|
228
|
+
<string>${STARTER_ID}</string>
|
|
229
|
+
<key>CFBundleExecutable</key>
|
|
230
|
+
<string>CortexCode</string>
|
|
231
|
+
<key>CFBundleIconFile</key>
|
|
232
|
+
<string>CortexCode.icns</string>
|
|
233
|
+
<key>CFBundlePackageType</key>
|
|
234
|
+
<string>APPL</string>
|
|
235
|
+
<key>CFBundleShortVersionString</key>
|
|
236
|
+
<string>1.0</string>
|
|
237
|
+
<key>LSUIElement</key>
|
|
238
|
+
<true/>
|
|
239
|
+
</dict>
|
|
240
|
+
</plist>
|
|
241
|
+
`,
|
|
242
|
+
);
|
|
243
|
+
writeIfChanged(join(bundle, 'Contents', 'MacOS', 'CortexCode'), starterScript(), 0o755);
|
|
244
|
+
const icon = join(bundle, 'Contents', 'Resources', 'CortexCode.icns');
|
|
245
|
+
if (!existsSync(icon)) {
|
|
246
|
+
mkdirSync(dirname(icon), { recursive: true });
|
|
247
|
+
copyFileSync(join(appDir, 'CortexCode.app', 'Contents', 'Resources', 'CortexCode.icns'), icon);
|
|
248
|
+
}
|
|
249
|
+
if (fresh) say(`CortexCode is in ${dirname(bundle)}: open it from there next time`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Linux: a desktop entry in the app list ($XDG_DATA_HOME/applications) that runs APPS/start. */
|
|
253
|
+
function linuxStarter(appDir) {
|
|
254
|
+
const script = join(APPS, 'start');
|
|
255
|
+
const icon = join(APPS, 'cortexcode.png');
|
|
256
|
+
writeIfChanged(script, starterScript(), 0o755);
|
|
257
|
+
if (!existsSync(icon)) copyFileSync(join(appDir, 'cortexcode', 'ui', 'shell', 'assets', 'cortexcode-512.png'), icon);
|
|
258
|
+
const data = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
259
|
+
const entry = join(data, 'applications', 'cortexcode.desktop');
|
|
260
|
+
const fresh = !existsSync(entry);
|
|
261
|
+
writeIfChanged(
|
|
262
|
+
entry,
|
|
263
|
+
`[Desktop Entry]
|
|
264
|
+
Type=Application
|
|
265
|
+
Name=CortexCode
|
|
266
|
+
Comment=Local coding agent
|
|
267
|
+
Exec="${script}"
|
|
268
|
+
Icon=${icon}
|
|
269
|
+
Terminal=false
|
|
270
|
+
Categories=Development;
|
|
271
|
+
`,
|
|
272
|
+
);
|
|
273
|
+
if (fresh) say('CortexCode is in your applications: open it from there next time');
|
|
274
|
+
}
|
|
275
|
+
|
|
146
276
|
/** Keep the newest versions (and the one starting now). */
|
|
147
277
|
function prune(keep) {
|
|
148
278
|
installed()
|
|
@@ -154,7 +284,7 @@ function prune(keep) {
|
|
|
154
284
|
async function main() {
|
|
155
285
|
const args = process.argv.slice(2);
|
|
156
286
|
if (args.includes('--help') || args.includes('-h')) {
|
|
157
|
-
process.stdout.write('npx cortexcode [--headless] [--safe]\n\nStarts CortexCode; the first run downloads it into ~/.cortexcode/app.\n');
|
|
287
|
+
process.stdout.write('npx cortexcode [--headless] [--safe]\n\nStarts CortexCode; the first run downloads it into ~/.cortexcode/app and adds it to your applications.\n');
|
|
158
288
|
return 0;
|
|
159
289
|
}
|
|
160
290
|
const pid = running();
|
|
@@ -163,29 +293,32 @@ async function main() {
|
|
|
163
293
|
return 0;
|
|
164
294
|
}
|
|
165
295
|
const key = platform();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const listed = await catalog();
|
|
169
|
-
const files = listed.app?.files?.[key];
|
|
170
|
-
if (listed.app && files?.archive) wanted = { app: listed.app, asset: files.archive };
|
|
171
|
-
else say(`no CortexCode app is released for ${key} yet`);
|
|
172
|
-
} catch (error) {
|
|
173
|
-
say(`cannot look for a new version (${error.message})`);
|
|
174
|
-
}
|
|
175
|
-
if (wanted && !installed().includes(wanted.app.version)) {
|
|
296
|
+
// The installed app updates itself (frame and plugins): only a machine without one downloads.
|
|
297
|
+
if (installed().length === 0) {
|
|
176
298
|
try {
|
|
177
|
-
await
|
|
299
|
+
const listed = await catalog();
|
|
300
|
+
const files = listed.app?.files?.[key];
|
|
301
|
+
if (listed.app && files?.archive) await install(listed.app, files.archive);
|
|
302
|
+
else say(`no CortexCode app is released for ${key} yet`);
|
|
178
303
|
} catch (error) {
|
|
179
|
-
say(error.message);
|
|
304
|
+
say(`cannot download CortexCode (${error.message})`);
|
|
180
305
|
}
|
|
181
306
|
}
|
|
182
|
-
const
|
|
183
|
-
const chosen = wanted && versions.includes(wanted.app.version) ? wanted.app.version : versions[0];
|
|
307
|
+
const chosen = installed()[0];
|
|
184
308
|
if (!chosen) {
|
|
185
309
|
say('CortexCode is not installed and could not be downloaded');
|
|
186
310
|
return 1;
|
|
187
311
|
}
|
|
188
312
|
prune(chosen);
|
|
313
|
+
// Only for the user's own home: a scratch home (CORTEX_HOME) must not take over the starter.
|
|
314
|
+
if (!process.env.CORTEX_HOME) {
|
|
315
|
+
try {
|
|
316
|
+
if (process.platform === 'darwin') macStarter(join(APPS, chosen));
|
|
317
|
+
else linuxStarter(join(APPS, chosen));
|
|
318
|
+
} catch (error) {
|
|
319
|
+
say(`cannot add CortexCode to the applications (${error.message})`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
189
322
|
// Its own session, detached from this terminal: closing the terminal does not quit it.
|
|
190
323
|
mkdirSync(join(HOME, 'logs'), { recursive: true });
|
|
191
324
|
const log = openSync(START_LOG, 'a');
|
package/package.json
CHANGED