codexpanel 0.0.1 → 0.1.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # codexpanel
2
+
3
+ codexpanel is a mobile control plane for managing local Codex work from a phone. It provides a Next.js web console, relay service, desktop agent, PostgreSQL-backed durable storage, shared protocol package, and reusable UI package in one monorepo.
4
+
5
+ ## Workspace
6
+
7
+ - `apps/web`: Next.js phone console plus static admin, device setup, and project docs
8
+ - `apps/relay`: HTTP/SSE/WSS relay service, PostgreSQL/SQLite storage, and device onboarding scripts
9
+ - `apps/agent`: desktop-side agent entrypoint
10
+ - `packages/protocol`: shared schemas and transport helpers
11
+ - `packages/codex-app-server`: Codex app-server connector bindings
12
+ - `packages/ui`: shared React UI primitives
13
+
14
+ Human reference files, local databases, deployment bundles, server handoff notes, and old demo artifacts live outside the production tree in `files_for_human/` and are ignored by Git.
15
+
16
+ ## Commands
17
+
18
+ ```powershell
19
+ npm run check
20
+ npm run build
21
+ npm run smoke:storage
22
+ npm run test:e2e
23
+ ```
24
+
25
+ ## Desktop agent install
26
+
27
+ The public npm entrypoint is the primary one-line installer for Windows desktop agents:
28
+
29
+ ```powershell
30
+ npx -y codexpanel
31
+ ```
32
+
33
+ By default it downloads the Windows bootstrap script from `https://jd.6a.gs/agent/bootstrap.ps1`, installs or updates the desktop agent under `%LOCALAPPDATA%\CodexPanelAgent`, and starts the agent against the production relay.
34
+
35
+ If the relay is protected, pass `--access-code <code>` or set `CODEXPANEL_ACCESS_CODE` before running the installer.
36
+
37
+ Use `--relay` for local or private relay verification:
38
+
39
+ ```powershell
40
+ npx -y codexpanel --relay http://127.0.0.1:4871
41
+ ```
42
+
43
+ Useful options include `--user-id`, `--device-id`, `--device-name`, `--workspace`, `--access-code`, `--no-autostart`, `--no-tunnel`, `--dry-run`, and `--print-command`.
44
+
45
+ ## Storage and deployment
46
+
47
+ The relay uses `CODEXPANEL_DATABASE_URL` or `DATABASE_URL` for PostgreSQL. If neither is set, it falls back to the local SQLite file at `CODEXPANEL_DB_FILE` for development only. Production startup rejects accidental SQLite fallback unless `CODEXPANEL_ALLOW_SQLITE_IN_PRODUCTION=true` is explicitly set for an emergency.
48
+
49
+ `npm run smoke:storage` always verifies the SQLite development fallback and the production guard. To verify the PostgreSQL path, set `CODEXPANEL_STORAGE_SMOKE_DATABASE_URL` to a disposable PostgreSQL database URL before running it; the smoke test rewrites its storage tables.
50
+
51
+ `docker-compose.yml` starts PostgreSQL and the relay together. The auto-deploy script pulls the configured branch, rebuilds Docker Compose, and waits for `/api/health` before declaring the update complete.
52
+
53
+ ## Architecture guardrails
54
+
55
+ Read [docs/CTO_ARCHITECTURE_DIRECTIVE.md](docs/CTO_ARCHITECTURE_DIRECTIVE.md) before changing Next.js, relay, storage, deployment, auth, protocol, or agent behavior.
56
+
57
+ - API compatibility is tracked in [docs/api-compatibility-matrix.md](docs/api-compatibility-matrix.md).
58
+ - `npm run check` runs `npm run scan:encoding` before workspace checks.
59
+ - Storage code lives under `apps/relay/src/storage/`; UI code must not import backend or database modules.
60
+
61
+ See [docs/git-workflow.md](docs/git-workflow.md) for the day-to-day Git and GitHub workflow.
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const http = require("http");
6
+ const https = require("https");
7
+ const os = require("os");
8
+ const path = require("path");
9
+ const { spawnSync } = require("child_process");
10
+
11
+ const VERSION = "0.1.0";
12
+ const DEFAULT_RELAY_URL = "https://jd.6a.gs";
13
+
14
+ function usage() {
15
+ return `
16
+ CodexPanel desktop agent installer
17
+
18
+ Usage:
19
+ npx -y codexpanel [install] [options]
20
+ npx -y codexpanel --relay http://127.0.0.1:4871
21
+
22
+ Options:
23
+ --relay, --url <url> Relay URL. Defaults to CODEXPANEL_RELAY_URL or ${DEFAULT_RELAY_URL}
24
+ --user, --user-id <id> CodexPanel user id. Defaults to CODEXPANEL_USER_ID or demo_user_a
25
+ --device, --device-id <id> Stable device id. Defaults to the relay bootstrap script's generated id
26
+ --name, --device-name <name> Device display name
27
+ --workspace, --cwd <path> Default workspace for Codex Desktop/app-server
28
+ --access-code <code> Access code for protected relay deployments
29
+ --mode <mode> Agent mode. Defaults to desktop-agent
30
+ --app-server-listen <url> Local Codex app-server WebSocket URL
31
+ --no-autostart Do not enable Windows login autostart
32
+ --no-tunnel Disable Cloudflare WSS direct tunnel
33
+ --print-command Print the equivalent PowerShell bootstrap command
34
+ --dry-run Print resolved installer details without running PowerShell
35
+ -h, --help Show help
36
+ -v, --version Show version
37
+ `.trim();
38
+ }
39
+
40
+ function parseArgs(argv) {
41
+ const args = [...argv];
42
+ const options = {
43
+ command: "install",
44
+ relayUrl: process.env.CODEXPANEL_RELAY_URL || process.env.BRIDGECODEX_RELAY_URL || DEFAULT_RELAY_URL,
45
+ userId: process.env.CODEXPANEL_USER_ID || process.env.BRIDGECODEX_USER_ID || "demo_user_a",
46
+ deviceId: process.env.CODEXPANEL_DEVICE_ID || process.env.BRIDGECODEX_DEVICE_ID || "",
47
+ deviceName: process.env.CODEXPANEL_DEVICE_NAME || process.env.BRIDGECODEX_DEVICE_NAME || "",
48
+ workspace: process.env.CODEXPANEL_WORKSPACE || process.env.BRIDGECODEX_WORKSPACE || "",
49
+ accessCode: process.env.CODEXPANEL_ACCESS_CODE || process.env.BRIDGECODEX_ACCESS_CODE || "",
50
+ mode: process.env.CODEXPANEL_AGENT_MODE || process.env.BRIDGECODEX_AGENT_MODE || "desktop-agent",
51
+ appServerListen: process.env.CODEX_APP_SERVER_LISTEN || "",
52
+ autoStart: true,
53
+ tunnelEnabled: true,
54
+ dryRun: false,
55
+ printCommand: false,
56
+ keepScript: false,
57
+ };
58
+
59
+ const readValue = (flag) => {
60
+ const value = args.shift();
61
+ if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
62
+ return value;
63
+ };
64
+
65
+ while (args.length) {
66
+ const arg = args.shift();
67
+ if (!arg) continue;
68
+ if (arg === "install" || arg === "setup") {
69
+ options.command = "install";
70
+ } else if (arg === "help") {
71
+ options.help = true;
72
+ } else if (arg === "version") {
73
+ options.version = true;
74
+ } else if (arg === "-h" || arg === "--help") {
75
+ options.help = true;
76
+ } else if (arg === "-v" || arg === "--version") {
77
+ options.version = true;
78
+ } else if (arg === "--relay" || arg === "--url") {
79
+ options.relayUrl = readValue(arg);
80
+ } else if (arg === "--user" || arg === "--user-id") {
81
+ options.userId = readValue(arg);
82
+ } else if (arg === "--device" || arg === "--device-id") {
83
+ options.deviceId = readValue(arg);
84
+ } else if (arg === "--name" || arg === "--device-name") {
85
+ options.deviceName = readValue(arg);
86
+ } else if (arg === "--workspace" || arg === "--cwd") {
87
+ options.workspace = readValue(arg);
88
+ } else if (arg === "--access-code") {
89
+ options.accessCode = readValue(arg);
90
+ } else if (arg === "--mode") {
91
+ options.mode = readValue(arg);
92
+ } else if (arg === "--app-server-listen") {
93
+ options.appServerListen = readValue(arg);
94
+ } else if (arg === "--no-autostart") {
95
+ options.autoStart = false;
96
+ } else if (arg === "--no-tunnel") {
97
+ options.tunnelEnabled = false;
98
+ } else if (arg === "--print-command") {
99
+ options.printCommand = true;
100
+ } else if (arg === "--dry-run") {
101
+ options.dryRun = true;
102
+ } else if (arg === "--keep-script") {
103
+ options.keepScript = true;
104
+ } else {
105
+ throw new Error(`Unknown option: ${arg}`);
106
+ }
107
+ }
108
+
109
+ return options;
110
+ }
111
+
112
+ function normalizeRelayUrl(value) {
113
+ const raw = String(value || "").trim().replace(/\/+$/, "");
114
+ if (!raw) throw new Error("Relay URL is empty");
115
+ const withProtocol = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
116
+ const url = new URL(withProtocol);
117
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
118
+ throw new Error(`Relay URL must use http or https: ${withProtocol}`);
119
+ }
120
+ return url.toString().replace(/\/+$/, "");
121
+ }
122
+
123
+ function bootstrapUrl(options) {
124
+ const url = new URL("/agent/bootstrap.ps1", normalizeRelayUrl(options.relayUrl));
125
+ url.searchParams.set("userId", options.userId);
126
+ if (options.deviceId) url.searchParams.set("deviceId", options.deviceId);
127
+ if (options.deviceName) url.searchParams.set("deviceName", options.deviceName);
128
+ if (options.workspace) url.searchParams.set("workspace", options.workspace);
129
+ if (options.accessCode) url.searchParams.set("accessCode", options.accessCode);
130
+ if (options.mode) url.searchParams.set("mode", options.mode);
131
+ if (options.appServerListen) url.searchParams.set("appServerListen", options.appServerListen);
132
+ url.searchParams.set("autoStart", options.autoStart ? "1" : "0");
133
+ url.searchParams.set("tunnelEnabled", options.tunnelEnabled ? "1" : "0");
134
+ return url;
135
+ }
136
+
137
+ function downloadText(url, redirects = 0) {
138
+ if (redirects > 5) return Promise.reject(new Error(`Too many redirects for ${url}`));
139
+ const client = url.protocol === "https:" ? https : http;
140
+ return new Promise((resolve, reject) => {
141
+ const req = client.get(url, {
142
+ headers: {
143
+ "user-agent": `codexpanel-npm/${VERSION}`,
144
+ "accept": "text/plain, */*",
145
+ },
146
+ timeout: 30000,
147
+ }, (res) => {
148
+ if ([301, 302, 303, 307, 308].includes(res.statusCode || 0) && res.headers.location) {
149
+ res.resume();
150
+ const next = new URL(res.headers.location, url);
151
+ downloadText(next, redirects + 1).then(resolve, reject);
152
+ return;
153
+ }
154
+ let body = "";
155
+ res.setEncoding("utf8");
156
+ res.on("data", (chunk) => body += chunk);
157
+ res.on("end", () => {
158
+ if ((res.statusCode || 0) < 200 || (res.statusCode || 0) >= 300) {
159
+ const authHint = res.statusCode === 401 || res.statusCode === 403
160
+ ? " The relay requires an access code; re-run with --access-code <code> or set CODEXPANEL_ACCESS_CODE."
161
+ : "";
162
+ reject(new Error(`HTTP ${res.statusCode} while downloading ${url}.${authHint}`));
163
+ return;
164
+ }
165
+ resolve(body.replace(/^\uFEFF/, ""));
166
+ });
167
+ });
168
+ req.on("timeout", () => req.destroy(new Error(`Timeout downloading ${url}`)));
169
+ req.on("error", reject);
170
+ });
171
+ }
172
+
173
+ function powershellPath() {
174
+ if (process.platform !== "win32") return "pwsh";
175
+ const candidates = ["pwsh.exe", "powershell.exe"];
176
+ for (const candidate of candidates) {
177
+ const result = spawnSync("where.exe", [candidate], { encoding: "utf8", windowsHide: true });
178
+ const first = (result.stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0];
179
+ if (first) return first;
180
+ }
181
+ return "powershell.exe";
182
+ }
183
+
184
+ function powershellCommand(url) {
185
+ const escaped = String(url).replace(/'/g, "''");
186
+ return `powershell -NoProfile -ExecutionPolicy Bypass -Command "irm '${escaped}' | iex"`;
187
+ }
188
+
189
+ async function install(options) {
190
+ const url = bootstrapUrl(options);
191
+ if (options.printCommand) console.log(powershellCommand(url));
192
+ if (options.dryRun) {
193
+ console.log(JSON.stringify({
194
+ ok: true,
195
+ platform: process.platform,
196
+ relayUrl: normalizeRelayUrl(options.relayUrl),
197
+ bootstrapUrl: String(url),
198
+ userId: options.userId,
199
+ mode: options.mode,
200
+ autoStart: options.autoStart,
201
+ tunnelEnabled: options.tunnelEnabled,
202
+ }, null, 2));
203
+ return;
204
+ }
205
+
206
+ if (process.platform !== "win32") {
207
+ throw new Error("CodexPanel desktop agent installation currently supports Windows. Use --print-command on Windows, or open the relay setup page for this platform.");
208
+ }
209
+
210
+ console.log(`CodexPanel: downloading installer from ${normalizeRelayUrl(options.relayUrl)}`);
211
+ const script = await downloadText(url);
212
+ if (!/CodexPanel/i.test(script) || !/desktop-agent/i.test(script)) {
213
+ throw new Error("Downloaded bootstrap script did not look like the CodexPanel Windows installer.");
214
+ }
215
+
216
+ const tmp = path.join(os.tmpdir(), `codexpanel-bootstrap-${Date.now()}-${Math.random().toString(16).slice(2)}.ps1`);
217
+ fs.writeFileSync(tmp, Buffer.concat([Buffer.from([0xEF, 0xBB, 0xBF]), Buffer.from(script, "utf8")]));
218
+
219
+ console.log("CodexPanel: starting Windows installer");
220
+ const result = spawnSync(powershellPath(), [
221
+ "-NoLogo",
222
+ "-NoProfile",
223
+ "-ExecutionPolicy",
224
+ "Bypass",
225
+ "-File",
226
+ tmp,
227
+ ], {
228
+ stdio: "inherit",
229
+ windowsHide: false,
230
+ });
231
+
232
+ if (!options.keepScript) {
233
+ try { fs.unlinkSync(tmp); } catch {}
234
+ } else {
235
+ console.log(`CodexPanel: kept bootstrap script at ${tmp}`);
236
+ }
237
+
238
+ if (result.error) throw result.error;
239
+ if (result.status !== 0) throw new Error(`PowerShell installer exited with code ${result.status}`);
240
+ }
241
+
242
+ async function main() {
243
+ const options = parseArgs(process.argv.slice(2));
244
+ if (options.version) {
245
+ console.log(VERSION);
246
+ return;
247
+ }
248
+ if (options.help) {
249
+ console.log(usage());
250
+ return;
251
+ }
252
+ if (options.command !== "install") throw new Error(`Unknown command: ${options.command}`);
253
+ await install(options);
254
+ }
255
+
256
+ main().catch((error) => {
257
+ console.error(`codexpanel: ${error.message}`);
258
+ process.exitCode = 1;
259
+ });
package/package.json CHANGED
@@ -1,7 +1,42 @@
1
- {
1
+ {
2
2
  "name": "codexpanel",
3
- "version": "0.0.1",
4
- "description": "placeholder package to reserve name",
5
- "bin": { "codexpanel": "cli.js" },
6
- "license": "MIT"
7
- }
3
+ "version": "0.1.0",
4
+ "description": "CodexPanel mobile control plane monorepo.",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "bin": {
8
+ "codexpanel": "bin/codexpanel.cjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "README.md"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "engines": {
18
+ "node": ">=18.0.0"
19
+ },
20
+ "workspaces": [
21
+ "apps/*",
22
+ "packages/*"
23
+ ],
24
+ "scripts": {
25
+ "dev:web": "npm run dev -w @codexpanel/web",
26
+ "build": "npm run build --workspaces --if-present",
27
+ "check": "npm run scan:encoding && npm run verify:api-contracts && npm run verify:storage && npm run check --workspaces --if-present",
28
+ "test:e2e": "playwright test",
29
+ "scan:encoding": "node scripts/scan-encoding.cjs",
30
+ "verify:api-contracts": "node scripts/verify-api-contracts.mjs",
31
+ "generate:codex": "npm run generate -w @codexpanel/codex-app-server",
32
+ "verify:storage": "node scripts/verify-storage-boundary.mjs",
33
+ "smoke:storage": "node scripts/storage-smoke.cjs",
34
+ "purge:legacy-plaintext": "node scripts/purge-legacy-plaintext-cache.mjs"
35
+ },
36
+ "devDependencies": {
37
+ "@playwright/test": "1.52.0",
38
+ "@types/node": "^24.0.0",
39
+ "tsx": "^4.19.4",
40
+ "typescript": "^5.8.3"
41
+ }
42
+ }
package/cli.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log('占名成功: codexpanel');
package/codexh5/cli.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log('占名成功: codexh5');
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log('占名成功: codexfish');
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log('占名成功: codexyes');
@@ -1,7 +0,0 @@
1
- {
2
- "name": "codexyes",
3
- "version": "0.0.1",
4
- "description": "placeholder package to reserve name",
5
- "bin": { "codexyes": "cli.js" },
6
- "license": "MIT"
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "name": "codexfish",
3
- "version": "0.0.1",
4
- "description": "placeholder package to reserve name",
5
- "bin": { "codexfish": "cli.js" },
6
- "license": "MIT"
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "name": "codexh5",
3
- "version": "0.0.1",
4
- "description": "placeholder package to reserve name",
5
- "bin": { "codexh5": "cli.js" },
6
- "license": "MIT"
7
- }