@bryanhu/codexly 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.23.0] - 2026-09-18
8
+
9
+ ### 新增
10
+
11
+ - 添加生产级 Docker 镜像与可独立下载的 Docker Compose 配置,支持自定义工作区、Codex Home、端口、配对密码及会话期限。
12
+ - 扩展发布流程,在 npm 包通过验证后同步发布带 SBOM 和 provenance 的 `linux/amd64`、`linux/arm64` GHCR 镜像。
13
+
14
+ ### 修复
15
+
16
+ - 限制项目选择只能落在配置工作区的真实路径内,阻止通过符号链接越过工作区边界。
17
+ - 修复运行中切换自定义 API 后仍复用旧 ChatGPT 认证、导致部分自定义模型不可用的问题。
18
+
7
19
  ## [0.22.1] - 2026-09-16
8
20
 
9
21
  ### 修复
package/README.md CHANGED
@@ -80,6 +80,40 @@ codexly
80
80
 
81
81
  The installation command tries the China mirror first, falls back to the official registry on failure, reuses the npm cache, and revalidates stale package metadata. `||` works in Bash, Zsh, cmd, and PowerShell 7+. In Windows PowerShell 5.1, run the command to the right of `||` separately if the first command fails.
82
82
 
83
+ ## Docker
84
+
85
+ Download the user-facing Compose file and start the published image:
86
+
87
+ ```bash
88
+ mkdir codexly && cd codexly
89
+ curl -fsSLO https://raw.githubusercontent.com/BryanHoo/Codexly/main/compose.yaml
90
+ curl -fsSLo .env.example https://raw.githubusercontent.com/BryanHoo/Codexly/main/.env.example
91
+ cp .env.example .env
92
+ docker compose pull
93
+ docker compose up --detach
94
+ docker compose logs --tail=50 codexly
95
+ ```
96
+
97
+ Open `http://127.0.0.1:3210` and use the random pairing code printed in the logs. The default named volume preserves Codex login data and Codexly's SQLite state. On Linux and macOS, the Compose file mounts the host root at `/workspace`, so every mounted disk is available to the project picker. Set `CODEXLY_WORKSPACE` to expose only one directory:
98
+
99
+ ```bash
100
+ CODEXLY_WORKSPACE=/path/to/projects docker compose up --detach
101
+ ```
102
+
103
+ Docker can only access host paths shared with the Docker engine. On Windows, set `CODEXLY_WORKSPACE` to a shared drive such as `C:/`; add extra bind mounts and repeat `--workspace` in `command` when multiple drives are required.
104
+
105
+ | Variable | Purpose |
106
+ | ----------------------- | -------------------------------------------------------------------- |
107
+ | `CODEXLY_VERSION` | Select the GHCR image tag; defaults to `latest` |
108
+ | `CODEXLY_PORT` | Set both the host and container port; defaults to `3210` |
109
+ | `CODEXLY_WORKSPACE` | Restrict the host directory mounted at `/workspace`; defaults to `/` |
110
+ | `CODEXLY_CODEX_HOME` | Bind a host Codex home instead of the managed `codexly-data` volume |
111
+ | `CODEXLY_LAN_PASSWORD` | Set a strong fixed access password instead of the logged random code |
112
+ | `CODEXLY_ALLOWED_HOSTS` | Allow comma-separated exact reverse proxy domains |
113
+ | `CODEXLY_SESSION_TTL` | Set a fixed session lifetime such as `12h` |
114
+
115
+ The image entrypoint accepts all normal CLI arguments, so orchestration platforms can replace `command` when needed. See the [complete Docker Compose deployment guide](docs/docker-deployment.md) for workspace, multiple-disk, Codex Home, update, backup, and troubleshooting instructions. Run `pnpm docker:build && pnpm docker:test` in a development checkout to build and smoke-test the local image.
116
+
83
117
  ## Usage
84
118
 
85
119
  Select **New task** for work that does not need a project. For repository work, add one or more host directories as ordered project roots, create a task, and submit your request with any required files, images, project references, or Skills. Archived tasks can be restored or permanently deleted from the project task list.
@@ -104,6 +138,7 @@ The terminal prints the LAN address and a random access password. Common options
104
138
  | `--lan-password <password>` | Set a 16-128 character password containing uppercase, lowercase, number, and symbol |
105
139
  | `--allowed-host <domain>` | Allow an exact reverse proxy domain; repeat the option for multiple domains |
106
140
  | `--session-ttl <duration>` | Set a fixed LAN session lifetime such as `12h`; omitted sessions last until server restart |
141
+ | `--workspace <path>` | Restrict project selection to this absolute root; repeat for multiple roots |
107
142
 
108
143
  Quote passwords containing shell-special characters. LAN mode uses unencrypted HTTP, so use it only on a trusted network and never expose it directly to the internet. Restarting Codexly invalidates the password and all sessions.
109
144
 
package/README.zh-CN.md CHANGED
@@ -50,6 +50,40 @@ codexly
50
50
 
51
51
  安装命令优先使用国内镜像,失败后回退官方源,复用 npm 缓存并重新校验过期的包元数据。`||` 适用于 Bash、Zsh、cmd 和 PowerShell 7+;Windows PowerShell 5.1 请在第一条命令失败后单独执行 `||` 右侧的命令。
52
52
 
53
+ ## Docker 部署
54
+
55
+ 下载用户版 Compose 配置并启动已发布镜像:
56
+
57
+ ```bash
58
+ mkdir codexly && cd codexly
59
+ curl -fsSLO https://raw.githubusercontent.com/BryanHoo/Codexly/main/compose.yaml
60
+ curl -fsSLo .env.example https://raw.githubusercontent.com/BryanHoo/Codexly/main/.env.example
61
+ cp .env.example .env
62
+ docker compose pull
63
+ docker compose up --detach
64
+ docker compose logs --tail=50 codexly
65
+ ```
66
+
67
+ 打开 `http://127.0.0.1:3210`,使用日志中的随机配对码。默认命名卷会持久化 Codex 登录数据和 Codexly SQLite 状态。在 Linux 和 macOS 上,Compose 默认把宿主根目录挂载到 `/workspace`,项目选择器可读取所有已挂载磁盘。设置 `CODEXLY_WORKSPACE` 后只暴露指定目录:
68
+
69
+ ```bash
70
+ CODEXLY_WORKSPACE=/path/to/projects docker compose up --detach
71
+ ```
72
+
73
+ Docker 只能访问 Docker Engine 已共享的宿主路径。Windows 需将 `CODEXLY_WORKSPACE` 设置为已共享盘符,例如 `C:/`;需要多个盘符时,在 Compose 中增加 bind mount,并在 `command` 中重复传入 `--workspace`。
74
+
75
+ | 变量 | 用途 |
76
+ | ----------------------- | --------------------------------------------------- |
77
+ | `CODEXLY_VERSION` | 指定 GHCR 镜像标签,默认 `latest` |
78
+ | `CODEXLY_PORT` | 同时设置宿主与容器端口,默认 `3210` |
79
+ | `CODEXLY_WORKSPACE` | 限制挂载到 `/workspace` 的宿主目录,默认 `/` |
80
+ | `CODEXLY_CODEX_HOME` | 绑定宿主 Codex Home,替代默认 `codexly-data` 命名卷 |
81
+ | `CODEXLY_LAN_PASSWORD` | 设置固定强密码,替代日志中生成的随机配对码 |
82
+ | `CODEXLY_ALLOWED_HOSTS` | 设置逗号分隔的反向代理精确域名 |
83
+ | `CODEXLY_SESSION_TTL` | 设置固定会话期限,例如 `12h` |
84
+
85
+ 镜像入口支持全部 CLI 参数,编排平台可按需覆盖 `command`。工作区、多磁盘、Codex Home、更新、备份和故障排查参见[完整 Docker Compose 部署指南](docs/docker-deployment.md)。开发仓库可运行 `pnpm docker:build && pnpm docker:test` 构建并冒烟测试本地镜像。
86
+
53
87
  ## 使用方式
