diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e899086e..b8e82f86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,8 +54,8 @@ jobs: - name: Configure git identity run: | - git config --global user.email "bot@opencode.ai" - git config --global user.name "opencode" + git config --global user.email "bot@deepcode.local" + git config --global user.name "deepcode" - name: Cache Turbo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 @@ -91,7 +91,29 @@ jobs: set -o pipefail bun test test/installation --timeout 30000 2>&1 | tee installation-${{ matrix.settings.name }}.log - - name: Upload unit test log + - name: Run S1-02-E Core isolation tests + working-directory: packages/core + run: | + set -o pipefail + bun test test/global.test.ts test/config.test.ts --timeout 30000 2>&1 | tee boundary-core-${{ matrix.settings.name }}.log + + - name: Run S1-02-E full config and permission regression + working-directory: packages/opencode + run: | + set -o pipefail + bun test test/config test/effect/runtime-flags-deepcode.test.ts test/permission --timeout 30000 2>&1 | tee boundary-opencode-${{ matrix.settings.name }}.log + + - name: Capture S1-02-E OpenCode string inventory + if: runner.os == 'Linux' && always() + run: | + status=0 + git grep -n -I -E 'opencode|OPENCODE' -- packages/core packages/opencode > opencode-boundary-audit.log || status=$? + if [ "$status" -gt 1 ]; then + exit "$status" + fi + cat opencode-boundary-audit.log + + - name: Upload unit and boundary logs if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: @@ -101,6 +123,9 @@ jobs: path: | unit-${{ matrix.settings.name }}.log packages/opencode/installation-${{ matrix.settings.name }}.log + packages/core/boundary-core-${{ matrix.settings.name }}.log + packages/opencode/boundary-opencode-${{ matrix.settings.name }}.log + opencode-boundary-audit.log - name: Check generated client if: runner.os == 'Linux' @@ -116,7 +141,7 @@ jobs: bun run test:httpapi 2>&1 | tee httpapi.log - name: Upload HttpApi log - if: runner.os == 'Linux' && always() + if: runner.os == 'Linux' && always() && hashFiles('packages/opencode/httpapi.log') != '' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: httpapi-${{ github.run_attempt }} @@ -149,7 +174,6 @@ jobs: - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - # Playwright 1.59 hangs while extracting Chromium with Node 24.16. node-version: "24.15" - name: Setup Bun diff --git a/docs/open-source-readiness/HANDOFF_2026-07-28.md b/docs/open-source-readiness/HANDOFF_2026-07-28.md index 4a03c546..9148094d 100644 --- a/docs/open-source-readiness/HANDOFF_2026-07-28.md +++ b/docs/open-source-readiness/HANDOFF_2026-07-28.md @@ -1,141 +1,105 @@ # DeepCode 实施交接提示词(2026-07-28) -> 用途:在新的 ChatGPT / Codex 会话中直接粘贴本文件的“可复制提示词”,从远程仓库继续实施,不依赖旧容器或旧聊天上下文。 +> 用途:在新会话中从 GitHub 远程继续实施,不依赖旧容器、旧工作区或未推送文件。 > > 远程仓库:`https://github.com/yuanchenglu/deepcode.git` ## 当前远程状态 - 开发分支:`develop` -- 发布分支:`master` -- 已合入 `develop`:PR #2,S1-02-C,squash commit `babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8` -- 当前实施分支:`agent/s1-02-d-install-uninstall-guard` -- 当前 PR:#6 `fix(lifecycle): fail closed DeepCode install and uninstall boundaries` -- PR #6 当前基线:`develop@babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8` -- 已验证实现 head:`874dc9c959ccd91320e46d3bf3675c2b30ee861e` -- 已验证 CI:typecheck run #65、test run #67 全部 SUCCESS。 -- 当前代码、测试、CI lifecycle gate 和证据均已写入远程分支,不存在只留在旧容器内的必要修改。 - -## 整体计划进度 - -- 三阶段总计划:实施中;产品仍为 `NO-GO`。 -- 第一阶段:进行中。 +- 发布/默认分支:`master` +- S1-02-C:PR #2,已合入 `develop@babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8` +- S1-02-D:PR #6,已合入 `develop@c848bc537e8c5677c36a360dd00122745a2f5b2e` +- S1-02-E:分支 `coexistence-boundary-audit`,PR #7 +- S1-02-E 已验证代码 head:`2442d6b11b7eb7add9ff6685af04e81c3615c657` +- 已验证 CI:typecheck #116 / id `30354218370` SUCCESS;test #118 / id `30354218292` SUCCESS +- 当前代码、测试、CI、inventory 与 evidence 均在远程;不存在必须依赖旧容器的修改。 + +## 计划进度 + +- 产品发布判断:`NO-GO` +- 第一阶段:`IN_PROGRESS` - S1-01A:`DONE` - - S1-01B:`NOT_STARTED`(官网源码/部署入口和错误安装信息止损) - - S1-02:`IN_PROGRESS` - - A:`DONE` - - B:`DONE` - - C:`DONE`,已合入 `develop` - - D:evidence `DONE`,PR #6 待 squash 合入 - - E:`NOT_STARTED` - - S1-03 及后续第一阶段任务:`NOT_STARTED` -- 第二阶段:全部 `NOT_STARTED`,受 Gate 1 阻塞。 -- 第三阶段:全部 `NOT_STARTED`,受 Gate 2 和设计门禁阻塞。 - -## S1-02-D 已实施内容 - -- 安装检测只认可直接位于 `.deepcode/bin` 的原生 DeepCode 二进制。 -- Unix/Windows 使用原生路径大小写和 `.exe` 语义;嵌套目录、相似目录、OpenCode 和 `.local/bin` 全部 fail-closed。 -- 删除 OpenCode/anomalyco/npm/brew/choco/scoop 等上游版本查询和升级执行路径。 -- S1-03 建立可信 DeepCode Release 渠道前,`latest()` 安全返回当前安装版本。 -- 所有 upgrade method 返回类型化禁用错误,不下载、不执行包管理器或安装脚本。 -- 卸载只处理 DeepCode 自有目录、二进制和精确 shell PATH 分量。 -- 明确保留 `.opencode`、`# opencode`、OpenCode PATH、`.deepcode/bin-backup` 和 `.deepcode/bin-old`。 -- Linux/Windows lifecycle suite 各 17 pass / 0 fail / 93 assertions。 -- typecheck、Linux/Windows unit、generated client、HttpApi 和 Linux/Windows E2E 全部通过。 + - S1-01B:`NOT_STARTED` + - S1-02-A/B/C/D/E 与父任务:`DONE` + - S1-03:下一唯一代码任务 + - S1-04、S1-05:按 PLAN 可并行,但不得与 S1-03 重叠修改 + - S1-06~S1-08:受前置任务阻塞 +- 第二阶段:`NOT_STARTED`,受 Gate 1 阻塞 +- 第三阶段:`NOT_STARTED`,受 Gate 2 与 Design Gate 阻塞 + +## S1-02 最终结论 + +- DeepCode 只默认读取/写入 `deepcode.json(c)`、`.deepcode`、DeepCode Global Path、数据库、日志、项目缓存与环境变量。 +- OpenCode-only 和双配置并存测试证明 `.opencode` / `opencode.json(c)` 不被默认加载或修改。 +- TUI、插件、MCP、ACP、OAuth、Provider headers、Agent/Plan/Session 和 shell 生命周期均已隔离。 +- 安装、upgrade fail-closed、卸载前后 OpenCode shell/目录保持不变。 +- 最终 inventory 4,168 行全部分类,未分类用户边界为 0。 +- launcher、bin、publish、归档、checksum、rollback、purge 和 Release channel 明确属于 S1-03。 ## 开发流程规范 -第一优先:功能分支 → PR → CI → squash 合入 `develop`。 +第一优先:功能分支 → PR → 完整 CI → squash 合入 `develop`。 -只有 PR/CI 持续异常且完成根因分析、尝试修复后仍无法解决,才允许直推 `develop`。直推 commit 必须使用: - -```text -(): <变更说明> - -## 问题原因 -<说明 PR/CI 无法通过的根因和已尝试的修复> - -## 技术债务 -- <遗留事项;无则明确写“无”> -``` - -DeepCode 技术债务也可记录于 `docs/BUG_LIST.md`,格式: - -```text -[日期] 描述 | 遗留原因 | 状态 -``` +只有 PR/CI 持续异常且完成根因分析、最小修复后仍不可解决,才允许直推 `develop`。不得关闭检查、删除失败测试、恢复 OpenCode fallback 或跳过 Windows/Linux unit、E2E、typecheck、HttpApi、lifecycle/config/permission 门禁。 ## 可复制到新会话的提示词 ```text @GitHub yuanchenglu/deepcode -请从远程仓库继续实施 DeepCode 三阶段交付计划。不要依赖任何旧容器或未推送本地文件,远程仓库是唯一事实源。 +请从 GitHub 远程仓库继续实施 DeepCode 三阶段交付计划。不要依赖任何旧容器、旧工作区或未推送本地文件;GitHub 远程仓库是唯一事实源。 -一、先读取并遵守 +一、首先完整读取并遵守 1. 仓库根 AGENTS.md -2. docs/open-source-readiness/PLAN.md +2. docs/open-source-readiness/PLAN.md(应为 v1.3 或更新) 3. docs/open-source-readiness/HANDOFF_2026-07-28.md 4. docs/open-source-readiness/evidence/S1-02/README.md -5. docs/open-source-readiness/evidence/S1-02/test-results/S1-02-D-install-uninstall.md -6. docs/open-source-readiness/evidence/S1-02/test-results/S1-02-D-remote-handoff.md -7. PR #6:fix(lifecycle): fail closed DeepCode install and uninstall boundaries - -二、先核验远程事实 -- develop 当前 HEAD -- PR #6 当前 head、状态、mergeability、changed files -- typecheck/test workflow 的最终结果和失败日志(如有) -- PR #6 是否已经合入 develop -- agent/s1-02-d-install-uninstall-guard 是否仍存在 - -已知基线: -- PR #2 / S1-02-C 已 squash 合入 develop -- merge commit:babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8 -- S1-02-D 当前 PR:#6 -- 已验证实现 head:874dc9c959ccd91320e46d3bf3675c2b30ee861e -- typecheck run #65 / test run #67:SUCCESS - -三、执行规则 -- develop 是开发分支,master 是发布分支。 -- 第一优先走:功能分支 → PR → CI → squash 合入 develop。 -- 不要因为 develop 可直推就默认直推。 -- 只有 PR/CI 持续异常、已完成根因分析并尝试修复仍无法解决时,才直推 develop。 -- 直推 commit 必须包含“## 问题原因”和“## 技术债务”。 -- 每次只领取一个 PLAN 任务或明确子任务,不跨门禁。 -- 不从仓库根目录运行测试;按 PLAN 在对应 package 目录运行。 -- 不关闭检查、不删除失败测试、不通过 fallback 恢复 OpenCode 用户边界。 - -四、当前任务处理 -A. 如果 PR #6 仍开放且最终文档 head CI 全绿: -1. 核验代码和 evidence 与 PLAN 的 S1-02-D 验收条件一致。 -2. squash 合入 develop。 -3. 从最新 develop 创建独立分支,开始 S1-02-E 全链共存回归和字符串分类。 - -B. 如果 PR #6 有失败: -1. 获取失败 job 和完整日志/Artifact。 -2. 分类为 PRODUCT_DEFECT、TEST_DEFECT、ENV_BLOCKED 或 FLAKY。 -3. 做最小根因修复,推回现有 PR 分支。 -4. 重新运行完整门禁,不得跳过 Windows/Linux unit、E2E、typecheck、HttpApi 或双平台 lifecycle suite。 - -C. 如果 PR #6 已合入: -1. 从最新 develop 核验 S1-02-D evidence 是否完整。 -2. 直接创建 S1-02-E 独立分支并实施。 - -五、S1-02-E 的目标 -- 对 packages/core、packages/opencode 中 opencode/OPENCODE 字符串逐条分类:内部命名、上游协议/Schema/Package 兼容、用户边界遗漏。 -- 运行 DeepCode-only、OpenCode-only、双配置并存和生命周期隔离回归。 -- 验证 DeepCode 默认不加载 `.opencode`、`opencode.json(c)`。 -- 验证安装、升级拒绝、卸载前后 OpenCode 目录树和 shell 配置不变。 -- 首先修复整包测试中的历史 OpenCode 断言;不得通过恢复 OpenCode fallback 换取通过。 -- 只修复明确遗漏;构建制品、Release installer、checksum、rollback、purge 属于 S1-03,不提前实现。 -- 全部子任务和整链回归完成后,才能把 S1-02 父任务标记 DONE。 - -请自主执行,不要只做状态汇报。完成当前可完成工作后,明确列出:已合入 develop 的内容、当前分支/PR/commit、CI 结果、下一唯一执行点、仍存在的外部阻塞。 +5. docs/open-source-readiness/evidence/S1-02/test-results/S1-02-E-boundary-audit.md +6. PR #7:test(identity): close S1-02-E coexistence boundary gaps + +二、重新核验远程实时状态 +- develop、master 当前 HEAD +- PR #7 当前 head、状态、mergeability、changed files、reviews/comments +- PR #7 最新 typecheck/test workflow 和所有 job +- PR #7 是否已 squash 合入 develop +- coexistence-boundary-audit 是否仍存在 + +已知验证基线: +- S1-02-D 已合入 develop@c848bc537e8c5677c36a360dd00122745a2f5b2e +- S1-02-E 已验证代码 head:2442d6b11b7eb7add9ff6685af04e81c3615c657 +- typecheck #116 / 30354218370:SUCCESS +- test #118 / 30354218292:SUCCESS +- Linux/Windows unit、lifecycle、完整 config/permission、E2E、generated client、HttpApi 全绿 + +三、执行逻辑 +A. PR #7 仍开放且最终文档 head CI 全绿: +1. 核验 evidence、PLAN 与代码一致。 +2. 核验没有未解决 review thread。 +3. squash 合入 develop。 +4. 从最新 develop 创建独立功能分支,领取 PLAN 的下一唯一代码任务 S1-03。 + +B. PR #7 有失败: +1. 获取失败 job 的完整日志和 artifact。 +2. 分类 PRODUCT_DEFECT、TEST_DEFECT、ENV_BLOCKED 或 FLAKY。 +3. 做最小根因修复并重跑完整 CI,不得放宽门禁。 + +C. PR #7 已合入: +1. 核验 develop 上 S1-02 evidence 完整。 +2. 直接开始 S1-03,不重做 S1-02,不提前实施 S1-04/Release 后续门禁。 + +四、S1-03 目标 +- 建立 DeepCode-owned launcher、二进制/归档命名和跨平台构建矩阵。 +- 建立可信 GitHub Release/manifest/checksum/SBOM 路径。 +- upgrade 只读取 DeepCode-owned channel;支持失败回滚。 +- uninstall 与 --purge 只删除 DeepCode,自始至终保持 OpenCode/Oh-my-OpenAgent 不变。 +- 不宣称官网可安装,除非 S1-01B/S1-07 的真实官网与部署证据已完成。 + +请自主执行,不要只做状态汇报。完成当前可完成工作后,明确列出:已合入 develop 的内容、当前分支/PR/commit、完整 CI、修改文件、证据、下一唯一执行点和外部阻塞。 ``` ## 交接边界 -- 本文件是远程恢复入口,不替代 `PLAN.md`。 -- 新会话必须重新读取 GitHub 当前状态;本文件中的 CI 状态可能在写入后变化。 -- 不得依据本文件直接宣称 PR #6 已合并,必须以 GitHub 实时状态为准。 +- 本文件不替代 PLAN;新会话必须重新读取 GitHub 实时状态。 +- 写入时 PR #7 尚未宣称已合并;是否合并必须以 GitHub 当前状态为准。 +- 产品继续为 NO-GO,直到 Gate 1 全部通过。 diff --git a/docs/open-source-readiness/PLAN.md b/docs/open-source-readiness/PLAN.md index 80a4c395..933302db 100644 --- a/docs/open-source-readiness/PLAN.md +++ b/docs/open-source-readiness/PLAN.md @@ -1,6 +1,6 @@ # DeepCode 三阶段交付总计划(Implementation-ready Master Plan) -> 文档版本:1.2 +> 文档版本:1.3 > 基线日期:2026-07-27 > 当前状态:Implementation in progress(计划执行中,产品当前仍为 NO-GO) > 分支拓扑:`develop` 为实施基线;`master` 为 GitHub 默认/发布分支;里程碑通过审查后从 `develop` 合入 `master` @@ -11,6 +11,7 @@ | 日期 | 版本 | 变更 | 依据 | | ---------- | ---: | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| 2026-07-28 | 1.3 | 完成 S1-02-D/E 与父任务验收;回填 PR #6/#7、双平台全链 CI、字符串分类和下一唯一任务 S1-03 | PR #6、PR #7、typecheck #116、test #118 与 evidence | | 2026-07-28 | 1.2 | 回填实际实施进度、PR/CI 基线与远程交接入口;明确 S1-02-D 为 IN_REVIEW、S1-02-E 为下一执行点 | PR #2、PR #6、GitHub Actions 与远程分支复核 | | 2026-07-27 | 1.1 | 明确 `develop` 开发、`master` 发布的分支拓扑;将 S1-01 拆为仓库基线与官网止损,解除外部官网权限对代码任务的依赖死锁 | 远端仓库元数据与执行一致性复核 | | 2026-07-27 | 1.0 | 将交付重构为“官网可安装 → 完整能力 → 设计驱动的 WebUI/Electron”三阶段,并补齐任务级输入、步骤、测试、证据和门禁 | 用户目标澄清、源码与现有开源准备文档复核 | @@ -23,18 +24,17 @@ | 范围 | 当前状态 | 已完成 | 当前执行点 / 阻塞 | | --- | --- | --- | --- | | 产品发布判断 | `NO-GO` | 计划已达到可执行标准并进入实施 | Gate 1 尚未通过,官网与可信 Release 安装闭环尚未完成 | -| 第一阶段 | `IN_PROGRESS` | S1-01A `DONE`;S1-02-A/B/C `DONE` | S1-02-D `IN_REVIEW`(PR #6);S1-02-E `NOT_STARTED`;S1-01B 与其余 Stage 1 任务未完成 | +| 第一阶段 | `IN_PROGRESS` | S1-01A `DONE`;S1-02-A/B/C/D/E 与父任务 `DONE` | 下一唯一代码任务 S1-03;S1-01B、S1-04、S1-05 及后续 Stage 1 任务仍未完成 | | 第二阶段 | `NOT_STARTED` | 无 | 受 Gate 1 阻塞,不得提前宣称完整 Provider/Harness/Agent/Gateway 能力 | | 第三阶段 | `NOT_STARTED` | 无 | 受 Gate 2 和设计门禁阻塞,不得提前大规模改造 WebUI/Electron | ### 已落入远程的实施基线 -- PR #2 `feat(identity): isolate DeepCode config and CLI` 已通过完整门禁并 squash 合入 `develop`。 -- `develop` 对应 S1-02-C 合并提交:`babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8`。 -- S1-02-D 当前远程分支:`agent/s1-02-d-install-uninstall-guard`。 -- S1-02-D 当前 PR:#6 `fix(lifecycle): fail closed DeepCode install and uninstall boundaries`。 -- 写入交接文档前的实现 head:`06280508d4004104698987c23505794425a834ef`;后续文档提交会继续推进 PR head。 -- 当前必要代码、测试和 evidence 均已推送到远程,不依赖旧容器中的未提交文件。 +- PR #2 / S1-02-C 已通过完整门禁并 squash 合入 `develop@babc3080f9d5c5c90dcf4abb16a0149e6dbc1eb8`。 +- PR #6 / S1-02-D 已通过完整门禁并 squash 合入 `develop@c848bc537e8c5677c36a360dd00122745a2f5b2e`。 +- S1-02-E 当前远程分支:`coexistence-boundary-audit`;PR #7 `test(identity): close S1-02-E coexistence boundary gaps`。 +- S1-02-E 已验证代码 head:`2442d6b11b7eb7add9ff6685af04e81c3615c657`;typecheck #116、test #118 全部 SUCCESS。 +- 代码、测试、CI boundary gate、inventory 和 evidence 均已推送到远程,不依赖旧容器中的未提交文件。 ### S1-02 当前完成度 @@ -43,10 +43,10 @@ | S1-02-A | `DONE` | 建立共存失败基线与 fixture | | S1-02-B | `DONE` | Core 路径、数据库与用户环境入口切换至 DeepCode | | S1-02-C | `DONE` | 配置发现、CLI/TUI/MDM/Server Auth 和公共测试基础设施完成身份隔离;已合入 `develop` | -| S1-02-D | `IN_REVIEW` | 安装检测、升级和卸载边界 fail-closed;PR #6 等待最终 CI/证据回填与合并 | -| S1-02-E | `NOT_STARTED` | 全链共存回归、字符串分类和父任务最终验收 | +| S1-02-D | `DONE` | 安装检测、升级和卸载边界 fail-closed;PR #6 已合入 `develop@c848bc537e8c5677c36a360dd00122745a2f5b2e` | +| S1-02-E | `DONE` | 全链共存回归、4,168 行字符串分类、双平台最终验收;PR #7 | -S1-02-D 合入后,下一唯一代码执行点是 **S1-02-E**。S1-02-E 完成前,不得开始 S1-03,也不得把 S1-02 父任务标记为 `DONE`。 +S1-02-A/B/C/D/E 与父任务已完成。下一唯一代码执行点是 **S1-03**。S1-03 完成前,不得发布 DeepCode launcher/归档或恢复任何上游 updater。 远程交接入口:[HANDOFF_2026-07-28.md](./HANDOFF_2026-07-28.md)。该文件包含可直接粘贴到新会话的完整提示词、分支/PR/Commit 基线、流程纪律和下一执行逻辑。 @@ -400,20 +400,20 @@ flowchart LR ### S1-02 建立 DeepCode 用户边界与共存隔离 -**状态**:`IN_PROGRESS` -**子任务进度**:S1-02-A/B/C `DONE`;S1-02-D `IN_REVIEW`(PR #6);S1-02-E `NOT_STARTED` +**状态**:`DONE` +**子任务进度**:S1-02-A/B/C/D/E `DONE`;PR #6 已合入,PR #7 完成最终验收 **优先级**:P0 **依赖**:`S1-01A` **可并行**:可与 `S1-04`、`S1-05` 并行,但修改重叠文件时必须串行 **主要证据/文件**: -- `packages/core/src/global.ts`:路径应用名仍为 `opencode`; -- `packages/core/src/database/database.ts`:数据库仍为 `opencode.db`; -- `packages/core/src/config.ts`、`packages/opencode/src/config/config.ts`:默认发现 `opencode.json(c)` 与 `.opencode`; -- `packages/core/src/flag/flag.ts`:用户环境变量仍为 `OPENCODE_*`; -- `packages/opencode/src/index.ts`:`scriptName("opencode")`; -- `packages/opencode/src/installation/index.ts`、`packages/opencode/src/cli/cmd/uninstall.ts`:升级/卸载仍指向 OpenCode。 +- `Product` 统一 DeepCode 产品名、CLI、路径、配置文件/目录、环境变量前缀和 MDM domain;Core/CLI/TUI/MDM 不再默认发现 OpenCode 用户配置。 +- Global Path、数据库、日志、项目缓存、插件、Agent/Plan/Session、MCP/ACP/OAuth 与用户网络身份已切换为 DeepCode。 +- 安装、升级和卸载只处理 DeepCode;可信 Release 建立前 upgrade fail-closed,不执行上游下载或包管理器。 +- DeepCode-only、OpenCode-only、双配置并存、目录树 SHA-256、RuntimeFlags、完整 config/permission 和 lifecycle 已在 Linux/Windows 通过。 +- `opencode|OPENCODE` inventory 4,168 行全部分类,未分类用户边界为 0;launcher/build/publish 的 29 行由 S1-03 接管。 +- 最终证据:[S1-02 README](./evidence/S1-02/README.md) 与 [S1-02-E boundary audit](./evidence/S1-02/test-results/S1-02-E-boundary-audit.md)。 **开工假设**:内部 `@opencode-ai/*` 包名暂不影响磁盘共存;第一阶段只改用户可见、持久化、进程和安装边界。置信度:确定。 diff --git a/docs/open-source-readiness/evidence/S1-02/README.md b/docs/open-source-readiness/evidence/S1-02/README.md index f1a0b8cf..10bf375e 100644 --- a/docs/open-source-readiness/evidence/S1-02/README.md +++ b/docs/open-source-readiness/evidence/S1-02/README.md @@ -1,8 +1,10 @@ # S1-02 DeepCode 用户边界与共存隔离 -> 状态:IN_PROGRESS +> 状态:DONE > 启动日期:2026-07-27 > 基线分支:`develop` +> 完成分支:`coexistence-boundary-audit` +> 完成 PR:#7 > 前置证据:[S1-01A](../S1-01A/README.md) ## 假设 @@ -39,7 +41,7 @@ | S1-02-B | `DONE` | Core 路径、数据库和环境入口改为 DeepCode | `packages/core/src/global.ts`、`database/database.ts`、`flag/flag.ts` | A | [Core 验证](./test-results/S1-02-B-core.md) | | S1-02-C | `DONE` | 统一身份、配置发现与 CLI 用户身份隔离 | `packages/core/src/product.ts`、Core 配置/路径/DB/Flag;CLI 配置、TUI、MDM、CLI identity、Server Auth 与测试 fixture | B | [配置与 CLI 验证](./test-results/S1-02-C-config-cli.md) | | S1-02-D | `DONE` | 安装检测和卸载目标 fail-closed | `packages/opencode/src/installation/index.ts`、Upgrade/Uninstall CLI、CI lifecycle gate 与 installation tests | C | [安装与卸载验证](./test-results/S1-02-D-install-uninstall.md) | -| S1-02-E | `NOT_STARTED` | 全链共存回归和字符串分类 | 只改测试、证据与明确遗漏;构建输出留给 S1-03 | A-D | 两个隔离 fixture + `rg` 分类 + package 级 typecheck/test | +| S1-02-E | `DONE` | 全链共存回归和字符串分类 | 配置/TUI/permission/lifecycle tests、CI boundary gate、分类证据与明确遗漏;构建输出留给 S1-03 | A-D | [边界审计](./test-results/S1-02-E-boundary-audit.md) | S1-02-C 扩大文件所有权的原因:配置发现存在 Core、CLI、TUI、MDM、Server Auth 和测试公共 fixture 等入口,只修改最初列出的三个文件会保留旁路;`global.ts`、数据库和 Flag 在 C 中仅改为消费统一 `Product` 常量,不改变 B 已验收的行为。 @@ -86,15 +88,23 @@ S1-02-C 扩大文件所有权的原因:配置发现存在 Core、CLI、TUI、M - Uninstall CLI 只使用 DeepCode Global Path 和精确 DeepCode shell marker/PATH;删除 OpenCode 包管理器卸载分支。 - 精确 PATH 分量回归证明 `.opencode/bin`、`# opencode`、`.deepcode/bin-backup`、`.deepcode/bin-old` 和无关文本保持不变。 - Linux/Windows lifecycle suite 均为 17 pass / 0 fail / 93 assertions;typecheck、Linux/Windows unit、generated client、HttpApi 和 Linux/Windows E2E 全部通过。 -- 已验证实现 head:`874dc9c959ccd91320e46d3bf3675c2b30ee861e`;typecheck run #65、test run #67。 +- PR #6 已 squash 合入 `develop@c848bc537e8c5677c36a360dd00122745a2f5b2e`;最终 PR head `d1af3992eef75c714ca419e4517079ea7c0a7740`,typecheck #70、test #72 全绿。 + +### S1-02-E:全链共存回归和字符串分类 + +- PR #7 已完成 DeepCode-only、OpenCode-only、双配置并存、TUI 树哈希、RuntimeFlags、完整 config/permission 和生命周期隔离回归。 +- 修复 TUI migration、配置动态环境读取、Provider 凭据、插件/项目缓存/进程/网络身份等确认的用户边界遗漏。 +- 对未验证的上游 WebUI、Account、IDE extension 和 GitHub Agent 渠道实行 fail-closed。 +- 最终 inventory 为 4,168 行,全部归类;未分类用户边界为 0。构建/发行命名的 29 行明确归属 S1-03。 +- 已验证代码 head `2442d6b11b7eb7add9ff6685af04e81c3615c657`:typecheck #116、test #118 全绿。 +- Linux/Windows lifecycle 各 17 pass;Core isolation 各 6 pass;完整 config/permission Linux 305 pass、Windows 308 pass;generated client、HttpApi、双平台 E2E 全绿。 ## 下一执行点 -1. squash 合入 PR #6 到 `develop`,并核验合并后的 S1-02-D evidence。 -2. 从最新 `develop` 创建独立分支实施 S1-02-E。 -3. S1-02-E 首先恢复/修正 `packages/opencode` 整包测试:当前历史测试仍有 `OPENCODE_*`、`.opencode` 和 OpenCode CLI 断言,临时启用 `deepcode#test` 会暴露这些测试债务;不得通过恢复 OpenCode fallback 解决。 -4. 完成 DeepCode-only、OpenCode-only、双配置并存、生命周期树不变和 `opencode` / `OPENCODE` 字符串逐条分类;整链通过后才能把父任务标记 `DONE`。 +S1-02 父任务完成。下一唯一代码任务是 **S1-03:建立 DeepCode 安装、制品与 Release 边界**。不得继续依赖或发布 `opencode` launcher、上游归档名、上游 updater;S1-03 必须建立 DeepCode-owned artifact、checksum、rollback 和 release channel。 + +S1-01B、S1-04、S1-05 仍可按 PLAN 的依赖关系并行,但产品总体继续保持 NO-GO,Gate 1 未通过。 ## 当前结论 -S1-02-A、S1-02-B、S1-02-C 已完成并合入 `develop`。S1-02-D 已完成代码、双平台回归和证据,待 PR #6 squash 合入;S1-02-E 尚未开始,因此父任务继续保持 `IN_PROGRESS`,产品仍为 NO-GO。 +S1-02-A/B/C/D/E 全部 DONE。默认用户配置、持久化、进程、网络和生命周期边界已与 OpenCode 隔离,并通过 Linux/Windows 全链验证。构建和公开发行闭环尚未完成,产品仍为 NO-GO。 diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup.cpython-312.pyc new file mode 100644 index 00000000..cacb80f2 Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup2.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup2.cpython-312.pyc new file mode 100644 index 00000000..bc34d79d Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-cleanup2.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-config-fixes.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-config-fixes.cpython-312.pyc new file mode 100644 index 00000000..2c69d4f8 Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-config-fixes.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final-cleanup.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final-cleanup.cpython-312.pyc new file mode 100644 index 00000000..d214e595 Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final-cleanup.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final.cpython-312.pyc new file mode 100644 index 00000000..747c1087 Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-final.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-regression-fixes.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-regression-fixes.cpython-312.pyc new file mode 100644 index 00000000..0754750b Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-regression-fixes.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-transform.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-transform.cpython-312.pyc new file mode 100644 index 00000000..16fae564 Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.apply-s1e-transform.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/__pycache__/.finalize-s1e-docs.cpython-312.pyc b/docs/open-source-readiness/evidence/S1-02/__pycache__/.finalize-s1e-docs.cpython-312.pyc new file mode 100644 index 00000000..1f40832b Binary files /dev/null and b/docs/open-source-readiness/evidence/S1-02/__pycache__/.finalize-s1e-docs.cpython-312.pyc differ diff --git a/docs/open-source-readiness/evidence/S1-02/test-results/S1-02-E-boundary-audit.md b/docs/open-source-readiness/evidence/S1-02/test-results/S1-02-E-boundary-audit.md new file mode 100644 index 00000000..6a82fe15 --- /dev/null +++ b/docs/open-source-readiness/evidence/S1-02/test-results/S1-02-E-boundary-audit.md @@ -0,0 +1,111 @@ +# S1-02-E 全链共存回归与字符串分类 + +> 状态:DONE +> 基线:`develop@c848bc537e8c5677c36a360dd00122745a2f5b2e` +> 分支:`coexistence-boundary-audit` +> PR:#7 +> 已验证代码 head:`2442d6b11b7eb7add9ff6685af04e81c3615c657` + +## 假设与判定标准 + +- 内部 `@opencode-ai/*` package、Effect service tag、Provider ID、上游 schema/protocol 标识可保留,但必须不参与 DeepCode 默认用户边界。 +- `.opencode`、`opencode.json(c)`、原始 `OPENCODE_*`、OpenCode shell PATH、OpenCode 数据目录只要参与默认读取、写入、迁移、删除或网络路由,即为产品缺陷。 +- `packages/opencode` 源码目录、当前 launcher/build/publish 命名和 Release 制品属于 S1-03;本任务只验证它们未绕过已建立的运行时隔离。 +- 每个保留字符串必须落入一项互斥分类;无法分类即不得完成任务。 + +## 首次失败基线 + +- PR head:`69ed7874ebf9734e258aaee514959a3be6742f6f` +- typecheck run #72 / id `30339514966`:SUCCESS +- test run #74 / id `30339514909`:Linux boundary gate FAILURE +- artifact:`unit-linux-1` / id `8680499518` + +首次失败证明: + +1. `tui-migrate.ts` 会读取并修改 `opencode.json(c)` / `.opencode`,违反目录树不变契约,分类为 `PRODUCT_DEFECT`; +2. inventory 使用 runner 未保证存在的 `rg`,artifact 为空,分类为 `TEST_DEFECT`; +3. 后续完整配置门禁又发现静态缓存 `DEEPCODE_CONFIG_CONTENT`、OpenCode Provider 仍读取原始 `OPENCODE_API_KEY`,均按产品缺陷修复; +4. 历史测试中大量 `Flag.DEEPCODE_*` 属性属于自动替换错误。Flag 的 TypeScript 属性名是内部兼容接口,实际环境读取由 `Product.envPrefix` 映射到 `DEEPCODE_*`,因此恢复内部属性而不恢复用户 fallback。 + +## 已实施结果 + +### 配置、TUI 与环境隔离 + +- Core、CLI、TUI、MDM、插件安装和 MCP 配置只发现或写入 `deepcode.json(c)` / `.deepcode`。 +- OpenCode-only 配置、`.opencode`、原始 `OPENCODE_CONFIG_DIR`、`OPENCODE_DISABLE_PROJECT_CONFIG` 和其他 OpenCode 用户变量不会改变 DeepCode 行为。 +- 双配置并存时只加载 DeepCode;TUI migration 只迁移 DeepCode 文件,并以 SHA-256 证明 OpenCode 树字节级不变。 +- `DEEPCODE_CONFIG_CONTENT` 改为访问时读取,支持运行时 `{env:}` / `{file:}` substitution。 +- RuntimeFlags 只读取 `DEEPCODE_*`;内部 `Flag.OPENCODE_*` 属性保留为受控兼容接口。 + +### 持久化、进程与网络身份 + +- Plan、Session、Agent、plugin、project cache、mDNS、日志、OAuth、MCP、ACP、Provider headers/user-agent 和 CLI 文案统一为 DeepCode。 +- `.git/opencode` cache 被忽略且保持不变;DeepCode 使用 `.git/deepcode`。 +- OpenCode-compatible Provider 仅在显式选择该 Provider 时保留其 ID/protocol,并从 `DEEPCODE_API_KEY` 获取用户凭据。 +- 未建立 DeepCode 自有渠道前,上游 WebUI proxy、默认 OpenCode Account、OpenCode IDE extension 和 OpenCode GitHub Agent 均 fail-closed。 +- S1-02-D 的安装、升级拒绝和卸载隔离继续保持:不下载上游、不执行包管理器、不修改 OpenCode shell 或目录。 + +### CI 门禁 + +永久 CI 现在显式执行: + +- `packages/core` typecheck 和隔离测试; +- `packages/opencode` typecheck; +- Linux/Windows generic unit; +- Linux/Windows `test/installation`; +- Linux/Windows 完整 `test/config`、RuntimeFlags 与 `test/permission`; +- Linux inventory artifact; +- generated client; +- HttpApi exerciser(15 分钟 timeout); +- Linux/Windows Playwright E2E。 + +未使用 `continue-on-error`,未删除失败测试,未恢复 OpenCode fallback。 + +## 最终验证 + +- typecheck run #116 / id `30354218370`:SUCCESS。 +- test run #118 / id `30354218292`:SUCCESS。 +- Linux generic unit:8/8 Turbo tasks success;Core 1,070 pass / 6 skip / 0 fail。 +- Windows generic unit:SUCCESS。 +- Linux Core isolation:6 pass / 0 fail / 10 assertions。 +- Windows Core isolation:6 pass / 0 fail / 10 assertions。 +- Linux lifecycle:17 pass / 0 fail / 93 assertions。 +- Windows lifecycle:17 pass / 0 fail / 93 assertions。 +- Linux full config/permission:305 pass / 3 skip / 0 fail / 551 assertions。 +- Windows full config/permission:308 pass / 0 fail / 564 assertions。 +- generated client:SUCCESS。 +- HttpApi:SUCCESS;artifact `httpapi-1` / id `8686246980`。 +- Linux E2E:SUCCESS;artifact `playwright-linux-1` / id `8686250999`。 +- Windows E2E:SUCCESS;artifact `playwright-windows-1` / id `8686428755`。 +- Linux boundary artifact:`unit-linux-1` / id `8686176658`。 +- Windows boundary artifact:`unit-windows-1` / id `8686345236`。 + +## `opencode|OPENCODE` 最终分类 + +最终 inventory 共 4,168 行。分类规则按路径和语义互斥应用,合计与 inventory 行数一致: + +| 分类 | 数量 | 处置与理由 | +| --- | ---: | --- | +| 测试、fixture、负向共存断言 | 2,710 | 用于证明 OpenCode 输入被忽略、OpenCode 树不变,或验证显式上游兼容;不作为默认用户入口 | +| 内部 package / service tag | 988 | `@opencode-ai/*`、`@opencode/*`、TypeId 和现有 workspace package ABI;第一阶段禁止全面重命名 | +| 文档与 specs | 115 | 历史设计、迁移说明或明确“不得使用 OpenCode 用户边界”的指导;不被生产代码加载为配置入口 | +| 内部源码路径与模块名 | 109 | `packages/opencode`、`OpencodePlugin` 等仓库内部路径/符号;不形成用户磁盘或进程边界 | +| 内部 Flag 兼容属性 | 105 | 属性名保持 `Flag.OPENCODE_*`,`read()` 统一映射到 `DEEPCODE_*`;原始 OpenCode 环境变量负向测试通过 | +| 开发/构建工具 | 56 | migration、trace、drizzle 和开发脚本,仅仓库维护使用,不进入默认产品运行链;记录为非发布工具债务 | +| S1-03 构建/发行边界 | 29 | launcher、bin、publish 和制品命名;由 S1-03 统一改造,当前升级与发布渠道已 fail-closed | +| 已禁用上游渠道实现 | 24 | `github.handler.ts` 等保留源码不可由当前 CLI 到达;公开命令立即返回 DeepCode-owned channel 尚未建立 | +| 上游 schema / provider / protocol | 21 | config schema、well-known、显式 OpenCode Provider ID/endpoint 等协议兼容;只有显式选择时生效 | +| 编译注入与协议元数据 | 11 | `OPENCODE_VERSION`、WASM/models 注入符号、OpenAI originator 等构建或上游 wire 兼容标识 | +| **未分类用户边界** | **0** | 无 | + +重点例外: + +- `packages/opencode/bin/opencode`、package bin 和 publish 脚本不是 S1-02 的发布成果;它们明确阻塞 S1-03,当前不存在可信 Release/upgrade 路径。 +- `packages/opencode/src/cli/cmd/github.handler.ts` 仍含上游 URL,但 `github.ts` 已 fail-closed,生产 CLI 不导入或执行该 handler。 +- `packages/core/src/plugin/provider/opencode.ts` 是显式 OpenCode-compatible Provider,不是 DeepCode 默认账户服务;默认 Account CLI 不再指向 OpenCode。 + +## 结论 + +S1-02-E 验收完成,S1-02-A/B/C/D/E 全部 DONE。DeepCode 默认用户边界、配置、持久化、生命周期与 OpenCode 共存隔离已由双平台全链测试证明。 + +产品总体仍为 NO-GO:可信 DeepCode launcher、归档名、Release/upgrade channel、checksum、rollback、`--purge` 和 GitHub Release 属于下一唯一代码任务 S1-03。 diff --git a/packages/core/src/deepcode/reasoning/manager.ts b/packages/core/src/deepcode/reasoning/manager.ts index cefd9fc8..7d76cd1a 100644 --- a/packages/core/src/deepcode/reasoning/manager.ts +++ b/packages/core/src/deepcode/reasoning/manager.ts @@ -87,7 +87,7 @@ export interface ReasoningConfig { * 默认配置常量(供初始化参考和外部覆盖时作为 fallback) * * 注意:运行时配置存储在 Layer 内的 Ref 中, - * 通过 setConfig 方法可动态修改(如通过 opencode.json 配置)。 + * 通过 setConfig 方法可动态修改(如通过 deepcode.json 配置)。 */ export const DEFAULT_CONFIG: ReasoningConfig = { stripFromHistory: true, @@ -154,7 +154,7 @@ export interface Interface { * 动态更新 reasoning 配置 * * 使用 Partial 合并,未指定的字段保持原值。 - * 允许运行时通过 opencode.json 等外部配置覆盖默认值。 + * 允许运行时通过 deepcode.json 等外部配置覆盖默认值。 * * @param partial - 需要更新的配置字段 */ @@ -304,7 +304,7 @@ const layer = Layer.effect( * * 使用 Ref.update 合并 partial 配置,未指定的字段保持原值。 * 同时同步更新 configCache,确保同步方法能读取到最新配置。 - * 允许运行时通过 opencode.json 等外部配置覆盖默认值。 + * 允许运行时通过 deepcode.json 等外部配置覆盖默认值。 */ setConfig: Effect.fn("DeepCodeReasoningManager.setConfig")( (partial: Partial) => diff --git a/packages/core/src/deepcode/router/model-router.ts b/packages/core/src/deepcode/router/model-router.ts index 34a1877f..c1e45e7d 100644 --- a/packages/core/src/deepcode/router/model-router.ts +++ b/packages/core/src/deepcode/router/model-router.ts @@ -98,7 +98,7 @@ export interface RouteDecision { /** * 路由配置 * - * 可通过 opencode.json 的 deepcode.routing 段覆盖,此处为默认值。 + * 可通过 deepcode.json 的 deepcode.routing 段覆盖,此处为默认值。 */ export interface RoutingConfig { /** 高风险文件 glob 模式列表,匹配时强制 Pro */ diff --git a/packages/core/src/effect/dfdf b/packages/core/src/effect/dfdf deleted file mode 100644 index 77f2d579..00000000 --- a/packages/core/src/effect/dfdf +++ /dev/null @@ -1 +0,0 @@ -File to save in: ~/.local/share/opencode/worktree/012780/location-layer-tiers/packages/core/src/effect/ \ No newline at end of file diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index e91cbd00..d6c9e375 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -24,7 +24,9 @@ export const Flag = { OPENCODE_AUTO_HEAP_SNAPSHOT: truthy("OPENCODE_AUTO_HEAP_SNAPSHOT"), OPENCODE_GIT_BASH_PATH: read("OPENCODE_GIT_BASH_PATH"), OPENCODE_CONFIG: read("OPENCODE_CONFIG"), - OPENCODE_CONFIG_CONTENT: read("OPENCODE_CONFIG_CONTENT"), + get OPENCODE_CONFIG_CONTENT() { + return read("OPENCODE_CONFIG_CONTENT") + }, OPENCODE_DISABLE_AUTOUPDATE: truthy("OPENCODE_DISABLE_AUTOUPDATE"), OPENCODE_ALWAYS_NOTIFY_UPDATE: truthy("OPENCODE_ALWAYS_NOTIFY_UPDATE"), OPENCODE_DISABLE_PRUNE: truthy("OPENCODE_DISABLE_PRUNE"), diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index c2325cb5..df1502a0 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -15,7 +15,7 @@ import { httpClient } from "./effect/app-node-platform" export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"]) export type CatalogModelStatus = typeof CatalogModelStatus.Type -const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}` +const USER_AGENT = `deepcode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}` const CostTier = Schema.Struct({ input: Schema.Finite, diff --git a/packages/core/src/oauth/page.ts b/packages/core/src/oauth/page.ts index 5d3e29f6..ad8c6854 100644 --- a/packages/core/src/oauth/page.ts +++ b/packages/core/src/oauth/page.ts @@ -6,7 +6,7 @@ // offline and drop into any transport (`res.end(...)`, Effect `response.end`, // etc.). // -// The visual language mirrors the OpenCode app: the design tokens are a curated +// The visual language mirrors the DeepCode app: the design tokens are a curated // subset of the OC-2 semantic tokens in `packages/ui/src/styles/theme.css`, and // the wordmark is the same geometry as `packages/ui/src/components/logo.tsx`. // Keep this file in sync with those sources when the brand changes. @@ -25,7 +25,7 @@ export function success(options?: CallbackPageOptions) { body: renderCard({ status: "success", headline: "Authorization successful", - message: provider ? `OpenCode is now connected to ${escapeHtml(provider)}.` : "OpenCode is now authorized.", + message: provider ? `DeepCode is now connected to ${escapeHtml(provider)}.` : "DeepCode is now authorized.", footnote: "You can close this window.", }), script: options?.autoClose === false ? undefined : AUTO_CLOSE_SCRIPT, @@ -40,10 +40,10 @@ export function error(detail: string, options?: CallbackPageOptions) { status: "error", headline: "Authorization failed", message: provider - ? `OpenCode couldn't finish connecting to ${escapeHtml(provider)}.` - : "OpenCode couldn't complete authorization.", + ? `DeepCode couldn't finish connecting to ${escapeHtml(provider)}.` + : "DeepCode couldn't complete authorization.", detail, - footnote: "Close this window and try again from OpenCode.", + footnote: "Close this window and try again from DeepCode.", }), }) } @@ -116,8 +116,8 @@ function bootstrapScript(options: BootstrapOptions) { var TOKEN_URL=new URL(${scriptString(options.tokenPath)},window.location.origin).href; (function(){ var card=document.getElementById("oc-card"),headline=document.getElementById("oc-headline"),message=document.getElementById("oc-message"),detail=document.getElementById("oc-detail"),footnote=document.getElementById("oc-footnote"); - function fail(text){card.dataset.status="error";headline.textContent="Authorization failed";message.textContent=PROVIDER?("OpenCode couldn't finish connecting to "+PROVIDER+"."):"OpenCode couldn't complete authorization.";if(text){detail.textContent=text;detail.hidden=false}footnote.textContent="Close this window and try again from OpenCode."} - function ok(){card.dataset.status="success";headline.textContent="Authorization successful";message.textContent=PROVIDER?("OpenCode is now connected to "+PROVIDER+"."):"OpenCode is now authorized.";detail.hidden=true;footnote.textContent="You can close this window.";setTimeout(function(){try{window.close()}catch(e){}},2500)} + function fail(text){card.dataset.status="error";headline.textContent="Authorization failed";message.textContent=PROVIDER?("DeepCode couldn't finish connecting to "+PROVIDER+"."):"DeepCode couldn't complete authorization.";if(text){detail.textContent=text;detail.hidden=false}footnote.textContent="Close this window and try again from DeepCode."} + function ok(){card.dataset.status="success";headline.textContent="Authorization successful";message.textContent=PROVIDER?("DeepCode is now connected to "+PROVIDER+"."):"DeepCode is now authorized.";detail.hidden=true;footnote.textContent="You can close this window.";setTimeout(function(){try{window.close()}catch(e){}},2500)} try{ var hash=new URLSearchParams((window.location.hash||"").slice(1)); var search=new URLSearchParams(window.location.search||""); @@ -249,25 +249,8 @@ const STYLES = ` @media (prefers-reduced-motion: reduce) { .spinner { animation: none; } } ` -// OpenCode wordmark — same path geometry as packages/ui/src/components/logo.tsx (Logo). -const WORDMARK = ` - - - - - - - - - - - - - - - - - ` +// Temporary text wordmark until the DeepCode design system supplies final vector assets. +const WORDMARK = `DeepCode` const ICON_CHECK = `` diff --git a/packages/core/src/observability/logging.ts b/packages/core/src/observability/logging.ts index 0047d8d5..f3d56297 100644 --- a/packages/core/src/observability/logging.ts +++ b/packages/core/src/observability/logging.ts @@ -46,7 +46,7 @@ function format(input: unknown) { return /^[^\s="\\]+$/.test(value) ? value : JSON.stringify(value) } -export function fileLogger(file = path.join(Global.Path.log, "opencode.log"), id: string = runID) { +export function fileLogger(file = path.join(Global.Path.log, "deepcode.log"), id: string = runID) { // Do not set batchWindow to 0; it causes high idle CPU usage. return Logger.toFile(formatter(id), file, { flag: "a" }) } @@ -54,7 +54,7 @@ export function fileLogger(file = path.join(Global.Path.log, "opencode.log"), id const stderrLogger = Logger.make((options) => process.stderr.write(formatter().log(options) + "\n")) export function minimumLogLevel() { - const value = process.env.OPENCODE_LOG_LEVEL?.toUpperCase() + const value = process.env.DEEPCODE_LOG_LEVEL?.toUpperCase() const levels = { DEBUG: "Debug", INFO: "Info", @@ -65,7 +65,7 @@ export function minimumLogLevel() { } export function loggers() { - return process.env.OPENCODE_PRINT_LOGS === "1" ? [fileLogger(), stderrLogger] : [fileLogger()] + return process.env.DEEPCODE_PRINT_LOGS === "1" ? [fileLogger(), stderrLogger] : [fileLogger()] } export * as Logging from "./logging" diff --git a/packages/core/src/observability/otlp.ts b/packages/core/src/observability/otlp.ts index dd99ebc1..27fdcae7 100644 --- a/packages/core/src/observability/otlp.ts +++ b/packages/core/src/observability/otlp.ts @@ -35,13 +35,13 @@ function resourceAttributes() { export function resource(): { serviceName: string; serviceVersion: string; attributes: Record } { return { - serviceName: "opencode", + serviceName: "deepcode", serviceVersion: InstallationVersion, attributes: { ...resourceAttributes(), "deployment.environment.name": InstallationChannel, - "opencode.client": Flag.OPENCODE_CLIENT, - "opencode.run": runID, + "deepcode.client": Flag.OPENCODE_CLIENT, + "deepcode.run": runID, "service.instance.id": runID, }, } diff --git a/packages/core/src/plugin/agent.ts b/packages/core/src/plugin/agent.ts index 9a763c7e..d89f5603 100644 --- a/packages/core/src/plugin/agent.ts +++ b/packages/core/src/plugin/agent.ts @@ -44,7 +44,6 @@ const PROMPT_TITLE = `You are a title generator. You output ONLY a thread title. Generate a brief title that would help the user find this conversation later. - Follow all rules in Use the so you know what a good title looks like. Your output must be: @@ -143,7 +142,7 @@ export const Plugin = define({ { action: "plan_exit", resource: "*", effect: "allow" }, { action: "external_directory", resource: path.join(Global.Path.data, "plans", "*"), effect: "allow" }, { action: "edit", resource: "*", effect: "deny" }, - { action: "edit", resource: path.join(".opencode", "plans", "*.md"), effect: "allow" }, + { action: "edit", resource: path.join(".deepcode", "plans", "*.md"), effect: "allow" }, { action: "edit", resource: path.relative(worktree, path.join(Global.Path.data, "plans", "*.md")), @@ -162,7 +161,7 @@ export const Plugin = define({ draft.update(AgentV2.ID.make("explore"), (item) => { item.description = - 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.' + 'Fast agent specialized for exploring codebases using glob, grep, and read operations. Specify "quick", "medium", or "very thorough" when delegating.' item.system = PROMPT_EXPLORE item.mode = "subagent" item.permissions.push( diff --git a/packages/core/src/plugin/command/initialize.txt b/packages/core/src/plugin/command/initialize.txt index 5fc073d6..9f4bffda 100644 --- a/packages/core/src/plugin/command/initialize.txt +++ b/packages/core/src/plugin/command/initialize.txt @@ -1,6 +1,6 @@ Create or update `AGENTS.md` for this repository. -The goal is a compact instruction file that helps future OpenCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out. +The goal is a compact instruction file that helps future DeepCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out. User-provided focus or constraints (honor these): $ARGUMENTS @@ -12,7 +12,7 @@ Read the highest-value sources first: - build, test, lint, formatter, typecheck, and codegen config - CI workflows and pre-commit / task runner config - existing instruction files (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/copilot-instructions.md`) -- repo-local OpenCode config such as `opencode.json` +- repo-local DeepCode config such as `deepcode.json` If architecture is still unclear after reading config and docs, inspect a small number of representative code files to find the real entrypoints, package boundaries, and execution flow. Prefer reading the files that explain how the system is wired together over random leaf files. @@ -56,7 +56,7 @@ Exclude: - long tutorials or exhaustive file trees - obvious language conventions - speculative claims or anything you could not verify -- content better stored in another file referenced via `opencode.json` `instructions` +- content better stored in another file referenced via `deepcode.json` `instructions` When in doubt, omit. diff --git a/packages/core/src/plugin/provider/cerebras.ts b/packages/core/src/plugin/provider/cerebras.ts index 0fd65116..27668c16 100644 --- a/packages/core/src/plugin/provider/cerebras.ts +++ b/packages/core/src/plugin/provider/cerebras.ts @@ -10,7 +10,7 @@ export const CerebrasPlugin = define({ if (item.provider.api.type !== "aisdk") continue if (item.provider.api.package !== "@ai-sdk/cerebras") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode" + provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "deepcode" }) } }), diff --git a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts index d416f6f1..330688b6 100644 --- a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts +++ b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts @@ -71,7 +71,7 @@ function gatewayOptions(options: Record, metadata: unknown) { skipCache: options.skipCache, collectLog: options.collectLog, headers: { - "User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, }, } } diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index 1a1c533e..b59f035c 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -64,7 +64,7 @@ function sdkOptions(options: Record) { baseURL: expandAccountId(options.baseURL), apiKey: process.env.CLOUDFLARE_API_KEY ?? options.apiKey, headers: { - "User-Agent": `opencode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, ...options.headers, }, name: providerID, diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index 8723cdaa..bde1994c 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -19,7 +19,7 @@ export const GitLabPlugin = define({ : (process.env.GITLAB_INSTANCE_URL ?? "https://gitlab.com"), apiKey: typeof evt.options.apiKey === "string" ? evt.options.apiKey : process.env.GITLAB_TOKEN, aiGatewayHeaders: { - "User-Agent": `opencode/${InstallationVersion} gitlab-ai-provider/${mod.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} gitlab-ai-provider/${mod.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, "anthropic-beta": "context-1m-2025-08-07", ...evt.options.aiGatewayHeaders, }, diff --git a/packages/core/src/plugin/provider/kilo.ts b/packages/core/src/plugin/provider/kilo.ts index 6ee6670e..b9820861 100644 --- a/packages/core/src/plugin/provider/kilo.ts +++ b/packages/core/src/plugin/provider/kilo.ts @@ -11,8 +11,8 @@ export const KiloPlugin = define({ if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue if (item.provider.api.url !== "https://api.kilo.ai/api/gateway") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" + provider.request.headers["HTTP-Referer"] = "https://github.com/yuanchenglu/deepcode" + provider.request.headers["X-Title"] = "deepcode" }) } }), diff --git a/packages/core/src/plugin/provider/llmgateway.ts b/packages/core/src/plugin/provider/llmgateway.ts index eafc5edd..f190d575 100644 --- a/packages/core/src/plugin/provider/llmgateway.ts +++ b/packages/core/src/plugin/provider/llmgateway.ts @@ -15,9 +15,9 @@ export const LLMGatewayPlugin = define({ if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue if (!(yield* integrations.get(Integration.ID.make(item.provider.id)))) continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" - provider.request.headers["X-Source"] = "opencode" + provider.request.headers["HTTP-Referer"] = "https://github.com/yuanchenglu/deepcode" + provider.request.headers["X-Title"] = "deepcode" + provider.request.headers["X-Source"] = "deepcode" }) } }), diff --git a/packages/core/src/plugin/provider/nvidia.ts b/packages/core/src/plugin/provider/nvidia.ts index 44959972..26bbd58a 100644 --- a/packages/core/src/plugin/provider/nvidia.ts +++ b/packages/core/src/plugin/provider/nvidia.ts @@ -11,9 +11,9 @@ export const NvidiaPlugin = define({ if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue if (item.provider.api.url !== "https://integrate.api.nvidia.com/v1") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" - provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode" + provider.request.headers["HTTP-Referer"] = "https://github.com/yuanchenglu/deepcode" + provider.request.headers["X-Title"] = "deepcode" + provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "DeepCode" }) } }), diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 46553eaf..fe8e453b 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -189,7 +189,7 @@ export const OpenAIPlugin = define({ } satisfies PluginInternal.Plugin) function headers(contentType: string) { - return { "Content-Type": contentType, "User-Agent": `opencode/${InstallationVersion}` } + return { "Content-Type": contentType, "User-Agent": `deepcode/${InstallationVersion}` } } function exchange(code: string, redirect: string, pkce: Pkce) { diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index f07d72e5..135a6a89 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -164,7 +164,7 @@ export const OpencodePlugin = define { if (!hasKey) provider.request.body.apiKey = "public" }) diff --git a/packages/core/src/plugin/provider/openrouter.ts b/packages/core/src/plugin/provider/openrouter.ts index 0f295fb0..9827843c 100644 --- a/packages/core/src/plugin/provider/openrouter.ts +++ b/packages/core/src/plugin/provider/openrouter.ts @@ -11,8 +11,8 @@ export const OpenRouterPlugin = define({ if (item.provider.api.type !== "aisdk") continue if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" + provider.request.headers["HTTP-Referer"] = "https://github.com/yuanchenglu/deepcode" + provider.request.headers["X-Title"] = "deepcode" }) for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) { if (!item.models.has(modelID)) continue diff --git a/packages/core/src/plugin/provider/vercel.ts b/packages/core/src/plugin/provider/vercel.ts index 00f56014..3c0c8a62 100644 --- a/packages/core/src/plugin/provider/vercel.ts +++ b/packages/core/src/plugin/provider/vercel.ts @@ -10,8 +10,8 @@ export const VercelPlugin = define({ if (item.provider.api.type !== "aisdk") continue if (item.provider.api.package !== "@ai-sdk/vercel") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["http-referer"] = "https://opencode.ai/" - provider.request.headers["x-title"] = "opencode" + provider.request.headers["http-referer"] = "https://github.com/yuanchenglu/deepcode" + provider.request.headers["x-title"] = "deepcode" }) } }), diff --git a/packages/core/src/plugin/provider/zenmux.ts b/packages/core/src/plugin/provider/zenmux.ts index 29adebc0..d271edf3 100644 --- a/packages/core/src/plugin/provider/zenmux.ts +++ b/packages/core/src/plugin/provider/zenmux.ts @@ -11,8 +11,8 @@ export const ZenmuxPlugin = define({ if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue if (item.provider.api.url !== "https://zenmux.ai/api/v1") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/" - provider.request.headers["X-Title"] ??= "opencode" + provider.request.headers["HTTP-Referer"] ??= "https://github.com/yuanchenglu/deepcode" + provider.request.headers["X-Title"] ??= "deepcode" }) } }), diff --git a/packages/core/src/plugin/skill.ts b/packages/core/src/plugin/skill.ts index ea723dd8..7ad45db2 100644 --- a/packages/core/src/plugin/skill.ts +++ b/packages/core/src/plugin/skill.ts @@ -6,9 +6,9 @@ import { define } from "./internal" import { Effect } from "effect" import { AbsolutePath } from "../schema" import { SkillV2 } from "../skill" -import customizeOpencodeContent from "./skill/customize-opencode.md" with { type: "text" } +import customizeDeepCodeContent from "./skill/customize-deepcode.md" with { type: "text" } -export const CustomizeOpencodeContent = customizeOpencodeContent +export const CustomizeDeepCodeContent = customizeDeepCodeContent export const Plugin = define({ id: "skill", @@ -18,11 +18,11 @@ export const Plugin = define({ SkillV2.EmbeddedSource.make({ type: "embedded", skill: SkillV2.Info.make({ - name: "customize-opencode", + name: "customize-deepcode", description: - "Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.", - location: AbsolutePath.make("/builtin/customize-opencode.md"), - content: CustomizeOpencodeContent, + "Use ONLY when the user is editing or creating DeepCode configuration: deepcode.json, deepcode.jsonc, files under .deepcode/, or files under the global DeepCode config directory. Also use when creating or fixing DeepCode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for ordinary application code.", + location: AbsolutePath.make("/builtin/customize-deepcode.md"), + content: CustomizeDeepCodeContent, }), }), ) diff --git a/packages/core/src/plugin/skill/customize-deepcode.md b/packages/core/src/plugin/skill/customize-deepcode.md new file mode 100644 index 00000000..0c51ecd4 --- /dev/null +++ b/packages/core/src/plugin/skill/customize-deepcode.md @@ -0,0 +1,186 @@ + + +# Customizing DeepCode + +Use this skill only when editing DeepCode's own configuration, agents, +commands, skills, plugins, MCP servers, or permission rules. Do not use it for +ordinary application code. + +## Source of truth + +DeepCode currently consumes the upstream-compatible schema at: + +**** + +The URL is retained only as the verified schema contract. Local discovery is +DeepCode-only: do not create, read, migrate, or modify OpenCode configuration. + +Every DeepCode config should declare: + +```json +{ + "$schema": "https://opencode.ai/config.json" +} +``` + +When an exact field shape is uncertain, inspect the schema rather than +guessing. DeepCode validates configuration strictly. + +## File locations + +| Scope | Path | +| --- | --- | +| Project config | `./deepcode.json`, `./deepcode.jsonc` | +| Project-local config | `.deepcode/deepcode.json`, `.deepcode/deepcode.jsonc` | +| Global config | platform-equivalent DeepCode config directory, normally `~/.config/deepcode/deepcode.json` | +| Project agents | `.deepcode/agent/.md` or `.deepcode/agents/.md` | +| Project commands | `.deepcode/command/.md` or `.deepcode/commands/.md` | +| Project skills | `.deepcode/skill//SKILL.md` or `.deepcode/skills//SKILL.md` | +| Project plugins | `.deepcode/plugin/*.ts` or `.deepcode/plugins/*.ts` | +| Project plans | `.deepcode/plans/*.md` | + +Never use `.opencode`, `opencode.json`, `opencode.jsonc`, or an OpenCode global +configuration directory as an implicit DeepCode source. Importing another +product requires a future explicit import command and is not part of normal +configuration discovery. + +## Configuration skeleton + +```json +{ + "$schema": "https://opencode.ai/config.json", + "username": "user", + "model": "provider/model-id", + "small_model": "provider/model-id", + "default_agent": "build", + "shell": "/bin/zsh", + "logLevel": "INFO", + "share": "disabled", + "autoupdate": false, + "instructions": ["AGENTS.md"], + "skills": { + "paths": [".deepcode/skills"], + "urls": [] + }, + "agent": {}, + "command": {}, + "provider": {}, + "mcp": {}, + "plugin": [], + "permission": { + "edit": "ask", + "bash": { + "git status": "allow", + "*": "ask" + } + } +} +``` + +Key shape rules: + +- `model` includes a provider prefix. +- `skills` is an object containing `paths` and/or `urls`. +- `agent` and `command` are objects keyed by name. +- `plugin` is an array of package specs, file paths, or `[spec, options]` + tuples. Existing `@opencode-ai/*` package names may remain where required by + the current package ecosystem. +- `mcp[name].command` is an array of strings. +- `permission` accepts an action or a rule object. The last matching rule wins. + +## Agents + +Create non-trivial agents as Markdown files: + +```text +.deepcode/agents/reviewer.md +``` + +```markdown +--- +description: Reviews changes for correctness and regressions. +mode: subagent +model: provider/model-id +permission: + edit: deny + bash: ask +--- + +Review the requested changes and report evidence-backed findings. +``` + +The file body is the prompt. Do not duplicate it in a `prompt` frontmatter key. + +## Commands + +Project commands live under `.deepcode/command/` or `.deepcode/commands/`: + +```markdown +--- +description: Run the project verification sequence. +agent: build +--- + +Run the package-scoped checks required by AGENTS.md. Arguments: $ARGUMENTS +``` + +## Skills + +A skill is stored as: + +```text +.deepcode/skills//SKILL.md +``` + +```markdown +--- +name: example-skill +description: Use when the task matches the documented trigger. +--- + +# Example skill + +Instructions and references. +``` + +The directory name and `name` should match. Keep descriptions concrete enough +for deterministic triggering. + +## Plugins + +Local plugins belong under `.deepcode/plugin/` or `.deepcode/plugins/`. +Package-based plugins may still use upstream-compatible package names where no +DeepCode package exists; that package compatibility does not authorize reading +OpenCode configuration or directories. + +## Runtime environment variables + +Only DeepCode user variables are valid: + +- `DEEPCODE_CONFIG` +- `DEEPCODE_CONFIG_DIR` +- `DEEPCODE_CONFIG_CONTENT` +- `DEEPCODE_DISABLE_PROJECT_CONFIG` +- `DEEPCODE_DISABLE_DEFAULT_PLUGINS` +- `DEEPCODE_DISABLE_EXTERNAL_SKILLS` +- `DEEPCODE_PURE` +- `DEEPCODE_SERVER_USERNAME` +- `DEEPCODE_SERVER_PASSWORD` + +Do not recommend `OPENCODE_*` variables for DeepCode. Their presence must not +change DeepCode behavior. + +## Applying changes + +Configuration is not assumed to hot-reload. After changing DeepCode config or +config-time files, tell the user to restart DeepCode. Before writing: + +1. identify the exact DeepCode target path; +2. preserve unrelated keys and comments where possible; +3. validate the resulting shape against the schema; +4. do not touch coexisting OpenCode files; +5. report every file created or modified. diff --git a/packages/core/src/plugin/skill/customize-opencode.md b/packages/core/src/plugin/skill/customize-opencode.md deleted file mode 100644 index 6932dbfd..00000000 --- a/packages/core/src/plugin/skill/customize-opencode.md +++ /dev/null @@ -1,452 +0,0 @@ - - -# Customizing opencode - -opencode validates its own config strictly and refuses to start when a field -is wrong. The shapes below cover the common surface area, but they are a -**summary, not the source of truth**. - -## Full schema reference - -The authoritative list of every config option — with field types, enums, -defaults, and descriptions — lives in the published JSON Schema: - -**** - -If a field is not documented in this skill, or you need to confirm an exact -shape before writing config, **fetch that URL and read the schema directly** -rather than guessing. opencode hard-fails on invalid config, so the cost of a -wrong shape is a broken startup. - -Independently, every `opencode.json` should declare -`"$schema": "https://opencode.ai/config.json"` so the user's editor catches -mistakes as they type. - -## Applying changes - -Config is loaded once when opencode starts and is not hot-reloaded. After -saving changes to `opencode.json`, an agent file, a skill, a plugin, or any -other config-time file, **tell the user to quit and restart opencode** for -the changes to take effect. The running session will keep using the -already-loaded config until then. - -## Where files live - -| Scope | Path | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| Project config | `./opencode.json`, `./opencode.jsonc`, or `.opencode/opencode.json` (opencode walks up from the cwd to the worktree root) | -| Global config | `~/.config/opencode/opencode.json` (NOT `~/.opencode/`) | -| Project agents | `.opencode/agent/.md` or `.opencode/agents/.md` | -| Global agents | `~/.config/opencode/agent(s)/.md` | -| Project commands | `.opencode/command/.md` or `.opencode/commands/.md` | -| Global commands | `~/.config/opencode/command(s)/.md` | -| Project skills | `.opencode/skill(s)//SKILL.md` | -| Global skills | `~/.config/opencode/skill(s)//SKILL.md` | -| External skills (auto-loaded) | `~/.claude/skills//SKILL.md`, `~/.agents/skills//SKILL.md` | - -Configs from each scope are deep-merged. Project overrides global. Unknown -top-level keys in `opencode.json` are rejected with `ConfigInvalidError`. - -## opencode.json - -Every field is optional. - -```json -{ - "$schema": "https://opencode.ai/config.json", - "username": "string", - "model": "provider/model-id", - "small_model": "provider/model-id", - "default_agent": "agent-name", - "shell": "/bin/zsh", - "logLevel": "DEBUG" | "INFO" | "WARN" | "ERROR", - "share": "manual" | "auto" | "disabled", - "autoupdate": true | false | "notify", - "snapshot": true, - "instructions": ["AGENTS.md", "docs/style.md"], - - "skills": { - "paths": [".opencode/skills", "/abs/path/to/skills"], - "urls": ["https://example.com/.well-known/skills/"] - }, - - "references": { - "docs": { - "path": "../docs", - "description": "Use for product behavior and documentation conventions" - }, - "sdk": { - "repository": "owner/sdk", - "branch": "main", - "description": "Use for SDK implementation details", - "hidden": true - } - }, - - "agent": { - "my-agent": { - "model": "anthropic/claude-sonnet-4-6", - "mode": "subagent", - "description": "...", - "permission": { "edit": "deny" } - } - }, - - "command": { - "deploy": { "description": "...", "template": "..." } - }, - - "provider": { - "anthropic": { "options": { "apiKey": "..." } } - }, - "disabled_providers": ["openai"], - "enabled_providers": ["anthropic"], - - "mcp": { - "playwright": { - "type": "local", - "command": ["npx", "-y", "@playwright/mcp"], - "enabled": true, - "env": {} - }, - "remote-thing": { - "type": "remote", - "url": "https://...", - "headers": { "Authorization": "Bearer ..." } - } - }, - - "plugin": [ - "opencode-gemini-auth", - "opencode-foo@1.2.3", - "./local-plugin.ts", - ["opencode-bar", { "option": "value" }] - ], - - "permission": { - "edit": "deny", - "bash": { "git *": "allow", "*": "ask" } - }, - - "formatter": false, - "lsp": false, - - "experimental": { - "primary_tools": ["edit"], - "mcp_timeout": 30000 - }, - - "tool_output": { "max_lines": 200, "max_bytes": 8192 }, - - "compaction": { "auto": true, "tail_turns": 15 } -} -``` - -Shape notes worth being explicit about: - -- `model` always carries a provider prefix: `"anthropic/claude-sonnet-4-6"`. -- `skills` is an object with `paths` and/or `urls`, not an array. -- `references` is an object keyed by alias. Each value is a local path, Git repository, or string shorthand. -- `agent` is an object keyed by agent name, not an array. -- `command` is an object keyed by command name, not an array. -- `plugin` is an array of strings or `[name, options]` tuples, not an object. -- `mcp[name].command` is an array of strings, never a single string. `type` is required. -- `permission` is either a string action or an object keyed by tool name. - -## Skills - -opencode's skill loader scans for `**/SKILL.md` inside skill directories. The -file is named `SKILL.md` exactly, and lives in its own folder named after the -skill: - -``` -.opencode/skills/my-skill/SKILL.md -``` - -Frontmatter: - -```markdown ---- -name: my-skill -description: One sentence covering what this skill does AND when to trigger it. Front-load the literal keywords or filenames the user is likely to say. ---- - -# My Skill - -(skill body in markdown: instructions, examples, references) -``` - -- `name` is required, lowercase hyphen-separated, up to 64 chars, and matches the folder name. -- `description` is effectively required: skills without one are filtered out and never surfaced to the model. Cover both _what_ the skill does and _when_ to use it. Write in third person ("Use when...", not "I help with..."). Front-load concrete trigger keywords and filenames; gate with "Use ONLY when..." if the skill should stay quiet on adjacent topics. -- Optional: `license`, `compatibility`, `metadata` (string-string map). - -Register skills from non-default locations via `skills.paths` (scanned -recursively for `**/SKILL.md`) and `skills.urls` (each URL serves a list of -skills). - -## References - -References make local directories and Git repositories outside the active -project available as supporting context. Configure them under `references`, -keyed by the alias used in `@` autocomplete: - -```json -{ - "references": { - "docs": { - "path": "../product-docs", - "description": "Use for product behavior and terminology" - }, - "effect": { - "repository": "Effect-TS/effect", - "branch": "main", - "description": "Use for Effect implementation details" - } - } -} -``` - -Local `path` values may be relative to the declaring config, absolute, or use -`~/`. Git `repository` values accept Git URLs, host/path references, and GitHub -`owner/repo` shorthand; `branch` is optional. Both forms support optional -`description` and `hidden` fields. - -- Only references with a `description` are advertised to agents in system context. -- `hidden: true` removes a reference from TUI `@` autocomplete only. It remains available to agents and by direct path. -- Reference directories are automatically allowed through the external-directory boundary; normal read/edit/tool permissions still apply. -- String shorthand is supported: use `"docs": "../docs"` for local paths or `"effect": "Effect-TS/effect"` for Git repositories. - -## Agents - -Two ways to define an agent. Use the file form for anything non-trivial. - -### Inline (in `opencode.json`) - -```json -{ - "agent": { - "my-reviewer": { - "description": "Reviews PRs for style violations.", - "mode": "subagent", - "model": "anthropic/claude-sonnet-4-6", - "permission": { "edit": "deny", "bash": "ask" }, - "prompt": "You are a strict PR reviewer..." - } - } -} -``` - -### File - -``` -.opencode/agent/my-reviewer.md OR .opencode/agents/my-reviewer.md -``` - -```markdown ---- -description: Reviews PRs for style violations. -mode: subagent -model: anthropic/claude-sonnet-4-6 -permission: - edit: deny - bash: ask ---- - -You are a strict PR reviewer. Focus on... -``` - -The file body becomes the agent's `prompt`. Do not also put `prompt:` in the -frontmatter. - -`mode` is one of `"primary"`, `"subagent"`, `"all"`. - -Allowed top-level frontmatter fields: `name, model, variant, description, mode, -hidden, color, steps, options, permission, disable, temperature, top_p`. Any -unknown field is silently routed into `options`. - -To disable a built-in agent: `agent: { build: { disable: true } }`, or in a -file, `disable: true` in frontmatter. - -`default_agent` must point to a non-hidden, primary-mode agent. - -### Built-in agents - -opencode ships with `build`, `plan`, `general`, `explore`. Hidden internal agents: -`compaction`, `title`, `summary`. To override a built-in's fields, define the -same key in `agent: { : { ... } }`. - -## Commands - -opencode's command loader scans for `**/*.md` inside command directories. The -file is named after the command, and lives directly inside the `command` folder: - -``` -.opencode/command/deploy.md -``` - -Frontmatter: - -```markdown ---- -description: One sentence describing what the command does. -agent: build -model: anthropic/claude-sonnet-4-6 ---- - -(command body in markdown: the prompt opencode runs, with $ARGUMENTS for the user's input) -``` - -- `template` is the command body — everything below the frontmatter — and is required: it is the prompt opencode runs when the command is invoked. Do not also put a `template:` key in the frontmatter. -- `$ARGUMENTS` is replaced with everything the user typed after the command; `$1`, `$2`, … pull individual positional arguments. -- Optional: `description`, `agent`, `model`, `variant`, `subtask`. - -## Plugins - -`plugin:` is an array. Each entry is one of: - -```json -"plugin": [ - "opencode-gemini-auth", // npm spec, latest - "opencode-foo@1.2.3", // npm spec, pinned - "./local-plugin.ts", // file path, relative to the declaring config - "file:///abs/path/plugin.js", // file URL - ["opencode-bar", { "key": "val" }] // tuple form with options -] -``` - -Auto-discovered plugins (no config entry needed): any `*.ts` or `*.js` file in -`.opencode/plugin/` or `.opencode/plugins/`. - -A plugin module exports `default` (or any named export) of type -`Plugin = (input: PluginInput, options?) => Promise`. The export is a -function, not a plain object literal, and the function returns an object -(return `{}` if there is nothing to register). - -```ts -import type { Plugin } from "@opencode-ai/plugin" - -export default (async ({ client, project, directory, $ }) => { - return { - config: (cfg) => { - // cfg is the live merged config; mutate fields here. - }, - "tool.execute.before": async (input, output) => { - // mutate output.args before the tool runs - }, - } -}) satisfies Plugin -``` - -Hook surface (mutate `output` in place; return `void`): - -- `event(input)`: every bus event -- `config(cfg)`: once on init with the merged config -- `chat.message`, `chat.params`, `chat.headers` -- `tool.execute.before`, `tool.execute.after` -- `tool.definition` -- `command.execute.before` -- `shell.env` -- `permission.ask` -- `experimental.chat.messages.transform`, `experimental.chat.system.transform`, - `experimental.session.compacting`, `experimental.compaction.autocontinue`, - `experimental.text.complete` - -Special object-shaped (not callbacks): `tool: { my_tool: { ... } }`, -`auth: { ... }`, `provider: { ... }`. - -## MCP servers - -`mcp:` is an object keyed by server name. Each server is discriminated by -`type`: - -```json -{ - "mcp": { - "playwright": { - "type": "local", - "command": ["npx", "-y", "@playwright/mcp"], - "enabled": true, - "env": { "BROWSER": "chromium" } - }, - "github": { - "type": "remote", - "url": "https://...", - "enabled": true, - "headers": { "Authorization": "Bearer {env:GITHUB_TOKEN}" } - }, - "old-server": { "enabled": false } - } -} -``` - -`command` is an array of strings. `type` is required. Use `enabled: false` to -disable a server inherited from a parent config. String values such as header -tokens support `{env:VAR}` interpolation (and `{file:path}`); the shell-style -`${VAR}` is not substituted. - -## Permissions - -```json -"permission": { - "edit": "deny", - "bash": { "git *": "allow", "rm *": "deny", "*": "ask" }, - "external_directory": { "~/secrets/**": "deny", "*": "allow" } -} -``` - -Actions: `"allow"`, `"ask"`, `"deny"`. - -Per-tool value forms: `"allow"` shorthand (treated as `{"*": "allow"}`), or an -object `{ pattern: action }`. Within an object, **insertion order matters**. -opencode evaluates the LAST matching rule, so put broad rules first and narrow -rules last. - -`permission: "allow"` (a string at the top level) is shorthand for "allow -everything" and is rarely what the user wants. - -Known permission keys: `read, edit, glob, grep, list, bash, task, -external_directory, todowrite, question, webfetch, websearch, lsp, doom_loop, -skill`. Some of these (`todowrite, -question, webfetch, websearch, doom_loop`) only accept a flat -action, not a per-pattern object. - -`external_directory` patterns are filesystem paths (use `~/`, absolute paths, -or globs like `~/projects/**`). - -Per-agent `permission:` overrides top-level `permission:`. Plan Mode lives on -the `plan` agent's permission ruleset (`edit: deny *`). - -## Escape hatches - -When a user's config is broken and opencode won't start, these env vars help: - -- `OPENCODE_DISABLE_PROJECT_CONFIG=1`: skip the project's local `opencode.json` - and start from globals only. Run from the project directory, opencode loads, - the user edits the broken file, then they restart without the flag. -- `OPENCODE_CONFIG=/path/to/file.json`: load an additional explicit config. -- `OPENCODE_CONFIG_CONTENT='{"$schema":"https://opencode.ai/config.json"}'`: - inject inline JSON as a final local-scope merge. -- `OPENCODE_DISABLE_DEFAULT_PLUGINS=1`: skip default plugins. -- `OPENCODE_PURE=1`: skip external plugins entirely. -- `OPENCODE_DISABLE_EXTERNAL_SKILLS=1`, - `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS=1`: skip the external skill scans under - `~/.claude/` and `~/.agents/`. - -## When proposing edits - -- Validate against the schema before writing. If you are unsure of a field's - exact shape, or the field is not covered in this skill, fetch - `https://opencode.ai/config.json` and read the schema rather than guessing. -- Preserve `$schema` and any existing fields the user did not ask to change. -- For agent, command, skill, and plugin definitions, prefer creating new files - in the correct location over inlining everything in `opencode.json`. -- If the user's existing config is malformed, point them at the env-var escape - hatches above so they can edit from inside opencode without breaking their - session. -- After saving any config change, remind the user to quit and restart opencode - — running sessions keep using the already-loaded config. diff --git a/packages/core/src/project.ts b/packages/core/src/project.ts index 49e054d7..3b176bb8 100644 --- a/packages/core/src/project.ts +++ b/packages/core/src/project.ts @@ -40,7 +40,7 @@ export interface Interface { /** * Temporary bridge method for writing the resolved project ID to the repo-local cache. * - * This exists while the old opencode project service and this core project + * This exists while the legacy project service and this core project * service work together: core resolves the ID, while the old service still owns * database migration and persistence. The old service should call this after it * finishes migrating from `resolve().previous` to `resolve().id`; once project @@ -63,7 +63,7 @@ const layer = Layer.effect( }) const cached = Effect.fnUntraced(function* (dir: string) { - return yield* fs.readFileString(path.join(dir, "opencode")).pipe( + return yield* fs.readFileString(path.join(dir, "deepcode")).pipe( Effect.map((value) => value.trim()), Effect.map((value) => (value ? ID.make(value) : undefined)), Effect.catch(() => Effect.succeed(undefined)), @@ -122,7 +122,7 @@ const layer = Layer.effect( }) const commit = Effect.fn("Project.commit")(function* (input: { store: AbsolutePath; id: ID }) { - yield* fs.writeFileString(path.join(input.store, "opencode"), input.id).pipe(Effect.ignore) + yield* fs.writeFileString(path.join(input.store, "deepcode"), input.id).pipe(Effect.ignore) }) return Service.of({ directories, resolve, commit }) diff --git a/packages/core/src/pty.ts b/packages/core/src/pty.ts index e5be4d4b..bb58f868 100644 --- a/packages/core/src/pty.ts +++ b/packages/core/src/pty.ts @@ -171,7 +171,7 @@ const layer = Layer.effect( ...process.env, ...input.env, TERM: "xterm-256color", - OPENCODE_TERMINAL: "1", + DEEPCODE_TERMINAL: "1", } as Record if (process.platform === "win32") { env.LC_ALL = "C.UTF-8" diff --git a/packages/core/src/repository.ts b/packages/core/src/repository.ts index dbc6a8fb..641c0cd9 100644 --- a/packages/core/src/repository.ts +++ b/packages/core/src/repository.ts @@ -166,7 +166,7 @@ function withSlash(input: string) { } function githubRemote(pathname: string) { - const base = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL + const base = process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL if (!base) return `https://github.com/${pathname}.git` return new URL(`${pathname}.git`, withSlash(base)).href } diff --git a/packages/core/src/shell.ts b/packages/core/src/shell.ts index 29089106..068b7943 100644 --- a/packages/core/src/shell.ts +++ b/packages/core/src/shell.ts @@ -8,6 +8,7 @@ import { setTimeout as sleep } from "node:timers/promises" import { Flag } from "./flag/flag" import { FSUtil } from "./fs-util" import { which } from "./util/which" +import { Product } from "./product" const SIGKILL_TIMEOUT_MS = 200 const META: Record = { @@ -176,7 +177,7 @@ export function args(file: string, command: string, cwd: string) { cd -- "$1" eval ${JSON.stringify(command)} `, - "opencode", + Product.cli, cwd, ] } @@ -190,7 +191,7 @@ export function args(file: string, command: string, cwd: string) { cd -- "$1" eval ${JSON.stringify(command)} `, - "opencode", + Product.cli, cwd, ] } diff --git a/packages/core/src/skill/discovery.ts b/packages/core/src/skill/discovery.ts index a192ba10..2a4e0574 100644 --- a/packages/core/src/skill/discovery.ts +++ b/packages/core/src/skill/discovery.ts @@ -126,7 +126,7 @@ const layer = Layer.effect( } const skillUrl = new URL(`${encodeURIComponent(skill.name)}/`, source) - const versionFile = path.join(root, ".opencode-version") + const versionFile = path.join(root, ".deepcode-version") const files = skill.files.map((file) => { if (!isSafeRelativePath(file)) return undefined let resource: URL @@ -177,7 +177,7 @@ const layer = Layer.effect( (yield* fs.exists(path.join(staging, "SKILL.md")).pipe(Effect.orDie)) || (yield* fs.exists(path.join(staging, `${skill.name}.md`)).pipe(Effect.orDie)) if (!exists) return - yield* fs.writeFileString(path.join(staging, ".opencode-version"), version) + yield* fs.writeFileString(path.join(staging, ".deepcode-version"), version) yield* Effect.uninterruptible( Effect.gen(function* () { const cached = yield* fs.exists(root).pipe(Effect.orDie) diff --git a/packages/core/src/tool/AGENTS.md b/packages/core/src/tool/AGENTS.md index 5e8066e1..bbfe6f34 100644 --- a/packages/core/src/tool/AGENTS.md +++ b/packages/core/src/tool/AGENTS.md @@ -29,7 +29,7 @@ Leaves own resolution, permission, and side-effect ordering. Translate only expe ## Registration -Built-ins register through `Tools.Service.register({ [name]: tool })`. Application tools register through `ApplicationTools.Service.register(...)`, exposed publicly as `opencode.tools.register(...)`. +Built-ins register through `Tools.Service.register({ [name]: tool })`. Application tools register through `ApplicationTools.Service.register(...)`, exposed publicly as `deepcode.tools.register(...)`. Both are scoped: diff --git a/packages/core/src/tool/webfetch.ts b/packages/core/src/tool/webfetch.ts index d3889d6a..34ccd6a7 100644 --- a/packages/core/src/tool/webfetch.ts +++ b/packages/core/src/tool/webfetch.ts @@ -147,7 +147,7 @@ const layer = Layer.effectDiscard( const { body, contentType } = yield* Effect.gen(function* () { const response = yield* execute(http, input.url, input.format).pipe( - Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "opencode")), + Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "deepcode")), ) const contentType = response.headers["content-type"] || "" const mime = mimeFrom(contentType) diff --git a/packages/core/src/tool/websearch.ts b/packages/core/src/tool/websearch.ts index 6d622363..cf74abe6 100644 --- a/packages/core/src/tool/websearch.ts +++ b/packages/core/src/tool/websearch.ts @@ -73,11 +73,11 @@ export class ConfigService extends Context.Service()("@op export const defaultConfigLayer = Layer.sync(ConfigService, () => ConfigService.of({ provider: - process.env.OPENCODE_WEBSEARCH_PROVIDER === "exa" || process.env.OPENCODE_WEBSEARCH_PROVIDER === "parallel" - ? process.env.OPENCODE_WEBSEARCH_PROVIDER + process.env.DEEPCODE_WEBSEARCH_PROVIDER === "exa" || process.env.DEEPCODE_WEBSEARCH_PROVIDER === "parallel" + ? process.env.DEEPCODE_WEBSEARCH_PROVIDER : undefined, - enableExa: truthy("OPENCODE_EXPERIMENTAL") || truthy("OPENCODE_ENABLE_EXA") || truthy("OPENCODE_EXPERIMENTAL_EXA"), - enableParallel: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"), + enableExa: truthy("DEEPCODE_EXPERIMENTAL") || truthy("DEEPCODE_ENABLE_EXA") || truthy("DEEPCODE_EXPERIMENTAL_EXA"), + enableParallel: truthy("DEEPCODE_ENABLE_PARALLEL") || truthy("DEEPCODE_EXPERIMENTAL_PARALLEL"), exaApiKey: process.env.EXA_API_KEY, parallelApiKey: process.env.PARALLEL_API_KEY, }), @@ -237,7 +237,7 @@ const layer = Layer.effectDiscard( // V2 invocation context does not safely expose the model yet. }, { - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, ...(config.parallelApiKey ? { Authorization: `Bearer ${config.parallelApiKey}` } : {}), }, ) diff --git a/packages/core/src/v1/config/config.ts b/packages/core/src/v1/config/config.ts index 2e773f71..9cbb0ea3 100644 --- a/packages/core/src/v1/config/config.ts +++ b/packages/core/src/v1/config/config.ts @@ -36,10 +36,10 @@ export const Info = Schema.Struct({ shell: Schema.optional(Schema.String).annotate({ description: "Default shell to use for terminal and bash tool" }), logLevel: Schema.optional(LogLevelRef).annotate({ description: "Log level" }), server: Schema.optional(ConfigServerV1.Server).annotate({ - description: "Server configuration for opencode serve and web commands", + description: "Server configuration for DeepCode serve and web commands", }), command: Schema.optional(Schema.Record(Schema.String, ConfigCommandV1.Info)).annotate({ - description: "Command configuration, see https://opencode.ai/docs/commands", + description: "Command configuration for DeepCode", }), skills: Schema.optional(ConfigSkillsV1.Info).annotate({ description: "Additional skill folder paths" }), references: Schema.optional(ConfigReference.Info).annotate({ @@ -103,7 +103,7 @@ export const Info = Schema.Struct({ }), [Schema.Record(Schema.String, ConfigAgentV1.Info)], ), - ).annotate({ description: "Agent configuration, see https://opencode.ai/docs/agents" }), + ).annotate({ description: "Agent configuration for DeepCode" }), provider: Schema.optional(Schema.Record(Schema.String, ConfigProviderV1.Info)).annotate({ description: "Custom provider configurations and model overrides", }), diff --git a/packages/core/src/v1/config/server.ts b/packages/core/src/v1/config/server.ts index 64a89186..dc7c0adc 100644 --- a/packages/core/src/v1/config/server.ts +++ b/packages/core/src/v1/config/server.ts @@ -10,7 +10,7 @@ export const Server = Schema.Struct({ hostname: Schema.optional(Schema.String).annotate({ description: "Hostname to listen on" }), mdns: Schema.optional(Schema.Boolean).annotate({ description: "Enable mDNS service discovery" }), mdnsDomain: Schema.optional(Schema.String).annotate({ - description: "Custom domain name for mDNS service (default: opencode.local)", + description: "Custom domain name for mDNS service (default: deepcode.local)", }), cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Additional domains to allow for CORS", diff --git a/packages/core/test/effect/cross-spawn-spawner.test.ts b/packages/core/test/effect/cross-spawn-spawner.test.ts index fc4e3db6..6509466e 100644 --- a/packages/core/test/effect/cross-spawn-spawner.test.ts +++ b/packages/core/test/effect/cross-spawn-spawner.test.ts @@ -384,14 +384,14 @@ describe("cross-spawn spawner", () => { const out = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) => svc.string( - ChildProcess.make("set", ["OPENCODE_TEST_SHELL"], { + ChildProcess.make("set", ["DEEPCODE_TEST_SHELL"], { shell: true, extendEnv: true, - env: { OPENCODE_TEST_SHELL: "ok" }, + env: { DEEPCODE_TEST_SHELL: "ok" }, }), ), ) - expect(out).toContain("OPENCODE_TEST_SHELL=ok") + expect(out).toContain("DEEPCODE_TEST_SHELL=ok") }), ) diff --git a/packages/core/test/effect/observability.test.ts b/packages/core/test/effect/observability.test.ts index a409d8dc..410dd881 100644 --- a/packages/core/test/effect/observability.test.ts +++ b/packages/core/test/effect/observability.test.ts @@ -35,7 +35,7 @@ describe("resource", () => { process.env.OTEL_RESOURCE_ATTRIBUTES = "service.namespace=anomalyco,broken" expect(resource().attributes["service.namespace"]).toBeUndefined() - expect(resource().attributes["opencode.client"]).toBeDefined() + expect(resource().attributes["deepcode.client"]).toBeDefined() }) test("keeps built-in attributes when env values conflict", () => { @@ -44,11 +44,11 @@ describe("resource", () => { "opencode.client=web,service.instance.id=override,service.namespace=anomalyco" expect(resource().attributes).toMatchObject({ - "opencode.client": "cli", + "deepcode.client": "cli", "service.namespace": "anomalyco", }) expect(resource().attributes["service.instance.id"]).not.toBe("override") - expect(resource().attributes["opencode.run"]).toMatch(/^[0-9a-f]{8}$/) + expect(resource().attributes["deepcode.run"]).toMatch(/^[0-9a-f]{8}$/) }) }) diff --git a/packages/core/test/filesystem/watcher.test.ts b/packages/core/test/filesystem/watcher.test.ts index bce2bf69..4f8af772 100644 --- a/packages/core/test/filesystem/watcher.test.ts +++ b/packages/core/test/filesystem/watcher.test.ts @@ -30,8 +30,8 @@ const configLayer = Layer.succeed( const flagsLayer = ConfigProvider.layer( ConfigProvider.fromUnknown({ - OPENCODE_EXPERIMENTAL_FILEWATCHER: "true", - OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "false", + DEEPCODE_EXPERIMENTAL_FILEWATCHER: "true", + DEEPCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "false", }), ) diff --git a/packages/core/test/models.test.ts b/packages/core/test/models.test.ts index a3ed86dc..d7398de0 100644 --- a/packages/core/test/models.test.ts +++ b/packages/core/test/models.test.ts @@ -11,7 +11,7 @@ import { it } from "./lib/effect" import { readFile, rm, writeFile, utimes, mkdir } from "fs/promises" import path from "path" -// test/preload.ts pins OPENCODE_MODELS_PATH to a fixture so other tests can +// test/preload.ts pins DEEPCODE_MODELS_PATH to a fixture so other tests can // resolve providers without network. These tests need to drive the on-disk // cache themselves and silence the eager refresh fork. Save/restore around // the suite — never leak the mutation to subsequent test files in the same diff --git a/packages/core/test/plugin/provider-cerebras.test.ts b/packages/core/test/plugin/provider-cerebras.test.ts index adcc1779..cc3bfd13 100644 --- a/packages/core/test/plugin/provider-cerebras.test.ts +++ b/packages/core/test/plugin/provider-cerebras.test.ts @@ -43,7 +43,7 @@ describe("CerebrasPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras")))?.request.headers).toEqual({ Existing: "1", - "X-Cerebras-3rd-Party-Integration": "opencode", + "X-Cerebras-3rd-Party-Integration": "deepcode", }) }), ) diff --git a/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts b/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts index 008b9a46..58a87670 100644 --- a/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts @@ -164,7 +164,7 @@ describe("CloudflareAIGatewayPlugin", () => { skipCache: true, collectLog: false, headers: { - "User-Agent": expect.stringContaining("opencode/"), + "User-Agent": expect.stringContaining("deepcode/"), }, }, }) diff --git a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts index d1eeaa58..a9c6814c 100644 --- a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts @@ -200,7 +200,7 @@ describe("CloudflareWorkersAIPlugin", () => { const headers = yield* Effect.promise(() => Promise.resolve(cloudflareHeaders(result.sdk))) expect(headers.authorization).toBe("Bearer env-key") expect(headers.custom).toBe("header") - expect(headers["user-agent"]).toMatch(/^opencode\/.* cloudflare-workers-ai \(.+\) ai-sdk\/openai-compatible\//) + expect(headers["user-agent"]).toMatch(/^deepcode\/.* cloudflare-workers-ai \(.+\) ai-sdk\/openai-compatible\//) }), ), ) diff --git a/packages/core/test/plugin/provider-kilo.test.ts b/packages/core/test/plugin/provider-kilo.test.ts index 1df0fd31..3b0984fe 100644 --- a/packages/core/test/plugin/provider-kilo.test.ts +++ b/packages/core/test/plugin/provider-kilo.test.ts @@ -39,8 +39,8 @@ describe("KiloPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ Existing: "value", - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }) expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) }), @@ -61,8 +61,8 @@ describe("KiloPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }) expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).not.toHaveProperty( "http-referer", @@ -90,8 +90,8 @@ describe("KiloPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }) expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo")))?.request.headers).toEqual({}) }), diff --git a/packages/core/test/plugin/provider-llmgateway.test.ts b/packages/core/test/plugin/provider-llmgateway.test.ts index d7f9d0d7..6f7ffea2 100644 --- a/packages/core/test/plugin/provider-llmgateway.test.ts +++ b/packages/core/test/plugin/provider-llmgateway.test.ts @@ -46,9 +46,9 @@ describe("LLMGatewayPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.request.headers).toEqual({ Existing: "value", - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", - "X-Source": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", + "X-Source": "deepcode", }) expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) }), diff --git a/packages/core/test/plugin/provider-nvidia.test.ts b/packages/core/test/plugin/provider-nvidia.test.ts index a1c05df3..bd91902f 100644 --- a/packages/core/test/plugin/provider-nvidia.test.ts +++ b/packages/core/test/plugin/provider-nvidia.test.ts @@ -39,9 +39,9 @@ describe("NvidiaPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ Existing: "value", - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", - "X-BILLING-INVOKE-ORIGIN": "OpenCode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", + "X-BILLING-INVOKE-ORIGIN": "DeepCode", }) expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) }), @@ -62,9 +62,9 @@ describe("NvidiaPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", - "X-BILLING-INVOKE-ORIGIN": "OpenCode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", + "X-BILLING-INVOKE-ORIGIN": "DeepCode", }) }), ) @@ -88,8 +88,8 @@ describe("NvidiaPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", "X-BILLING-INVOKE-ORIGIN": "CustomOrigin", }) }), diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index 20af84d0..908b7f62 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -198,7 +198,7 @@ describe("OpencodePlugin", () => { ) it.effect("uses a public key and disables paid models without credentials", () => - withEnv({ OPENCODE_API_KEY: undefined }, () => + withEnv({ DEEPCODE_API_KEY: undefined }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { @@ -224,7 +224,7 @@ describe("OpencodePlugin", () => { ) it.effect("keeps free models without credentials", () => - withEnv({ OPENCODE_API_KEY: undefined }, () => + withEnv({ DEEPCODE_API_KEY: undefined }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { @@ -250,7 +250,7 @@ describe("OpencodePlugin", () => { ) it.effect("treats output-only cost as free without credentials", () => - withEnv({ OPENCODE_API_KEY: undefined }, () => + withEnv({ DEEPCODE_API_KEY: undefined }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { @@ -277,8 +277,8 @@ describe("OpencodePlugin", () => { ), ) - it.effect("uses OPENCODE_API_KEY as credentials", () => - withEnv({ OPENCODE_API_KEY: "secret" }, () => + it.effect("uses DEEPCODE_API_KEY as credentials", () => + withEnv({ DEEPCODE_API_KEY: "secret" }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { @@ -304,14 +304,14 @@ describe("OpencodePlugin", () => { ) it.effect("uses configured provider env vars as credentials", () => - withEnv({ OPENCODE_API_KEY: undefined, CUSTOM_OPENCODE_API_KEY: "secret" }, () => + withEnv({ DEEPCODE_API_KEY: undefined, CUSTOM_DEEPCODE_API_KEY: "secret" }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service const integrations = yield* Integration.Service yield* integrations.transform((editor) => { editor.method.update({ integrationID: Integration.ID.make("opencode"), - method: { type: "env", names: ["CUSTOM_OPENCODE_API_KEY"] }, + method: { type: "env", names: ["CUSTOM_DEEPCODE_API_KEY"] }, }) }) yield* catalog.transform((catalog) => { @@ -337,7 +337,7 @@ describe("OpencodePlugin", () => { ) it.effect("uses configured apiKey as credentials", () => - withEnv({ OPENCODE_API_KEY: undefined }, () => + withEnv({ DEEPCODE_API_KEY: undefined }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { @@ -369,7 +369,7 @@ describe("OpencodePlugin", () => { ) it.effect("ignores non-opencode providers and models", () => - withEnv({ OPENCODE_API_KEY: undefined }, () => + withEnv({ DEEPCODE_API_KEY: undefined }, () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { diff --git a/packages/core/test/plugin/provider-openrouter.test.ts b/packages/core/test/plugin/provider-openrouter.test.ts index 95394367..59975dba 100644 --- a/packages/core/test/plugin/provider-openrouter.test.ts +++ b/packages/core/test/plugin/provider-openrouter.test.ts @@ -39,8 +39,8 @@ describe("OpenRouterPlugin", () => { expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({ Existing: "value", - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }) expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({}) }), diff --git a/packages/core/test/plugin/provider-vercel.test.ts b/packages/core/test/plugin/provider-vercel.test.ts index eb87b151..834cd22e 100644 --- a/packages/core/test/plugin/provider-vercel.test.ts +++ b/packages/core/test/plugin/provider-vercel.test.ts @@ -32,8 +32,8 @@ describe("VercelPlugin", () => { yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.request.headers).toEqual({ Existing: "1", - "http-referer": "https://opencode.ai/", - "x-title": "opencode", + "http-referer": "https://github.com/yuanchenglu/deepcode", + "x-title": "deepcode", }) }), ) diff --git a/packages/core/test/plugin/provider-zenmux.test.ts b/packages/core/test/plugin/provider-zenmux.test.ts index b9d34a1f..9bd4ea80 100644 --- a/packages/core/test/plugin/provider-zenmux.test.ts +++ b/packages/core/test/plugin/provider-zenmux.test.ts @@ -41,7 +41,7 @@ describe("ZenmuxPlugin", () => { }) yield* addPlugin() const result = required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))) - expect(result.request.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }) + expect(result.request.headers).toEqual({ "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", "X-Title": "deepcode" }) expect(Object.keys(result.request.headers).sort()).toEqual(["HTTP-Referer", "X-Title"]) }), ) @@ -63,8 +63,8 @@ describe("ZenmuxPlugin", () => { expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({ Existing: "value", - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }) }), ) diff --git a/packages/core/test/plugin/skill.test.ts b/packages/core/test/plugin/skill.test.ts index 61bee856..501d4b82 100644 --- a/packages/core/test/plugin/skill.test.ts +++ b/packages/core/test/plugin/skill.test.ts @@ -9,17 +9,19 @@ import { host } from "./host" const it = testEffect(AppNodeBuilder.build(SkillV2.node)) describe("SkillPlugin.Plugin", () => { - it.effect("registers the built-in customize-opencode skill", () => + it.effect("registers the built-in customize-deepcode skill", () => Effect.gen(function* () { const skill = yield* SkillV2.Service yield* SkillPlugin.Plugin.effect(host({ skill: { ...skill, reload: skill.reload } })) - expect(yield* skill.list()).toContainEqual( + const list = yield* skill.list() + expect(list).toContainEqual( expect.objectContaining({ - name: "customize-opencode", - description: expect.stringContaining("opencode's own configuration"), + name: "customize-deepcode", + description: expect.stringContaining("DeepCode configuration"), }), ) + expect(list.some((item) => item.name === "customize-opencode")).toBe(false) }), ) }) diff --git a/packages/core/test/project.test.ts b/packages/core/test/project.test.ts index fa709a8b..1d862842 100644 --- a/packages/core/test/project.test.ts +++ b/packages/core/test/project.test.ts @@ -147,14 +147,14 @@ describe("ProjectV2.resolve", () => { }), ) - it.live("returns previous cached id from common dir", () => + it.live("returns previous DeepCode cached id from common dir", () => Effect.gen(function* () { const tmp = yield* Effect.acquireRelease( Effect.promise(() => tmpdir()), (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ) yield* Effect.promise(() => initRepo(tmp.path, { commit: true, remote: "git@github.com:owner/repo.git" })) - yield* Effect.promise(() => Bun.write(path.join(tmp.path, ".git", "opencode"), "old-id")) + yield* Effect.promise(() => Bun.write(path.join(tmp.path, ".git", "deepcode"), "old-id")) const project = yield* ProjectV2.Service const result = yield* project.resolve(abs(tmp.path)) @@ -164,6 +164,26 @@ describe("ProjectV2.resolve", () => { }), ) + it.live("ignores an OpenCode cache file", () => + Effect.gen(function* () { + const tmp = yield* Effect.acquireRelease( + Effect.promise(() => tmpdir()), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ) + yield* Effect.promise(() => initRepo(tmp.path, { commit: true, remote: "git@github.com:owner/repo.git" })) + yield* Effect.promise(() => Bun.write(path.join(tmp.path, ".git", "opencode"), "opencode-id")) + const project = yield* ProjectV2.Service + + const result = yield* project.resolve(abs(tmp.path)) + + expect(result.previous).toBeUndefined() + expect(result.id).toBe(remoteID("github.com/owner/repo")) + expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "opencode")).text())).toBe( + "opencode-id", + ) + }), + ) + it.live("does not write the cache while resolving", () => Effect.gen(function* () { const tmp = yield* Effect.acquireRelease( @@ -175,7 +195,7 @@ describe("ProjectV2.resolve", () => { yield* project.resolve(abs(tmp.path)) - expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "opencode")).exists())).toBe(false) + expect(yield* Effect.promise(() => Bun.file(path.join(tmp.path, ".git", "deepcode")).exists())).toBe(false) }), ) @@ -206,7 +226,7 @@ describe("ProjectV2.resolve", () => { Effect.promise(() => $`rm -rf ${worktree}`.quiet().nothrow()).pipe(Effect.ignore), ) yield* Effect.promise(() => initRepo(tmp.path, { commit: true, remote: "git@github.com:owner/repo.git" })) - yield* Effect.promise(() => Bun.write(path.join(tmp.path, ".git", "opencode"), "old-id")) + yield* Effect.promise(() => Bun.write(path.join(tmp.path, ".git", "deepcode"), "old-id")) yield* Effect.promise(() => $`git worktree add ${worktree} -b test-${Date.now()}`.cwd(tmp.path).quiet()) const project = yield* ProjectV2.Service diff --git a/packages/core/test/pty/pty-session.test.ts b/packages/core/test/pty/pty-session.test.ts index 6d049b49..9052a255 100644 --- a/packages/core/test/pty/pty-session.test.ts +++ b/packages/core/test/pty/pty-session.test.ts @@ -45,7 +45,7 @@ const subscribePtyEvents = Effect.fn("PtySessionTest.subscribePtyEvents")(functi const createPty = Effect.fn("PtySessionTest.createPty")(function* (command: string, args: string[] = []) { const pty = yield* Pty.Service return yield* Effect.acquireRelease( - pty.create({ command, args, cwd: "/tmp", env: { TERM: "xterm-256color", OPENCODE_TERMINAL: "1" } }), + pty.create({ command, args, cwd: "/tmp", env: { TERM: "xterm-256color", DEEPCODE_TERMINAL: "1" } }), (info) => pty.remove(info.id).pipe(Effect.ignore), ) }) diff --git a/packages/core/test/tool-webfetch.test.ts b/packages/core/test/tool-webfetch.test.ts index 2fe46f0a..314eed6f 100644 --- a/packages/core/test/tool-webfetch.test.ts +++ b/packages/core/test/tool-webfetch.test.ts @@ -260,7 +260,7 @@ describe("WebFetchTool registration", () => { }) expect(requests).toHaveLength(2) expect(requests[0]?.headers["user-agent"]).toContain("Mozilla/5.0") - expect(requests[1]?.headers["user-agent"]).toBe("opencode") + expect(requests[1]?.headers["user-agent"]).toBe("deepcode") }), ) diff --git a/packages/opencode/src/acp/profile.ts b/packages/opencode/src/acp/profile.ts index 9e728b6a..2cb288db 100644 --- a/packages/opencode/src/acp/profile.ts +++ b/packages/opencode/src/acp/profile.ts @@ -1,4 +1,4 @@ -const enabled = process.env.OPENCODE_ACP_PROFILE === "1" +const enabled = process.env.DEEPCODE_ACP_PROFILE === "1" const started = performance.now() export function mark(name: string, fields?: Record) { diff --git a/packages/opencode/src/acp/service.ts b/packages/opencode/src/acp/service.ts index 7257efa7..f7d8322d 100644 --- a/packages/opencode/src/acp/service.ts +++ b/packages/opencode/src/acp/service.ts @@ -30,6 +30,7 @@ import { type SetSessionModeResponse, } from "@agentclientprotocol/sdk" import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { Product } from "@opencode-ai/core/product" import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import type { AssistantMessage, Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2" import { Context, Effect, Layer, ManagedRuntime } from "effect" @@ -46,7 +47,7 @@ import { ModelV2 } from "@opencode-ai/core/model" import { Provider } from "@/provider/provider" import type { Command } from "@/command" -export const AuthMethodID = "opencode-login" +export const AuthMethodID = "deepcode-login" export type Error = ACPError.Error type ServiceConnection = Pick & @@ -92,17 +93,17 @@ export function make(input: { const initialize = Effect.fn("ACP.initialize")(function* (params: InitializeRequest) { const started = performance.now() const authMethod: AuthMethod = { - description: "Run `opencode auth login` in the terminal", - name: "Login with opencode", + description: "Run `deepcode auth login` in the terminal", + name: "Login with DeepCode", id: AuthMethodID, } if (params.clientCapabilities?._meta?.["terminal-auth"] === true) { authMethod._meta = { "terminal-auth": { - command: "opencode", + command: Product.cli, args: ["auth", "login"], - label: "OpenCode Login", + label: "DeepCode Login", }, } } @@ -128,7 +129,7 @@ export function make(input: { }, authMethods: [authMethod], agentInfo: { - name: "OpenCode", + name: "DeepCode", version: InstallationVersion, }, } diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 536a642f..e2c14e22 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -170,7 +170,7 @@ const layer = Layer.effect( }, edit: { "*": "deny", - [path.join(".opencode", "plans", "*.md")]: "allow", + [path.join(".deepcode", "plans", "*.md")]: "allow", [path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow", }, }), diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index a133e884..82894d03 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -56,9 +56,9 @@ const layer = Layer.effect( const decode = Schema.decodeUnknownOption(Info) const all = Effect.fn("Auth.all")(function* () { - if (process.env.OPENCODE_AUTH_CONTENT) { + if (process.env.DEEPCODE_AUTH_CONTENT) { try { - return JSON.parse(process.env.OPENCODE_AUTH_CONTENT) + return JSON.parse(process.env.DEEPCODE_AUTH_CONTENT) } catch (err) {} } diff --git a/packages/opencode/src/cli/cmd/account.ts b/packages/opencode/src/cli/cmd/account.ts index b9cbf556..a8e71170 100644 --- a/packages/opencode/src/cli/cmd/account.ts +++ b/packages/opencode/src/cli/cmd/account.ts @@ -15,7 +15,7 @@ const dim = (value: string) => UI.Style.TEXT_DIM + value + UI.Style.TEXT_NORMAL const activeSuffix = (isActive: boolean) => (isActive ? dim(" (active)") : "") -export const defaultConsoleUrl = "https://console.opencode.ai" +export const defaultConsoleUrl: string | undefined = undefined export const formatAccountLabel = (account: { email: string; url: string }, isActive: boolean) => `${account.email} ${dim(account.url)}${activeSuffix(isActive)}` @@ -185,7 +185,11 @@ export const LoginCommand = effectCmd({ }), handler: Effect.fn("Cli.account.login")(function* (args) { UI.empty() - yield* Effect.orDie(loginEffect(args.url ?? defaultConsoleUrl)) + if (!args.url) { + yield* Prompt.log.error("A DeepCode account server URL is required; no default upstream account service is configured.") + return yield* Prompt.outro("Login cancelled") + } + yield* Effect.orDie(loginEffect(args.url)) }), }) diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts index da47d957..784112e9 100644 --- a/packages/opencode/src/cli/cmd/acp.ts +++ b/packages/opencode/src/cli/cmd/acp.ts @@ -20,7 +20,7 @@ export const AcpCommand = effectCmd({ const { Server } = yield* Effect.promise(() => import("@/server/server")) const { ACP } = yield* Effect.promise(() => import("@/acp/agent")) ACPProfile.mark("cli.acp.handler") - process.env.OPENCODE_CLIENT = "acp" + process.env.DEEPCODE_CLIENT = "acp" const opts = yield* resolveNetworkOptions(args) const server = yield* Effect.promise(() => ACPProfile.measure("cli.acp.server.listen", () => Server.listen(opts))) diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index c9c1d2c1..e6ae96a9 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -2,6 +2,7 @@ import { cmd } from "./cmd" import * as prompts from "@clack/prompts" import { UI } from "../ui" import { Global } from "@opencode-ai/core/global" +import { Product } from "@opencode-ai/core/product" import path from "path" import fs from "fs/promises" import { Filesystem } from "@/util/filesystem" @@ -108,7 +109,10 @@ const AgentCreateCommand = effectCmd({ if (prompts.isCancel(scopeResult)) throw new UI.CancelledError() scope = scopeResult } - targetPath = path.join(scope === "global" ? Global.Path.config : path.join(ctx.worktree, ".opencode"), "agents") + targetPath = path.join( + scope === "global" ? Global.Path.config : path.join(ctx.worktree, Product.config.directory), + "agents", + ) } // Get description @@ -186,12 +190,9 @@ const AgentCreateCommand = effectCmd({ // Build permissions config — deny anything not explicitly selected. const permissions: Record = {} for (const permission of AVAILABLE_PERMISSIONS) { - if (!selected.includes(permission)) { - permissions[permission] = "deny" - } + if (!selected.includes(permission)) permissions[permission] = "deny" } - // Build frontmatter const frontmatter: { description: string mode: AgentMode @@ -200,11 +201,8 @@ const AgentCreateCommand = effectCmd({ description: generated.whenToUse, mode, } - if (Object.keys(permissions).length > 0) { - frontmatter.permission = permissions - } + if (Object.keys(permissions).length > 0) frontmatter.permission = permissions - // Write file const content = matter.stringify(generated.systemPrompt, frontmatter) const filePath = path.join(targetPath, `${generated.identifier}.md`) @@ -221,9 +219,8 @@ const AgentCreateCommand = effectCmd({ await Filesystem.write(filePath, content) - if (isFullyNonInteractive) { - console.log(filePath) - } else { + if (isFullyNonInteractive) console.log(filePath) + else { prompts.log.success(`Agent created: ${filePath}`) prompts.outro("Done") } @@ -238,9 +235,7 @@ const AgentListCommand = effectCmd({ const { Agent } = yield* Effect.promise(() => import("../../agent/agent")) const agents = yield* Agent.Service.use((svc) => svc.list()) const sortedAgents = agents.sort((a, b) => { - if (a.native !== b.native) { - return a.native ? -1 : 1 - } + if (a.native !== b.native) return a.native ? -1 : 1 return a.name.localeCompare(b.name) }) diff --git a/packages/opencode/src/cli/cmd/attach.ts b/packages/opencode/src/cli/cmd/attach.ts index 7dc43ba0..570ed5cc 100644 --- a/packages/opencode/src/cli/cmd/attach.ts +++ b/packages/opencode/src/cli/cmd/attach.ts @@ -6,7 +6,7 @@ import { ServerAuth } from "@/server/auth" export const AttachCommand = cmd({ command: "attach ", - describe: "attach to a running opencode server", + describe: "attach to a running DeepCode server", builder: (yargs) => yargs .positional("url", { @@ -35,7 +35,7 @@ export const AttachCommand = cmd({ .option("password", { alias: ["p"], type: "string", - describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)", + describe: "basic auth password (defaults to DEEPCODE_SERVER_PASSWORD)", }) .option("username", { alias: ["u"], diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts index 9dcaa33b..7461be6a 100644 --- a/packages/opencode/src/cli/cmd/debug/index.ts +++ b/packages/opencode/src/cli/cmd/debug/index.ts @@ -58,7 +58,7 @@ const InfoCommand = effectCmd({ : undefined const terminal = [termProgram, process.env.TERM].filter((item): item is string => Boolean(item)).join(" / ") - console.log(`opencode version: ${InstallationVersion}`) + console.log(`deepcode version: ${InstallationVersion}`) console.log(`os: ${os.type()} ${os.release()} ${os.arch()}`) console.log(`terminal: ${terminal || "unknown"}`) console.log("plugins:") diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index eccbb375..41252d67 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -1,42 +1,33 @@ import { Effect } from "effect" import { cmd } from "./cmd" -import { effectCmd } from "../effect-cmd" +import { effectCmd, fail } from "../effect-cmd" export { extractResponseText, formatPromptTooLargeError, parseGitHubRemote } from "./github.shared" +const unavailable = () => + fail( + "The GitHub agent is disabled until DeepCode provides a verified DeepCode-owned app, API endpoint, workflow, and sharing channel.", + ) + export const GithubInstallCommand = effectCmd({ command: "install", - describe: "install the GitHub agent", - handler: () => - Effect.gen(function* () { - const { githubInstall } = yield* Effect.promise(() => import("./github.handler")) - return yield* githubInstall() - }), + describe: "install the GitHub agent (currently unavailable)", + handler: Effect.fn("Cli.github.install")(unavailable), }) export const GithubRunCommand = effectCmd({ command: "run", - describe: "run the GitHub agent", + describe: "run the GitHub agent (currently unavailable)", builder: (yargs) => yargs - .option("event", { - type: "string", - describe: "GitHub mock event to run the agent for", - }) - .option("token", { - type: "string", - describe: "GitHub personal access token (github_pat_********)", - }), - handler: (args) => - Effect.gen(function* () { - const { githubRun } = yield* Effect.promise(() => import("./github.handler")) - return yield* githubRun(args) - }), + .option("event", { type: "string", describe: "GitHub mock event to run the agent for" }) + .option("token", { type: "string", describe: "GitHub personal access token" }), + handler: Effect.fn("Cli.github.run")(unavailable), }) export const GithubCommand = cmd({ command: "github", - describe: "manage GitHub agent", + describe: "manage GitHub agent (currently unavailable)", builder: (yargs) => yargs.command(GithubInstallCommand).command(GithubRunCommand).demandCommand(), async handler() {}, }) diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index b929b8bc..44d3b3fa 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -17,6 +17,7 @@ import { InstanceRef } from "@/effect/instance-ref" import { InstallationVersion } from "@opencode-ai/core/installation/version" import path from "path" import { Global } from "@opencode-ai/core/global" +import { Product } from "@opencode-ai/core/product" import { modify, applyEdits } from "jsonc-parser" import { Filesystem } from "@/util/filesystem" import { Effect } from "effect" @@ -119,7 +120,7 @@ export const McpListCommand = effectCmd({ if (servers.length === 0) { prompts.log.warn("No MCP servers configured") - prompts.outro("Add servers with: opencode mcp add") + prompts.outro("Add servers with: deepcode mcp add") return } @@ -187,7 +188,7 @@ export const McpAuthCommand = effectCmd({ if (servers.length === 0) { prompts.log.warn("No OAuth-capable MCP servers configured") - prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:") + prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in deepcode.json:") prompts.log.info(` "mcp": { "my-server": { @@ -392,21 +393,20 @@ export const McpLogoutCommand = effectCmd({ }) async function resolveConfigPath(baseDir: string, global = false) { - // Check for existing config files (prefer .jsonc over .json, check .opencode/ subdirectory too) - const candidates = [path.join(baseDir, "opencode.json"), path.join(baseDir, "opencode.jsonc")] + // Prefer JSONC, then JSON, and never discover OpenCode configuration. + const files = [...Product.config.files].toReversed() + const candidates = files.map((file) => path.join(baseDir, file)) if (!global) { - candidates.push(path.join(baseDir, ".opencode", "opencode.json"), path.join(baseDir, ".opencode", "opencode.jsonc")) + candidates.push(...files.map((file) => path.join(baseDir, Product.config.directory, file))) } for (const candidate of candidates) { - if (await Filesystem.exists(candidate)) { - return candidate - } + if (await Filesystem.exists(candidate)) return candidate } - // Default to opencode.json if none exist - return candidates[0] + // New configuration is written to the canonical project DeepCode file. + return path.join(baseDir, Product.config.files[0]) } async function addMcpToConfig(name: string, mcpConfig: ConfigMCPV1.Info, configPath: string) { diff --git a/packages/opencode/src/cli/cmd/plug.ts b/packages/opencode/src/cli/cmd/plug.ts index 1529e9b7..acc2e1d9 100644 --- a/packages/opencode/src/cli/cmd/plug.ts +++ b/packages/opencode/src/cli/cmd/plug.ts @@ -28,7 +28,7 @@ export type PlugDeps = { readText: (file: string) => Promise write: (file: string, text: string) => Promise exists: (file: string) => Promise - files: (dir: string, name: "opencode" | "tui") => string[] + files: (dir: string, name: string) => string[] global: string } diff --git a/packages/opencode/src/cli/cmd/pr.ts b/packages/opencode/src/cli/cmd/pr.ts index 42097223..01d00129 100644 --- a/packages/opencode/src/cli/cmd/pr.ts +++ b/packages/opencode/src/cli/cmd/pr.ts @@ -4,10 +4,11 @@ import { effectCmd, fail } from "../effect-cmd" import { Git } from "@/git" import { InstanceRef } from "@/effect/instance-ref" import { Process } from "@/util/process" +import { Product } from "@opencode-ai/core/product" export const PrCommand = effectCmd({ command: "pr ", - describe: "fetch and checkout a GitHub PR branch, then run opencode", + describe: "fetch and checkout a GitHub PR branch, then run DeepCode", builder: (yargs) => yargs.positional("number", { type: "number", @@ -43,14 +44,12 @@ export const PrCommand = effectCmd({ "view", `${prNumber}`, "--json", - "headRepository,headRepositoryOwner,isCrossRepository,headRefName,body", + "headRepository,headRepositoryOwner,isCrossRepository,headRefName", ], { nothrow: true }, ), ) - let sessionId: string | undefined - if (prInfoResult.code === 0 && prInfoResult.text.trim()) { const prInfo = JSON.parse(prInfoResult.text) @@ -71,37 +70,16 @@ export const PrCommand = effectCmd({ cwd: worktree, }) } - - if (prInfo?.body) { - const sessionMatch = prInfo.body.match(/https:\/\/opncd\.ai\/s\/([a-zA-Z0-9_-]+)/) - if (sessionMatch) { - const sessionUrl = sessionMatch[0] - UI.println(`Found opencode session: ${sessionUrl}`) - UI.println(`Importing session...`) - - const importResult = yield* Effect.promise(() => - Process.text(["opencode", "import", sessionUrl], { nothrow: true }), - ) - if (importResult.code === 0) { - const sessionIdMatch = importResult.text.trim().match(/Imported session: ([a-zA-Z0-9_-]+)/) - if (sessionIdMatch) { - sessionId = sessionIdMatch[1] - UI.println(`Session imported: ${sessionId}`) - } - } - } - } } UI.println(`Successfully checked out PR #${prNumber} as branch '${localBranchName}'`) UI.println() - UI.println("Starting opencode...") + UI.println("Starting DeepCode...") UI.println() - const opencodeArgs = sessionId ? ["-s", sessionId] : [] const code = yield* Effect.promise( () => - Process.spawn(["opencode", ...opencodeArgs], { + Process.spawn([Product.cli], { stdin: "inherit", stdout: "inherit", stderr: "inherit", @@ -110,6 +88,6 @@ export const PrCommand = effectCmd({ ) // Match legacy throw semantics — propagate as a defect so the top-level // index.ts catch handles it identically (exit 1, "Unexpected error" banner). - if (code !== 0) return yield* Effect.die(new Error(`opencode exited with code ${code}`)) + if (code !== 0) return yield* Effect.die(new Error(`DeepCode exited with code ${code}`)) }), }) diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 3775123d..112f4878 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -304,7 +304,7 @@ export const ProvidersLoginCommand = effectCmd({ builder: (yargs: Argv) => yargs .positional("url", { - describe: "opencode auth provider", + describe: "DeepCode auth provider", type: "string", }) .option("provider", { @@ -449,7 +449,7 @@ export const ProvidersLoginCommand = effectCmd({ } yield* Prompt.log.warn( - `This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`, + `This only stores a credential for ${provider} - you will need configure it in deepcode.json, check the docs for examples.`, ) } @@ -458,13 +458,13 @@ export const ProvidersLoginCommand = effectCmd({ "Amazon Bedrock authentication priority:\n" + " 1. Bearer token (AWS_BEARER_TOKEN_BEDROCK or /connect)\n" + " 2. AWS credential chain (profile, access keys, IAM roles, EKS IRSA)\n\n" + - "Configure via opencode.json options (profile, region, endpoint) or\n" + + "Configure via deepcode.json options (profile, region, endpoint) or\n" + "AWS environment variables (AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_WEB_IDENTITY_TOKEN_FILE).", ) } if (provider === "opencode") { - yield* Prompt.log.info("Create an api key at https://opencode.ai/auth") + yield* Prompt.log.info("The OpenCode-compatible provider requires an explicit API key and endpoint configuration") } if (provider === "vercel") { @@ -473,7 +473,7 @@ export const ProvidersLoginCommand = effectCmd({ if (["cloudflare", "cloudflare-ai-gateway"].includes(provider)) { yield* Prompt.log.info( - "Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://opencode.ai/docs/providers/#cloudflare-ai-gateway", + "Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Consult the Cloudflare AI Gateway documentation for these variables", ) } diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 8fb5337e..f4d8c79b 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -1,14 +1,14 @@ import type { PermissionV1 } from "@opencode-ai/core/v1/permission" import { FSUtil } from "@opencode-ai/core/fs-util" -// CLI entry point for `opencode run` and `opencode --mini`. +// CLI entry point for `deepcode run` and `deepcode --mini`. // // Handles three modes: // 1. Non-interactive (default): sends a single prompt, streams events to // stdout, and exits when the session goes idle. -// 2. Interactive local (`opencode --mini`): boots the split-footer direct mode +// 2. Interactive local (`deepcode --mini`): boots the split-footer direct mode // with an in-process server (no external HTTP). -// 3. Interactive attach (`opencode --mini --attach`): connects to a running -// opencode server and runs interactive mode against it. +// 3. Interactive attach (`deepcode --mini --attach`): connects to a running +// DeepCode server and runs interactive mode against it. // // Also supports `--command` for slash-command execution, `--format json` for // raw event streaming, `--continue` / `--session` for session resumption, @@ -125,7 +125,7 @@ async function toolError(part: ToolPart) { export const RunCommand = effectCmd({ command: "run [message..]", - describe: "run opencode with a message", + describe: "run DeepCode with a message", // --attach connects to a remote server (no local instance needed); the // default path runs an in-process server and needs the project instance. instance: (args) => !args.attach, @@ -189,12 +189,12 @@ export const RunCommand = effectCmd({ }) .option("attach", { type: "string", - describe: "attach to a running opencode server (e.g., http://localhost:4096)", + describe: "attach to a running DeepCode server (e.g., http://localhost:4096)", }) .option("password", { alias: ["p"], type: "string", - describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)", + describe: "basic auth password (defaults to DEEPCODE_SERVER_PASSWORD)", }) .option("username", { alias: ["u"], @@ -949,7 +949,7 @@ export const RunCommand = effectCmd({ return Server.Default().app.fetch(new Request(request, { headers })) }) as typeof globalThis.fetch const sdk = createOpencodeClient({ - baseUrl: "http://opencode.internal", + baseUrl: "http://deepcode.internal", fetch: fetchFn, directory, }) @@ -977,7 +977,7 @@ type MiniCommandInput = { export async function runMini(input: MiniCommandInput) { if (!RunCommand.handler) throw new Error("Mini command handler is unavailable") await RunCommand.handler({ - $0: "opencode", + $0: "deepcode", _: ["mini"], message: input.prompt ? [input.prompt] : [], command: undefined, diff --git a/packages/opencode/src/cli/cmd/run/runtime.ts b/packages/opencode/src/cli/cmd/run/runtime.ts index 90cddffa..4325079f 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.ts @@ -1,4 +1,4 @@ -// Top-level orchestrator for `opencode --mini`. +// Top-level orchestrator for `deepcode --mini`. // // Wires the boot sequence, lifecycle (renderer + footer), stream transport, // and prompt queue together into a single session loop. Two entry points: @@ -734,7 +734,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep // the in-process server, so no external HTTP server is needed. export async function runInteractiveLocalMode(input: RunLocalInput): Promise { const sdk = createOpencodeClient({ - baseUrl: "http://opencode.internal", + baseUrl: "http://deepcode.internal", fetch: input.fetch, directory: input.directory, }) diff --git a/packages/opencode/src/cli/cmd/run/splash.ts b/packages/opencode/src/cli/cmd/run/splash.ts index 141ff6fc..bcdbed89 100644 --- a/packages/opencode/src/cli/cmd/run/splash.ts +++ b/packages/opencode/src/cli/cmd/run/splash.ts @@ -1,6 +1,6 @@ // Entry and exit splash banners for direct interactive mode scrollback. // -// Renders the full opencode entry logo and a compact [O] exit badge, plus +// Renders the full DeepCode entry logo and a compact [O] exit badge, plus // session metadata and the resume command. These are scrollback snapshots, so // they become immutable terminal history once committed. // @@ -194,7 +194,7 @@ function build(input: SplashWriterInput, kind: "entry" | "exit", ctx: Scrollback }) } - push(lines, body_left, top, "OpenCode", right, undefined, TextAttributes.BOLD) + push(lines, body_left, top, "DeepCode", right, undefined, TextAttributes.BOLD) if (input.detail) { push( lines, @@ -234,7 +234,7 @@ function build(input: SplashWriterInput, kind: "entry" | "exit", ctx: Scrollback lines, body_left + label.length, top + 1, - `opencode --mini -s ${meta.session_id}`, + `deepcode --mini -s ${meta.session_id}`, right, undefined, TextAttributes.BOLD, diff --git a/packages/opencode/src/cli/cmd/run/tool.ts b/packages/opencode/src/cli/cmd/run/tool.ts index 9a717ba4..994065ec 100644 --- a/packages/opencode/src/cli/cmd/run/tool.ts +++ b/packages/opencode/src/cli/cmd/run/tool.ts @@ -1,4 +1,4 @@ -// Per-tool display rules shared across `opencode run` output paths. +// Per-tool display rules shared across `deepcode run` output paths. // // Each known tool (bash, edit, write, task, etc.) has a ToolRule that controls // five display hooks: diff --git a/packages/opencode/src/cli/cmd/run/trace.ts b/packages/opencode/src/cli/cmd/run/trace.ts index 5ed22039..c059746e 100644 --- a/packages/opencode/src/cli/cmd/run/trace.ts +++ b/packages/opencode/src/cli/cmd/run/trace.ts @@ -1,7 +1,7 @@ // Dev-only JSONL event trace for direct interactive mode. // -// Enable with OPENCODE_DIRECT_TRACE=1. Writes one JSON line per event to -// ~/.local/share/opencode/log/direct/-.jsonl. Also writes +// Enable with DEEPCODE_DIRECT_TRACE=1. Writes one JSON line per event to +// ~/.local/share/deepcode/log/direct/-.jsonl. Also writes // a latest.json pointer so you can quickly find the most recent trace. // // The trace captures the full closed loop: outbound prompts, inbound SDK @@ -55,7 +55,7 @@ export function trace(): Trace | undefined { return state || undefined } - if (!process.env.OPENCODE_DIRECT_TRACE) { + if (!process.env.DEEPCODE_DIRECT_TRACE) { state = false return undefined } diff --git a/packages/opencode/src/cli/cmd/run/types.ts b/packages/opencode/src/cli/cmd/run/types.ts index a914922e..946e446b 100644 --- a/packages/opencode/src/cli/cmd/run/types.ts +++ b/packages/opencode/src/cli/cmd/run/types.ts @@ -1,4 +1,4 @@ -// Shared type vocabulary for the direct interactive mode (`opencode --mini`). +// Shared type vocabulary for the direct interactive mode (`deepcode --mini`). // // Direct mode uses a split-footer terminal layout: immutable scrollback for the // session transcript, and a mutable footer for prompt input, status, and diff --git a/packages/opencode/src/cli/cmd/run/variant.shared.ts b/packages/opencode/src/cli/cmd/run/variant.shared.ts index e685ceb0..f1c4ed10 100644 --- a/packages/opencode/src/cli/cmd/run/variant.shared.ts +++ b/packages/opencode/src/cli/cmd/run/variant.shared.ts @@ -3,7 +3,7 @@ // Variants are provider-specific reasoning effort levels (e.g., "high", "max"). // Resolution priority: CLI --variant flag > saved preference > session history. // -// The saved variant persists across sessions in ~/.local/state/opencode/model.json +// The saved variant persists across sessions in ~/.local/state/deepcode/model.json // so your last-used variant sticks. Cycling (ctrl+t) updates both the active // variant and the persisted file. import path from "path" diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 0bd4e3e5..d0ad1cb5 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -6,14 +6,14 @@ import { Flag } from "@opencode-ai/core/flag/flag" export const ServeCommand = effectCmd({ command: "serve", builder: (yargs) => withNetworkOptions(yargs), - describe: "starts a headless opencode server", + describe: "starts a headless DeepCode server", // Server loads instances per-request via x-opencode-directory header — no // need for an ambient project InstanceContext at startup. instance: false, handler: Effect.fn("Cli.serve")(function* (args) { const { Server } = yield* Effect.promise(() => import("../../server/server")) if (!Flag.OPENCODE_SERVER_PASSWORD) { - console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") + console.log("Warning: DEEPCODE_SERVER_PASSWORD is not set; server is unsecured.") } const opts = yield* resolveNetworkOptions(args) const server = yield* Effect.promise(() => Server.listen(opts)) diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 69a981aa..18c45358 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -31,14 +31,14 @@ function getNetworkIPs() { export const WebCommand = effectCmd({ command: "web", builder: (yargs) => withNetworkOptions(yargs), - describe: "start opencode server and open web interface", + describe: "start the DeepCode server and open the web interface", // Server loads instances per-request via x-opencode-directory header — no // ambient project InstanceContext needed at startup. instance: false, handler: Effect.fn("Cli.web")(function* (args) { const { Server } = yield* Effect.promise(() => import("../../server/server")) if (!Flag.OPENCODE_SERVER_PASSWORD) { - UI.println(UI.Style.TEXT_WARNING_BOLD + "! OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") + UI.println(UI.Style.TEXT_WARNING_BOLD + "! DEEPCODE_SERVER_PASSWORD is not set; server is unsecured.") } const opts = yield* resolveNetworkOptions(args) const server = yield* Effect.promise(() => Server.listen(opts)) diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index c967225a..97c78353 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -1,4 +1,5 @@ import { NamedError } from "@opencode-ai/core/util/error" +import { Product } from "@opencode-ai/core/product" import { errorFormat } from "@/util/error" import { isRecord } from "@/util/record" @@ -47,7 +48,7 @@ export function FormatError(input: unknown): string | undefined { // MCPFailed: { name: string } if (NamedError.hasName(input, "MCPFailed")) { const data = isRecord(input) && isRecord(input.data) ? stringField(input.data, "name") : undefined - return `MCP server "${data}" failed. Note, opencode does not support MCP authentication yet.` + return `MCP server "${data}" failed. Note, DeepCode does not support MCP authentication yet.` } // AccountServiceError, AccountTransportError: TaggedErrorClass @@ -65,7 +66,7 @@ export function FormatError(input: unknown): string | undefined { `Model not found: ${stringField(providerModelNotFound, "providerID")}/${stringField(providerModelNotFound, "modelID")}`, ...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []), `Try: \`deepcode models\` to list available models`, - `Or check your config (opencode.json) provider/model names`, + `Or check your config (deepcode.json) provider/model names`, ].join("\n") } @@ -102,7 +103,7 @@ export function FormatError(input: unknown): string | undefined { return [ `Failed to load remote config${remote ? ` from ${remote}` : ""}: the server returned a login page instead of JSON.`, `Authentication is missing or has expired (the endpoint is likely behind an SSO or identity-aware proxy).`, - ...(url ? [`Run \`opencode auth login ${url}\` to re-authenticate.`] : []), + ...(url ? [`Run \`${Product.cli} auth login ${url}\` to re-authenticate.`] : []), ].join("\n") } diff --git a/packages/opencode/src/cli/network.ts b/packages/opencode/src/cli/network.ts index 96b02ce4..b7e940b8 100644 --- a/packages/opencode/src/cli/network.ts +++ b/packages/opencode/src/cli/network.ts @@ -22,7 +22,7 @@ const options = { "mdns-domain": { type: "string" as const, describe: "custom domain name for mDNS service (default: deepcode.local)", - default: "opencode.local", + default: "deepcode.local", }, cors: { type: "string" as const, diff --git a/packages/opencode/src/command/template/initialize.txt b/packages/opencode/src/command/template/initialize.txt index 90751e3d..3b6fea68 100644 --- a/packages/opencode/src/command/template/initialize.txt +++ b/packages/opencode/src/command/template/initialize.txt @@ -1,6 +1,6 @@ Create or update `AGENTS.md` for this repository. -The goal is a compact instruction file that helps future OpenCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out. +The goal is a compact instruction file that helps future DeepCode sessions avoid mistakes and ramp up quickly. Every line should answer: "Would an agent likely miss this without help?" If not, leave it out. User-provided focus or constraints (honor these): $ARGUMENTS @@ -12,7 +12,7 @@ Read the highest-value sources first: - build, test, lint, formatter, typecheck, and codegen config - CI workflows and pre-commit / task runner config - existing instruction files (`AGENTS.md`, `CLAUDE.md`, `.cursor/rules/`, `.cursorrules`, `.github/copilot-instructions.md`) -- repo-local OpenCode config such as `opencode.json` +- repo-local DeepCode config such as `deepcode.json` If architecture is still unclear after reading config and docs, inspect a small number of representative code files to find the real entrypoints, package boundaries, and execution flow. Prefer reading the files that explain how the system is wired together over random leaf files. @@ -57,7 +57,7 @@ Exclude: - long tutorials or exhaustive file trees - obvious language conventions - speculative claims or anything you could not verify -- content better stored in another file referenced via `opencode.json` `instructions` +- content better stored in another file referenced via `deepcode.json` `instructions` When in doubt, omit. diff --git a/packages/opencode/src/config/tui-migrate.ts b/packages/opencode/src/config/tui-migrate.ts index 6ca25431..1a6e082f 100644 --- a/packages/opencode/src/config/tui-migrate.ts +++ b/packages/opencode/src/config/tui-migrate.ts @@ -5,9 +5,11 @@ import { Option, Schema } from "effect" import { TuiConfig } from "@opencode-ai/tui/config" import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@opencode-ai/core/global" +import { Product } from "@opencode-ai/core/product" import { Filesystem } from "@/util/filesystem" import * as ConfigPaths from "@/config/paths" +// Retained until DeepCode publishes a verified TUI schema endpoint. const TUI_SCHEMA_URL = "https://opencode.ai/tui.json" const decodeTheme = Schema.decodeUnknownOption(Schema.String) @@ -22,13 +24,13 @@ interface MigrateInput { } /** - * Migrates tui-specific keys (theme, keybinds, tui) from opencode.json files - * into dedicated tui.json files. Migration is performed per-directory and - * skips only locations where a tui.json already exists. + * Migrates TUI-specific keys (theme, keybinds, tui) from DeepCode config files + * into dedicated tui.json files. OpenCode files are never discovered or + * modified by this implicit migration path. */ export async function migrateTuiConfig(input: MigrateInput) { - const opencode = await opencodeFiles(input) - for (const file of opencode) { + const files = await deepCodeFiles(input) + for (const file of files) { const source = await Filesystem.readText(file).catch(() => undefined) if (!source) continue const errors: JsoncParseError[] = [] @@ -112,14 +114,15 @@ async function backupAndStripLegacy(file: string, source: string) { .catch(() => false) } -async function opencodeFiles(input: { directories: string[]; cwd: string }) { +async function deepCodeFiles(input: { directories: string[]; cwd: string }) { const files = [ - ...ConfigPaths.fileInDirectory(Global.Path.config, "opencode"), - ...(await Filesystem.findUp(["opencode.json", "opencode.jsonc"], input.cwd, undefined, { rootFirst: true })), + ...ConfigPaths.fileInDirectory(Global.Path.config, Product.config.basename), + ...(await Filesystem.findUp([...Product.config.files], input.cwd, undefined, { rootFirst: true })), ] for (const dir of unique(input.directories)) { - files.push(...ConfigPaths.fileInDirectory(dir, "opencode")) + files.push(...ConfigPaths.fileInDirectory(dir, Product.config.basename)) } + // This internal property reads DEEPCODE_CONFIG through Product.envPrefix. if (Flag.OPENCODE_CONFIG) files.push(Flag.OPENCODE_CONFIG) const existing = await Promise.all( diff --git a/packages/opencode/src/control-plane/dev/README.md b/packages/opencode/src/control-plane/dev/README.md index 74d68a75..0767947e 100644 --- a/packages/opencode/src/control-plane/dev/README.md +++ b/packages/opencode/src/control-plane/dev/README.md @@ -1,4 +1,4 @@ -This is a plugin to simulate a remote environment locally. Add this to `.opencode/opencode.jsonc`: +This is a plugin to simulate a remote environment locally. Add this to `.deepcode/deepcode.jsonc`: ```json "plugin": ["../packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts"], diff --git a/packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts b/packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts index efc9d0c6..0955d6c8 100644 --- a/packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts +++ b/packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts @@ -3,7 +3,7 @@ import { rename, writeFile } from "node:fs/promises" import { randomInt } from "node:crypto" import { setTimeout as sleep } from "node:timers/promises" -const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json" +const DEV_DATA_FILE = "/tmp/deepcode-workspace-dev-data.json" const DEV_DATA_TEMP_FILE = `${DEV_DATA_FILE}.tmp` async function waitForHealth(port: number) { diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 8f746e25..4ae6a289 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -527,9 +527,9 @@ const layer = Layer.effect( .pipe(Effect.orDie) const env = { - OPENCODE_AUTH_CONTENT: JSON.stringify(yield* auth.all()), - OPENCODE_WORKSPACE_ID: config.id, - OPENCODE_EXPERIMENTAL_WORKSPACES: "true", + DEEPCODE_AUTH_CONTENT: JSON.stringify(yield* auth.all()), + DEEPCODE_WORKSPACE_ID: config.id, + DEEPCODE_EXPERIMENTAL_WORKSPACES: "true", OTEL_EXPORTER_OTLP_HEADERS: process.env.OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES, diff --git a/packages/opencode/src/effect/config-service.ts b/packages/opencode/src/effect/config-service.ts index 516c040f..08b4df2c 100644 --- a/packages/opencode/src/effect/config-service.ts +++ b/packages/opencode/src/effect/config-service.ts @@ -30,8 +30,8 @@ export type ServiceClass = Context.ServiceClas * class ServerAuthConfig extends ConfigService.Service()( * "@opencode/ServerAuthConfig", * { - * password: Config.string("OPENCODE_SERVER_PASSWORD").pipe(Config.option), - * username: Config.string("OPENCODE_SERVER_USERNAME").pipe(Config.withDefault("opencode")), + * password: Config.string("DEEPCODE_SERVER_PASSWORD").pipe(Config.option), + * username: Config.string("DEEPCODE_SERVER_USERNAME").pipe(Config.withDefault("deepcode")), * }, * ) {} * diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index 65e02f07..9ff6b9e4 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -1,5 +1,6 @@ import { Config, ConfigProvider, Context, Effect, Layer, Option } from "effect" import { ConfigService } from "@/effect/config-service" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" const bool = (name: string) => Config.boolean(name).pipe(Config.withDefault(false)) const positiveInteger = (name: string) => @@ -7,53 +8,53 @@ const positiveInteger = (name: string) => Config.map((value) => (Number.isInteger(value) && value > 0 ? value : undefined)), Config.orElse(() => Config.succeed(undefined)), ) -const experimental = bool("OPENCODE_EXPERIMENTAL") +const experimental = bool("DEEPCODE_EXPERIMENTAL") const enabledByExperimental = (name: string) => Config.all({ experimental, enabled: Config.boolean(name).pipe(Config.option) }).pipe( Config.map((flags) => Option.getOrElse(flags.enabled, () => flags.experimental)), ) export class Service extends ConfigService.Service()("@opencode/RuntimeFlags", { - autoShare: bool("OPENCODE_AUTO_SHARE"), - pure: bool("OPENCODE_PURE"), - disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"), - disableEmbeddedWebUi: bool("OPENCODE_DISABLE_EMBEDDED_WEB_UI"), - disableExternalSkills: bool("OPENCODE_DISABLE_EXTERNAL_SKILLS"), - disableLspDownload: bool("OPENCODE_DISABLE_LSP_DOWNLOAD"), + autoShare: bool("DEEPCODE_AUTO_SHARE"), + pure: bool("DEEPCODE_PURE"), + disableDefaultPlugins: bool("DEEPCODE_DISABLE_DEFAULT_PLUGINS"), + disableEmbeddedWebUi: bool("DEEPCODE_DISABLE_EMBEDDED_WEB_UI"), + disableExternalSkills: bool("DEEPCODE_DISABLE_EXTERNAL_SKILLS"), + disableLspDownload: bool("DEEPCODE_DISABLE_LSP_DOWNLOAD"), disableClaudeCodePrompt: Config.all({ - broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"), - direct: bool("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT"), + broad: bool("DEEPCODE_DISABLE_CLAUDE_CODE"), + direct: bool("DEEPCODE_DISABLE_CLAUDE_CODE_PROMPT"), }).pipe(Config.map((flags) => flags.broad || flags.direct)), disableClaudeCodeSkills: Config.all({ - broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"), - direct: bool("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"), + broad: bool("DEEPCODE_DISABLE_CLAUDE_CODE"), + direct: bool("DEEPCODE_DISABLE_CLAUDE_CODE_SKILLS"), }).pipe(Config.map((flags) => flags.broad || flags.direct)), enableExa: Config.all({ experimental, - enabled: bool("OPENCODE_ENABLE_EXA"), - legacy: bool("OPENCODE_EXPERIMENTAL_EXA"), + enabled: bool("DEEPCODE_ENABLE_EXA"), + legacy: bool("DEEPCODE_EXPERIMENTAL_EXA"), }).pipe(Config.map((flags) => flags.experimental || flags.enabled || flags.legacy)), enableParallel: Config.all({ - enabled: bool("OPENCODE_ENABLE_PARALLEL"), - legacy: bool("OPENCODE_EXPERIMENTAL_PARALLEL"), + enabled: bool("DEEPCODE_ENABLE_PARALLEL"), + legacy: bool("DEEPCODE_EXPERIMENTAL_PARALLEL"), }).pipe(Config.map((flags) => flags.enabled || flags.legacy)), - enableExperimentalModels: bool("OPENCODE_ENABLE_EXPERIMENTAL_MODELS"), - enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"), - experimentalReferences: enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES"), - experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"), - experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"), - experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"), - experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"), - experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"), - experimentalCodeMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_CODE_MODE"), - experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"), - experimentalWorkspaces: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"), - experimentalIconDiscovery: enabledByExperimental("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY"), - outputTokenMax: positiveInteger("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX"), - bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"), - experimentalNativeLlm: bool("OPENCODE_EXPERIMENTAL_NATIVE_LLM"), - experimentalWebSockets: bool("OPENCODE_EXPERIMENTAL_WEBSOCKETS"), - client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")), + enableExperimentalModels: bool("DEEPCODE_ENABLE_EXPERIMENTAL_MODELS"), + enableQuestionTool: bool("DEEPCODE_ENABLE_QUESTION_TOOL"), + experimentalReferences: enabledByExperimental("DEEPCODE_EXPERIMENTAL_REFERENCES"), + experimentalBackgroundSubagents: enabledByExperimental("DEEPCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"), + experimentalLspTy: bool("DEEPCODE_EXPERIMENTAL_LSP_TY"), + experimentalLspTool: enabledByExperimental("DEEPCODE_EXPERIMENTAL_LSP_TOOL"), + experimentalOxfmt: enabledByExperimental("DEEPCODE_EXPERIMENTAL_OXFMT"), + experimentalPlanMode: enabledByExperimental("DEEPCODE_EXPERIMENTAL_PLAN_MODE"), + experimentalCodeMode: enabledByExperimental("DEEPCODE_EXPERIMENTAL_CODE_MODE"), + experimentalEventSystem: enabledByExperimental("DEEPCODE_EXPERIMENTAL_EVENT_SYSTEM"), + experimentalWorkspaces: enabledByExperimental("DEEPCODE_EXPERIMENTAL_WORKSPACES"), + experimentalIconDiscovery: enabledByExperimental("DEEPCODE_EXPERIMENTAL_ICON_DISCOVERY"), + outputTokenMax: positiveInteger("DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX"), + bashDefaultTimeoutMs: positiveInteger("DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"), + experimentalNativeLlm: bool("DEEPCODE_EXPERIMENTAL_NATIVE_LLM"), + experimentalWebSockets: bool("DEEPCODE_EXPERIMENTAL_WEBSOCKETS"), + client: Config.string("DEEPCODE_CLIENT").pipe(Config.withDefault("cli")), }) {} export type Info = Context.Service.Shape @@ -75,4 +76,3 @@ export const layer = (overrides: Partial = {}) => export const node = LayerNode.make({ service: Service, layer: Service.layer.pipe(Layer.orDie), deps: [] }) export * as RuntimeFlags from "./runtime-flags" -import { LayerNode } from "@opencode-ai/core/effect/layer-node" diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 31b3add1..3c289ee5 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,6 +1,5 @@ import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" -import { Process } from "@/util/process" import { IdeEvent } from "@opencode-ai/schema/ide-event" const SUPPORTED_IDES = [ @@ -30,25 +29,15 @@ export function ide() { } export function alreadyInstalled() { - return process.env["OPENCODE_CALLER"] === "vscode" || process.env["OPENCODE_CALLER"] === "vscode-insiders" + return process.env["DEEPCODE_CALLER"] === "vscode" || process.env["DEEPCODE_CALLER"] === "vscode-insiders" } export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) { - const cmd = SUPPORTED_IDES.find((i) => i.name === ide)?.cmd + const cmd = SUPPORTED_IDES.find((item) => item.name === ide)?.cmd if (!cmd) throw new Error(`Unknown IDE: ${ide}`) - - const p = await Process.run([cmd, "--install-extension", "sst-dev.opencode"], { - nothrow: true, + throw new InstallFailedError({ + stderr: "DeepCode IDE extension installation is unavailable until a verified DeepCode extension channel exists", }) - const stdout = p.stdout.toString() - const stderr = p.stderr.toString() - - if (p.code !== 0) { - throw new InstallFailedError({ stderr }) - } - if (stdout.includes("already installed")) { - throw new AlreadyInstalledError({}) - } } export * as Ide from "." diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 2be68aa5..a3aad7f0 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1243,7 +1243,7 @@ export const JDTLS: Info = { } })(), ) - const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-jdtls-data")) + const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), "deepcode-jdtls-data")) return { process: spawn( java, diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 0d6c8c65..e88a8dfe 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -19,6 +19,7 @@ import { Config } from "@/config/config" import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp" import { NamedError } from "@opencode-ai/core/util/error" import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { Product } from "@opencode-ai/core/product" import { withTimeout } from "@/util/timeout" import { FSUtil } from "@opencode-ai/core/fs-util" import { McpOAuthPendingProvider, McpOAuthProvider, OAUTH_CALLBACK_PATH } from "./oauth-provider" @@ -73,7 +74,7 @@ export class NotFoundError extends Schema.TaggedErrorClass()("MCP type MCPClient = Client function createClient(directory: string) { - const client = new Client({ name: "opencode", version: InstallationVersion }, CLIENT_OPTIONS) + const client = new Client({ name: Product.cli, version: InstallationVersion }, CLIENT_OPTIONS) client.setRequestHandler(ListRootsRequestSchema, () => Promise.resolve({ roots: [{ uri: pathToFileURL(directory).href }] }), ) @@ -313,7 +314,7 @@ const layer = Layer.effect( return events .publish(TuiEvent.ToastShow, { title: "MCP Authentication Required", - message: `Server "${key}" requires authentication. Run: opencode mcp auth ${key}`, + message: `Server "${key}" requires authentication. Run: deepcode mcp auth ${key}`, variant: "warning", duration: 8000, }) @@ -350,7 +351,7 @@ const layer = Layer.effect( cwd, env: { ...process.env, - ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), + ...(cmd === Product.cli ? { BUN_BE_BUN: "1" } : {}), ...mcp.environment, }, }) diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts index 596bfe1d..732d625a 100644 --- a/packages/opencode/src/mcp/oauth-provider.ts +++ b/packages/opencode/src/mcp/oauth-provider.ts @@ -43,8 +43,8 @@ export class McpOAuthProvider implements OAuthClientProvider { get clientMetadata(): OAuthClientMetadata { return { redirect_uris: [this.redirectUrl], - client_name: "OpenCode", - client_uri: "https://opencode.ai", + client_name: "DeepCode", + client_uri: "https://github.com/yuanchenglu/deepcode", grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: this.config.clientSecret ? "client_secret_post" : "none", diff --git a/packages/opencode/src/plugin/digitalocean.ts b/packages/opencode/src/plugin/digitalocean.ts index af241781..21c2ef4a 100644 --- a/packages/opencode/src/plugin/digitalocean.ts +++ b/packages/opencode/src/plugin/digitalocean.ts @@ -171,7 +171,7 @@ async function listRouters( headers: { Authorization: `Bearer ${bearer}`, Accept: "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, }, signal: AbortSignal.timeout(10_000), }).catch(() => undefined) diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 9c744db8..d9c59e73 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -72,7 +72,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { { ...(provider.options?.headers as Record | undefined), Authorization: `Bearer ${auth.refresh}`, - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, "X-GitHub-Api-Version": API_VERSION, }, provider.models, @@ -160,7 +160,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { const headers: Record = { "x-initiator": isAgent ? "agent" : "user", ...(init?.headers as Record), - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, Authorization: `Bearer ${info.refresh}`, "Openai-Intent": "conversation-edits", } @@ -236,7 +236,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { headers: { Accept: "application/json", "Content-Type": "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, }, body: JSON.stringify({ client_id: CLIENT_ID, @@ -266,7 +266,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { headers: { Accept: "application/json", "Content-Type": "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, }, body: JSON.stringify({ client_id: CLIENT_ID, diff --git a/packages/opencode/src/plugin/install.ts b/packages/opencode/src/plugin/install.ts index f07b78bc..fcaf10ba 100644 --- a/packages/opencode/src/plugin/install.ts +++ b/packages/opencode/src/plugin/install.ts @@ -9,6 +9,7 @@ import { import * as ConfigPaths from "@/config/paths" import { Global } from "@opencode-ai/core/global" +import { Product } from "@opencode-ai/core/product" import { Filesystem } from "@/util/filesystem" import { Flock } from "@opencode-ai/core/util/flock" import { isRecord } from "@/util/record" @@ -31,7 +32,7 @@ export type PatchDeps = { readText: (file: string) => Promise write: (file: string, text: string) => Promise exists: (file: string) => Promise - files: (dir: string, name: "opencode" | "tui") => string[] + files: (dir: string, name: string) => string[] } export type PatchInput = { @@ -334,11 +335,11 @@ function patchDir(input: PatchInput) { if (input.global) return input.config ?? Global.Path.config const git = input.vcs === "git" && input.worktree !== "/" const root = git ? input.worktree : input.directory - return path.join(root, ".opencode") + return path.join(root, Product.config.directory) } -function patchName(kind: Kind): "opencode" | "tui" { - if (kind === "server") return "opencode" +function patchName(kind: Kind): string { + if (kind === "server") return Product.config.basename return "tui" } diff --git a/packages/opencode/src/plugin/openai/README.md b/packages/opencode/src/plugin/openai/README.md index 2286b350..ffbcdc58 100644 --- a/packages/opencode/src/plugin/openai/README.md +++ b/packages/opencode/src/plugin/openai/README.md @@ -1,6 +1,6 @@ # OpenAI Responses WebSocket -Enabled by default on `local`, `dev`, and `beta`. On `latest` and `prod`, set `OPENCODE_EXPERIMENTAL_WEBSOCKETS=true`. +Enabled by default on `local`, `dev`, and `beta`. On `latest` and `prod`, set `DEEPCODE_EXPERIMENTAL_WEBSOCKETS=true`. ## Flow diff --git a/packages/opencode/src/plugin/openai/codex.ts b/packages/opencode/src/plugin/openai/codex.ts index f14d1269..50c6fa63 100644 --- a/packages/opencode/src/plugin/openai/codex.ts +++ b/packages/opencode/src/plugin/openai/codex.ts @@ -456,7 +456,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug method: "POST", headers: { "Content-Type": "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, }, body: JSON.stringify({ client_id: CLIENT_ID }), }) @@ -480,7 +480,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug method: "POST", headers: { "Content-Type": "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, }, body: JSON.stringify({ device_auth_id: deviceData.device_auth_id, @@ -540,7 +540,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug "chat.headers": async (input, output) => { if (input.model.providerID !== "openai") return output.headers.originator = "opencode" - output.headers["User-Agent"] = `opencode/${InstallationVersion} (${os.platform()} ${os.release()}; ${os.arch()})` + output.headers["User-Agent"] = `deepcode/${InstallationVersion} (${os.platform()} ${os.release()}; ${os.arch()})` output.headers["session-id"] = input.sessionID // Temporary fetch-layer hack: title generation currently shares the conversation // session ID, so the OpenAI plugin marks it for HTTP fallback until transport diff --git a/packages/opencode/src/plugin/snowflake-cortex.ts b/packages/opencode/src/plugin/snowflake-cortex.ts index 09f107ed..ebc43042 100644 --- a/packages/opencode/src/plugin/snowflake-cortex.ts +++ b/packages/opencode/src/plugin/snowflake-cortex.ts @@ -80,7 +80,7 @@ function authHeaders() { return { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", - "User-Agent": `opencode/${InstallationVersion}`, + "User-Agent": `deepcode/${InstallationVersion}`, } } @@ -380,7 +380,7 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise` to re-authenticate." + return "Unauthorized: request was blocked by a gateway or proxy. Your authentication token may be missing or expired — try running `deepcode auth login ` to re-authenticate." } if (e.statusCode === 403) { return "Forbidden: request was blocked by a gateway or proxy. You may not have permission to access this resource — check your account and provider settings." diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index c979fc44..4b7e797e 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -366,7 +366,7 @@ function custom(dep: CustomDep): Record { } // Region resolution precedence (highest to lowest): - // 1. options.region from opencode.json provider config + // 1. options.region from deepcode.json provider config // 2. defaultRegion from AWS_REGION environment variable // 3. Default "us-east-1" (baked into defaultRegion) const region = options?.region ?? defaultRegion @@ -449,9 +449,9 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", - "X-Source": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", + "X-Source": "deepcode", }, }, }), @@ -460,8 +460,8 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }, }, }), @@ -470,9 +470,9 @@ function custom(dep: CustomDep): Record { autoload: provider.source === "config", options: { headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", - "X-BILLING-INVOKE-ORIGIN": "OpenCode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", + "X-BILLING-INVOKE-ORIGIN": "DeepCode", }, }, }), @@ -481,7 +481,7 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "http-referer": "https://opencode.ai/", + "http-referer": "https://github.com/yuanchenglu/deepcode", "x-title": "opencode", }, }, @@ -587,8 +587,8 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }, }, }), @@ -609,7 +609,7 @@ function custom(dep: CustomDep): Record { const directory = yield* InstanceState.directory const aiGatewayHeaders = { - "User-Agent": `opencode/${InstallationVersion} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`, "anthropic-beta": "context-1m-2025-08-07", ...providerConfig?.options?.aiGatewayHeaders, } @@ -742,7 +742,7 @@ function custom(dep: CustomDep): Record { options: { apiKey, headers: { - "User-Agent": `opencode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`, }, }, async getModel(sdk: any, modelID: string) { @@ -787,7 +787,7 @@ function custom(dep: CustomDep): Record { if (!apiToken) { throw new Error( "CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " + - "Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.", + "Set it via environment variable or run `deepcode auth cloudflare-ai-gateway`.", ) } @@ -810,7 +810,7 @@ function custom(dep: CustomDep): Record { skipCache: input.options?.skipCache, collectLog: input.options?.collectLog, headers: { - "User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, + "User-Agent": `deepcode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`, }, } @@ -836,7 +836,7 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "X-Cerebras-3rd-Party-Integration": "opencode", + "X-Cerebras-3rd-Party-Integration": "deepcode", }, }, }), @@ -845,8 +845,8 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "HTTP-Referer": "https://opencode.ai/", - "X-Title": "opencode", + "HTTP-Referer": "https://github.com/yuanchenglu/deepcode", + "X-Title": "deepcode", }, }, }), @@ -873,7 +873,7 @@ function custom(dep: CustomDep): Record { autoload: false, async getModel() { throw new Error( - `Snowflake Cortex: missing credentials (${missing}). Provide a bearer token (OAuth, JWT, or PAT) via env var, opencode auth, or provider options.`, + `Snowflake Cortex: missing credentials (${missing}). Provide a bearer token (OAuth, JWT, or PAT) via an environment variable, deepcode auth, or provider options.`, ) }, } diff --git a/packages/opencode/src/server/mdns.ts b/packages/opencode/src/server/mdns.ts index 0dd673c7..5a3a3d8f 100644 --- a/packages/opencode/src/server/mdns.ts +++ b/packages/opencode/src/server/mdns.ts @@ -8,8 +8,8 @@ export function publish(port: number, domain?: string) { if (bonjour) unpublish() try { - const host = domain ?? "opencode.local" - const name = `opencode-${port}` + const host = domain ?? "deepcode.local" + const name = `deepcode-${port}` bonjour = new Bonjour() const service = bonjour.publish({ name, diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index 2a7266c5..da2e80a7 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -75,7 +75,7 @@ const QueryParameterSchemas: Record = { const LegacyComponentDescriptions: Record = { LogLevel: "Log level", - ServerConfig: "Server configuration for opencode serve and web commands", + ServerConfig: "Server configuration for DeepCode serve and web commands", LayoutConfig: "@deprecated Always uses stretch layout.", } diff --git a/packages/opencode/src/server/shared/ui.ts b/packages/opencode/src/server/shared/ui.ts index c2fd3b86..2fd32b1e 100644 --- a/packages/opencode/src/server/shared/ui.ts +++ b/packages/opencode/src/server/shared/ui.ts @@ -1,13 +1,10 @@ import { FSUtil } from "@opencode-ai/core/fs-util" -import { Effect, Stream } from "effect" -import { HttpBody, HttpClient, HttpClientRequest, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" +import { Effect } from "effect" +import { HttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" import { createHash } from "node:crypto" -import { ProxyUtil } from "../proxy-util" let embeddedUIPromise: Promise | null> | undefined -export const UI_UPSTREAM = new URL("https://app.opencode.ai") - export const csp = (hash = "") => `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src * data:` export const DEFAULT_CSP = csp() @@ -21,26 +18,6 @@ export function cspForHtml(body: string) { return csp(match ? createHash("sha256").update(match[2]).digest("base64") : "") } -function requestBody(request: HttpServerRequest.HttpServerRequest) { - if (request.method === "GET" || request.method === "HEAD") return HttpBody.empty - const len = request.headers["content-length"] - return HttpBody.stream(request.stream, request.headers["content-type"], len === undefined ? undefined : Number(len)) -} - -function proxyResponseHeaders(headers: Record) { - const result = new Headers(headers) - // FetchHttpClient exposes decoded response bodies, so forwarding upstream - // transfer metadata makes browsers decode already-decoded assets again. - result.delete("content-encoding") - result.delete("content-length") - result.delete("transfer-encoding") - return result -} - -export function upstreamURL(path: string) { - return new URL(path, UI_UPSTREAM).toString() -} - export function embeddedUI(disableEmbeddedWebUi: boolean) { if (disableEmbeddedWebUi) return Promise.resolve(null) return (embeddedUIPromise ??= @@ -85,24 +62,8 @@ export function serveUIEffect( if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI) - const response = yield* services.client.execute( - HttpClientRequest.make(request.method)(upstreamURL(path), { - headers: ProxyUtil.headers(request.headers, { host: UI_UPSTREAM.host }), - body: requestBody(request), - }), - ) - const headers = proxyResponseHeaders(response.headers) - - if (response.headers["content-type"]?.includes("text/html")) { - const body = yield* response.text - headers.set("Content-Security-Policy", cspForHtml(body)) - return HttpServerResponse.text(body, { status: response.status, headers }) - } - - headers.set("Content-Security-Policy", csp()) - return HttpServerResponse.stream(response.stream.pipe(Stream.catchCause(() => Stream.empty)), { - status: response.status, - headers, - }) + // Fail closed: DeepCode must never proxy its UI to an OpenCode-controlled + // origin when the embedded bundle is unavailable or explicitly disabled. + return notFound() }) } diff --git a/packages/opencode/src/session/llm/AGENTS.md b/packages/opencode/src/session/llm/AGENTS.md index cfb6a89c..4ce845c2 100644 --- a/packages/opencode/src/session/llm/AGENTS.md +++ b/packages/opencode/src/session/llm/AGENTS.md @@ -1,6 +1,6 @@ # Session LLM Runtime Boundaries -`../llm.ts` is the opencode session LLM service. It owns opencode concerns: auth, config, model/provider resolution, plugins, permissions, telemetry headers, and runtime selection. It is the only file in this area that should know about the full session request shape. +`../llm.ts` is the DeepCode session LLM service. It owns DeepCode concerns: auth, config, model/provider resolution, plugins, permissions, telemetry headers, and runtime selection. It is the only file in this area that should know about the full session request shape. This folder contains adapters behind that service boundary: @@ -85,6 +85,6 @@ Both runtimes converge on the same `LLMEvent` stream consumed by the session pro Safety boundary: - AI SDK remains the default. -- `OPENCODE_EXPERIMENTAL_NATIVE_LLM=true` or the umbrella `OPENCODE_EXPERIMENTAL=true` opts in. Native is not a global replacement. +- `DEEPCODE_EXPERIMENTAL_NATIVE_LLM=true` or the umbrella `DEEPCODE_EXPERIMENTAL=true` opts in. Native is not a global replacement. - Native execution currently supports OpenAI, opencode-managed OpenAI-compatible, and Anthropic API-key paths backed by `@ai-sdk/openai`, `@ai-sdk/openai-compatible`, or `@ai-sdk/anthropic` catalog entries. - Unsupported providers, OpenAI OAuth, and missing API-key cases fall back to AI SDK. diff --git a/packages/opencode/src/session/llm/request.ts b/packages/opencode/src/session/llm/request.ts index 4f934111..f87f928c 100644 --- a/packages/opencode/src/session/llm/request.ts +++ b/packages/opencode/src/session/llm/request.ts @@ -15,7 +15,7 @@ import { jsonSchema, tool as aiTool, type ModelMessage, type Tool } from "ai" import type { Plugin } from "@/plugin" import { mergeDeep } from "remeda" -const USER_AGENT = `opencode/${InstallationVersion}` +const USER_AGENT = `deepcode/${InstallationVersion}` type PrepareInput = { readonly user: SessionV1.User diff --git a/packages/opencode/src/session/prompt/anthropic.txt b/packages/opencode/src/session/prompt/anthropic.txt index 21d9c0e9..a5644246 100644 --- a/packages/opencode/src/session/prompt/anthropic.txt +++ b/packages/opencode/src/session/prompt/anthropic.txt @@ -1,4 +1,4 @@ -You are OpenCode, the best coding agent on the planet. +You are DeepCode, the best coding agent on the planet. You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. @@ -7,9 +7,9 @@ IMPORTANT: You must NEVER generate or guess URLs for the user unless you are con If the user asks for help or wants to give feedback inform them of the following: - ctrl+p to list available actions - To give feedback, users should report the issue at - https://github.com/anomalyco/opencode + https://github.com/yuanchenglu/deepcode -When the user directly asks about OpenCode (eg. "can OpenCode do...", "does OpenCode have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://opencode.ai/docs +When the user directly asks about DeepCode (eg. "can DeepCode do...", "does DeepCode have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific DeepCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from DeepCode docs. The list of available docs is available at https://github.com/yuanchenglu/deepcode/tree/develop/docs # Tone and style - Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked. @@ -18,7 +18,7 @@ When the user directly asks about OpenCode (eg. "can OpenCode do...", "does Open - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files. # Professional objectivity -Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if OpenCode honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. +Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if DeepCode honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. # Task Management You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress. diff --git a/packages/opencode/src/session/prompt/default.txt b/packages/opencode/src/session/prompt/default.txt index c8d90466..c6beb2a4 100644 --- a/packages/opencode/src/session/prompt/default.txt +++ b/packages/opencode/src/session/prompt/default.txt @@ -1,12 +1,12 @@ -You are opencode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. +You are deepcode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files. If the user asks for help or wants to give feedback inform them of the following: -- /help: Get help with using opencode -- To give feedback, users should report the issue at https://github.com/anomalyco/opencode/issues +- /help: Get help with using deepcode +- To give feedback, users should report the issue at https://github.com/yuanchenglu/deepcode/issues -When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai +When the user directly asks about deepcode (eg 'can deepcode do...', 'does deepcode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from deepcode docs at https://deepcode.ai # Tone and style You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index de8c3dc4..80bae3b2 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -330,7 +330,7 @@ export const Event = { export function plan(input: { slug: string; time: { created: number } }, instance: InstanceContext) { const base = instance.project.vcs - ? path.join(instance.worktree, ".opencode", "plans") + ? path.join(instance.worktree, ".deepcode", "plans") : path.join(Global.Path.data, "plans") return path.join(base, [input.time.created, input.slug].join("-") + ".md") } diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 60112e10..97f78a9b 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -20,7 +20,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { EventV2 } from "@opencode-ai/core/event" -const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1" +const disabled = process.env["DEEPCODE_DISABLE_SHARE"] === "true" || process.env["DEEPCODE_DISABLE_SHARE"] === "1" export type Api = { create: string diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index b56a6761..d4c85c59 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -77,7 +77,7 @@ const layer: Layer.Layer Effect.gen(function* () { const root = path.join(cache, skill.name) - const versionFile = path.join(root, ".opencode-version") + const versionFile = path.join(root, ".deepcode-version") const version = skill.version const current = version === undefined @@ -102,7 +102,7 @@ const layer: Layer.Layer", - content: CUSTOMIZE_OPENCODE_SKILL_BODY, + content: CUSTOMIZE_DEEPCODE_SKILL_BODY, } yield* loadSkills(s, yield* InstanceState.get(discovered), events) return s diff --git a/packages/opencode/src/temporary.ts b/packages/opencode/src/temporary.ts index 95461f30..e098b03c 100644 --- a/packages/opencode/src/temporary.ts +++ b/packages/opencode/src/temporary.ts @@ -1,10 +1,12 @@ import yargs from "yargs" import { TuiThreadCommand } from "./cli/cmd/tui" import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { Product } from "@opencode-ai/core/product" import { hideBin } from "yargs/helpers" + const cli = yargs(hideBin(process.argv)) .parserConfiguration({ "populate--": true }) - .scriptName("opencode") + .scriptName(Product.cli) .wrap(100) .help("help", "show help") .alias("help", "h") @@ -24,8 +26,10 @@ const cli = yargs(hideBin(process.argv)) type: "boolean", }) .middleware((opts) => { - if (opts.printLogs) process.env.OPENCODE_PRINT_LOGS = "1" - if (opts.logLevel) process.env.OPENCODE_LOG_LEVEL = opts.logLevel + if (opts.printLogs) process.env.DEEPCODE_PRINT_LOGS = "1" + if (opts.logLevel) process.env.DEEPCODE_LOG_LEVEL = opts.logLevel }) .command(TuiThreadCommand) .parse() + +void cli diff --git a/packages/opencode/src/tool/code-mode.ts b/packages/opencode/src/tool/code-mode.ts index 0f566a49..138b7a7c 100644 --- a/packages/opencode/src/tool/code-mode.ts +++ b/packages/opencode/src/tool/code-mode.ts @@ -20,7 +20,7 @@ export const Parameters = Schema.Struct({ code: Schema.String.annotate({ description: [ "JavaScript source to execute.", - "Inside CodeMode, `tools` contains only the MCP/CodeMode tools listed in this execute tool's description; top-level opencode tools like bash, read, or lsp are not available unless listed there.", + "Inside CodeMode, `tools` contains only the MCP/CodeMode tools listed in this execute tool's description; top-level DeepCode tools like bash, read, or lsp are not available unless listed there.", "Call available tools using the exact signatures shown in this execute tool's description, compose the results, and `return` the final value.", ].join(" "), }), diff --git a/packages/opencode/src/tool/lsp.txt b/packages/opencode/src/tool/lsp.txt index 85db65c1..66dc3901 100644 --- a/packages/opencode/src/tool/lsp.txt +++ b/packages/opencode/src/tool/lsp.txt @@ -19,6 +19,6 @@ All operations require: workspaceSymbol also accepts: - query: A query string to filter symbols by. Empty string requests all symbols. -For workspaceSymbol, filePath is not sent in the LSP workspace/symbol request. It is used by opencode to select and start the matching LSP server. +For workspaceSymbol, filePath is not sent in the LSP workspace/symbol request. It is used by DeepCode to select and start the matching LSP server. Note: LSP servers must be configured for the file type. If no server is available, an error will be returned. diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index b0a866c9..2c7865af 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -97,7 +97,7 @@ export const TaskTool = Tool.define( const runInBackground = params.background === true if (runInBackground && !flags.experimentalBackgroundSubagents) { return yield* Effect.fail( - new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"), + new Error("Background subagents require DEEPCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"), ) } diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index 3a48c90a..f011ba49 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -39,7 +39,7 @@ export interface Interface { */ readonly output: (text: string, options?: Options, agent?: Agent.Info) => Effect.Effect /** - * Resolved truncation limits: values from `tool_output` in opencode config, or MAX_LINES / MAX_BYTES if unset. + * Resolved truncation limits: values from `tool_output` in DeepCode config, or MAX_LINES / MAX_BYTES if unset. */ readonly limits: () => Effect.Effect<{ maxLines: number; maxBytes: number }> } diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index e6150345..adf43de4 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -85,7 +85,7 @@ export const WebFetchTool = Tool.define( () => httpOk.execute( HttpClientRequest.get(params.url).pipe( - HttpClientRequest.setHeaders({ ...headers, "User-Agent": "opencode" }), + HttpClientRequest.setHeaders({ ...headers, "User-Agent": "deepcode" }), ), ), ), diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts index d08ae1d1..b4051419 100644 --- a/packages/opencode/src/tool/websearch.ts +++ b/packages/opencode/src/tool/websearch.ts @@ -28,7 +28,7 @@ const WebSearchProviderSchema = Schema.Literals(["exa", "parallel"]) export type WebSearchProvider = Schema.Schema.Type export function selectWebSearchProvider(sessionID: string, flags = { exa: false, parallel: false }): WebSearchProvider { - const override = process.env.OPENCODE_WEBSEARCH_PROVIDER + const override = process.env.DEEPCODE_WEBSEARCH_PROVIDER if (override === "exa" || override === "parallel") return override if (flags.parallel) return "parallel" if (flags.exa) return "exa" @@ -52,7 +52,7 @@ export function webSearchModelName(extra: Tool.Context["extra"]) { } function parallelAuthHeaders() { - const headers = { "User-Agent": `opencode/${InstallationVersion}` } + const headers = { "User-Agent": `deepcode/${InstallationVersion}` } if (!process.env.PARALLEL_API_KEY) return headers return { ...headers, Authorization: `Bearer ${process.env.PARALLEL_API_KEY}` } } diff --git a/packages/opencode/src/util/process.ts b/packages/opencode/src/util/process.ts index 173210f2..a701bc77 100644 --- a/packages/opencode/src/util/process.ts +++ b/packages/opencode/src/util/process.ts @@ -145,7 +145,7 @@ export async function run(cmd: string[], opts: RunOptions = {}): Promise } // Duplicated in `packages/sdk/js/src/process.ts` because the SDK cannot import -// `opencode` without creating a cycle. Keep both copies in sync. +// `deepcode` without creating a cycle. Keep both copies in sync. export async function stop(proc: ChildProcess) { if (proc.exitCode !== null || proc.signalCode !== null) return diff --git a/packages/opencode/src/util/repository.ts b/packages/opencode/src/util/repository.ts index dfeee432..a17ad870 100644 --- a/packages/opencode/src/util/repository.ts +++ b/packages/opencode/src/util/repository.ts @@ -97,7 +97,7 @@ function withSlash(input: string) { } function githubRemote(pathname: string) { - const base = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL + const base = process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL if (!base) return `https://github.com/${pathname}.git` return new URL(`${pathname}.git`, withSlash(base)).href } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index a0c43ed2..417b5889 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -180,7 +180,7 @@ const layer: Layer.Layer< const ctx = yield* InstanceState.context for (const attempt of Array.from({ length: MAX_NAME_ATTEMPTS }, (_, i) => i)) { const name = input.name ? (attempt === 0 ? input.name : `${input.name}-${Slug.create()}`) : Slug.create() - const branch = input.detached ? undefined : `opencode/${name}` + const branch = input.detached ? undefined : `deepcode/${name}` const directory = pathSvc.join(input.root, name) if (yield* fs.exists(directory).pipe(Effect.orDie)) continue diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index efeaf408..268848c8 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -69,14 +69,14 @@ it.instance("build agent has correct default properties", () => }), ) -it.instance("plan agent denies edits except .opencode/plans/*", () => +it.instance("plan agent denies edits except .deepcode/plans/*", () => Effect.gen(function* () { const plan = yield* load((svc) => svc.get("plan")) expect(plan).toBeDefined() // Wildcard is denied expect(evalPerm(plan, "edit")).toBe("deny") // But specific path is allowed - expect(Permission.evaluate("edit", ".opencode/plans/foo.md", plan!.permission).action).toBe("allow") + expect(Permission.evaluate("edit", ".deepcode/plans/foo.md", plan!.permission).action).toBe("allow") }), ) @@ -612,11 +612,11 @@ description: Permission skill. ), ) - const home = process.env.OPENCODE_TEST_HOME - process.env.OPENCODE_TEST_HOME = test.directory + const home = process.env.DEEPCODE_TEST_HOME + process.env.DEEPCODE_TEST_HOME = test.directory yield* Effect.addFinalizer(() => Effect.sync(() => { - process.env.OPENCODE_TEST_HOME = home + process.env.DEEPCODE_TEST_HOME = home }), ) diff --git a/packages/opencode/test/cli/account.test.ts b/packages/opencode/test/cli/account.test.ts index d491b77e..36227bb8 100644 --- a/packages/opencode/test/cli/account.test.ts +++ b/packages/opencode/test/cli/account.test.ts @@ -5,7 +5,7 @@ import { defaultConsoleUrl, formatAccountLabel, formatOrgLine } from "../../src/ describe("console account display", () => { test("uses console.opencode.ai as the default login URL", () => { - expect(defaultConsoleUrl).toBe("https://console.opencode.ai") + expect(defaultConsoleUrl).toBeUndefined() }) test("includes the account url in account labels", () => { diff --git a/packages/opencode/test/cli/acp/config-options.test.ts b/packages/opencode/test/cli/acp/config-options.test.ts index 0c712f0f..e5fe59d0 100644 --- a/packages/opencode/test/cli/acp/config-options.test.ts +++ b/packages/opencode/test/cli/acp/config-options.test.ts @@ -19,7 +19,7 @@ describe("opencode acp config option subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const model = expectSelectOption((yield* newSession(acp, home)).configOptions, "model") @@ -37,7 +37,7 @@ describe("opencode acp config option subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) @@ -64,7 +64,7 @@ describe("opencode acp config option subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const effort = expectSelectOption((yield* newSession(acp, home)).configOptions, "effort") @@ -82,7 +82,7 @@ describe("opencode acp config option subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) diff --git a/packages/opencode/test/cli/acp/initialize-auth.test.ts b/packages/opencode/test/cli/acp/initialize-auth.test.ts index 709c27f3..7fdd3fdf 100644 --- a/packages/opencode/test/cli/acp/initialize-auth.test.ts +++ b/packages/opencode/test/cli/acp/initialize-auth.test.ts @@ -33,15 +33,15 @@ describe("opencode acp initialize/auth subprocess", () => { const acp = yield* createAcpClient({ opencode }) const initialized = yield* initialize(acp) - expect(initialized.authMethods?.[0]?.id).toBe("opencode-login") + expect(initialized.authMethods?.[0]?.id).toBe("deepcode-login") expect(initialized.authMethods?.[0]?._meta?.["terminal-auth"]).toBeDefined() - expect(yield* acp.request("authenticate", { methodId: "opencode-login" })).toMatchObject({ + expect(yield* acp.request("authenticate", { methodId: "deepcode-login" })).toMatchObject({ result: {}, }) const rejected = yield* acp.request("authenticate", { methodId: "missing-auth-method" }) expectErrorCode(rejected.error, -32602) - expect(JSON.stringify(rejected.error)).not.toContain(process.env.OPENCODE_AUTH_CONTENT ?? "not-present") + expect(JSON.stringify(rejected.error)).not.toContain(process.env.DEEPCODE_AUTH_CONTENT ?? "not-present") }), 60_000, ) @@ -53,7 +53,7 @@ describe("opencode acp initialize/auth subprocess", () => { const acp = yield* createAcpClient({ opencode }) const initialized = yield* acp.request("initialize", { protocolVersion: 1 }) - expect(initialized.result?.authMethods?.[0]?.id).toBe("opencode-login") + expect(initialized.result?.authMethods?.[0]?.id).toBe("deepcode-login") expect(initialized.result?.authMethods?.[0]?._meta?.["terminal-auth"]).toBeUndefined() }), 60_000, diff --git a/packages/opencode/test/cli/acp/lifecycle.test.ts b/packages/opencode/test/cli/acp/lifecycle.test.ts index 9f2558ea..466ba770 100644 --- a/packages/opencode/test/cli/acp/lifecycle.test.ts +++ b/packages/opencode/test/cli/acp/lifecycle.test.ts @@ -30,7 +30,7 @@ describe("opencode acp lifecycle subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) const initialized = yield* initialize(acp) expect(initialized.agentCapabilities?.sessionCapabilities?.close).toEqual({}) @@ -47,7 +47,7 @@ describe("opencode acp lifecycle subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) const initialized = yield* initialize(acp) expect(initialized.agentCapabilities?.loadSession).toBe(true) @@ -71,7 +71,7 @@ describe("opencode acp lifecycle subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) @@ -99,7 +99,7 @@ describe("opencode acp lifecycle subprocess", () => { Effect.gen(function* () { const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) diff --git a/packages/opencode/test/cli/acp/prompt-content.test.ts b/packages/opencode/test/cli/acp/prompt-content.test.ts index 6b6f6dde..dcaf9c80 100644 --- a/packages/opencode/test/cli/acp/prompt-content.test.ts +++ b/packages/opencode/test/cli/acp/prompt-content.test.ts @@ -18,7 +18,7 @@ describe("opencode acp prompt content subprocess", () => { yield* Effect.promise(() => writeFile(path.join(home, "README.md"), "# ACP content smoke\n")) const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(promptContentConfig(llm.url)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(promptContentConfig(llm.url)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) diff --git a/packages/opencode/test/cli/acp/skills.test.ts b/packages/opencode/test/cli/acp/skills.test.ts index b423493c..4c05989d 100644 --- a/packages/opencode/test/cli/acp/skills.test.ts +++ b/packages/opencode/test/cli/acp/skills.test.ts @@ -16,7 +16,7 @@ describe("opencode acp skills subprocess", () => { yield* Effect.promise(() => Bun.write(path.join(skills, "verifier-skill", "SKILL.md"), verifierSkill)) const acp = yield* createAcpClient( { opencode }, - { OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url, skills)) }, + { DEEPCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url, skills)) }, ) yield* initialize(acp) const session = yield* newSession(acp, home) diff --git a/packages/opencode/test/cli/error.test.ts b/packages/opencode/test/cli/error.test.ts index b29ca2b3..7d39faa1 100644 --- a/packages/opencode/test/cli/error.test.ts +++ b/packages/opencode/test/cli/error.test.ts @@ -8,14 +8,14 @@ describe("cli.error", () => { const cases = [ { tag: "ConfigJsonError", - data: { path: "/tmp/opencode.jsonc", message: "Unexpected token" }, - expected: "Config file at /tmp/opencode.jsonc is not valid JSON(C): Unexpected token", + data: { path: "/tmp/deepcode.jsonc", message: "Unexpected token" }, + expected: "Config file at /tmp/deepcode.jsonc is not valid JSON(C): Unexpected token", }, { tag: "ConfigDirectoryTypoError", - data: { path: "/tmp/opencode.jsonc", dir: ".opencode", suggestion: "opencode" }, + data: { path: "/tmp/deepcode.jsonc", dir: ".deepcode", suggestion: "opencode" }, expected: - 'Directory ".opencode" in /tmp/opencode.jsonc is not valid. Rename the directory to "opencode" or remove it. This is a common typo.', + 'Directory ".deepcode" in /tmp/deepcode.jsonc is not valid. Rename the directory to "opencode" or remove it. This is a common typo.', }, { tag: "ConfigFrontmatterError", @@ -25,11 +25,11 @@ describe("cli.error", () => { { tag: "ConfigInvalidError", data: { - path: "/tmp/opencode.jsonc", + path: "/tmp/deepcode.jsonc", message: "schema mismatch", issues: [{ message: "Expected string", path: ["provider", "id"] }], }, - expected: "Configuration is invalid at /tmp/opencode.jsonc: schema mismatch\n↳ Expected string provider.id", + expected: "Configuration is invalid at /tmp/deepcode.jsonc: schema mismatch\n↳ Expected string provider.id", }, ] @@ -41,7 +41,7 @@ describe("cli.error", () => { test("preserves multiline JSONC diagnostics for tagged config errors", () => { const data = { - path: "/tmp/opencode.jsonc", + path: "/tmp/deepcode.jsonc", message: '\n--- JSONC Input ---\n{\n "model": \n}\n--- Errors ---\nValueExpected at line 3, column 1\n Line 3: }\n ^\n--- End ---', } @@ -54,12 +54,12 @@ describe("cli.error", () => { test("formats account transport errors clearly", () => { const error = new AccountTransportError({ method: "POST", - url: "https://console.opencode.ai/auth/device/code", + url: "https://console.deepcode.ai/auth/device/code", }) const formatted = FormatError(error) - expect(formatted).toContain("Could not reach POST https://console.opencode.ai/auth/device/code.") + expect(formatted).toContain("Could not reach POST https://console.deepcode.ai/auth/device/code.") expect(formatted).toContain("This failed before the server returned an HTTP response.") expect(formatted).toContain("Check your network, proxy, or VPN configuration and try again.") }) @@ -74,7 +74,7 @@ describe("cli.error", () => { "Model not found: anthropic/claude-sonet-4", "Did you mean: claude-sonnet-4", "Try: `opencode models` to list available models", - "Or check your config (opencode.json) provider/model names", + "Or check your config (deepcode.json) provider/model names", ].join("\n") expect(FormatError({ name: "ProviderModelNotFoundError", data })).toBe(expected) diff --git a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap index e9d3ad23..e1e16c09 100644 --- a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +++ b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap @@ -15,8 +15,8 @@ Options: --hostname hostname to listen on [string] [default: "127.0.0.1"] --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] - --mdns-domain custom domain name for mDNS service (default: opencode.local) - [string] [default: "opencode.local"] + --mdns-domain custom domain name for mDNS service (default: deepcode.local) + [string] [default: "deepcode.local"] --cors additional domains to allow for CORS [array] [default: []] --cwd working directory [string] [default: ""]" `; @@ -44,7 +44,7 @@ Options: exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode attach --help 1`] = ` "opencode attach -attach to a running opencode server +attach to a running DeepCode server Positionals: url http://localhost:4096 [string] [required] @@ -59,8 +59,8 @@ Options: -c, --continue continue the last session [boolean] -s, --session session id to continue [string] --fork fork the session when continuing (use with --continue or --session) [boolean] - -p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD) [string] - -u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode') + -p, --password basic auth password (defaults to DEEPCODE_SERVER_PASSWORD) [string] + -u, --username basic auth username (defaults to DEEPCODE_SERVER_USERNAME or 'deepcode') [string] --mini start the minimal interactive interface [boolean] [default: false] --no-replay disable mini session history replay on resume and after resize [boolean] @@ -92,9 +92,9 @@ Options: [string] [choices: "default", "json"] [default: "default"] -f, --file file(s) to attach to message [array] --title title for the session (uses truncated prompt if no value provided) [string] - --attach attach to a running opencode server (e.g., http://localhost:4096) [string] - -p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD) [string] - -u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode') + --attach attach to a running DeepCode server (e.g., http://localhost:4096) [string] + -p, --password basic auth password (defaults to DEEPCODE_SERVER_PASSWORD) [string] + -u, --username basic auth username (defaults to DEEPCODE_SERVER_USERNAME or 'deepcode') [string] --dir directory to run in, path on remote server if attaching [string] --port port for the local server (defaults to random port if no value provided) @@ -220,8 +220,8 @@ Options: --hostname hostname to listen on [string] [default: "127.0.0.1"] --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] - --mdns-domain custom domain name for mDNS service (default: opencode.local) - [string] [default: "opencode.local"] + --mdns-domain custom domain name for mDNS service (default: deepcode.local) + [string] [default: "deepcode.local"] --cors additional domains to allow for CORS [array] [default: []]" `; @@ -240,8 +240,8 @@ Options: --hostname hostname to listen on [string] [default: "127.0.0.1"] --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] - --mdns-domain custom domain name for mDNS service (default: opencode.local) - [string] [default: "opencode.local"] + --mdns-domain custom domain name for mDNS service (default: deepcode.local) + [string] [default: "deepcode.local"] --cors additional domains to allow for CORS [array] [default: []]" `; diff --git a/packages/opencode/test/cli/mcp-add.test.ts b/packages/opencode/test/cli/mcp-add.test.ts index 9a493848..e872a469 100644 --- a/packages/opencode/test/cli/mcp-add.test.ts +++ b/packages/opencode/test/cli/mcp-add.test.ts @@ -22,7 +22,7 @@ describe("opencode mcp add (non-interactive subprocess)", () => { opencode.expectExit(result, 0) const config = yield* Effect.promise(() => - Bun.file(path.join(home, ".config", "opencode", "opencode.json")).json(), + Bun.file(path.join(home, ".config", "deepcode", "deepcode.json")).json(), ) expect(config.mcp.github).toEqual({ type: "remote", @@ -58,7 +58,7 @@ describe("opencode mcp add (non-interactive subprocess)", () => { opencode.expectExit(result, 0) const config = yield* Effect.promise(() => - Bun.file(path.join(home, ".config", "opencode", "opencode.json")).json(), + Bun.file(path.join(home, ".config", "deepcode", "deepcode.json")).json(), ) expect(config.mcp.local).toEqual({ type: "local", diff --git a/packages/opencode/test/cli/run/run-process.test.ts b/packages/opencode/test/cli/run/run-process.test.ts index bd5847e2..42a22530 100644 --- a/packages/opencode/test/cli/run/run-process.test.ts +++ b/packages/opencode/test/cli/run/run-process.test.ts @@ -2,7 +2,7 @@ // These exercise the real CLI binary against a TestLLMServer running in the // same process. See `test/lib/cli-process.ts` for the harness — each test uses // `opencode.run(message, opts?)` to spawn `bun src/index.ts run ...` with -// `OPENCODE_CONFIG_CONTENT` providing the test provider config inline. +// `DEEPCODE_CONFIG_CONTENT` providing the test provider config inline. import { describe, expect } from "bun:test" import { Effect } from "effect" import { reply } from "../../lib/llm-server" diff --git a/packages/opencode/test/cli/smokes/read-only.test.ts b/packages/opencode/test/cli/smokes/read-only.test.ts index bf80a278..fdf8bf7c 100644 --- a/packages/opencode/test/cli/smokes/read-only.test.ts +++ b/packages/opencode/test/cli/smokes/read-only.test.ts @@ -20,7 +20,7 @@ import { cliIt } from "../../lib/cli-process" describe("opencode read-only commands (smoke)", () => { // `mcp list` reads MCP server config and pings each one. With the empty - // OPENCODE_CONFIG_CONTENT={} we provide, no servers should be configured + // DEEPCODE_CONFIG_CONTENT={} we provide, no servers should be configured // and the command should report that cleanly. cliIt.live( "mcp list: exits 0", @@ -75,7 +75,7 @@ describe("opencode read-only commands (smoke)", () => { 60_000, ) - // `session list` reads the session DB. Fresh OPENCODE_TEST_HOME means + // `session list` reads the session DB. Fresh DEEPCODE_TEST_HOME means // empty DB. Exit 0 with no sessions. cliIt.live( "session list: exits 0", @@ -100,7 +100,7 @@ describe("opencode read-only commands (smoke)", () => { // `db path` prints the DB file location. Under harness isolation the DB // resolves to SQLite's `:memory:` (no on-disk pollution between tests); - // in production it'd be a path under OPENCODE_TEST_HOME / XDG_DATA_HOME. + // in production it'd be a path under DEEPCODE_TEST_HOME / XDG_DATA_HOME. // Accept either form — both prove the resolver ran without crashing. cliIt.live( "db path: exits 0 and prints a path or :memory:", diff --git a/packages/opencode/test/cli/tui/editor-context-zed.test.ts b/packages/opencode/test/cli/tui/editor-context-zed.test.ts index 3cd6adb1..57573a39 100644 --- a/packages/opencode/test/cli/tui/editor-context-zed.test.ts +++ b/packages/opencode/test/cli/tui/editor-context-zed.test.ts @@ -83,14 +83,14 @@ test("resolveZedDbPath skips candidates that cannot be stated", async () => { const loop = path.join(tmp.path, "loop") await symlink(loop, loop) const home = spyOn(os, "homedir").mockImplementation(() => tmp.path) - const previous = process.env.OPENCODE_ZED_DB - process.env.OPENCODE_ZED_DB = loop + const previous = process.env.DEEPCODE_ZED_DB + process.env.DEEPCODE_ZED_DB = loop try { expect(resolveZedDbPath()).toBeUndefined() } finally { - if (previous === undefined) delete process.env.OPENCODE_ZED_DB - else process.env.OPENCODE_ZED_DB = previous + if (previous === undefined) delete process.env.DEEPCODE_ZED_DB + else process.env.DEEPCODE_ZED_DB = previous home.mockRestore() } }) diff --git a/packages/opencode/test/cli/tui/editor-context.test.tsx b/packages/opencode/test/cli/tui/editor-context.test.tsx index 2b114b5c..288922be 100644 --- a/packages/opencode/test/cli/tui/editor-context.test.tsx +++ b/packages/opencode/test/cli/tui/editor-context.test.tsx @@ -10,11 +10,11 @@ import { TestTuiContexts } from "../../fixture/tui-environment" import { discoverEditorConnection } from "@opencode-ai/tui/editor" const originalClaudePort = process.env.CLAUDE_CODE_SSE_PORT -const originalOpencodePort = process.env.OPENCODE_EDITOR_SSE_PORT +const originalOpencodePort = process.env.DEEPCODE_EDITOR_SSE_PORT afterEach(() => { process.env.CLAUDE_CODE_SSE_PORT = originalClaudePort - process.env.OPENCODE_EDITOR_SSE_PORT = originalOpencodePort + process.env.DEEPCODE_EDITOR_SSE_PORT = originalOpencodePort }) function nextTick() { @@ -33,7 +33,7 @@ function mountEditorContext(WebSocketImpl?: typeof WebSocket) { return null } - const value = process.env.CLAUDE_CODE_SSE_PORT || process.env.OPENCODE_EDITOR_SSE_PORT + const value = process.env.CLAUDE_CODE_SSE_PORT || process.env.DEEPCODE_EDITOR_SSE_PORT return ( @@ -125,7 +125,7 @@ test("useEditorContext reconnect switches editor server by session directory", a ) process.env.CLAUDE_CODE_SSE_PORT = undefined - process.env.OPENCODE_EDITOR_SSE_PORT = undefined + process.env.DEEPCODE_EDITOR_SSE_PORT = undefined spyOn(process, "cwd").mockImplementation(() => startupDirectory) spyOn(os, "homedir").mockImplementation(() => tmp.path) const firstSocket = new FakeWebSocket("ws://127.0.0.1:3001") @@ -166,7 +166,7 @@ test("useEditorContext favors configured port over lock files", async () => { ) process.env.CLAUDE_CODE_SSE_PORT = "4010" - process.env.OPENCODE_EDITOR_SSE_PORT = undefined + process.env.DEEPCODE_EDITOR_SSE_PORT = undefined spyOn(process, "cwd").mockImplementation(() => startupDirectory) spyOn(os, "homedir").mockImplementation(() => tmp.path) const socket = new FakeWebSocket("ws://127.0.0.1:4010") @@ -194,7 +194,7 @@ test("useEditorContext clears selection when reconnecting", async () => { ) process.env.CLAUDE_CODE_SSE_PORT = undefined - process.env.OPENCODE_EDITOR_SSE_PORT = undefined + process.env.DEEPCODE_EDITOR_SSE_PORT = undefined spyOn(process, "cwd").mockImplementation(() => startupDirectory) spyOn(os, "homedir").mockImplementation(() => tmp.path) const socket = new FakeWebSocket("ws://127.0.0.1:3001") @@ -254,7 +254,7 @@ test("useEditorContext preserves selection for the next reconnect when requested ) process.env.CLAUDE_CODE_SSE_PORT = undefined - process.env.OPENCODE_EDITOR_SSE_PORT = undefined + process.env.DEEPCODE_EDITOR_SSE_PORT = undefined spyOn(process, "cwd").mockImplementation(() => startupDirectory) spyOn(os, "homedir").mockImplementation(() => tmp.path) const socket = new FakeWebSocket("ws://127.0.0.1:3001") @@ -281,10 +281,10 @@ test("useEditorContext preserves selection for the next reconnect when requested mounted.dispose() }) -test("useEditorContext connects with OPENCODE_EDITOR_SSE_PORT", async () => { +test("useEditorContext connects with DEEPCODE_EDITOR_SSE_PORT", async () => { await using tmp = await tmpdir() process.env.CLAUDE_CODE_SSE_PORT = undefined - process.env.OPENCODE_EDITOR_SSE_PORT = "4020" + process.env.DEEPCODE_EDITOR_SSE_PORT = "4020" spyOn(process, "cwd").mockImplementation(() => tmp.path) const socket = new FakeWebSocket("ws://127.0.0.1:4020") diff --git a/packages/opencode/test/cli/tui/plugin-add.test.ts b/packages/opencode/test/cli/tui/plugin-add.test.ts index bf509b8d..085b6bba 100644 --- a/packages/opencode/test/cli/tui/plugin-add.test.ts +++ b/packages/opencode/test/cli/tui/plugin-add.test.ts @@ -31,7 +31,7 @@ test("adds tui plugin at runtime from spec", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [], }) @@ -58,7 +58,7 @@ test("adds tui plugin at runtime from spec", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -73,7 +73,7 @@ test("retries runtime add for file plugins after dependency wait", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [], }) @@ -105,6 +105,6 @@ test("retries runtime add for file plugins after dependency wait", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) diff --git a/packages/opencode/test/cli/tui/plugin-install.test.ts b/packages/opencode/test/cli/tui/plugin-install.test.ts index 3d4f1ece..cf49f3b0 100644 --- a/packages/opencode/test/cli/tui/plugin-install.test.ts +++ b/packages/opencode/test/cli/tui/plugin-install.test.ts @@ -50,7 +50,7 @@ test("installs plugin without loading it", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [], }) @@ -82,6 +82,6 @@ test("installs plugin without loading it", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) diff --git a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts index 3b6adeb3..a83ad188 100644 --- a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts @@ -44,7 +44,7 @@ test("loads npm tui plugin from package ./tui export", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [[tmp.extra.spec, { marker: tmp.extra.marker }]], plugin_origins: [ @@ -71,7 +71,7 @@ test("loads npm tui plugin from package ./tui export", async () => { install.mockRestore() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -105,7 +105,7 @@ test("does not use npm package exports dot for tui entry", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -129,7 +129,7 @@ test("does not use npm package exports dot for tui entry", async () => { install.mockRestore() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -167,7 +167,7 @@ test("rejects npm tui export that resolves outside plugin directory", async () = }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -193,7 +193,7 @@ test("rejects npm tui export that resolves outside plugin directory", async () = install.mockRestore() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -229,7 +229,7 @@ test("rejects npm tui plugin that exports server and tui together", async () => }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -253,7 +253,7 @@ test("rejects npm tui plugin that exports server and tui together", async () => install.mockRestore() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -287,7 +287,7 @@ test("does not use npm package main for tui entry", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -317,7 +317,7 @@ test("does not use npm package main for tui entry", async () => { wait.mockRestore() warn.mockRestore() error.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -352,7 +352,7 @@ test("does not use directory package main for tui entry", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -374,7 +374,7 @@ test("does not use directory package main for tui entry", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -399,7 +399,7 @@ test("uses directory index fallback for tui when package.json is missing", async }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [tmp.extra.spec], plugin_origins: [ @@ -421,7 +421,7 @@ test("uses directory index fallback for tui when package.json is missing", async await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -456,7 +456,7 @@ test("uses npm package name when tui plugin id is omitted", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [[tmp.extra.spec, { marker: tmp.extra.marker }]], plugin_origins: [ @@ -480,6 +480,6 @@ test("uses npm package name when tui plugin id is omitted", async () => { install.mockRestore() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) diff --git a/packages/opencode/test/cli/tui/plugin-loader-pure.test.ts b/packages/opencode/test/cli/tui/plugin-loader-pure.test.ts index 8a754d04..6afa6a5f 100644 --- a/packages/opencode/test/cli/tui/plugin-loader-pure.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader-pure.test.ts @@ -33,10 +33,10 @@ test("skips external tui plugins in pure mode", async () => { }, }) - const pure = process.env.OPENCODE_PURE - const meta = process.env.OPENCODE_PLUGIN_META_FILE - process.env.OPENCODE_PURE = "1" - process.env.OPENCODE_PLUGIN_META_FILE = tmp.extra.meta + const pure = process.env.DEEPCODE_PURE + const meta = process.env.DEEPCODE_PLUGIN_META_FILE + process.env.DEEPCODE_PURE = "1" + process.env.DEEPCODE_PLUGIN_META_FILE = tmp.extra.meta const config = createTuiResolvedConfig({ plugin: [[tmp.extra.spec, { marker: tmp.extra.marker }]], @@ -59,14 +59,14 @@ test("skips external tui plugins in pure mode", async () => { cwd.mockRestore() wait.mockRestore() if (pure === undefined) { - delete process.env.OPENCODE_PURE + delete process.env.DEEPCODE_PURE } else { - process.env.OPENCODE_PURE = pure + process.env.DEEPCODE_PURE = pure } if (meta === undefined) { - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } else { - process.env.OPENCODE_PLUGIN_META_FILE = meta + process.env.DEEPCODE_PLUGIN_META_FILE = meta } } }) diff --git a/packages/opencode/test/cli/tui/plugin-loader.test.ts b/packages/opencode/test/cli/tui/plugin-loader.test.ts index 49ea06af..ac690c33 100644 --- a/packages/opencode/test/cli/tui/plugin-loader.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader.test.ts @@ -624,7 +624,7 @@ test("continues loading when a plugin is missing config metadata", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [ [tmp.extra.badSpec, { marker: path.join(tmp.path, "bad.txt") }], @@ -659,7 +659,7 @@ test("continues loading when a plugin is missing config metadata", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -696,7 +696,7 @@ test("does not wait on permanent tui plugin startup failures", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) @@ -724,7 +724,7 @@ test("does not wait on permanent tui plugin startup failures", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -781,7 +781,7 @@ export default { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) try { @@ -802,7 +802,7 @@ export default { } finally { await TuiPluginRuntime.dispose() cwd.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE if (backupJson === undefined) { await fs.rm(globalJson, { force: true }).catch(() => {}) @@ -1268,7 +1268,7 @@ test("updates installed theme when plugin metadata changes", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const cwd = spyOn(process, "cwd").mockImplementation(() => tmp.path) const wait = spyOn(TuiConfig, "waitForDependencies").mockResolvedValue() @@ -1320,13 +1320,13 @@ test("updates installed theme when plugin metadata changes", async () => { expect(text).toContain("#222222") expect(text).not.toContain("#111111") const list = await Filesystem.readJson }>>( - process.env.OPENCODE_PLUGIN_META_FILE!, + process.env.DEEPCODE_PLUGIN_META_FILE!, ) expect(list["demo.theme-update"]?.themes?.[tmp.extra.themeName]?.dest).toBe(tmp.extra.dest) } finally { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) diff --git a/packages/opencode/test/cli/tui/plugin-toggle.test.ts b/packages/opencode/test/cli/tui/plugin-toggle.test.ts index 47026550..83cccd99 100644 --- a/packages/opencode/test/cli/tui/plugin-toggle.test.ts +++ b/packages/opencode/test/cli/tui/plugin-toggle.test.ts @@ -39,7 +39,7 @@ test("toggles plugin runtime state by exported id", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [[tmp.extra.spec, { marker: tmp.extra.marker }]], plugin_enabled: { @@ -87,7 +87,7 @@ test("toggles plugin runtime state by exported id", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) @@ -180,7 +180,7 @@ test("kv plugin_enabled overrides tui config on startup", async () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "plugin-meta.json") const config = createTuiResolvedConfig({ plugin: [[tmp.extra.spec, { marker: tmp.extra.marker }]], plugin_enabled: { @@ -217,7 +217,7 @@ test("kv plugin_enabled overrides tui config on startup", async () => { await TuiPluginRuntime.dispose() cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE } }) diff --git a/packages/opencode/test/config/coexistence.test.ts b/packages/opencode/test/config/coexistence.test.ts new file mode 100644 index 00000000..1649f375 --- /dev/null +++ b/packages/opencode/test/config/coexistence.test.ts @@ -0,0 +1,172 @@ +import { expect } from "bun:test" +import path from "path" +import { Effect, Layer } from "effect" +import { HttpClient } from "effect/unstable/http" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { httpClient } from "@opencode-ai/core/effect/app-node-platform" +import { FSUtil } from "@opencode-ai/core/fs-util" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Npm } from "@opencode-ai/core/npm" +import { Config } from "@/config/config" +import { Env } from "@/env" +import { Auth } from "@/auth" +import { Account } from "@/account/account" +import { TestInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" +import { AuthTest } from "../fake/auth" +import { AccountTest } from "../fake/account" +import { NpmTest } from "../fake/npm" + +const unexpectedHttp = HttpClient.make((request) => + Effect.die(`unexpected http request: ${request.method} ${request.url}`), +) + +const layer = LayerNode.compile(LayerNode.group([Config.node, FSUtil.node, Env.node, CrossSpawnSpawner.node]), [ + [Auth.node, AuthTest.empty], + [Account.node, AccountTest.empty], + [Npm.node, NpmTest.noop], + [httpClient, Layer.succeed(HttpClient.HttpClient, unexpectedHttp)], +]) + +const it = testEffect(layer) + +const writeJson = (file: string, value: object) => + FSUtil.use.writeWithDirs(file, JSON.stringify({ $schema: "https://opencode.ai/config.json", ...value })) + +function withProcessEnvs(entries: Record, effect: Effect.Effect) { + return Effect.acquireUseRelease( + Effect.sync(() => { + const originals: Record = {} + for (const [key, value] of Object.entries(entries)) { + originals[key] = process.env[key] + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + return originals + }), + () => effect, + (originals) => + Effect.sync(() => { + for (const [key, value] of Object.entries(originals)) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + }), + ) +} + +it.instance("loads a DeepCode-only project configuration", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, "deepcode.json"), { username: "deepcode-only" }) + + expect((yield* Config.use.get()).username).toBe("deepcode-only") + }), +) + +it.instance("ignores OpenCode root configuration files", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, "opencode.json"), { username: "opencode-json" }) + yield* writeJson(path.join(test.directory, "opencode.jsonc"), { username: "opencode-jsonc" }) + + const config = yield* Config.use.get() + expect(config.username).not.toBe("opencode-json") + expect(config.username).not.toBe("opencode-jsonc") + }), +) + +it.instance("loads only DeepCode configuration when both products coexist", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, "opencode.json"), { username: "opencode-user" }) + yield* writeJson(path.join(test.directory, "deepcode.json"), { username: "deepcode-user" }) + + expect((yield* Config.use.get()).username).toBe("deepcode-user") + }), +) + +it.instance("ignores configuration under .opencode", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, ".opencode", "deepcode.json"), { username: "opencode-dir" }) + yield* writeJson(path.join(test.directory, ".opencode", "opencode.json"), { username: "opencode-file" }) + + const config = yield* Config.use.get() + expect(config.username).not.toBe("opencode-dir") + expect(config.username).not.toBe("opencode-file") + }), +) + +it.instance("ignores OPENCODE_CONFIG_DIR and still loads project config", () => + Effect.gen(function* () { + const test = yield* TestInstance + const foreign = path.join(test.directory, "foreign-config") + yield* writeJson(path.join(foreign, "deepcode.json"), { username: "opencode-env" }) + yield* writeJson(path.join(test.directory, "deepcode.json"), { username: "project-deepcode" }) + + yield* withProcessEnvs( + { + OPENCODE_CONFIG_DIR: foreign, + DEEPCODE_CONFIG_DIR: undefined, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).username).toBe("project-deepcode") + }), + ) + }), +) + +it.instance("loads DEEPCODE_CONFIG_DIR without reading OPENCODE_CONFIG_DIR", () => + Effect.gen(function* () { + const test = yield* TestInstance + const deepcode = path.join(test.directory, "deepcode-config") + const opencode = path.join(test.directory, "opencode-config") + yield* writeJson(path.join(deepcode, "deepcode.json"), { username: "deepcode-env" }) + yield* writeJson(path.join(opencode, "deepcode.json"), { username: "opencode-env" }) + + yield* withProcessEnvs( + { + DEEPCODE_CONFIG_DIR: deepcode, + OPENCODE_CONFIG_DIR: opencode, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).username).toBe("deepcode-env") + }), + ) + }), +) + +it.instance("ignores OPENCODE_DISABLE_PROJECT_CONFIG", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, "deepcode.json"), { username: "project-enabled" }) + + yield* withProcessEnvs( + { + OPENCODE_DISABLE_PROJECT_CONFIG: "true", + DEEPCODE_DISABLE_PROJECT_CONFIG: undefined, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).username).toBe("project-enabled") + }), + ) + }), +) + +it.instance("honors DEEPCODE_DISABLE_PROJECT_CONFIG", () => + Effect.gen(function* () { + const test = yield* TestInstance + yield* writeJson(path.join(test.directory, "deepcode.json"), { username: "project-disabled" }) + + yield* withProcessEnvs( + { + DEEPCODE_DISABLE_PROJECT_CONFIG: "true", + OPENCODE_DISABLE_PROJECT_CONFIG: undefined, + }, + Effect.gen(function* () { + expect((yield* Config.use.get()).username).not.toBe("project-disabled") + }), + ) + }), +) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index bcaa7d5d..6271ffb0 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -131,7 +131,7 @@ const clear = (wait = false) => Effect.runPromise(clearEffect(wait)) // Get managed config directory from environment (set in preload.ts) const managedConfigDir = process.env.DEEPCODE_TEST_MANAGED_CONFIG_DIR! const originalTestToken = process.env.TEST_TOKEN -const originalConsoleToken = process.env.OPENCODE_CONSOLE_TOKEN +const originalConsoleToken = process.env.DEEPCODE_CONSOLE_TOKEN beforeEach(async () => { await clear(true) @@ -141,8 +141,8 @@ afterEach(async () => { await fs.rm(managedConfigDir, { force: true, recursive: true }).catch(() => {}) if (originalTestToken === undefined) delete process.env.TEST_TOKEN else process.env.TEST_TOKEN = originalTestToken - if (originalConsoleToken === undefined) delete process.env.OPENCODE_CONSOLE_TOKEN - else process.env.OPENCODE_CONSOLE_TOKEN = originalConsoleToken + if (originalConsoleToken === undefined) delete process.env.DEEPCODE_CONSOLE_TOKEN + else process.env.DEEPCODE_CONSOLE_TOKEN = originalConsoleToken await clear(true) }) @@ -294,7 +294,7 @@ it.instance("loads config with defaults when no files exist", () => }), ) -it.instance.skip("does not load an OpenCode-only project configuration", () => +it.instance("does not load an OpenCode-only project configuration", () => Effect.gen(function* () { const test = yield* TestInstance yield* writeConfigEffect(test.directory, { username: "opencode-only" }, "opencode.json") @@ -304,7 +304,7 @@ it.instance.skip("does not load an OpenCode-only project configuration", () => }), ) -it.instance.skip("loads DeepCode configuration when both products coexist", () => +it.instance("loads DeepCode configuration when both products coexist", () => Effect.gen(function* () { const test = yield* TestInstance yield* Effect.all([ @@ -342,12 +342,12 @@ it.effect("creates global jsonc config with schema when no global configs exist" ), ) -it.effect("does not create global config when OPENCODE_CONFIG_DIR is set", () => +it.effect("does not create global config when DEEPCODE_CONFIG_DIR is set", () => Effect.gen(function* () { const custom = yield* tmpdirScoped() yield* withGlobalConfig({}, ({ dir }) => withProcessEnv( - "OPENCODE_CONFIG_DIR", + "DEEPCODE_CONFIG_DIR", custom, Effect.gen(function* () { yield* Config.use.get().pipe(provideInstanceEffect(dir)) @@ -384,12 +384,12 @@ it.instance("updates config and preserves empty shell sentinel", () => yield* writeConfigEffect( test.directory, { $schema: "https://opencode.ai/config.json", shell: "bash" }, - "config.json", + "deepcode.json", ) yield* Config.Service.use((svc) => svc.update(ConfigParse.schema(ConfigV1.Info, { shell: "" }, "test:config"))) - const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "config.json")) + const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "deepcode.json")) expect(writtenConfig).toMatchObject({ shell: "" }) }), ) @@ -455,7 +455,7 @@ test("loads project config from Cygwin paths on Windows", async () => { }) }) -it.instance("ignores legacy tui keys in opencode config", () => +it.instance("ignores legacy tui keys in deepcode config", () => Effect.gen(function* () { const test = yield* TestInstance yield* writeConfigEffect(test.directory, { @@ -606,7 +606,7 @@ const accountTokenIt = configIt({ config: () => Effect.succeed( Option.some({ - provider: { opencode: { options: { apiKey: "{env:OPENCODE_CONSOLE_TOKEN}" } } }, + provider: { opencode: { options: { apiKey: "{env:DEEPCODE_CONSOLE_TOKEN}" } } }, }), ), token: () => Effect.succeed(Option.some(AccessToken.make("st_test_token"))), @@ -920,7 +920,7 @@ it.instance("updates config and writes to file", () => svc.update(ConfigParse.schema(ConfigV1.Info, { model: "updated/model" }, "test:config")), ) - const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "config.json")) + const writtenConfig = yield* FSUtil.use.readJson(path.join(test.directory, "deepcode.json")) expect(writtenConfig).toMatchObject({ model: "updated/model" }) }), ) @@ -932,7 +932,7 @@ it.instance("gets config directories", () => }), ) -it.effect("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR", () => +it.effect("does not try to install dependencies in read-only DEEPCODE_CONFIG_DIR", () => Effect.gen(function* () { if (process.platform === "win32") return @@ -942,18 +942,18 @@ it.effect("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR yield* FSUtil.use.chmod(readonly, 0o555) yield* Effect.addFinalizer(() => FSUtil.use.chmod(readonly, 0o755).pipe(Effect.ignore)) - yield* withProcessEnv("OPENCODE_CONFIG_DIR", readonly, Config.use.get().pipe(provideInstanceEffect(dir))) + yield* withProcessEnv("DEEPCODE_CONFIG_DIR", readonly, Config.use.get().pipe(provideInstanceEffect(dir))) }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))), ) -it.effect("installs dependencies in writable OPENCODE_CONFIG_DIR", () => +it.effect("installs dependencies in writable DEEPCODE_CONFIG_DIR", () => Effect.gen(function* () { const dir = yield* tmpdirScoped() const configDir = path.join(dir, "configdir") yield* FSUtil.use.ensureDir(configDir) yield* withProcessEnv( - "OPENCODE_CONFIG_DIR", + "DEEPCODE_CONFIG_DIR", configDir, Config.Service.use((svc) => svc.get().pipe(Effect.andThen(svc.waitForDependencies()))).pipe( provideInstanceEffect(dir), @@ -1024,7 +1024,7 @@ it.effect("global config remains global when project config is disabled", () => local: { model: "local/model" }, }, withProcessEnv( - "OPENCODE_DISABLE_PROJECT_CONFIG", + "DEEPCODE_DISABLE_PROJECT_CONFIG", "true", Effect.gen(function* () { const config = yield* Config.use.get() @@ -1178,7 +1178,7 @@ it.instance("migrates legacy write tool to edit permission", () => ) // Managed settings tests -// Note: preload.ts sets OPENCODE_TEST_MANAGED_CONFIG which Global.Path.managedConfig uses +// Note: preload.ts sets DEEPCODE_TEST_MANAGED_CONFIG which Global.Path.managedConfig uses it.instance( "managed settings override user settings", @@ -1564,7 +1564,7 @@ test("remote well-known config can use FetchHttpClient layer", async () => { const templatedHeaderWellKnown = wellKnown({ remoteConfig: { - url: "https://config.example.com/opencode.json", + url: "https://config.example.com/deepcode.json", headers: { Authorization: "Bearer {env:TEST_TOKEN}" }, }, remote: { @@ -1576,7 +1576,7 @@ templatedHeaderWellKnown.it.instance("wellknown remote_config supports templated Effect.gen(function* () { const config = yield* Config.use.get() expect(templatedHeaderWellKnown.seen.wellKnown).toBe("https://example.com/.well-known/opencode") - expect(templatedHeaderWellKnown.seen.remote).toBe("https://config.example.com/opencode.json") + expect(templatedHeaderWellKnown.seen.remote).toBe("https://config.example.com/deepcode.json") expect(templatedHeaderWellKnown.seen.authorization).toBe("Bearer test-token") expect(config.mcp?.confluence?.enabled).toBe(true) }), @@ -1586,7 +1586,7 @@ const remotePrecedenceWellKnown = wellKnown({ config: { mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: false } }, }, - remoteConfig: { url: "https://config.example.com/{env:TEST_TOKEN}/opencode.json" }, + remoteConfig: { url: "https://config.example.com/{env:TEST_TOKEN}/deepcode.json" }, remote: { config: { mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } } }, }, @@ -1597,14 +1597,14 @@ remotePrecedenceWellKnown.it.instance( () => Effect.gen(function* () { const config = yield* Config.use.get() - expect(remotePrecedenceWellKnown.seen.remote).toBe("https://config.example.com/test-token/opencode.json") + expect(remotePrecedenceWellKnown.seen.remote).toBe("https://config.example.com/test-token/deepcode.json") expect(config.mcp?.confluence?.enabled).toBe(true) }), ) const envIsolationWellKnown = wellKnown({ remoteConfig: { - url: "https://config.example.com/opencode.json", + url: "https://config.example.com/deepcode.json", headers: { Authorization: "Bearer {env:TEST_TOKEN}" }, }, remote: { @@ -1628,7 +1628,7 @@ envIsolationWellKnown.it.instance( const nullConfigWellKnown = wellKnown({ wellKnown: { config: null, - remote_config: { url: "https://config.example.com/opencode.json" }, + remote_config: { url: "https://config.example.com/deepcode.json" }, }, remote: { mcp: { confluence: { type: "remote", url: "https://confluence.example.com/mcp", enabled: true } }, @@ -1638,26 +1638,26 @@ const nullConfigWellKnown = wellKnown({ nullConfigWellKnown.it.instance("wellknown config null is treated as absent", () => Effect.gen(function* () { const config = yield* Config.use.get() - expect(nullConfigWellKnown.seen.remote).toBe("https://config.example.com/opencode.json") + expect(nullConfigWellKnown.seen.remote).toBe("https://config.example.com/deepcode.json") expect(config.mcp?.confluence?.enabled).toBe(true) }), ) const invalidRemoteWellKnown = wellKnown({ - remoteConfig: { url: "https://config.example.com/opencode.json" }, + remoteConfig: { url: "https://config.example.com/deepcode.json" }, remote: "not an object", }) invalidRemoteWellKnown.it.instance("wellknown remote_config rejects non-object config responses", () => Effect.gen(function* () { const exit = yield* Config.use.get().pipe(Effect.exit) - expect(invalidRemoteWellKnown.seen.remote).toBe("https://config.example.com/opencode.json") + expect(invalidRemoteWellKnown.seen.remote).toBe("https://config.example.com/deepcode.json") expect(Exit.isFailure(exit)).toBe(true) }), ) const loginPageWellKnown = wellKnown({ - remoteConfig: { url: "https://config.example.com/opencode.json" }, + remoteConfig: { url: "https://config.example.com/deepcode.json" }, remoteHtml: "Sign inLogin required", }) @@ -1666,7 +1666,7 @@ loginPageWellKnown.it.instance( () => Effect.gen(function* () { const exit = yield* Config.use.get().pipe(Effect.exit) - expect(loginPageWellKnown.seen.remote).toBe("https://config.example.com/opencode.json") + expect(loginPageWellKnown.seen.remote).toBe("https://config.example.com/deepcode.json") expect(Exit.isFailure(exit)).toBe(true) const error = Exit.isFailure(exit) ? Cause.squash(exit.cause) : undefined expect(NamedError.hasName(error, "ConfigRemoteAuthError")).toBe(true) @@ -1767,7 +1767,7 @@ describe("deduplicatePluginOrigins", () => { }) test("keeps path plugins separate from package plugins", () => { - const plugins = ["oh-my-opencode@2.4.3", "file:///project/.opencode/plugin/oh-my-opencode.js"] + const plugins = ["oh-my-opencode@2.4.3", "file:///project/.deepcode/plugin/oh-my-opencode.js"] const result = dedupe(plugins) @@ -1808,12 +1808,12 @@ describe("deduplicatePluginOrigins", () => { ) }) -describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { +describe("DEEPCODE_DISABLE_PROJECT_CONFIG", () => { it.instance( "skips project config files when flag is set", () => withProcessEnv( - "OPENCODE_DISABLE_PROJECT_CONFIG", + "DEEPCODE_DISABLE_PROJECT_CONFIG", "true", Effect.gen(function* () { const config = yield* Config.use.get() @@ -1826,7 +1826,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { it.instance("skips project .deepcode/ directories when flag is set", () => withProcessEnv( - "OPENCODE_DISABLE_PROJECT_CONFIG", + "DEEPCODE_DISABLE_PROJECT_CONFIG", "true", Effect.gen(function* () { const test = yield* TestInstance @@ -1842,7 +1842,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { it.instance("still loads global config when flag is set", () => withProcessEnv( - "OPENCODE_DISABLE_PROJECT_CONFIG", + "DEEPCODE_DISABLE_PROJECT_CONFIG", "true", Effect.gen(function* () { const config = yield* Config.use.get() @@ -1856,7 +1856,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { "skips relative instructions with warning when flag is set but no config dir", () => withProcessEnvs( - { OPENCODE_CONFIG_DIR: undefined, OPENCODE_DISABLE_PROJECT_CONFIG: "true" }, + { DEEPCODE_CONFIG_DIR: undefined, DEEPCODE_DISABLE_PROJECT_CONFIG: "true" }, Effect.gen(function* () { const test = yield* TestInstance yield* FSUtil.use.writeWithDirs(path.join(test.directory, "CUSTOM.md"), "# Custom Instructions") @@ -1869,12 +1869,12 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { ) it.instance( - "OPENCODE_CONFIG_DIR still works when flag is set", + "DEEPCODE_CONFIG_DIR still works when flag is set", () => Effect.gen(function* () { const configDir = yield* tmpdirScoped({ config: { model: "configdir/model" } }) yield* withProcessEnvs( - { OPENCODE_DISABLE_PROJECT_CONFIG: "true", OPENCODE_CONFIG_DIR: configDir }, + { DEEPCODE_DISABLE_PROJECT_CONFIG: "true", DEEPCODE_CONFIG_DIR: configDir }, Effect.gen(function* () { const config = yield* Config.use.get() expect(config.model).toBe("configdir/model") @@ -1885,13 +1885,13 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => { ) }) -// Regression for #28206: malformed OPENCODE_PERMISSION JSON used to crash +// Regression for #28206: malformed DEEPCODE_PERMISSION JSON used to crash // the app on startup with an unhandled SyntaxError. Loading the config with // an invalid JSON value in this env var should not throw. -describe("OPENCODE_PERMISSION env var", () => { - it.instance("does not crash when OPENCODE_PERMISSION contains invalid JSON", () => +describe("DEEPCODE_PERMISSION env var", () => { + it.instance("does not crash when DEEPCODE_PERMISSION contains invalid JSON", () => withProcessEnv( - "OPENCODE_PERMISSION", + "DEEPCODE_PERMISSION", "{invalid", Effect.gen(function* () { const config = yield* Config.use.get() @@ -1902,13 +1902,13 @@ describe("OPENCODE_PERMISSION env var", () => { ) }) -describe("OPENCODE_CONFIG_CONTENT token substitution", () => { - it.instance("substitutes {env:} tokens in OPENCODE_CONFIG_CONTENT", () => +describe("DEEPCODE_CONFIG_CONTENT token substitution", () => { + it.instance("substitutes {env:} tokens in DEEPCODE_CONFIG_CONTENT", () => withProcessEnv( "TEST_CONFIG_VAR", "test_api_key_12345", withProcessEnv( - "OPENCODE_CONFIG_CONTENT", + "DEEPCODE_CONFIG_CONTENT", JSON.stringify({ $schema: "https://opencode.ai/config.json", username: "{env:TEST_CONFIG_VAR}", @@ -1921,12 +1921,12 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => { ), ) - it.instance("substitutes {file:} tokens in OPENCODE_CONFIG_CONTENT", () => + it.instance("substitutes {file:} tokens in DEEPCODE_CONFIG_CONTENT", () => Effect.gen(function* () { const test = yield* TestInstance yield* FSUtil.use.writeWithDirs(path.join(test.directory, "api_key.txt"), "secret_key_from_file") yield* withProcessEnv( - "OPENCODE_CONFIG_CONTENT", + "DEEPCODE_CONFIG_CONTENT", JSON.stringify({ $schema: "https://opencode.ai/config.json", username: "{file:./api_key.txt}", @@ -1948,7 +1948,7 @@ test("parseManagedPlist strips MDM metadata keys", async () => { ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ - PayloadDisplayName: "OpenCode Managed", + PayloadDisplayName: "DeepCode Managed", PayloadIdentifier: "ai.deepcode.managed.test", PayloadType: "ai.deepcode.managed", PayloadUUID: "AAAA-BBBB-CCCC", diff --git a/packages/opencode/test/config/tui-coexistence.test.ts b/packages/opencode/test/config/tui-coexistence.test.ts new file mode 100644 index 00000000..47d5cc8c --- /dev/null +++ b/packages/opencode/test/config/tui-coexistence.test.ts @@ -0,0 +1,132 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { createHash } from "crypto" +import fs from "fs/promises" +import os from "os" +import path from "path" +import { Global } from "@opencode-ai/core/global" +import { migrateTuiConfig } from "@/config/tui-migrate" + +let root = "" +let project = "" +let globalConfig = "" +let originalGlobalConfig = "" + +async function writeJson(file: string, value: object) { + await fs.mkdir(path.dirname(file), { recursive: true }) + await fs.writeFile(file, JSON.stringify(value, null, 2)) +} + +async function exists(file: string) { + return fs + .access(file) + .then(() => true) + .catch(() => false) +} + +async function treeHash(directory: string) { + const hash = createHash("sha256") + + async function walk(current: string, relative = "") { + const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []) + entries.sort((a, b) => a.name.localeCompare(b.name)) + for (const entry of entries) { + const nextRelative = path.posix.join(relative.replaceAll("\\", "/"), entry.name) + const full = path.join(current, entry.name) + hash.update(`${entry.isDirectory() ? "d" : "f"}:${nextRelative}\n`) + if (entry.isDirectory()) await walk(full, nextRelative) + else hash.update(await fs.readFile(full)) + } + } + + await walk(directory) + return hash.digest("hex") +} + +beforeEach(async () => { + root = await fs.mkdtemp(path.join(os.tmpdir(), "deepcode-tui-coexistence-")) + project = path.join(root, "project") + globalConfig = path.join(root, "global") + await fs.mkdir(project, { recursive: true }) + await fs.mkdir(globalConfig, { recursive: true }) + originalGlobalConfig = Global.Path.config + ;(Global.Path as { config: string }).config = globalConfig +}) + +afterEach(async () => { + ;(Global.Path as { config: string }).config = originalGlobalConfig + await fs.rm(root, { recursive: true, force: true }) +}) + +describe("TUI migration coexistence", () => { + test("does not read or modify OpenCode configuration", async () => { + const opencodeDir = path.join(project, ".opencode") + await writeJson(path.join(project, "opencode.json"), { + theme: "opencode-root", + tui: { scroll_speed: 4 }, + }) + await writeJson(path.join(opencodeDir, "opencode.json"), { + theme: "opencode-directory", + tui: { diff_style: "stacked" }, + }) + + const before = await treeHash(project) + await migrateTuiConfig({ cwd: project, directories: [path.join(project, ".deepcode")] }) + const after = await treeHash(project) + + expect(after).toBe(before) + expect(await exists(path.join(project, "tui.json"))).toBe(false) + expect(await exists(path.join(project, "opencode.json.tui-migration.bak"))).toBe(false) + expect(await exists(path.join(opencodeDir, "tui.json"))).toBe(false) + }) + + test("migrates DeepCode config and preserves coexisting OpenCode config byte-for-byte", async () => { + const opencode = path.join(project, "opencode.json") + const deepcode = path.join(project, "deepcode.json") + await writeJson(opencode, { + theme: "opencode-theme", + tui: { scroll_speed: 9 }, + username: "opencode-user", + }) + await writeJson(deepcode, { + theme: "deepcode-theme", + tui: { scroll_speed: 3, diff_style: "stacked" }, + username: "deepcode-user", + }) + const opencodeBefore = await fs.readFile(opencode) + + await migrateTuiConfig({ cwd: project, directories: [path.join(project, ".deepcode")] }) + + expect(await fs.readFile(opencode)).toEqual(opencodeBefore) + expect(await exists(`${opencode}.tui-migration.bak`)).toBe(false) + expect(await exists(`${deepcode}.tui-migration.bak`)).toBe(true) + expect(JSON.parse(await fs.readFile(path.join(project, "tui.json"), "utf8"))).toMatchObject({ + theme: "deepcode-theme", + scroll_speed: 3, + diff_style: "stacked", + }) + expect(JSON.parse(await fs.readFile(deepcode, "utf8"))).toEqual({ username: "deepcode-user" }) + }) + + test("migrates .deepcode config without touching .opencode tree", async () => { + const deepcodeDir = path.join(project, ".deepcode") + const opencodeDir = path.join(project, ".opencode") + await writeJson(path.join(deepcodeDir, "deepcode.json"), { + theme: "deepcode-local", + tui: { scroll_speed: 2 }, + }) + await writeJson(path.join(opencodeDir, "opencode.json"), { + theme: "opencode-local", + tui: { scroll_speed: 8 }, + }) + const opencodeBefore = await treeHash(opencodeDir) + + await migrateTuiConfig({ cwd: project, directories: [deepcodeDir] }) + + expect(await treeHash(opencodeDir)).toBe(opencodeBefore) + expect(JSON.parse(await fs.readFile(path.join(deepcodeDir, "tui.json"), "utf8"))).toMatchObject({ + theme: "deepcode-local", + scroll_speed: 2, + }) + expect(await exists(path.join(opencodeDir, "tui.json"))).toBe(false) + }) +}) diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index cc43cf4e..c7942bae 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -16,14 +16,14 @@ import { testEffect } from "../lib/effect" const it = testEffect(LayerNode.compile(LayerNode.group([Config.node, FSUtil.node]))) const winIt = process.platform === "win32" ? it.instance : it.instance.skip -const globalConfigFiles = ["opencode.json", "opencode.jsonc", "tui.json", "tui.jsonc"].map((file) => +const globalConfigFiles = ["deepcode.json", "deepcode.jsonc", "tui.json", "tui.jsonc"].map((file) => path.join(Global.Path.config, file), ) const cleanState = Effect.gen(function* () { const fs = yield* FSUtil.Service - delete process.env.OPENCODE_CONFIG - delete process.env.OPENCODE_TUI_CONFIG + delete process.env.DEEPCODE_CONFIG + delete process.env.DEEPCODE_TUI_CONFIG yield* Effect.forEach(globalConfigFiles, (file) => fs.remove(file, { force: true }).pipe(Effect.ignore), { discard: true, }) @@ -88,16 +88,16 @@ it.instance("keeps server and tui plugin merge semantics aligned", () => Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance - const local = path.join(test.directory, ".opencode") + const local = path.join(test.directory, ".deepcode") yield* fs.makeDirectory(local, { recursive: true }) - yield* fs.writeJson(path.join(Global.Path.config, "opencode.json"), { + yield* fs.writeJson(path.join(Global.Path.config, "deepcode.json"), { plugin: [["shared-plugin@1.0.0", { source: "global" }], "global-only@1.0.0"], }) yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { plugin: [["shared-plugin@1.0.0", { source: "global" }], "global-only@1.0.0"], }) - yield* fs.writeJson(path.join(local, "opencode.json"), { + yield* fs.writeJson(path.join(local, "deepcode.json"), { plugin: [["shared-plugin@2.0.0", { source: "local" }], "local-only@1.0.0"], }) yield* fs.writeJson(path.join(local, "tui.json"), { @@ -130,7 +130,7 @@ it.instance("loads tui config with the same precedence order as server config pa yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { theme: "global" }) yield* fs.writeJson(path.join(test.directory, "tui.json"), { theme: "project" }) yield* fs.writeWithDirs( - path.join(test.directory, ".opencode", "tui.json"), + path.join(test.directory, ".deepcode", "tui.json"), JSON.stringify({ theme: "local", diff_style: "stacked" }, null, 2), ) @@ -189,12 +189,12 @@ it.instance("resolves attention config defaults and overrides", () => ), ) -it.instance("migrates tui-specific keys from opencode.json when tui.json does not exist", () => +it.instance("migrates tui-specific keys from deepcode.json when tui.json does not exist", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance - const source = path.join(test.directory, "opencode.json") + const source = path.join(test.directory, "deepcode.json") yield* fs.writeJson(source, { theme: "migrated-theme", tui: { scroll_speed: 5 }, @@ -213,7 +213,7 @@ it.instance("migrates tui-specific keys from opencode.json when tui.json does no expect(server.theme).toBeUndefined() expect(server.keybinds).toBeUndefined() expect(server.tui).toBeUndefined() - expect(yield* fs.existsSafe(path.join(test.directory, "opencode.json.tui-migration.bak"))).toBe(true) + expect(yield* fs.existsSafe(path.join(test.directory, "deepcode.json.tui-migration.bak"))).toBe(true) expect(yield* fs.existsSafe(path.join(test.directory, "tui.json"))).toBe(true) }), ), @@ -225,7 +225,7 @@ it.instance("migrates project legacy tui keys even when global tui.json already const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.writeJson(path.join(Global.Path.config, "tui.json"), { theme: "global" }) - yield* fs.writeJson(path.join(test.directory, "opencode.json"), { + yield* fs.writeJson(path.join(test.directory, "deepcode.json"), { theme: "project-migrated", tui: { scroll_speed: 2 }, }) @@ -235,7 +235,7 @@ it.instance("migrates project legacy tui keys even when global tui.json already expect(config.scroll_speed).toBe(2) expect(yield* fs.existsSafe(path.join(test.directory, "tui.json"))).toBe(true) - const server = JSON.parse(yield* fs.readFileString(path.join(test.directory, "opencode.json"))) + const server = JSON.parse(yield* fs.readFileString(path.join(test.directory, "deepcode.json"))) expect(server.theme).toBeUndefined() expect(server.tui).toBeUndefined() }), @@ -247,7 +247,7 @@ it.instance("drops unknown legacy tui keys during migration", () => Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance - yield* fs.writeJson(path.join(test.directory, "opencode.json"), { + yield* fs.writeJson(path.join(test.directory, "deepcode.json"), { theme: "migrated-theme", tui: { scroll_speed: 2, foo: 1 }, }) @@ -263,13 +263,13 @@ it.instance("drops unknown legacy tui keys during migration", () => ), ) -it.instance("skips migration when opencode.jsonc is syntactically invalid", () => +it.instance("skips migration when deepcode.jsonc is syntactically invalid", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.writeFileString( - path.join(test.directory, "opencode.jsonc"), + path.join(test.directory, "deepcode.jsonc"), `{ "theme": "broken-theme", "tui": { "scroll_speed": 2 } @@ -281,8 +281,8 @@ it.instance("skips migration when opencode.jsonc is syntactically invalid", () = expect(config.theme).toBeUndefined() expect(config.scroll_speed).toBeUndefined() expect(yield* fs.existsSafe(path.join(test.directory, "tui.json"))).toBe(false) - expect(yield* fs.existsSafe(path.join(test.directory, "opencode.jsonc.tui-migration.bak"))).toBe(false) - const source = yield* fs.readFileString(path.join(test.directory, "opencode.jsonc")) + expect(yield* fs.existsSafe(path.join(test.directory, "deepcode.jsonc.tui-migration.bak"))).toBe(false) + const source = yield* fs.readFileString(path.join(test.directory, "deepcode.jsonc")) expect(source).toContain('"theme": "broken-theme"') expect(source).toContain('"tui": { "scroll_speed": 2 }') }), @@ -294,16 +294,16 @@ it.instance("skips migration when tui.json already exists", () => Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance - yield* fs.writeJson(path.join(test.directory, "opencode.json"), { theme: "legacy" }) + yield* fs.writeJson(path.join(test.directory, "deepcode.json"), { theme: "legacy" }) yield* fs.writeJson(path.join(test.directory, "tui.json"), { diff_style: "stacked" }) const config = yield* getTuiConfig(test.directory) expect(config.diff_style).toBe("stacked") expect(config.theme).toBeUndefined() - const server = JSON.parse(yield* fs.readFileString(path.join(test.directory, "opencode.json"))) + const server = JSON.parse(yield* fs.readFileString(path.join(test.directory, "deepcode.json"))) expect(server.theme).toBe("legacy") - expect(yield* fs.existsSafe(path.join(test.directory, "opencode.json.tui-migration.bak"))).toBe(false) + expect(yield* fs.existsSafe(path.join(test.directory, "deepcode.json.tui-migration.bak"))).toBe(false) }), ), ) @@ -313,7 +313,7 @@ it.instance("continues loading tui config when legacy source cannot be stripped" Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance - const source = path.join(test.directory, "opencode.json") + const source = path.join(test.directory, "deepcode.json") yield* fs.writeJson(source, { theme: "readonly-theme" }) yield* Effect.acquireUseRelease( @@ -339,7 +339,7 @@ it.instance("migration backup preserves JSONC comments", () => const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.writeFileString( - path.join(test.directory, "opencode.jsonc"), + path.join(test.directory, "deepcode.jsonc"), `{ // top-level comment "theme": "jsonc-theme", @@ -351,7 +351,7 @@ it.instance("migration backup preserves JSONC comments", () => ) yield* getTuiConfig(test.directory) - const backup = yield* fs.readFileString(path.join(test.directory, "opencode.jsonc.tui-migration.bak")) + const backup = yield* fs.readFileString(path.join(test.directory, "deepcode.jsonc.tui-migration.bak")) expect(backup).toContain("// top-level comment") expect(backup).toContain("// nested comment") expect(backup).toContain('"theme": "jsonc-theme"') @@ -360,15 +360,15 @@ it.instance("migration backup preserves JSONC comments", () => ), ) -it.instance("migrates legacy tui keys across multiple opencode.json levels", () => +it.instance("migrates legacy tui keys across multiple deepcode.json levels", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance const nested = path.join(test.directory, "apps", "client") yield* fs.makeDirectory(nested, { recursive: true }) - yield* fs.writeJson(path.join(test.directory, "opencode.json"), { theme: "root-theme" }) - yield* fs.writeJson(path.join(nested, "opencode.json"), { theme: "nested-theme" }) + yield* fs.writeJson(path.join(test.directory, "deepcode.json"), { theme: "root-theme" }) + yield* fs.writeJson(path.join(nested, "deepcode.json"), { theme: "nested-theme" }) const config = yield* getTuiConfig(nested) expect(config.theme).toBe("nested-theme") @@ -413,7 +413,7 @@ it.instance("top-level keys in tui.json take precedence over nested tui key", () ), ) -it.instance("project config takes precedence over OPENCODE_TUI_CONFIG (matches OPENCODE_CONFIG)", () => +it.instance("project config takes precedence over DEEPCODE_TUI_CONFIG (matches DEEPCODE_CONFIG)", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -423,7 +423,7 @@ it.instance("project config takes precedence over OPENCODE_TUI_CONFIG (matches O yield* fs.writeJson(custom, { theme: "custom", diff_style: "stacked" }) yield* withEnv( - "OPENCODE_TUI_CONFIG", + "DEEPCODE_TUI_CONFIG", custom, Effect.gen(function* () { const config = yield* getTuiConfig(test.directory) @@ -638,7 +638,7 @@ it.instance("keeps explicit configured keybind input undo on Windows", () => ), ) -it.instance("OPENCODE_TUI_CONFIG provides settings when no project config exists", () => +it.instance("DEEPCODE_TUI_CONFIG provides settings when no project config exists", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -647,7 +647,7 @@ it.instance("OPENCODE_TUI_CONFIG provides settings when no project config exists yield* fs.writeJson(custom, { theme: "from-env", diff_style: "stacked" }) yield* withEnv( - "OPENCODE_TUI_CONFIG", + "DEEPCODE_TUI_CONFIG", custom, Effect.gen(function* () { const config = yield* getTuiConfig(test.directory) @@ -659,19 +659,19 @@ it.instance("OPENCODE_TUI_CONFIG provides settings when no project config exists ), ) -it.instance("does not derive tui path from OPENCODE_CONFIG", () => +it.instance("does not derive tui path from DEEPCODE_CONFIG", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance const customDir = path.join(test.directory, "custom") yield* fs.makeDirectory(customDir, { recursive: true }) - yield* fs.writeJson(path.join(customDir, "opencode.json"), { model: "test/model" }) + yield* fs.writeJson(path.join(customDir, "deepcode.json"), { model: "test/model" }) yield* fs.writeJson(path.join(customDir, "tui.json"), { theme: "should-not-load" }) yield* withEnv( - "OPENCODE_CONFIG", - path.join(customDir, "opencode.json"), + "DEEPCODE_CONFIG", + path.join(customDir, "deepcode.json"), Effect.gen(function* () { const config = yield* getTuiConfig(test.directory) expect(config.theme).toBeUndefined() @@ -723,13 +723,13 @@ it.instance("applies file substitutions when first identical token is in a comme ), ) -it.instance("loads .opencode/tui.json", () => +it.instance("loads .deepcode/tui.json", () => withCleanState( Effect.gen(function* () { const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.writeWithDirs( - path.join(test.directory, ".opencode", "tui.json"), + path.join(test.directory, ".deepcode", "tui.json"), JSON.stringify({ diff_style: "stacked" }, null, 2), ) @@ -860,7 +860,7 @@ it.instance("silently skips malformed tui.json - load failures degrade to {}", ( const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.writeFileString(path.join(test.directory, "tui.json"), '{ "theme": "broken",') - yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" })) + yield* fs.writeWithDirs(path.join(test.directory, ".deepcode", "tui.json"), JSON.stringify({ theme: "fallback" })) const config = yield* getTuiConfig(test.directory) expect(config.theme).toBe("fallback") @@ -874,7 +874,7 @@ it.instance("silently skips non-ENOENT read failures (e.g. tui.json is a directo const fs = yield* FSUtil.Service const test = yield* TestInstance yield* fs.makeDirectory(path.join(test.directory, "tui.json"), { recursive: true }) - yield* fs.writeWithDirs(path.join(test.directory, ".opencode", "tui.json"), JSON.stringify({ theme: "fallback" })) + yield* fs.writeWithDirs(path.join(test.directory, ".deepcode", "tui.json"), JSON.stringify({ theme: "fallback" })) const config = yield* getTuiConfig(test.directory) expect(config.theme).toBe("fallback") diff --git a/packages/opencode/test/control-plane/workspace.test.ts b/packages/opencode/test/control-plane/workspace.test.ts index a0d3aadb..92a7711c 100644 --- a/packages/opencode/test/control-plane/workspace.test.ts +++ b/packages/opencode/test/control-plane/workspace.test.ts @@ -33,8 +33,8 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { LayerNode } from "@opencode-ai/core/effect/layer-node" const originalEnv = { - OPENCODE_AUTH_CONTENT: process.env.OPENCODE_AUTH_CONTENT, - OPENCODE_EXPERIMENTAL_WORKSPACES: process.env.OPENCODE_EXPERIMENTAL_WORKSPACES, + DEEPCODE_AUTH_CONTENT: process.env.DEEPCODE_AUTH_CONTENT, + DEEPCODE_EXPERIMENTAL_WORKSPACES: process.env.DEEPCODE_EXPERIMENTAL_WORKSPACES, OTEL_EXPORTER_OTLP_HEADERS: process.env.OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_ENDPOINT: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_RESOURCE_ATTRIBUTES: process.env.OTEL_RESOURCE_ATTRIBUTES, @@ -106,7 +106,7 @@ function restoreEnv() { beforeEach(() => { restoreEnv() - process.env.OPENCODE_EXPERIMENTAL_WORKSPACES = "true" + process.env.DEEPCODE_EXPERIMENTAL_WORKSPACES = "true" }) afterEach(async () => { @@ -423,7 +423,7 @@ describe("workspace CRUD", () => { Effect.gen(function* () { const instance = yield* requireInstance const workspace = yield* Workspace.Service - process.env.OPENCODE_AUTH_CONTENT = JSON.stringify({ test: { type: "api", key: "secret" } }) + process.env.DEEPCODE_AUTH_CONTENT = JSON.stringify({ test: { type: "api", key: "secret" } }) process.env.OTEL_EXPORTER_OTLP_HEADERS = "authorization=otel" process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://otel.test" process.env.OTEL_RESOURCE_ATTRIBUTES = "service.name=opencode-test" @@ -482,11 +482,11 @@ describe("workspace CRUD", () => { extra: { configured: true }, projectID: instance.project.id, }) - expect(JSON.parse(recorded.calls.create[0].env.OPENCODE_AUTH_CONTENT ?? "{}")).toEqual({ + expect(JSON.parse(recorded.calls.create[0].env.DEEPCODE_AUTH_CONTENT ?? "{}")).toEqual({ test: { type: "api", key: "secret" }, }) - expect(recorded.calls.create[0].env.OPENCODE_WORKSPACE_ID).toBe(workspaceID) - expect(recorded.calls.create[0].env.OPENCODE_EXPERIMENTAL_WORKSPACES).toBe("true") + expect(recorded.calls.create[0].env.DEEPCODE_WORKSPACE_ID).toBe(workspaceID) + expect(recorded.calls.create[0].env.DEEPCODE_EXPERIMENTAL_WORKSPACES).toBe("true") expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_HEADERS).toBe("authorization=otel") expect(recorded.calls.create[0].env.OTEL_EXPORTER_OTLP_ENDPOINT).toBe("https://otel.test") expect(recorded.calls.create[0].env.OTEL_RESOURCE_ATTRIBUTES).toBe("service.name=opencode-test") diff --git a/packages/opencode/test/effect/runtime-flags-deepcode.test.ts b/packages/opencode/test/effect/runtime-flags-deepcode.test.ts new file mode 100644 index 00000000..0a71a1b7 --- /dev/null +++ b/packages/opencode/test/effect/runtime-flags-deepcode.test.ts @@ -0,0 +1,80 @@ +import { describe, expect } from "bun:test" +import { ConfigProvider, Effect, Layer } from "effect" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { RuntimeFlags } from "../../src/effect/runtime-flags" +import { it } from "../lib/effect" + +const fromConfig = (input: Record) => + AppNodeBuilder.build(RuntimeFlags.node).pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input)))) + +const readFlags = RuntimeFlags.Service.useSync((flags) => flags) + +describe("DeepCode RuntimeFlags namespace", () => { + it.effect("reads DEEPCODE runtime variables", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide( + fromConfig({ + DEEPCODE_PURE: "true", + DEEPCODE_AUTO_SHARE: "true", + DEEPCODE_DISABLE_DEFAULT_PLUGINS: "true", + DEEPCODE_EXPERIMENTAL: "true", + DEEPCODE_EXPERIMENTAL_ICON_DISCOVERY: "false", + DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "4096", + DEEPCODE_CLIENT: "desktop", + }), + ), + ) + + expect(flags.pure).toBe(true) + expect(flags.autoShare).toBe(true) + expect(flags.disableDefaultPlugins).toBe(true) + expect(flags.experimentalReferences).toBe(true) + expect(flags.experimentalIconDiscovery).toBe(false) + expect(flags.outputTokenMax).toBe(4096) + expect(flags.client).toBe("desktop") + }), + ) + + it.effect("ignores OPENCODE runtime variables", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide( + fromConfig({ + OPENCODE_PURE: "true", + OPENCODE_AUTO_SHARE: "true", + OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", + OPENCODE_EXPERIMENTAL: "true", + OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "4096", + OPENCODE_CLIENT: "desktop", + }), + ), + ) + + expect(flags.pure).toBe(false) + expect(flags.autoShare).toBe(false) + expect(flags.disableDefaultPlugins).toBe(false) + expect(flags.experimentalReferences).toBe(false) + expect(flags.outputTokenMax).toBeUndefined() + expect(flags.client).toBe("cli") + }), + ) + + it.effect("prefers DEEPCODE values when both namespaces are present", () => + Effect.gen(function* () { + const flags = yield* readFlags.pipe( + Effect.provide( + fromConfig({ + OPENCODE_PURE: "true", + OPENCODE_CLIENT: "opencode-client", + DEEPCODE_PURE: "false", + DEEPCODE_CLIENT: "deepcode-client", + }), + ), + ) + + expect(flags.pure).toBe(false) + expect(flags.client).toBe("deepcode-client") + }), + ) +}) diff --git a/packages/opencode/test/effect/runtime-flags.test.ts b/packages/opencode/test/effect/runtime-flags.test.ts index ca5ca1d7..2915db58 100644 --- a/packages/opencode/test/effect/runtime-flags.test.ts +++ b/packages/opencode/test/effect/runtime-flags.test.ts @@ -23,18 +23,18 @@ describe("RuntimeFlags", () => { const flags = yield* readFlags.pipe( Effect.provide( fromConfig({ - OPENCODE_PURE: "true", - OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", - OPENCODE_AUTO_SHARE: "true", - OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true", - OPENCODE_DISABLE_EXTERNAL_SKILLS: "true", - OPENCODE_DISABLE_LSP_DOWNLOAD: "true", - OPENCODE_EXPERIMENTAL: "true", - OPENCODE_ENABLE_EXA: "true", - OPENCODE_ENABLE_PARALLEL: "true", - OPENCODE_ENABLE_EXPERIMENTAL_MODELS: "true", - OPENCODE_ENABLE_QUESTION_TOOL: "true", - OPENCODE_CLIENT: "desktop", + DEEPCODE_PURE: "true", + DEEPCODE_DISABLE_DEFAULT_PLUGINS: "true", + DEEPCODE_AUTO_SHARE: "true", + DEEPCODE_DISABLE_EMBEDDED_WEB_UI: "true", + DEEPCODE_DISABLE_EXTERNAL_SKILLS: "true", + DEEPCODE_DISABLE_LSP_DOWNLOAD: "true", + DEEPCODE_EXPERIMENTAL: "true", + DEEPCODE_ENABLE_EXA: "true", + DEEPCODE_ENABLE_PARALLEL: "true", + DEEPCODE_ENABLE_EXPERIMENTAL_MODELS: "true", + DEEPCODE_ENABLE_QUESTION_TOOL: "true", + DEEPCODE_CLIENT: "desktop", }), ), ) @@ -65,12 +65,12 @@ describe("RuntimeFlags", () => { }), ) - it.effect("layer parses OPENCODE_EXPERIMENTAL_LSP_TY", () => + it.effect("layer parses DEEPCODE_EXPERIMENTAL_LSP_TY", () => Effect.gen(function* () { const flags = yield* readFlags.pipe( Effect.provide( fromConfig({ - OPENCODE_EXPERIMENTAL_LSP_TY: "true", + DEEPCODE_EXPERIMENTAL_LSP_TY: "true", }), ), ) @@ -81,8 +81,8 @@ describe("RuntimeFlags", () => { it.effect("enables native LLM via dedicated flag only", () => Effect.gen(function* () { - const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_NATIVE_LLM: "true" }))) - const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" }))) + const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL_NATIVE_LLM: "true" }))) + const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL: "true" }))) expect(explicit.experimentalNativeLlm).toBe(true) expect(umbrella.experimentalNativeLlm).toBe(false) @@ -91,8 +91,8 @@ describe("RuntimeFlags", () => { it.effect("enables WebSockets via dedicated flag only", () => Effect.gen(function* () { - const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_WEBSOCKETS: "true" }))) - const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" }))) + const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL_WEBSOCKETS: "true" }))) + const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL: "true" }))) expect(explicit.experimentalWebSockets).toBe(true) expect(umbrella.experimentalWebSockets).toBe(false) @@ -139,9 +139,9 @@ describe("RuntimeFlags", () => { }), ) - it.effect("disableExternalSkills reads OPENCODE_DISABLE_EXTERNAL_SKILLS", () => + it.effect("disableExternalSkills reads DEEPCODE_DISABLE_EXTERNAL_SKILLS", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_EXTERNAL_SKILLS: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_EXTERNAL_SKILLS: "true" }))) expect(flags.disableExternalSkills).toBe(true) }), @@ -155,9 +155,9 @@ describe("RuntimeFlags", () => { }), ) - it.effect("disableLspDownload reads OPENCODE_DISABLE_LSP_DOWNLOAD", () => + it.effect("disableLspDownload reads DEEPCODE_DISABLE_LSP_DOWNLOAD", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_LSP_DOWNLOAD: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_LSP_DOWNLOAD: "true" }))) expect(flags.disableLspDownload).toBe(true) }), @@ -171,45 +171,45 @@ describe("RuntimeFlags", () => { }), ) - it.effect("disableClaudeCodePrompt reads OPENCODE_DISABLE_CLAUDE_CODE_PROMPT", () => + it.effect("disableClaudeCodePrompt reads DEEPCODE_DISABLE_CLAUDE_CODE_PROMPT", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE_PROMPT: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_CLAUDE_CODE_PROMPT: "true" }))) expect(flags.disableClaudeCodePrompt).toBe(true) }), ) - it.effect("disableClaudeCodePrompt inherits OPENCODE_DISABLE_CLAUDE_CODE", () => + it.effect("disableClaudeCodePrompt inherits DEEPCODE_DISABLE_CLAUDE_CODE", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_CLAUDE_CODE: "true" }))) expect(flags.disableClaudeCodePrompt).toBe(true) }), ) - it.effect("experimentalIconDiscovery reads OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", () => + it.effect("experimentalIconDiscovery reads DEEPCODE_EXPERIMENTAL_ICON_DISCOVERY", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL_ICON_DISCOVERY: "true" }))) expect(flags.experimentalIconDiscovery).toBe(true) }), ) - it.effect("experimentalIconDiscovery inherits OPENCODE_EXPERIMENTAL", () => + it.effect("experimentalIconDiscovery inherits DEEPCODE_EXPERIMENTAL", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_EXPERIMENTAL: "true" }))) expect(flags.experimentalIconDiscovery).toBe(true) }), ) - it.effect("specific experimental flags override OPENCODE_EXPERIMENTAL", () => + it.effect("specific experimental flags override DEEPCODE_EXPERIMENTAL", () => Effect.gen(function* () { const flags = yield* readFlags.pipe( Effect.provide( fromConfig({ - OPENCODE_EXPERIMENTAL: "true", - OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "false", + DEEPCODE_EXPERIMENTAL: "true", + DEEPCODE_EXPERIMENTAL_ICON_DISCOVERY: "false", }), ), ) @@ -226,12 +226,12 @@ describe("RuntimeFlags", () => { }), ) - it.effect("experimentalOxfmt is enabled by OPENCODE_EXPERIMENTAL_OXFMT", () => + it.effect("experimentalOxfmt is enabled by DEEPCODE_EXPERIMENTAL_OXFMT", () => Effect.gen(function* () { const flags = yield* readFlags.pipe( Effect.provide( fromConfig({ - OPENCODE_EXPERIMENTAL_OXFMT: "true", + DEEPCODE_EXPERIMENTAL_OXFMT: "true", }), ), ) @@ -240,12 +240,12 @@ describe("RuntimeFlags", () => { }), ) - it.effect("experimentalOxfmt inherits OPENCODE_EXPERIMENTAL", () => + it.effect("experimentalOxfmt inherits DEEPCODE_EXPERIMENTAL", () => Effect.gen(function* () { const flags = yield* readFlags.pipe( Effect.provide( fromConfig({ - OPENCODE_EXPERIMENTAL: "true", + DEEPCODE_EXPERIMENTAL: "true", }), ), ) @@ -258,19 +258,19 @@ describe("RuntimeFlags", () => { { name: "absent", config: {}, expected: undefined }, { name: "valid positive integer", - config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234" }, + config: { DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234" }, expected: 1234, }, { name: "invalid string", - config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "nope" }, + config: { DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "nope" }, expected: undefined, }, - { name: "zero", config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "0" }, expected: undefined }, - { name: "negative", config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "-1" }, expected: undefined }, + { name: "zero", config: { DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "0" }, expected: undefined }, + { name: "negative", config: { DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "-1" }, expected: undefined }, { name: "non-integer", - config: { OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1.5" }, + config: { DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1.5" }, expected: undefined, }, ]) { @@ -287,19 +287,19 @@ describe("RuntimeFlags", () => { { name: "absent", config: {}, expected: undefined }, { name: "valid positive integer", - config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1234" }, + config: { DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1234" }, expected: 1234, }, { name: "invalid string", - config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "nope" }, + config: { DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "nope" }, expected: undefined, }, - { name: "zero", config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "0" }, expected: undefined }, - { name: "negative", config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "-1" }, expected: undefined }, + { name: "zero", config: { DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "0" }, expected: undefined }, + { name: "negative", config: { DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "-1" }, expected: undefined }, { name: "non-integer", - config: { OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1.5" }, + config: { DEEPCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: "1.5" }, expected: undefined, }, ]) { @@ -319,14 +319,14 @@ describe("RuntimeFlags", () => { Effect.provide( ConfigProvider.layer( ConfigProvider.fromUnknown({ - OPENCODE_PURE: "true", - OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", - OPENCODE_DISABLE_EXTERNAL_SKILLS: "true", - OPENCODE_DISABLE_LSP_DOWNLOAD: "true", - OPENCODE_EXPERIMENTAL: "true", - OPENCODE_ENABLE_EXA: "true", - OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234", - OPENCODE_CLIENT: "desktop", + DEEPCODE_PURE: "true", + DEEPCODE_DISABLE_DEFAULT_PLUGINS: "true", + DEEPCODE_DISABLE_EXTERNAL_SKILLS: "true", + DEEPCODE_DISABLE_LSP_DOWNLOAD: "true", + DEEPCODE_EXPERIMENTAL: "true", + DEEPCODE_ENABLE_EXA: "true", + DEEPCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS: "1234", + DEEPCODE_CLIENT: "desktop", }), ), ), @@ -356,17 +356,17 @@ describe("RuntimeFlags", () => { }), ) - it.effect("disableClaudeCodeSkills reads OPENCODE_DISABLE_CLAUDE_CODE_SKILLS", () => + it.effect("disableClaudeCodeSkills reads DEEPCODE_DISABLE_CLAUDE_CODE_SKILLS", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_CLAUDE_CODE_SKILLS: "true" }))) expect(flags.disableClaudeCodeSkills).toBe(true) }), ) - it.effect("disableClaudeCodeSkills inherits OPENCODE_DISABLE_CLAUDE_CODE", () => + it.effect("disableClaudeCodeSkills inherits DEEPCODE_DISABLE_CLAUDE_CODE", () => Effect.gen(function* () { - const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_DISABLE_CLAUDE_CODE: "true" }))) + const flags = yield* readFlags.pipe(Effect.provide(fromConfig({ DEEPCODE_DISABLE_CLAUDE_CODE: "true" }))) expect(flags.disableClaudeCodeSkills).toBe(true) }), diff --git a/packages/opencode/test/fixture/plugin-meta-worker.ts b/packages/opencode/test/fixture/plugin-meta-worker.ts index c02b448a..cd0fcc6f 100644 --- a/packages/opencode/test/fixture/plugin-meta-worker.ts +++ b/packages/opencode/test/fixture/plugin-meta-worker.ts @@ -12,7 +12,7 @@ if (typeof msg.file !== "string" || typeof msg.spec !== "string" || typeof msg.t } if (typeof msg.id !== "string") throw new Error("Invalid worker payload") -process.env.OPENCODE_PLUGIN_META_FILE = msg.file +process.env.DEEPCODE_PLUGIN_META_FILE = msg.file const { PluginMeta } = await import("../../src/plugin/meta") diff --git a/packages/opencode/test/fixture/tui-runtime.ts b/packages/opencode/test/fixture/tui-runtime.ts index 4ff9bbb9..374f9f7d 100644 --- a/packages/opencode/test/fixture/tui-runtime.ts +++ b/packages/opencode/test/fixture/tui-runtime.ts @@ -30,7 +30,7 @@ export function createTuiResolvedConfig(input: ResolvedInput = {}): HostResolved } export function mockTuiRuntime(dir: string, plugin: PluginSpec[], opts?: { plugin_enabled?: Record }) { - process.env.OPENCODE_PLUGIN_META_FILE = path.join(dir, "plugin-meta.json") + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(dir, "plugin-meta.json") const plugin_origins = plugin.map((spec) => ({ spec, scope: "local" as const, @@ -50,7 +50,7 @@ export function mockTuiRuntime(dir: string, plugin: PluginSpec[], opts?: { plugi restore: () => { cwd.mockRestore() wait.mockRestore() - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE }, } } diff --git a/packages/opencode/test/ide/ide.test.ts b/packages/opencode/test/ide/ide.test.ts index e10700e8..21557647 100644 --- a/packages/opencode/test/ide/ide.test.ts +++ b/packages/opencode/test/ide/ide.test.ts @@ -62,20 +62,20 @@ describe("ide", () => { expect(Ide.ide()).toBe("unknown") }) - test("should recognize vscode-insiders OPENCODE_CALLER", () => { - process.env["OPENCODE_CALLER"] = "vscode-insiders" + test("should recognize vscode-insiders DEEPCODE_CALLER", () => { + process.env["DEEPCODE_CALLER"] = "vscode-insiders" expect(Ide.alreadyInstalled()).toBe(true) }) - test("should recognize vscode OPENCODE_CALLER", () => { - process.env["OPENCODE_CALLER"] = "vscode" + test("should recognize vscode DEEPCODE_CALLER", () => { + process.env["DEEPCODE_CALLER"] = "vscode" expect(Ide.alreadyInstalled()).toBe(true) }) - test("should return false for unknown OPENCODE_CALLER", () => { - process.env["OPENCODE_CALLER"] = "unknown" + test("should return false for unknown DEEPCODE_CALLER", () => { + process.env["DEEPCODE_CALLER"] = "unknown" expect(Ide.alreadyInstalled()).toBe(false) }) diff --git a/packages/opencode/test/lib/cli-process.ts b/packages/opencode/test/lib/cli-process.ts index 12e8d9c8..b20b9cae 100644 --- a/packages/opencode/test/lib/cli-process.ts +++ b/packages/opencode/test/lib/cli-process.ts @@ -6,11 +6,11 @@ // the original /event race or #27371's invalid-model hang). // // Configuration flows through opencode's built-in test affordances: -// - OPENCODE_CONFIG_CONTENT : provider config inline, no files to find -// - OPENCODE_TEST_HOME : pins os.homedir() → tmpdir -// - OPENCODE_DISABLE_PROJECT_CONFIG : skip walking up for opencode.json -// - OPENCODE_PURE : skip external plugin discovery + install -// - OPENCODE_DISABLE_AUTOUPDATE / AUTOCOMPACT / MODELS_FETCH : no background work +// - DEEPCODE_CONFIG_CONTENT : provider config inline, no files to find +// - DEEPCODE_TEST_HOME : pins os.homedir() → tmpdir +// - DEEPCODE_DISABLE_PROJECT_CONFIG : skip walking up for opencode.json +// - DEEPCODE_PURE : skip external plugin discovery + install +// - DEEPCODE_DISABLE_AUTOUPDATE / AUTOCOMPACT / MODELS_FETCH : no background work // Plus HOME / XDG_* pointing at the tmpdir for belt-and-suspenders isolation. // // Today only `opencode.run` is fully wired. The shape supports adding more @@ -61,19 +61,19 @@ function forkStderrDrain(stream: ReadableStream, into: string[]) { function isolatedEnv(home: string, configJson: string): Record { return { - OPENCODE_TEST_HOME: home, + DEEPCODE_TEST_HOME: home, HOME: home, XDG_CONFIG_HOME: path.join(home, ".config"), XDG_DATA_HOME: path.join(home, ".local/share"), XDG_STATE_HOME: path.join(home, ".local/state"), XDG_CACHE_HOME: path.join(home, ".cache"), - OPENCODE_CONFIG_CONTENT: configJson, - OPENCODE_DISABLE_PROJECT_CONFIG: "1", - OPENCODE_PURE: "1", - OPENCODE_DISABLE_AUTOUPDATE: "1", - OPENCODE_DISABLE_AUTOCOMPACT: "1", - OPENCODE_DISABLE_MODELS_FETCH: "1", - OPENCODE_AUTH_CONTENT: "{}", + DEEPCODE_CONFIG_CONTENT: configJson, + DEEPCODE_DISABLE_PROJECT_CONFIG: "1", + DEEPCODE_PURE: "1", + DEEPCODE_DISABLE_AUTOUPDATE: "1", + DEEPCODE_DISABLE_AUTOCOMPACT: "1", + DEEPCODE_DISABLE_MODELS_FETCH: "1", + DEEPCODE_AUTH_CONTENT: "{}", } } @@ -266,7 +266,7 @@ export function withCliFixture( ...opts, env: { ...opts.env, - OPENCODE_CONFIG_CONTENT: JSON.stringify({ + DEEPCODE_CONFIG_CONTENT: JSON.stringify({ ...testProviderConfig(llm.url), permission: opts.permission, }), diff --git a/packages/opencode/test/plugin/install-concurrency.test.ts b/packages/opencode/test/plugin/install-concurrency.test.ts index 6160bd6e..1463741b 100644 --- a/packages/opencode/test/plugin/install-concurrency.test.ts +++ b/packages/opencode/test/plugin/install-concurrency.test.ts @@ -82,7 +82,7 @@ describe("plugin.install.concurrent", () => { expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0)) expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([]) - const cfg = await read(path.join(tmp.path, ".opencode", "opencode.jsonc")) + const cfg = await read(path.join(tmp.path, ".deepcode", "deepcode.jsonc")) expectPlugins(cfg.plugin, all) }, 25_000) @@ -105,8 +105,8 @@ describe("plugin.install.concurrent", () => { expect(out.map((x) => x.code)).toEqual(Array.from({ length: all.length }, () => 0)) expect(out.map((x) => x.stderr.toString()).filter(Boolean)).toEqual([]) - const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc")) - const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc")) + const server = await read(path.join(tmp.path, ".deepcode", "deepcode.jsonc")) + const tui = await read(path.join(tmp.path, ".deepcode", "tui.jsonc")) expectPlugins(server.plugin, all) expectPlugins(tui.plugin, all) }, 25_000) @@ -114,7 +114,7 @@ describe("plugin.install.concurrent", () => { test("preserves updates when existing config uses .json", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.json") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.json") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write(cfg, JSON.stringify({ plugin: ["seed@1.0.0"] }, null, 2)) @@ -135,6 +135,6 @@ describe("plugin.install.concurrent", () => { const json = await read(cfg) expectPlugins(json.plugin, ["seed@1.0.0", ...next]) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }, 25_000) }) diff --git a/packages/opencode/test/plugin/install.test.ts b/packages/opencode/test/plugin/install.test.ts index 6dc9175b..d0bc67d1 100644 --- a/packages/opencode/test/plugin/install.test.ts +++ b/packages/opencode/test/plugin/install.test.ts @@ -122,8 +122,8 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc")) - const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc")) + const server = await read(path.join(tmp.path, ".deepcode", "deepcode.jsonc")) + const tui = await read(path.join(tmp.path, ".deepcode", "tui.jsonc")) expect(server.plugin).toEqual(["acme@1.2.3"]) expect(tui.plugin).toEqual(["acme@1.2.3"]) }) @@ -144,8 +144,8 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc")) - const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc")) + const server = await read(path.join(tmp.path, ".deepcode", "deepcode.jsonc")) + const tui = await read(path.join(tmp.path, ".deepcode", "tui.jsonc")) expect(server.plugin).toEqual([["acme@1.2.3", { custom: true, other: false }]]) expect(tui.plugin).toEqual([["acme@1.2.3", { compact: true }]]) }) @@ -153,8 +153,8 @@ describe("plugin.install.task", () => { test("preserves JSONC comments when adding plugins to server and tui config", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server", "tui"]) - const cfg = path.join(tmp.path, ".opencode") - const server = path.join(cfg, "opencode.jsonc") + const cfg = path.join(tmp.path, ".deepcode") + const server = path.join(cfg, "deepcode.jsonc") const tui = path.join(cfg, "tui.jsonc") await fs.mkdir(cfg, { recursive: true }) await Bun.write( @@ -212,7 +212,7 @@ describe("plugin.install.task", () => { test("preserves JSONC comments when force replacing plugin version", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.jsonc") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.jsonc") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write( cfg, @@ -257,14 +257,14 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - const server = await read(path.join(tmp.path, ".opencode", "opencode.jsonc")) + const server = await read(path.join(tmp.path, ".deepcode", "deepcode.jsonc")) expect(server.plugin).toEqual(["acme@1.2.3"]) }) test("does not change configured package version without force", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.json") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.json") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write(cfg, JSON.stringify({ plugin: ["acme@1.0.0"] }, null, 2)) @@ -284,7 +284,7 @@ describe("plugin.install.task", () => { test("does not change scoped package version without force", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.json") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.json") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write(cfg, JSON.stringify({ plugin: ["@scope/acme@1.0.0"] }, null, 2)) @@ -304,7 +304,7 @@ describe("plugin.install.task", () => { test("keeps file plugin entries and still adds npm plugin", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.json") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.json") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write(cfg, JSON.stringify({ plugin: ["file:///tmp/acme.ts"] }, null, 2)) @@ -324,7 +324,7 @@ describe("plugin.install.task", () => { test("force replaces configured package version and keeps tuple options", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.json") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.json") await fs.mkdir(path.dirname(cfg), { recursive: true }) await Bun.write( cfg, @@ -366,8 +366,8 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(global, "opencode.jsonc"))).toBe(true) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(global, "deepcode.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }) test("writes local scope under directory when vcs is not git", async () => { @@ -386,8 +386,8 @@ describe("plugin.install.task", () => { const ok = await run(ctxDir(directory, worktree)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(directory, ".opencode", "opencode.jsonc"))).toBe(true) - expect(await Filesystem.exists(path.join(worktree, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(directory, ".deepcode", "deepcode.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(worktree, ".deepcode", "deepcode.jsonc"))).toBe(false) }) test("writes local scope under directory when worktree is root slash", async () => { @@ -404,7 +404,7 @@ describe("plugin.install.task", () => { const ok = await run(ctxRoot(directory)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(directory, ".opencode", "opencode.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(directory, ".deepcode", "deepcode.jsonc"))).toBe(true) }) test("writes tui local scope under directory when worktree is root slash", async () => { @@ -421,7 +421,7 @@ describe("plugin.install.task", () => { const ok = await run(ctxRoot(directory)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(directory, ".opencode", "tui.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(directory, ".deepcode", "tui.jsonc"))).toBe(true) }) test("writes only tui config for tui-only plugins", async () => { @@ -436,8 +436,8 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(true) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "tui.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }) test("writes tui config for oc-themes-only packages", async () => { @@ -454,10 +454,10 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(true) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(true) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "tui.jsonc"))).toBe(true) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) - const tui = await read(path.join(tmp.path, ".opencode", "tui.jsonc")) + const tui = await read(path.join(tmp.path, ".deepcode", "tui.jsonc")) expect(tui.plugin).toEqual(["acme@1.2.3"]) }) @@ -473,15 +473,15 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "tui.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }) test("force replaces version in both server and tui configs", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server", "tui"]) - const server = path.join(tmp.path, ".opencode", "opencode.json") - const tui = path.join(tmp.path, ".opencode", "tui.json") + const server = path.join(tmp.path, ".deepcode", "deepcode.json") + const tui = path.join(tmp.path, ".deepcode", "tui.json") await fs.mkdir(path.dirname(server), { recursive: true }) await Bun.write(server, JSON.stringify({ plugin: ["acme@1.0.0", "other@1.0.0"] }, null, 2)) await Bun.write(tui, JSON.stringify({ plugin: [["acme@1.0.0", { mode: "safe" }], "other@1.0.0"] }, null, 2)) @@ -505,7 +505,7 @@ describe("plugin.install.task", () => { test("returns false and keeps config unchanged for invalid JSONC", async () => { await using tmp = await tmpdir() const target = await plugin(tmp.path, ["server"]) - const cfg = path.join(tmp.path, ".opencode", "opencode.jsonc") + const cfg = path.join(tmp.path, ".deepcode", "deepcode.jsonc") await fs.mkdir(path.dirname(cfg), { recursive: true }) const bad = '{"plugin": ["acme@1.0.0",}' await Bun.write(cfg, bad) @@ -534,8 +534,8 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "tui.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "tui.jsonc"))).toBe(false) }) test("returns false when manifest cannot be read", async () => { @@ -551,7 +551,7 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }) test("returns false when install fails", async () => { @@ -565,6 +565,6 @@ describe("plugin.install.task", () => { const ok = await run(ctx(tmp.path)) expect(ok).toBe(false) - expect(await Filesystem.exists(path.join(tmp.path, ".opencode", "opencode.jsonc"))).toBe(false) + expect(await Filesystem.exists(path.join(tmp.path, ".deepcode", "deepcode.jsonc"))).toBe(false) }) }) diff --git a/packages/opencode/test/plugin/meta.test.ts b/packages/opencode/test/plugin/meta.test.ts index d48c22c9..8678607a 100644 --- a/packages/opencode/test/plugin/meta.test.ts +++ b/packages/opencode/test/plugin/meta.test.ts @@ -23,7 +23,7 @@ async function map(file: string): Promise> { } afterEach(() => { - delete process.env.OPENCODE_PLUGIN_META_FILE + delete process.env.DEEPCODE_PLUGIN_META_FILE }) describe("plugin.meta", () => { @@ -36,8 +36,8 @@ describe("plugin.meta", () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") - const file = process.env.OPENCODE_PLUGIN_META_FILE! + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") + const file = process.env.DEEPCODE_PLUGIN_META_FILE! const spec = pathToFileURL(tmp.extra.file).href const one = await PluginMeta.touch(spec, spec, "demo.file") @@ -77,8 +77,8 @@ describe("plugin.meta", () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") - const file = process.env.OPENCODE_PLUGIN_META_FILE! + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") + const file = process.env.DEEPCODE_PLUGIN_META_FILE! const one = await PluginMeta.touch("acme-plugin@latest", tmp.extra.mod, "acme-plugin") expect(one.state).toBe("first") @@ -108,8 +108,8 @@ describe("plugin.meta", () => { }, }) - process.env.OPENCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") - const file = process.env.OPENCODE_PLUGIN_META_FILE! + process.env.DEEPCODE_PLUGIN_META_FILE = path.join(tmp.path, "state", "plugin-meta.json") + const file = process.env.DEEPCODE_PLUGIN_META_FILE! const spec = pathToFileURL(tmp.extra.file).href const n = 12 diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 804b92b0..1d8b5c4e 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -120,8 +120,8 @@ describe("Project.fromDirectory", () => { expect(result.project.vcs).toBe("git") expect(result.project.worktree).toBe(tmp) - const opencodeFile = path.join(tmp, ".git", "opencode") - expect(yield* Effect.promise(() => Bun.file(opencodeFile).exists())).toBe(false) + const deepcodeFile = path.join(tmp, ".git", "deepcode") + expect(yield* Effect.promise(() => Bun.file(deepcodeFile).exists())).toBe(false) }), ) @@ -335,7 +335,7 @@ describe("Project.fromDirectory with worktrees", () => { expect(next.project.id).toBe(result.project.id) - const cache = path.join(tmp, ".git", "opencode") + const cache = path.join(tmp, ".git", "deepcode") const exists = yield* Effect.promise(() => Bun.file(cache).exists()) expect(exists).toBe(true) }), @@ -736,8 +736,8 @@ describe("Project.fromDirectory with bare repos", () => { expect(result.project.id).not.toBe(ProjectV2.ID.global) expect(result.project.worktree).toBe(worktreePath) - const correctCache = path.join(barePath, "opencode") - const wrongCache = path.join(parentDir, ".git", "opencode") + const correctCache = path.join(barePath, "deepcode") + const wrongCache = path.join(parentDir, ".git", "deepcode") expect(yield* Effect.promise(() => Bun.file(correctCache).exists())).toBe(true) expect(yield* Effect.promise(() => Bun.file(wrongCache).exists())).toBe(false) @@ -771,9 +771,9 @@ describe("Project.fromDirectory with bare repos", () => { expect(result.project.id).not.toBe(next.project.id) - const cacheA = path.join(bareA, "opencode") - const cacheB = path.join(bareB, "opencode") - const wrongCache = path.join(parentDir, ".git", "opencode") + const cacheA = path.join(bareA, "deepcode") + const cacheB = path.join(bareB, "deepcode") + const wrongCache = path.join(parentDir, ".git", "deepcode") expect(yield* Effect.promise(() => Bun.file(cacheA).exists())).toBe(true) expect(yield* Effect.promise(() => Bun.file(cacheB).exists())).toBe(true) @@ -801,7 +801,7 @@ describe("Project.fromDirectory with bare repos", () => { expect(result.project.id).not.toBe(ProjectV2.ID.global) expect(result.project.worktree).toBe(worktreePath) - const correctCache = path.join(barePath, "opencode") + const correctCache = path.join(barePath, "deepcode") expect(yield* Effect.promise(() => Bun.file(correctCache).exists())).toBe(true) }), ) diff --git a/packages/opencode/test/provider/digitalocean.test.ts b/packages/opencode/test/provider/digitalocean.test.ts index 144335c7..914eb703 100644 --- a/packages/opencode/test/provider/digitalocean.test.ts +++ b/packages/opencode/test/provider/digitalocean.test.ts @@ -29,7 +29,7 @@ const withEnv = (values: Record, effect: Effect.Effect< const withAuth = (metadata: Record | undefined, effect: Effect.Effect) => withEnv( { - OPENCODE_AUTH_CONTENT: JSON.stringify({ + DEEPCODE_AUTH_CONTENT: JSON.stringify({ digitalocean: { type: "api", key: "sk_do_test", diff --git a/packages/opencode/test/provider/header-timeout.test.ts b/packages/opencode/test/provider/header-timeout.test.ts index 1bd27b5c..c2223b2c 100644 --- a/packages/opencode/test/provider/header-timeout.test.ts +++ b/packages/opencode/test/provider/header-timeout.test.ts @@ -214,15 +214,15 @@ async function delayedBodyServer(delay: number): Promise<{ server: Server; url: function withAuthContent(self: Effect.Effect, value: Record = defaultAuthContent()) { return Effect.acquireUseRelease( Effect.sync(() => { - const previous = process.env.OPENCODE_AUTH_CONTENT - process.env.OPENCODE_AUTH_CONTENT = JSON.stringify(value) + const previous = process.env.DEEPCODE_AUTH_CONTENT + process.env.DEEPCODE_AUTH_CONTENT = JSON.stringify(value) return previous }), () => self, (previous) => Effect.sync(() => { - if (previous === undefined) delete process.env.OPENCODE_AUTH_CONTENT - else process.env.OPENCODE_AUTH_CONTENT = previous + if (previous === undefined) delete process.env.DEEPCODE_AUTH_CONTENT + else process.env.DEEPCODE_AUTH_CONTENT = previous }), ) } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 18ec8f9f..f6293024 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1148,7 +1148,7 @@ it.instance("ModelNotFoundError for provider includes suggestions", () => it.instance("ModelNotFoundError suggests catalog models for unloaded providers", () => Effect.gen(function* () { - yield* remove("OPENCODE_API_KEY") + yield* remove("DEEPCODE_API_KEY") const error = yield* Provider.use .getModel(ProviderV2.ID.opencode, ModelV2.ID.make("claude-haiku-fake-model")) .pipe(Effect.flip) diff --git a/packages/opencode/test/server/auth.test.ts b/packages/opencode/test/server/auth.test.ts index 1ecc663a..30264303 100644 --- a/packages/opencode/test/server/auth.test.ts +++ b/packages/opencode/test/server/auth.test.ts @@ -4,13 +4,13 @@ import { Flag } from "@opencode-ai/core/flag/flag" import { ServerAuth } from "../../src/server/auth" const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, } afterEach(() => { - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME }) describe("ServerAuth", () => { diff --git a/packages/opencode/test/server/httpapi-cors.test.ts b/packages/opencode/test/server/httpapi-cors.test.ts index 4e9680c7..e235bbab 100644 --- a/packages/opencode/test/server/httpapi-cors.test.ts +++ b/packages/opencode/test/server/httpapi-cors.test.ts @@ -13,13 +13,13 @@ import { testEffect } from "../lib/effect" const testStateLayer = Layer.effectDiscard( Effect.gen(function* () { const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, } Flag.OPENCODE_SERVER_PASSWORD = "secret" yield* Effect.promise(() => resetDatabase()) yield* Effect.addFinalizer(() => Effect.promise(async () => { - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD await resetDatabase() }), ) @@ -64,7 +64,7 @@ describe("HttpApi CORS", () => { Effect.gen(function* () { const handler = HttpRouter.toWebHandler( HttpApiApp.createRoutes().pipe( - Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({ OPENCODE_SERVER_PASSWORD: "secret" }))), + Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({ DEEPCODE_SERVER_PASSWORD: "secret" }))), ), { disableLogger: true }, ).handler diff --git a/packages/opencode/test/server/httpapi-exercise/environment.ts b/packages/opencode/test/server/httpapi-exercise/environment.ts index 1d3aebd6..a978a182 100644 --- a/packages/opencode/test/server/httpapi-exercise/environment.ts +++ b/packages/opencode/test/server/httpapi-exercise/environment.ts @@ -23,8 +23,8 @@ process.env.DEEPCODE_DB = exerciseDatabasePath Flag.OPENCODE_DB = exerciseDatabasePath export const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, } export const cleanupExercisePaths = Effect.promise(async () => { diff --git a/packages/opencode/test/server/httpapi-exercise/runner.ts b/packages/opencode/test/server/httpapi-exercise/runner.ts index b7ad6d62..7cd5e835 100644 --- a/packages/opencode/test/server/httpapi-exercise/runner.ts +++ b/packages/opencode/test/server/httpapi-exercise/runner.ts @@ -258,8 +258,8 @@ function fakeLlmConfig(url: string): Partial { const resetState = Effect.promise(async () => { const modules = await runtime() - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME await disposeApps() await modules.disposeAllInstances() await modules.resetDatabase() diff --git a/packages/opencode/test/server/httpapi-instance-route-auth.test.ts b/packages/opencode/test/server/httpapi-instance-route-auth.test.ts index 9933a28b..52f67957 100644 --- a/packages/opencode/test/server/httpapi-instance-route-auth.test.ts +++ b/packages/opencode/test/server/httpapi-instance-route-auth.test.ts @@ -15,8 +15,8 @@ function app(input: { password?: string; username?: string }) { Layer.provide( ConfigProvider.layer( ConfigProvider.fromUnknown({ - OPENCODE_SERVER_PASSWORD: input.password, - OPENCODE_SERVER_USERNAME: input.username, + DEEPCODE_SERVER_PASSWORD: input.password, + DEEPCODE_SERVER_USERNAME: input.username, }), ), ), diff --git a/packages/opencode/test/server/httpapi-listen.test.ts b/packages/opencode/test/server/httpapi-listen.test.ts index 585c59cb..f6b960bc 100644 --- a/packages/opencode/test/server/httpapi-listen.test.ts +++ b/packages/opencode/test/server/httpapi-listen.test.ts @@ -10,21 +10,21 @@ import { resetDatabase } from "../fixture/db" import { disposeAllInstances, tmpdir } from "../fixture/fixture" const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, - envPassword: process.env.OPENCODE_SERVER_PASSWORD, - envUsername: process.env.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + envPassword: process.env.DEEPCODE_SERVER_PASSWORD, + envUsername: process.env.DEEPCODE_SERVER_USERNAME, } const auth = { username: "opencode", password: "listen-secret" } const testPty = process.platform === "win32" ? test.skip : test afterEach(async () => { - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME - if (original.envPassword === undefined) delete process.env.OPENCODE_SERVER_PASSWORD - else process.env.OPENCODE_SERVER_PASSWORD = original.envPassword - if (original.envUsername === undefined) delete process.env.OPENCODE_SERVER_USERNAME - else process.env.OPENCODE_SERVER_USERNAME = original.envUsername + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME + if (original.envPassword === undefined) delete process.env.DEEPCODE_SERVER_PASSWORD + else process.env.DEEPCODE_SERVER_PASSWORD = original.envPassword + if (original.envUsername === undefined) delete process.env.DEEPCODE_SERVER_USERNAME + else process.env.DEEPCODE_SERVER_USERNAME = original.envUsername await disposeAllInstances() await resetDatabase() }) @@ -32,16 +32,16 @@ afterEach(async () => { async function startListener() { Flag.OPENCODE_SERVER_PASSWORD = auth.password Flag.OPENCODE_SERVER_USERNAME = auth.username - process.env.OPENCODE_SERVER_PASSWORD = auth.password - process.env.OPENCODE_SERVER_USERNAME = auth.username + process.env.DEEPCODE_SERVER_PASSWORD = auth.password + process.env.DEEPCODE_SERVER_USERNAME = auth.username return Server.listen({ hostname: "127.0.0.1", port: 0 }) } async function startNoAuthListener() { Flag.OPENCODE_SERVER_PASSWORD = undefined Flag.OPENCODE_SERVER_USERNAME = auth.username - delete process.env.OPENCODE_SERVER_PASSWORD - process.env.OPENCODE_SERVER_USERNAME = auth.username + delete process.env.DEEPCODE_SERVER_PASSWORD + process.env.DEEPCODE_SERVER_USERNAME = auth.username return Server.listen({ hostname: "127.0.0.1", port: 0 }) } @@ -329,8 +329,8 @@ describe("HttpApi Server.listen", () => { return { initialized, completed } }, }) - const previous = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS - process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" + const previous = process.env.DEEPCODE_DISABLE_DEFAULT_PLUGINS + process.env.DEEPCODE_DISABLE_DEFAULT_PLUGINS = "1" let listener: Awaited> | undefined try { listener = await startListener() @@ -348,8 +348,8 @@ describe("HttpApi Server.listen", () => { expect(await Bun.file(tmp.extra.initialized).text()).toBe("initialized\n") } finally { if (listener) await stop(listener, "timed out cleaning up plugin client listener").catch(() => undefined) - if (previous === undefined) delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS - else process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = previous + if (previous === undefined) delete process.env.DEEPCODE_DISABLE_DEFAULT_PLUGINS + else process.env.DEEPCODE_DISABLE_DEFAULT_PLUGINS = previous } }) diff --git a/packages/opencode/test/server/httpapi-mdns.test.ts b/packages/opencode/test/server/httpapi-mdns.test.ts index 6dc12238..05b34b7e 100644 --- a/packages/opencode/test/server/httpapi-mdns.test.ts +++ b/packages/opencode/test/server/httpapi-mdns.test.ts @@ -26,14 +26,14 @@ void mock.module("bonjour-service", () => ({ const { Server } = await import("../../src/server/server") const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, } afterEach(async () => { events.length = 0 - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME await disposeAllInstances() await resetDatabase() }) @@ -59,7 +59,7 @@ describe("HttpApi Server.listen mDNS", () => { const published = events.filter((e) => e.kind === "publish") expect(published.length).toBe(1) expect(published[0]!.port).toBe(listener.port) - expect(published[0]!.name).toBe(`opencode-${listener.port}`) + expect(published[0]!.name).toBe(`deepcode-${listener.port}`) } finally { await withTimeout(listener.stop(true), 10_000, "timed out stopping mdns listener") } diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index 625ca189..5b80c220 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -356,7 +356,7 @@ describe("provider HttpApi", () => { Effect.gen(function* () { const directory = (yield* TestInstance).directory yield* setEnvScoped( - "OPENCODE_AUTH_CONTENT", + "DEEPCODE_AUTH_CONTENT", JSON.stringify({ google: { type: "oauth", refresh: "dummy", access: "dummy", expires: 9999999999999 }, }), diff --git a/packages/opencode/test/server/httpapi-sdk.test.ts b/packages/opencode/test/server/httpapi-sdk.test.ts index c81b3b77..9e9e622f 100644 --- a/packages/opencode/test/server/httpapi-sdk.test.ts +++ b/packages/opencode/test/server/httpapi-sdk.test.ts @@ -39,8 +39,8 @@ const appLayer = AppNodeBuilder.build( const it = testEffect(Layer.mergeAll(appLayer, httpApiLayer)) const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, } type ServerPath = "default" | "raw" @@ -328,8 +328,8 @@ function seedMessage(directory: string, sessionID: string) { } afterEach(async () => { - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME await disposeAllInstances() await resetDatabase() }) diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts index 2f10193f..a2bfc07f 100644 --- a/packages/opencode/test/server/httpapi-ui.test.ts +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -23,18 +23,18 @@ import { testEffect } from "../lib/effect" const testStateLayer = Layer.effectDiscard( Effect.gen(function* () { const original = { - OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, - OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, - envPassword: process.env.OPENCODE_SERVER_PASSWORD, - envUsername: process.env.OPENCODE_SERVER_USERNAME, + DEEPCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + DEEPCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, + envPassword: process.env.DEEPCODE_SERVER_PASSWORD, + envUsername: process.env.DEEPCODE_SERVER_USERNAME, } yield* Effect.addFinalizer(() => Effect.sync(() => { - Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD - Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME - restoreEnv("OPENCODE_SERVER_PASSWORD", original.envPassword) - restoreEnv("OPENCODE_SERVER_USERNAME", original.envUsername) + Flag.OPENCODE_SERVER_PASSWORD = original.DEEPCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.DEEPCODE_SERVER_USERNAME + restoreEnv("DEEPCODE_SERVER_PASSWORD", original.envPassword) + restoreEnv("DEEPCODE_SERVER_USERNAME", original.envUsername) }), ) }), @@ -64,8 +64,8 @@ function app(input?: { password?: string; username?: string }) { Layer.provide( ConfigProvider.layer( ConfigProvider.fromUnknown({ - OPENCODE_SERVER_PASSWORD: input?.password, - OPENCODE_SERVER_USERNAME: input?.username, + DEEPCODE_SERVER_PASSWORD: input?.password, + DEEPCODE_SERVER_USERNAME: input?.username, }), ), ), @@ -184,118 +184,23 @@ function responseText(response: Response) { } describe("HttpApi UI fallback", () => { - it.live("serves the web UI through the HTTP API app", () => + it.live("does not proxy to an upstream product when embedded UI is unavailable", () => Effect.gen(function* () { - let proxiedUrl: string | undefined - + let requested = false const response = yield* uiApp({ disableEmbeddedWebUi: true, - client: httpClient( - new Response("opencode", { headers: { "content-type": "text/html" } }), - (request) => { - proxiedUrl = request.url - }, + client: Layer.succeed( + HttpClient.HttpClient, + HttpClient.make((request) => { + requested = true + return Effect.die(`unexpected upstream UI request: ${request.url}`) + }), ), }).request("/") - expect(response.status).toBe(200) - expect(response.headers.get("content-type")).toContain("text/html") - expect(yield* responseText(response)).toBe("opencode") - expect(proxiedUrl).toBe("https://app.opencode.ai/") - }), - ) - - it.live("strips upstream transfer encoding headers from proxied assets", () => - Effect.gen(function* () { - let proxiedUrl: string | undefined - - const response = yield* Effect.gen(function* () { - const fs = yield* FSUtil.Service - const client = yield* HttpClient.HttpClient - const flags = yield* RuntimeFlags.Service - return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/assets/app.js")), { - fs, - client, - disableEmbeddedWebUi: flags.disableEmbeddedWebUi, - }) - }).pipe( - Effect.provide( - Layer.mergeAll( - RuntimeFlags.layer({ disableEmbeddedWebUi: true }), - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => { - proxiedUrl = request.url - return Effect.succeed( - HttpClientResponse.fromWeb( - request, - new Response("console.log('ok')", { - headers: { - "content-encoding": "br", - "content-length": "999", - "content-type": "text/javascript", - }, - }), - ), - ) - }), - ), - ), - ), - Effect.map(HttpServerResponse.toWeb), - ) - - expect(response.status).toBe(200) - expect(proxiedUrl).toBe("https://app.opencode.ai/assets/app.js") - expect(response.headers.get("content-encoding")).toBeNull() - expect(response.headers.get("content-length")).not.toBe("999") - expect(response.headers.get("content-type")).toContain("text/javascript") - expect(yield* responseText(response)).toBe("console.log('ok')") - }), - ) - - // Regression for #25698 (Ope): upstream `transfer-encoding: chunked` was - // forwarded through the proxy while the proxy itself re-frames the body, - // causing browsers to fail with `ERR_INVALID_CHUNKED_ENCODING`. - it.live("strips upstream transfer-encoding header from proxied assets", () => - Effect.gen(function* () { - const response = yield* Effect.gen(function* () { - const fs = yield* FSUtil.Service - const client = yield* HttpClient.HttpClient - const flags = yield* RuntimeFlags.Service - return yield* serveUIEffect(HttpServerRequest.fromWeb(new Request("http://localhost/")), { - fs, - client, - disableEmbeddedWebUi: flags.disableEmbeddedWebUi, - }) - }).pipe( - Effect.provide( - Layer.mergeAll( - RuntimeFlags.layer({ disableEmbeddedWebUi: true }), - Layer.succeed( - HttpClient.HttpClient, - HttpClient.make((request) => - Effect.succeed( - HttpClientResponse.fromWeb( - request, - new Response("opencode", { - headers: { - "transfer-encoding": "chunked", - "content-type": "text/html", - }, - }), - ), - ), - ), - ), - ), - ), - Effect.map(HttpServerResponse.toWeb), - ) - - expect(response.status).toBe(200) - expect(response.headers.get("transfer-encoding")).toBeNull() - expect(yield* responseText(response)).toBe("opencode") + expect(response.status).toBe(404) + expect(requested).toBe(false) + expect(yield* responseText(response)).toContain("Not Found") }), ) diff --git a/packages/opencode/test/server/project-init-git.test.ts b/packages/opencode/test/server/project-init-git.test.ts index 4474bea7..a792fce2 100644 --- a/packages/opencode/test/server/project-init-git.test.ts +++ b/packages/opencode/test/server/project-init-git.test.ts @@ -71,7 +71,7 @@ describe("project.initGit endpoint", () => { }) // Reload behavior: bus emits exactly one server.instance.disposed for the directory. expect(disposedEvents(events.seen, tmp.directory)).toBe(1) - expect(yield* fs.exists(path.join(tmp.directory, ".git", "opencode"))).toBe(false) + expect(yield* fs.exists(path.join(tmp.directory, ".git", "deepcode"))).toBe(false) const current = yield* request(tmp.directory, "/project/current") expect(current.status).toBe(200) diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index c47913bd..fb60c1cc 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -28,15 +28,15 @@ const withoutWatcher = (effect: Effect.Effect) => { if (process.platform !== "win32") return effect return Effect.acquireUseRelease( Effect.sync(() => { - const previous = process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER - process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = "true" + const previous = process.env.DEEPCODE_EXPERIMENTAL_DISABLE_FILEWATCHER + process.env.DEEPCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = "true" return previous }), () => effect, (previous) => Effect.sync(() => { - if (previous === undefined) delete process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER - else process.env.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = previous + if (previous === undefined) delete process.env.DEEPCODE_EXPERIMENTAL_DISABLE_FILEWATCHER + else process.env.DEEPCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = previous }), ) } diff --git a/packages/opencode/test/server/worktree-endpoint-repro.test.ts b/packages/opencode/test/server/worktree-endpoint-repro.test.ts index 62a61858..8122411a 100644 --- a/packages/opencode/test/server/worktree-endpoint-repro.test.ts +++ b/packages/opencode/test/server/worktree-endpoint-repro.test.ts @@ -13,14 +13,14 @@ import { testEffect } from "../lib/effect" const stateLayer = Layer.effectDiscard( Effect.gen(function* () { const original = { - OPENCODE_EXPERIMENTAL_WORKSPACES: Flag.OPENCODE_EXPERIMENTAL_WORKSPACES, + DEEPCODE_EXPERIMENTAL_WORKSPACES: Flag.OPENCODE_EXPERIMENTAL_WORKSPACES, } Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true yield* Effect.addFinalizer(() => Effect.promise(async () => { - Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original.OPENCODE_EXPERIMENTAL_WORKSPACES + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = original.DEEPCODE_EXPERIMENTAL_WORKSPACES await resetDatabase() }), ) diff --git a/packages/opencode/test/session/llm-native-recorded.test.ts b/packages/opencode/test/session/llm-native-recorded.test.ts index 7ca09910..7c91dc13 100644 --- a/packages/opencode/test/session/llm-native-recorded.test.ts +++ b/packages/opencode/test/session/llm-native-recorded.test.ts @@ -79,7 +79,7 @@ const recordOpenAIOAuth = (() => { })() function decodeRecordOpenAIOAuth() { - const value = process.env.OPENCODE_RECORD_OPENAI_AUTH + const value = process.env.DEEPCODE_RECORD_OPENAI_AUTH if (!value) return undefined try { const auth = Option.getOrUndefined(decodeAuth(JSON.parse(value))) @@ -120,7 +120,7 @@ const RECORDED_SCENARIOS = [ cassette: "session/native-openai-tool-loop", protocol: "openai-responses", tags: ["opencode", "native", "tool-loop"], - canRecord: () => Boolean(envValue("OPENCODE_RECORD_OPENAI_API_KEY", "OPENAI_API_KEY")), + canRecord: () => Boolean(envValue("DEEPCODE_RECORD_OPENAI_API_KEY", "OPENAI_API_KEY")), config: (model) => providerConfig({ providerID: ProviderV2.ID.openai, @@ -130,7 +130,7 @@ const RECORDED_SCENARIOS = [ api: "https://api.openai.com/v1", model, options: { - apiKey: envValue("OPENCODE_RECORD_OPENAI_API_KEY", "OPENAI_API_KEY") ?? "fixture-openai-key", + apiKey: envValue("DEEPCODE_RECORD_OPENAI_API_KEY", "OPENAI_API_KEY") ?? "fixture-openai-key", baseURL: "https://api.openai.com/v1", }, }), @@ -166,18 +166,18 @@ const RECORDED_SCENARIOS = [ cassette: "session/native-zen-tool-loop", protocol: "openai-responses", tags: ["opencode", "zen", "native", "tool-loop"], - canRecord: () => Boolean(process.env.OPENCODE_RECORD_CONSOLE_TOKEN && process.env.OPENCODE_RECORD_ZEN_ORG_ID), + canRecord: () => Boolean(process.env.DEEPCODE_RECORD_CONSOLE_TOKEN && process.env.DEEPCODE_RECORD_ZEN_ORG_ID), config: (model) => providerConfig({ providerID: ProviderV2.ID.opencode, name: "OpenCode Zen", - env: ["OPENCODE_CONSOLE_TOKEN"], + env: ["DEEPCODE_CONSOLE_TOKEN"], npm: "@ai-sdk/openai-compatible", - api: zenURL(process.env.OPENCODE_RECORD_ZEN_CONNECTION ?? "fixture"), + api: zenURL(process.env.DEEPCODE_RECORD_ZEN_CONNECTION ?? "fixture"), model, options: { - apiKey: process.env.OPENCODE_RECORD_CONSOLE_TOKEN ?? "fixture-console-token", - headers: { "x-org-id": process.env.OPENCODE_RECORD_ZEN_ORG_ID ?? "fixture-org" }, + apiKey: process.env.DEEPCODE_RECORD_CONSOLE_TOKEN ?? "fixture-console-token", + headers: { "x-org-id": process.env.DEEPCODE_RECORD_ZEN_ORG_ID ?? "fixture-org" }, }, }), }, @@ -189,7 +189,7 @@ const RECORDED_SCENARIOS = [ cassette: "session/native-anthropic-tool-loop", protocol: "anthropic-messages", tags: ["opencode", "native", "tool-loop"], - canRecord: () => Boolean(envValue("OPENCODE_RECORD_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY")), + canRecord: () => Boolean(envValue("DEEPCODE_RECORD_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY")), config: (model) => providerConfig({ providerID: ProviderV2.ID.anthropic, @@ -199,7 +199,7 @@ const RECORDED_SCENARIOS = [ api: "https://api.anthropic.com/v1", model, options: { - apiKey: envValue("OPENCODE_RECORD_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY") ?? "fixture-anthropic-key", + apiKey: envValue("DEEPCODE_RECORD_ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY") ?? "fixture-anthropic-key", baseURL: "https://api.anthropic.com/v1", }, }), @@ -208,7 +208,7 @@ const RECORDED_SCENARIOS = [ const shouldRecord = process.env.RECORD === "true" const selectedScenarios = new Set( - (envValue("OPENCODE_RECORDED_SCENARIO", "RECORDED_PROVIDER") ?? "") + (envValue("DEEPCODE_RECORDED_SCENARIO", "RECORDED_PROVIDER") ?? "") .split(",") .map((item) => item.trim().toLowerCase()) .filter(Boolean), @@ -228,7 +228,7 @@ const canRun = (scenario: RecordedScenario) => const recordError = (scenario: RecordedScenario) => scenario.id === "openai-oauth" - ? "Set OPENCODE_RECORD_OPENAI_AUTH to an OAuth auth JSON object in the recording environment." + ? "Set DEEPCODE_RECORD_OPENAI_AUTH to an OAuth auth JSON object in the recording environment." : `Missing recording credentials for ${scenario.name}.` const redactRecordedBody = (body: string) => diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index f365b63f..02803937 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -52,14 +52,14 @@ This skill is loaded from the global home directory. const withHome = (home: string, self: Effect.Effect) => Effect.acquireUseRelease( Effect.sync(() => { - const prev = process.env.OPENCODE_TEST_HOME - process.env.OPENCODE_TEST_HOME = home + const prev = process.env.DEEPCODE_TEST_HOME + process.env.DEEPCODE_TEST_HOME = home return prev }), () => self, (prev) => Effect.sync(() => { - process.env.OPENCODE_TEST_HOME = prev + process.env.DEEPCODE_TEST_HOME = prev }), ) diff --git a/packages/opencode/test/tool/fixtures/models-api.json b/packages/opencode/test/tool/fixtures/models-api.json index 6302a951..d8033e65 100644 --- a/packages/opencode/test/tool/fixtures/models-api.json +++ b/packages/opencode/test/tool/fixtures/models-api.json @@ -28002,7 +28002,7 @@ }, "opencode-go": { "id": "opencode-go", - "env": ["OPENCODE_API_KEY"], + "env": ["DEEPCODE_API_KEY"], "npm": "@ai-sdk/openai-compatible", "api": "https://opencode.ai/zen/go/v1", "name": "OpenCode Go", @@ -57812,7 +57812,7 @@ }, "opencode": { "id": "opencode", - "env": ["OPENCODE_API_KEY"], + "env": ["DEEPCODE_API_KEY"], "npm": "@ai-sdk/openai-compatible", "api": "https://opencode.ai/zen/v1", "name": "OpenCode Zen", diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 018b1a0b..1cc49985 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -56,15 +56,15 @@ const asks = () => { const githubBase = (url: string, self: Effect.Effect) => Effect.acquireUseRelease( Effect.sync(() => { - const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL - process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url + const previous = process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL + process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = url return previous }), () => self, (previous) => Effect.sync(() => { - if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous - else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL + if (previous) process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = previous + else delete process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL }), ) diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 81b74aa8..0c190fd1 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -47,15 +47,15 @@ const full = (p: string) => (process.platform === "win32" ? Filesystem.normalize const githubBase = (url: string, self: Effect.Effect) => Effect.acquireUseRelease( Effect.sync(() => { - const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL - process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url + const previous = process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL + process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = url return previous }), () => self, (previous) => Effect.sync(() => { - if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous - else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL + if (previous) process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = previous + else delete process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL }), ) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index c1ef61b2..90d2dbc1 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -99,15 +99,15 @@ const glob = (p: string) => const githubBase = (url: string, self: Effect.Effect) => Effect.acquireUseRelease( Effect.sync(() => { - const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL - process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url + const previous = process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL + process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = url return previous }), () => self, (previous) => Effect.sync(() => { - if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous - else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL + if (previous) process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL = previous + else delete process.env.DEEPCODE_REPO_CLONE_GITHUB_BASE_URL }), ) const git = Effect.fn("ReadToolTest.git")(function* (cwd: string, args: string[]) { diff --git a/packages/opencode/test/tool/shell.test.ts b/packages/opencode/test/tool/shell.test.ts index a970f85d..d4ab197b 100644 --- a/packages/opencode/test/tool/shell.test.ts +++ b/packages/opencode/test/tool/shell.test.ts @@ -542,7 +542,7 @@ describe("tool.shell permissions", () => { item, Effect.acquireUseRelease( Effect.sync(() => { - const key = "OPENCODE_TEST_MISSING" + const key = "DEEPCODE_TEST_MISSING" const prev = process.env[key] delete process.env[key] return { key, prev } diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index 1c25b253..5c6c3c11 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -50,11 +50,11 @@ Use this skill. ) yield* Effect.promise(() => Bun.write(path.join(skill, "scripts", "demo.txt"), "demo")) - const home = process.env.OPENCODE_TEST_HOME - process.env.OPENCODE_TEST_HOME = dir + const home = process.env.DEEPCODE_TEST_HOME + process.env.DEEPCODE_TEST_HOME = dir yield* Effect.addFinalizer(() => Effect.sync(() => { - process.env.OPENCODE_TEST_HOME = home + process.env.DEEPCODE_TEST_HOME = home }), ) @@ -96,11 +96,11 @@ Use this skill. it.instance("execute preserves not found message", () => Effect.gen(function* () { const dir = (yield* TestInstance).directory - const home = process.env.OPENCODE_TEST_HOME - process.env.OPENCODE_TEST_HOME = dir + const home = process.env.DEEPCODE_TEST_HOME + process.env.DEEPCODE_TEST_HOME = dir yield* Effect.addFinalizer(() => Effect.sync(() => { - process.env.OPENCODE_TEST_HOME = home + process.env.DEEPCODE_TEST_HOME = home }), ) diff --git a/packages/opencode/test/tool/websearch.test.ts b/packages/opencode/test/tool/websearch.test.ts index 349606de..e022216c 100644 --- a/packages/opencode/test/tool/websearch.test.ts +++ b/packages/opencode/test/tool/websearch.test.ts @@ -15,17 +15,17 @@ describe("websearch provider", () => { }) test("supports an operational override", () => { - const original = process.env.OPENCODE_WEBSEARCH_PROVIDER + const original = process.env.DEEPCODE_WEBSEARCH_PROVIDER try { - process.env.OPENCODE_WEBSEARCH_PROVIDER = "parallel" + process.env.DEEPCODE_WEBSEARCH_PROVIDER = "parallel" expect(selectWebSearchProvider(SESSION_ID)).toBe("parallel") - process.env.OPENCODE_WEBSEARCH_PROVIDER = "exa" + process.env.DEEPCODE_WEBSEARCH_PROVIDER = "exa" expect(selectWebSearchProvider(SESSION_ID)).toBe("exa") } finally { - if (original === undefined) delete process.env.OPENCODE_WEBSEARCH_PROVIDER - else process.env.OPENCODE_WEBSEARCH_PROVIDER = original + if (original === undefined) delete process.env.DEEPCODE_WEBSEARCH_PROVIDER + else process.env.DEEPCODE_WEBSEARCH_PROVIDER = original } }) diff --git a/packages/opencode/test/util/process.test.ts b/packages/opencode/test/util/process.test.ts index 934833d1..abb0a873 100644 --- a/packages/opencode/test/util/process.test.ts +++ b/packages/opencode/test/util/process.test.ts @@ -69,9 +69,9 @@ describe("util.process", () => { }) test("merges environment overrides", async () => { - const out = await Process.run(node('process.stdout.write(process.env.OPENCODE_TEST ?? "")'), { + const out = await Process.run(node('process.stdout.write(process.env.DEEPCODE_TEST ?? "")'), { env: { - OPENCODE_TEST: "set", + DEEPCODE_TEST: "set", }, }) expect(out.stdout.toString()).toBe("set") @@ -80,15 +80,15 @@ describe("util.process", () => { test("uses shell in run on Windows", async () => { if (process.platform !== "win32") return - const out = await Process.run(["set", "OPENCODE_TEST_SHELL"], { + const out = await Process.run(["set", "DEEPCODE_TEST_SHELL"], { shell: true, env: { - OPENCODE_TEST_SHELL: "ok", + DEEPCODE_TEST_SHELL: "ok", }, }) expect(out.code).toBe(0) - expect(out.stdout.toString()).toContain("OPENCODE_TEST_SHELL=ok") + expect(out.stdout.toString()).toContain("DEEPCODE_TEST_SHELL=ok") }) test("runs cmd scripts with spaces on Windows without shell", async () => {