@covro/covro 0.1.1-dev.8 → 0.2.0-beta.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/README.md CHANGED
@@ -31,10 +31,8 @@
31
31
 
32
32
  - 📱 **Step away from your desk. Stay with the task.** Follow tool activity, inspect file changes, handle permission requests, and answer Codex's questions from your computer or phone. Closing the page doesn't cancel the task.
33
33
  - 🗂️ **See every project in one place.** Run independent tasks in parallel and spot which conversations are working, waiting for approval, or ready to read.
34
- - 💬 **Pick up your Codex CLI conversations.** Browse saved history, resume existing work, or fork a new direction. Rename conversations to find them later and archive them when you're done. View and copy the full Thread ID in conversation details or diagnostics.
35
- - ⚡ **Open long conversations sooner.** Recent messages appear first; scrolling preloads older history. On this device, cached conversation lists and history pages appear while the server checks for updates. [Loading and cache behavior](docs/session-history.md).
34
+ - 💬 **Pick up your Codex CLI conversations.** Browse saved history, resume existing work, or fork a new direction. Rename conversations to find them later and archive them when you're done.
36
35
  - 📖 **Review the work beside the conversation.** Read Markdown documents with code, tables, and images, or open JSON files in a desktop side panel while you keep talking to Codex. JSON previews include indentation, syntax highlighting, and original text for invalid JSON. Copy the unchanged source, with a full-screen reader on mobile. File previews are read-only; Markdown and JSON files have a 2 MiB limit.
37
- - 🔎 **Inspect code changes.** Open Codex’s saved patches for a turn or tool in the same read-only panel. Switch files, inspect line numbers, see the exact words changed within each line, and jump between change blocks.
38
36
  - ✍️ **Switch tasks without losing your draft.** Each conversation keeps its own draft, pasted images, and model settings in the current page. Copy or export results when you're done.
39
37
  - 🖥️ **Run a command beside your task.** Open **Shell** to execute commands in the selected project's directory, follow stdout/stderr, interrupt a run, and reuse recent commands. The panel uses Codex's configured sandbox, with a ten-minute limit and no interactive input. Closing it keeps the command running. [Capabilities and limits](docs/shell.md).
40
38
 
@@ -69,24 +67,19 @@ covro
69
67
 
70
68
  `covro` starts in the background and prints the local address. Open **<http://127.0.0.1:8787>** to get started. Close the terminal and keep working in your browser.
71
69
 
72
- | Command | Purpose |
73
- | ------------------------- | -------------------------------------------------------------------------------------- |
74
- | `covro status` | Show the running instance and its address |
75
- | `covro doctor` | Diagnose the running instance's session and GitHub credentials without printing tokens |
76
- | `covro logs` | Read recent service logs |
77
- | `covro stop` | Stop the service |
78
- | `covro run` | Run in the foreground |
79
- | `covro service install` | Opt in to login auto-start on macOS or Linux |
80
- | `covro service uninstall` | Remove login auto-start |
70
+ | Command | Purpose |
71
+ | ------------------------- | -------------------------------------------- |
72
+ | `covro status` | Show the running instance and its address |
73
+ | `covro logs` | Read recent service logs |
74
+ | `covro stop` | Stop the service |
75
+ | `covro run` | Run in the foreground |
76
+ | `covro service install` | Opt in to login auto-start on macOS or Linux |
77
+ | `covro service uninstall` | Remove login auto-start |
81
78
 
82
79
  The npm package is `@covro/covro`; the command is `covro`. Install the development channel with `npm install -g @covro/covro@dev`.
83
80
 
84
81
  The npm CLI uses `~/.covro/config.json` and a fixed data directory beside it. Reuse an existing configuration with `covro --config /absolute/path/config.json`.
85
82
 
86
- Open **Service configuration → Advanced settings** to edit size limits in MiB (1 MiB = 1024 × 1024 bytes). Decimal values must correspond to whole bytes; existing byte values are preserved exactly. JSON files and the API continue to use bytes. Saved changes take effect after restarting the service.
87
-
88
- On macOS, background startup uses the logged-in user's GUI launchd domain so commands can access existing Keychain credentials when their execution permissions allow it. This does not enable login auto-start; use `covro service install` for that. Without a desktop session, headless startup remains available with a Keychain access notice. After installing this fix, restart existing instances with `covro stop` and `covro start`. See [startup and credential diagnostics](docs/distribution.md#macos-sessions-and-credentials).
89
-
90
83
  <a id="run-from-source"></a>
91
84
 
92
85
  ### 🛠️ Run from source
@@ -151,7 +144,6 @@ Use the browser to follow work, review requests, and continue conversations. An
151
144
  ## Development and contribution
152
145
 
