codexpanel 0.1.7 → 0.1.8

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 CHANGED
@@ -10,10 +10,10 @@ codexpanel is a mobile control plane for managing local Codex work from a phone.
10
10
  - `apps/relay`: HTTP/SSE/WSS relay service, PostgreSQL/SQLite storage, and device onboarding scripts
11
11
  - `apps/agent`: desktop-side agent entrypoint
12
12
  - `packages/protocol`: shared schemas and transport helpers
13
- - `packages/codex-app-server`: Codex app-server connector bindings
13
+ - `packages/codex-app-server`: Codex app-server schema and method snapshots
14
14
  - `packages/ui`: shared React UI primitives
15
15
 
16
- 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.
16
+ Human reference files, local databases, deployment bundles, server handoff notes, and retired prototype artifacts live outside the production tree in `files_for_human/` and are ignored by Git.
17
17
 
18
18
  ## Commands
19
19
 
@@ -44,7 +44,7 @@ npx -y codexpanel --server local
44
44
  npx -y codexpanel --server https://example.com
45
45
  ```
46
46
 
47
- `--server test` uses `https://jd.6a.gs`. `--server local` dynamically finds a free local relay port instead of assuming `4871`. A full URL connects to a self-hosted relay. Useful options include `--device-name`, `--workspace`, `--terminal-login`, `--token-login`, `--no-browser`, `--no-autostart`, `--no-tunnel`, `--dry-run`, and `--print-command`.
47
+ `--server test` uses `https://jd.6a.gs`. `--server local` dynamically finds a free local relay port instead of assuming `4871`. A full URL connects to a self-hosted relay. Useful options include `--device-name`, `--workspace`, `--token-login`, `--no-browser`, `--no-autostart`, `--dry-run`, and `--print-command`. The retired `--legacy-tunnel` flag is rejected because direct tunnel access is not part of the final control path.
48
48
 
49
49
  For terminal token login, open the printed `/desktop/setup?flowId=...` link, sign in, click the one-time terminal token button, then run `npx -y codexpanel login --token-login` and paste the token. The token is scoped to the current setup flow and expires quickly.
50
50
 
@@ -58,21 +58,34 @@ Web/admin login uses a session cookie. Configure at least one of these before ex
58
58
 
59
59
  ```env
60
60
  CODEXPANEL_ADMIN_USERNAME=admin
61
- CODEXPANEL_ADMIN_PASSWORD=change_me
61
+ CODEXPANEL_ADMIN_USER_ID=admin
62
+ CODEXPANEL_ADMIN_PASSWORD=ChangeRoot9361A
62
63
  CODEXPANEL_SESSION_SECRET=long_random_secret
63
64
  CODEXPANEL_LOGIN_MAX_ATTEMPTS=8
65
+ CODEXPANEL_LOGIN_IP_MAX_ATTEMPTS=24
64
66
  CODEXPANEL_LOGIN_WINDOW_MS=900000
65
67
  CODEXPANEL_LOGIN_LOCKOUT_MS=900000
66
- CODEXPANEL_REGISTRATION_ENABLED=true
68
+ CODEXPANEL_LOGIN_MAX_BUCKETS=5000
69
+ CODEXPANEL_EMAIL_CODE_IP_MAX_ATTEMPTS=10
70
+ CODEXPANEL_EMAIL_CODE_IP_WINDOW_MS=900000
71
+ CODEXPANEL_EMAIL_CODE_MAX_VERIFY_ATTEMPTS=8
72
+ CODEXPANEL_EMAIL_CODE_MAX_CODES=5000
73
+ CODEXPANEL_DESKTOP_SETUP_MAX_PENDING=1000
74
+ CODEXPANEL_DESKTOP_SETUP_START_WINDOW_MS=900000
75
+ CODEXPANEL_DESKTOP_SETUP_START_MAX_ATTEMPTS=20
76
+ CODEXPANEL_DESKTOP_SETUP_START_MAX_IP_BUCKETS=5000
77
+ CODEXPANEL_REGISTRATION_ENABLED=false
67
78
  ```
68
79
 
69
- Desktop setup uses browser login binding or terminal username/password/token login. Extra users can be provided with `CODEXPANEL_USERS_JSON` using `username`, `password`, `userId`, `role`, and `name`.
80
+ Desktop setup uses browser login binding or a one-time terminal token. `CODEXPANEL_ADMIN_*` only bootstraps the first admin; additional users must be created through registration or admin operations.
70
81
 
71
- `CODEXPANEL_REGISTRATION_ENABLED=true` opens self-service web registration and is the current default. New users are stored in the existing relay users table with PBKDF2 password hashes and receive `CODEXPANEL_REGISTRATION_DEFAULT_ROLE` (`operator` or `viewer`, default `operator`). Set it to `false` if you need to close public onboarding again.
82
+ `CODEXPANEL_REGISTRATION_ENABLED=true` opens self-service web registration. If the variable is omitted, development defaults to open and production defaults to closed; production onboarding must opt in explicitly. New users are stored in the existing relay users table with PBKDF2 password hashes and receive `CODEXPANEL_REGISTRATION_DEFAULT_ROLE` (`operator` or `viewer`, default `operator`).
83
+
84
+ Desktop setup start requests are also rate-limited by source IP and capped by pending flow count. Tune `CODEXPANEL_DESKTOP_SETUP_*` only if installer traffic legitimately exceeds the defaults.
72
85
 
73
86
  ## First-batch user operations
74
87
 
75
- Use `/admin.html` with an admin session for first-batch testing:
88
+ Use `/admin/` with an admin session for first-batch testing:
76
89
 
77
90
  - Enable/disable users, change roles, and reset passwords. These actions invalidate existing local-user sessions.
78
91
  - Recharge, gift, or deduct balance through the wallet adjustment action. Every change requires an idempotency key and is recorded in the user wallet ledger; do not edit `balance` directly.
@@ -87,7 +100,7 @@ Rollback for a test user is done by disabling the user, revoking the monthly car
87
100
  - `/console/`: mobile control console and PWA start URL.
88
101
  - `/desktop/setup`: browser sign-in and desktop device binding flow.
89
102
  - `/device.html`: migration page that points users to `npx -y codexpanel`.
90
- - `/admin.html` and `/project.html`: admin and product documentation pages.
103
+ - `/admin/` and `/project.html`: admin and product documentation pages.
91
104
 
92
105
  ## Storage and deployment
93
106
 
