cybercode-cli 1.0.0
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/LICENSE +53 -0
- package/README.md +174 -0
- package/bin/cli.mjs +314 -0
- package/package.json +44 -0
- package/python/agent_core.py +1029 -0
- package/python/webui_codex.html +673 -0
- package/python/webui_codex.py +583 -0
- package/skills/faceless-explainer.md +194 -0
- package/skills/general-video.md +141 -0
- package/skills/hyperframes-animation.md +82 -0
- package/skills/hyperframes-cli.md +109 -0
- package/skills/hyperframes-core.md +78 -0
- package/skills/hyperframes-creative.md +68 -0
- package/skills/hyperframes-media.md +81 -0
- package/skills/hyperframes-registry.md +101 -0
- package/skills/hyperframes.md +144 -0
- package/skills/motion-graphics.md +170 -0
- package/skills/product-launch-video.md +199 -0
- package/skills/website-to-video.md +141 -0
- package/templates/mykey_template.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 webui_codex contributors
|
|
4
|
+
|
|
5
|
+
This project's agent core (agent_core.py) was developed with reference to the
|
|
6
|
+
GenericAgent project (https://github.com/lsdefine/GenericAgent), which is also
|
|
7
|
+
licensed under the MIT License:
|
|
8
|
+
|
|
9
|
+
MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2025 lsdefine
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
|
|
31
|
+
The HyperFrames skills bundled in the skills/ directory are from the
|
|
32
|
+
HyperFrames project (https://github.com/heygen-com/hyperframes), also MIT
|
|
33
|
+
licensed. See individual skill files for their original copyright notices.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
38
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
39
|
+
in the Software without restriction, including without limitation the rights
|
|
40
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
41
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
42
|
+
furnished to do so, subject to the following conditions:
|
|
43
|
+
|
|
44
|
+
The above copyright notice and this permission notice shall be included in all
|
|
45
|
+
copies or substantial portions of the Software.
|
|
46
|
+
|
|
47
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
48
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
49
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
50
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
51
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
52
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
53
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# webui_codex — Codex-dark Web UI with a Built-in Self-Evolving Agent
|
|
2
|
+
|
|
3
|
+
A standalone, self-contained web UI + agent framework styled after the
|
|
4
|
+
Codex-dark interface. **No external agent dependency** — the entire agent
|
|
5
|
+
core (LLM client, agent loop, 9 atomic tools, layered memory) ships inside
|
|
6
|
+
`agent_core.py` and runs from a single directory.
|
|
7
|
+
|
|
8
|
+
   
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx cybercode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That's it. On first run it:
|
|
17
|
+
1. Finds Python 3.11+ on your system
|
|
18
|
+
2. Installs `requests` if missing
|
|
19
|
+
3. Copies the bundled agent + skills to `~/.cybercode/`
|
|
20
|
+
4. Creates a `mykey.json` template (edit it with your API key)
|
|
21
|
+
5. Starts the web UI and opens your browser
|
|
22
|
+
|
|
23
|
+
## Attribution & License
|
|
24
|
+
|
|
25
|
+
The agent core architecture in `agent_core.py` was developed with reference to
|
|
26
|
+
[**GenericAgent**](https://github.com/lsdefine/GenericAgent) by lsdefine
|
|
27
|
+
(MIT License, Copyright © 2025 lsdefine). We gratefully acknowledge the
|
|
28
|
+
GenericAgent project — its ~100-line agent loop, 9-atomic-tool design, and
|
|
29
|
+
layered memory concept directly inspired this implementation.
|
|
30
|
+
|
|
31
|
+
The bundled HyperFrames skills (`skills/`) originate from
|
|
32
|
+
[**HyperFrames**](https://github.com/heygen-com/hyperframes) by HeyGen
|
|
33
|
+
(MIT License).
|
|
34
|
+
|
|
35
|
+
See `LICENSE` for the full MIT license text, including the GenericAgent
|
|
36
|
+
copyright notice as required by its license terms.
|
|
37
|
+
|
|
38
|
+
## What it is
|
|
39
|
+
|
|
40
|
+
`webui_codex.py` boots a self-contained `Agent` (from `agent_core.py`), serves
|
|
41
|
+
`webui_codex.html` at `/`, and exposes a JSON + SSE API the page talks to.
|
|
42
|
+
The UI keeps the Codex-dark look — blue radial-gradient desktop, traffic-light
|
|
43
|
+
window chrome, dark sidebar with threads + skills, centered *Let's build*
|
|
44
|
+
empty state, pill LLM switcher, rounded composer with Local/Worktree/Cloud
|
|
45
|
+
modes — but every control is wired to the built-in agent.
|
|
46
|
+
|
|
47
|
+
**Standalone: no GenericAgent dependency.** The agent core (`agent_core.py`)
|
|
48
|
+
is a from-scratch reimplementation that ships in this repo. You do **not** need
|
|
49
|
+
GenericAgent installed. The only runtime dependency is `requests` (for the LLM
|
|
50
|
+
HTTP client); everything else uses the Python stdlib.
|
|
51
|
+
|
|
52
|
+
## Files
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
webui-codex/
|
|
56
|
+
├── package.json # npm package config (bin, files, metadata)
|
|
57
|
+
├── bin/
|
|
58
|
+
│ └── cli.mjs # Node.js launcher (finds Python, bootstraps, opens browser)
|
|
59
|
+
├── python/
|
|
60
|
+
│ ├── agent_core.py # Self-contained agent core (LLM client + loop + 9 tools + memory)
|
|
61
|
+
│ ├── webui_codex.py # stdlib HTTP server + SSE + API
|
|
62
|
+
│ └── webui_codex.html # Codex-dark UI (single file, no build step)
|
|
63
|
+
├── skills/ # HyperFrames video skills (12 files, from HeyGen)
|
|
64
|
+
├── templates/
|
|
65
|
+
│ └── mykey_template.json # API key template (auto-copied on first run)
|
|
66
|
+
├── LICENSE # MIT (includes GenericAgent + HyperFrames copyright notices)
|
|
67
|
+
└── README.md # This file
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Manual (without npx)
|
|
71
|
+
|
|
72
|
+
If you prefer to run the Python server directly:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. Configure your LLM (any OpenAI-compatible endpoint)
|
|
76
|
+
cat > mykey.json << 'EOF'
|
|
77
|
+
{
|
|
78
|
+
"llm1": {
|
|
79
|
+
"apikey": "sk-your-key-here",
|
|
80
|
+
"apibase": "https://api.openai.com",
|
|
81
|
+
"model": "gpt-4o",
|
|
82
|
+
"name": "GPT-4o"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
EOF
|
|
86
|
+
|
|
87
|
+
# 2. Install requests (only dependency)
|
|
88
|
+
pip install requests
|
|
89
|
+
|
|
90
|
+
# 3. Run
|
|
91
|
+
python python/webui_codex.py # http://127.0.0.1:18600
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Supported LLM providers
|
|
95
|
+
|
|
96
|
+
Any OpenAI-compatible chat completions endpoint works:
|
|
97
|
+
- **OpenAI** (GPT-4o, o1, o3, etc.)
|
|
98
|
+
- **DeepSeek**
|
|
99
|
+
- **Kimi / Moonshot**
|
|
100
|
+
- **MiniMax**
|
|
101
|
+
- **Local models** via Ollama, LM Studio, vLLM, etc.
|
|
102
|
+
- **Anthropic Claude** via OpenAI-compatible proxies
|
|
103
|
+
|
|
104
|
+
## What works
|
|
105
|
+
|
|
106
|
+
| UI element | Wired to |
|
|
107
|
+
| :--- | :--- |
|
|
108
|
+
| **Composer + send** | `agent.put_task()` → SSE stream of deltas + final `done` |
|
|
109
|
+
| **Stop button / red send toggle** | `agent.abort()` |
|
|
110
|
+
| **New thread** | `/api/new` → clears conversation history |
|
|
111
|
+
| **Thread list (sidebar)** | `/api/sessions` → scans `temp/model_responses/` logs |
|
|
112
|
+
| **Click a thread** | `/api/continue` → restores that session + replays bubbles |
|
|
113
|
+
| **LLM pill + dropdown** | `/api/llm` → `agent.next_llm(idx)` |
|
|
114
|
+
| **Skills panel (sidebar)** | `/api/skills` → lists `memory/` SOPs and skill files |
|
|
115
|
+
| **Suggest tasks** | prefills the autonomous-planning prompt |
|
|
116
|
+
| **Status dot** | `agent.is_running` polled every 4s |
|
|
117
|
+
| **Tool / file refs** | `🛠️ Tool: \`name\`` lines → tool chips; `[FILE:path]` → clickable file chips |
|
|
118
|
+
| **Turn folding** | `**LLM Running (Turn N) ...**` markers → collapsible turn sections |
|
|
119
|
+
| **Make video button** | Injects HyperFrames skill preamble + `/video` command |
|
|
120
|
+
| **Video gallery** | `/api/videos` → scans for rendered `.mp4` files |
|
|
121
|
+
| **Video playback** | `/api/video/<path>` → streams with Range support (seekable) |
|
|
122
|
+
|
|
123
|
+
## The 9 Atomic Tools
|
|
124
|
+
|
|
125
|
+
The agent core exposes exactly 9 tools (same design philosophy as GenericAgent):
|
|
126
|
+
|
|
127
|
+
| Tool | Function |
|
|
128
|
+
| :--- | :--- |
|
|
129
|
+
| `code_run` | Execute Python or shell scripts |
|
|
130
|
+
| `file_read` | Read files with line numbers, keyword search |
|
|
131
|
+
| `file_write` | Create / overwrite / append files |
|
|
132
|
+
| `file_patch` | Patch a unique text block in a file |
|
|
133
|
+
| `web_scan` | Fetch and simplify web pages (urllib-based) |
|
|
134
|
+
| `web_execute_js` | Execute JS in a browser (requires TMWebDriver; degrades gracefully) |
|
|
135
|
+
| `ask_user` | Interrupt to ask the user a question |
|
|
136
|
+
| `update_working_checkpoint` | Short-term working memory notepad |
|
|
137
|
+
| `start_long_term_update` | Distill experience into long-term memory |
|
|
138
|
+
|
|
139
|
+
## HyperFrames — Built-in Video Generation
|
|
140
|
+
|
|
141
|
+
The HyperFrames skill pack (12 files) is bundled in `skills/`. Click the
|
|
142
|
+
**Make video** button in the sidebar, or type `/video <description>`. The agent
|
|
143
|
+
reads the bundled skills and uses the `npx hyperframes` CLI to render HTML
|
|
144
|
+
compositions into `.mp4` files. Rendered videos appear in the sidebar gallery
|
|
145
|
+
and can be played inline.
|
|
146
|
+
|
|
147
|
+
## API Reference
|
|
148
|
+
|
|
149
|
+
| Method | Path | Description |
|
|
150
|
+
| :--- | :--- | :--- |
|
|
151
|
+
| GET | `/` | The web UI |
|
|
152
|
+
| GET | `/api/status` | Agent status: running, LLM, history |
|
|
153
|
+
| GET | `/api/sessions` | Recoverable sessions (log files) |
|
|
154
|
+
| GET | `/api/skills` | Agent memory/SOP files |
|
|
155
|
+
| GET | `/api/messages?path=...` | Replay messages from a session log |
|
|
156
|
+
| GET | `/api/hyperframes` | List bundled HyperFrames skills |
|
|
157
|
+
| GET | `/api/hyperframes/<name>` | Raw skill markdown |
|
|
158
|
+
| GET | `/api/videos` | Rendered `.mp4` files |
|
|
159
|
+
| GET | `/api/video/<path>` | Stream a video (Range-supported) |
|
|
160
|
+
| POST | `/api/chat` | SSE stream: delta / done / error |
|
|
161
|
+
| POST | `/api/llm` | Switch LLM by index |
|
|
162
|
+
| POST | `/api/stop` | Abort current task |
|
|
163
|
+
| POST | `/api/new` | Start fresh conversation |
|
|
164
|
+
| POST | `/api/continue` | Restore session N |
|
|
165
|
+
| POST | `/api/btw` | Side question (while task runs) |
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT License — see `LICENSE` for full text.
|
|
170
|
+
|
|
171
|
+
This project's agent core was developed with reference to
|
|
172
|
+
[GenericAgent](https://github.com/lsdefine/GenericAgent) (MIT, Copyright © 2025
|
|
173
|
+
lsdefine). The GenericAgent copyright notice is included in `LICENSE` as
|
|
174
|
+
required by its license terms. HyperFrames skills are MIT-licensed by HeyGen.
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cybercode CLI launcher — bootstraps a Python env and starts the web UI.
|
|
3
|
+
// Designed for `npx cybercode` one-click usage.
|
|
4
|
+
|
|
5
|
+
import { spawn, spawnSync, execSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join, dirname, resolve } from "node:path";
|
|
8
|
+
import { homedir, platform } from "node:os";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pkg = require("../package.json");
|
|
15
|
+
|
|
16
|
+
const COLORS = {
|
|
17
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
18
|
+
green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m",
|
|
19
|
+
red: "\x1b[31m", cyan: "\x1b[36m",
|
|
20
|
+
};
|
|
21
|
+
const c = (color, text) => `${COLORS[color] || ""}${text}${COLORS.reset}`;
|
|
22
|
+
|
|
23
|
+
// ---- Parse CLI args ----
|
|
24
|
+
function parseArgs() {
|
|
25
|
+
const args = { port: null, host: "127.0.0.1", dir: null, noBrowser: false, llm: 0, help: false, version: false };
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
const a = argv[i];
|
|
29
|
+
if (a === "-h" || a === "--help") args.help = true;
|
|
30
|
+
else if (a === "-V" || a === "--version") args.version = true;
|
|
31
|
+
else if (a === "-p" || a === "--port") args.port = parseInt(argv[++i], 10);
|
|
32
|
+
else if (a === "--host") args.host = argv[++i];
|
|
33
|
+
else if (a === "--dir") args.dir = argv[++i];
|
|
34
|
+
else if (a === "--no-browser") args.noBrowser = true;
|
|
35
|
+
else if (a === "--llm") args.llm = parseInt(argv[++i], 10);
|
|
36
|
+
else if (a === "--open") args.noBrowser = false;
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function showHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
${c("bold", "cybercode")} ${c("dim", `v${pkg.version}`)} — Codex-dark web UI with a built-in self-evolving agent
|
|
44
|
+
|
|
45
|
+
${c("bold", "USAGE")}
|
|
46
|
+
npx cybercode # start with defaults
|
|
47
|
+
npx cybercode --port 8080 # custom port
|
|
48
|
+
npx cybercode --host 0.0.0.0 # listen on all interfaces
|
|
49
|
+
npx cybercode --no-browser # don't auto-open browser
|
|
50
|
+
npx cybercode --dir ~/my-agent # custom working directory
|
|
51
|
+
npx cybercode --llm 1 # start on 2nd configured LLM
|
|
52
|
+
|
|
53
|
+
${c("bold", "OPTIONS")}
|
|
54
|
+
-p, --port <num> Port (default: auto-find free port near 18600)
|
|
55
|
+
--host <addr> Bind address (default: 127.0.0.1)
|
|
56
|
+
--dir <path> Working directory (default: ~/.cybercode)
|
|
57
|
+
--llm <num> LLM index to start with (default: 0)
|
|
58
|
+
--no-browser Don't auto-open the browser
|
|
59
|
+
-h, --help Show this help
|
|
60
|
+
-V, --version Show version
|
|
61
|
+
|
|
62
|
+
${c("bold", "FIRST RUN")}
|
|
63
|
+
On first launch, a ${c("cyan", "mykey.json")} template is created in the working
|
|
64
|
+
directory. Edit it with your LLM API keys (OpenAI, DeepSeek, etc.), then
|
|
65
|
+
restart. Any OpenAI-compatible endpoint works.
|
|
66
|
+
|
|
67
|
+
${c("bold", "ATTRIBUTION")}
|
|
68
|
+
Agent core inspired by GenericAgent (MIT). HyperFrames skills from HeyGen (MIT).
|
|
69
|
+
See LICENSE for details.
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---- Find Python 3.11+ ----
|
|
74
|
+
function findPython() {
|
|
75
|
+
const candidates = process.env.WEBUI_CODEX_PYTHON
|
|
76
|
+
? [process.env.WEBUI_CODEX_PYTHON]
|
|
77
|
+
: ["python3", "python3.12", "python3.11", "python"];
|
|
78
|
+
|
|
79
|
+
for (const cmd of candidates) {
|
|
80
|
+
try {
|
|
81
|
+
const result = spawnSync(cmd, ["--version"], { encoding: "utf-8", timeout: 5000 });
|
|
82
|
+
const output = (result.stdout || "") + (result.stderr || "");
|
|
83
|
+
const match = output.match(/Python (\d+)\.(\d+)/);
|
|
84
|
+
if (match) {
|
|
85
|
+
const major = parseInt(match[1], 10);
|
|
86
|
+
const minor = parseInt(match[2], 10);
|
|
87
|
+
if (major > 3 || (major === 3 && minor >= 11)) {
|
|
88
|
+
return cmd;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.error(c("red", "✗ Python 3.11+ not found."));
|
|
95
|
+
console.error(c("dim", " Install Python 3.11 or 3.12, or set WEBUI_CODEX_PYTHON env var."));
|
|
96
|
+
console.error(c("dim", " Download: https://www.python.org/downloads/"));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- Find a free port ----
|
|
101
|
+
function findFreePort(preferred) {
|
|
102
|
+
const start = preferred || 18600;
|
|
103
|
+
for (let port = start; port <= start + 100; port++) {
|
|
104
|
+
try {
|
|
105
|
+
const result = spawnSync("python3", ["-c", `import socket; s=socket.socket(); s.bind(("127.0.0.1",${port})); s.close(); print(${port})`], { encoding: "utf-8", timeout: 3000 });
|
|
106
|
+
if (result.stdout && result.stdout.trim()) return parseInt(result.stdout.trim(), 10);
|
|
107
|
+
} catch {}
|
|
108
|
+
}
|
|
109
|
+
return start;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---- Copy directory recursively ----
|
|
113
|
+
function copyDir(src, dest) {
|
|
114
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
115
|
+
for (const entry of readdirSync(src)) {
|
|
116
|
+
const srcPath = join(src, entry);
|
|
117
|
+
const destPath = join(dest, entry);
|
|
118
|
+
const stat = statSync(srcPath);
|
|
119
|
+
if (stat.isDirectory()) {
|
|
120
|
+
copyDir(srcPath, destPath);
|
|
121
|
+
} else {
|
|
122
|
+
copyFileSync(srcPath, destPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---- Ensure requests is installed ----
|
|
128
|
+
function ensureRequests(python) {
|
|
129
|
+
try {
|
|
130
|
+
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
|
+
}
|
|
134
|
+
} catch {}
|
|
135
|
+
|
|
136
|
+
console.log(c("yellow", "→ installing requests..."));
|
|
137
|
+
try {
|
|
138
|
+
spawnSync(python, ["-m", "pip", "install", "requests", "--quiet", "--disable-pip-version-check"], { stdio: "inherit", timeout: 60000 });
|
|
139
|
+
} catch {
|
|
140
|
+
console.error(c("red", "✗ Failed to install requests. Please run: pip install requests"));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---- Wait for server ----
|
|
146
|
+
function waitForServer(url, maxRetries = 30) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
let retries = 0;
|
|
149
|
+
const check = () => {
|
|
150
|
+
import("node:http").then(({ default: http }) => {
|
|
151
|
+
const req = http.get(url, (res) => {
|
|
152
|
+
res.resume();
|
|
153
|
+
if (res.statusCode === 200) resolve();
|
|
154
|
+
else if (++retries < maxRetries) setTimeout(check, 300);
|
|
155
|
+
else reject(new Error("server unhealthy"));
|
|
156
|
+
});
|
|
157
|
+
req.on("error", () => {
|
|
158
|
+
if (++retries < maxRetries) setTimeout(check, 300);
|
|
159
|
+
else reject(new Error("server not responding"));
|
|
160
|
+
});
|
|
161
|
+
req.setTimeout(2000, () => { req.destroy(); if (++retries < maxRetries) setTimeout(check, 300); else reject(new Error("timeout")); });
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
check();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---- Open browser ----
|
|
169
|
+
function openBrowser(url) {
|
|
170
|
+
const cmds = {
|
|
171
|
+
darwin: ["open", [url]],
|
|
172
|
+
win32: ["cmd", ["/c", "start", url]],
|
|
173
|
+
linux: ["xdg-open", [url]],
|
|
174
|
+
};
|
|
175
|
+
const plat = platform();
|
|
176
|
+
const entry = cmds[plat] || cmds.linux;
|
|
177
|
+
try {
|
|
178
|
+
spawn(entry[0], entry[1], { detached: true, stdio: "ignore" }).unref();
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---- Main ----
|
|
183
|
+
async function main() {
|
|
184
|
+
const args = parseArgs();
|
|
185
|
+
|
|
186
|
+
if (args.help) { showHelp(); process.exit(0); }
|
|
187
|
+
if (args.version) { console.log(pkg.version); process.exit(0); }
|
|
188
|
+
|
|
189
|
+
const python = findPython();
|
|
190
|
+
const workDir = resolve(args.dir || join(homedir(), ".cybercode"));
|
|
191
|
+
|
|
192
|
+
// Create working directory
|
|
193
|
+
if (!existsSync(workDir)) mkdirSync(workDir, { recursive: true });
|
|
194
|
+
|
|
195
|
+
// Version stamp — re-copy files if package version changed
|
|
196
|
+
const stampPath = join(workDir, ".version");
|
|
197
|
+
const currentVersion = pkg.version;
|
|
198
|
+
let needsCopy = true;
|
|
199
|
+
if (existsSync(stampPath)) {
|
|
200
|
+
const stamped = readFileSync(stampPath, "utf-8").trim();
|
|
201
|
+
if (stamped === currentVersion) needsCopy = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Copy bundled files
|
|
205
|
+
const bundledPython = join(__dirname, "..", "python");
|
|
206
|
+
const bundledSkills = join(__dirname, "..", "skills");
|
|
207
|
+
|
|
208
|
+
if (needsCopy) {
|
|
209
|
+
if (existsSync(bundledPython)) copyDir(bundledPython, workDir);
|
|
210
|
+
if (existsSync(bundledSkills)) copyDir(bundledSkills, join(workDir, "skills"));
|
|
211
|
+
writeFileSync(stampPath, currentVersion, "utf-8");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create mykey.json from template if not exists
|
|
215
|
+
const mykeyPath = join(workDir, "mykey.json");
|
|
216
|
+
if (!existsSync(mykeyPath)) {
|
|
217
|
+
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
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check if mykey.json has been configured
|
|
228
|
+
let configured = false;
|
|
229
|
+
try {
|
|
230
|
+
const mykey = JSON.parse(readFileSync(mykeyPath, "utf-8"));
|
|
231
|
+
configured = Object.values(mykey).some(v => v.apikey && !v.apikey.includes("YOUR-"));
|
|
232
|
+
} catch {}
|
|
233
|
+
|
|
234
|
+
// Ensure requests
|
|
235
|
+
ensureRequests(python);
|
|
236
|
+
|
|
237
|
+
// Find port
|
|
238
|
+
const port = args.port || findFreePort(18600);
|
|
239
|
+
const url = `http://${args.host}:${port}`;
|
|
240
|
+
|
|
241
|
+
// Banner
|
|
242
|
+
console.log();
|
|
243
|
+
console.log(` ${c("bold", c("blue", "╭─────────────────────────────────────────────────╮"))}`);
|
|
244
|
+
console.log(` ${c("bold", c("blue", "│"))} ${c("bold", "cybercode")} ${c("dim", `v${currentVersion}`)} ${c("bold", c("blue", "│"))}`);
|
|
245
|
+
console.log(` ${c("bold", c("blue", "│"))} ${c("dim", "working dir:")} ${workDir.padEnd(34).slice(0, 34)} ${c("bold", c("blue", "│"))}`);
|
|
246
|
+
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
|
+
}
|
|
250
|
+
console.log(` ${c("bold", c("blue", "╰─────────────────────────────────────────────────╯"))}`);
|
|
251
|
+
console.log();
|
|
252
|
+
|
|
253
|
+
if (!configured) {
|
|
254
|
+
console.log(c("yellow", ` ⚠ mykey.json not configured yet.`));
|
|
255
|
+
console.log(c("dim", ` Edit: ${mykeyPath}`));
|
|
256
|
+
console.log(c("dim", ` Add your OpenAI-compatible API key, then restart.`));
|
|
257
|
+
console.log(c("dim", ` Or run: nano ${mykeyPath}`));
|
|
258
|
+
console.log(c("dim", ` The UI will still load and show a setup banner.`));
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Launch Python server
|
|
263
|
+
const pyArgs = [
|
|
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
|
|
277
|
+
child.stdout.on("data", (data) => process.stdout.write(data));
|
|
278
|
+
child.stderr.on("data", (data) => process.stderr.write(c("dim", data.toString())));
|
|
279
|
+
|
|
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
|
+
if (!args.noBrowser) {
|
|
291
|
+
try {
|
|
292
|
+
await waitForServer(`${url}/api/status`, 40);
|
|
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
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Handle Ctrl+C
|
|
301
|
+
process.on("SIGINT", () => {
|
|
302
|
+
child.kill("SIGINT");
|
|
303
|
+
process.exit(0);
|
|
304
|
+
});
|
|
305
|
+
process.on("SIGTERM", () => {
|
|
306
|
+
child.kill("SIGTERM");
|
|
307
|
+
process.exit(0);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main().catch((err) => {
|
|
312
|
+
console.error(c("red", `✗ ${err.message}`));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cybercode-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Codex-dark web UI with a built-in self-evolving agent + HyperFrames video skills",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"cybercode": "bin/cli.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"python/",
|
|
15
|
+
"skills/",
|
|
16
|
+
"templates/",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"agent",
|
|
25
|
+
"ai",
|
|
26
|
+
"llm",
|
|
27
|
+
"codex",
|
|
28
|
+
"web-ui",
|
|
29
|
+
"automation",
|
|
30
|
+
"self-evolving",
|
|
31
|
+
"hyperframes",
|
|
32
|
+
"video-generation"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": ""
|
|
38
|
+
},
|
|
39
|
+
"homepage": "",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"start": "node bin/cli.mjs",
|
|
42
|
+
"prepublishOnly": "node -e \"console.log('Publishing cybercode...')\""
|
|
43
|
+
}
|
|
44
|
+
}
|