54
88
 
55
89
  不依赖项目时,直接选择“新建任务”。处理仓库时,先添加一个或多个运行端目录作为有序的项目根,再创建任务,并按需附加文件、图片、项目引用或 Skills 后提交需求。归档的任务可以从项目任务列表中恢复或永久删除。
@@ -74,6 +108,7 @@ codexly start --lan
74
108
  | `--lan-password <password>` | 设置 16 至 128 位密码,必须包含大小写字母、数字和符号 |
75
109
  | `--allowed-host <domain>` | 允许精确的反向代理域名;多个域名可重复传入 |
76
110
  | `--session-ttl <duration>` | 设置固定会话期限,如 `12h`;省略时持续到 Server 重启 |
111
+ | `--workspace <path>` | 限制项目选择的绝对根目录;多个根目录可重复传入 |
77
112
 
78
113
  密码含 Shell 特殊字符时请使用引号。局域网模式使用未加密 HTTP,只能用于可信网络,禁止直接暴露到互联网。重启 Codexly 会使密码和全部会话失效。
79
114
 
@@ -1,13 +1,13 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { value_exports } from './chunk-6JWQNFM2.js';
3
- import { require_pino, require_safe_stable_stringify, CodexProtocolMappingError, CodexProviderConnectionError, RpcConnectionClosedError, RpcProtocolError, RpcResponseError, RpcTimeoutError } from './chunk-NUJPWN62.js';
3
+ import { require_pino, require_safe_stable_stringify, CodexProtocolMappingError, CodexProviderConnectionError, RpcConnectionClosedError, RpcProtocolError, RpcResponseError, RpcTimeoutError } from './chunk-PRIMEL2G.js';
4
4
  import { WorkbenchPetProviderError, listTaskCatalog, PendingRequestResolutionError, deleteArchivedTasks, queryCompletedTasks, CompletedTasksQueryError, submitTask, TaskSubmissionError, collectQuestionGroups, formatQuestionAnswers, ProjectTodoError, saveProjectTodo } from './chunk-B2673ODM.js';
5
5
  import { Type, SkillInstallScopeSchema, ScheduledTaskSchema, SubmitTaskResponseSchema, AgentTaskSchema, AsyncQuestionGroupSchema, AnswerAsyncQuestionResultSchema, AgentTurnSchema, DEFAULT_COMMIT_MESSAGE_MODEL, TEMPORARY_TASK_API_PATH, TEMPORARY_TASK_SCOPE_ID, MAX_AGENT_FILE_BYTES, AccessStatusResponseSchema, PairAccessRequestSchema, BingWallpaperCatalogSchema, HealthResponseSchema, AppInfoResponseSchema, AppUpdateProgressResponseSchema, AgentMutationErrorSchema, InstallAppUpdateResponseSchema, InstallAppUpdateRequestSchema, EventStreamMetricsResponseSchema, AgentCapabilitiesSchema, AgentModelPageSchema, AgentGlobalSettingsResponseSchema, AgentGlobalSettingsSchema, MAX_AGENT_IMAGE_BYTES, MAX_AGENT_TEXT_BYTES, GlobalInstructionsSchema, MemorySettingsSchema, AgentPreferencesSchema, SaveGlobalInstructionsSchema, MemorySettingsUpdateSchema, PersonalizationResetSchema, AgentProviderConnectionStatusSchema, StartOfficialProviderLoginResponseSchema, StartOfficialProviderLoginRequestSchema, AgentProviderConnectionMutationResponseSchema, CancelProviderLoginRequestSchema, ConfigureCustomProviderResponseSchema, ConfigureCustomProviderRequestSchema, WorkbenchPetCatalogResponseSchema, WorkbenchPetDownloadResponseSchema, WorkbenchPetDownloadRequestSchema, ProjectPageSchema, HostFileListingSchema, HostFileQuerySchema, ProjectDirectoryListingSchema, ProjectDirectoryQuerySchema, ProjectOpenCapabilitiesResponseSchema, OpenProjectResponseSchema, ProjectRootQuerySchema, OpenProjectRequestSchema, ReorderProjectsResponseSchema, ReorderProjectsRequestSchema, AgentSkillPageSchema, AgentProjectDefaultsResponseSchema, AgentProjectDefaultsSchema, AddProjectResponseSchema, AddProjectRequestSchema, RenameProjectResponseSchema, RenameProjectRequestSchema, RemoveProjectResponseSchema, RemoveProjectRequestSchema, InstalledSkillPageSchema, OfficialPluginPageSchema, OfficialPluginDetailSchema, OfficialPluginInstallResponseSchema, OfficialPluginUninstallResponseSchema, ClawhubSkillPageSchema, ClawhubSkillDetailSchema, ConfiguredMcpServerPageSchema, OpenSkillDirectoryResponseSchema, SetSkillEnabledResponseSchema, SetMcpServerEnabledResponseSchema, SkillInstallResponseSchema, ScheduledTaskPreviewSchema, ScheduledTaskPreviewRequestSchema, ScheduledTaskPageSchema, ScheduledTaskMutationResponseSchema, ScheduledTaskInputSchema, DeleteScheduledTaskResponseSchema, SetScheduledTaskEnabledRequestSchema, AgentTaskPageSchema, UnsubscribeAgentTaskResponseSchema, AgentTaskSnapshotResponseSchema, AgentMcpServerPageSchema, AgentBackgroundTerminalPageSchema, TerminateAgentBackgroundTerminalResponseSchema, PinAgentTaskResponseSchema, PinAgentTaskRequestSchema, RenameAgentTaskResponseSchema, RenameAgentTaskRequestSchema, ArchiveAgentTaskResponseSchema, ArchiveAgentTaskRequestSchema, UnarchiveAgentTaskResponseSchema, UnarchiveAgentTaskRequestSchema, DeleteAgentTaskResponseSchema, DeleteAgentTaskRequestSchema, TaskSearchPageSchema, TaskSearchQuerySchema, SearchOccurrencesPageSchema, SearchOccurrencesQuerySchema, StartAgentTaskResponseSchema, StartAgentTaskRequestSchema, StartAgentTurnResponseSchema, StartAgentTurnRequestSchema, SteerAgentTurnResponseSchema, SteerAgentTurnRequestSchema, InterruptAgentTurnResponseSchema, InterruptAgentTurnRequestSchema, ResolvePendingRequestResponseSchema, ResolvePendingRequestRequestSchema, SubmitTaskRequestSchema, AsyncQuestionPageSchema, AnswerAsyncQuestionResponseSchema, AnswerAsyncQuestionRequestSchema, DismissAsyncQuestionsRequestSchema, ProjectTodoPageSchema, ProjectTodoResponseSchema, ProjectTodoDraftSchema, SaveProjectTodoRequestSchema, DeleteProjectTodoResponseSchema, DeleteProjectTodoRequestSchema, AgentQueuedSubmissionListSchema, AddAgentQueuedSubmissionResponseSchema, AddAgentQueuedSubmissionRequestSchema, UpdateAgentQueuedSubmissionResponseSchema, UpdateAgentQueuedSubmissionRequestSchema, DeleteAgentQueuedSubmissionResponseSchema, ReorderAgentQueuedSubmissionsResponseSchema, ReorderAgentQueuedSubmissionsRequestSchema, StartAgentQueuedSubmissionResponseSchema, StartAgentQueuedSubmissionRequestSchema, AgentPromptInputSchema, AgentProviderConnectionRecordSchema, MAX_AGENT_IMAGES, MAX_AGENT_IMAGE_TOTAL_BYTES, MAX_AGENT_FILE_TOTAL_BYTES, ProjectGitStatusSchema, ProjectGitStatusQuerySchema, ProjectGitHistoryPageSchema, ProjectGitHistoryQuerySchema, ProjectGitCommitFilesPageSchema, ProjectGitCommitFilesQuerySchema, ProjectGitCommitFileDiffSchema, ProjectGitCommitFileDiffQuerySchema, SwitchProjectBranchRequestSchema, CreateProjectBranchRequestSchema, GenerateCommitMessageResponseSchema, GenerateCommitMessageRequestSchema, CommitProjectChangesResponseSchema, CommitProjectChangesRequestSchema, ProjectFileTreeSchema, ProjectFileTreeQuerySchema, ProjectFileSearchPageSchema, ProjectFileSearchQuerySchema, StopProjectFileSearchResponseSchema, StopProjectFileSearchRequestSchema, ProjectSourceFileQuerySchema, ProjectSourceFileSchema, AgentAttachmentUploadResponseSchema, HostFileKindSchema, ImportHostAttachmentRequestSchema, AgentAttachmentKindSchema, AgentTaskSettingsResponseSchema, AgentTaskSettingsSchema, ReloadAgentMcpServersResponseSchema, ReloadAgentMcpServersRequestSchema, ReviewAgentTaskResponseSchema, ReviewAgentTaskRequestSchema, CompactAgentTaskResponseSchema, CompactAgentTaskRequestSchema, ForkAgentTaskResponseSchema, ForkAgentTaskRequestSchema, UploadAgentFeedbackResponseSchema, UploadAgentFeedbackRequestSchema, DeleteArchivedTasksResponseSchema, DeleteArchivedTasksRequestSchema, AgentTaskCatalogSchema, TaskCatalogQuerySchema, CompletedTasksPageSchema, CompletedTasksQuerySchema, UpdateTaskSettingsAndDefaultsResponseSchema, UpdateTaskSettingsAndDefaultsRequestSchema, NavigableAgentTaskResponseSchema, OpenAgentTaskAttachmentResponseSchema, OpenAgentTaskAttachmentRequestSchema, UpdateAgentGoalResponseSchema, UpdateAgentGoalRequestSchema, ClearAgentGoalResponseSchema, ProjectTodoSchema, AgentMessageAttachmentSchema, ProjectGitWorktreePageSchema, CreateProjectWorktreeResponseSchema, CreateProjectWorktreeRequestSchema, ProjectWorktreeMutationResponseSchema, SwitchProjectWorktreeRequestSchema, RenameProjectFileResponseSchema, RenameProjectFileRequestSchema, DeleteProjectFileResponseSchema, DeleteProjectFileRequestSchema, MAX_EVENT_BATCH_SIZE } from './chunk-ZXTTTT25.js';