153
146
  ```sh
154
- pnpm format:check
155
147
  pnpm typecheck
156
148
  pnpm lint
157
149
  pnpm test
@@ -161,10 +153,6 @@ pnpm test:e2e
161
153
 
162
154
  Browser tests require the Chromium version used by the existing Playwright dependency. Tests and CI use explicit fixtures; they do not send real model requests.
163
155
 
164
- CI requires Prettier formatting to pass. Run `pnpm format` to fix formatting locally; see [formatting conventions](docs/contributing.md#格式检查) for generated-file exclusions.
165
-
166
- PR titles must follow Conventional Commits, for example `feat(ui): add JSON preview` or `fix: preserve scroll position`. See [PR checks and squash merge conventions](docs/contributing.md) for supported types, Release PRs, and release behavior.
167
-
168
156
  Found a problem or have an idea? [Open an issue](https://github.com/yuzheng14/codex-web-ui/issues). Include your OS, Node.js and Codex versions, installation method, and steps to reproduce. Small, focused PRs are welcome. Update both `README.md` and `README.zh-CN.md` when changing user-facing facts or setup instructions.
169
157
 
170
158
  <a id="documentation"></a>
package/README.zh-CN.md CHANGED
@@ -31,10 +31,8 @@
31
31
 
32
32
  - 📱 **离开电脑,也能跟进任务。** 在电脑或手机上查看工具活动与文件变更、处理权限审批、回答 Codex 的问题。关闭页面,任务仍会继续。
33
33
  - 🗂️ **多个项目,一眼掌握进展。** 让独立任务并行执行,随时看到哪些会话正在工作、等待审批,哪些已有结果可读。
34
- - 💬 **接着你的 Codex CLI 会话。** 浏览已有历史、恢复工作,或 Fork 一个新方向;为会话命名以便查找,完成后归档。在会话详情或状态诊断中查看、复制完整 Thread ID。
35
- - ⚡ **更快打开长会话。** 优先显示最近消息,向上滚动时提前加载历史;本机会话列表和历史分片可先显示缓存,再由服务端核对更新。[加载与缓存说明](docs/session-history.md)。
34
+ - 💬 **接着你的 Codex CLI 会话。** 浏览已有历史、恢复工作,或 Fork 一个新方向;为会话命名以便查找,完成后归档。
36
35
  - 📖 **一边看产出,一边继续讨论。** 电脑上将 Markdown 文档、JSON 文件与会话并排打开,继续向 Codex 提问;Markdown 支持代码、表格与图片,JSON 支持格式化缩进、语法高亮,格式无效时保留原文,可复制完整原文,手机上也有全屏阅读器。文件预览保持只读,Markdown 和 JSON 文件限制为 2 MiB。
37
- - 🔎 **逐行查看代码变更。** 在同一只读预览窗口查看某轮或某工具的 Codex 补丁快照,支持切换文件、查看行号、突出显示行内变化的文字和跳转变更区块。
38
36
  - ✍️ **切换任务,草稿还在。** 每个会话分别保留当前页面中的草稿、粘贴图片和模型设置;完成后可以复制或导出结果。
39
37
  - 🖥️ **在任务旁运行命令。** 打开 **Shell**,在所选项目目录执行命令、查看实时标准输出和错误输出、中断执行,并复用近期命令。面板沿用 Codex 配置的沙箱,最长运行十分钟,不支持交互输入;关闭面板后命令仍继续运行。[能力与限制](docs/shell.md)。
40
38
 
@@ -69,24 +67,19 @@ covro
69
67
 
70
68
  `covro` 默认在后台启动,并输出本机访问地址。打开 **<http://127.0.0.1:8787>** 即可开始使用。关掉终端后,仍可继续通过浏览器工作。
71
69
 
72
- | 命令 | 用途 |
73
- | ------------------------- | -------------------------------------------- |
74
- | `covro status` | 查看运行实例与访问地址 |
75
- | `covro doctor` | 诊断运行实例的会话和 GitHub 凭证,不输出令牌 |
76
- | `covro logs` | 查看最近的服务日志 |
77
- | `covro stop` | 停止服务 |
78
- | `covro run` | 在前台运行 |
79
- | `covro service install` | 在 macOS 或 Linux 上主动启用登录自启 |
80
- | `covro service uninstall` | 移除登录自启 |
70
+ | 命令 | 用途 |
71
+ | ------------------------- | ------------------------------------ |
72
+ | `covro status` | 查看运行实例与访问地址 |
73
+ | `covro logs` | 查看最近的服务日志 |
74
+ | `covro stop` | 停止服务 |
75
+ | `covro run` | 在前台运行 |
76
+ | `covro service install` | 在 macOS 或 Linux 上主动启用登录自启 |
77
+ | `covro service uninstall` | 移除登录自启 |
81
78
 
82
79
  npm 包名为 `@covro/covro`,命令为 `covro`。开发渠道使用 `npm install -g @covro/covro@dev` 安装。
83
80
 
84
81
  npm CLI 默认使用 `~/.covro/config.json`,数据目录固定在配置文件旁。可以通过 `covro --config /absolute/path/config.json` 沿用已有配置。
85
82
 
86
- 在**服务配置 → 高级设置**中,以 MiB 编辑大小限制(1 MiB = 1024 × 1024 字节)。小数必须精确对应整数字节;已有字节值会原样保留。JSON 文件和 API 继续使用字节,保存后需重启服务生效。
87
-
88
- macOS 后台启动使用已登录用户的 GUI launchd 域,使权限允许的命令能够访问已有钥匙串凭证;这不会开启登录自启,登录自启仍需显式运行 `covro service install`。没有桌面会话时仍支持 headless 启动,并提示钥匙串访问限制。安装此修复后,已有实例需要执行 `covro stop` 和 `covro start` 重启。详见[启动与凭证诊断](docs/distribution.md#macos-sessions-and-credentials)。
89
-
90
83
  <a id="从源码运行"></a>
91
84
 
92
85
  ### 🛠️ 从源码运行
@@ -151,7 +144,6 @@ Covro 是面向单用户的 Codex CLI Web UI,通过 `codex app-server` 连接
151
144
  ## 开发与参与
152
145
 
153
146
  ```sh
154
- pnpm format:check
155
147
  pnpm typecheck
156
148
  pnpm lint
157
149
  pnpm test
@@ -161,10 +153,6 @@ pnpm test:e2e
161
153
 
162
154
  浏览器测试需要现有 Playwright 依赖所对应的 Chromium。测试与 CI 使用明确的测试夹具,不发起真实模型请求。
163
155
 
