cortexcode 0.1.1 → 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 +4 -1
- package/bin/cortexcode.mjs +141 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,10 @@ 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 the installed app at once; the app updates its frame and plugins itself.
|
|
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
|
|
11
14
|
runs on its own: you can close the terminal (its start output goes to `~/.cortexcode/logs/start.log`);
|
|
12
15
|
running the command again while it runs only says so.
|
|
13
16
|
|
package/bin/cortexcode.mjs
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
// later runs start the installed app at once, without looking at the catalog: the app updates its
|
|
5
5
|
// frame and plugins itself, so a newer app release is only for machines that have none. The app runs
|
|
6
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).
|
|
7
8
|
// Arguments go to the app (`--headless`, `--safe`).
|
|
8
9
|
|
|
9
10
|
import { spawn } from 'node:child_process';
|
|
10
11
|
import { createHash } from 'node:crypto';
|
|
11
|
-
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';
|
|
12
13
|
import http from 'node:http';
|
|
13
14
|
import https from 'node:https';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
15
|
-
import { join } from 'node:path';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
16
17
|
import { pipeline } from 'node:stream/promises';
|
|
17
18
|
|
|
18
19
|
const REPO = process.env.CORTEXCODE_REPO || 'agentbusiness/CortexCode';
|
|
@@ -144,6 +145,134 @@ function running() {
|
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
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
|
+
|
|
147
276
|
/** Keep the newest versions (and the one starting now). */
|
|
148
277
|
function prune(keep) {
|
|
149
278
|
installed()
|
|
@@ -155,7 +284,7 @@ function prune(keep) {
|
|
|
155
284
|
async function main() {
|
|
156
285
|
const args = process.argv.slice(2);
|
|
157
286
|
if (args.includes('--help') || args.includes('-h')) {
|
|
158
|
-
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');
|
|
159
288
|
return 0;
|
|
160
289
|
}
|
|
161
290
|
const pid = running();
|
|
@@ -181,6 +310,15 @@ async function main() {
|
|
|
181
310
|
return 1;
|
|
182
311
|
}
|
|
183
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
|
+
}
|
|
184
322
|
// Its own session, detached from this terminal: closing the terminal does not quit it.
|
|
185
323
|
mkdirSync(join(HOME, 'logs'), { recursive: true });
|
|
186
324
|
const log = openSync(START_LOG, 'a');
|
package/package.json
CHANGED