package/README.zh.md CHANGED
@@ -10,10 +10,10 @@ codexpanel 是一个面向 Codex 本地工作的移动控制平面。它把手
10
10
  - `apps/relay`:HTTP/SSE/WSS relay 服务、PostgreSQL/SQLite 存储、设备接入脚本。
11
11
  - `apps/agent`:电脑端 agent 入口。
12
12
  - `packages/protocol`:共享 schema 和传输 helper。
13
- - `packages/codex-app-server`:Codex app-server 连接绑定。
13
+ - `packages/codex-app-server`:Codex app-server schema 与 method 快照。
14
14
  - `packages/ui`:共享 React UI 基础组件。
15
15
 
16
- 人工参考文件、本地数据库、部署包、服务器交接说明和旧 demo 产物放在 `files_for_human/`,该目录不属于生产树并被 Git 忽略。
16
+ 人工参考文件、本地数据库、部署包、服务器交接说明和已退役原型产物放在 `files_for_human/`,该目录不属于生产树并被 Git 忽略。
17
17
 
18
18
  ## 常用命令
19
19
 
@@ -44,7 +44,7 @@ npx -y codexpanel --server local
44
44
  npx -y codexpanel --server https://example.com
45
45
  ```
46
46
 
47
- `--server test` 使用 `https://jd.6a.gs`。`--server local` 会动态寻找可用本地端口,不再假定 `4871`。完整 URL 会连接自部署 relay。常用参数包括 `--device-name`、`--workspace`、`--terminal-login`、`--token-login`、`--no-browser`、`--no-autostart`、`--no-tunnel`、`--dry-run` 和 `--print-command`。
47
+ `--server test` 使用 `https://jd.6a.gs`。`--server local` 会动态寻找可用本地端口,不再假定 `4871`。完整 URL 会连接自部署 relay。常用参数包括 `--device-name`、`--workspace`、`--token-login`、`--no-browser`、`--no-autostart`、`--dry-run` 和 `--print-command`。已退役的 `--legacy-tunnel` 会被拒绝,因为 direct tunnel 不属于最终控制路径。
48
48
 
49
49
  终端 token 登录的使用方式是:打开安装器打印的 `/desktop/setup?flowId=...` 链接,在浏览器登录后点击一次性终端 token 按钮,再运行 `npx -y codexpanel login --token-login` 并粘贴 token。该 token 只绑定当前安装流,过期很快。
50
50
 
@@ -58,21 +58,34 @@ Web/admin 登录使用 session cookie。公网暴露 relay 前至少配置以下
58
58
 
59
59
  ```env
60
60
  CODEXPANEL_ADMIN_USERNAME=admin
61
- CODEXPANEL_ADMIN_PASSWORD=change_me
61
+ CODEXPANEL_ADMIN_USER_ID=admin
62
+ CODEXPANEL_ADMIN_PASSWORD=ChangeRoot9361A
62
63
  CODEXPANEL_SESSION_SECRET=long_random_secret
63
64
  CODEXPANEL_LOGIN_MAX_ATTEMPTS=8
65
+ CODEXPANEL_LOGIN_IP_MAX_ATTEMPTS=24
64
66
  CODEXPANEL_LOGIN_WINDOW_MS=900000
65
67
  CODEXPANEL_LOGIN_LOCKOUT_MS=900000
66
- CODEXPANEL_REGISTRATION_ENABLED=true
68
+ CODEXPANEL_LOGIN_MAX_BUCKETS=5000
69
+ CODEXPANEL_EMAIL_CODE_IP_MAX_ATTEMPTS=10
70
+ CODEXPANEL_EMAIL_CODE_IP_WINDOW_MS=900000
71
+ CODEXPANEL_EMAIL_CODE_MAX_VERIFY_ATTEMPTS=8
72
+ CODEXPANEL_EMAIL_CODE_MAX_CODES=5000
73
+ CODEXPANEL_DESKTOP_SETUP_MAX_PENDING=1000
74
+ CODEXPANEL_DESKTOP_SETUP_START_WINDOW_MS=900000
75
+ CODEXPANEL_DESKTOP_SETUP_START_MAX_ATTEMPTS=20
76
+ CODEXPANEL_DESKTOP_SETUP_START_MAX_IP_BUCKETS=5000
77
+ CODEXPANEL_REGISTRATION_ENABLED=false
67
78
  ```
68
79
 
69
- 电脑端 setup 支持浏览器登录绑定、终端用户名密码登录和终端一次性 token 登录。额外用户可以通过 `CODEXPANEL_USERS_JSON` 提供,字段包括 `username`、`password`、`userId`、`role` 和 `name`。
80
+ 电脑端 setup 支持浏览器登录绑定和终端一次性 token 登录。`CODEXPANEL_ADMIN_*` 只用于启动首个管理员;其他用户必须通过注册或后台运营创建。
70
81
 
71
- `CODEXPANEL_REGISTRATION_ENABLED=true` 会打开自助注册,目前默认开启。新用户写入 relay 的 users 表,密码使用 PBKDF2 hash,角色来自 `CODEXPANEL_REGISTRATION_DEFAULT_ROLE`,默认 `operator`。需要关闭公开接入时设为 `false`。
82
+ `CODEXPANEL_REGISTRATION_ENABLED=true` 会打开自助注册。未配置该变量时,开发环境默认开放、生产环境默认关闭;生产环境如需开放注册必须显式设置为 `true`。新用户写入 relay 的 users 表,密码使用 PBKDF2 hash,角色来自 `CODEXPANEL_REGISTRATION_DEFAULT_ROLE`,默认 `operator`。
83
+
84
+ 电脑端 setup 启动请求会按来源 IP 限流,并受 pending flow 总量限制。只有安装流量确实超过默认值时,才需要调整 `CODEXPANEL_DESKTOP_SETUP_*`。
72
85
 
73
86
  ## 第一批用户运营
74
87
 
75
- 使用 admin session 打开 `/admin.html` 进行第一批测试:
88
+ 使用 admin session 打开 `/admin/` 进行第一批测试:
76
89
 
77
90
  - 启用/禁用用户、调整角色、重置密码。这些操作会让对应本地用户旧 session 失效。
78
91
  - 通过钱包调整操作充值、赠送或扣减余额。每次变更都需要幂等键,并写入钱包流水;不要直接改 `balance`。
@@ -87,7 +100,7 @@ CODEXPANEL_REGISTRATION_ENABLED=true
87
100
  - `/console/`:手机控制台和 PWA start URL。
