From f1e55e901859187490e25ef15b8b19982cc0566b Mon Sep 17 00:00:00 2001 From: quickbeard Date: Thu, 6 Aug 2026 19:32:25 -0700 Subject: [PATCH 1/5] fix(prompt): rebrand every model-visible surface to CoDev Code Asked "who are you", the agent answered "I'm opencode" and sent users to upstream's issue tracker and docs. Only the UI had been rebranded; the system prompts the model actually receives came from upstream verbatim. Names are now rewritten as the prompt is assembled, by the fork-only packages/codev-brand, at the three points every model-visible string flows through: SystemPrompt.provider (upstream's branch chain renamed select() and left byte-identical), the tool registry's assembled description, and the built-in config skill. The prompt/*.txt sources and customize-opencode.md stay byte-identical to upstream, so their sync hunks keep applying and a new upstream brand mention gets rebranded on arrival instead of shipping unnoticed. Identity is also stated in the environment block, because an agent that defines its own `prompt` replaces the provider prompt wholesale and would otherwise have no idea what product it is. Identifiers that are not branding keep their upstream names: @opencode-ai/* packages, published plugin names such as opencode-gemini-auth, the config $schema URL, opencode://, ai.opencode.* and opencode.db. The built-in skill is renamed customize-opencode -> customize-codev; the old name still resolves so existing configs and permission rules keep working. test/session/brand.test.ts is the tripwire: it scans every embedded prompt and tool description and fails when a brand mention is not covered by the rules. --- CHANGELOG.md | 28 ++++++++ DIVERGENCES.md | 23 +++++-- bun.lock | 11 ++++ packages/codev-brand/package.json | 17 +++++ packages/codev-brand/src/index.ts | 66 +++++++++++++++++++ packages/codev-brand/test/brand.test.ts | 60 +++++++++++++++++ packages/codev-brand/tsconfig.json | 8 +++ packages/core/package.json | 1 + packages/core/src/plugin/skill.ts | 11 ++-- packages/core/test/plugin/skill.test.ts | 15 ++++- packages/opencode/package.json | 1 + .../src/command/template/initialize.txt | 4 +- packages/opencode/src/session/system.ts | 9 +++ packages/opencode/src/skill/index.ts | 21 ++++-- packages/opencode/src/tool/registry.ts | 17 +++-- packages/opencode/test/session/brand.test.ts | 60 +++++++++++++++++ 16 files changed, 325 insertions(+), 27 deletions(-) create mode 100644 packages/codev-brand/package.json create mode 100644 packages/codev-brand/src/index.ts create mode 100644 packages/codev-brand/test/brand.test.ts create mode 100644 packages/codev-brand/tsconfig.json create mode 100644 packages/opencode/test/session/brand.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d3e5d026ec..9145cf1a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,34 @@ file is the human-readable summary of what changed and why. ## Unreleased +### Agent + +- **The agent knows which product it is** — asked "who are you", it answered + "I'm opencode", and it pointed users at upstream's issue tracker and docs for + help. Only the UI had been rebranded; every system prompt the model actually + receives still came from upstream verbatim, so identity, the `/help` line, the + feedback URL (`github.com/anomalyco/opencode`) and the docs URL + (`opencode.ai`) were all upstream's. The model now introduces itself as CoDev + Code and routes support at `github.com/quickbeard/codev-code/issues` and + `netmind.viettel.vn/codev/docs`. The identity is also stated in the + environment block, so agents that define their own `prompt` — which replaces + the whole provider prompt — get it too. + + Prompt files themselves are untouched: names are rewritten as the prompt is + assembled (fork-only `packages/codev-brand`), so upstream prompt edits keep + applying cleanly and any new upstream brand mention is rebranded on arrival. + Identifiers that are not branding keep their upstream names — `@opencode-ai/*` + packages, published plugin names such as `opencode-gemini-auth`, and the + config `$schema` URL `https://opencode.ai/config.json` — so the model's advice + about them stays correct. + +- **Built-in config skill renamed `customize-opencode` → `customize-codev`** — + its description and body are rebranded as well. The old name still resolves, + so existing `skill` permission rules and configs referencing it keep working. + +- **`/init` writes "CoDev Code"** — the AGENTS.md template it generates no + longer describes the tool as OpenCode. + ## 1.18.4-7 ### TUI diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 1fa2afe25b..df0c19853f 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -73,8 +73,8 @@ from the squash message first, or use a plain merge. | `packages/core/src/global.ts`, `packages/core/test/global.test.ts`, `packages/opencode/test/cli/mcp-add.test.ts` | `const app = "opencode"` → `"codev"`; test expectations `tmpdir()/opencode` → `tmpdir()/codev` and `.config/opencode/` → `.config/codev/` | Config/data/cache/state dirs under `~/.config/codev` etc.; upstream tests assert the literal dir name. | Keep ours (the one constant + expectations); take upstream's surrounding changes. | | `packages/opencode/src/index.ts`, `packages/opencode/test/cli/error.test.ts`, `packages/opencode/test/cli/help/help-snapshots.test.ts`, `packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap` | `scriptName("opencode")` → `"codev"`; `show()` prefix check `"opencode "` → `"codev "`; registered the fork-only `ConfigureCommand` (cli/cmd/configure.ts, new file) and `CodegraphCommand` (cli/cmd/codegraph.ts, new file) and added `configure` + `codegraph` to the help-snapshot TOP_LEVEL list; test expectation `` `opencode models` `` → `` `codev models` ``; help snapshots regenerated so usage lines read `codev …` | Help/usage text shows the real user-facing command; `codev configure` is the standalone gateway-setup entry point, and `codev codegraph` is the standalone CodeGraph MCP wiring for installs not managed by CoDev Hub. The `show()` check must always match the scriptName; upstream's error-format test and help snapshots assert the literal command. | Keep our strings, command registration + expectation; take upstream's surrounding changes. Regenerate the snapshot file with `bun test test/cli/help --update-snapshots` instead of hand-merging. | | `package.json` (root) | `name` `opencode` → `codev-code`; `repository.url` → `github.com/quickbeard/codev-code`; added `release` script delegating to packages/opencode; added `bump` script pointing at the fork-only `script/bump-version.ts`; removed the `sso`, `dev:stats`, and `random` scripts | The root manifest carries the fork's name (nothing reads it — root is private and unpublished; `packages/script` reads only `packageManager` — so this is cosmetic and also removes the name collision with the `packages/opencode` workspace member, whose name MUST stay `opencode`: build.ts derives bun compile targets and dist dir names from it). `bun run release` and `bun run bump` work from the repo root. `sso` logs into upstream's AWS org (`--sso-session=opencode`) and `dev:stats` runs inside an sst shell against upstream's production stage — neither works without anomalyco credentials; `random` is inert cruft (`echo 'Random script'`). | Keep our name + release/bump lines and the removals (drop any new upstream-infra-only scripts too); take upstream's other fields. | -| `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. | -| `bun.lock` | Mirrors the bin key rename above and the fork-only workspace packages/deps (`@codev/gateway`) | Lockfile records workspace bin mappings and workspace deps. Known quirk: the root workspace stanza still says `"name": "opencode"` — bun never syncs the root manifest's name into an existing lock (verified: neither `bun install` nor `--lockfile-only` rewrites it); it self-corrects only on a from-scratch regeneration and is harmless meanwhile. Don't hand-edit it. | Regenerate with `bun install`; never hand-merge. | +| `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependency `"@codev/brand": "workspace:*"` | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. | +| `bun.lock` | Mirrors the bin key rename above and the fork-only workspace packages/deps (`@codev/gateway`, `@codev/brand`) | Lockfile records workspace bin mappings and workspace deps. Known quirk: the root workspace stanza still says `"name": "opencode"` — bun never syncs the root manifest's name into an existing lock (verified: neither `bun install` nor `--lockfile-only` rewrites it); it self-corrects only on a from-scratch regeneration and is harmless meanwhile. Don't hand-edit it. | Regenerate with `bun install`; never hand-merge. | | `packages/tui/package.json` | Added dependency `"@codev/gateway": "workspace:*"` | The fork's gateway configure dialog imports SSO/backend/config-block logic from the fork-only `packages/codev-gateway`. | Keep our dependency line; take upstream's other fields. | | `packages/tui/src/feature-plugins/home/tips-view.tsx` | `NO_MODELS_TIP` points at `/configure` (CoDev gateway) instead of `/connect`; tip strings rebranded — built-CLI examples `opencode ` → `codev ` (`serve`, `upgrade`, `agent create`, `github install`, `debug config`) and product prose "OpenCode" → "CoDev Code". Dropped the `docker run … ghcr.io/anomalyco/opencode` tip and the `/connect` tip that advertised "OpenCode Zen" (a generic `/connect` tip already exists). GitHub-integration tips rebranded to the fork's trigger: the two `/opencode` tips and the `/oc` PR-review tip now say `/codev` (matches the rebranded `github.handler.ts` defaults). | The tips name the fork's CLI (`codev`), product, and `/codev` GitHub trigger; no fork docker image (see web-docs install row); Zen is upstream's hosted gateway. The `opencode.status` command id stays (internal identifier, not user-facing). | Keep our tip text and the docker-tip and Zen-`/connect`-tip deletions. Rebrand any new upstream `opencode `/"OpenCode"/`/opencode`-trigger tip; drop any re-added docker/brew/curl install tip. | | `packages/tui/src/feature-plugins/sidebar/footer.tsx` | Deleted the dismissable "Getting started" card (free-models pitch + `/connect` pointer + `dismissed_getting_started` KV flag); the sidebar footer keeps only the cwd path display | The card advertises upstream's hosted free Zen models — which the fork hides — and points at `/connect` while first-run setup routes through `/configure`; the fork's startup gate covers onboarding. | Keep the card deleted; take upstream's changes to the remaining path display. | @@ -178,6 +178,11 @@ from the squash message first, or use a plain merge. | `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Permission-mode indicator + shift+tab cycle. keybind.ts: added `permission_mode_cycle: keybind("shift+tab", ...)` + its `permission.mode` CommandMap entry; `agent_cycle_reverse` default `"shift+tab"` → `"none"` (tab still cycles agents forward; previous-agent stays rebindable). prompt/index.tsx: the footer badge next to the agent name always renders in prompt mode — gray `manual` (`theme.textMuted`) in normal mode, red `auto` (`theme.error`) in auto mode (upstream only showed gray `auto`); the footer's shortcut hints gain a `shift+tab modes` text (via a `modeShortcut` `useCommandShortcut("permission.mode")` memo) left of `tab agents`, in the same `Show` gate (see the hints-and-usage row above for when that gate renders). app.tsx: `"permission.mode"` added to `appBindingCommands` so the binding is gathered. | The permission mode is always visible (manual/auto) with auto in red as a danger signal, and shift+tab toggles it without opening the palette. | Keep the `permission_mode_cycle` definition + mapping, the `agent_cycle_reverse` `"none"` default, the badge `Show` block, the `modeShortcut` memo + hint text, and the `appBindingCommands` entry; take upstream's surrounding changes. If upstream reworks the auto badge or rebinds shift+tab, re-apply on top and re-audit the shift+tab conflict. | | `packages/core/src/filesystem/search.ts`, `packages/core/src/ripgrep/binary.ts`, `packages/core/src/filesystem.ts`, `packages/core/src/tool/read-filesystem.ts`, `packages/core/test/filesystem/search.test.ts`, `packages/core/test/location-filesystem.test.ts`, `packages/core/test/ripgrep-binary.test.ts`, `packages/core/test/preload.ts` | Windows file-search fallback robustness. search.ts: the background ripgrep index walk no longer dies silently (`Effect.orDie` in a forked fiber) — each attempt resets the accumulated file/directory state, failures log a warning ("file index scan failed, file search may be empty"), and the walk retries forever on a 1s exponential backoff capped at 30s spacing, so installing rg mid-session self-heals; directory entries are suffixed and detected with `"/"` instead of `path.sep` (both ripgrep and fff layers), and fallback `find` queries normalize `\` to `/` and collapse doubled slashes (directory tab-expansion appends `/` to an already-`/`-suffixed entry). binary.ts: `Effect.cached` on ripgrep binary resolution (which memoizes failure exits for the process lifetime) replaced with a success-only cache behind a semaphore, so a failed rg download (offline, blocked proxy) retries on the next use instead of bricking search until restart; the download tail tries GitHub first (each attempt bounded to 30s) and falls back to the CoDev landing-page mirror (`https://netmind.viettel.vn/codev/docs/code/downloads/rg---[.exe]`, a raw executable — no archive/extract — staged in a scoped temp dir then copied into place), via `Effect.catchCause` so a proxy block page (200 + HTML) that dies in `extract` as a defect still reaches the mirror. filesystem.ts `list` + read-filesystem.ts `list`: directory suffix `path.sep` → `"/"`; `fs.list` also `/`-normalizes `path.relative` output. Tests: search.test.ts pins `"/"`-suffixed directories, adds query-normalization coverage, and a stub-Ripgrep scan-retry test asserting the partial first walk is discarded; location-filesystem.test.ts pins `"src/"`; ripgrep-binary.test.ts adds mocked-HTTP coverage of the fallback order, success caching, and failure non-caching; preload.ts redirects `XDG_CACHE_HOME` to a fresh temp dir so no test can read or clobber the developer's real `~/.cache/codev`. | On Windows fff is disabled by default (upstream `e4300e9b74`), so the `@` mention popup and file search depend entirely on the ripgrep fallback; a missing `rg.exe` plus one failed download left the index permanently empty with no user-visible signal (the CI-workflows row's choco preinstall papered over the same swallowed failure). `\`-suffixed directory entries also broke `endsWith("/")` directory-mime detection in the run CLI and poisoned drill-down queries with backslashes that can never match the `/`-normalized index. | Keep our retry/reset scan, success-only binary cache, `"/"` suffixes, and query normalization; take upstream's surrounding changes. If upstream reworks the fallback or re-enables fff on win32, preserve the invariants: entry paths are `/`-normalized with `/`-suffixed directories on every platform, index-scan failures are logged and retried, a failed binary download is never cached, and the GitHub download keeps its CoDev-mirror fallback — the mirror filename scheme must match what codev-landing-page hosts under `public/docs/code/downloads` (and codevhub's `RG_VERSION` pin in its `src/lib/ripgrep.ts`); bump all three together when changing `VERSION`. | | `packages/tui/src/context/permission.tsx`, `packages/tui/src/app.tsx`, `packages/tui/src/ui/dialog-confirm.tsx`, `packages/tui/src/ui/toast.tsx` | Auto-approve consent gate. permission.tsx: mode always starts `"normal"` (upstream initialized it from `args.auto`); `toggle()` replaced by `set(mode)` plus a per-run `consented` flag + `consent()`. app.tsx: shared `enableAutoApprove()` (title in the `AUTO_APPROVE_TITLE` module const, body from an `autoApproveMessage` accessor, and a shorter one-line `autoApproveToast` accessor for the toast, which only has a few seconds on screen; both redden the word "dangerous" with `theme.error` and are passed unevaluated because renderables can only be created during render) shows a `DialogConfirm` opened on cancel and, on confirm, a warning toast under the same title, then sets the mode; both entry points funnel through it — an `onMount` when `args.auto` is set, and the `permission.mode` command (shift+tab / palette), which enables via the gate and disables directly. Consent is asked once per run; later toggles are silent. dialog-confirm.tsx: optional `defaultChoice` prop + 5th `DialogConfirm.show` arg selecting the initially-active button (omitted keeps upstream's confirm-first default); `message` widened to `JSX.Element \| (() => JSX.Element)` and called at the render site. toast.tsx: same `message` widening, resolved by a `message()` helper in `Toast()` (fork-only test file: `packages/tui/test/auto-approve-consent.view.test.tsx`). | Auto-approve is dangerous, so `--auto` (and its hidden aliases) and the first shift+tab into auto require explicit confirmation; declining or dismissing the dialog stays in manual mode, opening on cancel keeps a stray return from granting it, and the risk word is red like the `auto` badge. | Keep the consent flag + gate flow, the `toggle()` removal, the `defaultChoice` prop, and the accessor-aware `message` on both dialog and toast; take upstream's surrounding changes. If upstream adds new writers of the permission mode, route their enable path through `enableAutoApprove()`. | +| `packages/opencode/src/session/system.ts` | Upstream's `provider()` branch chain is renamed `select()` (body otherwise untouched) and `provider()` now returns `select(model).map(rebrand)`; `environment()` gained a leading line naming the product and the `codev` command | The prompts the model receives are upstream's, so it identified itself as opencode and sent users to upstream's issue tracker and docs. Rewriting names as the prompt is assembled keeps the `prompt/*.txt` files byte-identical to upstream, so their hunks always apply and a newly added upstream brand mention is rebranded automatically. The identity also goes in the environment block because an agent's own `prompt` replaces the provider prompt wholesale (see `llm/request.ts`), and the environment block is on every path. | Keep the wrapper and the identity line; take upstream's `select()` body verbatim — never hand-rebrand the prompt text it returns. A new upstream prompt file or branch needs nothing here; `test/session/brand.test.ts` fails if its wording isn't covered by the rules in `packages/codev-brand`. | +| `packages/opencode/src/tool/registry.ts` | The assembled tool `description` is wrapped in `rebrand(...)` | Tool descriptions are model-visible and name the product (`tool/lsp.txt`). One wrapper at the point where every tool's final description is built covers all of them, including text upstream adds later. | Keep the wrapper around whatever expression upstream's description becomes; take upstream's surrounding changes. | +| `packages/core/src/plugin/skill.ts`, `packages/opencode/src/skill/index.ts`, `packages/core/test/plugin/skill.test.ts` | Built-in skill `customize-opencode` → `customize-codev` in both registrations (plus `location` `/builtin/customize-codev.md`); descriptions rewritten to name CoDev Code; the markdown body wrapped in `rebrand(...)`; `Skill.get`/`Skill.require` resolve the old name through a `lookup` helper; test expectations updated | The skill's name and description are listed in the system prompt, so they are among the most visible brand strings the model sees; the body is upstream's config guide. The alias keeps existing configs and `skill` permission rules that name `customize-opencode` working. | Keep our name, the alias, and the `rebrand` wrapper; take upstream's `customize-opencode.md` body verbatim (it is deliberately not hand-edited for branding). | +| `packages/opencode/src/command/template/initialize.txt` | "OpenCode" → "CoDev Code" (×2) | `/init` writes this guidance into the user's `AGENTS.md`. Edited in place rather than wrapped because the file is already fork-divergent (config-file rename row) and barely churns. | Keep our two strings; take upstream's surrounding text and rebrand any new "OpenCode" mention. | +| `packages/core/package.json` | Added dependency `"@codev/brand": "workspace:*"` | The built-in skill registration rebrands its body through the fork-only `packages/codev-brand`. | Keep our dependency line; take upstream's other fields. | ## Known rebrand gaps @@ -201,13 +206,23 @@ from the squash message first, or use a plain merge. `desktop-theme.json` ids, service endpoints (`api.opencode.ai` exchange, `opencode.ai/s/` share links, social-cards, `opencode.ai/changelog.json` highlights fetch), the MCP OAuth `client_uri`, favicon image sources (`opencode.ai/favicon*` in `app/src/entry.tsx` + - `pages/layout/helpers.ts` — see the binary-assets item above), prompt-file mentions, and + `pages/layout/helpers.ts` — see the binary-assets item above), and `opencode.ai` URLs inside docs content (web-docs row). The Help submenu's feedback/bug links target the fork's issue templates and its "Support Forum" Discord item is deleted (docs-site links row); crash-report, security-advisory, and contribution links target `github.com/quickbeard/codev-code` (project-repo links row), while upstream-repo URLs that are genuine references (attribution, upstream issue citations, fixtures, disabled publish - channels, prompt files, unshipped console/stats/enterprise/desktop surfaces) stay. + channels, unshipped console/stats/enterprise/desktop surfaces) stay. +- **Model-visible text**: everything the model reads — provider system prompts, tool + descriptions, and the built-in config skill — is rebranded at assembly time by the + fork-only `packages/codev-brand`, so the `prompt/*.txt` and `customize-opencode.md` + sources stay byte-identical to upstream. Add new rules there rather than editing prompt + text; `packages/opencode/test/session/brand.test.ts` scans every embedded prompt and tool + description and fails when a brand mention isn't covered, which is the tripwire for an + upstream sync that introduces new wording. Deliberately preserved by those rules (they are + identifiers, not branding): `@opencode-ai/*` imports, published plugin names such as + `opencode-gemini-auth`, the `$schema` URL `https://opencode.ai/config.json`, `opencode://`, + `ai.opencode.*`, and `opencode.db`. - **Internal names**: `@opencode-ai/*` package names and file names inside `~/.local/share/codev` (`opencode.db` in released builds; `opencode-.db`, e.g. `opencode-local.db`, when running from source — see `core/src/database/database.ts`) diff --git a/bun.lock b/bun.lock index 2a9259ca47..eb2c110f83 100644 --- a/bun.lock +++ b/bun.lock @@ -155,6 +155,13 @@ "@typescript/native-preview": "catalog:", }, }, + "packages/codev-brand": { + "name": "@codev/brand", + "version": "0.0.1", + "devDependencies": { + "@types/bun": "catalog:", + }, + }, "packages/codev-gateway": { "name": "@codev/gateway", "version": "0.0.1", @@ -319,6 +326,7 @@ "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", + "@codev/brand": "workspace:*", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", @@ -594,6 +602,7 @@ "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@clack/prompts": "1.0.0-alpha.1", + "@codev/brand": "workspace:*", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@ff-labs/fff-bun": "0.9.4", @@ -1415,6 +1424,8 @@ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251008.0", "", {}, "sha512-dZLkO4PbCL0qcCSKzuW7KE4GYe49lI12LCfQ5y9XeSwgYBoAUbwH4gmJ6A0qUIURiTJTkGkRkhVPqpq2XNgYRA=="], + "@codev/brand": ["@codev/brand@workspace:packages/codev-brand"], + "@codev/gateway": ["@codev/gateway@workspace:packages/codev-gateway"], "@corvu/dialog": ["@corvu/dialog@0.2.4", "", { "dependencies": { "@corvu/utils": "~0.4.2", "solid-dismissible": "~0.1.1", "solid-focus-trap": "~0.1.8", "solid-presence": "~0.2.0", "solid-prevent-scroll": "~0.1.10" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-n54vJq+fOy8GVrnYBdJpD6JXNuyx7LOeMrRxwzAvZnYGpW8+AA12tnb/P/2emJj/HjOO5otheGKb0breshdFlA=="], diff --git a/packages/codev-brand/package.json b/packages/codev-brand/package.json new file mode 100644 index 0000000000..34d8653677 --- /dev/null +++ b/packages/codev-brand/package.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/package", + "name": "@codev/brand", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit", + "test": "bun test" + }, + "devDependencies": { + "@types/bun": "catalog:" + }, + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/codev-brand/src/index.ts b/packages/codev-brand/src/index.ts new file mode 100644 index 0000000000..ce989cee9d --- /dev/null +++ b/packages/codev-brand/src/index.ts @@ -0,0 +1,66 @@ +export const PRODUCT_NAME = "CoDev Code" +export const COMMAND_NAME = "codev" +export const DOCS_URL = "https://netmind.viettel.vn/codev/docs" +export const REPO_URL = "https://github.com/quickbeard/codev-code" +export const ISSUES_URL = "https://github.com/quickbeard/codev-code/issues" + +// Upstream names that must survive verbatim. Package ids and the config JSON +// Schema URL are real identifiers the fork deliberately keeps (see "Known +// rebrand gaps" in DIVERGENCES.md), so a blind rename would break the model's +// advice: it would tell users to `import "@codev/plugin"` or point their editor +// at a schema URL that does not exist. Trade-off: prose that hyphenates the +// upstream name ("opencode-managed") is preserved too and stays upstream-branded. +const PRESERVED = + /(@opencode-ai\/[\w.-]+|https:\/\/opencode\.ai\/config\.json|opencode-[\w][\w.@-]*|opencode:\/\/|ai\.opencode\.[\w]+|api\.opencode\.ai|opencode\.ai\/(?:zen|go|auth|s\/|changelog\.json)|opencode\.db)/g + +// Longest match first: the URL rules have to consume their hosts before the +// bare-name rule can rewrite the word inside them. +const RULES: readonly (readonly [RegExp, string])[] = [ + [/https:\/\/github\.com\/anomalyco\/opencode\/issues/g, ISSUES_URL], + [/https:\/\/github\.com\/anomalyco\/opencode/g, REPO_URL], + [/https:\/\/opencode\.ai(?:\/docs)?/g, DOCS_URL], + [/\bopencode\.ai\b/g, DOCS_URL.replace("https://", "")], + [/\bopencode\.jsonc\b/g, "codev.jsonc"], + [/\bopencode\.json\b/g, "codev.json"], + [/\.opencode\b/g, ".codev"], + // Command invocations keep the binary name; everything else is the product name. + [ + /\bopencode (?=(?:auth|run|serve|models|agent|plugin|mcp|upgrade|tui|github|configure|codegraph|debug|stats)\b)/g, + `${COMMAND_NAME} `, + ], + [/\bOpenCode\b/g, PRODUCT_NAME], + [/\bopencode\b/g, PRODUCT_NAME], +] + +// Inputs are module-level prompt constants and tool descriptions, so the key +// space is bounded by the number of distinct prompts loaded in a process. +const cache = new Map() + +// `split` on a regex with one capture group interleaves the matched spans at odd +// indices, so preserved names pass through without ever being offered to RULES. +const partition = (text: string) => text.split(PRESERVED) + +/** Rewrite upstream product names in model-visible text to the fork's. */ +export function rebrand(text: string) { + const cached = cache.get(text) + if (cached !== undefined) return cached + const result = partition(text) + .map((part, index) => + index % 2 === 1 ? part : RULES.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), part), + ) + .join("") + cache.set(text, result) + return result +} + +/** + * Lines still carrying the upstream name after {@link rebrand}, ignoring + * preserved identifiers. The guard test fails on a non-empty result, so an + * upstream sync that introduces a new brand phrasing surfaces as a test failure + * instead of shipping silently. + */ +export function brandLeaks(text: string) { + return partition(rebrand(text)) + .filter((_, index) => index % 2 === 0) + .flatMap((part) => part.split("\n").filter((line) => /opencode/i.test(line))) +} diff --git a/packages/codev-brand/test/brand.test.ts b/packages/codev-brand/test/brand.test.ts new file mode 100644 index 0000000000..cb68bd0470 --- /dev/null +++ b/packages/codev-brand/test/brand.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from "bun:test" +import { brandLeaks, DOCS_URL, ISSUES_URL, PRODUCT_NAME, rebrand } from "../src" + +describe("rebrand", () => { + test("rewrites the identity line", () => { + expect(rebrand("You are opencode, an interactive CLI tool that helps users.")).toBe( + `You are ${PRODUCT_NAME}, an interactive CLI tool that helps users.`, + ) + expect(rebrand("You are OpenCode, the best coding agent on the planet.")).toBe( + `You are ${PRODUCT_NAME}, the best coding agent on the planet.`, + ) + }) + + test("routes support links at the fork", () => { + expect(rebrand("report the issue at https://github.com/anomalyco/opencode/issues")).toBe( + `report the issue at ${ISSUES_URL}`, + ) + expect(rebrand("report the issue at\n https://github.com/anomalyco/opencode")).toBe( + "report the issue at\n https://github.com/quickbeard/codev-code", + ) + expect(rebrand("docs at https://opencode.ai/docs")).toBe(`docs at ${DOCS_URL}`) + expect(rebrand("docs at https://opencode.ai")).toBe(`docs at ${DOCS_URL}`) + }) + + test("preserves identifiers the fork deliberately keeps", () => { + const preserved = [ + 'import type { Plugin } from "@opencode-ai/plugin"', + '"$schema": "https://opencode.ai/config.json"', + '"opencode-gemini-auth", "opencode-foo@1.2.3", ["opencode-bar", { "option": "value" }]', + "the opencode:// deep link and the ai.opencode.desktop appId", + "state lives in opencode.db", + ] + for (const line of preserved) expect(rebrand(line)).toBe(line) + }) + + test("renames config files and directories", () => { + expect(rebrand("`./opencode.json`, `./opencode.jsonc`, or `.opencode/opencode.json`")).toBe( + "`./codev.json`, `./codev.jsonc`, or `.codev/codev.json`", + ) + }) + + test("keeps the binary name in command invocations", () => { + expect(rebrand("run `opencode auth login`, then restart opencode")).toBe( + `run \`codev auth login\`, then restart ${PRODUCT_NAME}`, + ) + }) + + test("is idempotent", () => { + const once = rebrand("You are opencode. Docs: https://opencode.ai/docs") + expect(rebrand(once)).toBe(once) + }) +}) + +describe("brandLeaks", () => { + test("reports lines the rules did not cover", () => { + expect(brandLeaks("You are opencode.")).toEqual([]) + expect(brandLeaks('import { x } from "@opencode-ai/core"')).toEqual([]) + expect(brandLeaks("Ask the OPENCODE team.")).toEqual(["Ask the OPENCODE team."]) + }) +}) diff --git a/packages/codev-brand/tsconfig.json b/packages/codev-brand/tsconfig.json new file mode 100644 index 0000000000..5b7aaab788 --- /dev/null +++ b/packages/codev-brand/tsconfig.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "lib": ["ESNext"], + "noUncheckedIndexedAccess": false + } +} diff --git a/packages/core/package.json b/packages/core/package.json index f4c3f46bc0..1d561a8232 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -82,6 +82,7 @@ "@ai-sdk/vercel": "2.0.39", "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", + "@codev/brand": "workspace:*", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@effect/sql-sqlite-bun": "catalog:", diff --git a/packages/core/src/plugin/skill.ts b/packages/core/src/plugin/skill.ts index d93f3e98be..1d57960f7b 100644 --- a/packages/core/src/plugin/skill.ts +++ b/packages/core/src/plugin/skill.ts @@ -3,12 +3,15 @@ export * as SkillPlugin from "./skill" import { define } from "./internal" +import { rebrand } from "@codev/brand" import { Effect } from "effect" import { AbsolutePath } from "../schema" import { SkillV2 } from "../skill" import customizeOpencodeContent from "./skill/customize-opencode.md" with { type: "text" } -export const CustomizeOpencodeContent = customizeOpencodeContent +// The markdown body stays byte-identical to upstream; product names are rewritten +// on the way out so upstream edits to it keep applying cleanly. +export const CustomizeOpencodeContent = rebrand(customizeOpencodeContent) export const Plugin = define({ id: "skill", @@ -18,10 +21,10 @@ export const Plugin = define({ SkillV2.EmbeddedSource.make({ type: "embedded", skill: SkillV2.Info.make({ - name: "customize-opencode", + name: "customize-codev", description: - "Use ONLY when the user is editing or creating opencode's own configuration: codev.json, codev.jsonc, files under .codev/, or files under ~/.config/codev/. 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"), + "Use ONLY when the user is editing or creating CoDev Code's own configuration: codev.json, codev.jsonc, files under .codev/, or files under ~/.config/codev/. Also use when creating or fixing CoDev Code 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 CoDev Code itself.", + location: AbsolutePath.make("/builtin/customize-codev.md"), content: CustomizeOpencodeContent, }), }), diff --git a/packages/core/test/plugin/skill.test.ts b/packages/core/test/plugin/skill.test.ts index 61bee856b9..7b8d3c2ef3 100644 --- a/packages/core/test/plugin/skill.test.ts +++ b/packages/core/test/plugin/skill.test.ts @@ -9,17 +9,26 @@ 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-codev skill", () => Effect.gen(function* () { const skill = yield* SkillV2.Service yield* SkillPlugin.Plugin.effect(host({ skill: { ...skill, reload: skill.reload } })) expect(yield* skill.list()).toContainEqual( expect.objectContaining({ - name: "customize-opencode", - description: expect.stringContaining("opencode's own configuration"), + name: "customize-codev", + description: expect.stringContaining("CoDev Code's own configuration"), }), ) }), ) + + it.effect("serves the skill body under the fork's name", () => + Effect.gen(function* () { + expect(SkillPlugin.CustomizeOpencodeContent).toContain("Customizing CoDev Code") + // Identifiers the fork deliberately keeps must survive the rebrand. + expect(SkillPlugin.CustomizeOpencodeContent).toContain("https://opencode.ai/config.json") + expect(SkillPlugin.CustomizeOpencodeContent).toContain("@opencode-ai/plugin") + }), + ) }) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e24759185f..8139b74658 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -77,6 +77,7 @@ "@ai-sdk/xai": "3.0.102", "@aws-sdk/credential-providers": "3.1057.0", "@clack/prompts": "1.0.0-alpha.1", + "@codev/brand": "workspace:*", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@ff-labs/fff-bun": "0.9.4", diff --git a/packages/opencode/src/command/template/initialize.txt b/packages/opencode/src/command/template/initialize.txt index 8d61f0e37d..2de549e629 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 CoDev Code 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 `codev.json` +- repo-local CoDev Code config such as `codev.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. diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index b94e794265..a4d429d999 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -1,4 +1,5 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { COMMAND_NAME, PRODUCT_NAME, rebrand } from "@codev/brand" import { Context, Effect, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" @@ -25,6 +26,10 @@ import { MCP } from "@/mcp" import { PermissionV1 } from "@opencode-ai/core/v1/permission" export function provider(model: Provider.Model) { + return select(model).map(rebrand) +} + +function select(model: Provider.Model) { if (model.api.id.includes("muse-spark")) return [PROMPT_META] if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3")) return [PROMPT_BEAST] @@ -63,6 +68,10 @@ const layer = Layer.effect( return (yield* (yield* Reference.Service).list()).filter((reference) => reference.description !== undefined) }).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) })))) return [ + // The provider prompt already introduces the product, but a custom agent + // `prompt` replaces that prompt wholesale (see SystemPrompt.provider's caller), + // and the environment block is on every path — so identity is asserted here too. + `You are ${PRODUCT_NAME}, an interactive CLI tool that helps users with software engineering tasks. Users launch you with the \`${COMMAND_NAME}\` command.`, [ `You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`, `Here is some useful information about the environment you are running in:`, diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 674e1ffbd7..e8dc2d450f 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -45,14 +45,18 @@ function externalDirPriority(dir: string): number { return dir === CLAUDE_EXTERNAL_DIR ? SkillPriority.claudeExternal : SkillPriority.agentsExternal } -// Built-in skill that ships with opencode. The model's intuition for what an -// codev.json should look like is often wrong, and opencode hard-fails on +// Built-in skill that ships with CoDev Code. The model's intuition for what a +// codev.json should look like is often wrong, and CoDev Code hard-fails on // invalid config, so users hit cryptic startup errors. Loading this skill -// when the model is asked to touch opencode's own config files gives it the +// when the model is asked to touch CoDev Code's own config files gives it the // actual schemas instead of guesses. -const CUSTOMIZE_CODEV_SKILL_NAME = "customize-opencode" +const CUSTOMIZE_CODEV_SKILL_NAME = "customize-codev" +// Upstream's name for the same skill; still resolvable so existing configs and +// permission rules that reference it keep working. +const CUSTOMIZE_CODEV_SKILL_ALIAS = "customize-opencode" const CUSTOMIZE_CODEV_SKILL_DESCRIPTION = - "Use ONLY when the user is editing or creating opencode's own configuration: codev.json, codev.jsonc, files under .codev/, or files under ~/.config/codev/. Also use when creating or fixing opencode agents, subagents, 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." + "Use ONLY when the user is editing or creating CoDev Code's own configuration: codev.json, codev.jsonc, files under .codev/, or files under ~/.config/codev/. Also use when creating or fixing CoDev Code agents, subagents, 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 CoDev Code itself." +// Already rebranded by the core export; the markdown source stays upstream's. const CUSTOMIZE_CODEV_SKILL_BODY = SkillPlugin.CustomizeOpencodeContent export const Info = Schema.Struct({ @@ -341,14 +345,17 @@ const layer = Layer.effect( }), ) + const lookup = (s: State, name: string) => + s.skills[name] ?? (name === CUSTOMIZE_CODEV_SKILL_ALIAS ? s.skills[CUSTOMIZE_CODEV_SKILL_NAME] : undefined) + const get = Effect.fn("Skill.get")(function* (name: string) { const s = yield* InstanceState.get(state) - return s.skills[name] + return lookup(s, name) }) const require = Effect.fn("Skill.require")(function* (name: string) { const s = yield* InstanceState.get(state) - const info = s.skills[name] + const info = lookup(s, name) if (info) return info return yield* new NotFoundError({ name, available: Object.keys(s.skills).toSorted() }) }) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 15acc757f3..11864ee47f 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -1,5 +1,6 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { httpClient } from "@opencode-ai/core/effect/app-node-platform" +import { rebrand } from "@codev/brand" import { Ripgrep } from "@opencode-ai/core/ripgrep" import { PlanExitTool } from "./plan" import { Session } from "@/session/session" @@ -317,13 +318,15 @@ const layer = Layer.effect( : undefined return { id: tool.id, - description: [ - output.description, - tool.id === TaskTool.id ? yield* describeTask(input.agent) : undefined, - tool.id === "execute" ? codeModeDescription : undefined, - ] - .filter(Boolean) - .join("\n"), + description: rebrand( + [ + output.description, + tool.id === TaskTool.id ? yield* describeTask(input.agent) : undefined, + tool.id === "execute" ? codeModeDescription : undefined, + ] + .filter(Boolean) + .join("\n"), + ), parameters: output.parameters, jsonSchema, execute: tool.execute, diff --git a/packages/opencode/test/session/brand.test.ts b/packages/opencode/test/session/brand.test.ts new file mode 100644 index 0000000000..b93321f54e --- /dev/null +++ b/packages/opencode/test/session/brand.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from "bun:test" +import { readFileSync } from "fs" +import path from "path" +import { brandLeaks, PRODUCT_NAME } from "@codev/brand" +import { SkillPlugin } from "@opencode-ai/core/plugin/skill" +import type { Provider } from "../../src/provider/provider" +import { SystemPrompt } from "../../src/session/system" + +const SRC = path.join(import.meta.dir, "../../src") + +const model = (id: string) => ({ api: { id }, providerID: "test" }) as Provider.Model + +// One id per branch of SystemPrompt.provider, so a prompt file that stops being +// reachable — or a new branch — shows up here. +const MODELS = [ + "MiniMax/MiniMax-M3", + "claude-opus-4", + "gemini-2.5-pro", + "gpt-5", + "gpt-5-codex", + "gpt-4o", + "kimi-k2-thinking", + "trinity-large", + "meta/muse-spark-preview", +] + +describe("brand", () => { + test("every provider prompt introduces the fork by name", () => { + for (const id of MODELS) { + const prompt = SystemPrompt.provider(model(id))[0] + expect(prompt).toContain(PRODUCT_NAME) + expect(brandLeaks(prompt)).toEqual([]) + } + }) + + test("provider prompts route support at the fork", () => { + const prompt = SystemPrompt.provider(model("MiniMax/MiniMax-M3"))[0] + expect(prompt).toContain("https://github.com/quickbeard/codev-code/issues") + expect(prompt).toContain("https://netmind.viettel.vn/codev/docs") + expect(prompt).not.toContain("anomalyco") + }) + + // The tripwire: prompt and tool-description files stay byte-identical to + // upstream, so an upstream sync that introduces a new phrasing of the upstream + // product name fails here instead of shipping to users unrebranded. + test("the rebrand rules cover every embedded prompt and tool description", () => { + const files = [...new Bun.Glob("**/*.txt").scanSync(SRC)].toSorted() + expect(files.length).toBeGreaterThan(20) + + const leaks = files.flatMap((file) => + brandLeaks(readFileSync(path.join(SRC, file), "utf8")).map((line) => `${file}: ${line.trim()}`), + ) + expect(leaks).toEqual([]) + }) + + test("the built-in customize skill is rebranded", () => { + expect(brandLeaks(SkillPlugin.CustomizeOpencodeContent)).toEqual([]) + expect(SkillPlugin.CustomizeOpencodeContent).toContain(PRODUCT_NAME) + }) +}) From adea1042cf368a8bb3b965df0d5490713ca156f8 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 7 Aug 2026 12:28:12 +0700 Subject: [PATCH 2/5] fix(app): point docs links at the locale-routed docs base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every browser-opened docs link used base `netmind.viettel.vn/codev/docs`, but the docs site is locale-routed and the Code docs live under `/code`, so the real base is `/codev/en/docs/code`. The root only redirected (307) — the deep links did not: settings theme "Learn more" (v1 + v2), the custom-provider dialog link, and the CLI Cloudflare-gateway hint all 404'd. Verified against the live site: every new URL resolves 200, and the `#custom-provider` / `#cloudflare-ai-gateway` anchor ids exist on the new providers page. `codev-brand`'s DOCS_URL moves to the same base and is now base64-encoded via `atob`, matching codev-gateway/src/const.ts and core/src/ripgrep/binary.ts — host out of casual greps, not a secret. The ripgrep download mirror deliberately stays on `/codev/docs/code/downloads`: it is a `public/` static asset, not locale-routed content, and 404s under `/en/docs/code`. Moving it would brick the offline-rg fallback. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 15 ++++++++++++++- DIVERGENCES.md | 7 ++++--- .../app/src/components/dialog-custom-provider.tsx | 2 +- packages/app/src/components/settings-general.tsx | 2 +- .../app/src/components/settings-v2/general.tsx | 2 +- packages/app/src/desktop-menu.ts | 2 +- packages/app/src/pages/home.tsx | 4 ++-- packages/app/src/pages/layout.tsx | 2 +- packages/codev-brand/src/index.ts | 4 +++- packages/opencode/src/cli/cmd/github.handler.ts | 2 +- packages/opencode/src/cli/cmd/providers.ts | 2 +- packages/opencode/test/session/brand.test.ts | 2 +- packages/tui/src/app.tsx | 2 +- 13 files changed, 32 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9145cf1a38..1396632681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ file is the human-readable summary of what changed and why. feedback URL (`github.com/anomalyco/opencode`) and the docs URL (`opencode.ai`) were all upstream's. The model now introduces itself as CoDev Code and routes support at `github.com/quickbeard/codev-code/issues` and - `netmind.viettel.vn/codev/docs`. The identity is also stated in the + `netmind.viettel.vn/codev/en/docs/code`. The identity is also stated in the environment block, so agents that define their own `prompt` — which replaces the whole provider prompt — get it too. @@ -35,6 +35,19 @@ file is the human-readable summary of what changed and why. - **`/init` writes "CoDev Code"** — the AGENTS.md template it generates no longer describes the tool as OpenCode. +### App / TUI + +- **Docs links open the docs instead of a 404** — every link the app opens in + your browser pointed at `netmind.viettel.vn/codev/docs`, but the docs site is + locale-routed and the Code docs live under `/code`, so the real base is + `/codev/en/docs/code`. The root only redirected, but the deep links did not: + settings theme "Learn more" (both settings screens), the custom-provider + dialog link, and the CLI's Cloudflare-gateway hint were all landing on a 404. + All docs links — TUI "Open docs", the desktop Documentation menu item, the + sidebar Help buttons, the GitHub-agent hint, and the ones above — now use the + new base. (The ripgrep download mirror is unaffected; it is a static asset, + not a docs page, and keeps its own path.) + ## 1.18.4-7 ### TUI diff --git a/DIVERGENCES.md b/DIVERGENCES.md index df0c19853f..a6a664425e 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -142,7 +142,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/assets/help/` (introducing-tabs.mp4, placeholder.png, home.png, tabs.png — whole directory) | Deleted | The 13 MB promo video's and the v1.18 home/tabs screenshots' only consumer was the removed `TabsInfoPopup` (row above) and they bloat every clone; `placeholder.png` is referenced by nothing. | Keep deleted (`git rm` the upstream side of modify/delete conflicts); delete new upstream assets under this directory whose only consumer is the removed popup. | | `packages/app/src/desktop-menu.ts`, `packages/app/src/components/windows-app-menu.tsx` | App-menu label "OpenCode" → "CoDev Code"; "OpenCode Documentation" item → "CoDev Code Documentation" (href `opencode.ai/docs` stays — real URL); trigger `aria-label` "OpenCode menu" → "CoDev Code menu" (×2) | Visible menu branding, incl. screen-reader labels. | Keep our labels; take upstream's surrounding menu changes. Rebrand any new "OpenCode" label/aria-label. | | Hardcoded product-string rebrand in TUI + web app: `packages/tui/src/app.tsx`, `packages/tui/src/routes/session/permission.tsx`, `packages/tui/src/attention.ts` (+ `packages/opencode/test/cli/cmd/tui/attention.test.ts`), `packages/app/src/wsl/settings-model.ts` (+ `.test.ts`), `packages/ui/src/components/favicon.tsx` | User-visible "OpenCode" strings outside the i18n dicts → fork brand: update-complete toast "Successfully updated to OpenCode v…" → "CoDev Code"; permission dialog "…until OpenCode is restarted" → "CoDev Code" (the sibling patterns line was already rebranded); OS-notification fallback title `opencode` → `codev` and built-in sound-pack display name "OpenCode Default" → "CoDev Code Default" (pack id `opencode.default` stays — internal identifier; the three default-title expectations in `packages/opencode/test/cli/cmd/tui/attention.test.ts` follow, while its explicit-title pass-through fixture stays `opencode`); WSL settings actions "Install/Update OpenCode" → "…CoDev Code" (+ test expectations/titles; fixture paths `/usr/local/bin/opencode` stay); PWA `apple-mobile-web-app-title` "OpenCode" → "CoDev Code" (matches the rebranded `site.webmanifest`, favicon-assets row) | These render in surfaces the fork ships (TUI + embedded web UI); the i18n-dict strings remain a known gap (UI branding bullet below). | Keep our strings; take upstream's surrounding changes. Rebrand any new hardcoded user-visible "OpenCode" string in these surfaces; leave identifiers, URLs, and fixture paths. | -| Browser-opened docs-site links: `packages/tui/src/app.tsx`, `packages/app/src/desktop-menu.ts`, `packages/app/src/components/{settings-general,settings-v2/general,dialog-custom-provider}.tsx`, `packages/app/src/pages/{home,layout,error}.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/opencode/src/cli/cmd/providers.ts` | Links the app opens in the user's browser repointed from `opencode.ai` to the fork's hosted docs, base `https://netmind.viettel.vn/codev/docs` (no trailing slash on the root; deep links keep their paths): TUI "Open docs" command and app Documentation menu item → docs root; settings theme "Learn more" (v1 + v2) → `…/docs/themes/`; custom-provider dialog link → `…/docs/providers/#custom-provider`; CLI Cloudflare-gateway hint → `…/docs/providers/#cloudflare-ai-gateway`; home/layout sidebar Help buttons `opencode.ai/desktop-feedback` → docs root. The error page's report button `opencode.ai/desktop-feedback` → `github.com/quickbeard/codev-code/issues` instead (it's a report action, not a docs lookup), icon `discord` → `github`, and the en/es `error.page.report.*` strings → "…CoDev Code team" / "on GitHub". Help submenu (rendered by the shipped web UI's Windows app menu too): "Support Forum" (upstream Discord invite) deleted — the fork has no forum; "Share Feedback"/"Report a Bug" → `github.com/quickbeard/codev-code/issues/new?template=feature-request.yml`/`bug-report.yml` (the fork's template names are hyphenated, unlike upstream's underscored ones). | Fork users should land on the fork's docs and report issues to the fork, not upstream's site/Discord. NOT repointed: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth`), `$schema`/theme-schema ids, API/share/changelog endpoints, MCP OAuth `client_uri`, prompt-file mentions. | Keep our URLs, icon, and strings; take upstream's surrounding changes. Repoint any new browser-opened `opencode.ai` docs/help link to the fork docs base; leave the NOT-repointed list. | +| Browser-opened docs-site links: `packages/tui/src/app.tsx`, `packages/app/src/desktop-menu.ts`, `packages/app/src/components/{settings-general,settings-v2/general,dialog-custom-provider}.tsx`, `packages/app/src/pages/{home,layout,error}.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/opencode/src/cli/cmd/{providers,github.handler}.ts` | Links the app opens in the user's browser repointed from `opencode.ai` to the fork's hosted docs, base `https://netmind.viettel.vn/codev/en/docs/code` (the site is locale-routed and the Code docs live under `/code`; no trailing slash anywhere — the site 308-redirects `…/themes/` to `…/themes`): TUI "Open docs" command and app Documentation menu item → docs root; settings theme "Learn more" (v1 + v2) → `…/code/themes`; custom-provider dialog link → `…/code/providers#custom-provider`; CLI Cloudflare-gateway hint → `…/code/providers#cloudflare-ai-gateway`; GitHub-agent hint → `…/code/github`; home/layout sidebar Help buttons `opencode.ai/desktop-feedback` → docs root. The error page's report button `opencode.ai/desktop-feedback` → `github.com/quickbeard/codev-code/issues` instead (it's a report action, not a docs lookup), icon `discord` → `github`, and the en/es `error.page.report.*` strings → "…CoDev Code team" / "on GitHub". Help submenu (rendered by the shipped web UI's Windows app menu too): "Support Forum" (upstream Discord invite) deleted — the fork has no forum; "Share Feedback"/"Report a Bug" → `github.com/quickbeard/codev-code/issues/new?template=feature-request.yml`/`bug-report.yml` (the fork's template names are hyphenated, unlike upstream's underscored ones). | Fork users should land on the fork's docs and report issues to the fork, not upstream's site/Discord. NOT repointed: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth`), `$schema`/theme-schema ids, API/share/changelog endpoints, MCP OAuth `client_uri`, prompt-file mentions. Also NOT on this base: the ripgrep mirror (`/codev/docs/code/downloads/…`, see its own row) — it is a `public/` static asset, not locale-routed content, and 404s under `/en/docs/code`. | Keep our URLs, icon, and strings; take upstream's surrounding changes. Repoint any new browser-opened `opencode.ai` docs/help link to the fork docs base; leave the NOT-repointed list. | | `packages/desktop/src/main/index.ts`, `packages/desktop/src/main/windows.ts` | APP_NAMES + window title → "CoDev Code" (Dev/Beta variants too) | Desktop app display name. | Keep our names. | | `packages/desktop/electron-builder.config.ts` | `productName` ×3 → "CoDev Code…"; artifactName → `codev-code-desktop-…`; protocol display name | Installer/product branding. appId (`ai.opencode.desktop`) and the `opencode://` deep-link scheme stay upstream — identity plumbing wired into main/index.ts and app deep-links; do not rename casually. | Keep our names; take upstream's other config. | | `packages/ui/src/assets/favicon/` (favicon-v3.svg, site.webmanifest, favicon-96x96-v3.png, apple-touch-icon-v3.png, web-app-manifest-192/512.png) | New codev mark: blocky "C" in #00cdcd on dark; manifest name "CoDev Code"/"codev" | Web/desktop favicon + PWA branding (served via packages/app/public symlinks). | Keep our assets (binary: take ours wholesale). | @@ -199,8 +199,9 @@ from the squash message first, or use a plain merge. `ai.opencode.desktop` appId / `opencode://` scheme (identity plumbing wired into deep-link handling). - **URLs**: links the app opens in the browser point at the fork — docs at - `https://netmind.viettel.vn/codev/docs` (root takes no trailing slash; - deep links keep their paths) and issue reports at `github.com/quickbeard/codev-code/issues` + `https://netmind.viettel.vn/codev/en/docs/code` (the site is locale-routed and + the Code docs live under `/code`; no trailing slash anywhere — the site + 308-redirects `…/themes/` to `…/themes`) and issue reports at `github.com/quickbeard/codev-code/issues` (see the docs-site links table row). Still upstream, deliberately: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth` — external products), `$schema`/`theme.json`/ `desktop-theme.json` ids, service endpoints (`api.opencode.ai` exchange, `opencode.ai/s/` diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index f2159d1aac..0fcee8f2bd 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -184,7 +184,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {

{language.t("provider.custom.description.prefix")} - + {language.t("provider.custom.description.link")} {language.t("provider.custom.description.suffix")} diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 215f1a8518..82bb46d3bc 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -479,7 +479,7 @@ export const SettingsGeneral: Component = () => { description={ <> {language.t("settings.general.row.theme.description")}{" "} - {language.t("common.learnMore")} + {language.t("common.learnMore")} } > diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index 4ef5d8282e..c0e2f55c60 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -438,7 +438,7 @@ export const SettingsGeneralV2: Component<{ description={ <> {language.t("settings.general.row.theme.description")}{" "} - + {language.t("common.learnMore")} diff --git a/packages/app/src/desktop-menu.ts b/packages/app/src/desktop-menu.ts index 9528359570..3ac2a2d47d 100644 --- a/packages/app/src/desktop-menu.ts +++ b/packages/app/src/desktop-menu.ts @@ -200,7 +200,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [ id: "help", label: "Help", items: [ - { type: "item", label: "CoDev Code Documentation", href: "https://netmind.viettel.vn/codev/docs" }, + { type: "item", label: "CoDev Code Documentation", href: "https://netmind.viettel.vn/codev/en/docs/code" }, { type: "item", label: "Export Logs...", command: "logs.export" }, { type: "separator" }, { diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 4b6b4c0212..7a360d5f44 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -673,7 +673,7 @@ export function NewHome() { clearNotifications={clearNotifications} unseenCount={unseenCount} openSettings={openSettings} - openHelp={() => platform.openLink("https://netmind.viettel.vn/codev/docs")} + openHelp={() => platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} language={language} onWheel={(event) => { if (sessionViewport) containHomeWheel(event, sessionViewport) @@ -780,7 +780,7 @@ export function NewHome() { platform.openLink("https://netmind.viettel.vn/codev/docs")} + openHelp={() => platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} language={language} /> diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 7a200b6c92..221e4ff792 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2238,7 +2238,7 @@ export default function LegacyLayout(props: ParentProps) { settingsKeybind={() => command.keybind("settings.open")} onOpenSettings={openSettings} helpLabel={() => language.t("sidebar.help")} - onOpenHelp={() => platform.openLink("https://netmind.viettel.vn/codev/docs")} + onOpenHelp={() => platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} renderPanel={() => mobile ? : } diff --git a/packages/codev-brand/src/index.ts b/packages/codev-brand/src/index.ts index ce989cee9d..ec7e288423 100644 --- a/packages/codev-brand/src/index.ts +++ b/packages/codev-brand/src/index.ts @@ -1,6 +1,8 @@ export const PRODUCT_NAME = "CoDev Code" export const COMMAND_NAME = "codev" -export const DOCS_URL = "https://netmind.viettel.vn/codev/docs" +// Base64 (like codev-gateway's const.ts) to keep the host out of casual greps, +// not as a secret — the decoded URL ships in the system prompt the model reads. +export const DOCS_URL = atob("aHR0cHM6Ly9uZXRtaW5kLnZpZXR0ZWwudm4vY29kZXYvZW4vZG9jcy9jb2Rl") export const REPO_URL = "https://github.com/quickbeard/codev-code" export const ISSUES_URL = "https://github.com/quickbeard/codev-code/issues" diff --git a/packages/opencode/src/cli/cmd/github.handler.ts b/packages/opencode/src/cli/cmd/github.handler.ts index a9640ab9b3..3c18363bb5 100644 --- a/packages/opencode/src/cli/cmd/github.handler.ts +++ b/packages/opencode/src/cli/cmd/github.handler.ts @@ -198,7 +198,7 @@ export const githubInstall = Effect.fn("Cli.github.install")(function* () { "", " 3. Go to a GitHub issue and comment `/codev summarize` to see the agent in action", "", - " Learn more about the GitHub agent - https://netmind.viettel.vn/codev/docs/code/github", + " Learn more about the GitHub agent - https://netmind.viettel.vn/codev/en/docs/code/github", ].join("\n"), ) } diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 762eb6a188..f4665a1780 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -476,7 +476,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://netmind.viettel.vn/codev/docs/providers/#cloudflare-ai-gateway", + "Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://netmind.viettel.vn/codev/en/docs/code/providers#cloudflare-ai-gateway", ) } diff --git a/packages/opencode/test/session/brand.test.ts b/packages/opencode/test/session/brand.test.ts index b93321f54e..e729ab0bb5 100644 --- a/packages/opencode/test/session/brand.test.ts +++ b/packages/opencode/test/session/brand.test.ts @@ -36,7 +36,7 @@ describe("brand", () => { test("provider prompts route support at the fork", () => { const prompt = SystemPrompt.provider(model("MiniMax/MiniMax-M3"))[0] expect(prompt).toContain("https://github.com/quickbeard/codev-code/issues") - expect(prompt).toContain("https://netmind.viettel.vn/codev/docs") + expect(prompt).toContain("https://netmind.viettel.vn/codev/en/docs/code") expect(prompt).not.toContain("anomalyco") }) diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index e29a0bf273..2a75df6418 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -945,7 +945,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi name: "docs.open", title: "Open docs", run: () => { - open("https://netmind.viettel.vn/codev/docs").catch(() => {}) + open("https://netmind.viettel.vn/codev/en/docs/code").catch(() => {}) dialog.clear() }, category: "System", From 166575b1a3ddbdbb57788c7fa040d0495c174cd0 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 7 Aug 2026 14:48:33 +0700 Subject: [PATCH 3/5] refactor: source every docs link from the base64 DOCS_URL constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `netmind.viettel.vn` appeared in plaintext at 10 call sites across app, tui, and the CLI, which defeats the point of the base64 host in codev-gateway, core/ripgrep/binary.ts, and codev-brand. Rather than repeat an `atob(...)` at each site, every link now derives from `DOCS_URL` in `@codev/brand` — one encoded host in the codebase and one place to fix when the site moves. `app` and `tui` take a `@codev/brand` workspace dependency; it is a zero-dep leaf using only `atob`, so it is safe in client runtime code. The two tests that pinned a URL literal keep pinning it, base64-encoded, so they still verify the value independently of the constant they check. CHANGELOG and DIVERGENCES prose points at `DOCS_URL` / `MIRROR` instead of spelling out the host — same result as encoding, without turning the fork's conflict-resolution guidance into base64. DIVERGENCES gains a row for `packages/app/package.json` and extends the `packages/tui/package.json` row. Grep for netmind or viettel across the repo now returns nothing. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 23 ++++++++++--------- DIVERGENCES.md | 14 ++++++----- bun.lock | 2 ++ packages/app/package.json | 1 + .../src/components/dialog-custom-provider.tsx | 3 ++- .../app/src/components/settings-general.tsx | 3 ++- .../src/components/settings-v2/general.tsx | 3 ++- packages/app/src/desktop-menu.ts | 4 +++- packages/app/src/pages/home.tsx | 5 ++-- packages/app/src/pages/layout.tsx | 3 ++- packages/core/test/ripgrep-binary.test.ts | 2 +- .../opencode/src/cli/cmd/github.handler.ts | 3 ++- packages/opencode/src/cli/cmd/providers.ts | 3 ++- packages/opencode/test/session/brand.test.ts | 4 +++- packages/tui/package.json | 1 + packages/tui/src/app.tsx | 19 ++++++++++----- 16 files changed, 59 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1396632681..b6eaef3d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ file is the human-readable summary of what changed and why. receives still came from upstream verbatim, so identity, the `/help` line, the feedback URL (`github.com/anomalyco/opencode`) and the docs URL (`opencode.ai`) were all upstream's. The model now introduces itself as CoDev - Code and routes support at `github.com/quickbeard/codev-code/issues` and - `netmind.viettel.vn/codev/en/docs/code`. The identity is also stated in the + Code and routes support at `github.com/quickbeard/codev-code/issues` and the + fork's hosted docs. The identity is also stated in the environment block, so agents that define their own `prompt` — which replaces the whole provider prompt — get it too. @@ -38,15 +38,16 @@ file is the human-readable summary of what changed and why. ### App / TUI - **Docs links open the docs instead of a 404** — every link the app opens in - your browser pointed at `netmind.viettel.vn/codev/docs`, but the docs site is - locale-routed and the Code docs live under `/code`, so the real base is - `/codev/en/docs/code`. The root only redirected, but the deep links did not: - settings theme "Learn more" (both settings screens), the custom-provider - dialog link, and the CLI's Cloudflare-gateway hint were all landing on a 404. - All docs links — TUI "Open docs", the desktop Documentation menu item, the - sidebar Help buttons, the GitHub-agent hint, and the ones above — now use the - new base. (The ripgrep download mirror is unaffected; it is a static asset, - not a docs page, and keeps its own path.) + your browser used a docs base that was missing the site's locale segment and + its `/code` section, so while the root merely redirected, the deep links did + not resolve at all: settings theme "Learn more" (both settings screens), the + custom-provider dialog link, and the CLI's Cloudflare-gateway hint were all + landing on a 404. Every docs link — those three plus TUI "Open docs", the + desktop Documentation menu item, the sidebar Help buttons, and the + GitHub-agent hint — now derives from the single `DOCS_URL` constant in + `packages/codev-brand`, so there is one place to correct if the site moves + again. (The ripgrep download mirror is unaffected; it is a static asset, not + a docs page, and keeps its own path.) ## 1.18.4-7 diff --git a/DIVERGENCES.md b/DIVERGENCES.md index a6a664425e..31edabccef 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -75,7 +75,8 @@ from the squash message first, or use a plain merge. | `package.json` (root) | `name` `opencode` → `codev-code`; `repository.url` → `github.com/quickbeard/codev-code`; added `release` script delegating to packages/opencode; added `bump` script pointing at the fork-only `script/bump-version.ts`; removed the `sso`, `dev:stats`, and `random` scripts | The root manifest carries the fork's name (nothing reads it — root is private and unpublished; `packages/script` reads only `packageManager` — so this is cosmetic and also removes the name collision with the `packages/opencode` workspace member, whose name MUST stay `opencode`: build.ts derives bun compile targets and dist dir names from it). `bun run release` and `bun run bump` work from the repo root. `sso` logs into upstream's AWS org (`--sso-session=opencode`) and `dev:stats` runs inside an sst shell against upstream's production stage — neither works without anomalyco credentials; `random` is inert cruft (`echo 'Random script'`). | Keep our name + release/bump lines and the removals (drop any new upstream-infra-only scripts too); take upstream's other fields. | | `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependency `"@codev/brand": "workspace:*"` | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. | | `bun.lock` | Mirrors the bin key rename above and the fork-only workspace packages/deps (`@codev/gateway`, `@codev/brand`) | Lockfile records workspace bin mappings and workspace deps. Known quirk: the root workspace stanza still says `"name": "opencode"` — bun never syncs the root manifest's name into an existing lock (verified: neither `bun install` nor `--lockfile-only` rewrites it); it self-corrects only on a from-scratch regeneration and is harmless meanwhile. Don't hand-edit it. | Regenerate with `bun install`; never hand-merge. | -| `packages/tui/package.json` | Added dependency `"@codev/gateway": "workspace:*"` | The fork's gateway configure dialog imports SSO/backend/config-block logic from the fork-only `packages/codev-gateway`. | Keep our dependency line; take upstream's other fields. | +| `packages/tui/package.json` | Added dependencies `"@codev/gateway": "workspace:*"` and `"@codev/brand": "workspace:*"` | The fork's gateway configure dialog imports SSO/backend/config-block logic from the fork-only `packages/codev-gateway`; the "Open docs" command takes its URL from `DOCS_URL` in the fork-only `packages/codev-brand` instead of hardcoding the host. | Keep our dependency lines; take upstream's other fields. | +| `packages/app/package.json` | Added dependency `"@codev/brand": "workspace:*"` | Every docs link the app opens in the browser takes its base from `DOCS_URL` in the fork-only `packages/codev-brand` (see the docs-site links row) instead of hardcoding the host. `codev-brand` is a zero-dependency leaf that uses only `atob`, so it is safe in client runtime code. | Keep our dependency line; take upstream's other fields. | | `packages/tui/src/feature-plugins/home/tips-view.tsx` | `NO_MODELS_TIP` points at `/configure` (CoDev gateway) instead of `/connect`; tip strings rebranded — built-CLI examples `opencode ` → `codev ` (`serve`, `upgrade`, `agent create`, `github install`, `debug config`) and product prose "OpenCode" → "CoDev Code". Dropped the `docker run … ghcr.io/anomalyco/opencode` tip and the `/connect` tip that advertised "OpenCode Zen" (a generic `/connect` tip already exists). GitHub-integration tips rebranded to the fork's trigger: the two `/opencode` tips and the `/oc` PR-review tip now say `/codev` (matches the rebranded `github.handler.ts` defaults). | The tips name the fork's CLI (`codev`), product, and `/codev` GitHub trigger; no fork docker image (see web-docs install row); Zen is upstream's hosted gateway. The `opencode.status` command id stays (internal identifier, not user-facing). | Keep our tip text and the docker-tip and Zen-`/connect`-tip deletions. Rebrand any new upstream `opencode `/"OpenCode"/`/opencode`-trigger tip; drop any re-added docker/brew/curl install tip. | | `packages/tui/src/feature-plugins/sidebar/footer.tsx` | Deleted the dismissable "Getting started" card (free-models pitch + `/connect` pointer + `dismissed_getting_started` KV flag); the sidebar footer keeps only the cwd path display | The card advertises upstream's hosted free Zen models — which the fork hides — and points at `/connect` while first-run setup routes through `/configure`; the fork's startup gate covers onboarding. | Keep the card deleted; take upstream's changes to the remaining path display. | | `packages/tui/src/ui/dialog.tsx` | Added a `persistent` store flag (with `setPersistent`/`persistent` accessors): escape and the click-outside backdrop no-op while set, and ctrl+c dispatches `app.exit.confirm` (base-mode-only, so suppressed under modal) instead of closing; every `replace`/`clear` resets it to false | The CoDev gateway configure flow is a non-dismissable first-run gate; ctrl+c stays the quit escape hatch (double-press — see the double-Ctrl+C row). Default-false keeps every other dialog dismissable. | Keep our persistent flag, the escape/backdrop guards, the ctrl+c `app.exit.confirm` dispatch, and the reset lines; take upstream's surrounding changes. | @@ -142,7 +143,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/assets/help/` (introducing-tabs.mp4, placeholder.png, home.png, tabs.png — whole directory) | Deleted | The 13 MB promo video's and the v1.18 home/tabs screenshots' only consumer was the removed `TabsInfoPopup` (row above) and they bloat every clone; `placeholder.png` is referenced by nothing. | Keep deleted (`git rm` the upstream side of modify/delete conflicts); delete new upstream assets under this directory whose only consumer is the removed popup. | | `packages/app/src/desktop-menu.ts`, `packages/app/src/components/windows-app-menu.tsx` | App-menu label "OpenCode" → "CoDev Code"; "OpenCode Documentation" item → "CoDev Code Documentation" (href `opencode.ai/docs` stays — real URL); trigger `aria-label` "OpenCode menu" → "CoDev Code menu" (×2) | Visible menu branding, incl. screen-reader labels. | Keep our labels; take upstream's surrounding menu changes. Rebrand any new "OpenCode" label/aria-label. | | Hardcoded product-string rebrand in TUI + web app: `packages/tui/src/app.tsx`, `packages/tui/src/routes/session/permission.tsx`, `packages/tui/src/attention.ts` (+ `packages/opencode/test/cli/cmd/tui/attention.test.ts`), `packages/app/src/wsl/settings-model.ts` (+ `.test.ts`), `packages/ui/src/components/favicon.tsx` | User-visible "OpenCode" strings outside the i18n dicts → fork brand: update-complete toast "Successfully updated to OpenCode v…" → "CoDev Code"; permission dialog "…until OpenCode is restarted" → "CoDev Code" (the sibling patterns line was already rebranded); OS-notification fallback title `opencode` → `codev` and built-in sound-pack display name "OpenCode Default" → "CoDev Code Default" (pack id `opencode.default` stays — internal identifier; the three default-title expectations in `packages/opencode/test/cli/cmd/tui/attention.test.ts` follow, while its explicit-title pass-through fixture stays `opencode`); WSL settings actions "Install/Update OpenCode" → "…CoDev Code" (+ test expectations/titles; fixture paths `/usr/local/bin/opencode` stay); PWA `apple-mobile-web-app-title` "OpenCode" → "CoDev Code" (matches the rebranded `site.webmanifest`, favicon-assets row) | These render in surfaces the fork ships (TUI + embedded web UI); the i18n-dict strings remain a known gap (UI branding bullet below). | Keep our strings; take upstream's surrounding changes. Rebrand any new hardcoded user-visible "OpenCode" string in these surfaces; leave identifiers, URLs, and fixture paths. | -| Browser-opened docs-site links: `packages/tui/src/app.tsx`, `packages/app/src/desktop-menu.ts`, `packages/app/src/components/{settings-general,settings-v2/general,dialog-custom-provider}.tsx`, `packages/app/src/pages/{home,layout,error}.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/opencode/src/cli/cmd/{providers,github.handler}.ts` | Links the app opens in the user's browser repointed from `opencode.ai` to the fork's hosted docs, base `https://netmind.viettel.vn/codev/en/docs/code` (the site is locale-routed and the Code docs live under `/code`; no trailing slash anywhere — the site 308-redirects `…/themes/` to `…/themes`): TUI "Open docs" command and app Documentation menu item → docs root; settings theme "Learn more" (v1 + v2) → `…/code/themes`; custom-provider dialog link → `…/code/providers#custom-provider`; CLI Cloudflare-gateway hint → `…/code/providers#cloudflare-ai-gateway`; GitHub-agent hint → `…/code/github`; home/layout sidebar Help buttons `opencode.ai/desktop-feedback` → docs root. The error page's report button `opencode.ai/desktop-feedback` → `github.com/quickbeard/codev-code/issues` instead (it's a report action, not a docs lookup), icon `discord` → `github`, and the en/es `error.page.report.*` strings → "…CoDev Code team" / "on GitHub". Help submenu (rendered by the shipped web UI's Windows app menu too): "Support Forum" (upstream Discord invite) deleted — the fork has no forum; "Share Feedback"/"Report a Bug" → `github.com/quickbeard/codev-code/issues/new?template=feature-request.yml`/`bug-report.yml` (the fork's template names are hyphenated, unlike upstream's underscored ones). | Fork users should land on the fork's docs and report issues to the fork, not upstream's site/Discord. NOT repointed: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth`), `$schema`/theme-schema ids, API/share/changelog endpoints, MCP OAuth `client_uri`, prompt-file mentions. Also NOT on this base: the ripgrep mirror (`/codev/docs/code/downloads/…`, see its own row) — it is a `public/` static asset, not locale-routed content, and 404s under `/en/docs/code`. | Keep our URLs, icon, and strings; take upstream's surrounding changes. Repoint any new browser-opened `opencode.ai` docs/help link to the fork docs base; leave the NOT-repointed list. | +| Browser-opened docs-site links: `packages/tui/src/app.tsx`, `packages/app/src/desktop-menu.ts`, `packages/app/src/components/{settings-general,settings-v2/general,dialog-custom-provider}.tsx`, `packages/app/src/pages/{home,layout,error}.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/opencode/src/cli/cmd/{providers,github.handler}.ts` | Links the app opens in the user's browser repointed from `opencode.ai` to the fork's hosted docs. Each one derives its base from `DOCS_URL` (`packages/codev-brand/src/index.ts`, base64-encoded) rather than hardcoding the host: the site is locale-routed and the Code docs live under `/code`, and no URL takes a trailing slash (the site 308-redirects `…/themes/` to `…/themes`). Sites: TUI "Open docs" command and app Documentation menu item → docs root; settings theme "Learn more" (v1 + v2) → `…/code/themes`; custom-provider dialog link → `…/code/providers#custom-provider`; CLI Cloudflare-gateway hint → `…/code/providers#cloudflare-ai-gateway`; GitHub-agent hint → `…/code/github`; home/layout sidebar Help buttons `opencode.ai/desktop-feedback` → docs root. The error page's report button `opencode.ai/desktop-feedback` → `github.com/quickbeard/codev-code/issues` instead (it's a report action, not a docs lookup), icon `discord` → `github`, and the en/es `error.page.report.*` strings → "…CoDev Code team" / "on GitHub". Help submenu (rendered by the shipped web UI's Windows app menu too): "Support Forum" (upstream Discord invite) deleted — the fork has no forum; "Share Feedback"/"Report a Bug" → `github.com/quickbeard/codev-code/issues/new?template=feature-request.yml`/`bug-report.yml` (the fork's template names are hyphenated, unlike upstream's underscored ones). | Fork users should land on the fork's docs and report issues to the fork, not upstream's site/Discord. NOT repointed: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth`), `$schema`/theme-schema ids, API/share/changelog endpoints, MCP OAuth `client_uri`, prompt-file mentions. Also NOT on this base: the ripgrep mirror (`/codev/docs/code/downloads/…`, see its own row) — it is a `public/` static asset, not locale-routed content, and 404s under `/en/docs/code`. | Keep our URLs, icon, and strings; take upstream's surrounding changes. Repoint any new browser-opened `opencode.ai` docs/help link to the fork docs base; leave the NOT-repointed list. | | `packages/desktop/src/main/index.ts`, `packages/desktop/src/main/windows.ts` | APP_NAMES + window title → "CoDev Code" (Dev/Beta variants too) | Desktop app display name. | Keep our names. | | `packages/desktop/electron-builder.config.ts` | `productName` ×3 → "CoDev Code…"; artifactName → `codev-code-desktop-…`; protocol display name | Installer/product branding. appId (`ai.opencode.desktop`) and the `opencode://` deep-link scheme stay upstream — identity plumbing wired into main/index.ts and app deep-links; do not rename casually. | Keep our names; take upstream's other config. | | `packages/ui/src/assets/favicon/` (favicon-v3.svg, site.webmanifest, favicon-96x96-v3.png, apple-touch-icon-v3.png, web-app-manifest-192/512.png) | New codev mark: blocky "C" in #00cdcd on dark; manifest name "CoDev Code"/"codev" | Web/desktop favicon + PWA branding (served via packages/app/public symlinks). | Keep our assets (binary: take ours wholesale). | @@ -176,7 +177,7 @@ from the squash message first, or use a plain merge. | `packages/tui/src/component/prompt/autocomplete.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/opencode/src/cli/cmd/run/footer.prompt.tsx`, `packages/core/src/filesystem/search.ts`, `packages/tui/test/fixture/tui-sdk.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx`, `packages/core/test/filesystem/search.test.ts` | `@` mention autocomplete lists file/directory results before references/agents/MCP resources; TUI and run-CLI menus cap the leading files so up to 3 non-file matches stay in the first visible page; web app group rank reordered to recent → file → reference → agent → resource (extracted into fork-only `prompt-input/at-options.ts`). TUI and run-CLI file resources track menu visibility so opening on an empty query fetches (previously nothing refetched until the query changed); core's ripgrep-fallback `find` returns its first entries for an empty query to mirror fff. Tests: tui-sdk fixture's `FetchHandler` gains an optional `method` arg, `renderFooter` accepts `findFiles`/`agents` overrides + a mention-menu ordering test, core search tests cover the fallback's empty query (fork-only test files: `packages/tui/test/prompt-autocomplete.test.tsx`, `packages/app/test-browser/prompt-at-options.test.ts`, `packages/app/src/components/prompt-input/at-options{,.test}.ts`) | Files are the dominant `@` use case; they should rank first and appear on a bare `@`. | Keep our ordering/reserve logic, resource source tracking, empty-query branch, fixture/test additions; take upstream's surrounding changes. | | `packages/slack/.gitignore`, `sdks/vscode/.gitignore` | Added the missing trailing newline (content unchanged) | Upstream's copies end without a newline; POSIX text files should end with one, and tools that concatenate or append to these files misbehave without it. | Whitespace-only: keep the trailing newline; take upstream's entries. | | `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Permission-mode indicator + shift+tab cycle. keybind.ts: added `permission_mode_cycle: keybind("shift+tab", ...)` + its `permission.mode` CommandMap entry; `agent_cycle_reverse` default `"shift+tab"` → `"none"` (tab still cycles agents forward; previous-agent stays rebindable). prompt/index.tsx: the footer badge next to the agent name always renders in prompt mode — gray `manual` (`theme.textMuted`) in normal mode, red `auto` (`theme.error`) in auto mode (upstream only showed gray `auto`); the footer's shortcut hints gain a `shift+tab modes` text (via a `modeShortcut` `useCommandShortcut("permission.mode")` memo) left of `tab agents`, in the same `Show` gate (see the hints-and-usage row above for when that gate renders). app.tsx: `"permission.mode"` added to `appBindingCommands` so the binding is gathered. | The permission mode is always visible (manual/auto) with auto in red as a danger signal, and shift+tab toggles it without opening the palette. | Keep the `permission_mode_cycle` definition + mapping, the `agent_cycle_reverse` `"none"` default, the badge `Show` block, the `modeShortcut` memo + hint text, and the `appBindingCommands` entry; take upstream's surrounding changes. If upstream reworks the auto badge or rebinds shift+tab, re-apply on top and re-audit the shift+tab conflict. | -| `packages/core/src/filesystem/search.ts`, `packages/core/src/ripgrep/binary.ts`, `packages/core/src/filesystem.ts`, `packages/core/src/tool/read-filesystem.ts`, `packages/core/test/filesystem/search.test.ts`, `packages/core/test/location-filesystem.test.ts`, `packages/core/test/ripgrep-binary.test.ts`, `packages/core/test/preload.ts` | Windows file-search fallback robustness. search.ts: the background ripgrep index walk no longer dies silently (`Effect.orDie` in a forked fiber) — each attempt resets the accumulated file/directory state, failures log a warning ("file index scan failed, file search may be empty"), and the walk retries forever on a 1s exponential backoff capped at 30s spacing, so installing rg mid-session self-heals; directory entries are suffixed and detected with `"/"` instead of `path.sep` (both ripgrep and fff layers), and fallback `find` queries normalize `\` to `/` and collapse doubled slashes (directory tab-expansion appends `/` to an already-`/`-suffixed entry). binary.ts: `Effect.cached` on ripgrep binary resolution (which memoizes failure exits for the process lifetime) replaced with a success-only cache behind a semaphore, so a failed rg download (offline, blocked proxy) retries on the next use instead of bricking search until restart; the download tail tries GitHub first (each attempt bounded to 30s) and falls back to the CoDev landing-page mirror (`https://netmind.viettel.vn/codev/docs/code/downloads/rg---[.exe]`, a raw executable — no archive/extract — staged in a scoped temp dir then copied into place), via `Effect.catchCause` so a proxy block page (200 + HTML) that dies in `extract` as a defect still reaches the mirror. filesystem.ts `list` + read-filesystem.ts `list`: directory suffix `path.sep` → `"/"`; `fs.list` also `/`-normalizes `path.relative` output. Tests: search.test.ts pins `"/"`-suffixed directories, adds query-normalization coverage, and a stub-Ripgrep scan-retry test asserting the partial first walk is discarded; location-filesystem.test.ts pins `"src/"`; ripgrep-binary.test.ts adds mocked-HTTP coverage of the fallback order, success caching, and failure non-caching; preload.ts redirects `XDG_CACHE_HOME` to a fresh temp dir so no test can read or clobber the developer's real `~/.cache/codev`. | On Windows fff is disabled by default (upstream `e4300e9b74`), so the `@` mention popup and file search depend entirely on the ripgrep fallback; a missing `rg.exe` plus one failed download left the index permanently empty with no user-visible signal (the CI-workflows row's choco preinstall papered over the same swallowed failure). `\`-suffixed directory entries also broke `endsWith("/")` directory-mime detection in the run CLI and poisoned drill-down queries with backslashes that can never match the `/`-normalized index. | Keep our retry/reset scan, success-only binary cache, `"/"` suffixes, and query normalization; take upstream's surrounding changes. If upstream reworks the fallback or re-enables fff on win32, preserve the invariants: entry paths are `/`-normalized with `/`-suffixed directories on every platform, index-scan failures are logged and retried, a failed binary download is never cached, and the GitHub download keeps its CoDev-mirror fallback — the mirror filename scheme must match what codev-landing-page hosts under `public/docs/code/downloads` (and codevhub's `RG_VERSION` pin in its `src/lib/ripgrep.ts`); bump all three together when changing `VERSION`. | +| `packages/core/src/filesystem/search.ts`, `packages/core/src/ripgrep/binary.ts`, `packages/core/src/filesystem.ts`, `packages/core/src/tool/read-filesystem.ts`, `packages/core/test/filesystem/search.test.ts`, `packages/core/test/location-filesystem.test.ts`, `packages/core/test/ripgrep-binary.test.ts`, `packages/core/test/preload.ts` | Windows file-search fallback robustness. search.ts: the background ripgrep index walk no longer dies silently (`Effect.orDie` in a forked fiber) — each attempt resets the accumulated file/directory state, failures log a warning ("file index scan failed, file search may be empty"), and the walk retries forever on a 1s exponential backoff capped at 30s spacing, so installing rg mid-session self-heals; directory entries are suffixed and detected with `"/"` instead of `path.sep` (both ripgrep and fff layers), and fallback `find` queries normalize `\` to `/` and collapse doubled slashes (directory tab-expansion appends `/` to an already-`/`-suffixed entry). binary.ts: `Effect.cached` on ripgrep binary resolution (which memoizes failure exits for the process lifetime) replaced with a success-only cache behind a semaphore, so a failed rg download (offline, blocked proxy) retries on the next use instead of bricking search until restart; the download tail tries GitHub first (each attempt bounded to 30s) and falls back to the CoDev landing-page mirror (the base64-encoded `MIRROR` constant plus `/rg---[.exe]`, a raw executable — no archive/extract — staged in a scoped temp dir then copied into place), via `Effect.catchCause` so a proxy block page (200 + HTML) that dies in `extract` as a defect still reaches the mirror. filesystem.ts `list` + read-filesystem.ts `list`: directory suffix `path.sep` → `"/"`; `fs.list` also `/`-normalizes `path.relative` output. Tests: search.test.ts pins `"/"`-suffixed directories, adds query-normalization coverage, and a stub-Ripgrep scan-retry test asserting the partial first walk is discarded; location-filesystem.test.ts pins `"src/"`; ripgrep-binary.test.ts adds mocked-HTTP coverage of the fallback order, success caching, and failure non-caching; preload.ts redirects `XDG_CACHE_HOME` to a fresh temp dir so no test can read or clobber the developer's real `~/.cache/codev`. | On Windows fff is disabled by default (upstream `e4300e9b74`), so the `@` mention popup and file search depend entirely on the ripgrep fallback; a missing `rg.exe` plus one failed download left the index permanently empty with no user-visible signal (the CI-workflows row's choco preinstall papered over the same swallowed failure). `\`-suffixed directory entries also broke `endsWith("/")` directory-mime detection in the run CLI and poisoned drill-down queries with backslashes that can never match the `/`-normalized index. | Keep our retry/reset scan, success-only binary cache, `"/"` suffixes, and query normalization; take upstream's surrounding changes. If upstream reworks the fallback or re-enables fff on win32, preserve the invariants: entry paths are `/`-normalized with `/`-suffixed directories on every platform, index-scan failures are logged and retried, a failed binary download is never cached, and the GitHub download keeps its CoDev-mirror fallback — the mirror filename scheme must match what codev-landing-page hosts under `public/docs/code/downloads` (and codevhub's `RG_VERSION` pin in its `src/lib/ripgrep.ts`); bump all three together when changing `VERSION`. | | `packages/tui/src/context/permission.tsx`, `packages/tui/src/app.tsx`, `packages/tui/src/ui/dialog-confirm.tsx`, `packages/tui/src/ui/toast.tsx` | Auto-approve consent gate. permission.tsx: mode always starts `"normal"` (upstream initialized it from `args.auto`); `toggle()` replaced by `set(mode)` plus a per-run `consented` flag + `consent()`. app.tsx: shared `enableAutoApprove()` (title in the `AUTO_APPROVE_TITLE` module const, body from an `autoApproveMessage` accessor, and a shorter one-line `autoApproveToast` accessor for the toast, which only has a few seconds on screen; both redden the word "dangerous" with `theme.error` and are passed unevaluated because renderables can only be created during render) shows a `DialogConfirm` opened on cancel and, on confirm, a warning toast under the same title, then sets the mode; both entry points funnel through it — an `onMount` when `args.auto` is set, and the `permission.mode` command (shift+tab / palette), which enables via the gate and disables directly. Consent is asked once per run; later toggles are silent. dialog-confirm.tsx: optional `defaultChoice` prop + 5th `DialogConfirm.show` arg selecting the initially-active button (omitted keeps upstream's confirm-first default); `message` widened to `JSX.Element \| (() => JSX.Element)` and called at the render site. toast.tsx: same `message` widening, resolved by a `message()` helper in `Toast()` (fork-only test file: `packages/tui/test/auto-approve-consent.view.test.tsx`). | Auto-approve is dangerous, so `--auto` (and its hidden aliases) and the first shift+tab into auto require explicit confirmation; declining or dismissing the dialog stays in manual mode, opening on cancel keeps a stray return from granting it, and the risk word is red like the `auto` badge. | Keep the consent flag + gate flow, the `toggle()` removal, the `defaultChoice` prop, and the accessor-aware `message` on both dialog and toast; take upstream's surrounding changes. If upstream adds new writers of the permission mode, route their enable path through `enableAutoApprove()`. | | `packages/opencode/src/session/system.ts` | Upstream's `provider()` branch chain is renamed `select()` (body otherwise untouched) and `provider()` now returns `select(model).map(rebrand)`; `environment()` gained a leading line naming the product and the `codev` command | The prompts the model receives are upstream's, so it identified itself as opencode and sent users to upstream's issue tracker and docs. Rewriting names as the prompt is assembled keeps the `prompt/*.txt` files byte-identical to upstream, so their hunks always apply and a newly added upstream brand mention is rebranded automatically. The identity also goes in the environment block because an agent's own `prompt` replaces the provider prompt wholesale (see `llm/request.ts`), and the environment block is on every path. | Keep the wrapper and the identity line; take upstream's `select()` body verbatim — never hand-rebrand the prompt text it returns. A new upstream prompt file or branch needs nothing here; `test/session/brand.test.ts` fails if its wording isn't covered by the rules in `packages/codev-brand`. | | `packages/opencode/src/tool/registry.ts` | The assembled tool `description` is wrapped in `rebrand(...)` | Tool descriptions are model-visible and name the product (`tool/lsp.txt`). One wrapper at the point where every tool's final description is built covers all of them, including text upstream adds later. | Keep the wrapper around whatever expression upstream's description becomes; take upstream's surrounding changes. | @@ -199,9 +200,10 @@ from the squash message first, or use a plain merge. `ai.opencode.desktop` appId / `opencode://` scheme (identity plumbing wired into deep-link handling). - **URLs**: links the app opens in the browser point at the fork — docs at - `https://netmind.viettel.vn/codev/en/docs/code` (the site is locale-routed and - the Code docs live under `/code`; no trailing slash anywhere — the site - 308-redirects `…/themes/` to `…/themes`) and issue reports at `github.com/quickbeard/codev-code/issues` + the base in `DOCS_URL` (`packages/codev-brand/src/index.ts`, base64-encoded — + the site is locale-routed and the Code docs live under `/code`; no trailing + slash anywhere, the site 308-redirects `…/themes/` to `…/themes`) and issue + reports at `github.com/quickbeard/codev-code/issues` (see the docs-site links table row). Still upstream, deliberately: Zen/Go product URLs (`opencode.ai/zen`, `/go`, `/auth` — external products), `$schema`/`theme.json`/ `desktop-theme.json` ids, service endpoints (`api.opencode.ai` exchange, `opencode.ai/s/` diff --git a/bun.lock b/bun.lock index eb2c110f83..4ff26c3d0f 100644 --- a/bun.lock +++ b/bun.lock @@ -31,6 +31,7 @@ "name": "@opencode-ai/app", "version": "1.18.4-7", "dependencies": { + "@codev/brand": "workspace:*", "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", @@ -965,6 +966,7 @@ "name": "@opencode-ai/tui", "version": "1.18.4-7", "dependencies": { + "@codev/brand": "workspace:*", "@codev/gateway": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", diff --git a/packages/app/package.json b/packages/app/package.json index 1ca6ead538..58e76b09ab 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -47,6 +47,7 @@ "vite-plugin-solid": "catalog:" }, "dependencies": { + "@codev/brand": "workspace:*", "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", "@dnd-kit/dom": "0.5.0", diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 0fcee8f2bd..45c673b75c 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -1,4 +1,5 @@ import { Button } from "@opencode-ai/ui/button" +import { DOCS_URL } from "@codev/brand" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -184,7 +185,7 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {

{language.t("provider.custom.description.prefix")} - + {language.t("provider.custom.description.link")} {language.t("provider.custom.description.suffix")} diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 82bb46d3bc..1da800edc0 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -1,5 +1,6 @@ import { Component, Show, createMemo, createResource, onMount, type JSX } from "solid-js" import { Button } from "@opencode-ai/ui/button" +import { DOCS_URL } from "@codev/brand" import { Icon } from "@opencode-ai/ui/icon" import { Select } from "@opencode-ai/ui/select" import { Switch } from "@opencode-ai/ui/switch" @@ -479,7 +480,7 @@ export const SettingsGeneral: Component = () => { description={ <> {language.t("settings.general.row.theme.description")}{" "} - {language.t("common.learnMore")} + {language.t("common.learnMore")} } > diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index c0e2f55c60..6c75de5e9a 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -1,5 +1,6 @@ import { Component, Show, createMemo, createResource, onMount } from "solid-js" import { createMediaQuery } from "@solid-primitives/media" +import { DOCS_URL } from "@codev/brand" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { SelectV2 } from "@opencode-ai/ui/v2/select-v2" import { Switch } from "@opencode-ai/ui/v2/switch-v2" @@ -438,7 +439,7 @@ export const SettingsGeneralV2: Component<{ description={ <> {language.t("settings.general.row.theme.description")}{" "} - + {language.t("common.learnMore")} diff --git a/packages/app/src/desktop-menu.ts b/packages/app/src/desktop-menu.ts index 3ac2a2d47d..58e2ac6f63 100644 --- a/packages/app/src/desktop-menu.ts +++ b/packages/app/src/desktop-menu.ts @@ -1,3 +1,5 @@ +import { DOCS_URL } from "@codev/brand" + export type DesktopMenuPlatform = "macos" | "windows" export type DesktopMenuAction = @@ -200,7 +202,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [ id: "help", label: "Help", items: [ - { type: "item", label: "CoDev Code Documentation", href: "https://netmind.viettel.vn/codev/en/docs/code" }, + { type: "item", label: "CoDev Code Documentation", href: DOCS_URL }, { type: "item", label: "Export Logs...", command: "logs.export" }, { type: "separator" }, { diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 7a360d5f44..302bb4c2e9 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -16,6 +16,7 @@ import { Switch, } from "solid-js" import { makeEventListener } from "@solid-primitives/event-listener" +import { DOCS_URL } from "@codev/brand" import { createStore, produce } from "solid-js/store" import { useQuery } from "@tanstack/solid-query" import { Button } from "@opencode-ai/ui/button" @@ -673,7 +674,7 @@ export function NewHome() { clearNotifications={clearNotifications} unseenCount={unseenCount} openSettings={openSettings} - openHelp={() => platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} + openHelp={() => platform.openLink(DOCS_URL)} language={language} onWheel={(event) => { if (sessionViewport) containHomeWheel(event, sessionViewport) @@ -780,7 +781,7 @@ export function NewHome() { platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} + openHelp={() => platform.openLink(DOCS_URL)} language={language} /> diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 221e4ff792..67028fd089 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -12,6 +12,7 @@ import { type Accessor, } from "solid-js" import { makeEventListener } from "@solid-primitives/event-listener" +import { DOCS_URL } from "@codev/brand" import { useNavigate, useParams } from "@solidjs/router" import { useLayout, LocalProject } from "@/context/layout" import { useServerSync } from "@/context/server-sync" @@ -2238,7 +2239,7 @@ export default function LegacyLayout(props: ParentProps) { settingsKeybind={() => command.keybind("settings.open")} onOpenSettings={openSettings} helpLabel={() => language.t("sidebar.help")} - onOpenHelp={() => platform.openLink("https://netmind.viettel.vn/codev/en/docs/code")} + onOpenHelp={() => platform.openLink(DOCS_URL)} renderPanel={() => mobile ? : } diff --git a/packages/core/test/ripgrep-binary.test.ts b/packages/core/test/ripgrep-binary.test.ts index 699ed795e3..ec55c39f0f 100644 --- a/packages/core/test/ripgrep-binary.test.ts +++ b/packages/core/test/ripgrep-binary.test.ts @@ -11,7 +11,7 @@ import path from "path" // Pins the mirror filename contract: these names must match what // codev-landing-page hosts under public/docs/code/downloads. -const MIRROR_URL = `https://netmind.viettel.vn/codev/docs/code/downloads/rg-15.1.0-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}` +const MIRROR_URL = `${atob("aHR0cHM6Ly9uZXRtaW5kLnZpZXR0ZWwudm4vY29kZXYvZG9jcy9jb2RlL2Rvd25sb2Fkcw==")}/rg-15.1.0-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}` const GITHUB_PREFIX = "https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/" const target = path.join(Global.Path.bin, process.platform === "win32" ? "rg.exe" : "rg") diff --git a/packages/opencode/src/cli/cmd/github.handler.ts b/packages/opencode/src/cli/cmd/github.handler.ts index 3c18363bb5..c1aa26794d 100644 --- a/packages/opencode/src/cli/cmd/github.handler.ts +++ b/packages/opencode/src/cli/cmd/github.handler.ts @@ -1,4 +1,5 @@ import path from "path" +import { DOCS_URL } from "@codev/brand" import { Filesystem } from "@/util/filesystem" import * as prompts from "@clack/prompts" import { map, pipe, sortBy, values } from "remeda" @@ -198,7 +199,7 @@ export const githubInstall = Effect.fn("Cli.github.install")(function* () { "", " 3. Go to a GitHub issue and comment `/codev summarize` to see the agent in action", "", - " Learn more about the GitHub agent - https://netmind.viettel.vn/codev/en/docs/code/github", + ` Learn more about the GitHub agent - ${DOCS_URL}/github`, ].join("\n"), ) } diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index f4665a1780..f1f4e0e56c 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -1,4 +1,5 @@ import type { Argv } from "yargs" +import { DOCS_URL } from "@codev/brand" import { Auth } from "../../auth" import { cmd } from "./cmd" import { CliError, effectCmd, fail } from "../effect-cmd" @@ -476,7 +477,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://netmind.viettel.vn/codev/en/docs/code/providers#cloudflare-ai-gateway", + `Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: ${DOCS_URL}/providers#cloudflare-ai-gateway`, ) } diff --git a/packages/opencode/test/session/brand.test.ts b/packages/opencode/test/session/brand.test.ts index e729ab0bb5..2037f5507f 100644 --- a/packages/opencode/test/session/brand.test.ts +++ b/packages/opencode/test/session/brand.test.ts @@ -36,7 +36,9 @@ describe("brand", () => { test("provider prompts route support at the fork", () => { const prompt = SystemPrompt.provider(model("MiniMax/MiniMax-M3"))[0] expect(prompt).toContain("https://github.com/quickbeard/codev-code/issues") - expect(prompt).toContain("https://netmind.viettel.vn/codev/en/docs/code") + // Literal, not DOCS_URL, so this independently pins the docs base the model + // is handed. Base64 for the same reason codev-brand encodes it. + expect(prompt).toContain(atob("aHR0cHM6Ly9uZXRtaW5kLnZpZXR0ZWwudm4vY29kZXYvZW4vZG9jcy9jb2Rl")) expect(prompt).not.toContain("anomalyco") }) diff --git a/packages/tui/package.json b/packages/tui/package.json index 88ff1091dc..8aa631db62 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -48,6 +48,7 @@ "./component/register-spinner": "./src/component/register-spinner.ts" }, "dependencies": { + "@codev/brand": "workspace:*", "@codev/gateway": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 2a75df6418..b063651fc1 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -2,6 +2,7 @@ import { render, TimeToFirstDraw, useRenderer, useTerminalDimensions } from "@op import { registerOpencodeSpinner } from "./component/register-spinner" import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui" import { Deferred, Effect } from "effect" +import { DOCS_URL } from "@codev/brand" import { Global } from "@opencode-ai/core/global" import { Flag } from "@opencode-ai/core/flag/flag" import { InstallationVersion } from "@opencode-ai/core/installation/version" @@ -656,7 +657,13 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi // the one-shot open-on-startup trigger and needs no per-step escape handling // inside the flows (Connect provider is upstream's dialog — untouched). createEffect(() => { - if (!shouldOpenConfigureGate({ syncComplete: sync.status === "complete", connected: connected(), dialogOpen: dialog.stack.length > 0 })) + if ( + !shouldOpenConfigureGate({ + syncComplete: sync.status === "complete", + connected: connected(), + dialogOpen: dialog.stack.length > 0, + }) + ) return dialog.replace(() => ) }) @@ -945,7 +952,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi name: "docs.open", title: "Open docs", run: () => { - open("https://netmind.viettel.vn/codev/en/docs/code").catch(() => {}) + open(DOCS_URL).catch(() => {}) dialog.clear() }, category: "System", @@ -1264,9 +1271,7 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi evt.stopPropagation() }} onMouseUp={ - !Flag.CODEV_EXPERIMENTAL_DISABLE_COPY_ON_SELECT - ? () => Selection.copy(renderer, toast, clipboard) - : undefined + !Flag.CODEV_EXPERIMENTAL_DISABLE_COPY_ON_SELECT ? () => Selection.copy(renderer, toast, clipboard) : undefined } > @@ -1283,7 +1288,9 @@ function App(props: { onSnapshot?: () => Promise; pluginHost: TuiPlugi fork's startup auto-session); mounting Session with it would fetch a nonexistent ID and toast a server error. */} {(_) => } From 6ac8156ea2602126a9871d492332640f5e4626ab Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 7 Aug 2026 14:52:02 +0700 Subject: [PATCH 4/5] chore: stop prettier from reformatting DIVERGENCES.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running prettier on DIVERGENCES.md realigns every row of the changed-files table, so a one-row edit produces a ~120-row diff — noise in the one file that records upstream-conflict guidance, and a guaranteed conflict on the next sync. Nothing in the repo formatted it before (the pre-push hook only runs `bun typecheck`), so this pins the status quo rather than changing it. Co-Authored-By: Claude Opus 5 (1M context) --- .prettierignore | 3 +++ DIVERGENCES.md | 1 + 2 files changed, 4 insertions(+) diff --git a/.prettierignore b/.prettierignore index 7c72d70225..9809024fce 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,6 @@ sst-env.d.ts packages/desktop/src/bindings.ts packages/client/src/generated/ packages/client/src/generated-effect/ +# Formatting realigns every row of the changed-files table, so a one-row edit +# churns the whole file — noise in the one place that records upstream-conflict guidance. +DIVERGENCES.md diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 31edabccef..8fa9420335 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -75,6 +75,7 @@ from the squash message first, or use a plain merge. | `package.json` (root) | `name` `opencode` → `codev-code`; `repository.url` → `github.com/quickbeard/codev-code`; added `release` script delegating to packages/opencode; added `bump` script pointing at the fork-only `script/bump-version.ts`; removed the `sso`, `dev:stats`, and `random` scripts | The root manifest carries the fork's name (nothing reads it — root is private and unpublished; `packages/script` reads only `packageManager` — so this is cosmetic and also removes the name collision with the `packages/opencode` workspace member, whose name MUST stay `opencode`: build.ts derives bun compile targets and dist dir names from it). `bun run release` and `bun run bump` work from the repo root. `sso` logs into upstream's AWS org (`--sso-session=opencode`) and `dev:stats` runs inside an sst shell against upstream's production stage — neither works without anomalyco credentials; `random` is inert cruft (`echo 'Random script'`). | Keep our name + release/bump lines and the removals (drop any new upstream-infra-only scripts too); take upstream's other fields. | | `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependency `"@codev/brand": "workspace:*"` | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. | | `bun.lock` | Mirrors the bin key rename above and the fork-only workspace packages/deps (`@codev/gateway`, `@codev/brand`) | Lockfile records workspace bin mappings and workspace deps. Known quirk: the root workspace stanza still says `"name": "opencode"` — bun never syncs the root manifest's name into an existing lock (verified: neither `bun install` nor `--lockfile-only` rewrites it); it self-corrects only on a from-scratch regeneration and is harmless meanwhile. Don't hand-edit it. | Regenerate with `bun install`; never hand-merge. | +| `.prettierignore` | Appended `DIVERGENCES.md` (with a comment saying why) | Prettier realigns every row of this file's changed-files table, so a one-row edit churns all ~120 rows — noise in the one file that records upstream-conflict guidance, and a guaranteed conflict on the next sync. Nothing in the repo formatted it before (the pre-push hook only runs `bun typecheck`), so this pins the status quo rather than changing it. | Keep our line; take upstream's other entries. | | `packages/tui/package.json` | Added dependencies `"@codev/gateway": "workspace:*"` and `"@codev/brand": "workspace:*"` | The fork's gateway configure dialog imports SSO/backend/config-block logic from the fork-only `packages/codev-gateway`; the "Open docs" command takes its URL from `DOCS_URL` in the fork-only `packages/codev-brand` instead of hardcoding the host. | Keep our dependency lines; take upstream's other fields. | | `packages/app/package.json` | Added dependency `"@codev/brand": "workspace:*"` | Every docs link the app opens in the browser takes its base from `DOCS_URL` in the fork-only `packages/codev-brand` (see the docs-site links row) instead of hardcoding the host. `codev-brand` is a zero-dependency leaf that uses only `atob`, so it is safe in client runtime code. | Keep our dependency line; take upstream's other fields. | | `packages/tui/src/feature-plugins/home/tips-view.tsx` | `NO_MODELS_TIP` points at `/configure` (CoDev gateway) instead of `/connect`; tip strings rebranded — built-CLI examples `opencode ` → `codev ` (`serve`, `upgrade`, `agent create`, `github install`, `debug config`) and product prose "OpenCode" → "CoDev Code". Dropped the `docker run … ghcr.io/anomalyco/opencode` tip and the `/connect` tip that advertised "OpenCode Zen" (a generic `/connect` tip already exists). GitHub-integration tips rebranded to the fork's trigger: the two `/opencode` tips and the `/oc` PR-review tip now say `/codev` (matches the rebranded `github.handler.ts` defaults). | The tips name the fork's CLI (`codev`), product, and `/codev` GitHub trigger; no fork docker image (see web-docs install row); Zen is upstream's hosted gateway. The `opencode.status` command id stays (internal identifier, not user-facing). | Keep our tip text and the docker-tip and Zen-`/connect`-tip deletions. Rebrand any new upstream `opencode `/"OpenCode"/`/opencode`-trigger tip; drop any re-added docker/brew/curl install tip. | From 70afcdf30e7adf4d78bc1cdae8755374f7290032 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Fri, 7 Aug 2026 16:19:32 +0700 Subject: [PATCH 5/5] minor updates --- CHANGELOG.md | 54 ---------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6eaef3d4f..980259f91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,64 +7,10 @@ file is the human-readable summary of what changed and why. ## Unreleased -### Agent - -- **The agent knows which product it is** — asked "who are you", it answered - "I'm opencode", and it pointed users at upstream's issue tracker and docs for - help. Only the UI had been rebranded; every system prompt the model actually - receives still came from upstream verbatim, so identity, the `/help` line, the - feedback URL (`github.com/anomalyco/opencode`) and the docs URL - (`opencode.ai`) were all upstream's. The model now introduces itself as CoDev - Code and routes support at `github.com/quickbeard/codev-code/issues` and the - fork's hosted docs. The identity is also stated in the - environment block, so agents that define their own `prompt` — which replaces - the whole provider prompt — get it too. - - Prompt files themselves are untouched: names are rewritten as the prompt is - assembled (fork-only `packages/codev-brand`), so upstream prompt edits keep - applying cleanly and any new upstream brand mention is rebranded on arrival. - Identifiers that are not branding keep their upstream names — `@opencode-ai/*` - packages, published plugin names such as `opencode-gemini-auth`, and the - config `$schema` URL `https://opencode.ai/config.json` — so the model's advice - about them stays correct. - -- **Built-in config skill renamed `customize-opencode` → `customize-codev`** — - its description and body are rebranded as well. The old name still resolves, - so existing `skill` permission rules and configs referencing it keep working. - -- **`/init` writes "CoDev Code"** — the AGENTS.md template it generates no - longer describes the tool as OpenCode. - -### App / TUI - -- **Docs links open the docs instead of a 404** — every link the app opens in - your browser used a docs base that was missing the site's locale segment and - its `/code` section, so while the root merely redirected, the deep links did - not resolve at all: settings theme "Learn more" (both settings screens), the - custom-provider dialog link, and the CLI's Cloudflare-gateway hint were all - landing on a 404. Every docs link — those three plus TUI "Open docs", the - desktop Documentation menu item, the sidebar Help buttons, and the - GitHub-agent hint — now derives from the single `DOCS_URL` constant in - `packages/codev-brand`, so there is one place to correct if the site moves - again. (The ripgrep download mirror is unaffected; it is a static asset, not - a docs page, and keeps its own path.) - ## 1.18.4-7 ### TUI -- **Pasting text works on Windows again** — on some Windows and WSL machines no - paste method reached the prompt at all: not `ctrl+v`, not right-click, not - Shift+Insert. Both routes that can deliver text end at the same clipboard - read — the `ctrl+v` keybind directly, and bracketed paste via the fallback the - prompt takes when the terminal hands it an empty paste — and that read went - through `clipboardy`, whose Windows backend is the only one that shells out to - a helper binary shipped inside its own package. That file does not exist in - our compiled single-file `codev.exe`, so every read failed silently and both - routes dead-ended. The clipboard is now read with PowerShell directly, the - same way copying already worked. A failed read also now says so instead of - doing nothing. Copying and pasting images were never affected. - - **Compact TODO panel above the prompt** — while the sidebar is hidden and the session has unfinished todos, up to 4 items around the active one now render directly above the input, with a summary line such as