6
6
  import { __commonJS, __require, __toESM } from './chunk-K6J42667.js';
7
7
  import { Buffer as Buffer$1 } from 'buffer';
8
8
  import { randomBytes, createHash, timingSafeEqual, randomUUID } from 'crypto';
9
9
  import { createWriteStream, constants, createReadStream } from 'fs';
10
- import { mkdtemp, rm, realpath, lstat, stat, readFile, open, mkdir, readdir, access, writeFile, rename, unlink, link, rmdir, readlink } from 'fs/promises';
10
+ import { mkdtemp, rm, realpath, lstat, stat, readFile, open, readdir, mkdir, access, writeFile, rename, unlink, link, rmdir, readlink } from 'fs/promises';
11
11
  import { tmpdir, homedir } from 'os';
12
12
  import { join, isAbsolute, dirname, resolve, relative, sep, basename, extname, normalize, win32, posix } from 'path';
13
13
  import { Transform, Readable } from 'stream';
@@ -13180,49 +13180,49 @@ var require_fast_uri = __commonJS({
13180
13180
  schemelessOptions.skipEscape = true;
13181
13181
  return serialize(resolved, schemelessOptions);
13182
13182
  }
13183
- function resolveComponent(base, relative8, options, skipNormalization) {
13183
+ function resolveComponent(base, relative9, options, skipNormalization) {
13184
13184
  const target = {};
13185
13185
  if (!skipNormalization) {
13186
13186
  base = parse2(serialize(base, options), options);
13187
- relative8 = parse2(serialize(relative8, options), options);
13187
+ relative9 = parse2(serialize(relative9, options), options);
13188
13188
  }
13189
13189
  options = options || {};
13190
- if (!options.tolerant && relative8.scheme) {
13191
- target.scheme = relative8.scheme;
13192
- target.userinfo = relative8.userinfo;
13193
- target.host = relative8.host;
13194
- target.port = relative8.port;
13195
- target.path = removeDotSegments(relative8.path || "");
13196
- target.query = relative8.query;
13190
+ if (!options.tolerant && relative9.scheme) {
13191
+ target.scheme = relative9.scheme;
13192
+ target.userinfo = relative9.userinfo;
13193
+ target.host = relative9.host;
13194
+ target.port = relative9.port;
13195
+ target.path = removeDotSegments(relative9.path || "");
13196
+ target.query = relative9.query;
13197
13197
  } else {
13198
- if (relative8.userinfo !== void 0 || relative8.host !== void 0 || relative8.port !== void 0) {
13199
- target.userinfo = relative8.userinfo;
13200
- target.host = relative8.host;
13201
- target.port = relative8.port;
13202
- target.path = removeDotSegments(relative8.path || "");
13203
- target.query = relative8.query;
13198
+ if (relative9.userinfo !== void 0 || relative9.host !== void 0 || relative9.port !== void 0) {
13199
+ target.userinfo = relative9.userinfo;
13200
+ target.host = relative9.host;
13201
+ target.port = relative9.port;
13202
+ target.path = removeDotSegments(relative9.path || "");
13203
+ target.query = relative9.query;
13204
13204
  } else {
13205
- if (!relative8.path) {
13205
+ if (!relative9.path) {
13206
13206
  target.path = base.path;
13207
- if (relative8.query !== void 0) {
13208
- target.query = relative8.query;
13207
+ if (relative9.query !== void 0) {
13208
+ target.query = relative9.query;
13209
13209
  } else {
13210
13210
  target.query = base.query;
13211
13211
  }
13212
13212
  } else {
13213
- if (relative8.path[0] === "/") {
13214
- target.path = removeDotSegments(relative8.path);
13213
+ if (relative9.path[0] === "/") {
13214
+ target.path = removeDotSegments(relative9.path);
13215
13215
  } else {
13216
13216
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
13217
- target.path = "/" + relative8.path;
13217
+ target.path = "/" + relative9.path;
13218
13218
  } else if (!base.path) {
13219
- target.path = relative8.path;
13219
+ target.path = relative9.path;
13220
13220
  } else {
13221
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative8.path;
13221
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative9.path;
13222
13222
  }
13223
13223
  target.path = removeDotSegments(target.path);
13224
13224
  }
13225
- target.query = relative8.query;
13225
+ target.query = relative9.query;
13226
13226
  }
13227
13227
  target.userinfo = base.userinfo;
13228
13228
  target.host = base.host;
@@ -13230,7 +13230,7 @@ var require_fast_uri = __commonJS({
13230
13230
  }
13231
13231
  target.scheme = base.scheme;
13232
13232
  }
13233
- target.fragment = relative8.fragment;
13233
+ target.fragment = relative9.fragment;
13234
13234
  return target;
13235
13235
  }
13236
13236
  function equal(uriA, uriB, options) {
@@ -19715,49 +19715,49 @@ var require_fast_uri2 = __commonJS({
19715
19715
  schemelessOptions.skipEscape = true;
19716
19716
  return serialize(resolved, schemelessOptions);
19717
19717
  }
19718
- function resolveComponent(base, relative8, options, skipNormalization) {
19718
+ function resolveComponent(base, relative9, options, skipNormalization) {
19719
19719
  const target = {};
19720
19720
  if (!skipNormalization) {
19721
19721
  base = parse2(serialize(base, options), options);
19722
- relative8 = parse2(serialize(relative8, options), options);
19722
+ relative9 = parse2(serialize(relative9, options), options);
19723
19723
  }
19724
19724
  options = options || {};
19725
- if (!options.tolerant && relative8.scheme) {
19726
- target.scheme = relative8.scheme;
19727
- target.userinfo = relative8.userinfo;
19728
- target.host = relative8.host;
19729
- target.port = relative8.port;
19730
- target.path = removeDotSegments(relative8.path || "");
19731
- target.query = relative8.query;
19725
+ if (!options.tolerant && relative9.scheme) {
19726
+ target.scheme = relative9.scheme;
19727
+ target.userinfo = relative9.userinfo;
19728
+ target.host = relative9.host;
19729
+ target.port = relative9.port;
19730
+ target.path = removeDotSegments(relative9.path || "");
19731
+ target.query = relative9.query;
19732
19732
  } else {
19733
- if (relative8.userinfo !== void 0 || relative8.host !== void 0 || relative8.port !== void 0) {
19734
- target.userinfo = relative8.userinfo;
19735
- target.host = relative8.host;
19736
- target.port = relative8.port;
19737
- target.path = removeDotSegments(relative8.path || "");
19738
- target.query = relative8.query;
19733
+ if (relative9.userinfo !== void 0 || relative9.host !== void 0 || relative9.port !== void 0) {
19734
+ target.userinfo = relative9.userinfo;
19735
+ target.host = relative9.host;
19736
+ target.port = relative9.port;
19737
+ target.path = removeDotSegments(relative9.path || "");
19738
+ target.query = relative9.query;
19739
19739
  } else {
19740
- if (!relative8.path) {
19740
+ if (!relative9.path) {
19741
19741
  target.path = base.path;
19742
- if (relative8.query !== void 0) {
19743
- target.query = relative8.query;
19742
+ if (relative9.query !== void 0) {
19743
+ target.query = relative9.query;
19744
19744
  } else {
19745
19745
  target.query = base.query;
19746
19746
  }
19747
19747
  } else {
19748
- if (relative8.path[0] === "/") {
19749
- target.path = removeDotSegments(relative8.path);
19748
+ if (relative9.path[0] === "/") {
19749
+ target.path = removeDotSegments(relative9.path);
19750
19750
  } else {
19751
19751
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
19752
- target.path = "/" + relative8.path;
19752
+ target.path = "/" + relative9.path;
19753
19753
  } else if (!base.path) {
19754
- target.path = relative8.path;
19754
+ target.path = relative9.path;
19755
19755
  } else {
19756
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative8.path;
19756
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative9.path;
19757
19757
  }
19758
19758
  target.path = removeDotSegments(target.path);
19759
19759
  }
19760
- target.query = relative8.query;
19760
+ target.query = relative9.query;
19761
19761
  }
19762
19762
  target.userinfo = base.userinfo;
19763
19763
  target.host = base.host;
@@ -19765,7 +19765,7 @@ var require_fast_uri2 = __commonJS({
19765
19765
  }
19766
19766
  target.scheme = base.scheme;
19767
19767
  }
19768
- target.fragment = relative8.fragment;
19768
+ target.fragment = relative9.fragment;
19769
19769
  return target;
19770
19770
  }
19771
19771
  function equal(uriA, uriB, options) {
@@ -36591,10 +36591,10 @@ var require_resolve_block_map = __commonJS({
36591
36591
  let offset = bm.offset;
36592
36592
  let commentEnd = null;
36593
36593
  for (const collItem of bm.items) {
36594
- const { start, key, sep: sep7, value } = collItem;
36594
+ const { start, key, sep: sep8, value } = collItem;
36595
36595
  const keyProps = resolveProps.resolveProps(start, {
36596
36596
  indicator: "explicit-key-ind",
36597
- next: key ?? sep7?.[0],
36597
+ next: key ?? sep8?.[0],
36598
36598
  offset,
36599
36599
  onError: onError2,
36600
36600
  parentIndent: bm.indent,
@@ -36608,7 +36608,7 @@ var require_resolve_block_map = __commonJS({
36608
36608
  else if ("indent" in key && key.indent !== bm.indent)
36609
36609
  onError2(offset, "BAD_INDENT", startColMsg);
36610
36610
  }
36611
- if (!keyProps.anchor && !keyProps.tag && !sep7) {
36611
+ if (!keyProps.anchor && !keyProps.tag && !sep8) {
36612
36612
  commentEnd = keyProps.end;
36613
36613
  if (keyProps.comment) {
36614
36614
  if (map.comment)
@@ -36632,7 +36632,7 @@ var require_resolve_block_map = __commonJS({
36632
36632
  ctx.atKey = false;
36633
36633
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
36634
36634
  onError2(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
36635
- const valueProps = resolveProps.resolveProps(sep7 ?? [], {
36635
+ const valueProps = resolveProps.resolveProps(sep8 ?? [], {
36636
36636
  indicator: "map-value-ind",
36637
36637
  next: value,
36638
36638
  offset: keyNode.range[2],
@@ -36648,7 +36648,7 @@ var require_resolve_block_map = __commonJS({
36648
36648
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
36649
36649
  onError2(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
36650
36650
  }
36651
- const valueNode = value ? composeNode(ctx, value, valueProps, onError2) : composeEmptyNode(ctx, offset, sep7, null, valueProps, onError2);
36651
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError2) : composeEmptyNode(ctx, offset, sep8, null, valueProps, onError2);
36652
36652
  if (ctx.schema.compat)
36653
36653
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError2);
36654
36654
  offset = valueNode.range[2];
@@ -36737,7 +36737,7 @@ var require_resolve_end = __commonJS({
36737
36737
  let comment = "";
36738
36738
  if (end) {
36739
36739
  let hasSpace = false;
36740
- let sep7 = "";
36740
+ let sep8 = "";
36741
36741
  for (const token of end) {
36742
36742
  const { source, type } = token;
36743
36743
  switch (type) {
@@ -36751,13 +36751,13 @@ var require_resolve_end = __commonJS({
36751
36751
  if (!comment)
36752
36752
  comment = cb;
36753
36753
  else
36754
- comment += sep7 + cb;
36755
- sep7 = "";
36754
+ comment += sep8 + cb;
36755
+ sep8 = "";
36756
36756
  break;
36757
36757
  }
36758
36758
  case "newline":
36759
36759
  if (comment)
36760
- sep7 += source;
36760
+ sep8 += source;
36761
36761
  hasSpace = true;
36762
36762
  break;
36763
36763
  default:
@@ -36799,18 +36799,18 @@ var require_resolve_flow_collection = __commonJS({
36799
36799
  let offset = fc.offset + fc.start.source.length;
36800
36800
  for (let i2 = 0; i2 < fc.items.length; ++i2) {
36801
36801
  const collItem = fc.items[i2];
36802
- const { start, key, sep: sep7, value } = collItem;
36802
+ const { start, key, sep: sep8, value } = collItem;
36803
36803
  const props = resolveProps.resolveProps(start, {
36804
36804
  flow: fcName,
36805
36805
  indicator: "explicit-key-ind",
36806
- next: key ?? sep7?.[0],
36806
+ next: key ?? sep8?.[0],
36807
36807
  offset,
36808
36808
  onError: onError2,
36809
36809
  parentIndent: fc.indent,
36810
36810
  startOnNewline: false
36811
36811
  });
36812
36812
  if (!props.found) {
36813
- if (!props.anchor && !props.tag && !sep7 && !value) {
36813
+ if (!props.anchor && !props.tag && !sep8 && !value) {
36814
36814
  if (i2 === 0 && props.comma)
36815
36815
  onError2(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
36816
36816
  else if (i2 < fc.items.length - 1)
@@ -36864,8 +36864,8 @@ var require_resolve_flow_collection = __commonJS({
36864
36864
  }
36865
36865
  }
36866
36866
  }
36867
- if (!isMap && !sep7 && !props.found) {
36868
- const valueNode = value ? composeNode(ctx, value, props, onError2) : composeEmptyNode(ctx, props.end, sep7, null, props, onError2);
36867
+ if (!isMap && !sep8 && !props.found) {
36868
+ const valueNode = value ? composeNode(ctx, value, props, onError2) : composeEmptyNode(ctx, props.end, sep8, null, props, onError2);
36869
36869
  coll.items.push(valueNode);
36870
36870
  offset = valueNode.range[2];
36871
36871
  if (isBlock(value))
@@ -36877,7 +36877,7 @@ var require_resolve_flow_collection = __commonJS({
36877
36877
  if (isBlock(key))
36878
36878
  onError2(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
36879
36879
  ctx.atKey = false;
36880
- const valueProps = resolveProps.resolveProps(sep7 ?? [], {
36880
+ const valueProps = resolveProps.resolveProps(sep8 ?? [], {
36881
36881
  flow: fcName,
36882
36882
  indicator: "map-value-ind",
36883
36883
  next: value,
@@ -36888,8 +36888,8 @@ var require_resolve_flow_collection = __commonJS({
36888
36888
  });
36889
36889
  if (valueProps.found) {
36890
36890
  if (!isMap && !props.found && ctx.options.strict) {
36891
- if (sep7)
36892
- for (const st of sep7) {
36891
+ if (sep8)
36892
+ for (const st of sep8) {
36893
36893
  if (st === valueProps.found)
36894
36894
  break;
36895
36895
  if (st.type === "newline") {
@@ -36906,7 +36906,7 @@ var require_resolve_flow_collection = __commonJS({
36906
36906
  else
36907
36907
  onError2(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
36908
36908
  }
36909
- const valueNode = value ? composeNode(ctx, value, valueProps, onError2) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep7, null, valueProps, onError2) : null;
36909
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError2) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep8, null, valueProps, onError2) : null;
36910
36910
  if (valueNode) {
36911
36911
  if (isBlock(value))
36912
36912
  onError2(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -37084,7 +37084,7 @@ var require_resolve_block_scalar = __commonJS({
37084
37084
  chompStart = i2 + 1;
37085
37085
  }
37086
37086
  let value = "";
37087
- let sep7 = "";
37087
+ let sep8 = "";
37088
37088
  let prevMoreIndented = false;
37089
37089
  for (let i2 = 0; i2 < contentStart; ++i2)
37090
37090
  value += lines[i2][0].slice(trimIndent) + "\n";
@@ -37101,24 +37101,24 @@ var require_resolve_block_scalar = __commonJS({
37101
37101
  indent = "";
37102
37102
  }
37103
37103
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
37104
- value += sep7 + indent.slice(trimIndent) + content;
37105
- sep7 = "\n";
37104
+ value += sep8 + indent.slice(trimIndent) + content;
37105
+ sep8 = "\n";
37106
37106
  } else if (indent.length > trimIndent || content[0] === " ") {
37107
- if (sep7 === " ")
37108
- sep7 = "\n";
37109
- else if (!prevMoreIndented && sep7 === "\n")
37110
- sep7 = "\n\n";
37111
- value += sep7 + indent.slice(trimIndent) + content;
37112
- sep7 = "\n";
37107
+ if (sep8 === " ")
37108
+ sep8 = "\n";
37109
+ else if (!prevMoreIndented && sep8 === "\n")
37110
+ sep8 = "\n\n";
37111
+ value += sep8 + indent.slice(trimIndent) + content;
37112
+ sep8 = "\n";
37113
37113
  prevMoreIndented = true;
37114
37114
  } else if (content === "") {
37115
- if (sep7 === "\n")
37115
+ if (sep8 === "\n")
37116
37116
  value += "\n";
37117
37117
  else
37118
- sep7 = "\n";
37118
+ sep8 = "\n";
37119
37119
  } else {
37120
- value += sep7 + content;
37121
- sep7 = " ";
37120
+ value += sep8 + content;
37121
+ sep8 = " ";
37122
37122
  prevMoreIndented = false;
37123
37123
  }
37124
37124
  }
@@ -37299,25 +37299,25 @@ var require_resolve_flow_scalar = __commonJS({
37299
37299
  if (!match)
37300
37300
  return source;
37301
37301
  let res = match[1];
37302
- let sep7 = " ";
37302
+ let sep8 = " ";
37303
37303
  let pos = first2.lastIndex;
37304
37304
  line.lastIndex = pos;
37305
37305
  while (match = line.exec(source)) {
37306
37306
  if (match[1] === "") {
37307
- if (sep7 === "\n")
37308
- res += sep7;
37307
+ if (sep8 === "\n")
37308
+ res += sep8;
37309
37309
  else
37310
- sep7 = "\n";
37310
+ sep8 = "\n";
37311
37311
  } else {
37312
- res += sep7 + match[1];
37313
- sep7 = " ";
37312
+ res += sep8 + match[1];
37313
+ sep8 = " ";
37314
37314
  }
37315
37315
  pos = line.lastIndex;
37316
37316
  }
37317
37317
  const last2 = /[ \t]*(.*)/sy;
37318
37318
  last2.lastIndex = pos;
37319
37319
  match = last2.exec(source);
37320
- return res + sep7 + (match?.[1] ?? "");
37320
+ return res + sep8 + (match?.[1] ?? "");
37321
37321
  }
37322
37322
  function doubleQuotedValue(source, onError2) {
37323
37323
  let res = "";
@@ -38120,14 +38120,14 @@ var require_cst_stringify = __commonJS({
38120
38120
  }
38121
38121
  }
38122
38122
  }
38123
- function stringifyItem({ start, key, sep: sep7, value }) {
38123
+ function stringifyItem({ start, key, sep: sep8, value }) {
38124
38124
  let res = "";
38125
38125
  for (const st of start)
38126
38126
  res += st.source;
38127
38127
  if (key)
38128
38128
  res += stringifyToken(key);
38129
- if (sep7)
38130
- for (const st of sep7)
38129
+ if (sep8)
38130
+ for (const st of sep8)
38131
38131
  res += st.source;
38132
38132
  if (value)
38133
38133
  res += stringifyToken(value);
@@ -39289,18 +39289,18 @@ var require_parser = __commonJS({
39289
39289
  if (this.type === "map-value-ind") {
39290
39290
  const prev = getPrevProps(this.peek(2));
39291
39291
  const start = getFirstKeyStartProps(prev);
39292
- let sep7;
39292
+ let sep8;
39293
39293
  if (scalar.end) {
39294
- sep7 = scalar.end;
39295
- sep7.push(this.sourceToken);
39294
+ sep8 = scalar.end;
39295
+ sep8.push(this.sourceToken);
39296
39296
  delete scalar.end;
39297
39297
  } else
39298
- sep7 = [this.sourceToken];
39298
+ sep8 = [this.sourceToken];
39299
39299
  const map = {
39300
39300
  type: "block-map",
39301
39301
  offset: scalar.offset,
39302
39302
  indent: scalar.indent,
39303
- items: [{ start, key: scalar, sep: sep7 }]
39303
+ items: [{ start, key: scalar, sep: sep8 }]
39304
39304
  };
39305
39305
  this.onKeyLine = true;
39306
39306
  this.stack[this.stack.length - 1] = map;
@@ -39453,15 +39453,15 @@ var require_parser = __commonJS({
39453
39453
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
39454
39454
  const start2 = getFirstKeyStartProps(it.start);
39455
39455
  const key = it.key;
39456
- const sep7 = it.sep;
39457
- sep7.push(this.sourceToken);
39456
+ const sep8 = it.sep;
39457
+ sep8.push(this.sourceToken);
39458
39458
  delete it.key;
39459
39459
  delete it.sep;
39460
39460
  this.stack.push({
39461
39461
  type: "block-map",
39462
39462
  offset: this.offset,
39463
39463
  indent: this.indent,
39464
- items: [{ start: start2, key, sep: sep7 }]
39464
+ items: [{ start: start2, key, sep: sep8 }]
39465
39465
  });
39466
39466
  } else if (start.length > 0) {
39467
39467
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -39655,13 +39655,13 @@ var require_parser = __commonJS({
39655
39655
  const prev = getPrevProps(parent);
39656
39656
  const start = getFirstKeyStartProps(prev);
39657
39657
  fixFlowSeqItems(fc);
39658
- const sep7 = fc.end.splice(1, fc.end.length);
39659
- sep7.push(this.sourceToken);
39658
+ const sep8 = fc.end.splice(1, fc.end.length);
39659
+ sep8.push(this.sourceToken);
39660
39660
  const map = {
39661
39661
  type: "block-map",
39662
39662
  offset: fc.offset,
39663
39663
  indent: fc.indent,
39664
- items: [{ start, key: fc, sep: sep7 }]
39664
+ items: [{ start, key: fc, sep: sep8 }]
39665
39665
  };
39666
39666
  this.onKeyLine = true;
39667
39667
  this.stack[this.stack.length - 1] = map;
@@ -41495,7 +41495,7 @@ var require_parseParams = __commonJS({
41495
41495
  // node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js
41496
41496
  var require_basename = __commonJS({
41497
41497
  "node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
41498
- module.exports = function basename7(path) {
41498
+ module.exports = function basename8(path) {
41499
41499
  if (typeof path !== "string") {
41500
41500
  return "";
41501
41501
  }
@@ -41521,7 +41521,7 @@ var require_multipart = __commonJS({
41521
41521
  var Dicer = require_Dicer();
41522
41522
  var parseParams = require_parseParams();
41523
41523
  var decodeText = require_decodeText();
41524
- var basename7 = require_basename();
41524
+ var basename8 = require_basename();
41525
41525
  var getLimit = require_getLimit();
41526
41526
  var RE_BOUNDARY = /^boundary$/i;
41527
41527
  var RE_FIELD = /^form-data$/i;
@@ -41638,7 +41638,7 @@ var require_multipart = __commonJS({
41638
41638
  } else if (RE_FILENAME.test(parsed[i2][0])) {
41639
41639
  filename = parsed[i2][1];
41640
41640
  if (!preservePath) {
41641
- filename = basename7(filename);
41641
+ filename = basename8(filename);
41642
41642
  }
41643
41643
  }
41644
41644
  }
@@ -46561,7 +46561,7 @@ var require_parseTokenList = __commonJS({
46561
46561
  // node_modules/.pnpm/depd@2.0.0/node_modules/depd/index.js
46562
46562
  var require_depd = __commonJS({
46563
46563
  "node_modules/.pnpm/depd@2.0.0/node_modules/depd/index.js"(exports, module) {
46564
- var relative8 = __require("path").relative;
46564
+ var relative9 = __require("path").relative;
46565
46565
  module.exports = depd;
46566
46566
  var basePath = process.cwd();
46567
46567
  function containsNamespace(str, namespace) {
@@ -46753,7 +46753,7 @@ var require_depd = __commonJS({
46753
46753
  return formatted;
46754
46754
  }
46755
46755
  function formatLocation(callSite) {
46756
- return relative8(basePath, callSite[0]) + ":" + callSite[1] + ":" + callSite[2];
46756
+ return relative9(basePath, callSite[0]) + ":" + callSite[1] + ":" + callSite[2];
46757
46757
  }
46758
46758
  function getStack() {
46759
46759
  var limit = Error.stackTraceLimit;
@@ -47283,7 +47283,7 @@ var require_send = __commonJS({
47283
47283
  var join12 = path.join;
47284
47284
  var normalize2 = path.normalize;
47285
47285
  var resolve8 = path.resolve;
47286
- var sep7 = path.sep;
47286
+ var sep8 = path.sep;
47287
47287
  var Readable4 = stream.Readable;
47288
47288
  var BYTES_RANGE_REGEXP = /^ *bytes=/;
47289
47289
  var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1e3;
@@ -47360,20 +47360,20 @@ var require_send = __commonJS({
47360
47360
  let parts;
47361
47361
  if (root !== null) {
47362
47362
  if (path2) {
47363
- path2 = normalize2("." + sep7 + path2);
47363
+ path2 = normalize2("." + sep8 + path2);
47364
47364
  }
47365
47365
  if (UP_PATH_REGEXP.test(path2)) {
47366
47366
  debug2('malicious path "%s"', path2);
47367
47367
  return { statusCode: 403 };
47368
47368
  }
47369
- parts = path2.split(sep7);
47369
+ parts = path2.split(sep8);
47370
47370
  path2 = normalize2(join12(root, path2));
47371
47371
  } else {
47372
47372
  if (UP_PATH_REGEXP.test(path2)) {
47373
47373
  debug2('malicious path "%s"', path2);
47374
47374
  return { statusCode: 403 };
47375
47375
  }
47376
- parts = normalize2(path2).split(sep7);
47376
+ parts = normalize2(path2).split(sep8);
47377
47377
  path2 = resolve8(path2);
47378
47378
  }
47379
47379
  return { path: path2, parts };
@@ -47666,7 +47666,7 @@ var require_send = __commonJS({
47666
47666
  }
47667
47667
  async function sendFile(request2, path2, options) {
47668
47668
  const { error, stat: stat6 } = await tryStat(path2);
47669
- if (error && error.code === "ENOENT" && !extname3(path2) && path2[path2.length - 1] !== sep7) {
47669
+ if (error && error.code === "ENOENT" && !extname3(path2) && path2[path2.length - 1] !== sep8) {
47670
47670
  let err2 = error;
47671
47671
  for (let i2 = 0; i2 < options.extensions.length; i2++) {
47672
47672
  const extension = options.extensions[i2];
@@ -48404,7 +48404,7 @@ var require_static = __commonJS({
48404
48404
  var path = __require("path");
48405
48405
  var { fileURLToPath } = __require("url");
48406
48406
  var { statSync } = __require("fs");
48407
- var { basename: basename7 } = __require("path");
48407
+ var { basename: basename8 } = __require("path");
48408
48408
  var { glob } = require_index_min();
48409
48409
  var fp = require_fastify_plugin();
48410
48410
  var send = require_send2();
@@ -48429,7 +48429,7 @@ var require_static = __commonJS({
48429
48429
  deflate: ".deflate"
48430
48430
  };
48431
48431
  function contentDisposition(filename) {
48432
- return create(basename7(filename));
48432
+ return create(basename8(filename));
48433
48433
  }
48434
48434
  async function fastifyStatic2(fastify, opts) {
48435
48435
  const suppressWarning = opts.suppressWarning ?? false;
@@ -66470,6 +66470,31 @@ var ProjectDirectoryBrowserError = class extends Error {
66470
66470
  }
66471
66471
  reason;
66472
66472
  };
66473
+ function isWithinRoot(root, path) {
66474
+ const relativePath = relative(root, path);
66475
+ return relativePath === "" || !relativePath.startsWith(`..${sep}`) && relativePath !== ".." && !isAbsolute(relativePath);
66476
+ }
66477
+ async function normalizeWorkspaceRoots(roots) {
66478
+ if (roots === void 0) return [];
66479
+ return Promise.all(
66480
+ roots.map(async (root) => {
66481
+ if (!isAbsolute(root)) {
66482
+ throw new ProjectDirectoryBrowserError(
66483
+ "Workspace root path must be absolute",
66484
+ "invalid-directory"
66485
+ );
66486
+ }
66487
+ const normalizedRoot = await realpath(root);
66488
+ if (!(await lstat(normalizedRoot)).isDirectory()) {
66489
+ throw new ProjectDirectoryBrowserError(
66490
+ "Workspace root path must identify a directory",
66491
+ "invalid-directory"
66492
+ );
66493
+ }
66494
+ return normalizedRoot;
66495
+ })
66496
+ );
66497
+ }
66473
66498
  function toDirectoryError(error) {
66474
66499
  const code = error.code;
66475
66500
  const unavailable = code === "EACCES" || code === "EPERM";
@@ -66483,7 +66508,14 @@ function compareDirectories(left, right) {
66483
66508
  return left.name.localeCompare(right.name, "en", { sensitivity: "base" }) || left.name.localeCompare(right.name, "en");
66484
66509
  }
66485
66510
  async function resolveProjectDirectory(requestedPath, options = {}) {
66486
- const path = requestedPath ?? options.homePath ?? homedir();
66511
+ let workspaceRoots;
66512
+ try {
66513
+ workspaceRoots = await normalizeWorkspaceRoots(options.workspaceRoots);
66514
+ } catch (error) {
66515
+ if (error instanceof ProjectDirectoryBrowserError) throw error;
66516
+ throw toDirectoryError(error);
66517
+ }
66518
+ const path = requestedPath ?? workspaceRoots[0] ?? options.homePath ?? homedir();
66487
66519
  if (!isAbsolute(path)) {
66488
66520
  throw new ProjectDirectoryBrowserError(
66489
66521
  "Project directory path must be absolute",
@@ -66498,6 +66530,12 @@ async function resolveProjectDirectory(requestedPath, options = {}) {
66498
66530
  "invalid-directory"
66499
66531
  );
66500
66532
  }
66533
+ if (workspaceRoots.length > 0 && !workspaceRoots.some((root) => isWithinRoot(root, normalizedPath))) {
66534
+ throw new ProjectDirectoryBrowserError(
66535
+ "Project directory is outside the configured workspace roots",
66536
+ "invalid-directory"
66537
+ );
66538
+ }
66501
66539
  return normalizedPath;
66502
66540
  } catch (error) {
66503
66541
  if (error instanceof ProjectDirectoryBrowserError) {
@@ -66507,10 +66545,11 @@ async function resolveProjectDirectory(requestedPath, options = {}) {
66507
66545
  }
66508
66546
  }
66509
66547
  async function readProjectDirectory(requestedPath, options = {}) {
66510
- const [path, roots] = await Promise.all([
66548
+ const [path, workspaceRoots] = await Promise.all([
66511
66549
  resolveProjectDirectory(requestedPath, options),
66512
- (options.filesystemRoots ?? listFilesystemRoots)()
66550
+ normalizeWorkspaceRoots(options.workspaceRoots)
66513
66551
  ]);
66552
+ const roots = workspaceRoots.length > 0 ? workspaceRoots.map((root) => ({ name: basename(root), path: root })) : await (options.filesystemRoots ?? listFilesystemRoots)();
66514
66553
  let entries;
66515
66554
  try {
66516
66555
  const children = await readdir(path, { withFileTypes: true });
@@ -66525,7 +66564,7 @@ async function readProjectDirectory(requestedPath, options = {}) {
66525
66564
  const parentPath = dirname(path);
66526
66565
  return {
66527
66566
  entries,
66528
- parentPath: parentPath === path ? null : parentPath,
66567
+ parentPath: parentPath === path || workspaceRoots.some((root) => root === path) ? null : parentPath,
66529
66568
  path,
66530
66569
  roots
66531
66570
  };
@@ -72961,6 +73000,9 @@ async function createCodexlyServer(options) {
72961
73000
  const readImageFile = options.readProjectImageFile ?? readProjectImageFile;
72962
73001
  const readSourceFile = options.readProjectSourceFile ?? readProjectSourceFile;
72963
73002
  const projectOpenService = options.projectOpenService ?? createProjectOpenService();
73003
+ const workspaceBrowserOptions = options.workspaceRoots === void 0 ? {} : { workspaceRoots: options.workspaceRoots };
73004
+ const browseProjectDirectory = options.readProjectDirectory ?? ((path, browserOptions) => readProjectDirectory(path, { ...browserOptions, ...workspaceBrowserOptions }));
73005
+ const selectProjectDirectory = options.resolveProjectDirectory ?? ((path) => resolveProjectDirectory(path, workspaceBrowserOptions));
72964
73006
  const skillMarketService = options.skillMarketService ?? createSkillMarketService({
72965
73007
  catalog: createClawhubClient(),
72966
73008
  projectOpenService,
@@ -73203,7 +73245,7 @@ async function createCodexlyServer(options) {
73203
73245
  searchProjectFiles,
73204
73246
  stopProjectFileSearch,
73205
73247
  readHostFileDirectory: options.readHostFileDirectory ?? readHostFileDirectory,
73206
- readProjectDirectory: options.readProjectDirectory ?? readProjectDirectory,
73248
+ readProjectDirectory: browseProjectDirectory,
73207
73249
  readImageFile,
73208
73250
  readInheritedTaskSettings,
73209
73251
  readProjectGitHistory: options.readProjectGitHistory ?? readProjectGitHistory,
@@ -73215,7 +73257,7 @@ async function createCodexlyServer(options) {
73215
73257
  releaseProjectContext,
73216
73258
  resolveProviderTurnInput,
73217
73259
  runIdempotent: idempotencyRunner.run,
73218
- resolveProjectDirectory: options.resolveProjectDirectory ?? resolveProjectDirectory,
73260
+ resolveProjectDirectory: selectProjectDirectory,
73219
73261
  resolveHostAttachment: options.resolveHostAttachment ?? resolveHostAttachment,
73220
73262
  readScheduledTaskAttachment: async (projectId, attachmentId) => scheduledTaskAttachmentManager === void 0 ? void 0 : scheduledTaskAttachmentManager.read(projectId, attachmentId),
73221
73263
  settingsRepository: options.settingsRepository,
@@ -12020,6 +12020,7 @@ var CodexProviderConnectionService = class {
12020
12020
  try {
12021
12021
  readProviderCapabilities(await this.#client.request("modelProvider/capabilities/read", {}));
12022
12022
  if (apiKey !== void 0) {
12023
+ await this.#client.request("account/logout");
12023
12024
  const loginResponse = await this.#client.request("account/login/start", {
12024
12025
  apiKey,
12025
12026
  type: "apiKey"
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { createCodexlyServer, SqliteStateRepository, normalizeAllowedHost } from './chunk-4VM2ZUPT.js';
3
+ import { createCodexlyServer, SqliteStateRepository, normalizeAllowedHost } from './chunk-HGSPZCBB.js';
4
4
  import './chunk-6JWQNFM2.js';
5
- import { startCodexAppServer, locateCodexBinary, createCodexRuntimeProvider, createCodexWorkbenchPetProvider, CodexProjectRepository, checkCodexVersion } from './chunk-NUJPWN62.js';
5
+ import { startCodexAppServer, locateCodexBinary, createCodexRuntimeProvider, createCodexWorkbenchPetProvider, CodexProjectRepository, checkCodexVersion } from './chunk-PRIMEL2G.js';
6
6
  import './chunk-B2673ODM.js';
7
7
  import { DEFAULT_COMMIT_MESSAGE_MODEL } from './chunk-ZXTTTT25.js';
8
8
  import './chunk-GT7XTSEG.js';
@@ -22,7 +22,7 @@ createRequire(import.meta.url);
22
22
 
23
23
  // package.json
24
24
  var package_default = {
25
- version: "0.22.1"};
25
+ version: "0.23.0"};
26
26
  var INSTALL_TIMEOUT_MS = 2 * 6e4;
27
27
  function resolveNpmCommandInvocation(args, platform = process.platform, execPath = process.execPath, elevated = false) {
28
28
  const invocation = platform === "win32" ? {
@@ -436,6 +436,8 @@ Start options:
436
436
  --codex-bin <path> Use the Codex executable at the specified path.
437
437
  --codex-home <path> Use a custom Codex home directory instead of CODEX_HOME
438
438
  or the default ~/.codex directory.
439
+ --workspace <path> Restrict project selection to an absolute workspace root.
440
+ May be repeated for multiple roots.
439
441
 
440
442
  Doctor options:
441
443
  --codex-bin <path> Check the Codex executable at the specified path.
@@ -450,6 +452,7 @@ Examples:
450
452
  codexly start --lan --lan-password 'Strong-Lan_Pass9!'
451
453
  codexly start --allowed-host code.example.com
452
454
  codexly start --lan --session-ttl 12h
455
+ codexly start --workspace /workspace
453
456
  codexly doctor --codex-bin /path/to/codex
454
457
  codexly version
455
458
 
@@ -463,7 +466,7 @@ function parseCommandOptions(args, valueOptions, flagOptions = /* @__PURE__ */ n
463
466
  if (!option || !valueOptions.has(option) && !flagOptions.has(option)) {
464
467
  throw new Error(`\u672A\u77E5\u9009\u9879: ${option ?? "<empty>"}`);
465
468
  }
466
- const repeatable = option === "--allowed-host";
469
+ const repeatable = option === "--allowed-host" || option === "--workspace";
467
470
  if (seen.has(option) && !repeatable) {
468
471
  throw new Error(`\u9009\u9879\u91CD\u590D: ${option}`);
469
472
  }
@@ -486,6 +489,8 @@ function parseCommandOptions(args, valueOptions, flagOptions = /* @__PURE__ */ n
486
489
  parsed.lanPassword = value;
487
490
  } else if (option === "--allowed-host") {
488
491
  parsed.allowedHosts = [...parsed.allowedHosts ?? [], value];
492
+ } else if (option === "--workspace") {
493
+ parsed.workspaceRoots = [...parsed.workspaceRoots ?? [], value];
489
494
  } else if (option === "--port") {
490
495
  if (!/^\d+$/u.test(value)) {
491
496
  throw new Error("--port \u5FC5\u987B\u662F 1 \u5230 65535 \u4E4B\u95F4\u7684\u6574\u6570");
@@ -502,6 +507,24 @@ function parseCommandOptions(args, valueOptions, flagOptions = /* @__PURE__ */ n
502
507
  }
503
508
  return parsed;
504
509
  }
510
+ function applyStartEnvironmentDefaults(options, environment) {
511
+ const environmentArgs = [];
512
+ const append = (option, value) => {
513
+ if (value !== void 0 && value.trim() !== "") environmentArgs.push(option, value.trim());
514
+ };
515
+ append("--lan-password", environment["CODEXLY_LAN_PASSWORD"]);
516
+ append("--port", environment["CODEXLY_PORT"]);
517
+ append("--session-ttl", environment["CODEXLY_SESSION_TTL"]);
518
+ append("--workspace", environment["CODEXLY_WORKSPACE"]);
519
+ for (const host of environment["CODEXLY_ALLOWED_HOSTS"]?.split(",") ?? []) {
520
+ append("--allowed-host", host);
521
+ }
522
+ const defaults = parseCommandOptions(
523
+ environmentArgs,
524
+ /* @__PURE__ */ new Set(["--allowed-host", "--lan-password", "--port", "--session-ttl", "--workspace"])
525
+ );
526
+ return { ...defaults, ...options };
527
+ }
505
528
 
506
529
  // src/cli-shutdown.ts
507
530
  function createProcessShutdownSignal() {
@@ -920,17 +943,21 @@ async function runDoctor(args, dependencies, output) {
920
943
  return 0;
921
944
  }
922
945
  async function runStart(args, dependencies, signal, output) {
923
- const options = parseCommandOptions(
924
- args,
925
- /* @__PURE__ */ new Set([
926
- "--allowed-host",
927
- "--codex-bin",
928
- "--codex-home",
929
- "--lan-password",
930
- "--port",
931
- "--session-ttl"
932
- ]),
933
- /* @__PURE__ */ new Set(["--lan"])
946
+ const options = applyStartEnvironmentDefaults(
947
+ parseCommandOptions(
948
+ args,
949
+ /* @__PURE__ */ new Set([
950
+ "--allowed-host",
951
+ "--codex-bin",
952
+ "--codex-home",
953
+ "--lan-password",
954
+ "--port",
955
+ "--session-ttl",
956
+ "--workspace"
957
+ ]),
958
+ /* @__PURE__ */ new Set(["--lan"])
959
+ ),
960
+ process.env
934
961
  );
935
962
  if (options.sessionTtl !== void 0 && options.lan !== true) {
936
963
  throw new Error("--session-ttl \u53EA\u80FD\u4E0E --lan \u4E00\u8D77\u4F7F\u7528");
@@ -1036,8 +1063,9 @@ async function runStart(args, dependencies, signal, output) {
1036
1063
  projectTodoRepository: stateRepository,
1037
1064
  settingsRepository: stateRepository,
1038
1065
  staticRoot: dependencies.webRoot,
1039
- // Standalone Thread 继承 app-server 的 cwd,不再创建伪项目目录。
1040
- standaloneCwd: process.cwd()
1066
+ // 受限部署让临时任务继承首个工作区,避免绕过项目目录边界。
1067
+ standaloneCwd: options.workspaceRoots?.[0] ?? process.cwd(),
1068
+ ...options.workspaceRoots === void 0 ? {} : { workspaceRoots: options.workspaceRoots }
1041
1069
  });
1042
1070
  const host = options.lan === true ? "0.0.0.0" : "127.0.0.1";
1043
1071
  const activePort = await listenOnAvailablePort(server, host, port);
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
- export { CodexAppServerExitedError, CodexAppServerProcess, CodexAppServerShutdownError, CodexAppServerSpawnError, CodexProjectRepository, CodexProtocolMappingError, CodexProviderConnectionError, CodexProviderConnectionService, CodexRuntimeProvider, CodexWorkbenchPetProvider, JsonlRpcClient, RpcConnectionClosedError, RpcProtocolError, RpcResponseError, RpcTimeoutError, SUPPORTED_CODEX_VERSION, SUPPORTED_CODEX_VERSION_RANGE, checkCodexVersion, createCodexRuntimeProvider, createCodexWorkbenchPetProvider, locateCodexBinary, startCodexAppServer } from '../../chunk-NUJPWN62.js';
2
+ export { CodexAppServerExitedError, CodexAppServerProcess, CodexAppServerShutdownError, CodexAppServerSpawnError, CodexProjectRepository, CodexProtocolMappingError, CodexProviderConnectionError, CodexProviderConnectionService, CodexRuntimeProvider, CodexWorkbenchPetProvider, JsonlRpcClient, RpcConnectionClosedError, RpcProtocolError, RpcResponseError, RpcTimeoutError, SUPPORTED_CODEX_VERSION, SUPPORTED_CODEX_VERSION_RANGE, checkCodexVersion, createCodexRuntimeProvider, createCodexWorkbenchPetProvider, locateCodexBinary, startCodexAppServer } from '../../chunk-PRIMEL2G.js';
3
3
  import '../../chunk-B2673ODM.js';
4
4
  import '../../chunk-ZXTTTT25.js';
5
5
  import '../../chunk-GT7XTSEG.js';
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
- export { AccessSessionService, AgentEventStream, GitCommitError, GitCommitReviewError, SqliteStateRepository, commitSelectedProjectChanges, createCodexlyServer, normalizeAllowedHost, readProjectGitCommitFileDiff, readProjectGitCommitFiles } from '../chunk-4VM2ZUPT.js';
2
+ export { AccessSessionService, AgentEventStream, GitCommitError, GitCommitReviewError, SqliteStateRepository, commitSelectedProjectChanges, createCodexlyServer, normalizeAllowedHost, readProjectGitCommitFileDiff, readProjectGitCommitFiles } from '../chunk-HGSPZCBB.js';
3
3
  import '../chunk-6JWQNFM2.js';
4
- import '../chunk-NUJPWN62.js';
4
+ import '../chunk-PRIMEL2G.js';
5
5
  import '../chunk-B2673ODM.js';
6
6
  import '../chunk-ZXTTTT25.js';
7
7
  import '../chunk-GT7XTSEG.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryanhu/codexly",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "通过 Web 操作本地 Coding Agent 的应用",
5
5
  "type": "module",
6
6
  "author": "BryanHu",
@@ -66,6 +66,8 @@
66
66
  "codex:schema:check": "node ./tools/verify-codex-schema.mjs",
67
67
  "codex:schema:update": "node ./tools/verify-codex-schema.mjs --update",
68
68
  "dev": "pnpm --filter @codexly/web dev",
69
+ "docker:build": "docker build --tag codexly:local .",
70
+ "docker:test": "node ./tools/test-docker-image.mjs",
69
71
  "format": "prettier --write .",
70
72
  "format:check": "prettier --check .",
71
73
  "lint": "oxlint . --max-warnings 0 --report-unused-disable-directives",