88
101
  - `/desktop/setup`:浏览器登录和电脑设备绑定流程。
89
102
  - `/device.html`:迁移页,引导用户改用 `npx -y codexpanel`。
90
- - `/admin.html` 和 `/project.html`:管理后台和产品文档页。
103
+ - `/admin/` 和 `/project.html`:管理后台和产品文档页。
91
104
 
92
105
  ## 存储与部署
93
106
 
@@ -11,7 +11,7 @@ const crypto = require("crypto");
11
11
  const readline = require("readline");
12
12
  const { spawn, spawnSync } = require("child_process");
13
13
 
14
- const VERSION = "0.1.6";
14
+ const VERSION = "0.1.8";
15
15
  const PROD_URL = "https://codexpanel.com";
16
16
  const TEST_URL = "https://jd.6a.gs";
17
17
  const LOCAL_HOST = "127.0.0.1";
@@ -31,11 +31,9 @@ Options:
31
31
  --server <name|url> production/prod, test/jd, local, or a relay URL. Defaults to production
32
32
  --workspace, --cwd <path> Default workspace for Codex Desktop/app-server
33
33
  --device-name, --name <name> Device display name shown in CodexPanel
34
- --terminal-login Ask for username and password in this terminal
35
34
  --token-login Ask for a one-time terminal token from the setup page
36
35
  --no-browser Do not open a browser; print the login URL instead
37
36
  --no-autostart Do not enable Windows login autostart
38
- --no-tunnel Disable Cloudflare WSS direct tunnel
39
37
  --dry-run Print resolved installer details without installing
40
38
  --print-command Print the equivalent PowerShell bootstrap command after login approval
41
39
  -h, --help Show help
@@ -56,11 +54,9 @@ function parseArgs(argv) {
56
54
  server: process.env.CODEXPANEL_SERVER || "production",
57
55
  workspace: process.env.CODEXPANEL_WORKSPACE || "",
58
56
  deviceName: process.env.CODEXPANEL_DEVICE_NAME || "",
59
- terminalLogin: false,
60
57
  tokenLogin: false,
61
58
  browser: true,
62
59
  autoStart: true,
63
- tunnelEnabled: true,
64
60
  dryRun: false,
65
61
  printCommand: false,
66
62
  };
@@ -80,17 +76,17 @@ function parseArgs(argv) {
80
76
  else if (arg === "--server") options.server = readValue(arg);
81
77
  else if (arg === "--workspace" || arg === "--cwd") options.workspace = readValue(arg);
82
78
  else if (arg === "--device-name" || arg === "--name") options.deviceName = readValue(arg);
83
- else if (arg === "--terminal-login") options.terminalLogin = true;
79
+ else if (arg === "--terminal-login") throw new Error("--terminal-login has been retired. Use browser sign-in or --token-login.");
84
80
  else if (arg === "--token-login") options.tokenLogin = true;
85
81
  else if (arg === "--no-browser") options.browser = false;
86
82
  else if (arg === "--no-autostart") options.autoStart = false;
87
- else if (arg === "--no-tunnel") options.tunnelEnabled = false;
83
+ else if (arg === "--legacy-tunnel") throw new Error("--legacy-tunnel has been retired. Device traffic uses the server WSS gateway.");
84
+ else if (arg === "--no-tunnel") throw new Error("--no-tunnel has been retired. Device traffic uses the server WSS gateway.");
88
85
  else if (arg === "--dry-run") options.dryRun = true;
89
86
  else if (arg === "--print-command") options.printCommand = true; else {
90
87
  throw new Error(`Unknown option: ${arg}`);
91
88
  }
92
89
  }
93
- if (options.terminalLogin && options.tokenLogin) throw new Error("Choose only one of --terminal-login or --token-login.");
94
90
  return options;
95
91
  }
96
92
 
@@ -164,7 +160,6 @@ function computerProfile(options, resolvedServer) {
164
160
  deviceName: options.deviceName || `${os.hostname()} / ${defaultUsername()} Codex Desktop Agent`,
165
161
  workspace: options.workspace || process.env.USERPROFILE || process.env.HOME || process.cwd(),
166
162
  autoStart: options.autoStart,
167
- tunnelEnabled: options.tunnelEnabled,
168
163
  panelPortHint: DEFAULT_PANEL_PORT_START,
169
164
  };
170
165
  }