164
- CI 要求通过 Prettier 格式检查。可在本地运行 `pnpm format` 修复格式,生成文件的排除范围见[格式检查约定](docs/contributing.md#格式检查)。
165
-
166
- PR 标题须符合 Conventional Commits,例如 `feat(ui): add JSON preview` 或 `fix: preserve scroll position`。支持的 type、Release PR 检查及 squash 合并与发版约定见[开发与 PR 约定](docs/contributing.md)。
167
-
168
156
  遇到问题或有想法,欢迎[提交 Issue](https://github.com/yuzheng14/codex-web-ui/issues),附上操作系统、Node.js 与 Codex 版本、安装方式和复现步骤。也欢迎范围明确的小 PR。修改面向使用者的事实或安装步骤时,请同步更新 `README.md` 和 `README.zh-CN.md`。
169
157
 
170
158
  <a id="文档导航"></a>
@@ -6,7 +6,6 @@ import { startGateway } from "../server/main.js";
6
6
  import { packageInfo, paths } from "./paths.js";
7
7
  import { UpdateManager } from "../updates/manager.js";
8
8
  import { saveJson } from "../updates/files.js";
9
- import { doctor, launchContext } from "./doctor.js";
10
9
  async function runDaemon(configPath) {
11
10
  const location = paths(configPath);
12
11
  await mkdir(location.state, { recursive: true, mode: 448 });
@@ -14,7 +13,6 @@ async function runDaemon(configPath) {
14
13
  let config;
15
14
  let runtime;
16
15
  let updates;
17
- const context = await launchContext();
18
16
  const server = createServer((request, response) => {
19
17
  void (async () => {
20
18
  response.setHeader("content-type", "application/json");
@@ -35,12 +33,9 @@ async function runDaemon(configPath) {
35
33
  dataDir: config.dataDir,
36
34
  maintenance: runtime?.service.isInMaintenance(),
37
35
  codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,
38
- codexReady: runtime?.codex.ready ?? false,
39
- launchContext: context
36
+ codexReady: runtime?.codex.ready ?? false
40
37
  })
41
38
  );
42
- } else if (request.url === "/doctor") {
43
- response.end(JSON.stringify(await doctor()));
44
39
  } else if (request.url === "/stop") {
45
40
  await stop(() => response.end(JSON.stringify({ stopped: true })));
46
41
  } else if (request.url === "/prepare-update") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/daemon.ts"],
4
- "sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\nimport { doctor, launchContext } from './doctor.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const context = await launchContext();\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n launchContext: context,\n }),\n );\n } else if (request.url === '/doctor') {\n response.end(JSON.stringify(await doctor()));\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createServer } from 'node:http';\nimport { chmod, mkdir, rm } from 'node:fs/promises';\nimport { acquireInstanceLock } from '../server/instance-lock.js';\nimport { loadConfig } from '../server/config.js';\nimport { startGateway } from '../server/main.js';\nimport { packageInfo, paths } from './paths.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { saveJson } from '../updates/files.js';\n\nexport async function runDaemon(configPath: string) {\n const location = paths(configPath);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const release = await acquireInstanceLock(location.state);\n let config: Awaited<ReturnType<typeof loadConfig>>;\n let runtime: Awaited<ReturnType<typeof startGateway>> | undefined;\n let updates: UpdateManager | undefined;\n const server = createServer((request, response) => {\n void (async () => {\n response.setHeader('content-type', 'application/json');\n if (request.method !== 'POST') throw new Error('Expected POST');\n let body = '';\n for await (const chunk of request) {\n body += String(chunk);\n if (body.length > 8192) throw new Error('Control request too large');\n }\n const input: unknown = body ? JSON.parse(body) : {};\n if (request.url === '/status') {\n response.end(\n JSON.stringify({\n version: packageInfo.version,\n pid: process.pid,\n origin: config.publicOrigin[0],\n configPath,\n dataDir: config.dataDir,\n maintenance: runtime?.service.isInMaintenance(),\n codexVersion: runtime?.codex.diagnostics().codexVersion ?? null,\n codexReady: runtime?.codex.ready ?? false,\n }),\n );\n } else if (request.url === '/stop') {\n await stop(() => response.end(JSON.stringify({ stopped: true })));\n } else if (request.url === '/prepare-update') {\n response.end(JSON.stringify(runtime!.service.prepareUpdate()));\n } else if (request.url === '/cancel-update') {\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ cancelled: true }));\n } else if (request.url === '/activate-update') {\n const id = (input as { id?: unknown }).id;\n if (typeof id !== 'string') throw new Error('Invalid upgrade identity');\n await updates!.commitActivation(id);\n runtime!.service.setMaintenance(false);\n response.end(JSON.stringify({ activated: true }));\n } else if (request.url === '/update-status') {\n response.end(JSON.stringify(await updates!.status()));\n } else if (request.url === '/update-check') {\n const channel = (input as { channel?: unknown }).channel;\n if (channel !== undefined && !['stable', 'beta', 'dev'].includes(String(channel)))\n throw new Error('Invalid channel');\n response.end(\n JSON.stringify(await updates!.check(channel as 'stable' | 'beta' | 'dev' | undefined)),\n );\n } else if (request.url === '/update') {\n response.end(\n JSON.stringify(await updates!.start(input as Parameters<UpdateManager['start']>[0])),\n );\n } else {\n response.statusCode = 404;\n response.end(JSON.stringify({ error: 'Unknown control action' }));\n }\n })().catch((error: unknown) => {\n response.statusCode = 400;\n response.end(\n JSON.stringify({ error: error instanceof Error ? error.message : 'Control failed' }),\n );\n });\n });\n let stopping: Promise<void> | undefined;\n const stop = (respond?: () => void) =>\n (stopping ??= (async () => {\n await runtime?.close();\n respond?.();\n await new Promise<void>((done) => server.close(() => done()));\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await release();\n })());\n try {\n config = await loadConfig({ configPath, development: false });\n updates = new UpdateManager(\n location,\n config.dataDir,\n () => runtime?.codex.diagnostics().codexVersion ?? null,\n );\n await updates.initialize();\n const maintenance = await updates.activationPending();\n runtime = await startGateway(config, { updates, maintenance });\n if (process.platform !== 'win32') await rm(location.socket, { force: true });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(location.socket, done);\n });\n if (process.platform !== 'win32') await chmod(location.socket, 0o600);\n await saveJson(`${location.state}/instance.json`, {\n root: updates.context?.root,\n config: location.config,\n socket: location.socket,\n });\n console.log(`Covro ${packageInfo.version} listening on ${config.publicOrigin[0]}`);\n process.once('SIGINT', () => void stop());\n process.once('SIGTERM', () => void stop());\n } catch (error) {\n updates?.close();\n await runtime?.close();\n await release();\n throw error;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,OAAO,OAAO,UAAU;AACjC,SAAS,2BAA2B;AACpC,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,aAAa,aAAa;AACnC,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAEzB,eAAsB,UAAU,YAAoB;AAClD,QAAM,WAAW,MAAM,UAAU;AACjC,QAAM,MAAM,SAAS,OAAO,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC5D,QAAM,UAAU,MAAM,oBAAoB,SAAS,KAAK;AACxD,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,QAAM,SAAS,aAAa,CAAC,SAAS,aAAa;AACjD,UAAM,YAAY;AAChB,eAAS,UAAU,gBAAgB,kBAAkB;AACrD,UAAI,QAAQ,WAAW,OAAQ,OAAM,IAAI,MAAM,eAAe;AAC9D,UAAI,OAAO;AACX,uBAAiB,SAAS,SAAS;AACjC,gBAAQ,OAAO,KAAK;AACpB,YAAI,KAAK,SAAS,KAAM,OAAM,IAAI,MAAM,2BAA2B;AAAA,MACrE;AACA,YAAM,QAAiB,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAClD,UAAI,QAAQ,QAAQ,WAAW;AAC7B,iBAAS;AAAA,UACP,KAAK,UAAU;AAAA,YACb,SAAS,YAAY;AAAA,YACrB,KAAK,QAAQ;AAAA,YACb,QAAQ,OAAO,aAAa,CAAC;AAAA,YAC7B;AAAA,YACA,SAAS,OAAO;AAAA,YAChB,aAAa,SAAS,QAAQ,gBAAgB;AAAA,YAC9C,cAAc,SAAS,MAAM,YAAY,EAAE,gBAAgB;AAAA,YAC3D,YAAY,SAAS,MAAM,SAAS;AAAA,UACtC,CAAC;AAAA,QACH;AAAA,MACF,WAAW,QAAQ,QAAQ,SAAS;AAClC,cAAM,KAAK,MAAM,SAAS,IAAI,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC;AAAA,MAClE,WAAW,QAAQ,QAAQ,mBAAmB;AAC5C,iBAAS,IAAI,KAAK,UAAU,QAAS,QAAQ,cAAc,CAAC,CAAC;AAAA,MAC/D,WAAW,QAAQ,QAAQ,kBAAkB;AAC3C,gBAAS,QAAQ,eAAe,KAAK;AACrC,iBAAS,IAAI,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC,CAAC;AAAA,MAClD,WAAW,QAAQ,QAAQ,oBAAoB;AAC7C,cAAM,KAAM,MAA2B;AACvC,YAAI,OAAO,OAAO,SAAU,OAAM,IAAI,MAAM,0BAA0B;AACtE,cAAM,QAAS,iBAAiB,EAAE;AAClC,gBAAS,QAAQ,eAAe,KAAK;AACrC,iBAAS,IAAI,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC,CAAC;AAAA,MAClD,WAAW,QAAQ,QAAQ,kBAAkB;AAC3C,iBAAS,IAAI,KAAK,UAAU,MAAM,QAAS,OAAO,CAAC,CAAC;AAAA,MACtD,WAAW,QAAQ,QAAQ,iBAAiB;AAC1C,cAAM,UAAW,MAAgC;AACjD,YAAI,YAAY,UAAa,CAAC,CAAC,UAAU,QAAQ,KAAK,EAAE,SAAS,OAAO,OAAO,CAAC;AAC9E,gBAAM,IAAI,MAAM,iBAAiB;AACnC,iBAAS;AAAA,UACP,KAAK,UAAU,MAAM,QAAS,MAAM,OAAgD,CAAC;AAAA,QACvF;AAAA,MACF,WAAW,QAAQ,QAAQ,WAAW;AACpC,iBAAS;AAAA,UACP,KAAK,UAAU,MAAM,QAAS,MAAM,KAA8C,CAAC;AAAA,QACrF;AAAA,MACF,OAAO;AACL,iBAAS,aAAa;AACtB,iBAAS,IAAI,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,CAAC;AAAA,MAClE;AAAA,IACF,GAAG,EAAE,MAAM,CAAC,UAAmB;AAC7B,eAAS,aAAa;AACtB,eAAS;AAAA,QACP,KAAK,UAAU,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,iBAAiB,CAAC;AAAA,MACrF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,MAAI;AACJ,QAAM,OAAO,CAAC,YACX,cAAc,YAAY;AACzB,UAAM,SAAS,MAAM;AACrB,cAAU;AACV,UAAM,IAAI,QAAc,CAAC,SAAS,OAAO,MAAM,MAAM,KAAK,CAAC,CAAC;AAC5D,QAAI,QAAQ,aAAa,QAAS,OAAM,GAAG,SAAS,QAAQ,EAAE,OAAO,KAAK,CAAC;AAC3E,UAAM,QAAQ;AAAA,EAChB,GAAG;AACL,MAAI;AACF,aAAS,MAAM,WAAW,EAAE,YAAY,aAAa,MAAM,CAAC;AAC5D,cAAU,IAAI;AAAA,MACZ;AAAA,MACA,OAAO;AAAA,MACP,MAAM,SAAS,MAAM,YAAY,EAAE,gBAAgB;AAAA,IACrD;AACA,UAAM,QAAQ,WAAW;AACzB,UAAM,cAAc,MAAM,QAAQ,kBAAkB;AACpD,cAAU,MAAM,aAAa,QAAQ,EAAE,SAAS,YAAY,CAAC;AAC7D,QAAI,QAAQ,aAAa,QAAS,OAAM,GAAG,SAAS,QAAQ,EAAE,OAAO,KAAK,CAAC;AAC3E,UAAM,IAAI,QAAc,CAAC,MAAM,WAAW;AACxC,aAAO,KAAK,SAAS,MAAM;AAC3B,aAAO,OAAO,SAAS,QAAQ,IAAI;AAAA,IACrC,CAAC;AACD,QAAI,QAAQ,aAAa,QAAS,OAAM,MAAM,SAAS,QAAQ,GAAK;AACpE,UAAM,SAAS,GAAG,SAAS,KAAK,kBAAkB;AAAA,MAChD,MAAM,QAAQ,SAAS;AAAA,MACvB,QAAQ,SAAS;AAAA,MACjB,QAAQ,SAAS;AAAA,IACnB,CAAC;AACD,YAAQ,IAAI,SAAS,YAAY,OAAO,iBAAiB,OAAO,aAAa,CAAC,CAAC,EAAE;AACjF,YAAQ,KAAK,UAAU,MAAM,KAAK,KAAK,CAAC;AACxC,YAAQ,KAAK,WAAW,MAAM,KAAK,KAAK,CAAC;AAAA,EAC3C,SAAS,OAAO;AACd,aAAS,MAAM;AACf,UAAM,SAAS,MAAM;AACrB,UAAM,QAAQ;AACd,UAAM;AAAA,EACR;AACF;",
6
6
  "names": []
7
7
  }
package/dist/cli/main.js CHANGED
@@ -1,7 +1,8 @@
1
- import { open } from "node:fs/promises";
1
+ import { spawn } from "node:child_process";
2
+ import { mkdir, open } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
- import { control, isNotRunning } from "./control.js";
4
- import { startBackground as startManagedBackground, stopBackground } from "./background.js";
4
+ import { setTimeout } from "node:timers/promises";
5
+ import { control, isNotRunning, stopAndWait } from "./control.js";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { ensureConfig, packageInfo, packageRoot, paths } from "./paths.js";
7
8
  import { runDaemon } from "./daemon.js";
@@ -46,7 +47,34 @@ async function startBackground(location) {
46
47
  if (!isNotRunning(error)) throw error;
47
48
  }
48
49
  await ensureConfig(location);
49
- return startManagedBackground(location, join(packageRoot, "bin/covro.mjs"));
50
+ await mkdir(location.state, { recursive: true, mode: 448 });
51
+ const log = await open(location.log, "a", 384);
52
+ const child = spawn(
53
+ process.execPath,
54
+ [join(packageRoot, "bin/covro.mjs"), "run", "--config", location.config],
55
+ {
56
+ detached: true,
57
+ stdio: ["ignore", log.fd, log.fd],
58
+ windowsHide: true
59
+ }
60
+ );
61
+ let failure;
62
+ child.once("error", (error) => {
63
+ failure = error;
64
+ });
65
+ child.unref();
66
+ await log.close();
67
+ for (let i = 0; i < 240; i++) {
68
+ if (failure) throw failure;
69
+ try {
70
+ return await control(location, "status");
71
+ } catch (error) {
72
+ if (!isNotRunning(error)) throw error;
73
+ }
74
+ if (child.exitCode !== null) throw new Error(`Covro \u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B ${location.log}`);
75
+ await setTimeout(250);
76
+ }
77
+ throw new Error(`Covro \u5C1A\u672A\u786E\u8BA4\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C covro status \u5E76\u67E5\u770B ${location.log}`);
50
78
  }
51
79
  async function main(args = process.argv.slice(2)) {
52
80
  const { command, subcommand, channel, allowDowngrade, allowUnverifiedCodex, location } = parseArguments(args);
@@ -56,7 +84,7 @@ async function main(args = process.argv.slice(2)) {
56
84
  }
57
85
  if (command === "--help" || command === "help") {
58
86
  console.log(
59
- "Covro\n\ncovro [start|run|stop|status|doctor|logs] [--config FILE]\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\ncovro service install|uninstall [--config FILE]\n\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002doctor \u4ECE\u8FD0\u884C\u5B9E\u4F8B\u68C0\u67E5\u4F1A\u8BDD\u548C GitHub \u51ED\u8BC1\uFF0C\u4E0D\u663E\u793A\u4EE4\u724C\u3002"
87
+ "Covro\n\ncovro [start|run|stop|status|logs] [--config FILE]\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\ncovro service install|uninstall [--config FILE]\n\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002"
60
88
  );
61
89
  return;
62
90
  }
@@ -91,10 +119,10 @@ async function main(args = process.argv.slice(2)) {
91
119
  2
92
120
  )
93
121
  );
94
- } else if (command === "status" || command === "stop" || command === "doctor") {
122
+ } else if (command === "status" || command === "stop") {
95
123
  try {
96
124
  if (command === "stop") {
97
- await stopBackground(location);
125
+ await stopAndWait(location);
98
126
  console.log("Covro \u5DF2\u505C\u6B62");
99
127
  } else console.log(JSON.stringify(await control(location, command), null, 2));
100
128
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/main.ts"],
4
- "sourcesContent": ["import type { UpdateStatus } from '../shared/updates.js';\nimport { open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { control, isNotRunning } from './control.js';\nimport { startBackground as startManagedBackground, stopBackground } from './background.js';\nimport { randomUUID } from 'node:crypto';\nimport { ensureConfig, packageInfo, packageRoot, paths, type CovroPaths } from './paths.js';\nimport { runDaemon } from './daemon.js';\nimport { service } from './service.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { loadConfig } from '../server/config.js';\n\nexport function parseArguments(args: string[]) {\n const positional: string[] = [];\n let configPath: string | undefined;\n let channel: 'stable' | 'beta' | 'dev' | undefined;\n let allowDowngrade = false;\n let allowUnverifiedCodex = false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === '--config' && args[i + 1]) configPath = args[++i];\n else if (arg === '--channel' && ['stable', 'beta', 'dev'].includes(args[i + 1] ?? ''))\n channel = args[++i] as typeof channel;\n else if (arg === '--allow-downgrade') allowDowngrade = true;\n else if (arg === '--allow-unverified-codex') allowUnverifiedCodex = true;\n else if (arg.startsWith('-') && arg !== '--help' && arg !== '--version')\n throw new Error(`Unknown argument: ${arg}`);\n else positional.push(arg);\n }\n const [command = 'start', subcommand] = positional;\n if (positional.length > (['service', 'update'].includes(command) ? 2 : 1))\n throw new Error('Too many arguments');\n if ((channel || allowDowngrade || allowUnverifiedCodex) && command !== 'update')\n throw new Error('\u5347\u7EA7\u9009\u9879\u4EC5\u7528\u4E8E update');\n return {\n command,\n subcommand,\n channel,\n allowDowngrade,\n allowUnverifiedCodex,\n location: paths(configPath),\n };\n}\n\nexport async function startBackground(location: CovroPaths) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n await ensureConfig(location);\n return startManagedBackground(location, join(packageRoot, 'bin/covro.mjs'));\n}\n\nexport async function main(args = process.argv.slice(2)) {\n const { command, subcommand, channel, allowDowngrade, allowUnverifiedCodex, location } =\n parseArguments(args);\n if (command === '--version') {\n console.log(packageInfo.version);\n return;\n }\n if (command === '--help' || command === 'help') {\n console.log(\n 'Covro\\n\\ncovro [start|run|stop|status|doctor|logs] [--config FILE]\\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\\ncovro service install|uninstall [--config FILE]\\n\\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002doctor \u4ECE\u8FD0\u884C\u5B9E\u4F8B\u68C0\u67E5\u4F1A\u8BDD\u548C GitHub \u51ED\u8BC1\uFF0C\u4E0D\u663E\u793A\u4EE4\u724C\u3002',\n );\n return;\n }\n if (command === 'start') console.log(JSON.stringify(await startBackground(location), null, 2));\n else if (command === 'run') {\n await ensureConfig(location);\n await runDaemon(location.config);\n } else if (command === 'update') {\n if (subcommand === 'repair') {\n const config = await loadConfig({ configPath: location.config, development: false });\n const manager = new UpdateManager(location, config.dataDir);\n console.log(JSON.stringify(await manager.repair(), null, 2));\n return;\n }\n if (subcommand && subcommand !== 'status') throw new Error('\u672A\u77E5\u5347\u7EA7\u547D\u4EE4');\n await startBackground(location);\n let codexConfirmation;\n if (allowUnverifiedCodex && !subcommand) {\n const checked = await control<UpdateStatus>(location, 'update-check', { channel });\n const candidate = checked.available;\n if (candidate?.codex.status === 'unverified' && candidate.codex.version)\n codexConfirmation = { version: candidate.codex.version, target: candidate.version };\n }\n console.log(\n JSON.stringify(\n await control(\n location,\n subcommand === 'status' ? 'update-check' : 'update',\n subcommand === 'status'\n ? { channel }\n : { clientOperationId: randomUUID(), channel, allowDowngrade, codexConfirmation },\n ),\n null,\n 2,\n ),\n );\n } else if (command === 'status' || command === 'stop' || command === 'doctor') {\n try {\n if (command === 'stop') {\n await stopBackground(location);\n console.log('Covro \u5DF2\u505C\u6B62');\n } else console.log(JSON.stringify(await control(location, command), null, 2));\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n console.log('Covro \u672A\u8FD0\u884C');\n }\n } else if (command === 'logs') {\n const handle = await open(location.log, 'r');\n try {\n const { size } = await handle.stat();\n const buffer = Buffer.alloc(Math.min(size, 64 * 1024));\n await handle.read(buffer, 0, buffer.length, Math.max(0, size - buffer.length));\n console.log(buffer.toString('utf8'));\n } finally {\n await handle.close();\n }\n } else if (command === 'service' && ['install', 'uninstall'].includes(subcommand ?? '')) {\n await service(location, subcommand === 'install');\n console.log(subcommand === 'install' ? '\u5DF2\u542F\u7528 Covro \u767B\u5F55\u81EA\u542F' : '\u5DF2\u53D6\u6D88 Covro \u767B\u5F55\u81EA\u542F');\n } else throw new Error('\u672A\u77E5\u547D\u4EE4\uFF0C\u8FD0\u884C covro --help \u67E5\u770B\u7528\u6CD5');\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { UpdateStatus } from '../shared/updates.js';\nimport { spawn } from 'node:child_process';\nimport { mkdir, open } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\nimport { control, isNotRunning, stopAndWait } from './control.js';\nimport { randomUUID } from 'node:crypto';\nimport { ensureConfig, packageInfo, packageRoot, paths, type CovroPaths } from './paths.js';\nimport { runDaemon } from './daemon.js';\nimport { service } from './service.js';\nimport { UpdateManager } from '../updates/manager.js';\nimport { loadConfig } from '../server/config.js';\n\nexport function parseArguments(args: string[]) {\n const positional: string[] = [];\n let configPath: string | undefined;\n let channel: 'stable' | 'beta' | 'dev' | undefined;\n let allowDowngrade = false;\n let allowUnverifiedCodex = false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg === '--config' && args[i + 1]) configPath = args[++i];\n else if (arg === '--channel' && ['stable', 'beta', 'dev'].includes(args[i + 1] ?? ''))\n channel = args[++i] as typeof channel;\n else if (arg === '--allow-downgrade') allowDowngrade = true;\n else if (arg === '--allow-unverified-codex') allowUnverifiedCodex = true;\n else if (arg.startsWith('-') && arg !== '--help' && arg !== '--version')\n throw new Error(`Unknown argument: ${arg}`);\n else positional.push(arg);\n }\n const [command = 'start', subcommand] = positional;\n if (positional.length > (['service', 'update'].includes(command) ? 2 : 1))\n throw new Error('Too many arguments');\n if ((channel || allowDowngrade || allowUnverifiedCodex) && command !== 'update')\n throw new Error('\u5347\u7EA7\u9009\u9879\u4EC5\u7528\u4E8E update');\n return {\n command,\n subcommand,\n channel,\n allowDowngrade,\n allowUnverifiedCodex,\n location: paths(configPath),\n };\n}\n\nexport async function startBackground(location: CovroPaths) {\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n await ensureConfig(location);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n const log = await open(location.log, 'a', 0o600);\n const child = spawn(\n process.execPath,\n [join(packageRoot, 'bin/covro.mjs'), 'run', '--config', location.config],\n {\n detached: true,\n stdio: ['ignore', log.fd, log.fd],\n windowsHide: true,\n },\n );\n let failure: Error | undefined;\n child.once('error', (error) => {\n failure = error;\n });\n child.unref();\n await log.close();\n for (let i = 0; i < 240; i++) {\n if (failure) throw failure;\n try {\n return await control(location, 'status');\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n }\n if (child.exitCode !== null) throw new Error(`Covro \u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B ${location.log}`);\n await setTimeout(250);\n }\n throw new Error(`Covro \u5C1A\u672A\u786E\u8BA4\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C covro status \u5E76\u67E5\u770B ${location.log}`);\n}\n\nexport async function main(args = process.argv.slice(2)) {\n const { command, subcommand, channel, allowDowngrade, allowUnverifiedCodex, location } =\n parseArguments(args);\n if (command === '--version') {\n console.log(packageInfo.version);\n return;\n }\n if (command === '--help' || command === 'help') {\n console.log(\n 'Covro\\n\\ncovro [start|run|stop|status|logs] [--config FILE]\\ncovro update [status|repair] [--channel stable|beta|dev] [--allow-downgrade] [--allow-unverified-codex]\\ncovro service install|uninstall [--config FILE]\\n\\n\u9ED8\u8BA4\u540E\u53F0\u8FD0\u884C\uFF1Bservice install \u5355\u72EC\u542F\u7528\u767B\u5F55\u81EA\u542F\u3002',\n );\n return;\n }\n if (command === 'start') console.log(JSON.stringify(await startBackground(location), null, 2));\n else if (command === 'run') {\n await ensureConfig(location);\n await runDaemon(location.config);\n } else if (command === 'update') {\n if (subcommand === 'repair') {\n const config = await loadConfig({ configPath: location.config, development: false });\n const manager = new UpdateManager(location, config.dataDir);\n console.log(JSON.stringify(await manager.repair(), null, 2));\n return;\n }\n if (subcommand && subcommand !== 'status') throw new Error('\u672A\u77E5\u5347\u7EA7\u547D\u4EE4');\n await startBackground(location);\n let codexConfirmation;\n if (allowUnverifiedCodex && !subcommand) {\n const checked = await control<UpdateStatus>(location, 'update-check', { channel });\n const candidate = checked.available;\n if (candidate?.codex.status === 'unverified' && candidate.codex.version)\n codexConfirmation = { version: candidate.codex.version, target: candidate.version };\n }\n console.log(\n JSON.stringify(\n await control(\n location,\n subcommand === 'status' ? 'update-check' : 'update',\n subcommand === 'status'\n ? { channel }\n : { clientOperationId: randomUUID(), channel, allowDowngrade, codexConfirmation },\n ),\n null,\n 2,\n ),\n );\n } else if (command === 'status' || command === 'stop') {\n try {\n if (command === 'stop') {\n await stopAndWait(location);\n console.log('Covro \u5DF2\u505C\u6B62');\n } else console.log(JSON.stringify(await control(location, command), null, 2));\n } catch (error) {\n if (!isNotRunning(error)) throw error;\n console.log('Covro \u672A\u8FD0\u884C');\n }\n } else if (command === 'logs') {\n const handle = await open(location.log, 'r');\n try {\n const { size } = await handle.stat();\n const buffer = Buffer.alloc(Math.min(size, 64 * 1024));\n await handle.read(buffer, 0, buffer.length, Math.max(0, size - buffer.length));\n console.log(buffer.toString('utf8'));\n } finally {\n await handle.close();\n }\n } else if (command === 'service' && ['install', 'uninstall'].includes(subcommand ?? '')) {\n await service(location, subcommand === 'install');\n console.log(subcommand === 'install' ? '\u5DF2\u542F\u7528 Covro \u767B\u5F55\u81EA\u542F' : '\u5DF2\u53D6\u6D88 Covro \u767B\u5F55\u81EA\u542F');\n } else throw new Error('\u672A\u77E5\u547D\u4EE4\uFF0C\u8FD0\u884C covro --help \u67E5\u770B\u7528\u6CD5');\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,9 +5,9 @@ import { join } from "node:path";
5
5
  import { mkdir, rm, writeFile } from "node:fs/promises";
6
6
  import { stopAndWait } from "./control.js";
7
7
  import { saveJson } from "../updates/files.js";
8
- import { ensureConfig, packageRoot } from "./paths.js";
9
- import { macOSLoginService } from "./background.js";
8
+ import { packageRoot } from "./paths.js";
10
9
  const exec = promisify(execFile);
10
+ const xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
11
11
  const unit = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
12
12
  async function service(location, install) {
13
13
  if (!["darwin", "linux"].includes(process.platform))
@@ -16,24 +16,49 @@ async function service(location, install) {
16
16
  );
17
17
  const label = `dev.covro.${location.key}`;
18
18
  const entry = join(packageRoot, "bin/covro.mjs");
19
- if (process.platform === "darwin") {
20
- if (install) await ensureConfig(location);
21
- await macOSLoginService(location, entry, install);
22
- return;
23
- }
24
19
  if (install) {
25
20
  await stopAndWait(location);
26
21
  await mkdir(location.state, { recursive: true, mode: 448 });
27
22
  }
28
- const directory = join(homedir(), ".config/systemd/user");
29
- const filename = join(directory, `${label}.service`);
30
- if (install) {
23
+ if (process.platform === "darwin") {
24
+ const directory = join(homedir(), "Library/LaunchAgents");
25
+ const filename = join(directory, `${label}.plist`);
26
+ const domain = `gui/${process.getuid()}`;
27
+ if (!install) {
28
+ await exec("launchctl", ["bootout", domain, filename]);
29
+ await rm(filename);
30
+ await rm(join(location.state, "service.json"), { force: true });
31
+ return;
32
+ }
31
33
  await mkdir(directory, { recursive: true });
32
- if (/[\r\n]/.test([entry, location.config, location.home, process.env.PATH].join("")))
33
- throw new Error("\u670D\u52A1\u8DEF\u5F84\u548C PATH \u4E0D\u80FD\u5305\u542B\u6362\u884C");
34
+ const args = [process.execPath, entry, "run", "--config", location.config];
34
35
  await writeFile(
35
36
  filename,
36
- `[Unit]
37
+ `<?xml version="1.0" encoding="UTF-8"?>
38
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
+ <plist version="1.0"><dict>
40
+ <key>Label</key><string>${label}</string>
41
+ <key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join("")}</array>
42
+ <key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>30</integer>
43
+ <key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(process.env.PATH ?? "/usr/bin:/bin")}</string><key>COVRO_HOME</key><string>${xml(location.home)}</string></dict>
44
+ <key>StandardOutPath</key><string>${xml(location.log)}</string><key>StandardErrorPath</key><string>${xml(location.log)}</string>
45
+ </dict></plist>
46
+ `,
47
+ { mode: 384 }
48
+ );
49
+ await exec("launchctl", ["enable", `${domain}/${label}`]);
50
+ await exec("launchctl", ["bootstrap", domain, filename]);
51
+ await saveJson(join(location.state, "service.json"), { platform: "darwin", label });
52
+ } else {
53
+ const directory = join(homedir(), ".config/systemd/user");
54
+ const filename = join(directory, `${label}.service`);
55
+ if (install) {
56
+ await mkdir(directory, { recursive: true });
57
+ if (/[\r\n]/.test([entry, location.config, location.home, process.env.PATH].join("")))
58
+ throw new Error("\u670D\u52A1\u8DEF\u5F84\u548C PATH \u4E0D\u80FD\u5305\u542B\u6362\u884C");
59
+ await writeFile(
60
+ filename,
61
+ `[Unit]
37
62
  Description=Covro
38
63
  [Service]
39
64
  ExecStart=${[process.execPath, entry, "run", "--config", location.config].map(unit).join(" ")}
@@ -43,16 +68,17 @@ RestartSec=30
43
68
  [Install]
44
69
  WantedBy=default.target
45
70
  `,
46
- { mode: 384 }
47
- );
48
- await exec("systemctl", ["--user", "daemon-reload"]);
49
- await exec("systemctl", ["--user", "enable", "--now", `${label}.service`]);
50
- await saveJson(join(location.state, "service.json"), { platform: "linux", label });
51
- } else {
52
- await exec("systemctl", ["--user", "disable", "--now", `${label}.service`]);
53
- await rm(filename);
54
- await rm(join(location.state, "service.json"), { force: true });
55
- await exec("systemctl", ["--user", "daemon-reload"]);
71
+ { mode: 384 }
72
+ );
73
+ await exec("systemctl", ["--user", "daemon-reload"]);
74
+ await exec("systemctl", ["--user", "enable", "--now", `${label}.service`]);
75
+ await saveJson(join(location.state, "service.json"), { platform: "linux", label });
76
+ } else {
77
+ await exec("systemctl", ["--user", "disable", "--now", `${label}.service`]);
78
+ await rm(filename);
79
+ await rm(join(location.state, "service.json"), { force: true });
80
+ await exec("systemctl", ["--user", "daemon-reload"]);
81
+ }
56
82
  }
57
83
  }
58
84
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cli/service.ts"],
4
- "sourcesContent": ["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport { stopAndWait } from './control.js';\nimport { saveJson } from '../updates/files.js';\nimport { ensureConfig, packageRoot, type CovroPaths } from './paths.js';\nimport { macOSLoginService } from './background.js';\n\nconst exec = promisify(execFile);\nconst unit = (value: string) =>\n `\"${value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"').replaceAll('%', '%%')}\"`;\n\nexport async function service(location: CovroPaths, install: boolean) {\n if (!['darwin', 'linux'].includes(process.platform))\n throw new Error(\n '\u767B\u5F55\u81EA\u542F\u76EE\u524D\u652F\u6301 macOS launchd \u548C Linux systemd --user\uFF1B\u53EF\u4F7F\u7528 covro start \u540E\u53F0\u8FD0\u884C\u3002',\n );\n const label = `dev.covro.${location.key}`;\n const entry = join(packageRoot, 'bin/covro.mjs');\n if (process.platform === 'darwin') {\n if (install) await ensureConfig(location);\n await macOSLoginService(location, entry, install);\n return;\n }\n if (install) {\n await stopAndWait(location);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n }\n const directory = join(homedir(), '.config/systemd/user');\n const filename = join(directory, `${label}.service`);\n if (install) {\n await mkdir(directory, { recursive: true });\n if (/[\\r\\n]/.test([entry, location.config, location.home, process.env.PATH].join('')))\n throw new Error('\u670D\u52A1\u8DEF\u5F84\u548C PATH \u4E0D\u80FD\u5305\u542B\u6362\u884C');\n await writeFile(\n filename,\n `[Unit]\\nDescription=Covro\\n[Service]\\nExecStart=${[process.execPath, entry, 'run', '--config', location.config].map(unit).join(' ')}\\nEnvironment=${unit(`COVRO_HOME=${location.home}`)} ${unit(`PATH=${process.env.PATH ?? '/usr/bin:/bin'}`)}\\nRestart=on-failure\\nRestartSec=30\\n[Install]\\nWantedBy=default.target\\n`,\n { mode: 0o600 },\n );\n await exec('systemctl', ['--user', 'daemon-reload']);\n await exec('systemctl', ['--user', 'enable', '--now', `${label}.service`]);\n await saveJson(join(location.state, 'service.json'), { platform: 'linux', label });\n } else {\n await exec('systemctl', ['--user', 'disable', '--now', `${label}.service`]);\n await rm(filename);\n await rm(join(location.state, 'service.json'), { force: true });\n await exec('systemctl', ['--user', 'daemon-reload']);\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,OAAO,IAAI,iBAAiB;AACrC,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,cAAc,mBAAoC;AAC3D,SAAS,yBAAyB;AAElC,MAAM,OAAO,UAAU,QAAQ;AAC/B,MAAM,OAAO,CAAC,UACZ,IAAI,MAAM,WAAW,MAAM,MAAM,EAAE,WAAW,KAAK,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAEjF,eAAsB,QAAQ,UAAsB,SAAkB;AACpE,MAAI,CAAC,CAAC,UAAU,OAAO,EAAE,SAAS,QAAQ,QAAQ;AAChD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AACF,QAAM,QAAQ,aAAa,SAAS,GAAG;AACvC,QAAM,QAAQ,KAAK,aAAa,eAAe;AAC/C,MAAI,QAAQ,aAAa,UAAU;AACjC,QAAI,QAAS,OAAM,aAAa,QAAQ;AACxC,UAAM,kBAAkB,UAAU,OAAO,OAAO;AAChD;AAAA,EACF;AACA,MAAI,SAAS;AACX,UAAM,YAAY,QAAQ;AAC1B,UAAM,MAAM,SAAS,OAAO,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EAC9D;AACA,QAAM,YAAY,KAAK,QAAQ,GAAG,sBAAsB;AACxD,QAAM,WAAW,KAAK,WAAW,GAAG,KAAK,UAAU;AACnD,MAAI,SAAS;AACX,UAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAI,SAAS,KAAK,CAAC,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;AAClF,YAAM,IAAI,MAAM,0EAAmB;AACrC,UAAM;AAAA,MACJ;AAAA,MACA;AAAA;AAAA;AAAA,YAAmD,CAAC,QAAQ,UAAU,OAAO,OAAO,YAAY,SAAS,MAAM,EAAE,IAAI,IAAI,EAAE,KAAK,GAAG,CAAC;AAAA,cAAiB,KAAK,cAAc,SAAS,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ,QAAQ,IAAI,QAAQ,eAAe,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAC/O,EAAE,MAAM,IAAM;AAAA,IAChB;AACA,UAAM,KAAK,aAAa,CAAC,UAAU,eAAe,CAAC;AACnD,UAAM,KAAK,aAAa,CAAC,UAAU,UAAU,SAAS,GAAG,KAAK,UAAU,CAAC;AACzE,UAAM,SAAS,KAAK,SAAS,OAAO,cAAc,GAAG,EAAE,UAAU,SAAS,MAAM,CAAC;AAAA,EACnF,OAAO;AACL,UAAM,KAAK,aAAa,CAAC,UAAU,WAAW,SAAS,GAAG,KAAK,UAAU,CAAC;AAC1E,UAAM,GAAG,QAAQ;AACjB,UAAM,GAAG,KAAK,SAAS,OAAO,cAAc,GAAG,EAAE,OAAO,KAAK,CAAC;AAC9D,UAAM,KAAK,aAAa,CAAC,UAAU,eAAe,CAAC;AAAA,EACrD;AACF;",
4
+ "sourcesContent": ["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport { stopAndWait } from './control.js';\nimport { saveJson } from '../updates/files.js';\nimport { packageRoot, type CovroPaths } from './paths.js';\n\nconst exec = promisify(execFile);\nconst xml = (value: string) =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\nconst unit = (value: string) =>\n `\"${value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"').replaceAll('%', '%%')}\"`;\n\nexport async function service(location: CovroPaths, install: boolean) {\n if (!['darwin', 'linux'].includes(process.platform))\n throw new Error(\n '\u767B\u5F55\u81EA\u542F\u76EE\u524D\u652F\u6301 macOS launchd \u548C Linux systemd --user\uFF1B\u53EF\u4F7F\u7528 covro start \u540E\u53F0\u8FD0\u884C\u3002',\n );\n const label = `dev.covro.${location.key}`;\n const entry = join(packageRoot, 'bin/covro.mjs');\n if (install) {\n await stopAndWait(location);\n await mkdir(location.state, { recursive: true, mode: 0o700 });\n }\n if (process.platform === 'darwin') {\n const directory = join(homedir(), 'Library/LaunchAgents');\n const filename = join(directory, `${label}.plist`);\n const domain = `gui/${process.getuid!()}`;\n if (!install) {\n await exec('launchctl', ['bootout', domain, filename]);\n await rm(filename);\n await rm(join(location.state, 'service.json'), { force: true });\n return;\n }\n await mkdir(directory, { recursive: true });\n const args = [process.execPath, entry, 'run', '--config', location.config];\n await writeFile(\n filename,\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\\n<plist version=\"1.0\"><dict>\n<key>Label</key><string>${label}</string>\n<key>ProgramArguments</key><array>${args.map((arg) => `<string>${xml(arg)}</string>`).join('')}</array>\n<key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict><key>ThrottleInterval</key><integer>30</integer>\n<key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(process.env.PATH ?? '/usr/bin:/bin')}</string><key>COVRO_HOME</key><string>${xml(location.home)}</string></dict>\n<key>StandardOutPath</key><string>${xml(location.log)}</string><key>StandardErrorPath</key><string>${xml(location.log)}</string>\n</dict></plist>\\n`,\n { mode: 0o600 },\n );\n await exec('launchctl', ['enable', `${domain}/${label}`]);\n await exec('launchctl', ['bootstrap', domain, filename]);\n await saveJson(join(location.state, 'service.json'), { platform: 'darwin', label });\n } else {\n const directory = join(homedir(), '.config/systemd/user');\n const filename = join(directory, `${label}.service`);\n if (install) {\n await mkdir(directory, { recursive: true });\n if (/[\\r\\n]/.test([entry, location.config, location.home, process.env.PATH].join('')))\n throw new Error('\u670D\u52A1\u8DEF\u5F84\u548C PATH \u4E0D\u80FD\u5305\u542B\u6362\u884C');\n await writeFile(\n filename,\n `[Unit]\\nDescription=Covro\\n[Service]\\nExecStart=${[process.execPath, entry, 'run', '--config', location.config].map(unit).join(' ')}\\nEnvironment=${unit(`COVRO_HOME=${location.home}`)} ${unit(`PATH=${process.env.PATH ?? '/usr/bin:/bin'}`)}\\nRestart=on-failure\\nRestartSec=30\\n[Install]\\nWantedBy=default.target\\n`,\n { mode: 0o600 },\n );\n await exec('systemctl', ['--user', 'daemon-reload']);\n await exec('systemctl', ['--user', 'enable', '--now', `${label}.service`]);\n await saveJson(join(location.state, 'service.json'), { platform: 'linux', label });\n } else {\n await exec('systemctl', ['--user', 'disable', '--now', `${label}.service`]);\n await rm(filename);\n await rm(join(location.state, 'service.json'), { force: true });\n await exec('systemctl', ['--user', 'daemon-reload']);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -536,73 +536,6 @@ class CodexAdapter {
536
536
  const sequence = ++this.archiveSequence;
537
537
  return this.storedThread(await this.readThreadHeader(id), sequence);
538
538
  }
539
- async readThreadPage(id, continuation) {
540
- const epoch = this.epoch;
541
- let cursor = null;
542
- if (continuation) {
543
- try {
544
- const decoded = JSON.parse(Buffer.from(continuation, "base64url").toString("utf8"));
545
- if (decoded.threadId !== id || decoded.epoch !== epoch || typeof decoded.cursor !== "string")
546
- throw new Error("Invalid cursor");
547
- cursor = decoded.cursor;
548
- } catch {
549
- throw new AppError("HISTORY_CURSOR_EXPIRED", "\u5386\u53F2\u5206\u9875\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u540C\u6B65\u4F1A\u8BDD\u3002", 409);
550
- }
551
- }
552
- const sequence = ++this.archiveSequence;
553
- const header = await this.readThreadHeader(id);
554
- if (header.historyMode !== "paginated") return this.readThread(id);
555
- const page = await this.call("thread/turns/list", {
556
- threadId: id,
557
- cursor,
558
- limit: 12,
559
- sortDirection: "desc",
560
- itemsView: "summary"
561
- });
562
- if (page.nextCursor && page.nextCursor === cursor)
563
- throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid turn pagination");
564
- await this.hydrateTurns(id, page.data);
565
- if (this.epoch !== epoch)
566
- throw new AppError("HISTORY_CURSOR_EXPIRED", "\u8BFB\u53D6\u671F\u95F4 Codex \u5DF2\u91CD\u542F\uFF0C\u8BF7\u91CD\u65B0\u540C\u6B65\u4F1A\u8BDD\u3002", 409);
567
- const view = await this.storedThread(
568
- { ...header, turns: page.data.slice().reverse() },
569
- sequence
570
- );
571
- return {
572
- ...view,
573
- history: {
574
- olderCursor: page.nextCursor ? Buffer.from(
575
- JSON.stringify({
576
- threadId: id,
577
- epoch,
578
- cursor: page.nextCursor
579
- })
580
- ).toString("base64url") : null
581
- }
582
- };
583
- }
584
- async hydrateTurns(id, turns) {
585
- for (const turn of turns) {
586
- if (turn.itemsView === "full") continue;
587
- let cursor = null;
588
- const seen = /* @__PURE__ */ new Set();
589
- turn.items = [];
590
- do {
591
- const page = await this.call("thread/items/list", {
592
- threadId: id,
593
- turnId: turn.id,
594
- cursor,
595
- limit: 20,
596
- sortDirection: "asc"
597
- });
598
- turn.items.push(...page.data.map((entry) => entry.item));
599
- cursor = page.nextCursor;
600
- if (cursor && (seen.has(cursor) || seen.size >= 1e3))
601
- throw new CodexError("PROTOCOL_INCOMPATIBLE", "Invalid item pagination");
602
- if (cursor) seen.add(cursor);
603
- } while (cursor);
604
- }
605
- }
606
539
  async readThread(id) {
607
540
  const sequence = ++this.archiveSequence;
608
541
  const thread = await this.readThreadHeader(id);
@@ -793,7 +726,7 @@ class CodexAdapter {
793
726
  throw error;
794
727
  }
795
728
  titles?.register(result);
796
- const view = await this.readThreadPage(result.thread.id);
729
+ const view = await this.readThread(result.thread.id);
797
730
  return { ...view, runtimeSettings: await this.responseRuntimeSettings(result) };
798
731
  }
799
732
  async forkThread(id, cwd, settings) {