cybercode-cli 1.0.0 → 1.0.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/bin/cli.mjs +41 -103
- package/package.json +1 -1
- package/python/webui_codex.py +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// cybercode CLI launcher — bootstraps a Python env and starts the web UI.
|
|
3
3
|
// Designed for `npx cybercode` one-click usage.
|
|
4
4
|
|
|
5
|
-
import { spawn, spawnSync
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
6
|
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync, readFileSync } from "node:fs";
|
|
7
7
|
import { join, dirname, resolve } from "node:path";
|
|
8
8
|
import { homedir, platform } from "node:os";
|
|
@@ -20,10 +20,15 @@ const COLORS = {
|
|
|
20
20
|
};
|
|
21
21
|
const c = (color, text) => `${COLORS[color] || ""}${text}${COLORS.reset}`;
|
|
22
22
|
|
|
23
|
+
function splitCommand(argv) {
|
|
24
|
+
if (argv[0] === "webui") return { command: "webui", args: argv.slice(1) };
|
|
25
|
+
return { command: "webui", args: argv };
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
// ---- Parse CLI args ----
|
|
24
|
-
function parseArgs() {
|
|
29
|
+
function parseArgs(rawArgv) {
|
|
25
30
|
const args = { port: null, host: "127.0.0.1", dir: null, noBrowser: false, llm: 0, help: false, version: false };
|
|
26
|
-
const argv =
|
|
31
|
+
const argv = rawArgv.slice();
|
|
27
32
|
for (let i = 0; i < argv.length; i++) {
|
|
28
33
|
const a = argv[i];
|
|
29
34
|
if (a === "-h" || a === "--help") args.help = true;
|
|
@@ -43,12 +48,12 @@ function showHelp() {
|
|
|
43
48
|
${c("bold", "cybercode")} ${c("dim", `v${pkg.version}`)} — Codex-dark web UI with a built-in self-evolving agent
|
|
44
49
|
|
|
45
50
|
${c("bold", "USAGE")}
|
|
46
|
-
npx cybercode
|
|
47
|
-
npx cybercode --port 8080
|
|
48
|
-
npx cybercode --host 0.0.0.0
|
|
49
|
-
npx cybercode --no-browser
|
|
50
|
-
npx cybercode --dir ~/my-agent
|
|
51
|
-
npx cybercode --llm 1
|
|
51
|
+
npx cybercode webui # start the web UI
|
|
52
|
+
npx cybercode webui --port 8080 # custom port
|
|
53
|
+
npx cybercode webui --host 0.0.0.0 # listen on all interfaces
|
|
54
|
+
npx cybercode webui --no-browser # don't auto-open browser
|
|
55
|
+
npx cybercode webui --dir ~/my-agent # custom working directory
|
|
56
|
+
npx cybercode webui --llm 1 # start on 2nd configured LLM
|
|
52
57
|
|
|
53
58
|
${c("bold", "OPTIONS")}
|
|
54
59
|
-p, --port <num> Port (default: auto-find free port near 18600)
|
|
@@ -84,9 +89,7 @@ function findPython() {
|
|
|
84
89
|
if (match) {
|
|
85
90
|
const major = parseInt(match[1], 10);
|
|
86
91
|
const minor = parseInt(match[2], 10);
|
|
87
|
-
if (major > 3 || (major === 3 && minor >= 11))
|
|
88
|
-
return cmd;
|
|
89
|
-
}
|
|
92
|
+
if (major > 3 || (major === 3 && minor >= 11)) return cmd;
|
|
90
93
|
}
|
|
91
94
|
} catch {}
|
|
92
95
|
}
|
|
@@ -109,40 +112,30 @@ function findFreePort(preferred) {
|
|
|
109
112
|
return start;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
// ---- Copy directory recursively ----
|
|
113
115
|
function copyDir(src, dest) {
|
|
114
116
|
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
115
117
|
for (const entry of readdirSync(src)) {
|
|
116
118
|
const srcPath = join(src, entry);
|
|
117
119
|
const destPath = join(dest, entry);
|
|
118
120
|
const stat = statSync(srcPath);
|
|
119
|
-
if (stat.isDirectory())
|
|
120
|
-
|
|
121
|
-
} else {
|
|
122
|
-
copyFileSync(srcPath, destPath);
|
|
123
|
-
}
|
|
121
|
+
if (stat.isDirectory()) copyDir(srcPath, destPath);
|
|
122
|
+
else copyFileSync(srcPath, destPath);
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
// ---- Ensure requests is installed ----
|
|
128
126
|
function ensureRequests(python) {
|
|
129
127
|
try {
|
|
130
128
|
const result = spawnSync(python, ["-c", "import requests; print(requests.__version__)"], { encoding: "utf-8", timeout: 5000 });
|
|
131
|
-
if (result.status === 0 && result.stdout.trim())
|
|
132
|
-
return; // already installed
|
|
133
|
-
}
|
|
129
|
+
if (result.status === 0 && result.stdout.trim()) return;
|
|
134
130
|
} catch {}
|
|
135
|
-
|
|
136
131
|
console.log(c("yellow", "→ installing requests..."));
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} catch {
|
|
132
|
+
const install = spawnSync(python, ["-m", "pip", "install", "requests", "--quiet", "--disable-pip-version-check"], { stdio: "inherit", timeout: 60000 });
|
|
133
|
+
if (install.status !== 0) {
|
|
140
134
|
console.error(c("red", "✗ Failed to install requests. Please run: pip install requests"));
|
|
141
135
|
process.exit(1);
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
138
|
|
|
145
|
-
// ---- Wait for server ----
|
|
146
139
|
function waitForServer(url, maxRetries = 30) {
|
|
147
140
|
return new Promise((resolve, reject) => {
|
|
148
141
|
let retries = 0;
|
|
@@ -165,7 +158,6 @@ function waitForServer(url, maxRetries = 30) {
|
|
|
165
158
|
});
|
|
166
159
|
}
|
|
167
160
|
|
|
168
|
-
// ---- Open browser ----
|
|
169
161
|
function openBrowser(url) {
|
|
170
162
|
const cmds = {
|
|
171
163
|
darwin: ["open", [url]],
|
|
@@ -174,25 +166,18 @@ function openBrowser(url) {
|
|
|
174
166
|
};
|
|
175
167
|
const plat = platform();
|
|
176
168
|
const entry = cmds[plat] || cmds.linux;
|
|
177
|
-
try {
|
|
178
|
-
spawn(entry[0], entry[1], { detached: true, stdio: "ignore" }).unref();
|
|
179
|
-
} catch {}
|
|
169
|
+
try { spawn(entry[0], entry[1], { detached: true, stdio: "ignore" }).unref(); } catch {}
|
|
180
170
|
}
|
|
181
171
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const args = parseArgs();
|
|
185
|
-
|
|
172
|
+
async function launchWebUI(rawArgv) {
|
|
173
|
+
const args = parseArgs(rawArgv);
|
|
186
174
|
if (args.help) { showHelp(); process.exit(0); }
|
|
187
175
|
if (args.version) { console.log(pkg.version); process.exit(0); }
|
|
188
176
|
|
|
189
177
|
const python = findPython();
|
|
190
178
|
const workDir = resolve(args.dir || join(homedir(), ".cybercode"));
|
|
191
|
-
|
|
192
|
-
// Create working directory
|
|
193
179
|
if (!existsSync(workDir)) mkdirSync(workDir, { recursive: true });
|
|
194
180
|
|
|
195
|
-
// Version stamp — re-copy files if package version changed
|
|
196
181
|
const stampPath = join(workDir, ".version");
|
|
197
182
|
const currentVersion = pkg.version;
|
|
198
183
|
let needsCopy = true;
|
|
@@ -201,52 +186,36 @@ async function main() {
|
|
|
201
186
|
if (stamped === currentVersion) needsCopy = false;
|
|
202
187
|
}
|
|
203
188
|
|
|
204
|
-
// Copy bundled files
|
|
205
189
|
const bundledPython = join(__dirname, "..", "python");
|
|
206
190
|
const bundledSkills = join(__dirname, "..", "skills");
|
|
207
|
-
|
|
208
191
|
if (needsCopy) {
|
|
209
192
|
if (existsSync(bundledPython)) copyDir(bundledPython, workDir);
|
|
210
193
|
if (existsSync(bundledSkills)) copyDir(bundledSkills, join(workDir, "skills"));
|
|
211
194
|
writeFileSync(stampPath, currentVersion, "utf-8");
|
|
212
195
|
}
|
|
213
196
|
|
|
214
|
-
// Create mykey.json from template if not exists
|
|
215
197
|
const mykeyPath = join(workDir, "mykey.json");
|
|
216
198
|
if (!existsSync(mykeyPath)) {
|
|
217
199
|
const templatePath = join(__dirname, "..", "templates", "mykey_template.json");
|
|
218
|
-
if (existsSync(templatePath))
|
|
219
|
-
copyFileSync(templatePath, mykeyPath);
|
|
220
|
-
} else {
|
|
221
|
-
writeFileSync(mykeyPath, JSON.stringify({
|
|
222
|
-
llm1: { apikey: "sk-YOUR-KEY", apibase: "https://api.openai.com", model: "gpt-4o", name: "GPT-4o" }
|
|
223
|
-
}, null, 2));
|
|
224
|
-
}
|
|
200
|
+
if (existsSync(templatePath)) copyFileSync(templatePath, mykeyPath);
|
|
225
201
|
}
|
|
226
202
|
|
|
227
|
-
// Check if mykey.json has been configured
|
|
228
203
|
let configured = false;
|
|
229
204
|
try {
|
|
230
205
|
const mykey = JSON.parse(readFileSync(mykeyPath, "utf-8"));
|
|
231
|
-
configured = Object.values(mykey).some(v => v.apikey && !v.apikey.includes("YOUR-"));
|
|
206
|
+
configured = Object.values(mykey).some(v => v.apikey && !String(v.apikey).includes("YOUR-"));
|
|
232
207
|
} catch {}
|
|
233
208
|
|
|
234
|
-
// Ensure requests
|
|
235
209
|
ensureRequests(python);
|
|
236
|
-
|
|
237
|
-
// Find port
|
|
238
210
|
const port = args.port || findFreePort(18600);
|
|
239
211
|
const url = `http://${args.host}:${port}`;
|
|
240
212
|
|
|
241
|
-
// Banner
|
|
242
213
|
console.log();
|
|
243
214
|
console.log(` ${c("bold", c("blue", "╭─────────────────────────────────────────────────╮"))}`);
|
|
244
215
|
console.log(` ${c("bold", c("blue", "│"))} ${c("bold", "cybercode")} ${c("dim", `v${currentVersion}`)} ${c("bold", c("blue", "│"))}`);
|
|
245
216
|
console.log(` ${c("bold", c("blue", "│"))} ${c("dim", "working dir:")} ${workDir.padEnd(34).slice(0, 34)} ${c("bold", c("blue", "│"))}`);
|
|
246
217
|
console.log(` ${c("bold", c("blue", "│"))} ${c("green", `▶ ${url}`)}${" ".repeat(Math.max(0, 33 - url.length))} ${c("bold", c("blue", "│"))}`);
|
|
247
|
-
if (!configured) {
|
|
248
|
-
console.log(` ${c("bold", c("blue", "│"))} ${c("yellow", "⚠ edit mykey.json to add your API key")} ${c("bold", c("blue", "│"))}`);
|
|
249
|
-
}
|
|
218
|
+
if (!configured) console.log(` ${c("bold", c("blue", "│"))} ${c("yellow", "⚠ edit mykey.json to add your API key")} ${c("bold", c("blue", "│"))}`);
|
|
250
219
|
console.log(` ${c("bold", c("blue", "╰─────────────────────────────────────────────────╯"))}`);
|
|
251
220
|
console.log();
|
|
252
221
|
|
|
@@ -254,61 +223,30 @@ async function main() {
|
|
|
254
223
|
console.log(c("yellow", ` ⚠ mykey.json not configured yet.`));
|
|
255
224
|
console.log(c("dim", ` Edit: ${mykeyPath}`));
|
|
256
225
|
console.log(c("dim", ` Add your OpenAI-compatible API key, then restart.`));
|
|
257
|
-
console.log(c("dim", ` Or run: nano ${mykeyPath}`));
|
|
258
226
|
console.log(c("dim", ` The UI will still load and show a setup banner.`));
|
|
259
227
|
console.log();
|
|
260
228
|
}
|
|
261
229
|
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
join(workDir, "webui_codex.py"),
|
|
265
|
-
"--port", String(port),
|
|
266
|
-
"--host", args.host,
|
|
267
|
-
"--llm_no", String(args.llm),
|
|
268
|
-
];
|
|
269
|
-
|
|
270
|
-
const child = spawn(python, pyArgs, {
|
|
271
|
-
cwd: workDir,
|
|
272
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
273
|
-
env: { ...process.env, PYTHONUNBUFFERED: "1" },
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// Pipe server output
|
|
230
|
+
const pyArgs = [join(workDir, "webui_codex.py"), "--port", String(port), "--host", args.host, "--llm_no", String(args.llm)];
|
|
231
|
+
const child = spawn(python, pyArgs, { cwd: workDir, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PYTHONUNBUFFERED: "1" } });
|
|
277
232
|
child.stdout.on("data", (data) => process.stdout.write(data));
|
|
278
233
|
child.stderr.on("data", (data) => process.stderr.write(c("dim", data.toString())));
|
|
234
|
+
child.on("error", (err) => { console.error(c("red", `✗ Failed to start: ${err.message}`)); process.exit(1); });
|
|
235
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
279
236
|
|
|
280
|
-
child.on("error", (err) => {
|
|
281
|
-
console.error(c("red", `✗ Failed to start: ${err.message}`));
|
|
282
|
-
process.exit(1);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
child.on("exit", (code) => {
|
|
286
|
-
process.exit(code || 0);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// Wait for server, then open browser
|
|
290
237
|
if (!args.noBrowser) {
|
|
291
|
-
try {
|
|
292
|
-
|
|
293
|
-
openBrowser(url);
|
|
294
|
-
} catch {
|
|
295
|
-
// Server might still be starting; the user can open manually
|
|
296
|
-
console.log(c("dim", ` (browser auto-open skipped — open ${url} manually)`));
|
|
297
|
-
}
|
|
238
|
+
try { await waitForServer(`${url}/api/status`, 40); openBrowser(url); }
|
|
239
|
+
catch { console.log(c("dim", ` (browser auto-open skipped — open ${url} manually)`)); }
|
|
298
240
|
}
|
|
299
241
|
|
|
300
|
-
|
|
301
|
-
process.on("
|
|
302
|
-
child.kill("SIGINT");
|
|
303
|
-
process.exit(0);
|
|
304
|
-
});
|
|
305
|
-
process.on("SIGTERM", () => {
|
|
306
|
-
child.kill("SIGTERM");
|
|
307
|
-
process.exit(0);
|
|
308
|
-
});
|
|
242
|
+
process.on("SIGINT", () => { child.kill("SIGINT"); process.exit(0); });
|
|
243
|
+
process.on("SIGTERM", () => { child.kill("SIGTERM"); process.exit(0); });
|
|
309
244
|
}
|
|
310
245
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
});
|
|
246
|
+
const argv = process.argv.slice(2);
|
|
247
|
+
const { command, args } = splitCommand(argv);
|
|
248
|
+
if (command === "webui") {
|
|
249
|
+
launchWebUI(args).catch((err) => { console.error(c("red", `✗ ${err.message}`)); process.exit(1); });
|
|
250
|
+
} else {
|
|
251
|
+
showHelp();
|
|
252
|
+
}
|
package/package.json
CHANGED
package/python/webui_codex.py
CHANGED
|
@@ -567,7 +567,7 @@ def main():
|
|
|
567
567
|
|
|
568
568
|
url = f"http://{args.host}:{args.port}"
|
|
569
569
|
print(f"\n ╭───────────────────────────────────────────╮")
|
|
570
|
-
print(f" │
|
|
570
|
+
print(f" │ cybercode · self-contained agent │")
|
|
571
571
|
print(f" │ open {url:<33}│")
|
|
572
572
|
print(f" │ Ctrl+C to stop │")
|
|
573
573
|
print(f" ╰───────────────────────────────────────────╯\n")
|