@@ -369,7 +364,12 @@ async function preloadResources(relayUrl) {
369
364
  let manifest = {};
370
365
  try { manifest = JSON.parse(manifestText); }
371
366
  catch { throw new Error("Server returned an invalid agent manifest."); }
372
- const resources = [manifest.desktopAgent, manifest.connector, manifest.bootstrap].filter(Boolean);
367
+ const resources = [
368
+ manifest.hostRuntime,
369
+ manifest.hostRuntimeSchema,
370
+ ...(Array.isArray(manifest.hostRuntimeFiles) ? manifest.hostRuntimeFiles : []),
371
+ manifest.bootstrap,
372
+ ].filter(Boolean);
373
373
  for (const item of resources) {
374
374
  const resourceUrl = new URL(item.path, relayUrl).toString();
375
375
  console.log(`CodexPanel: 预下载资源 / Downloading ${item.fileName || item.path}`);
@@ -409,37 +409,18 @@ async function pollSetup(relayUrl, flowId, flowSecret) {
409
409
  throw new Error("Setup login timed out after 10 minutes.");
410
410
  }
411
411
 
412
- async function terminalLogin(relayUrl, flowId, flowSecret) {
413
- const rl = createInterface();
414
- try {
415
- const username = (await question(rl, "CodexPanel username: ")).trim();
416
- const password = await question(rl, "CodexPanel password: ", { silent: true });
417
- try {
418
- return await requestJson("POST", relayUrl, "/api/desktop/setup/login-token", { flowId, flowSecret, username, password }, {}, 30000);
419
- } catch (error) {
420
- if (error.data?.code !== "device_rebind_required") throw error;
421
- console.log("这台电脑已绑定到其他账号,不会自动切换绑定。");
422
- const answer = (await question(rl, "Type YES to rebind this computer to the current account / 输入 YES 切换绑定: ")).trim();
423
- if (answer !== "YES") throw new Error("Setup kept the existing binding. Run npx -y codexpanel again if you want to switch later.");
424
- return await requestJson("POST", relayUrl, "/api/desktop/setup/login-token", { flowId, flowSecret, username, password, confirmRebind: true }, {}, 30000);
425
- }
426
- } finally {
427
- rl.close();
428
- }
429
- }
430
-
431
412
  async function tokenLogin(relayUrl, flowId, flowSecret) {
432
413
  const rl = createInterface();
433
414
  try {
434
415
  const token = (await question(rl, "One-time CodexPanel terminal token from setup page: ")).trim();
435
416
  try {
436
- return await requestJson("POST", relayUrl, "/api/desktop/setup/login-token", { flowId, flowSecret, token }, {}, 30000);
417
+ return await requestJson("POST", relayUrl, "/api/iam/desktop/setup/login-token", { flowId, flowSecret, token }, {}, 30000);
437
418
  } catch (error) {
438
419
  if (error.data?.code !== "device_rebind_required") throw error;
439
420
  console.log("这台电脑已绑定到其他账号,不会自动切换绑定。");
440
421
  const answer = (await question(rl, "Type YES to rebind this computer to the current account / 输入 YES 切换绑定: ")).trim();
441
422
  if (answer !== "YES") throw new Error("Setup kept the existing binding. Run npx -y codexpanel again if you want to switch later.");
442
- return await requestJson("POST", relayUrl, "/api/desktop/setup/login-token", { flowId, flowSecret, token, confirmRebind: true }, {}, 30000);
423
+ return await requestJson("POST", relayUrl, "/api/iam/desktop/setup/login-token", { flowId, flowSecret, token, confirmRebind: true }, {}, 30000);
443
424
  }
444
425
  } finally {
445
426
  rl.close();
@@ -454,16 +435,15 @@ async function runBootstrap(relayUrl, approval, options) {
454
435
  url.searchParams.set("deviceId", approval.deviceId || stableDeviceId());
455
436
  if (approval.deviceName) url.searchParams.set("deviceName", approval.deviceName);
456
437
  if (options.workspace) url.searchParams.set("workspace", options.workspace);
457
- url.searchParams.set("mode", "desktop-agent");
438
+ url.searchParams.set("mode", "host-runtime");
458
439
  url.searchParams.set("autoStart", options.autoStart ? "1" : "0");
459
- url.searchParams.set("tunnelEnabled", options.tunnelEnabled ? "1" : "0");
460
440
 
461
441
  if (options.printCommand) console.log(powershellCommand(url));
462
442
  if (process.platform !== "win32") throw new Error("CodexPanel desktop agent installation currently supports Windows. Use the login URL on Windows to bind this computer.");
463
443
 
464
444
  console.log("\nCodexPanel: 正在下载最终安装脚本 / Downloading final bootstrap script...");
465
445
  const script = await downloadText(url.toString(), 30000);
466
- if (!/CodexPanel/i.test(script) || !/desktop-agent/i.test(script)) throw new Error("Downloaded bootstrap script did not look like the CodexPanel Windows installer.");
446
+ if (!/CodexPanel/i.test(script) || !/host-runtime/i.test(script)) throw new Error("Downloaded bootstrap script did not look like the CodexPanel Windows installer.");
467
447
  const tmp = path.join(os.tmpdir(), `codexpanel-bootstrap-${Date.now()}-${Math.random().toString(16).slice(2)}.ps1`);
468
448
  fs.writeFileSync(tmp, Buffer.concat([Buffer.from([0xEF, 0xBB, 0xBF]), Buffer.from(script, "utf8")]));
469
449
  console.log("CodexPanel: 正在启动 Windows 安装器 / Starting Windows installer...");
@@ -487,11 +467,9 @@ async function install(options) {
487
467
  deviceId: profile.deviceId,
488
468
  deviceName: profile.deviceName,
489
469
  workspace: profile.workspace,
490
- browserLogin: !options.terminalLogin && !options.tokenLogin,
491
- terminalLogin: options.terminalLogin,
470
+ browserLogin: !options.tokenLogin,
492
471
  tokenLogin: options.tokenLogin,
493
472
  autoStart: options.autoStart,
494
- tunnelEnabled: options.tunnelEnabled,
495
473
  }, null, 2));
496
474
  return;
497
475
  }
@@ -513,9 +491,7 @@ async function install(options) {
513
491
  if (start.oneTimeCode) console.log(`Binding code / 8 位绑定码: ${start.oneTimeCode}`);
514
492
 
515
493
  let approval;
516
- if (options.terminalLogin) {
517
- approval = await terminalLogin(resolved.relayUrl, start.flowId, start.flowSecret);
518
- } else if (options.tokenLogin) {
494
+ if (options.tokenLogin) {
519
495
  approval = await tokenLogin(resolved.relayUrl, start.flowId, start.flowSecret);
520
496
  } else {
521
497
  if (options.browser) {
@@ -12,7 +12,7 @@
12
12
 
13
13
  安装流程被分成两段。
14
14
 
15
- 第一段是资源准备:CLI 会先读取安装清单,再下载并校验电脑端 agent、app-server 连接器和安装脚本探针。这样做的好处是,用户按下 Enter 的时候,系统已经知道安装所需资源是可用的,不会出现“浏览器已经打开了,但安装器还在等下载”的割裂感。
15
+ 第一段是资源准备:CLI 会先读取安装清单,再下载并校验电脑端 host-runtime agent、运行时模块和安装脚本探针。这样做的好处是,用户按下 Enter 的时候,系统已经知道安装所需资源是可用的,不会出现“浏览器已经打开了,但安装器还在等下载”的割裂感。
16
16
 
17
17
  第二段才是登录绑定:终端会提示 `资源已准备好。按 Enter 拉起浏览器登录并绑定这台电脑。`,用户确认后,浏览器才会打开登录页。这样更像一个正式安装流程,而不是悄悄弹出一个网页。
18
18
 
@@ -37,21 +37,7 @@
37
37
 
38
38
  页面完成绑定后会提示:`绑定成功,可以关闭浏览器,或回到安装界面查看进度。`
39
39
 
40
- ## 终端登录方式
41
-
42
- 除了浏览器登录,这个安装器也支持两种终端模式。
43
-
44
- ### 用户名和密码
45
-
46
- 运行:
47
-
48
- ```powershell
49
- npx -y codexpanel login --terminal-login
50
- ```
51
-
52
- 然后按提示输入用户名和密码。它会像普通登录一样走 session,成功后继续完成设备绑定。
53
-
54
- ### 一次性 token
40
+ ## 终端一次性 token
55
41
 
56
42
  如果你想在浏览器里登录,但让终端继续完成绑定,可以先打开安装器打印的 `/desktop/setup?flowId=...` 页面,登录后点击“生成终端一次性 token”。然后运行:
57
43
 
@@ -116,7 +102,7 @@ npx -y codexpanel --server https://example.com
116
102
  - relay 地址
117
103
  - 最近心跳
118
104
  - agent 运行状态
119
- - Codex app-server 状态
105
+ - host-runtime / WSS 状态
120
106
  - 是否能打开或重启 agent
121
107
 
122
108
  这块面板的目的,是让不熟悉命令行的人也能看懂“现在装到哪一步了”。
@@ -149,7 +135,6 @@ CLI 同时生成本机 profile,包括:
149
135
  - `deviceName`
150
136
  - `workspace`
151
137
  - `autoStart`
152
- - `tunnelEnabled`
153
138
  - `panelPortHint`
154
139
 
155
140
  `deviceId` 来自本机 hostname、Windows domain、系统用户名和 user profile 路径的稳定 hash。它不是随机数,所以同一台电脑同一系统用户重复安装时会得到同一个 deviceId,方便识别换绑和 token 轮换。
@@ -165,21 +150,25 @@ GET /agent/version
165
150
  relay 返回 agent release manifest,里面包含:
166
151
 
167
152
  - `agentVersion`
168
- - `desktopAgent.path`
169
- - `desktopAgent.sha256`
170
- - `connector.path`
171
- - `connector.sha256`
153
+ - `hostRuntime.path`
154
+ - `hostRuntime.sha256`
155
+ - `hostRuntimeFiles[]`
156
+ - `hostRuntimeSchema.path`
157
+ - `hostRuntimeSchema.sha256`
172
158
  - `bootstrap.path`
173
159
  - `bootstrap.sha256`
174
160
 
175
161
  CLI 随后下载 manifest 指向的资源:
176
162
 
177
163
  ```text
178
- GET /agent/desktop-agent.js
179
- GET /agent/app-server-connector.js
164
+ GET /agent/host-runtime.cjs
165
+ GET /agent/runtime/*.cjs
166
+ GET /agent/generated/codex-app-server-schemas.ts
180
167
  GET /agent/bootstrap.ps1
181
168
  ```
182
169
 
170
+ 旧 `/agent/desktop-agent.js` 和 `/agent/app-server-connector.js` 下载入口已清退;安装器不再预检或下载旧 standalone bridge。
171
+
183
172
  每个资源下载后都会按 manifest 中的 `sha256` 校验。只有这些资源全部可下载、hash 正确,安装器才进入登录阶段。这样做的意义是:用户按 Enter 打开浏览器前,安装器已经确认服务端资源完整,后续失败更容易定位到登录、绑定或本机启动,而不是混在下载阶段。
184
173
 
185
174
  ### 3. 创建 setup flow
@@ -218,20 +207,20 @@ GET /desktop/setup?flowId=...&code=...
218
207
  页面会先调用:
219
208
 
220
209
  ```text
221
- GET /api/auth/status
210
+ GET /api/iam/auth/status
222
211
  POST /api/desktop/setup/poll
223
212
  ```
224
213
 
225
214
  如果用户未登录,页面显示登录/注册表单。登录使用:
226
215
 
227
216
  ```text
228
- POST /api/auth/login
217
+ POST /api/iam/auth/login
229
218
  ```
230
219
 
231
220
  注册使用:
232
221
 
233
222
  ```text
234
- POST /api/auth/register
223
+ POST /api/iam/auth/register
235
224
  ```
236
225
 
237
226
  成功后 relay 设置 `codexpanel_session` cookie。浏览器后续审批靠 session cookie 鉴权。
@@ -284,29 +273,7 @@ relay 会校验:
284
273
  Binding succeeded. You can close this browser or return to the installer.
285
274
  ```
286
275
 
287
- ### 5. 终端登录和一次性 token 登录
288
-
289
- 如果用户选择终端用户名密码:
290
-
291
- ```powershell
292
- npx -y codexpanel login --terminal-login
293
- ```
294
-
295
- CLI 会在终端询问用户名和密码,然后调用:
296
-
297
- ```text
298
- POST /api/desktop/setup/login-token
299
- ```
300
-
301
- 请求体包含:
302
-
303
- - `flowId`
304
- - `flowSecret`
305
- - `username`
306
- - `password`
307
- - 可选 `confirmRebind`
308
-
309
- relay 使用和 Web 登录相同的用户体系校验密码、登录失败限流、用户状态和角色,然后复用 setup approve 逻辑完成审批。
276
+ ### 5. 终端一次性 token 登录
310
277
 
311
278
  如果用户选择一次性 token 登录:
312
279
 
@@ -316,7 +283,7 @@ relay 使用和 Web 登录相同的用户体系校验密码、登录失败限流
316
283
  4. 页面调用:
317
284
 
318
285
  ```text
319
- POST /api/desktop/setup/terminal-token
286
+ POST /api/iam/desktop/setup/terminal-token
320
287
  ```
321
288
 
322
289
  relay 校验当前浏览器 session、flowId 和 code,然后给这个 setup flow 写入短时 token hash。页面只显示 token 明文一次。
@@ -330,7 +297,7 @@ npx -y codexpanel login --token-login
330
297
  并调用:
331
298
 
332
299
  ```text
333
- POST /api/desktop/setup/login-token
300
+ POST /api/iam/desktop/setup/login-token
334
301
  ```
335
302
 
336
303
  请求体包含:
@@ -362,14 +329,12 @@ Windows bootstrap 用 `setupToken` 向 relay 换取当前 flow 的安装上下
362
329
  - `DeviceName`
363
330
  - `Workspace`
364
331
  - `AgentVersion`
365
- - `TunnelEnabled`
366
- - `TunnelLocalHost`
367
- - `TunnelLocalPort`
368
332
 
369
333
  bootstrap 在 Windows 本机做这些事:
370
334
 
371
335
  1. 创建 `%LOCALAPPDATA%\CodexPanelAgent`。
372
- 2. 下载 `desktop-agent.js` 和 `app-server-connector.js` 到本机。
336
+ 2. 下载 `host-runtime.cjs`、`runtime/*.cjs` 和
337
+ `generated/codex-app-server-schemas.ts`。
373
338
  3. 查找 Node 和 Codex 可执行文件。
374
339
  4. 动态选择本地状态面板端口。
375
340
  5. 生成 `agent-runtime.json`。
@@ -380,7 +345,7 @@ bootstrap 在 Windows 本机做这些事:
380
345
  POST /api/desktop/setup/complete
381
346
  ```
382
347
 
383
- 8. 写入本地配置、启动 agent,并打开本地状态面板。
348
+ 8. 写入本地配置、启动 `host-runtime.cjs`,并打开本地状态面板。
384
349
  9. 有限次数确认云端是否已经看到这台设备在线;如果网络或心跳暂时未就绪,安装不会无限等待,会提示查看控制台、本地面板或日志。
385
350
 
386
351
  `agent-runtime.json` 是本机 agent 的主要数据来源,包含:
@@ -390,11 +355,11 @@ POST /api/desktop/setup/complete
390
355
  - deviceId
391
356
  - deviceName
392
357
  - deviceToken 明文
358
+ - deviceWssUrl,最终 Device Agent WSS 入口,HTTPS relay 会派生为 `wss://<host>/device-wss`
393
359
  - workspace
394
360
  - Codex 路径
395
- - app-server listen 地址
361
+ - provider transport(固定为 `stdio://`)
396
362
  - 本地 panel host/port/token
397
- - tunnel 配置
398
363
  - helper 脚本路径
399
364
  - 日志路径
400
365
 
@@ -425,93 +390,47 @@ relay 收到请求后会:
425
390
 
426
391
  ## 技术工作原理:agent 启动后做什么
427
392
 
428
- agent 启动入口是 `desktop-agent.js`。它先读取 `agent-runtime.json`,然后初始化几个循环和服务。
429
-
430
- 启动时立即执行:
431
-
432
- 1. `announceOnline("boot")`
433
- 2. `checkForAgentUpdate("boot")`
434
- 3. `startLocalPanel()`
435
- 4. 如果开启 SSE,执行 `connectSse()`
436
- 5. `startTunnelLoop()`
437
- 6. `startPollLoop()`
438
-
439
- ### 上线注册
440
-
441
- agent 调用:
442
-
443
- ```text
444
- POST /api/device/register
445
- ```
446
-
447
- 请求体来自 `onlinePayload()`,包含:
448
-
449
- - `agentId`
450
- - `userId`
451
- - `deviceId`
452
- - `deviceName`
453
- - host、系统用户、domain、platform
454
- - agentVersion
455
- - desktopStatus
456
- - workspace
457
- - codexPath
458
- - sandbox 和 approvalPolicy
459
- - mode
460
- - appServerListen
461
- - tunnel 状态
462
- - panelUrl 和 panelPort
463
- - npmPackageVersion
464
- - capabilities
465
-
466
- capabilities 会告诉 relay 这台设备能做什么,例如:
467
-
468
- - `codex-cli`
469
- - `app-server-proxy`
470
- - `codex-desktop`
471
- - `threads`
472
- - `turns`
473
- - `approvals`
474
- - `cloudflare-tunnel-client`
475
- - `wss-direct-rpc`
476
-
477
- 注册成功后,agent 还会发一个 `agent.online` event 到:
478
-
479
- ```text
480
- POST /api/events
481
- ```
482
-
483
- ### 控制事件轮询和心跳
484
-
485
- agent 的主循环是 `startPollLoop()`。它每 10 秒执行一次 `pollOnce()`。
393
+ workspace/package agent 的默认入口已经切到 `host-runtime.cjs`,并会读取
394
+ `CODEXPANEL_AGENT_CONFIG` 指向的 `agent-runtime.json`,把 `userId`、`deviceId`、
395
+ `deviceToken`、`workspace`、`codexPath` 和 `deviceWssUrl` 映射成
396
+ `CODEXPANEL_*` 运行环境。relay 当前提供 `/device-wss` upgrade transport,
397
+ 用于 device-token `device.hello`、heartbeat、Agent Protocol event、command result
398
+ 和 `server.command` 下发。旧 `desktop-agent.js` Windows standalone 下载入口已清退。
486
399
 
487
- 每次 poll 调用:
400
+ standalone `host-runtime.cjs` 启动时会先读取 `agent-runtime.json`,然后初始化最终链路组件。
488
401
 
489
- ```text
490
- POST /api/agent/pull-control
491
- ```
402
+ 启动时立即执行:
492
403
 
493
- 请求体包含:
404
+ 1. 创建 `DeviceWssClient`,使用 `deviceToken` 对 `/device-wss` 发送 `device.hello`。
405
+ 2. 启动 `HostRuntimeAdapter`,通过 `stdio://` JSONL 初始化 Codex app-server。
406
+ 3. 通过 WSS 上报 Agent Protocol v1 event、heartbeat、command result 和 resource chunk。
407
+ 4. 启动 `runtime/local-panel.cjs` 提供本机 `/api/status` 和固定 helper action。
494
408
 
495
- - `sinceId`:上次处理到的事件 ID。
496
- - `payload`:当前 `onlinePayload()`。
409
+ legacy tunnel 已退役,agent 包内不再包含 `startTunnelLoop()`、
410
+ cloudflared 启动器或本地 WSS direct RPC 服务。
411
+ legacy poll fallback 也已退役,agent 包内不再包含 `startPollLoop()`、
412
+ `pollOnce()` 或 `/api/agent/pull-control` 控制轮询。
497
413
 
498
- relay 返回:
414
+ ### 上线握手
499
415
 
500
- - 当前设备状态。
501
- - `events`:发给 agent 的控制事件。
502
- - `nextEventId`:下一次轮询游标。
503
- - `agentRelease`:服务端最新 agent release 信息。
504
- - agent client 数等运行信息。
416
+ 安装完成时,`POST /api/desktop/setup/complete` 会把设备记录写成
417
+ `mode=host-runtime`,并保存服务端侧 device token hash。本机 agent 启动后不再信任
418
+ client-supplied user/device 权限字段,而是通过 `/device-wss` 的 `device.hello`
419
+ 使用服务端签发的 device token 建立长连接。
505
420
 
506
- agent 收到后会:
421
+ 握手后,设备能力来自 host runtime 的 provider metadata 和 capability map。设备事件会保留
422
+ Agent Protocol envelope 与 raw provider payload,不再通过旧 `/api/events` 或
423
+ `/api/device/register` 作为主注册路径。
424
+ 旧 `/api/device/register`、`/api/device/update`、`/api/device/ping` 和
425
+ `/api/device/offline` HTTP lifecycle route 已退役;在线、心跳和离线状态均由
426
+ `/device-wss` 的 hello、heartbeat、close/stale heartbeat 决定。
507
427
 
508
- 1. `agentRelease` 检查是否需要自更新。
509
- 2. 对每个 event 调用 `handleBridgeEvent()`。
510
- 3. 更新 `lastPolledEventId`。
511
- 4. 持久化本地 agent state。
512
- 5. 更新本地状态面板里的 `lastHeartbeatAt`。
428
+ ### Legacy 控制事件轮询已删除
513
429
 
514
- 如果请求失败,agent 会记录 `lastHeartbeatError`,10 秒后继续下一轮。失败不会让 agent 退出,这样断网、服务端重启、DNS 抖动后都能自动恢复。
430
+ 最终架构不把轮询作为正常交互路径。`enableLegacyPoll`、
431
+ `CODEXPANEL_AGENT_ENABLE_LEGACY_POLL`、`startPollLoop()`、`pollOnce()` 和
432
+ `POST /api/agent/pull-control` 都已退役;设备命令由服务端 command
433
+ channel 下发,事件由 SSE 或设备 WSS 通道同步。
515
434
 
516
435
  ### 事件如何被处理
517
436
 
@@ -523,7 +442,7 @@ agent 收到后会:
523
442
 
524
443
  当前主要事件类型:
525
444
 
526
- - `agent.app.request`:代理到 Codex app-server,例如 statusstartconnect、listThreads、readThread、startThread、resumeThread、startTurn、interruptresolveServerRequest
445
+ - `server.command`:由最终 `/commands` 网关下发到 host-runtime,例如 listSessionsreadSession、startThread、resumeThread、startTurn、steerTurninterruptTurn、resolveApproval
527
446
  - `task.start`:启动 Codex CLI task,并先发 approval request。
528
447
  - `approval.resolve`:用户批准或拒绝后继续或取消任务。
529
448
  - `run.cancel`:取消当前运行中的 Codex CLI。
@@ -536,57 +455,22 @@ CLI task 启动后,agent 会把 stdout/stderr 和 Codex JSON event 转成 rela
536
455
  - `run.completed`
537
456
  - `agent.error`
538
457
 
539
- ### SSE 辅助通道
540
-
541
- 如果启用 SSE,agent 还会连接:
542
-
543
- ```text
544
- GET /events?role=agent&client=<deviceId>
545
- ```
546
-
547
- SSE 连接成功后,relay 可以直接推事件给 agent。SSE 失败或断开时:
548
-
549
- - 非 200 状态:2 秒后重连。
550
- - 连接 end:1.5 秒后重连。
551
- - 网络 error:1.5 秒后重连。
552
-
553
- 即使 SSE 不可用,10 秒一次的 `/api/agent/pull-control` 轮询仍然是兜底控制通道。
458
+ ### Agent SSE 辅助通道已删除
554
459
 
555
- ### WSS/直连 tunnel 循环
460
+ `GET /events?role=agent&client=<deviceId>` 已退役。最终架构中,`GET /events`
461
+ 只服务 Web / Mobile / Admin 客户端的 SSE 读取;设备侧命令、心跳、事件上报和
462
+ `command.result` 都通过 `/device-wss` 完成。
556
463
 
557
- 如果 `tunnelEnabled` true,agent 会运行 `startTunnelLoop()`。
464
+ ### Legacy WSS/直连 tunnel 已删除
558
465
 
559
- 循环逻辑:
466
+ 最终架构不使用 direct tunnel/domain lease 作为主链路。`--legacy-tunnel`
467
+ 和 `--no-tunnel` 都已退役,CLI 会拒绝这些参数;relay bootstrap 不再写入
468
+ tunnel/cloudflared 配置,agent 包内也不再包含本地 direct RPC tunnel 实现。
560
469
 
561
- 1. 确保本机 tunnel local server 已启动。
562
- 2. 如果没有有效 lease 或 cloudflared 不在运行,调用:
470
+ 历史上的 `/api/domain-lease/*`、cloudflared token、entry URL WSS URL
471
+ 只作为 WS-F 清退对象保留,不再作为新安装或正式产品控制路径。
563
472
 
564
- ```text
565
- POST /api/domain-lease/claim
566
- ```
567
-
568
- 3. 如果已有 lease,调用:
569
-
570
- ```text
571
- POST /api/domain-lease/heartbeat
572
- ```
573
-
574
- 4. relay 返回更新后的 lease,agent 更新本地 tunnel 状态。
575
-
576
- lease 中包含 entry URL、data host、WSS URL、cloudflared token 等直连所需信息。agent 会启动 cloudflared,把外部 WSS 连接导到本机 tunnel local service。
577
-
578
- 如果 heartbeat 失败,并且错误类似:
579
-
580
- - reclaim required
581
- - unknown domain lease
582
- - domain lease is released
583
- - hostname is outdated
584
-
585
- agent 会停止 cloudflared、清空 lease,并在 1 秒后重新 claim。其他失败则按 `TUNNEL_HEARTBEAT_MS` 继续重试。
586
-
587
- ### 自更新循环
588
-
589
- agent 在启动和每次 control poll 时检查服务端 release。
473
+ ### Release 资源更新
590
474
 
591
475
  数据来源:
592
476
 
@@ -594,16 +478,16 @@ agent 在启动和每次 control poll 时检查服务端 release。
594
478
  GET /agent/version
595
479
  ```
596
480
 
597
- 或 `/api/agent/pull-control` 返回的 `agentRelease`。
598
-
599
- 如果 `agentVersion` 与本地不同,agent 会下载:
481
+ 安装器会预检并下载:
600
482
 
601
483
  ```text
602
- GET /agent/desktop-agent.js
603
- GET /agent/app-server-connector.js
484
+ GET /agent/host-runtime.cjs
485
+ GET /agent/runtime/*.cjs
486
+ GET /agent/generated/codex-app-server-schemas.ts
604
487
  ```
605
488
 
606
- 下载后校验 sha256,替换本地文件,发送 `agent.update.started`,停止 cloudflared,启动新 agent 进程,再退出旧进程。如果更新失败,发送 `agent.update.failed`,保留旧 agent 继续运行。
489
+ 下载后校验 sha256,再写入本机安装目录。旧 `desktop-agent.js`
490
+ `app-server-connector.js` 不再作为安装、启动或自更新入口。
607
491
 
608
492
  ### 本地状态面板
609
493
 
@@ -636,8 +520,7 @@ POST /api/action
636
520
  - workspace
637
521
  - 当前工作模式、审批权限、sandbox、approvalPolicy、approvalsReviewer
638
522
  - 最近心跳和最近错误
639
- - Codex app-server 状态
640
- - WSS/tunnel 状态
523
+ - host-runtime / Codex stdio bridge 状态
641
524
  - helper 脚本路径
642
525
  - 下一步建议
643
526
 
@@ -658,9 +541,9 @@ POST /api/action
658
541
  交互过程是:
659
542
 
660
543
  1. 用户在控制台选择“默认模式 / 计划模式 / 目标模式”或“请求批准 / 替我审批 / 完全访问”。
661
- 2. 控制台调用 desktop-agent 直连 RPC `setControlState`;如果是 relay 直连 app-server 设备,则调用 `POST /api/app/control-state`。
544
+ 2. 控制台通过服务端控制门面下发状态更新,最终由 agent 处理标准化后的控制事件。
662
545
  3. 电脑端 agent 把标准化后的 `controlState` 写入本机 `agent-runtime.json` 和 agent state,字段包括 `sandbox`、`approvalPolicy`、`approvalsReviewer`、`networkAccess`、`workMode`、`permissionPreset`。
663
- 4. agent 通过心跳、status RPC、本地状态面板 `/api/status` 把真实状态回传给 relay 和控制台。
546
+ 4. agent 通过服务端事件/心跳和本地状态面板 `/api/status` 把真实状态回传给 relay 和控制台。
664
547
  5. 后续 `thread/start`、`thread/resume`、`turn/start` 会从设备已保存的 `controlState` 补默认值,保证真正发给 Codex app-server 的权限和模式与控制台看到的一致。
665
548
 
666
549
  审批权限的映射是:
@@ -688,14 +571,14 @@ POST /api/action
688
571
  | deviceId | 本机 hostname/domain/user/profile hash | 识别同一台电脑和换绑 |
689
572
  | deviceName | CLI 参数或 hostname + 系统用户名 | 展示给用户确认 |
690
573
  | flowId/flowSecret/code | `/api/desktop/setup/start` | 连接 CLI、浏览器和 relay 的短时安装流 |
691
- | session cookie | `/api/auth/login` 或 `/api/auth/register` | 浏览器用户身份 |
574
+ | session cookie | `/api/iam/auth/login` 或 `/api/iam/auth/register` | 浏览器用户身份 |
692
575
  | setupToken | `/api/desktop/setup/approve` 成功后生成 | Windows bootstrap 下载安装上下文 |
693
576
  | deviceToken | setup flow 审批后生成 | agent 长期鉴权,明文只在本机保存 |
694
577
  | deviceTokenHash | relay 计算并保存 | 服务端验证 deviceToken |
695
578
  | panelToken | bootstrap 本机生成 | 本地状态面板鉴权 |
696
- | controlState | 控制台 `/api/app/control-state` 或 desktop-agent `setControlState` RPC | 决定后续 Codex 工作模式、审批路由、sandbox 和网络权限 |
697
- | agentRelease | `/agent/version` 或 pull-control 返回 | agent 自更新 |
698
- | heartbeat 状态 | agent poll tunnel heartbeat | 面板展示、服务端在线状态 |
579
+ | controlState | 服务端控制门面下发的标准控制事件 | 决定后续 Codex 工作模式、审批路由、sandbox 和网络权限 |
580
+ | agentRelease | `/agent/version` | agent 自更新 |
581
+ | heartbeat 状态 | 设备 WSS/SSE 路径 | 面板展示、服务端在线状态 |
699
582
 
700
583
  ## 重试和失败提示
701
584
 
@@ -708,7 +591,6 @@ POST /api/action
708
591
  - PowerShell bootstrap 失败:终端显示安装目录、日志路径和失败原因。
709
592
  - agent 心跳失败:agent 记录 `lastHeartbeatError`,面板展示错误和建议,下一轮继续重试。
710
593
  - SSE 断开:1.5 到 2 秒后重连。
711
- - tunnel lease 失效:停止 cloudflared 并重新 claim。
712
594
  - 自更新失败:记录事件并继续运行旧版本。
713
595
 
714
596
  ## 版本和更新怎么看
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexpanel",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "CodexPanel mobile control plane monorepo.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -28,16 +28,21 @@
28
28
  "verify:version": "node scripts/sync-product-version.mjs --check",
29
29
  "prebuild": "npm run sync:version",
30
30
  "build": "npm run build --workspaces --if-present",
31
- "check": "npm run verify:version && npm run scan:encoding && npm run verify:api-contracts && npm run verify:storage && npm run check --workspaces --if-present",
31
+ "check": "npm run verify:version && npm run scan:encoding && npm run verify:api-contracts && npm run verify:client-gateway && npm run verify:server-gateway-boundary && npm run verify:iam && npm run verify:storage && npm run verify:domain-config && node scripts/money-smoke.cjs && npm run check --workspaces --if-present",
32
32
  "release:local": "node scripts/release-local.mjs",
33
33
  "release:server": "bash scripts/server-autodeploy.sh",
34
+ "release:test": "node scripts/test-release.mjs",
34
35
  "release:production": "node scripts/production-release.mjs",
35
36
  "verify:release": "node scripts/verify-release.mjs",
36
37
  "test:e2e": "playwright test",
37
38
  "scan:encoding": "node scripts/scan-encoding.cjs",
38
39
  "verify:api-contracts": "node scripts/verify-api-contracts.mjs",
40
+ "verify:client-gateway": "node scripts/verify-client-gateway-contract.mjs",
41
+ "verify:server-gateway-boundary": "node scripts/verify-server-gateway-boundary.mjs",
42
+ "verify:iam": "node scripts/verify-iam-contract.mjs && npm run check -w @codexpanel/iam-service",
39
43
  "generate:codex": "npm run generate -w @codexpanel/codex-app-server",
40
44
  "verify:storage": "node scripts/verify-storage-boundary.mjs",
45
+ "verify:domain-config": "node scripts/verify-domain-config.mjs",
41
46
  "smoke:storage": "node scripts/storage-smoke.cjs",
42
47
  "purge:retired-desktop-residue": "node scripts/purge-retired-desktop-residue.mjs"
43
48
  },