diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..3aeef82d62
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# web + desktop packages
+packages/app/ @adamdotdevin
+packages/tauri/ @adamdotdevin
+packages/desktop/src-tauri/ @brendonovich
+packages/desktop/ @adamdotdevin
diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS
index 34b7b23648..22c9a923d3 100644
--- a/.github/TEAM_MEMBERS
+++ b/.github/TEAM_MEMBERS
@@ -1,30 +1,15 @@
-aidtya
-aloks98
-altimateanas
-anandgupta42
-ankitksharma
-anusha-sharma
-arora-saurabh448
-dvanaken
-frasermarlow
-gaurpulkit
-govindpawa
-jontsai
-kulvirgit
-mdesmet
-mhallida
-ppradnesh
-rakendd
-ralphstodomingo
-ravik-aai
-robertmaybin
-sahrizvi
-sanjaykr5
-saravmajestic
-sgvarsh
-shreyastelkar
-sourabhchrs93
-suryaiyer95
-tshreyas
-vivekvenkatareddy
-yukthagv
+adamdotdevin
+Brendonovich
+fwang
+Hona
+iamdavidhill
+jayair
+jlongster
+kitlangton
+kommander
+MrMushrooooom
+nexxeln
+R44VC0RP
+rekram1-node
+RhysSullivan
+thdxr
diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td
new file mode 100644
index 0000000000..28535b5779
--- /dev/null
+++ b/.github/VOUCHED.td
@@ -0,0 +1,23 @@
+# Vouched contributors for this project.
+#
+# See https://github.com/mitchellh/vouch for details.
+#
+# Syntax:
+# - One handle per line (without @), sorted alphabetically.
+# - Optional platform prefix: platform:username (e.g., github:user).
+# - Denounce with minus prefix: -username or -platform:username.
+# - Optional details after a space following the handle.
+adamdotdevin
+-agusbasari29 AI PR slop
+ariane-emory
+edemaine
+-florianleibert
+fwang
+iamdavidhill
+jayair
+kitlangton
+kommander
+r44vc0rp
+rekram1-node
+-spider-yamet clawdbot/llm psychosis, spam pinging the team
+thdxr
diff --git a/.github/meta/merge-report-v1.2.27.json b/.github/meta/merge-report-v1.2.27.json
new file mode 100644
index 0000000000..921693ffd0
--- /dev/null
+++ b/.github/meta/merge-report-v1.2.27.json
@@ -0,0 +1,2907 @@
+{
+ "version": "v1.2.27",
+ "startedAt": "2026-03-21T20:48:07.243Z",
+ "files": [
+ {
+ "file": ".opencode/command/issues.md",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 6,
+ "before": "Search through existing issues in anomalyco/opencode using the gh cli to find issues matching this query:",
+ "after": "Search through existing issues in AltimateAI/altimate-code using the gh cli to find issues matching this query:",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": ".opencode/opencode.jsonc",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/config.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "package.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 92,
+ "before": " \"url\": \"https://github.com/anomalyco/opencode\"",
+ "after": " \"url\": \"https://github.com/AltimateAI/altimate-code\"",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/acp/README.md",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 72,
+ "before": " \"OpenCode\": {",
+ "after": " \"Altimate Code\": {",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 161,
+ "before": "### Mapping to OpenCode",
+ "after": "### Mapping to Altimate Code",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/acp/agent.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 566,
+ "before": " label: \"OpenCode Login\",",
+ "after": " label: \"Altimate Code Login\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 591,
+ "before": " name: \"OpenCode\",",
+ "after": " name: \"Altimate Code\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1017,
+ "before": " // OpenCode stores files internally as { type: \"file\", url, filename, mime }.",
+ "after": " // Altimate Code stores files internally as { type: \"file\", url, filename, mime }.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/github.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 247,
+ "before": " \" Learn more about the GitHub agent - https://opencode.ai/docs/github/#usage-examples\",",
+ "after": " \" Learn more about the GitHub agent - https://altimate.ai/docs/github/#usage-examples\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 366,
+ "before": " `https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,",
+ "after": " `https://api.altimate.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,",
+ "rule": "API subdomain"
+ },
+ {
+ "line": 409,
+ "before": " uses: anomalyco/opencode/github@latest${envStr}",
+ "after": " uses: AltimateAI/altimate-code/github@latest${envStr}",
+ "rule": "Main repo"
+ },
+ {
+ "line": 479,
+ "before": " const shareBaseUrl = isMock ? \"https://dev.opencode.ai\" : \"https://opencode.ai\"",
+ "after": " const shareBaseUrl = isMock ? \"https://dev.altimate.ai\" : \"https://opencode.ai\"",
+ "rule": "Dev subdomain"
+ },
+ {
+ "line": 479,
+ "before": " const shareBaseUrl = isMock ? \"https://dev.altimate.ai\" : \"https://opencode.ai\"",
+ "after": " const shareBaseUrl = isMock ? \"https://dev.altimate.ai\" : \"https://altimate.ai\"",
+ "rule": "Root domain"
+ },
+ {
+ "line": 738,
+ "before": " if (!value) return \"https://api.opencode.ai\"",
+ "after": " if (!value) return \"https://api.altimate.ai\"",
+ "rule": "API subdomain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/import.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 22,
+ "before": "/** Extract share ID from a share URL like https://opncd.ai/share/abc123 */",
+ "after": "/** Extract share ID from a share URL like https://altimate.ai/share/abc123 */",
+ "rule": "Short domain alias"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/providers.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 426,
+ "before": " prompts.log.info(\"Create an api key at https://opencode.ai/auth\")",
+ "after": " prompts.log.info(\"Create an api key at https://altimate.ai/auth\")",
+ "rule": "Root domain"
+ },
+ {
+ "line": 435,
+ "before": " \"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://opencode.ai/docs/providers/#cloudflare-ai-gateway\",",
+ "after": " \"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://altimate.ai/docs/providers/#cloudflare-ai-gateway\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/app.tsx",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 352,
+ "before": " renderer.setTerminalTitle(\"OpenCode\")",
+ "after": " renderer.setTerminalTitle(\"Altimate Code\")",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 359,
+ "before": " renderer.setTerminalTitle(\"OpenCode\")",
+ "after": " renderer.setTerminalTitle(\"Altimate Code\")",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 665,
+ "before": " open(\"https://opencode.ai/docs\").catch(() => {})",
+ "after": " open(\"https://altimate.ai/docs\").catch(() => {})",
+ "rule": "Root domain"
+ },
+ {
+ "line": 857,
+ "before": " message: `OpenCode v${evt.properties.version} is available. Run 'opencode upgrade' to update manually.`,",
+ "after": " message: `Altimate Code v${evt.properties.version} is available. Run 'opencode upgrade' to update manually.`,",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 912,
+ "before": " const issueURL = new URL(\"https://github.com/anomalyco/opencode/issues/new?template=bug-report.yml\")",
+ "after": " const issueURL = new URL(\"https://github.com/AltimateAI/altimate-code/issues/new?template=bug-report.yml\")",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 223,
+ "before": " OpenCode Zen gives you access to all the best coding models at the cheapest prices with a single API",
+ "after": " Altimate Code Zen gives you access to all the best coding models at the cheapest prices with a single API",
+ "rule": "Product name: Zen"
+ },
+ {
+ "line": 227,
+ "before": " Go to https://opencode.ai/zen to get a key",
+ "after": " Go to https://altimate.ai/zen to get a key",
+ "rule": "Root domain"
+ },
+ {
+ "line": 234,
+ "before": " OpenCode Go is a $10 per month subscription that provides reliable access to popular open coding models",
+ "after": " Altimate Code Go is a $10 per month subscription that provides reliable access to popular open coding models",
+ "rule": "Product name: Go"
+ },
+ {
+ "line": 238,
+ "before": " Go to https://opencode.ai/zen and enable OpenCode Go",
+ "after": " Go to https://altimate.ai/zen and enable OpenCode Go",
+ "rule": "Root domain"
+ },
+ {
+ "line": 238,
+ "before": " Go to https://altimate.ai/zen and enable OpenCode Go",
+ "after": " Go to https://altimate.ai/zen and enable Altimate Code Go",
+ "rule": "Product name: Go"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/component/tips.tsx",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 57,
+ "before": " \"Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai\",",
+ "after": " \"Run {highlight}/share{/highlight} to create a public link to your conversation at altimate.ai\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 90,
+ "before": " \"OpenCode auto-handles OAuth for remote MCP servers requiring auth\",",
+ "after": " \"Altimate Code auto-handles OAuth for remote MCP servers requiring auth\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 99,
+ "before": " \"OpenCode auto-formats files using prettier, gofmt, ruff, and more\",",
+ "after": " \"Altimate Code auto-formats files using prettier, gofmt, ruff, and more\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 102,
+ "before": " \"OpenCode uses LSP servers for intelligent code analysis\",",
+ "after": " \"Altimate Code uses LSP servers for intelligent code analysis\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 107,
+ "before": " \"Create a plugin to prevent OpenCode from reading sensitive files\",",
+ "after": " \"Create a plugin to prevent Altimate Code from reading sensitive files\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 112,
+ "before": " \"Run {highlight}opencode serve{/highlight} for headless API access to OpenCode\",",
+ "after": " \"Run {highlight}opencode serve{/highlight} for headless API access to Altimate Code\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 145,
+ "before": " \"Run {highlight}docker run -it --rm ghcr.io/anomalyco/opencode{/highlight} for containerized use\",",
+ "after": " \"Run {highlight}docker run -it --rm ghcr.io/AltimateAI/altimate-code{/highlight} for containerized use\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 146,
+ "before": " \"Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models\",",
+ "after": " \"Use {highlight}/connect{/highlight} with Altimate Code Zen for curated, tested models\",",
+ "rule": "Product name: Zen"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/aura.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/ayu.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/carbonfox.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/catppuccin-frappe.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/catppuccin.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/cobalt2.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/cursor.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/dracula.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/everforest.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/flexoki.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/github.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/gruvbox.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/kanagawa.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/lucent-orng.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/material.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/matrix.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/mercury.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/monokai.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/nightowl.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/nord.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/one-dark.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/opencode.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/orng.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/osaka-jade.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/palenight.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/rosepine.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/solarized.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/synthwave84.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/tokyonight.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/vercel.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/vesper.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/context/theme/zenburn.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 2,
+ "before": " \"$schema\": \"https://opencode.ai/theme.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/theme.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 160,
+ "before": " ",
+ "after": " ",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 164,
+ "before": " This will allow the following patterns until OpenCode is restarted",
+ "after": " This will allow the following patterns until Altimate Code is restarted",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 504,
+ "before": " Tell OpenCode what to do differently",
+ "after": " Tell Altimate Code what to do differently",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 307,
+ "before": " OpenCode includes free models so you can start immediately.",
+ "after": " Altimate Code includes free models so you can start immediately.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/cli/cmd/uninstall.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 58,
+ "before": " prompts.intro(\"Uninstall OpenCode\")",
+ "after": " prompts.intro(\"Uninstall Altimate Code\")",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 232,
+ "before": " prompts.log.success(\"Thank you for using OpenCode!\")",
+ "after": " prompts.log.success(\"Thank you for using Altimate Code!\")",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/config/config.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 81,
+ "before": " // Config loading order (low -> high precedence): https://opencode.ai/docs/config#precedence-order",
+ "after": " // Config loading order (low -> high precedence): https://altimate.ai/docs/config#precedence-order",
+ "rule": "Root domain"
+ },
+ {
+ "line": 102,
+ "before": " if (!remoteConfig.$schema) remoteConfig.$schema = \"https://opencode.ai/config.json\"",
+ "after": " if (!remoteConfig.$schema) remoteConfig.$schema = \"https://altimate.ai/config.json\"",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1082,
+ "before": " .describe(\"Command configuration, see https://opencode.ai/docs/commands\"),",
+ "after": " .describe(\"Command configuration, see https://altimate.ai/docs/commands\"),",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1149,
+ "before": " .describe(\"Agent configuration, see https://opencode.ai/docs/agents\"),",
+ "after": " .describe(\"Agent configuration, see https://altimate.ai/docs/agents\"),",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1333,
+ "before": " result[\"$schema\"] = \"https://opencode.ai/config.json\"",
+ "after": " result[\"$schema\"] = \"https://altimate.ai/config.json\"",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1377,
+ "before": " parsed.data.$schema = \"https://opencode.ai/config.json\"",
+ "after": " parsed.data.$schema = \"https://altimate.ai/config.json\"",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1378,
+ "before": " const updated = original.replace(/^\\s*\\{/, '{\\n \"$schema\": \"https://opencode.ai/config.json\",')",
+ "after": " const updated = original.replace(/^\\s*\\{/, '{\\n \"$schema\": \"https://altimate.ai/config.json\",')",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/config/migrate-tui-config.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 15,
+ "before": "const TUI_SCHEMA_URL = \"https://opencode.ai/tui.json\"",
+ "after": "const TUI_SCHEMA_URL = \"https://altimate.ai/tui.json\"",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/installation/index.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 37,
+ "before": " const body = await fetch(\"https://opencode.ai/install\").then((res) => {",
+ "after": " const body = await fetch(\"https://altimate.ai/install\").then((res) => {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 185,
+ "before": " const tapFormula = await text([\"brew\", \"list\", \"--formula\", \"anomalyco/tap/opencode\"])",
+ "after": " const tapFormula = await text([\"brew\", \"list\", \"--formula\", \"AltimateAI/tap/altimate-code\"])",
+ "rule": "Homebrew tap"
+ },
+ {
+ "line": 186,
+ "before": " if (tapFormula.includes(\"opencode\")) return \"anomalyco/tap/opencode\"",
+ "after": " if (tapFormula.includes(\"opencode\")) return \"AltimateAI/tap/altimate-code\"",
+ "rule": "Homebrew tap"
+ },
+ {
+ "line": 383,
+ "before": " return fetch(\"https://api.github.com/repos/anomalyco/opencode/releases/latest\")",
+ "after": " return fetch(\"https://api.github.com/repos/AltimateAI/altimate-code/releases/latest\")",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/mcp/oauth-callback.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 10,
+ "before": "
OpenCode - Authorization Successful",
+ "after": " Altimate Code - Authorization Successful",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 21,
+ "before": " You can close this window and return to OpenCode.
",
+ "after": " You can close this window and return to Altimate Code.
",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 30,
+ "before": " OpenCode - Authorization Failed",
+ "after": " Altimate Code - Authorization Failed",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/mcp/oauth-provider.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 41,
+ "before": " client_name: \"OpenCode\",",
+ "after": " client_name: \"Altimate Code\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 42,
+ "before": " client_uri: \"https://opencode.ai\",",
+ "after": " client_uri: \"https://altimate.ai\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/plugin/codex.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 150,
+ "before": " OpenCode - Codex Authorization Successful",
+ "after": " Altimate Code - Codex Authorization Successful",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 181,
+ "before": " You can close this window and return to OpenCode.
",
+ "after": " You can close this window and return to Altimate Code.
",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 192,
+ "before": " OpenCode - Codex Authorization Failed",
+ "after": " Altimate Code - Codex Authorization Failed",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/provider/provider.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 405,
+ "before": " \"HTTP-Referer\": \"https://opencode.ai/\",",
+ "after": " \"HTTP-Referer\": \"https://altimate.ai/\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 416,
+ "before": " \"http-referer\": \"https://opencode.ai/\",",
+ "after": " \"http-referer\": \"https://altimate.ai/\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 515,
+ "before": " \"HTTP-Referer\": \"https://opencode.ai/\",",
+ "after": " \"HTTP-Referer\": \"https://altimate.ai/\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 671,
+ "before": " \"HTTP-Referer\": \"https://opencode.ai/\",",
+ "after": " \"HTTP-Referer\": \"https://altimate.ai/\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/config.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 19,
+ "before": " description: \"Retrieve the current OpenCode configuration settings and preferences.\",",
+ "after": " description: \"Retrieve the current Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 40,
+ "before": " description: \"Update OpenCode configuration settings and preferences.\",",
+ "after": " description: \"Update Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/experimental.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 196,
+ "before": " \"Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.\",",
+ "after": " \"Get a list of all Altimate Code sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/global.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 24,
+ "before": " description: \"Get health information about the OpenCode server.\",",
+ "after": " description: \"Get health information about the Altimate Code server.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 45,
+ "before": " description: \"Subscribe to global events from the OpenCode system using server-sent events.\",",
+ "after": " description: \"Subscribe to global events from the Altimate Code system using server-sent events.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 114,
+ "before": " description: \"Retrieve the current global OpenCode configuration settings and preferences.\",",
+ "after": " description: \"Retrieve the current global Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 135,
+ "before": " description: \"Update global OpenCode configuration settings and preferences.\",",
+ "after": " description: \"Update global Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 160,
+ "before": " description: \"Clean up and dispose all OpenCode instances, releasing all resources.\",",
+ "after": " description: \"Clean up and dispose all Altimate Code instances, releasing all resources.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/project.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 18,
+ "before": " description: \"Get a list of projects that have been opened with OpenCode.\",",
+ "after": " description: \"Get a list of projects that have been opened with Altimate Code.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 40,
+ "before": " description: \"Retrieve the currently active project that OpenCode is working with.\",",
+ "after": " description: \"Retrieve the currently active project that Altimate Code is working with.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/pty.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 17,
+ "before": " description: \"Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.\",",
+ "after": " description: \"Get a list of all active pseudo-terminal (PTY) sessions managed by Altimate Code.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/routes/session.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 31,
+ "before": " description: \"Get a list of all OpenCode sessions, sorted by most recently updated.\",",
+ "after": " description: \"Get a list of all Altimate Code sessions, sorted by most recently updated.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 99,
+ "before": " description: \"Retrieve detailed information about a specific OpenCode session.\",",
+ "after": " description: \"Retrieve detailed information about a specific Altimate Code session.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 192,
+ "before": " description: \"Create a new OpenCode session for interacting with AI assistants and managing conversations.\",",
+ "after": " description: \"Create a new Altimate Code session for interacting with AI assistants and managing conversations.\",",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/server/server.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 118,
+ "before": " // *.opencode.ai (https only, adjust if needed)",
+ "after": " // *.altimate.ai (https only, adjust if needed)",
+ "rule": "Root domain"
+ },
+ {
+ "line": 258,
+ "before": " description: \"Clean up and dispose the current OpenCode instance, releasing all resources.\",",
+ "after": " description: \"Clean up and dispose the current Altimate Code instance, releasing all resources.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 280,
+ "before": " description: \"Retrieve the current working directory and related path information for the OpenCode instance.\",",
+ "after": " description: \"Retrieve the current working directory and related path information for the Altimate Code instance.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 343,
+ "before": " description: \"Get a list of all available commands in the OpenCode system.\",",
+ "after": " description: \"Get a list of all available commands in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 417,
+ "before": " description: \"Get a list of all available AI agents in the OpenCode system.\",",
+ "after": " description: \"Get a list of all available AI agents in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 439,
+ "before": " description: \"Get a list of all available skills in the OpenCode system.\",",
+ "after": " description: \"Get a list of all available skills in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 560,
+ "before": " const response = await proxy(`https://app.opencode.ai${path}`, {",
+ "after": " const response = await proxy(`https://app.altimate.ai${path}`, {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 564,
+ "before": " host: \"app.opencode.ai\",",
+ "after": " host: \"app.altimate.ai\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/session/index.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 819,
+ "before": " // It looks like OpenCode's cost calculation assumes all providers return inputTokens the same way Anthropic does (I'm guessing getUsage logic was originally implemented with anthropic), so it's causing incorrect cost calculation for OpenRouter and others.",
+ "after": " // It looks like Altimate Code's cost calculation assumes all providers return inputTokens the same way Anthropic does (I'm guessing getUsage logic was originally implemented with anthropic), so it's causing incorrect cost calculation for OpenRouter and others.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/session/prompt/anthropic.txt",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 1,
+ "before": "You are OpenCode, the best coding agent on the planet.",
+ "after": "You are Altimate Code, the best coding agent on the planet.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 10,
+ "before": " https://github.com/anomalyco/opencode",
+ "after": " https://github.com/AltimateAI/altimate-code",
+ "rule": "Main repo"
+ },
+ {
+ "line": 12,
+ "before": "When the user directly asks about OpenCode (eg. \"can OpenCode do...\", \"does OpenCode have...\"), or asks in second person (eg. \"are you able...\", \"can you do...\"), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://opencode.ai/docs",
+ "after": "When the user directly asks about OpenCode (eg. \"can OpenCode do...\", \"does OpenCode have...\"), or asks in second person (eg. \"are you able...\", \"can you do...\"), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://altimate.ai/docs",
+ "rule": "Root domain"
+ },
+ {
+ "line": 12,
+ "before": "When the user directly asks about OpenCode (eg. \"can OpenCode do...\", \"does OpenCode have...\"), or asks in second person (eg. \"are you able...\", \"can you do...\"), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://altimate.ai/docs",
+ "after": "When the user directly asks about Altimate Code (eg. \"can Altimate Code do...\", \"does Altimate Code have...\"), or asks in second person (eg. \"are you able...\", \"can you do...\"), or asks how to use a specific Altimate Code feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from Altimate Code docs. The list of available docs is available at https://altimate.ai/docs",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 21,
+ "before": "Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if OpenCode honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.",
+ "after": "Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Altimate Code honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/session/prompt/codex_header.txt",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 1,
+ "before": "You are OpenCode, the best coding agent on the planet.",
+ "after": "You are Altimate Code, the best coding agent on the planet.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/session/prompt/qwen.txt",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 9,
+ "before": "- To give feedback, users should report the issue at https://github.com/anomalyco/opencode/issues",
+ "after": "- To give feedback, users should report the issue at https://github.com/AltimateAI/altimate-code/issues",
+ "rule": "Main repo"
+ },
+ {
+ "line": 11,
+ "before": "When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai",
+ "after": "When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://altimate.ai",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/session/retry.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 67,
+ "before": " return `Free usage exceeded, add credits https://opencode.ai/zen`",
+ "after": " return `Free usage exceeded, add credits https://altimate.ai/zen`",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/src/share/share-next.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 50,
+ "before": " const baseUrl = await Config.get().then((x) => x.enterprise?.url ?? \"https://opncd.ai\")",
+ "after": " const baseUrl = await Config.get().then((x) => x.enterprise?.url ?? \"https://altimate.ai\")",
+ "rule": "Short domain alias"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/branding/branding.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 106,
+ "before": " test(\"no references to opencode.ai domain\", () => {",
+ "after": " test(\"no references to altimate.ai domain\", () => {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 107,
+ "before": " // Should reference altimate.ai, not opencode.ai",
+ "after": " // Should reference altimate.ai, not altimate.ai",
+ "rule": "Root domain"
+ },
+ {
+ "line": 110,
+ "before": " expect(line).not.toContain(\"opencode.ai\")",
+ "after": " expect(line).not.toContain(\"altimate.ai\")",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/branding/upstream-merge-guard.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 209,
+ "before": " test('oauth-provider.ts has client_name: \"Altimate Code\" not \"OpenCode\"', () => {",
+ "after": " test('oauth-provider.ts has client_name: \"Altimate Code\" not \"Altimate Code\"', () => {",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 212,
+ "before": " expect(content).not.toMatch(/client_name:\\s*\"OpenCode\"/)",
+ "after": " expect(content).not.toMatch(/client_name:\\s*\"Altimate Code\"/)",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 215,
+ "before": " test('oauth-callback.ts HTML titles contain \"Altimate Code\" not \"OpenCode\"', () => {",
+ "after": " test('oauth-callback.ts HTML titles contain \"Altimate Code\" not \"Altimate Code\"', () => {",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 222,
+ "before": " expect(title).not.toContain(\"OpenCode\")",
+ "after": " expect(title).not.toContain(\"Altimate Code\")",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 226,
+ "before": " test(\"oauth-callback.ts body text references Altimate Code not OpenCode\", () => {",
+ "after": " test(\"oauth-callback.ts body text references Altimate Code not Altimate Code\", () => {",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 230,
+ "before": " // No user-facing \"OpenCode\" references (excluding internal identifiers)",
+ "after": " // No user-facing \"Altimate Code\" references (excluding internal identifiers)",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 247,
+ "before": "// 5. No opencode.ai Domain Leaks in src/",
+ "after": "// 5. No altimate.ai Domain Leaks in src/",
+ "rule": "Root domain"
+ },
+ {
+ "line": 249,
+ "before": "describe(\"No opencode.ai domain leaks in src/\", () => {",
+ "after": "describe(\"No altimate.ai domain leaks in src/\", () => {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 266,
+ "before": " test(\"no opencode.ai domain references in any src/ .ts files\", async () => {",
+ "after": " test(\"no altimate.ai domain references in any src/ .ts files\", async () => {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 284,
+ "before": " test(\"no opencode.ai domain references in any src/ .tsx files\", async () => {",
+ "after": " test(\"no altimate.ai domain references in any src/ .tsx files\", async () => {",
+ "rule": "Root domain"
+ },
+ {
+ "line": 311,
+ "before": " // The sidebar footer must say \"Altimate Code\", not \"OpenCode\"",
+ "after": " // The sidebar footer must say \"Altimate Code\", not \"Altimate Code\"",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/cli/import.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 11,
+ "before": " expect(parseShareUrl(\"https://opncd.ai/share/Jsj3hNIW\")).toBe(\"Jsj3hNIW\")",
+ "after": " expect(parseShareUrl(\"https://altimate.ai/share/Jsj3hNIW\")).toBe(\"Jsj3hNIW\")",
+ "rule": "Short domain alias"
+ },
+ {
+ "line": 17,
+ "before": " expect(parseShareUrl(\"https://opncd.ai/s/Jsj3hNIW\")).toBeNull() // legacy format",
+ "after": " expect(parseShareUrl(\"https://altimate.ai/s/Jsj3hNIW\")).toBeNull() // legacy format",
+ "rule": "Short domain alias"
+ },
+ {
+ "line": 18,
+ "before": " expect(parseShareUrl(\"https://opncd.ai/share/\")).toBeNull()",
+ "after": " expect(parseShareUrl(\"https://altimate.ai/share/\")).toBeNull()",
+ "rule": "Short domain alias"
+ },
+ {
+ "line": 19,
+ "before": " expect(parseShareUrl(\"https://opncd.ai/share/id/extra\")).toBeNull()",
+ "after": " expect(parseShareUrl(\"https://altimate.ai/share/id/extra\")).toBeNull()",
+ "rule": "Short domain alias"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/config/agent-color.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 15,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 40,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/config/config.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 40,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 74,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 111,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 136,
+ "before": " \"$schema\": \"https://opencode.ai/config.json\",",
+ "after": " \"$schema\": \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 159,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 166,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 189,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 299,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 318,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 336,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 368,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 398,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 429,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 459,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 481,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 829,
+ "before": " JSON.stringify({ $schema: \"https://opencode.ai/config.json\", plugin: [\"@scope/plugin\"] }, null, 2),",
+ "after": " JSON.stringify({ $schema: \"https://altimate.ai/config.json\", plugin: [\"@scope/plugin\"] }, null, 2),",
+ "rule": "Root domain"
+ },
+ {
+ "line": 864,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 873,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 940,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 948,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 979,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 987,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1023,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1032,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1071,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1102,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1133,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1163,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1172,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1192,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1200,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1219,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1240,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1269,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1298,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1327,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1362,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1395,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1443,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1462,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1501,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1518,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1553,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1569,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1637,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1711,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1795,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1830,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1925,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1971,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1984,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2023,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2058,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/fixture/fixture.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 53,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/mcp/headers.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 56,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/mcp/oauth-auto-connect.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 110,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/mcp/oauth-browser.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 114,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 165,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 214,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/memory/tools.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 8,
+ "before": "// without requiring the full OpenCode runtime.",
+ "after": "// without requiring the full Altimate Code runtime.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/provider/amazon-bedrock.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 18,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 50,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 75,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 143,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 176,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 209,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 247,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 284,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 320,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 356,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/provider/gitlab-duo.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 16,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 40,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 72,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 109,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 145,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 177,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 207,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 231,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 266,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/provider/provider.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 16,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 43,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 70,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 94,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 120,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 151,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 181,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 216,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 259,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 293,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 320,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 342,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 373,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 397,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 422,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 461,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 502,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 537,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 565,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 590,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 610,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 648,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 687,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 726,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 762,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 786,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 811,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 844,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 881,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 926,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 950,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 991,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1026,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1068,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1101,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1143,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1185,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1222,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1272,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1306,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1341,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1376,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1419,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1459,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1485,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1520,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1548,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1576,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1596,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1620,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1643,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1668,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1705,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1742,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1778,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1822,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1849,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1887,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1928,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1968,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2005,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2045,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2083,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2140,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2184,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2231,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 2256,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/provider/transform.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 1346,
+ "before": " url: \"https://api.opencode.ai\",",
+ "after": " url: \"https://api.altimate.ai\",",
+ "rule": "API subdomain"
+ },
+ {
+ "line": 1380,
+ "before": " url: \"https://api.opencode.ai\",",
+ "after": " url: \"https://api.altimate.ai\",",
+ "rule": "API subdomain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/session/llm.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 251,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 374,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 503,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 604,
+ "before": " $schema: \"https://opencode.ai/config.json\",",
+ "after": " $schema: \"https://altimate.ai/config.json\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/skill/skill.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 365,
+ "before": "# OpenCode Skill",
+ "after": "# Altimate Code Skill",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 375,
+ "before": "# OpenCode Skill",
+ "after": "# Altimate Code Skill",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/tool/fixtures/models-api.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 20980,
+ "before": " \"api\": \"https://opencode.ai/zen/v1\",",
+ "after": " \"api\": \"https://altimate.ai/zen/v1\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 20981,
+ "before": " \"name\": \"OpenCode Zen\",",
+ "after": " \"name\": \"Altimate Code Zen\",",
+ "rule": "Product name: Zen"
+ },
+ {
+ "line": 20982,
+ "before": " \"doc\": \"https://opencode.ai/docs/zen\",",
+ "after": " \"doc\": \"https://altimate.ai/docs/zen\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/upstream/github.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 40,
+ "before": " html_url: \"https://github.com/anomalyco/opencode/releases/tag/v1.2.26\",",
+ "after": " html_url: \"https://github.com/AltimateAI/altimate-code/releases/tag/v1.2.26\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 48,
+ "before": " html_url: \"https://github.com/anomalyco/opencode/releases/tag/v1.2.25\",",
+ "after": " html_url: \"https://github.com/AltimateAI/altimate-code/releases/tag/v1.2.25\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 56,
+ "before": " html_url: \"https://github.com/anomalyco/opencode/releases/tag/v1.2.24-beta.1\",",
+ "after": " html_url: \"https://github.com/AltimateAI/altimate-code/releases/tag/v1.2.24-beta.1\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 64,
+ "before": " html_url: \"https://github.com/anomalyco/opencode/releases/tag/v1.2.24\",",
+ "after": " html_url: \"https://github.com/AltimateAI/altimate-code/releases/tag/v1.2.24\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 74,
+ "before": " html_url: \"https://github.com/anomalyco/opencode/releases/tag/v1.3.0\",",
+ "after": " html_url: \"https://github.com/AltimateAI/altimate-code/releases/tag/v1.3.0\",",
+ "rule": "Main repo"
+ },
+ {
+ "line": 92,
+ "before": " const releases = await fetchReleases(\"anomalyco/opencode\")",
+ "after": " const releases = await fetchReleases(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 101,
+ "before": " const releases = await fetchReleases(\"anomalyco/opencode\", {",
+ "after": " const releases = await fetchReleases(\"AltimateAI/altimate-code\", {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 112,
+ "before": " const releases = await fetchReleases(\"anomalyco/opencode\", {",
+ "after": " const releases = await fetchReleases(\"AltimateAI/altimate-code\", {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 121,
+ "before": " const releases = await fetchReleases(\"anomalyco/opencode\")",
+ "after": " const releases = await fetchReleases(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 128,
+ "before": " expect(fetchReleases(\"anomalyco/opencode\")).rejects.toThrow(",
+ "after": " expect(fetchReleases(\"AltimateAI/altimate-code\")).rejects.toThrow(",
+ "rule": "Main repo"
+ },
+ {
+ "line": 136,
+ "before": " await fetchReleases(\"anomalyco/opencode\")",
+ "after": " await fetchReleases(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 137,
+ "before": " expect(lastExecCmd).toContain(\"repos/anomalyco/opencode/releases\")",
+ "after": " expect(lastExecCmd).toContain(\"repos/AltimateAI/altimate-code/releases\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 143,
+ "before": " await fetchReleases(\"anomalyco/opencode\", { limit: 5 })",
+ "after": " await fetchReleases(\"AltimateAI/altimate-code\", { limit: 5 })",
+ "rule": "Main repo"
+ },
+ {
+ "line": 150,
+ "before": " await fetchReleases(\"anomalyco/opencode\")",
+ "after": " await fetchReleases(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 159,
+ "before": " await fetchReleases(\"anomalyco/opencode\", { limit: 10 })",
+ "after": " await fetchReleases(\"AltimateAI/altimate-code\", { limit: 10 })",
+ "rule": "Main repo"
+ },
+ {
+ "line": 179,
+ "before": " const release = await getRelease(\"anomalyco/opencode\", \"v1.2.26\")",
+ "after": " const release = await getRelease(\"AltimateAI/altimate-code\", \"v1.2.26\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 188,
+ "before": " const release = await getRelease(\"anomalyco/opencode\", \"v1.3.0\")",
+ "after": " const release = await getRelease(\"AltimateAI/altimate-code\", \"v1.3.0\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 195,
+ "before": " const release = await getRelease(\"anomalyco/opencode\", \"v99.99.99\")",
+ "after": " const release = await getRelease(\"AltimateAI/altimate-code\", \"v99.99.99\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 202,
+ "before": " const release = await getRelease(\"anomalyco/opencode\", \"v1.2.26\")",
+ "after": " const release = await getRelease(\"AltimateAI/altimate-code\", \"v1.2.26\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 209,
+ "before": " await getRelease(\"anomalyco/opencode\", \"v1.2.26\")",
+ "after": " await getRelease(\"AltimateAI/altimate-code\", \"v1.2.26\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 228,
+ "before": " const tags = await getReleaseTags(\"anomalyco/opencode\")",
+ "after": " const tags = await getReleaseTags(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 236,
+ "before": " const tags = await getReleaseTags(\"anomalyco/opencode\", {",
+ "after": " const tags = await getReleaseTags(\"AltimateAI/altimate-code\", {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 256,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.2.26\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.2.26\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 265,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v99.99.99\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v99.99.99\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 273,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.2.24-beta.1\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.2.24-beta.1\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 282,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.3.0\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.3.0\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 289,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.2.24-beta.1\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.2.24-beta.1\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 296,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"vscode-v0.0.5\")",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"vscode-v0.0.5\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 297,
+ "before": " expect(result.reason).toContain(\"anomalyco/opencode\")",
+ "after": " expect(result.reason).toContain(\"AltimateAI/altimate-code\")",
+ "rule": "Main repo"
+ },
+ {
+ "line": 303,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.2.24-beta.1\", {",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.2.24-beta.1\", {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 314,
+ "before": " const result = await validateRelease(\"anomalyco/opencode\", \"v1.2.24-beta.1\", {",
+ "after": " const result = await validateRelease(\"AltimateAI/altimate-code\", \"v1.2.24-beta.1\", {",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "packages/opencode/test/upstream/release-only-merge.test.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 88,
+ "before": " expect(listScript).toContain(\"Upstream OpenCode Releases\")",
+ "after": " expect(listScript).toContain(\"Upstream Altimate Code Releases\")",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 150,
+ "before": " test(\"upstreamRepo points to anomalyco/opencode\", () => {",
+ "after": " test(\"upstreamRepo points to AltimateAI/altimate-code\", () => {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 151,
+ "before": " expect(configModule).toContain('\"anomalyco/opencode\"')",
+ "after": " expect(configModule).toContain('\"AltimateAI/altimate-code\"')",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "packages/sdk/js/src/gen/types.gen.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 1212,
+ "before": " * Command configuration, see https://opencode.ai/docs/commands",
+ "after": " * Command configuration, see https://altimate.ai/docs/commands",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1269,
+ "before": " * Agent configuration, see https://opencode.ai/docs/agent",
+ "after": " * Agent configuration, see https://altimate.ai/docs/agent",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/sdk/js/src/v2/gen/sdk.gen.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 235,
+ "before": " * Retrieve the current global OpenCode configuration settings and preferences.",
+ "after": " * Retrieve the current global Altimate Code configuration settings and preferences.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 247,
+ "before": " * Update global OpenCode configuration settings and preferences.",
+ "after": " * Update global Altimate Code configuration settings and preferences.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 273,
+ "before": " * Get health information about the OpenCode server.",
+ "after": " * Get health information about the Altimate Code server.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 285,
+ "before": " * Subscribe to global events from the OpenCode system using server-sent events.",
+ "after": " * Subscribe to global events from the Altimate Code system using server-sent events.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 297,
+ "before": " * Clean up and dispose all OpenCode instances, releasing all resources.",
+ "after": " * Clean up and dispose all Altimate Code instances, releasing all resources.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 372,
+ "before": " * Get a list of projects that have been opened with OpenCode.",
+ "after": " * Get a list of projects that have been opened with Altimate Code.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 402,
+ "before": " * Retrieve the currently active project that OpenCode is working with.",
+ "after": " * Retrieve the currently active project that Altimate Code is working with.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 516,
+ "before": " * Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.",
+ "after": " * Get a list of all active pseudo-terminal (PTY) sessions managed by Altimate Code.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 735,
+ "before": " * Retrieve the current OpenCode configuration settings and preferences.",
+ "after": " * Retrieve the current Altimate Code configuration settings and preferences.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 765,
+ "before": " * Update OpenCode configuration settings and preferences.",
+ "after": " * Update Altimate Code configuration settings and preferences.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1015,
+ "before": " * Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.",
+ "after": " * Get a list of all Altimate Code sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1251,
+ "before": " * Get a list of all OpenCode sessions, sorted by most recently updated.",
+ "after": " * Get a list of all Altimate Code sessions, sorted by most recently updated.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1289,
+ "before": " * Create a new OpenCode session for interacting with AI assistants and managing conversations.",
+ "after": " * Create a new Altimate Code session for interacting with AI assistants and managing conversations.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1394,
+ "before": " * Retrieve detailed information about a specific OpenCode session.",
+ "after": " * Retrieve detailed information about a specific Altimate Code session.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 3570,
+ "before": " * Clean up and dispose the current OpenCode instance, releasing all resources.",
+ "after": " * Clean up and dispose the current Altimate Code instance, releasing all resources.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 3602,
+ "before": " * Retrieve the current working directory and related path information for the OpenCode instance.",
+ "after": " * Retrieve the current working directory and related path information for the Altimate Code instance.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 3666,
+ "before": " * Get a list of all available commands in the OpenCode system.",
+ "after": " * Get a list of all available commands in the Altimate Code system.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 3743,
+ "before": " * Get a list of all available AI agents in the OpenCode system.",
+ "after": " * Get a list of all available AI agents in the Altimate Code system.",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 3773,
+ "before": " * Get a list of all available skills in the OpenCode system.",
+ "after": " * Get a list of all available skills in the Altimate Code system.",
+ "rule": "Product name: generic"
+ }
+ ]
+ },
+ {
+ "file": "packages/sdk/js/src/v2/gen/types.gen.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 1318,
+ "before": " * Command configuration, see https://opencode.ai/docs/commands",
+ "after": " * Command configuration, see https://altimate.ai/docs/commands",
+ "rule": "Root domain"
+ },
+ {
+ "line": 1392,
+ "before": " * Agent configuration, see https://opencode.ai/docs/agents",
+ "after": " * Agent configuration, see https://altimate.ai/docs/agents",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "packages/sdk/openapi.json",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 13,
+ "before": " \"description\": \"Get health information about the OpenCode server.\",",
+ "after": " \"description\": \"Get health information about the Altimate Code server.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 48,
+ "before": " \"description\": \"Subscribe to global events from the OpenCode system using server-sent events.\",",
+ "after": " \"description\": \"Subscribe to global events from the Altimate Code system using server-sent events.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 73,
+ "before": " \"description\": \"Retrieve the current global OpenCode configuration settings and preferences.\",",
+ "after": " \"description\": \"Retrieve the current global Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 96,
+ "before": " \"description\": \"Update global OpenCode configuration settings and preferences.\",",
+ "after": " \"description\": \"Update global Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 140,
+ "before": " \"description\": \"Clean up and dispose all OpenCode instances, releasing all resources.\",",
+ "after": " \"description\": \"Clean up and dispose all Altimate Code instances, releasing all resources.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 278,
+ "before": " \"description\": \"Get a list of projects that have been opened with OpenCode.\",",
+ "after": " \"description\": \"Get a list of projects that have been opened with Altimate Code.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 322,
+ "before": " \"description\": \"Retrieve the currently active project that OpenCode is working with.\",",
+ "after": " \"description\": \"Retrieve the currently active project that Altimate Code is working with.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 510,
+ "before": " \"description\": \"Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.\",",
+ "after": " \"description\": \"Get a list of all active pseudo-terminal (PTY) sessions managed by Altimate Code.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 900,
+ "before": " \"description\": \"Retrieve the current OpenCode configuration settings and preferences.\",",
+ "after": " \"description\": \"Retrieve the current Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 939,
+ "before": " \"description\": \"Update OpenCode configuration settings and preferences.\",",
+ "after": " \"description\": \"Update Altimate Code configuration settings and preferences.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1635,
+ "before": " \"description\": \"Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.\",",
+ "after": " \"description\": \"Get a list of all Altimate Code sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1759,
+ "before": " \"description\": \"Get a list of all OpenCode sessions, sorted by most recently updated.\",",
+ "after": " \"description\": \"Get a list of all Altimate Code sessions, sorted by most recently updated.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1801,
+ "before": " \"description\": \"Create a new OpenCode session for interacting with AI assistants and managing conversations.\",",
+ "after": " \"description\": \"Create a new Altimate Code session for interacting with AI assistants and managing conversations.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 1943,
+ "before": " \"description\": \"Retrieve detailed information about a specific OpenCode session.\",",
+ "after": " \"description\": \"Retrieve detailed information about a specific Altimate Code session.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 6466,
+ "before": " \"description\": \"Clean up and dispose the current OpenCode instance, releasing all resources.\",",
+ "after": " \"description\": \"Clean up and dispose the current Altimate Code instance, releasing all resources.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 6507,
+ "before": " \"description\": \"Retrieve the current working directory and related path information for the OpenCode instance.\",",
+ "after": " \"description\": \"Retrieve the current working directory and related path information for the Altimate Code instance.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 6589,
+ "before": " \"description\": \"Get a list of all available commands in the OpenCode system.\",",
+ "after": " \"description\": \"Get a list of all available commands in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 6717,
+ "before": " \"description\": \"Get a list of all available AI agents in the OpenCode system.\",",
+ "after": " \"description\": \"Get a list of all available AI agents in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 6761,
+ "before": " \"description\": \"Get a list of all available skills in the OpenCode system.\",",
+ "after": " \"description\": \"Get a list of all available skills in the Altimate Code system.\",",
+ "rule": "Product name: generic"
+ },
+ {
+ "line": 10343,
+ "before": " \"description\": \"Command configuration, see https://opencode.ai/docs/commands\",",
+ "after": " \"description\": \"Command configuration, see https://altimate.ai/docs/commands\",",
+ "rule": "Root domain"
+ },
+ {
+ "line": 10477,
+ "before": " \"description\": \"Agent configuration, see https://opencode.ai/docs/agents\",",
+ "after": " \"description\": \"Agent configuration, see https://altimate.ai/docs/agents\",",
+ "rule": "Root domain"
+ }
+ ]
+ },
+ {
+ "file": "script/changelog.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 15,
+ "before": " const data = await fetch(\"https://api.github.com/repos/anomalyco/opencode/releases?per_page=100\").then((res) => {",
+ "after": " const data = await fetch(\"https://api.github.com/repos/AltimateAI/altimate-code/releases?per_page=100\").then((res) => {",
+ "rule": "Main repo"
+ },
+ {
+ "line": 46,
+ "before": " await $`gh api \"/repos/anomalyco/opencode/compare/${fromRef}...${toRef}\" --jq '.commits[] | {sha: .sha, login: .author.login, message: .commit.message}'`.text()",
+ "after": " await $`gh api \"/repos/AltimateAI/altimate-code/compare/${fromRef}...${toRef}\" --jq '.commits[] | {sha: .sha, login: .author.login, message: .commit.message}'`.text()",
+ "rule": "Main repo"
+ },
+ {
+ "line": 204,
+ "before": " await $`gh api \"/repos/anomalyco/opencode/compare/${fromRef}...${toRef}\" --jq '.commits[] | {login: .author.login, message: .commit.message}'`.text()",
+ "after": " await $`gh api \"/repos/AltimateAI/altimate-code/compare/${fromRef}...${toRef}\" --jq '.commits[] | {login: .author.login, message: .commit.message}'`.text()",
+ "rule": "Main repo"
+ }
+ ]
+ },
+ {
+ "file": "script/stats.ts",
+ "transform": "branding",
+ "changes": [
+ {
+ "line": 76,
+ "before": " const url = `https://api.github.com/repos/anomalyco/opencode/releases?page=${page}&per_page=${per}`",
+ "after": " const url = `https://api.github.com/repos/AltimateAI/altimate-code/releases?page=${page}&per_page=${per}`",
+ "rule": "Main repo"
+ },
+ {
+ "line": 191,
+ "before": "console.log(\"Fetching GitHub releases for anomalyco/opencode...\\n\")",
+ "after": "console.log(\"Fetching GitHub releases for AltimateAI/altimate-code...\\n\")",
+ "rule": "Main repo"
+ }
+ ]
+ }
+ ],
+ "totalChanges": 391,
+ "totalFiles": 92,
+ "conflicts": []
+}
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index b32f39f16a..393bf90518 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,13 +1,29 @@
-## Summary
+### Issue for this PR
-What changed and why?
+Closes #
-## Test Plan
+### Type of change
-How was this tested?
+- [ ] Bug fix
+- [ ] New feature
+- [ ] Refactor / code improvement
+- [ ] Documentation
-## Checklist
+### What does this PR do?
-- [ ] Tests added/updated
-- [ ] Documentation updated (if needed)
-- [ ] CHANGELOG updated (if user-facing)
+Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
+
+**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
+
+### How did you verify your code works?
+
+### Screenshots / recordings
+
+_If this is a UI change, please include a screenshot or recording._
+
+### Checklist
+
+- [ ] I have tested my changes locally
+- [ ] I have not included unrelated changes in this PR
+
+_If you do not follow this template your PR will be automatically rejected._
diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml
new file mode 100644
index 0000000000..a7106667b1
--- /dev/null
+++ b/.github/workflows/beta.yml
@@ -0,0 +1,37 @@
+name: beta
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "0 * * * *"
+
+jobs:
+ sync:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Setup Git Committer
+ id: setup-git-committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Install OpenCode
+ run: bun i -g opencode-ai
+
+ - name: Sync beta branch
+ env:
+ GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ run: bun script/beta.ts
diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml
new file mode 100644
index 0000000000..c3bcf9f686
--- /dev/null
+++ b/.github/workflows/compliance-close.yml
@@ -0,0 +1,95 @@
+name: compliance-close
+
+on:
+ schedule:
+ # Run every 30 minutes to check for expired compliance windows
+ - cron: "*/30 * * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+
+jobs:
+ close-non-compliant:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Close non-compliant issues and PRs after 2 hours
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { data: items } = await github.rest.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ labels: 'needs:compliance',
+ state: 'open',
+ per_page: 100,
+ });
+
+ if (items.length === 0) {
+ core.info('No open issues/PRs with needs:compliance label');
+ return;
+ }
+
+ const now = Date.now();
+ const twoHours = 2 * 60 * 60 * 1000;
+
+ for (const item of items) {
+ const isPR = !!item.pull_request;
+ const kind = isPR ? 'PR' : 'issue';
+
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: item.number,
+ });
+
+ const complianceComment = comments.find(c => c.body.includes(''));
+ if (!complianceComment) continue;
+
+ const commentAge = now - new Date(complianceComment.created_at).getTime();
+ if (commentAge < twoHours) {
+ core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`);
+ continue;
+ }
+
+ const closeMessage = isPR
+ ? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.'
+ : 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.';
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: item.number,
+ body: closeMessage,
+ });
+
+ try {
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: item.number,
+ name: 'needs:compliance',
+ });
+ } catch (e) {}
+
+ if (isPR) {
+ await github.rest.pulls.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: item.number,
+ state: 'closed',
+ });
+ } else {
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: item.number,
+ state: 'closed',
+ state_reason: 'not_planned',
+ });
+ }
+
+ core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`);
+ }
diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml
new file mode 100644
index 0000000000..c7df066d41
--- /dev/null
+++ b/.github/workflows/containers.yml
@@ -0,0 +1,45 @@
+name: containers
+
+on:
+ push:
+ branches:
+ - dev
+ paths:
+ - packages/containers/**
+ - .github/workflows/containers.yml
+ - package.json
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ packages: write
+
+jobs:
+ build:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ env:
+ REGISTRY: ghcr.io/${{ github.repository_owner }}
+ TAG: "24.04"
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push containers
+ run: bun ./packages/containers/script/build.ts --push
+ env:
+ REGISTRY: ${{ env.REGISTRY }}
+ TAG: ${{ env.TAG }}
diff --git a/.github/workflows/daily-issues-recap.yml b/.github/workflows/daily-issues-recap.yml
new file mode 100644
index 0000000000..31cf08233b
--- /dev/null
+++ b/.github/workflows/daily-issues-recap.yml
@@ -0,0 +1,170 @@
+name: daily-issues-recap
+
+on:
+ schedule:
+ # Run at 6 PM EST (23:00 UTC, or 22:00 UTC during daylight saving)
+ - cron: "0 23 * * *"
+ workflow_dispatch: # Allow manual trigger for testing
+
+jobs:
+ daily-recap:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ issues: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Generate daily issues recap
+ id: recap
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh issue*": "allow",
+ "gh search*": "allow"
+ },
+ "webfetch": "deny",
+ "edit": "deny",
+ "write": "deny"
+ }
+ run: |
+ # Get today's date range
+ TODAY=$(date -u +%Y-%m-%d)
+
+ opencode run -m opencode/claude-sonnet-4-5 "Generate a daily issues recap for the OpenCode repository.
+
+ TODAY'S DATE: ${TODAY}
+
+ STEP 1: Gather today's issues
+ Search for all OPEN issues created today (${TODAY}) using:
+ gh issue list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,body,labels,state,comments,createdAt,author --limit 500
+
+ IMPORTANT: EXCLUDE all issues authored by Anomaly team members. Filter out issues where the author login matches ANY of these:
+ adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
+ This recap is specifically for COMMUNITY (external) issues only.
+
+ STEP 2: Analyze and categorize
+ For each issue created today, categorize it:
+
+ **Severity Assessment:**
+ - CRITICAL: Crashes, data loss, security issues, blocks major functionality
+ - HIGH: Significant bugs affecting many users, important features broken
+ - MEDIUM: Bugs with workarounds, minor features broken
+ - LOW: Minor issues, cosmetic, nice-to-haves
+
+ **Activity Assessment:**
+ - Note issues with high comment counts or engagement
+ - Note issues from repeat reporters (check if author has filed before)
+
+ STEP 3: Cross-reference with existing issues
+ For issues that seem like feature requests or recurring bugs:
+ - Search for similar older issues to identify patterns
+ - Note if this is a frequently requested feature
+ - Identify any issues that are duplicates of long-standing requests
+
+ STEP 4: Generate the recap
+ Create a structured recap with these sections:
+
+ ===DISCORD_START===
+ **Daily Issues Recap - ${TODAY}**
+
+ **Summary Stats**
+ - Total issues opened today: [count]
+ - By category: [bugs/features/questions]
+
+ **Critical/High Priority Issues**
+ [List any CRITICAL or HIGH severity issues with brief descriptions and issue numbers]
+
+ **Most Active/Discussed**
+ [Issues with significant engagement or from active community members]
+
+ **Trending Topics**
+ [Patterns noticed - e.g., 'Multiple reports about X', 'Continued interest in Y feature']
+
+ **Duplicates & Related**
+ [Issues that relate to existing open issues]
+ ===DISCORD_END===
+
+ STEP 5: Format for Discord
+ Format the recap as a Discord-compatible message:
+ - Use Discord markdown (**, __, etc.)
+ - BE EXTREMELY CONCISE - this is an EOD summary, not a detailed report
+ - Use hyperlinked issue numbers with suppressed embeds: [#1234]()
+ - Group related issues on single lines where possible
+ - Add emoji sparingly for critical items only
+ - HARD LIMIT: Keep under 1800 characters total
+ - Skip sections that have nothing notable (e.g., if no critical issues, omit that section)
+ - Prioritize signal over completeness - only surface what matters
+
+ OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/recap_raw.txt
+
+ # Extract only the Discord message between markers
+ sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/recap_raw.txt | grep -v '===DISCORD' > /tmp/recap.txt
+
+ echo "recap_file=/tmp/recap.txt" >> $GITHUB_OUTPUT
+
+ - name: Post to Discord
+ env:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
+ run: |
+ if [ -z "$DISCORD_WEBHOOK_URL" ]; then
+ echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
+ cat /tmp/recap.txt
+ exit 0
+ fi
+
+ # Read the recap
+ RECAP_RAW=$(cat /tmp/recap.txt)
+ RECAP_LENGTH=${#RECAP_RAW}
+
+ echo "Recap length: ${RECAP_LENGTH} chars"
+
+ # Function to post a message to Discord
+ post_to_discord() {
+ local msg="$1"
+ local content=$(echo "$msg" | jq -Rs '.')
+ curl -s -H "Content-Type: application/json" \
+ -X POST \
+ -d "{\"content\": ${content}}" \
+ "$DISCORD_WEBHOOK_URL"
+ sleep 1
+ }
+
+ # If under limit, send as single message
+ if [ "$RECAP_LENGTH" -le 1950 ]; then
+ post_to_discord "$RECAP_RAW"
+ else
+ echo "Splitting into multiple messages..."
+ remaining="$RECAP_RAW"
+ while [ ${#remaining} -gt 0 ]; do
+ if [ ${#remaining} -le 1950 ]; then
+ post_to_discord "$remaining"
+ break
+ else
+ chunk="${remaining:0:1900}"
+ last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
+ if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
+ chunk="${remaining:0:$last_newline}"
+ remaining="${remaining:$((last_newline+1))}"
+ else
+ chunk="${remaining:0:1900}"
+ remaining="${remaining:1900}"
+ fi
+ post_to_discord "$chunk"
+ fi
+ done
+ fi
+
+ echo "Posted daily recap to Discord"
diff --git a/.github/workflows/daily-pr-recap.yml b/.github/workflows/daily-pr-recap.yml
new file mode 100644
index 0000000000..2f0f023cfd
--- /dev/null
+++ b/.github/workflows/daily-pr-recap.yml
@@ -0,0 +1,173 @@
+name: daily-pr-recap
+
+on:
+ schedule:
+ # Run at 5pm EST (22:00 UTC, or 21:00 UTC during daylight saving)
+ - cron: "0 22 * * *"
+ workflow_dispatch: # Allow manual trigger for testing
+
+jobs:
+ pr-recap:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ pull-requests: read
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Generate daily PR recap
+ id: recap
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh pr*": "allow",
+ "gh search*": "allow"
+ },
+ "webfetch": "deny",
+ "edit": "deny",
+ "write": "deny"
+ }
+ run: |
+ TODAY=$(date -u +%Y-%m-%d)
+
+ opencode run -m opencode/claude-sonnet-4-5 "Generate a daily PR activity recap for the OpenCode repository.
+
+ TODAY'S DATE: ${TODAY}
+
+ STEP 1: Gather PR data
+ Run these commands to gather PR information. ONLY include OPEN PRs created or updated TODAY (${TODAY}):
+
+ # Open PRs created today
+ gh pr list --repo ${{ github.repository }} --state open --search \"created:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
+
+ # Open PRs with activity today (updated today)
+ gh pr list --repo ${{ github.repository }} --state open --search \"updated:${TODAY}\" --json number,title,author,labels,createdAt,updatedAt,reviewDecision,isDraft,additions,deletions --limit 100
+
+ IMPORTANT: EXCLUDE all PRs authored by Anomaly team members. Filter out PRs where the author login matches ANY of these:
+ adamdotdevin, Brendonovich, fwang, Hona, iamdavidhill, jayair, kitlangton, kommander, MrMushrooooom, R44VC0RP, rekram1-node, thdxr
+ This recap is specifically for COMMUNITY (external) contributions only.
+
+
+
+ STEP 2: For high-activity PRs, check comment counts
+ For promising PRs, run:
+ gh pr view [NUMBER] --repo ${{ github.repository }} --json comments --jq '[.comments[] | select(.author.login != \"copilot-pull-request-reviewer\" and .author.login != \"github-actions\")] | length'
+
+ IMPORTANT: When counting comments/activity, EXCLUDE these bot accounts:
+ - copilot-pull-request-reviewer
+ - github-actions
+
+ STEP 3: Identify what matters (ONLY from today's PRs)
+
+ **Bug Fixes From Today:**
+ - PRs with 'fix' or 'bug' in title created/updated today
+ - Small bug fixes (< 100 lines changed) that are easy to review
+ - Bug fixes from community contributors
+
+ **High Activity Today:**
+ - PRs with significant human comments today (excluding bots listed above)
+ - PRs with back-and-forth discussion today
+
+ **Quick Wins:**
+ - Small PRs (< 50 lines) that are approved or nearly approved
+ - PRs that just need a final review
+
+ STEP 4: Generate the recap
+ Create a structured recap:
+
+ ===DISCORD_START===
+ **Daily PR Recap - ${TODAY}**
+
+ **New PRs Today**
+ [PRs opened today - group by type: bug fixes, features, etc.]
+
+ **Active PRs Today**
+ [PRs with activity/updates today - significant discussion]
+
+ **Quick Wins**
+ [Small PRs ready to merge]
+ ===DISCORD_END===
+
+ STEP 5: Format for Discord
+ - Use Discord markdown (**, __, etc.)
+ - BE EXTREMELY CONCISE - surface what we might miss
+ - Use hyperlinked PR numbers with suppressed embeds: [#1234]()
+ - Include PR author: [#1234]() (@author)
+ - For bug fixes, add brief description of what it fixes
+ - Show line count for quick wins: \"(+15/-3 lines)\"
+ - HARD LIMIT: Keep under 1800 characters total
+ - Skip empty sections
+ - Focus on PRs that need human eyes
+
+ OUTPUT: Output ONLY the content between ===DISCORD_START=== and ===DISCORD_END=== markers. Include the markers so I can extract it." > /tmp/pr_recap_raw.txt
+
+ # Extract only the Discord message between markers
+ sed -n '/===DISCORD_START===/,/===DISCORD_END===/p' /tmp/pr_recap_raw.txt | grep -v '===DISCORD' > /tmp/pr_recap.txt
+
+ echo "recap_file=/tmp/pr_recap.txt" >> $GITHUB_OUTPUT
+
+ - name: Post to Discord
+ env:
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ISSUES_WEBHOOK_URL }}
+ run: |
+ if [ -z "$DISCORD_WEBHOOK_URL" ]; then
+ echo "Warning: DISCORD_ISSUES_WEBHOOK_URL secret not set, skipping Discord post"
+ cat /tmp/pr_recap.txt
+ exit 0
+ fi
+
+ # Read the recap
+ RECAP_RAW=$(cat /tmp/pr_recap.txt)
+ RECAP_LENGTH=${#RECAP_RAW}
+
+ echo "Recap length: ${RECAP_LENGTH} chars"
+
+ # Function to post a message to Discord
+ post_to_discord() {
+ local msg="$1"
+ local content=$(echo "$msg" | jq -Rs '.')
+ curl -s -H "Content-Type: application/json" \
+ -X POST \
+ -d "{\"content\": ${content}}" \
+ "$DISCORD_WEBHOOK_URL"
+ sleep 1
+ }
+
+ # If under limit, send as single message
+ if [ "$RECAP_LENGTH" -le 1950 ]; then
+ post_to_discord "$RECAP_RAW"
+ else
+ echo "Splitting into multiple messages..."
+ remaining="$RECAP_RAW"
+ while [ ${#remaining} -gt 0 ]; do
+ if [ ${#remaining} -le 1950 ]; then
+ post_to_discord "$remaining"
+ break
+ else
+ chunk="${remaining:0:1900}"
+ last_newline=$(echo "$chunk" | grep -bo $'\n' | tail -1 | cut -d: -f1)
+ if [ -n "$last_newline" ] && [ "$last_newline" -gt 500 ]; then
+ chunk="${remaining:0:$last_newline}"
+ remaining="${remaining:$((last_newline+1))}"
+ else
+ chunk="${remaining:0:1900}"
+ remaining="${remaining:1900}"
+ fi
+ post_to_discord "$chunk"
+ fi
+ done
+ fi
+
+ echo "Posted daily PR recap to Discord"
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000000..c08d7edf3b
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,38 @@
+name: deploy
+
+on:
+ push:
+ branches:
+ - dev
+ - production
+ workflow_dispatch:
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ deploy:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: ./.github/actions/setup-bun
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ # Workaround for Pulumi version conflict:
+ # GitHub runners have Pulumi 3.212.0+ pre-installed, which removed the -root flag
+ # from pulumi-language-nodejs (see https://github.com/pulumi/pulumi/pull/21065).
+ # SST 3.17.x uses Pulumi SDK 3.210.0 which still passes -root, causing a conflict.
+ # Removing the system language plugin forces SST to use its bundled compatible version.
+ # TODO: Remove when sst supports Pulumi >3.210.0
+ - name: Fix Pulumi version conflict
+ run: sudo rm -f /usr/local/bin/pulumi-language-nodejs
+
+ - run: bun sst deploy --stage=${{ github.ref_name }}
+ env:
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
+ PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
+ STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml
new file mode 100644
index 0000000000..fff2ec4292
--- /dev/null
+++ b/.github/workflows/docs-locale-sync.yml
@@ -0,0 +1,99 @@
+name: docs-locale-sync
+
+on:
+ push:
+ branches:
+ - dev
+ paths:
+ - packages/web/src/content/docs/*.mdx
+
+jobs:
+ sync-locales:
+ if: github.actor != 'opencode-agent[bot]'
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+ ref: ${{ github.ref_name }}
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Compute changed English docs
+ id: changes
+ run: |
+ FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'packages/web/src/content/docs/*.mdx' || true)
+ if [ -z "$FILES" ]; then
+ echo "has_changes=false" >> "$GITHUB_OUTPUT"
+ echo "No English docs changed in push range"
+ exit 0
+ fi
+ echo "has_changes=true" >> "$GITHUB_OUTPUT"
+ {
+ echo "files<> "$GITHUB_OUTPUT"
+
+ - name: Install OpenCode
+ if: steps.changes.outputs.has_changes == 'true'
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Sync locale docs with OpenCode
+ if: steps.changes.outputs.has_changes == 'true'
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ OPENCODE_CONFIG_CONTENT: |
+ {
+ "permission": {
+ "*": "deny",
+ "read": "allow",
+ "edit": "allow",
+ "glob": "allow",
+ "task": "allow"
+ }
+ }
+ run: |
+ opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
+ Update localized docs to match the latest English docs changes.
+
+ Changed English doc files:
+
+ ${{ steps.changes.outputs.files }}
+
+
+ Requirements:
+ 1. Update all relevant locale docs under packages/web/src/content/docs// so they reflect these English page changes.
+ 2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
+ 3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
+ 4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
+ 5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
+ 6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
+ 7. Keep locale docs structure aligned with their corresponding English pages.
+ 8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
+ 9. If no locale updates are needed, make no changes.
+ EOF
+
+ - name: Commit and push locale docs updates
+ if: steps.changes.outputs.has_changes == 'true'
+ run: |
+ if [ -z "$(git status --porcelain)" ]; then
+ echo "No locale docs changes to commit"
+ exit 0
+ fi
+ git add -A
+ git commit -m "docs(i18n): sync locale docs from english changes"
+ git pull --rebase --autostash origin "$GITHUB_REF_NAME"
+ git push origin HEAD:"$GITHUB_REF_NAME"
diff --git a/.github/workflows/docs-update.yml b/.github/workflows/docs-update.yml
new file mode 100644
index 0000000000..900ad2b0c5
--- /dev/null
+++ b/.github/workflows/docs-update.yml
@@ -0,0 +1,72 @@
+name: docs-update
+
+on:
+ schedule:
+ - cron: "0 */12 * * *"
+ workflow_dispatch:
+
+env:
+ LOOKBACK_HOURS: 4
+
+jobs:
+ update-docs:
+ if: github.repository == 'sst/opencode'
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ id-token: write
+ contents: write
+ pull-requests: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Fetch full history to access commits
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Get recent commits
+ id: commits
+ run: |
+ COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
+ if [ -z "$COMMITS" ]; then
+ echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours"
+ echo "has_commits=false" >> $GITHUB_OUTPUT
+ else
+ echo "has_commits=true" >> $GITHUB_OUTPUT
+ {
+ echo "list<> $GITHUB_OUTPUT
+ fi
+
+ - name: Run opencode
+ if: steps.commits.outputs.has_commits == 'true'
+ uses: sst/opencode/github@latest
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ with:
+ model: opencode/gpt-5.2
+ agent: docs
+ prompt: |
+ Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation.
+
+
+ ${{ steps.commits.outputs.list }}
+
+
+ Steps:
+ 1. For each commit that looks like a new feature or significant change:
+ - Read the changed files to understand what was added
+ - Check if the feature is already documented in packages/web/src/content/docs/*
+ 2. If you find undocumented features:
+ - Update the relevant documentation files in packages/web/src/content/docs/*
+ - Follow the existing documentation style and structure
+ - Make sure to document the feature clearly with examples where appropriate
+ 3. If all new features are already documented, report that no updates are needed
+ 4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too.
+
+ Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior.
+ Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all.
+ Try to keep documentation only for large features or changes that already have a good spot to be documented.
diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml
new file mode 100644
index 0000000000..6c1943fe7b
--- /dev/null
+++ b/.github/workflows/duplicate-issues.yml
@@ -0,0 +1,177 @@
+name: duplicate-issues
+
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ check-duplicates:
+ if: github.event.action == 'opened'
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ issues: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Check duplicates and compliance
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh issue*": "allow"
+ },
+ "webfetch": "deny"
+ }
+ run: |
+ opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
+
+ Issue number: ${{ github.event.issue.number }}
+
+ Lookup this issue with gh issue view ${{ github.event.issue.number }}.
+
+ You have TWO tasks. Perform both, then post a SINGLE comment (if needed).
+
+ ---
+
+ TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK
+
+ Check whether the issue follows our contributing guidelines and issue templates.
+
+ This project has three issue templates that every issue MUST use one of:
+
+ 1. Bug Report - requires a Description field with real content
+ 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
+ 3. Question - requires the Question field with real content
+
+ Additionally check:
+ - No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
+ - The issue has real content, not just template placeholder text left unchanged
+ - Bug reports should include some context about how to reproduce
+ - Feature requests should explain the problem or need
+ - We want to push for having the user provide system description & information
+
+ Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
+
+ ---
+
+ TASK 2: DUPLICATE CHECK
+
+ Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates.
+ Consider:
+ 1. Similar titles or descriptions
+ 2. Same error messages or symptoms
+ 3. Related functionality or components
+ 4. Similar feature requests
+
+ Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997.
+
+ ---
+
+ POSTING YOUR COMMENT:
+
+ Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
+
+ If the issue is NOT compliant, start the comment with:
+
+ Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
+
+ If duplicates were found, include a section about potential duplicates with links.
+
+ If the issue mentions keybinds/keyboard shortcuts, include a note about #4997.
+
+ If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all.
+
+ Use this format for the comment:
+
+ [If not compliant:]
+
+ This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).
+
+ **What needs to be fixed:**
+ - [specific reasons]
+
+ Please edit this issue to address the above within **2 hours**, or it will be automatically closed.
+
+ [If duplicates found, add:]
+ ---
+ This issue might be a duplicate of existing issues. Please check:
+ - #[issue_number]: [brief description of similarity]
+
+ [If keybind-related, add:]
+ For keybind-related issues, please also check our pinned keybinds documentation: #4997
+
+ [End with if not compliant:]
+ If you believe this was flagged incorrectly, please let a maintainer know.
+
+ Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
+
+ recheck-compliance:
+ if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ issues: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Recheck compliance
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: |
+ {
+ "bash": {
+ "*": "deny",
+ "gh issue*": "allow"
+ },
+ "webfetch": "deny"
+ }
+ run: |
+ opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
+
+ Lookup this issue with gh issue view ${{ github.event.issue.number }}.
+
+ Re-check whether the issue now follows our contributing guidelines and issue templates.
+
+ This project has three issue templates that every issue MUST use one of:
+
+ 1. Bug Report - requires a Description field with real content
+ 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
+ 3. Question - requires the Question field with real content
+
+ Additionally check:
+ - No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
+ - The issue has real content, not just template placeholder text left unchanged
+ - Bug reports should include some context about how to reproduce
+ - Feature requests should explain the problem or need
+ - We want to push for having the user provide system description & information
+
+ Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
+
+ If the issue is NOW compliant:
+ 1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance
+ 2. Find and delete the previous compliance comment (the one containing ) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id}
+ 3. Post a short comment thanking them for updating the issue.
+
+ If the issue is STILL not compliant:
+ Post a comment explaining what still needs to be fixed. Keep the needs:compliance label."
diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml
new file mode 100644
index 0000000000..706ab2989e
--- /dev/null
+++ b/.github/workflows/generate.yml
@@ -0,0 +1,51 @@
+name: generate
+
+on:
+ push:
+ branches:
+ - dev
+
+jobs:
+ generate:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Generate
+ run: ./script/generate.ts
+
+ - name: Commit and push
+ run: |
+ if [ -z "$(git status --porcelain)" ]; then
+ echo "No changes to commit"
+ exit 0
+ fi
+ git add -A
+ git commit -m "chore: generate" --allow-empty
+ git push origin HEAD:${{ github.ref_name }} --no-verify
+ # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
+ # echo ""
+ # echo "============================================"
+ # echo "Failed to push generated code."
+ # echo "Please run locally and push:"
+ # echo ""
+ # echo " ./script/generate.ts"
+ # echo " git add -A && git commit -m \"chore: generate\" && git push"
+ # echo ""
+ # echo "============================================"
+ # exit 1
+ # fi
diff --git a/.github/workflows/nix-eval.yml b/.github/workflows/nix-eval.yml
new file mode 100644
index 0000000000..c76b2c9729
--- /dev/null
+++ b/.github/workflows/nix-eval.yml
@@ -0,0 +1,95 @@
+name: nix-eval
+
+on:
+ push:
+ branches: [dev]
+ pull_request:
+ branches: [dev]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ nix-eval:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ timeout-minutes: 15
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Setup Nix
+ uses: nixbuild/nix-quick-install-action@v34
+
+ - name: Evaluate flake outputs (all systems)
+ run: |
+ set -euo pipefail
+ nix --version
+
+ echo "=== Flake metadata ==="
+ nix flake metadata
+
+ echo ""
+ echo "=== Flake structure ==="
+ nix flake show --all-systems
+
+ SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
+ PACKAGES="opencode"
+ # TODO: move 'desktop' to PACKAGES when #11755 is fixed
+ OPTIONAL_PACKAGES="desktop"
+
+ echo ""
+ echo "=== Evaluating packages for all systems ==="
+ for system in $SYSTEMS; do
+ echo ""
+ echo "--- $system ---"
+ for pkg in $PACKAGES; do
+ printf " %s: " "$pkg"
+ if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
+ echo "✓"
+ else
+ echo "✗"
+ echo "::error::Evaluation failed for packages.$system.$pkg"
+ echo "$output"
+ exit 1
+ fi
+ done
+ done
+
+ echo ""
+ echo "=== Evaluating optional packages ==="
+ for system in $SYSTEMS; do
+ echo ""
+ echo "--- $system ---"
+ for pkg in $OPTIONAL_PACKAGES; do
+ printf " %s: " "$pkg"
+ if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
+ echo "✓"
+ else
+ echo "✗"
+ echo "::warning::Evaluation failed for packages.$system.$pkg"
+ echo "$output"
+ fi
+ done
+ done
+
+ echo ""
+ echo "=== Evaluating devShells for all systems ==="
+ for system in $SYSTEMS; do
+ printf "%s: " "$system"
+ if output=$(nix eval ".#devShells.$system.default.drvPath" --raw 2>&1); then
+ echo "✓"
+ else
+ echo "✗"
+ echo "::error::Evaluation failed for devShells.$system.default"
+ echo "$output"
+ exit 1
+ fi
+ done
+
+ echo ""
+ echo "=== All evaluations passed ==="
diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml
new file mode 100644
index 0000000000..2529c14c20
--- /dev/null
+++ b/.github/workflows/nix-hashes.yml
@@ -0,0 +1,148 @@
+name: nix-hashes
+
+permissions:
+ contents: write
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [dev, beta]
+ paths:
+ - "bun.lock"
+ - "package.json"
+ - "packages/*/package.json"
+ - "flake.lock"
+ - "nix/node_modules.nix"
+ - "nix/scripts/**"
+ - "patches/**"
+ - ".github/workflows/nix-hashes.yml"
+
+jobs:
+ # Native runners required: bun install cross-compilation flags (--os/--cpu)
+ # do not produce byte-identical node_modules as native installs.
+ compute-hash:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - system: x86_64-linux
+ runner: blacksmith-4vcpu-ubuntu-2404
+ - system: aarch64-linux
+ runner: blacksmith-4vcpu-ubuntu-2404-arm
+ - system: x86_64-darwin
+ runner: macos-15-intel
+ - system: aarch64-darwin
+ runner: macos-latest
+ runs-on: ${{ matrix.runner }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Setup Nix
+ uses: nixbuild/nix-quick-install-action@v34
+
+ - name: Compute node_modules hash
+ id: hash
+ env:
+ SYSTEM: ${{ matrix.system }}
+ run: |
+ set -euo pipefail
+
+ BUILD_LOG=$(mktemp)
+ trap 'rm -f "$BUILD_LOG"' EXIT
+
+ # Build with fakeHash to trigger hash mismatch and reveal correct hash
+ nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true
+
+ # Extract hash from build log with portability
+ HASH="$(grep -oE 'sha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)"
+
+ if [ -z "$HASH" ]; then
+ echo "::error::Failed to compute hash for ${SYSTEM}"
+ cat "$BUILD_LOG"
+ exit 1
+ fi
+
+ echo "$HASH" > hash.txt
+ echo "Computed hash for ${SYSTEM}: $HASH"
+
+ - name: Upload hash
+ uses: actions/upload-artifact@v4
+ with:
+ name: hash-${{ matrix.system }}
+ path: hash.txt
+ retention-days: 1
+
+ update-hashes:
+ needs: compute-hash
+ if: github.event_name != 'pull_request'
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+ ref: ${{ github.ref_name }}
+
+ - name: Setup git committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Pull latest changes
+ run: |
+ git pull --rebase --autostash origin "$GITHUB_REF_NAME"
+
+ - name: Download hash artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: hashes
+ pattern: hash-*
+
+ - name: Update hashes.json
+ run: |
+ set -euo pipefail
+
+ HASH_FILE="nix/hashes.json"
+
+ [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE"
+
+ for SYSTEM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do
+ FILE="hashes/hash-${SYSTEM}/hash.txt"
+ if [ -f "$FILE" ]; then
+ HASH="$(tr -d '[:space:]' < "$FILE")"
+ echo "${SYSTEM}: ${HASH}"
+ jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json
+ mv tmp.json "$HASH_FILE"
+ else
+ echo "::warning::Missing hash for ${SYSTEM}"
+ fi
+ done
+
+ cat "$HASH_FILE"
+
+ - name: Commit changes
+ run: |
+ set -euo pipefail
+
+ HASH_FILE="nix/hashes.json"
+
+ if [ -z "$(git status --short -- "$HASH_FILE")" ]; then
+ echo "No changes to commit"
+ echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
+ echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+
+ git add "$HASH_FILE"
+ git commit -m "chore: update nix node_modules hashes"
+
+ git pull --rebase --autostash origin "$GITHUB_REF_NAME"
+ git push origin HEAD:"$GITHUB_REF_NAME"
+
+ echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
+ echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/notify-discord.yml b/.github/workflows/notify-discord.yml
new file mode 100644
index 0000000000..b1d8053603
--- /dev/null
+++ b/.github/workflows/notify-discord.yml
@@ -0,0 +1,14 @@
+name: notify-discord
+
+on:
+ release:
+ types: [released] # fires when a draft release is published
+
+jobs:
+ notify:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - name: Send nicely-formatted embed to Discord
+ uses: SethCohen/github-releases-to-discord@v1
+ with:
+ webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
diff --git a/.github/workflows/publish-github-action.yml b/.github/workflows/publish-github-action.yml
new file mode 100644
index 0000000000..d2789373a3
--- /dev/null
+++ b/.github/workflows/publish-github-action.yml
@@ -0,0 +1,30 @@
+name: publish-github-action
+
+on:
+ workflow_dispatch:
+ push:
+ tags:
+ - "github-v*.*.*"
+ - "!github-v1"
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+permissions:
+ contents: write
+
+jobs:
+ publish:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - run: git fetch --force --tags
+
+ - name: Publish
+ run: |
+ git config --global user.email "opencode@sst.dev"
+ git config --global user.name "opencode"
+ ./script/publish
+ working-directory: ./github
diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml
new file mode 100644
index 0000000000..f49a105780
--- /dev/null
+++ b/.github/workflows/publish-vscode.yml
@@ -0,0 +1,37 @@
+name: publish-vscode
+
+on:
+ workflow_dispatch:
+ push:
+ tags:
+ - "vscode-v*.*.*"
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+permissions:
+ contents: write
+
+jobs:
+ publish:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - uses: ./.github/actions/setup-bun
+
+ - run: git fetch --force --tags
+ - run: bun install -g @vscode/vsce
+
+ - name: Install extension dependencies
+ run: bun install
+ working-directory: ./sdks/vscode
+
+ - name: Publish
+ run: |
+ ./script/publish
+ working-directory: ./sdks/vscode
+ env:
+ VSCE_PAT: ${{ secrets.VSCE_PAT }}
+ OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000000..b425b32a58
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,447 @@
+name: publish
+run-name: "${{ format('release {0}', inputs.bump) }}"
+
+on:
+ push:
+ branches:
+ - ci
+ - dev
+ - beta
+ - snapshot-*
+ workflow_dispatch:
+ inputs:
+ bump:
+ description: "Bump major, minor, or patch"
+ required: false
+ type: choice
+ options:
+ - major
+ - minor
+ - patch
+ version:
+ description: "Override version (optional)"
+ required: false
+ type: string
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }}
+
+permissions:
+ id-token: write
+ contents: write
+ packages: write
+
+jobs:
+ version:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ if: github.repository == 'anomalyco/opencode'
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Install OpenCode
+ if: inputs.bump || inputs.version
+ run: bun i -g opencode-ai
+
+ - id: version
+ run: |
+ ./script/version.ts
+ env:
+ GH_TOKEN: ${{ steps.committer.outputs.token }}
+ OPENCODE_BUMP: ${{ inputs.bump }}
+ OPENCODE_VERSION: ${{ inputs.version }}
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
+ outputs:
+ version: ${{ steps.version.outputs.version }}
+ release: ${{ steps.version.outputs.release }}
+ tag: ${{ steps.version.outputs.tag }}
+ repo: ${{ steps.version.outputs.repo }}
+
+ build-cli:
+ needs: version
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ if: github.repository == 'anomalyco/opencode'
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-tags: true
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Build
+ id: build
+ run: |
+ ./packages/opencode/script/build.ts
+ env:
+ OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+ OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
+ GH_REPO: ${{ needs.version.outputs.repo }}
+ GH_TOKEN: ${{ steps.committer.outputs.token }}
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: opencode-cli
+ path: packages/opencode/dist
+ outputs:
+ version: ${{ needs.version.outputs.version }}
+
+ build-tauri:
+ needs:
+ - build-cli
+ - version
+ continue-on-error: false
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - host: macos-latest
+ target: x86_64-apple-darwin
+ - host: macos-latest
+ target: aarch64-apple-darwin
+ # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
+ - host: windows-2025
+ target: aarch64-pc-windows-msvc
+ - host: blacksmith-4vcpu-windows-2025
+ target: x86_64-pc-windows-msvc
+ - host: blacksmith-4vcpu-ubuntu-2404
+ target: x86_64-unknown-linux-gnu
+ - host: blacksmith-8vcpu-ubuntu-2404-arm
+ target: aarch64-unknown-linux-gnu
+ runs-on: ${{ matrix.settings.host }}
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-tags: true
+
+ - uses: apple-actions/import-codesign-certs@v2
+ if: ${{ runner.os == 'macOS' }}
+ with:
+ keychain: build
+ p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+ p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+ - name: Verify Certificate
+ if: ${{ runner.os == 'macOS' }}
+ run: |
+ CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
+ CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
+ echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
+ echo "Certificate imported."
+
+ - name: Setup Apple API Key
+ if: ${{ runner.os == 'macOS' }}
+ run: |
+ echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
+
+ - uses: ./.github/actions/setup-bun
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ - name: Cache apt packages
+ if: contains(matrix.settings.host, 'ubuntu')
+ uses: actions/cache@v4
+ with:
+ path: ~/apt-cache
+ key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-${{ hashFiles('.github/workflows/publish.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.settings.target }}-apt-
+
+ - name: install dependencies (ubuntu only)
+ if: contains(matrix.settings.host, 'ubuntu')
+ run: |
+ mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
+ sudo chmod -R a+rw ~/apt-cache
+
+ - name: install Rust stable
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: ${{ matrix.settings.target }}
+
+ - uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: packages/desktop/src-tauri
+ shared-key: ${{ matrix.settings.target }}
+
+ - name: Prepare
+ run: |
+ cd packages/desktop
+ bun ./scripts/prepare.ts
+ env:
+ OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+ GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
+ RUST_TARGET: ${{ matrix.settings.target }}
+ GH_TOKEN: ${{ github.token }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+
+ - name: Resolve tauri portable SHA
+ if: contains(matrix.settings.host, 'ubuntu')
+ run: echo "TAURI_PORTABLE_SHA=$(git ls-remote https://github.com/tauri-apps/tauri.git refs/heads/feat/truly-portable-appimage | cut -f1)" >> "$GITHUB_ENV"
+
+ # Fixes AppImage build issues, can be removed when https://github.com/tauri-apps/tauri/pull/12491 is released
+ - name: Install tauri-cli from portable appimage branch
+ uses: taiki-e/cache-cargo-install-action@v3
+ if: contains(matrix.settings.host, 'ubuntu')
+ with:
+ tool: tauri-cli
+ git: https://github.com/tauri-apps/tauri
+ # branch: feat/truly-portable-appimage
+ rev: ${{ env.TAURI_PORTABLE_SHA }}
+
+ - name: Show tauri-cli version
+ if: contains(matrix.settings.host, 'ubuntu')
+ run: cargo tauri --version
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Build and upload artifacts
+ uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
+ timeout-minutes: 60
+ with:
+ projectPath: packages/desktop
+ uploadWorkflowArtifacts: true
+ tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
+ args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose
+ updaterJsonPreferNsis: true
+ releaseId: ${{ needs.version.outputs.release }}
+ tagName: ${{ needs.version.outputs.tag }}
+ releaseDraft: true
+ releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
+ repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }}
+ releaseCommitish: ${{ github.sha }}
+ env:
+ GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
+ TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
+ APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
+ APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
+ APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
+ APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
+
+ build-electron:
+ needs:
+ - build-cli
+ - version
+ continue-on-error: false
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - host: macos-latest
+ target: x86_64-apple-darwin
+ platform_flag: --mac --x64
+ - host: macos-latest
+ target: aarch64-apple-darwin
+ platform_flag: --mac --arm64
+ # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
+ - host: "windows-2025"
+ target: aarch64-pc-windows-msvc
+ platform_flag: --win --arm64
+ - host: "blacksmith-4vcpu-windows-2025"
+ target: x86_64-pc-windows-msvc
+ platform_flag: --win
+ - host: "blacksmith-4vcpu-ubuntu-2404"
+ target: x86_64-unknown-linux-gnu
+ platform_flag: --linux
+ - host: "blacksmith-4vcpu-ubuntu-2404"
+ target: aarch64-unknown-linux-gnu
+ platform_flag: --linux
+ runs-on: ${{ matrix.settings.host }}
+ # if: github.ref_name == 'beta'
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: apple-actions/import-codesign-certs@v2
+ if: runner.os == 'macOS'
+ with:
+ keychain: build
+ p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+ p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+ - name: Setup Apple API Key
+ if: runner.os == 'macOS'
+ run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
+
+ - uses: ./.github/actions/setup-bun
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+
+ - name: Cache apt packages
+ if: contains(matrix.settings.host, 'ubuntu')
+ uses: actions/cache@v4
+ with:
+ path: ~/apt-cache
+ key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
+
+ - name: Install dependencies (ubuntu only)
+ if: contains(matrix.settings.host, 'ubuntu')
+ run: |
+ mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
+ sudo chmod -R a+rw ~/apt-cache
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - name: Prepare
+ run: bun ./scripts/prepare.ts
+ working-directory: packages/desktop-electron
+ env:
+ OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+ RUST_TARGET: ${{ matrix.settings.target }}
+ GH_TOKEN: ${{ github.token }}
+ GITHUB_RUN_ID: ${{ github.run_id }}
+
+ - name: Build
+ run: bun run build
+ working-directory: packages/desktop-electron
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+ - name: Package and publish
+ if: needs.version.outputs.release
+ run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
+ working-directory: packages/desktop-electron
+ timeout-minutes: 60
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+ GH_TOKEN: ${{ steps.committer.outputs.token }}
+ CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
+ CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+ APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
+ APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
+ APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
+
+ - name: Package (no publish)
+ if: ${{ !needs.version.outputs.release }}
+ run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
+ working-directory: packages/desktop-electron
+ timeout-minutes: 60
+ env:
+ OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: opencode-electron-${{ matrix.settings.target }}
+ path: packages/desktop-electron/dist/*
+
+ - uses: actions/upload-artifact@v4
+ if: needs.version.outputs.release
+ with:
+ name: latest-yml-${{ matrix.settings.target }}
+ path: packages/desktop-electron/dist/latest*.yml
+
+ publish:
+ needs:
+ - version
+ - build-cli
+ - build-tauri
+ - build-electron
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "24"
+ registry-url: "https://registry.npmjs.org"
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: opencode-cli
+ path: packages/opencode/dist
+
+ - uses: actions/download-artifact@v4
+ if: needs.version.outputs.release
+ with:
+ pattern: latest-yml-*
+ path: /tmp/latest-yml
+
+ - name: Cache apt packages (AUR)
+ uses: actions/cache@v4
+ with:
+ path: /var/cache/apt/archives
+ key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
+ restore-keys: |
+ ${{ runner.os }}-apt-aur-
+
+ - name: Setup SSH for AUR
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y pacman-package-manager
+ mkdir -p ~/.ssh
+ echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
+ chmod 600 ~/.ssh/id_rsa
+ git config --global user.email "opencode@sst.dev"
+ git config --global user.name "opencode"
+ ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
+
+ - run: ./script/publish.ts
+ env:
+ OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+ OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
+ AUR_KEY: ${{ secrets.AUR_KEY }}
+ GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
+ GH_REPO: ${{ needs.version.outputs.repo }}
+ NPM_CONFIG_PROVENANCE: false
+ LATEST_YML_DIR: /tmp/latest-yml
diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml
new file mode 100644
index 0000000000..3f5caa55c8
--- /dev/null
+++ b/.github/workflows/release-github-action.yml
@@ -0,0 +1,29 @@
+name: release-github-action
+
+on:
+ push:
+ branches:
+ - dev
+ paths:
+ - "github/**"
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+permissions:
+ contents: write
+
+jobs:
+ release:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - run: git fetch --force --tags
+
+ - name: Release
+ run: |
+ git config --global user.email "opencode@sst.dev"
+ git config --global user.name "opencode"
+ ./github/script/release
diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml
new file mode 100644
index 0000000000..58e73fac8f
--- /dev/null
+++ b/.github/workflows/review.yml
@@ -0,0 +1,83 @@
+name: review
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ check-guidelines:
+ if: |
+ github.event.issue.pull_request &&
+ startsWith(github.event.comment.body, '/review') &&
+ contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Get PR number
+ id: pr-number
+ run: |
+ if [ "${{ github.event_name }}" = "pull_request_target" ]; then
+ echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
+ else
+ echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Get PR details
+ id: pr-details
+ run: |
+ gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json
+ echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT
+ echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Check PR guidelines compliance
+ env:
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
+ PR_TITLE: ${{ steps.pr-details.outputs.title }}
+ run: |
+ PR_BODY=$(jq -r .body pr_data.json)
+ opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}'
+
+
+ ${{ steps.pr-number.outputs.number }}
+
+
+
+ $PR_BODY
+
+
+ Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
+
+ When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
+ When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
+
+ Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
+ If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors.
+ Generally, write a comment instead of writing suggested change if you can help it.
+
+ Command MUST be like this.
+ \`\`\`
+ gh api \
+ --method POST \
+ -H \"Accept: application/vnd.github+json\" \
+ -H \"X-GitHub-Api-Version: 2022-11-28\" \
+ /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \
+ -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
+ \`\`\`
+
+ Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."
diff --git a/.github/workflows/sign-cli.yml b/.github/workflows/sign-cli.yml
new file mode 100644
index 0000000000..d9d61fd800
--- /dev/null
+++ b/.github/workflows/sign-cli.yml
@@ -0,0 +1,54 @@
+name: sign-cli
+
+on:
+ push:
+ branches:
+ - brendan/desktop-signpath
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ actions: read
+
+jobs:
+ sign-cli:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ if: github.repository == 'anomalyco/opencode'
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-tags: true
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Build
+ run: |
+ ./packages/opencode/script/build.ts
+
+ - name: Upload unsigned Windows CLI
+ id: upload_unsigned_windows_cli
+ uses: actions/upload-artifact@v4
+ with:
+ name: unsigned-opencode-windows-cli
+ path: packages/opencode/dist/opencode-windows-x64/bin/opencode.exe
+ if-no-files-found: error
+
+ - name: Submit SignPath signing request
+ id: submit_signpath_signing_request
+ uses: signpath/github-action-submit-signing-request@v1
+ with:
+ api-token: ${{ secrets.SIGNPATH_API_KEY }}
+ organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
+ project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
+ signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
+ artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
+ github-artifact-id: ${{ steps.upload_unsigned_windows_cli.outputs.artifact-id }}
+ wait-for-completion: true
+ output-artifact-directory: signed-opencode-cli
+
+ - name: Upload signed Windows CLI
+ uses: actions/upload-artifact@v4
+ with:
+ name: signed-opencode-windows-cli
+ path: signed-opencode-cli/*.exe
+ if-no-files-found: error
diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml
new file mode 100644
index 0000000000..a4b8583f92
--- /dev/null
+++ b/.github/workflows/stale-issues.yml
@@ -0,0 +1,33 @@
+name: stale-issues
+
+on:
+ schedule:
+ - cron: "30 1 * * *" # Daily at 1:30 AM
+ workflow_dispatch:
+
+env:
+ DAYS_BEFORE_STALE: 90
+ DAYS_BEFORE_CLOSE: 7
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/stale@v10
+ with:
+ days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
+ days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
+ stale-issue-label: "stale"
+ close-issue-message: |
+ [automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity.
+
+ Feel free to reopen if you still need this!
+ stale-issue-message: |
+ [automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days.
+
+ It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity.
+ remove-stale-when-updated: true
+ exempt-issue-labels: "pinned,security,feature-request,on-hold"
+ start-date: "2025-12-27"
diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml
new file mode 100644
index 0000000000..824733901d
--- /dev/null
+++ b/.github/workflows/stats.yml
@@ -0,0 +1,35 @@
+name: stats
+
+on:
+ schedule:
+ - cron: "0 12 * * *" # Run daily at 12:00 UTC
+ workflow_dispatch: # Allow manual trigger
+
+concurrency: ${{ github.workflow }}-${{ github.ref }}
+
+jobs:
+ stats:
+ if: github.repository == 'anomalyco/opencode'
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Run stats script
+ run: bun script/stats.ts
+
+ - name: Commit stats
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add STATS.md
+ git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
+ git push
+ env:
+ POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
diff --git a/.github/workflows/sync-zed-extension.yml b/.github/workflows/sync-zed-extension.yml
new file mode 100644
index 0000000000..f14487cde9
--- /dev/null
+++ b/.github/workflows/sync-zed-extension.yml
@@ -0,0 +1,35 @@
+name: "sync-zed-extension"
+
+on:
+ workflow_dispatch:
+ release:
+ types: [published]
+
+jobs:
+ zed:
+ name: Release Zed Extension
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: ./.github/actions/setup-bun
+
+ - name: Get version tag
+ id: get_tag
+ run: |
+ if [ "${{ github.event_name }}" = "release" ]; then
+ TAG="${{ github.event.release.tag_name }}"
+ else
+ TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
+ fi
+ echo "tag=${TAG}" >> $GITHUB_OUTPUT
+ echo "Using tag: ${TAG}"
+
+ - name: Sync Zed extension
+ run: |
+ ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
+ env:
+ ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
+ ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000..c928e82234
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,98 @@
+name: test
+
+on:
+ push:
+ branches:
+ - dev
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ # Keep every run on dev so cancelled checks do not pollute the default branch
+ # commit history. PRs and other branches still share a group and cancel stale runs.
+ group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ unit:
+ name: unit (${{ matrix.settings.name }})
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - name: linux
+ host: blacksmith-4vcpu-ubuntu-2404
+ - name: windows
+ host: blacksmith-4vcpu-windows-2025
+ runs-on: ${{ matrix.settings.host }}
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Configure git identity
+ run: |
+ git config --global user.email "bot@opencode.ai"
+ git config --global user.name "opencode"
+
+ - name: Run unit tests
+ run: bun turbo test
+
+ e2e:
+ name: e2e (${{ matrix.settings.name }})
+ needs: unit
+ strategy:
+ fail-fast: false
+ matrix:
+ settings:
+ - name: linux
+ host: blacksmith-4vcpu-ubuntu-2404
+ playwright: bunx playwright install --with-deps
+ - name: windows
+ host: blacksmith-4vcpu-windows-2025
+ playwright: bunx playwright install
+ runs-on: ${{ matrix.settings.host }}
+ env:
+ PLAYWRIGHT_BROWSERS_PATH: 0
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Install Playwright browsers
+ working-directory: packages/app
+ run: ${{ matrix.settings.playwright }}
+
+ - name: Run app e2e tests
+ run: bun --cwd packages/app test:e2e:local
+ env:
+ CI: true
+ timeout-minutes: 30
+
+ - name: Upload Playwright artifacts
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
+ if-no-files-found: ignore
+ retention-days: 7
+ path: |
+ packages/app/e2e/test-results
+ packages/app/e2e/playwright-report
diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml
new file mode 100644
index 0000000000..99e7b5b34f
--- /dev/null
+++ b/.github/workflows/triage.yml
@@ -0,0 +1,37 @@
+name: triage
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ triage:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ permissions:
+ contents: read
+ issues: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Install opencode
+ run: curl -fsSL https://opencode.ai/install | bash
+
+ - name: Triage issue
+ env:
+ OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ ISSUE_TITLE: ${{ github.event.issue.title }}
+ ISSUE_BODY: ${{ github.event.issue.body }}
+ run: |
+ opencode run --agent triage "The following issue was just opened, triage it:
+
+ Title: $ISSUE_TITLE
+
+ $ISSUE_BODY"
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
new file mode 100644
index 0000000000..b247d24b40
--- /dev/null
+++ b/.github/workflows/typecheck.yml
@@ -0,0 +1,21 @@
+name: typecheck
+
+on:
+ push:
+ branches: [dev]
+ pull_request:
+ branches: [dev]
+ workflow_dispatch:
+
+jobs:
+ typecheck:
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: ./.github/actions/setup-bun
+
+ - name: Run typecheck
+ run: bun typecheck
diff --git a/.github/workflows/vouch-check-issue.yml b/.github/workflows/vouch-check-issue.yml
new file mode 100644
index 0000000000..4c2aa960b2
--- /dev/null
+++ b/.github/workflows/vouch-check-issue.yml
@@ -0,0 +1,116 @@
+name: vouch-check-issue
+
+on:
+ issues:
+ types: [opened]
+
+permissions:
+ contents: read
+ issues: write
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if issue author is denounced
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const author = context.payload.issue.user.login;
+ const issueNumber = context.payload.issue.number;
+
+ // Skip bots
+ if (author.endsWith('[bot]')) {
+ core.info(`Skipping bot: ${author}`);
+ return;
+ }
+
+ // Read the VOUCHED.td file via API (no checkout needed)
+ let content;
+ try {
+ const response = await github.rest.repos.getContent({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ path: '.github/VOUCHED.td',
+ });
+ content = Buffer.from(response.data.content, 'base64').toString('utf-8');
+ } catch (error) {
+ if (error.status === 404) {
+ core.info('No .github/VOUCHED.td file found, skipping check.');
+ return;
+ }
+ throw error;
+ }
+
+ // Parse the .td file for vouched and denounced users
+ const vouched = new Set();
+ const denounced = new Map();
+ for (const line of content.split('\n')) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith('#')) continue;
+
+ const isDenounced = trimmed.startsWith('-');
+ const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
+ if (!rest) continue;
+
+ const spaceIdx = rest.indexOf(' ');
+ const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
+ const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
+
+ // Handle platform:username or bare username
+ // Only match bare usernames or github: prefix (skip other platforms)
+ const colonIdx = handle.indexOf(':');
+ if (colonIdx !== -1) {
+ const platform = handle.slice(0, colonIdx).toLowerCase();
+ if (platform !== 'github') continue;
+ }
+ const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
+ if (!username) continue;
+
+ if (isDenounced) {
+ denounced.set(username.toLowerCase(), reason);
+ continue;
+ }
+
+ vouched.add(username.toLowerCase());
+ }
+
+ // Check if the author is denounced
+ const reason = denounced.get(author.toLowerCase());
+ if (reason !== undefined) {
+ // Author is denounced — close the issue
+ const body = 'This issue has been automatically closed.';
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ body,
+ });
+
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ state: 'closed',
+ state_reason: 'not_planned',
+ });
+
+ core.info(`Closed issue #${issueNumber} from denounced user ${author}`);
+ return;
+ }
+
+ // Author is positively vouched — add label
+ if (!vouched.has(author.toLowerCase())) {
+ core.info(`User ${author} is not denounced or vouched. Allowing issue.`);
+ return;
+ }
+
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ labels: ['Vouched'],
+ });
+
+ core.info(`Added vouched label to issue #${issueNumber} from ${author}`);
diff --git a/.github/workflows/vouch-check-pr.yml b/.github/workflows/vouch-check-pr.yml
new file mode 100644
index 0000000000..51816dfb75
--- /dev/null
+++ b/.github/workflows/vouch-check-pr.yml
@@ -0,0 +1,114 @@
+name: vouch-check-pr
+
+on:
+ pull_request_target:
+ types: [opened]
+
+permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check if PR author is denounced
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const author = context.payload.pull_request.user.login;
+ const prNumber = context.payload.pull_request.number;
+
+ // Skip bots
+ if (author.endsWith('[bot]')) {
+ core.info(`Skipping bot: ${author}`);
+ return;
+ }
+
+ // Read the VOUCHED.td file via API (no checkout needed)
+ let content;
+ try {
+ const response = await github.rest.repos.getContent({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ path: '.github/VOUCHED.td',
+ });
+ content = Buffer.from(response.data.content, 'base64').toString('utf-8');
+ } catch (error) {
+ if (error.status === 404) {
+ core.info('No .github/VOUCHED.td file found, skipping check.');
+ return;
+ }
+ throw error;
+ }
+
+ // Parse the .td file for vouched and denounced users
+ const vouched = new Set();
+ const denounced = new Map();
+ for (const line of content.split('\n')) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith('#')) continue;
+
+ const isDenounced = trimmed.startsWith('-');
+ const rest = isDenounced ? trimmed.slice(1).trim() : trimmed;
+ if (!rest) continue;
+
+ const spaceIdx = rest.indexOf(' ');
+ const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
+ const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
+
+ // Handle platform:username or bare username
+ // Only match bare usernames or github: prefix (skip other platforms)
+ const colonIdx = handle.indexOf(':');
+ if (colonIdx !== -1) {
+ const platform = handle.slice(0, colonIdx).toLowerCase();
+ if (platform !== 'github') continue;
+ }
+ const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
+ if (!username) continue;
+
+ if (isDenounced) {
+ denounced.set(username.toLowerCase(), reason);
+ continue;
+ }
+
+ vouched.add(username.toLowerCase());
+ }
+
+ // Check if the author is denounced
+ const reason = denounced.get(author.toLowerCase());
+ if (reason !== undefined) {
+ // Author is denounced — close the PR
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body: 'This pull request has been automatically closed.',
+ });
+
+ await github.rest.pulls.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: prNumber,
+ state: 'closed',
+ });
+
+ core.info(`Closed PR #${prNumber} from denounced user ${author}`);
+ return;
+ }
+
+ // Author is positively vouched — add label
+ if (!vouched.has(author.toLowerCase())) {
+ core.info(`User ${author} is not denounced or vouched. Allowing PR.`);
+ return;
+ }
+
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ labels: ['Vouched'],
+ });
+
+ core.info(`Added vouched label to PR #${prNumber} from ${author}`);
diff --git a/.github/workflows/vouch-manage-by-issue.yml b/.github/workflows/vouch-manage-by-issue.yml
new file mode 100644
index 0000000000..9604bf87f3
--- /dev/null
+++ b/.github/workflows/vouch-manage-by-issue.yml
@@ -0,0 +1,38 @@
+name: vouch-manage-by-issue
+
+on:
+ issue_comment:
+ types: [created]
+
+concurrency:
+ group: vouch-manage
+ cancel-in-progress: false
+
+permissions:
+ contents: write
+ issues: write
+ pull-requests: read
+
+jobs:
+ manage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ fetch-depth: 0
+
+ - name: Setup git committer
+ id: committer
+ uses: ./.github/actions/setup-git-committer
+ with:
+ opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+ opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+ - uses: mitchellh/vouch/action/manage-by-issue@main
+ with:
+ issue-id: ${{ github.event.issue.number }}
+ comment-id: ${{ github.event.comment.id }}
+ roles: admin,maintain
+ env:
+ GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
diff --git a/.gitignore b/.gitignore
index b10c1bb043..c287d91ac1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,11 @@
.DS_Store
node_modules
-__pycache__
.worktrees
.sst
.env
.idea
.vscode
.codex
-.claude
*~
playground
tmp
@@ -25,12 +23,8 @@ target
.scripts
.direnv/
-# Commit message scratch files
-.github/meta/
-
# Local dev files
opencode-dev
logs/
-docs/site/
*.bun-build
tsconfig.tsbuildinfo
diff --git a/.signpath/policies/opencode/test-signing.yml b/.signpath/policies/opencode/test-signing.yml
new file mode 100644
index 0000000000..683b27adb7
--- /dev/null
+++ b/.signpath/policies/opencode/test-signing.yml
@@ -0,0 +1,5 @@
+github-policies:
+ runners:
+ allowed_groups:
+ - "GitHub Actions"
+ - "blacksmith runners 01kbd5v56sg8tz7rea39b7ygpt"
diff --git a/.zed/settings.json b/.zed/settings.json
new file mode 100644
index 0000000000..a3a5e1e2b2
--- /dev/null
+++ b/.zed/settings.json
@@ -0,0 +1,9 @@
+{
+ "format_on_save": "on",
+ "formatter": {
+ "external": {
+ "command": "bunx",
+ "arguments": ["prettier", "--stdin-filepath", "{buffer_path}"]
+ }
+ }
+}
diff --git a/LICENSE b/LICENSE
index ba8c6f3e8c..6439474bee 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,5 @@
MIT License
-Copyright (c) 2026 Altimate AI
Copyright (c) 2025 opencode
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/STATS.md b/STATS.md
new file mode 100644
index 0000000000..44819a6eb8
--- /dev/null
+++ b/STATS.md
@@ -0,0 +1,217 @@
+# Download Stats
+
+| Date | GitHub Downloads | npm Downloads | Total |
+| ---------- | -------------------- | -------------------- | --------------------- |
+| 2025-06-29 | 18,789 (+0) | 39,420 (+0) | 58,209 (+0) |
+| 2025-06-30 | 20,127 (+1,338) | 41,059 (+1,639) | 61,186 (+2,977) |
+| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
+| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
+| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
+| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
+| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
+| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
+| 2025-07-08 | 38,052 (+4,286) | 64,468 (+4,774) | 102,520 (+9,060) |
+| 2025-07-09 | 40,924 (+2,872) | 67,935 (+3,467) | 108,859 (+6,339) |
+| 2025-07-10 | 43,796 (+2,872) | 71,402 (+3,467) | 115,198 (+6,339) |
+| 2025-07-11 | 46,982 (+3,186) | 77,462 (+6,060) | 124,444 (+9,246) |
+| 2025-07-12 | 49,302 (+2,320) | 82,177 (+4,715) | 131,479 (+7,035) |
+| 2025-07-13 | 50,803 (+1,501) | 86,394 (+4,217) | 137,197 (+5,718) |
+| 2025-07-14 | 53,283 (+2,480) | 87,860 (+1,466) | 141,143 (+3,946) |
+| 2025-07-15 | 57,590 (+4,307) | 91,036 (+3,176) | 148,626 (+7,483) |
+| 2025-07-16 | 62,313 (+4,723) | 95,258 (+4,222) | 157,571 (+8,945) |
+| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
+| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
+| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
+| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |
+| 2025-07-21 | 80,197 (+3,744) | 113,537 (+4,493) | 193,734 (+8,237) |
+| 2025-07-22 | 84,251 (+4,054) | 118,073 (+4,536) | 202,324 (+8,590) |
+| 2025-07-23 | 88,589 (+4,338) | 121,436 (+3,363) | 210,025 (+7,701) |
+| 2025-07-24 | 92,469 (+3,880) | 124,091 (+2,655) | 216,560 (+6,535) |
+| 2025-07-25 | 96,417 (+3,948) | 126,985 (+2,894) | 223,402 (+6,842) |
+| 2025-07-26 | 100,646 (+4,229) | 131,411 (+4,426) | 232,057 (+8,655) |
+| 2025-07-27 | 102,644 (+1,998) | 134,736 (+3,325) | 237,380 (+5,323) |
+| 2025-07-28 | 105,446 (+2,802) | 136,016 (+1,280) | 241,462 (+4,082) |
+| 2025-07-29 | 108,998 (+3,552) | 137,542 (+1,526) | 246,540 (+5,078) |
+| 2025-07-30 | 113,544 (+4,546) | 140,317 (+2,775) | 253,861 (+7,321) |
+| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
+| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
+| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
+| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
+| 2025-08-04 | 136,266 (+4,869) | 153,260 (+2,809) | 289,526 (+7,678) |
+| 2025-08-05 | 141,596 (+5,330) | 155,752 (+2,492) | 297,348 (+7,822) |
+| 2025-08-06 | 147,067 (+5,471) | 158,309 (+2,557) | 305,376 (+8,028) |
+| 2025-08-07 | 152,591 (+5,524) | 160,889 (+2,580) | 313,480 (+8,104) |
+| 2025-08-08 | 158,187 (+5,596) | 163,448 (+2,559) | 321,635 (+8,155) |
+| 2025-08-09 | 162,770 (+4,583) | 165,721 (+2,273) | 328,491 (+6,856) |
+| 2025-08-10 | 165,695 (+2,925) | 167,109 (+1,388) | 332,804 (+4,313) |
+| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
+| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
+| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
+| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
+| 2025-08-15 | 193,608 (+4,545) | 181,792 (+2,051) | 375,400 (+6,596) |
+| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
+| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
+| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
+| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
+| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
+| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
+| 2025-08-22 | 225,899 (+5,434) | 197,204 (+2,420) | 423,103 (+7,854) |
+| 2025-08-23 | 229,005 (+3,106) | 199,238 (+2,034) | 428,243 (+5,140) |
+| 2025-08-24 | 232,098 (+3,093) | 201,157 (+1,919) | 433,255 (+5,012) |
+| 2025-08-25 | 236,607 (+4,509) | 202,650 (+1,493) | 439,257 (+6,002) |
+| 2025-08-26 | 242,783 (+6,176) | 205,242 (+2,592) | 448,025 (+8,768) |
+| 2025-08-27 | 248,409 (+5,626) | 205,242 (+0) | 453,651 (+5,626) |
+| 2025-08-28 | 252,796 (+4,387) | 205,242 (+0) | 458,038 (+4,387) |
+| 2025-08-29 | 256,045 (+3,249) | 211,075 (+5,833) | 467,120 (+9,082) |
+| 2025-08-30 | 258,863 (+2,818) | 212,397 (+1,322) | 471,260 (+4,140) |
+| 2025-08-31 | 262,004 (+3,141) | 213,944 (+1,547) | 475,948 (+4,688) |
+| 2025-09-01 | 265,359 (+3,355) | 215,115 (+1,171) | 480,474 (+4,526) |
+| 2025-09-02 | 270,483 (+5,124) | 217,075 (+1,960) | 487,558 (+7,084) |
+| 2025-09-03 | 274,793 (+4,310) | 219,755 (+2,680) | 494,548 (+6,990) |
+| 2025-09-04 | 280,430 (+5,637) | 222,103 (+2,348) | 502,533 (+7,985) |
+| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
+| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
+| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
+| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
+| 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) |
+| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
+| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
+| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
+| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
+| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
+| 2025-09-15 | 334,201 (+5,325) | 250,983 (+2,738) | 585,184 (+8,063) |
+| 2025-09-16 | 342,609 (+8,408) | 255,264 (+4,281) | 597,873 (+12,689) |
+| 2025-09-17 | 351,117 (+8,508) | 260,970 (+5,706) | 612,087 (+14,214) |
+| 2025-09-18 | 358,717 (+7,600) | 266,922 (+5,952) | 625,639 (+13,552) |
+| 2025-09-19 | 365,401 (+6,684) | 271,859 (+4,937) | 637,260 (+11,621) |
+| 2025-09-20 | 372,092 (+6,691) | 276,917 (+5,058) | 649,009 (+11,749) |
+| 2025-09-21 | 377,079 (+4,987) | 280,261 (+3,344) | 657,340 (+8,331) |
+| 2025-09-22 | 382,492 (+5,413) | 284,009 (+3,748) | 666,501 (+9,161) |
+| 2025-09-23 | 387,008 (+4,516) | 289,129 (+5,120) | 676,137 (+9,636) |
+| 2025-09-24 | 393,325 (+6,317) | 294,927 (+5,798) | 688,252 (+12,115) |
+| 2025-09-25 | 398,879 (+5,554) | 301,663 (+6,736) | 700,542 (+12,290) |
+| 2025-09-26 | 404,334 (+5,455) | 306,713 (+5,050) | 711,047 (+10,505) |
+| 2025-09-27 | 411,618 (+7,284) | 317,763 (+11,050) | 729,381 (+18,334) |
+| 2025-09-28 | 414,910 (+3,292) | 322,522 (+4,759) | 737,432 (+8,051) |
+| 2025-09-29 | 419,919 (+5,009) | 328,033 (+5,511) | 747,952 (+10,520) |
+| 2025-09-30 | 427,991 (+8,072) | 336,472 (+8,439) | 764,463 (+16,511) |
+| 2025-10-01 | 433,591 (+5,600) | 341,742 (+5,270) | 775,333 (+10,870) |
+| 2025-10-02 | 440,852 (+7,261) | 348,099 (+6,357) | 788,951 (+13,618) |
+| 2025-10-03 | 446,829 (+5,977) | 359,937 (+11,838) | 806,766 (+17,815) |
+| 2025-10-04 | 452,561 (+5,732) | 370,386 (+10,449) | 822,947 (+16,181) |
+| 2025-10-05 | 455,559 (+2,998) | 374,745 (+4,359) | 830,304 (+7,357) |
+| 2025-10-06 | 460,927 (+5,368) | 379,489 (+4,744) | 840,416 (+10,112) |
+| 2025-10-07 | 467,336 (+6,409) | 385,438 (+5,949) | 852,774 (+12,358) |
+| 2025-10-08 | 474,643 (+7,307) | 394,139 (+8,701) | 868,782 (+16,008) |
+| 2025-10-09 | 479,203 (+4,560) | 400,526 (+6,387) | 879,729 (+10,947) |
+| 2025-10-10 | 484,374 (+5,171) | 406,015 (+5,489) | 890,389 (+10,660) |
+| 2025-10-11 | 488,427 (+4,053) | 414,699 (+8,684) | 903,126 (+12,737) |
+| 2025-10-12 | 492,125 (+3,698) | 418,745 (+4,046) | 910,870 (+7,744) |
+| 2025-10-14 | 505,130 (+13,005) | 429,286 (+10,541) | 934,416 (+23,546) |
+| 2025-10-15 | 512,717 (+7,587) | 439,290 (+10,004) | 952,007 (+17,591) |
+| 2025-10-16 | 517,719 (+5,002) | 447,137 (+7,847) | 964,856 (+12,849) |
+| 2025-10-17 | 526,239 (+8,520) | 457,467 (+10,330) | 983,706 (+18,850) |
+| 2025-10-18 | 531,564 (+5,325) | 465,272 (+7,805) | 996,836 (+13,130) |
+| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
+| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
+| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
+| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
+| 2025-10-23 | 564,716 (+6,767) | 498,736 (+7,341) | 1,063,452 (+14,108) |
+| 2025-10-24 | 572,692 (+7,976) | 506,905 (+8,169) | 1,079,597 (+16,145) |
+| 2025-10-25 | 578,927 (+6,235) | 516,129 (+9,224) | 1,095,056 (+15,459) |
+| 2025-10-26 | 584,409 (+5,482) | 521,179 (+5,050) | 1,105,588 (+10,532) |
+| 2025-10-27 | 589,999 (+5,590) | 526,001 (+4,822) | 1,116,000 (+10,412) |
+| 2025-10-28 | 595,776 (+5,777) | 532,438 (+6,437) | 1,128,214 (+12,214) |
+| 2025-10-29 | 606,259 (+10,483) | 542,064 (+9,626) | 1,148,323 (+20,109) |
+| 2025-10-30 | 613,746 (+7,487) | 542,064 (+0) | 1,155,810 (+7,487) |
+| 2025-10-30 | 617,846 (+4,100) | 555,026 (+12,962) | 1,172,872 (+17,062) |
+| 2025-10-31 | 626,612 (+8,766) | 564,579 (+9,553) | 1,191,191 (+18,319) |
+| 2025-11-01 | 636,100 (+9,488) | 581,806 (+17,227) | 1,217,906 (+26,715) |
+| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
+| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
+| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
+| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
+| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
+| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
+| 2025-11-08 | 706,035 (+9,389) | 653,489 (+11,343) | 1,359,524 (+20,732) |
+| 2025-11-09 | 713,462 (+7,427) | 660,459 (+6,970) | 1,373,921 (+14,397) |
+| 2025-11-10 | 722,288 (+8,826) | 668,225 (+7,766) | 1,390,513 (+16,592) |
+| 2025-11-11 | 729,769 (+7,481) | 677,501 (+9,276) | 1,407,270 (+16,757) |
+| 2025-11-12 | 740,180 (+10,411) | 686,454 (+8,953) | 1,426,634 (+19,364) |
+| 2025-11-13 | 749,905 (+9,725) | 696,157 (+9,703) | 1,446,062 (+19,428) |
+| 2025-11-14 | 759,928 (+10,023) | 705,237 (+9,080) | 1,465,165 (+19,103) |
+| 2025-11-15 | 765,955 (+6,027) | 712,870 (+7,633) | 1,478,825 (+13,660) |
+| 2025-11-16 | 771,069 (+5,114) | 716,596 (+3,726) | 1,487,665 (+8,840) |
+| 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) |
+| 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) |
+| 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) |
+| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) |
+| 2025-11-21 | 826,309 (+11,689) | 769,307 (+11,400) | 1,595,616 (+23,089) |
+| 2025-11-22 | 837,269 (+10,960) | 780,996 (+11,689) | 1,618,265 (+22,649) |
+| 2025-11-23 | 846,609 (+9,340) | 795,069 (+14,073) | 1,641,678 (+23,413) |
+| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
+| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
+| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
+| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
+| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
+| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
+| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
+| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
+| 2025-12-02 | 939,250 (+13,352) | 890,919 (+14,419) | 1,830,169 (+27,771) |
+| 2025-12-03 | 952,249 (+12,999) | 903,713 (+12,794) | 1,855,962 (+25,793) |
+| 2025-12-04 | 965,611 (+13,362) | 916,471 (+12,758) | 1,882,082 (+26,120) |
+| 2025-12-05 | 977,996 (+12,385) | 930,616 (+14,145) | 1,908,612 (+26,530) |
+| 2025-12-06 | 987,884 (+9,888) | 943,773 (+13,157) | 1,931,657 (+23,045) |
+| 2025-12-07 | 994,046 (+6,162) | 951,425 (+7,652) | 1,945,471 (+13,814) |
+| 2025-12-08 | 1,000,898 (+6,852) | 957,149 (+5,724) | 1,958,047 (+12,576) |
+| 2025-12-09 | 1,011,488 (+10,590) | 973,922 (+16,773) | 1,985,410 (+27,363) |
+| 2025-12-10 | 1,025,891 (+14,403) | 991,708 (+17,786) | 2,017,599 (+32,189) |
+| 2025-12-11 | 1,045,110 (+19,219) | 1,010,559 (+18,851) | 2,055,669 (+38,070) |
+| 2025-12-12 | 1,061,340 (+16,230) | 1,030,838 (+20,279) | 2,092,178 (+36,509) |
+| 2025-12-13 | 1,073,561 (+12,221) | 1,044,608 (+13,770) | 2,118,169 (+25,991) |
+| 2025-12-14 | 1,082,042 (+8,481) | 1,052,425 (+7,817) | 2,134,467 (+16,298) |
+| 2025-12-15 | 1,093,632 (+11,590) | 1,059,078 (+6,653) | 2,152,710 (+18,243) |
+| 2025-12-16 | 1,120,477 (+26,845) | 1,078,022 (+18,944) | 2,198,499 (+45,789) |
+| 2025-12-17 | 1,151,067 (+30,590) | 1,097,661 (+19,639) | 2,248,728 (+50,229) |
+| 2025-12-18 | 1,178,658 (+27,591) | 1,113,418 (+15,757) | 2,292,076 (+43,348) |
+| 2025-12-19 | 1,203,485 (+24,827) | 1,129,698 (+16,280) | 2,333,183 (+41,107) |
+| 2025-12-20 | 1,223,000 (+19,515) | 1,146,258 (+16,560) | 2,369,258 (+36,075) |
+| 2025-12-21 | 1,242,675 (+19,675) | 1,158,909 (+12,651) | 2,401,584 (+32,326) |
+| 2025-12-22 | 1,262,522 (+19,847) | 1,169,121 (+10,212) | 2,431,643 (+30,059) |
+| 2025-12-23 | 1,286,548 (+24,026) | 1,186,439 (+17,318) | 2,472,987 (+41,344) |
+| 2025-12-24 | 1,309,323 (+22,775) | 1,203,767 (+17,328) | 2,513,090 (+40,103) |
+| 2025-12-25 | 1,333,032 (+23,709) | 1,217,283 (+13,516) | 2,550,315 (+37,225) |
+| 2025-12-26 | 1,352,411 (+19,379) | 1,227,615 (+10,332) | 2,580,026 (+29,711) |
+| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
+| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
+| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
+| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
+| 2025-12-31 | 1,479,598 (+34,148) | 1,293,235 (+20,546) | 2,772,833 (+54,694) |
+| 2026-01-01 | 1,508,883 (+29,285) | 1,309,874 (+16,639) | 2,818,757 (+45,924) |
+| 2026-01-02 | 1,563,474 (+54,591) | 1,320,959 (+11,085) | 2,884,433 (+65,676) |
+| 2026-01-03 | 1,618,065 (+54,591) | 1,331,914 (+10,955) | 2,949,979 (+65,546) |
+| 2026-01-04 | 1,672,656 (+39,702) | 1,339,883 (+7,969) | 3,012,539 (+62,560) |
+| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
+| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
+| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
+| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
+| 2026-01-09 | 2,443,565 (+170,935) | 1,469,451 (+36,971) | 3,913,016 (+207,906) |
+| 2026-01-10 | 2,632,023 (+188,458) | 1,503,670 (+34,219) | 4,135,693 (+222,677) |
+| 2026-01-11 | 2,836,394 (+204,371) | 1,530,479 (+26,809) | 4,366,873 (+231,180) |
+| 2026-01-12 | 3,053,594 (+217,200) | 1,553,671 (+23,192) | 4,607,265 (+240,392) |
+| 2026-01-13 | 3,297,078 (+243,484) | 1,595,062 (+41,391) | 4,892,140 (+284,875) |
+| 2026-01-14 | 3,568,928 (+271,850) | 1,645,362 (+50,300) | 5,214,290 (+322,150) |
+| 2026-01-16 | 4,121,550 (+552,622) | 1,754,418 (+109,056) | 5,875,968 (+661,678) |
+| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
+| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856) | 6,466,794 (+271,921) |
+| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941) | 6,724,220 (+257,426) |
+| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553) | 7,032,664 (+308,444) |
+| 2026-01-21 | 5,444,842 (+315,843) | 1,962,531 (+58,866) | 7,407,373 (+374,709) |
+| 2026-01-22 | 5,766,340 (+321,498) | 2,029,487 (+66,956) | 7,795,827 (+388,454) |
+| 2026-01-23 | 6,096,236 (+329,896) | 2,096,235 (+66,748) | 8,192,471 (+396,644) |
+| 2026-01-24 | 6,371,019 (+274,783) | 2,156,870 (+60,635) | 8,527,889 (+335,418) |
+| 2026-01-25 | 6,639,082 (+268,063) | 2,187,853 (+30,983) | 8,826,935 (+299,046) |
+| 2026-01-26 | 6,941,620 (+302,538) | 2,232,115 (+44,262) | 9,173,735 (+346,800) |
+| 2026-01-27 | 7,208,093 (+266,473) | 2,280,762 (+48,647) | 9,488,855 (+315,120) |
+| 2026-01-28 | 7,489,370 (+281,277) | 2,314,849 (+34,087) | 9,804,219 (+315,364) |
+| 2026-01-29 | 7,815,471 (+326,101) | 2,374,982 (+60,133) | 10,190,453 (+386,234) |
diff --git a/bun.lock b/bun.lock
index e0f417e6f6..1ce6d54372 100644
--- a/bun.lock
+++ b/bun.lock
@@ -12,7 +12,6 @@
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
- "@types/better-sqlite3": "7.6.13",
"@types/pg": "8.18.0",
"@typescript/native-preview": "catalog:",
"husky": "9.1.7",
@@ -42,7 +41,6 @@
"optionalDependencies": {
"@databricks/sql": "^1.0.0",
"@google-cloud/bigquery": "^8.0.0",
- "better-sqlite3": "^11.0.0",
"duckdb": "^1.0.0",
"mssql": "^11.0.0",
"mysql2": "^3.0.0",
@@ -81,15 +79,13 @@
"@ai-sdk/togetherai": "1.0.34",
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
- "@altimateai/altimate-core": "^0.2.5",
- "@altimateai/drivers": "workspace:*",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.6.0",
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
- "@modelcontextprotocol/sdk": "1.26.0",
+ "@modelcontextprotocol/sdk": "1.25.2",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
@@ -140,12 +136,13 @@
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"xdg-basedir": "5.1.0",
- "yaml": "2.8.2",
"yargs": "18.0.0",
"zod": "catalog:",
"zod-to-json-schema": "3.24.5",
},
"devDependencies": {
+ "@altimateai/altimate-core": "0.2.5",
+ "@altimateai/drivers": "workspace:*",
"@babel/core": "7.28.4",
"@effect/language-service": "0.79.0",
"@octokit/webhooks-types": "7.6.1",
@@ -163,7 +160,6 @@
"@types/babel__core": "7.20.5",
"@types/bun": "catalog:",
"@types/mime-types": "3.0.1",
- "@types/pg": "8.18.0",
"@types/semver": "^7.5.8",
"@types/turndown": "5.0.5",
"@types/which": "3.0.4",
@@ -764,7 +760,7 @@
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
- "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="],
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.2", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
@@ -1286,7 +1282,7 @@
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
- "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
@@ -1478,7 +1474,7 @@
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
- "express-rate-limit": ["express-rate-limit@8.3.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw=="],
+ "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
@@ -2854,8 +2850,6 @@
"@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
- "@modelcontextprotocol/sdk/hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="],
-
"@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
"@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
@@ -3126,10 +3120,10 @@
"table-layout/typical": ["typical@7.3.0", "", {}, "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw=="],
- "tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
-
"tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
+ "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+
"tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
diff --git a/github/script/release b/github/script/release
index 5d72009c09..35180b4543 100755
--- a/github/script/release
+++ b/github/script/release
@@ -10,7 +10,7 @@ while [ "$#" -gt 0 ]; do
done
# Get the latest Git tag
-git fetch origin --force --tags
+git fetch --force --tags
latest_tag=$(git tag --sort=committerdate | grep -E '^github-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
if [ -z "$latest_tag" ]; then
echo "No tags found"
@@ -38,4 +38,4 @@ echo "New version: $new_version"
# Tag
git tag $new_version
-git push origin "$new_version"
\ No newline at end of file
+git push --tags
\ No newline at end of file
diff --git a/package.json b/package.json
index b532d05b7f..fa9ac41d4b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"description": "AI-powered development tool",
"private": true,
"type": "module",
- "packageManager": "bun@1.3.10",
+ "packageManager": "bun@1.3.9",
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop tauri dev",
diff --git a/packages/opencode/Dockerfile b/packages/opencode/Dockerfile
index 28fc82f57b..f92b48a6d1 100644
--- a/packages/opencode/Dockerfile
+++ b/packages/opencode/Dockerfile
@@ -7,13 +7,12 @@ ENV BUN_RUNTIME_TRANSPILER_CACHE_PATH=${BUN_RUNTIME_TRANSPILER_CACHE_PATH}
RUN apk add libgcc libstdc++ ripgrep
FROM base AS build-amd64
-COPY dist/@altimateai/altimate-code-linux-x64-baseline-musl/bin/altimate /usr/local/bin/altimate
+COPY dist/opencode-linux-x64-baseline-musl/bin/opencode /usr/local/bin/opencode
FROM base AS build-arm64
-COPY dist/@altimateai/altimate-code-linux-arm64-musl/bin/altimate /usr/local/bin/altimate
+COPY dist/opencode-linux-arm64-musl/bin/opencode /usr/local/bin/opencode
ARG TARGETARCH
FROM build-${TARGETARCH}
-RUN ln -sf /usr/local/bin/altimate /usr/local/bin/altimate-code
-RUN altimate --version
-ENTRYPOINT ["altimate"]
+RUN opencode --version
+ENTRYPOINT ["opencode"]
diff --git a/packages/opencode/bin/opencode b/packages/opencode/bin/opencode
new file mode 100755
index 0000000000..a7674ce2f8
--- /dev/null
+++ b/packages/opencode/bin/opencode
@@ -0,0 +1,179 @@
+#!/usr/bin/env node
+
+const childProcess = require("child_process")
+const fs = require("fs")
+const path = require("path")
+const os = require("os")
+
+function run(target) {
+ const result = childProcess.spawnSync(target, process.argv.slice(2), {
+ stdio: "inherit",
+ })
+ if (result.error) {
+ console.error(result.error.message)
+ process.exit(1)
+ }
+ const code = typeof result.status === "number" ? result.status : 0
+ process.exit(code)
+}
+
+const envPath = process.env.OPENCODE_BIN_PATH
+if (envPath) {
+ run(envPath)
+}
+
+const scriptPath = fs.realpathSync(__filename)
+const scriptDir = path.dirname(scriptPath)
+
+//
+const cached = path.join(scriptDir, ".opencode")
+if (fs.existsSync(cached)) {
+ run(cached)
+}
+
+const platformMap = {
+ darwin: "darwin",
+ linux: "linux",
+ win32: "windows",
+}
+const archMap = {
+ x64: "x64",
+ arm64: "arm64",
+ arm: "arm",
+}
+
+let platform = platformMap[os.platform()]
+if (!platform) {
+ platform = os.platform()
+}
+let arch = archMap[os.arch()]
+if (!arch) {
+ arch = os.arch()
+}
+const base = "opencode-" + platform + "-" + arch
+const binary = platform === "windows" ? "opencode.exe" : "opencode"
+
+function supportsAvx2() {
+ if (arch !== "x64") return false
+
+ if (platform === "linux") {
+ try {
+ return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
+ } catch {
+ return false
+ }
+ }
+
+ if (platform === "darwin") {
+ try {
+ const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
+ encoding: "utf8",
+ timeout: 1500,
+ })
+ if (result.status !== 0) return false
+ return (result.stdout || "").trim() === "1"
+ } catch {
+ return false
+ }
+ }
+
+ if (platform === "windows") {
+ const cmd =
+ '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
+
+ for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
+ try {
+ const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
+ encoding: "utf8",
+ timeout: 3000,
+ windowsHide: true,
+ })
+ if (result.status !== 0) continue
+ const out = (result.stdout || "").trim().toLowerCase()
+ if (out === "true" || out === "1") return true
+ if (out === "false" || out === "0") return false
+ } catch {
+ continue
+ }
+ }
+
+ return false
+ }
+
+ return false
+}
+
+const names = (() => {
+ const avx2 = supportsAvx2()
+ const baseline = arch === "x64" && !avx2
+
+ if (platform === "linux") {
+ const musl = (() => {
+ try {
+ if (fs.existsSync("/etc/alpine-release")) return true
+ } catch {
+ // ignore
+ }
+
+ try {
+ const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
+ const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
+ if (text.includes("musl")) return true
+ } catch {
+ // ignore
+ }
+
+ return false
+ })()
+
+ if (musl) {
+ if (arch === "x64") {
+ if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
+ return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
+ }
+ return [`${base}-musl`, base]
+ }
+
+ if (arch === "x64") {
+ if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
+ return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
+ }
+ return [base, `${base}-musl`]
+ }
+
+ if (arch === "x64") {
+ if (baseline) return [`${base}-baseline`, base]
+ return [base, `${base}-baseline`]
+ }
+ return [base]
+})()
+
+function findBinary(startDir) {
+ let current = startDir
+ for (;;) {
+ const modules = path.join(current, "node_modules")
+ if (fs.existsSync(modules)) {
+ for (const name of names) {
+ const candidate = path.join(modules, name, "bin", binary)
+ if (fs.existsSync(candidate)) return candidate
+ }
+ }
+ const parent = path.dirname(current)
+ if (parent === current) {
+ return
+ }
+ current = parent
+ }
+}
+
+const resolved = findBinary(scriptDir)
+if (!resolved) {
+ console.error(
+ "It seems that your package manager failed to install the right version of the opencode CLI for your platform. You can try manually installing " +
+ names.map((n) => `\"${n}\"`).join(" or ") +
+ " package",
+ )
+ process.exit(1)
+}
+
+run(resolved)
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 96c8cb1924..f597169fde 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -9,8 +9,6 @@
"typecheck": "tsgo --noEmit",
"test": "bun test --timeout 30000",
"build": "bun run script/build.ts",
- "build:local": "bun run script/build.ts --single --skip-install",
- "local": "./script/local.sh",
"dev": "bun run --conditions=browser ./src/index.ts",
"db": "bun drizzle-kit"
},
@@ -22,6 +20,8 @@
"./*": "./src/*.ts"
},
"devDependencies": {
+ "@altimateai/altimate-core": "0.2.5",
+ "@altimateai/drivers": "workspace:*",
"@babel/core": "7.28.4",
"@effect/language-service": "0.79.0",
"@octokit/webhooks-types": "7.6.1",
@@ -38,8 +38,8 @@
"@tsconfig/bun": "catalog:",
"@types/babel__core": "7.20.5",
"@types/bun": "catalog:",
+ "@types/js-yaml": "4.0.9",
"@types/mime-types": "3.0.1",
- "@types/pg": "8.18.0",
"@types/semver": "^7.5.8",
"@types/turndown": "5.0.5",
"@types/which": "3.0.4",
@@ -50,6 +50,7 @@
"typescript": "catalog:",
"vscode-languageserver-types": "3.17.5",
"why-is-node-running": "3.2.2",
+ "yaml": "2.8.3",
"zod-to-json-schema": "3.24.5"
},
"dependencies": {
@@ -75,15 +76,13 @@
"@ai-sdk/togetherai": "1.0.34",
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
- "@altimateai/altimate-core": "^0.2.5",
- "@altimateai/drivers": "workspace:*",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.6.0",
"@gitlab/opencode-gitlab-auth": "1.3.3",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
- "@modelcontextprotocol/sdk": "1.26.0",
+ "@modelcontextprotocol/sdk": "1.25.2",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
@@ -134,7 +133,6 @@
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"xdg-basedir": "5.1.0",
- "yaml": "2.8.2",
"yargs": "18.0.0",
"zod": "catalog:",
"zod-to-json-schema": "3.24.5"
diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts
index 37ace9be55..129e0ca34f 100755
--- a/packages/opencode/script/publish.ts
+++ b/packages/opencode/script/publish.ts
@@ -11,7 +11,7 @@ process.chdir(dir)
// NAPI native modules that must be installed alongside the CLI binary.
// These cannot be embedded in Bun's single-file executable — the JS loader
// dynamically require()s platform-specific .node binaries at runtime.
-const altimateCoreDep = pkg.dependencies["@altimateai/altimate-core"]
+const altimateCoreDep = (pkg.dependencies as Record)["@altimateai/altimate-core"]
if (!altimateCoreDep) {
console.error("Missing required dependency: @altimateai/altimate-core in package.json")
process.exit(1)
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index f935ab4857..01e98ac20f 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -201,7 +201,6 @@ export namespace ACP {
return
}
// altimate_change end
-
const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve()
const next = prev
.then(async () => {
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index b6363e8d6f..fdfa920a81 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -65,6 +65,19 @@ export namespace Agent {
"*": "ask",
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
},
+ // altimate_change start - SQL write safety denials
+ sql_execute_write: {
+ "DROP DATABASE *": "deny",
+ "DROP SCHEMA *": "deny",
+ "TRUNCATE *": "deny",
+ "drop database *": "deny",
+ "drop schema *": "deny",
+ "truncate *": "deny",
+ "Drop Database *": "deny",
+ "Drop Schema *": "deny",
+ "Truncate *": "deny",
+ },
+ // altimate_change end
question: "deny",
plan_enter: "deny",
plan_exit: "deny",
@@ -75,40 +88,11 @@ export namespace Agent {
"*.env.*": "ask",
"*.env.example": "allow",
},
- // Safety defaults for bash commands.
- // IMPORTANT: "*": "ask" must come FIRST because evaluation uses last-match-wins.
- //
- // "ask" = user sees prompt and can approve. Used for destructive file/git
- // commands that are common in legitimate workflows (rm -rf ./build,
- // git push --force after rebase, git clean in CI).
- // "deny" = blocked entirely, no prompt. Used for database DDL that is
- // almost never intentional in an agent context.
- //
- // Users can override any of these in altimate-code.json.
- bash: {
- "*": "ask",
- "rm -rf *": "ask",
- "rm -fr *": "ask",
- "git push --force *": "ask",
- "git push -f *": "ask",
- "git reset --hard *": "ask",
- "git clean -f *": "ask",
- "DROP DATABASE *": "deny",
- "DROP SCHEMA *": "deny",
- "TRUNCATE *": "deny",
- "drop database *": "deny",
- "drop schema *": "deny",
- "truncate *": "deny",
- },
})
const user = PermissionNext.fromConfig(cfg.permission ?? {})
// Safety deny rules that CANNOT be overridden by wildcard allows.
// Appended after user config so they always take precedence via last-match-wins.
- // Users who need to override must use specific patterns like
- // `"DROP DATABASE test_db": "allow"` — wildcard `bash: "allow"` won't work.
- // Both UPPER and lowercase variants are included because Wildcard.match
- // is case-sensitive on Linux/macOS.
const safetyDenials = PermissionNext.fromConfig({
bash: {
"DROP DATABASE *": "deny",
@@ -206,6 +190,21 @@ export namespace Agent {
native: true,
},
// altimate_change end
+ build: {
+ name: "build",
+ description: "The default agent. Executes tools based on configured permissions.",
+ options: {},
+ permission: PermissionNext.merge(
+ defaults,
+ PermissionNext.fromConfig({
+ question: "allow",
+ plan_enter: "allow",
+ }),
+ user,
+ ),
+ mode: "primary",
+ native: true,
+ },
plan: {
name: "plan",
description: "Plan mode. Disallows all edit tools.",
@@ -224,7 +223,7 @@ export namespace Agent {
[path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
},
}),
- userWithSafety,
+ user,
),
mode: "primary",
native: true,
@@ -238,7 +237,7 @@ export namespace Agent {
todoread: "deny",
todowrite: "deny",
}),
- userWithSafety,
+ user,
),
options: {},
mode: "subagent",
@@ -263,7 +262,7 @@ export namespace Agent {
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
},
}),
- userWithSafety,
+ user,
),
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
prompt: PROMPT_EXPLORE,
@@ -282,7 +281,7 @@ export namespace Agent {
PermissionNext.fromConfig({
"*": "deny",
}),
- userWithSafety,
+ user,
),
options: {},
},
@@ -298,7 +297,7 @@ export namespace Agent {
PermissionNext.fromConfig({
"*": "deny",
}),
- userWithSafety,
+ user,
),
prompt: PROMPT_TITLE,
},
@@ -313,7 +312,7 @@ export namespace Agent {
PermissionNext.fromConfig({
"*": "deny",
}),
- userWithSafety,
+ user,
),
prompt: PROMPT_SUMMARY,
},
@@ -329,7 +328,7 @@ export namespace Agent {
item = result[key] = {
name: key,
mode: "all",
- permission: PermissionNext.merge(defaults, userWithSafety),
+ permission: PermissionNext.merge(defaults, user),
options: {},
native: false,
}
@@ -345,8 +344,7 @@ export namespace Agent {
item.name = value.name ?? item.name
item.steps = value.steps ?? item.steps
item.options = mergeDeep(item.options, value.options ?? {})
- // Re-apply safety denials AFTER user config so they cannot be overridden
- item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {}), safetyDenials)
+ item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {}))
}
// Ensure Truncate.GLOB is allowed unless explicitly configured
diff --git a/packages/opencode/src/altimate/native/connections/dbt-profiles.ts b/packages/opencode/src/altimate/native/connections/dbt-profiles.ts
index e08892e128..2dcf7738c4 100644
--- a/packages/opencode/src/altimate/native/connections/dbt-profiles.ts
+++ b/packages/opencode/src/altimate/native/connections/dbt-profiles.ts
@@ -121,7 +121,7 @@ export async function parseDbtProfiles(
} catch {
try {
// Fall back to `js-yaml`
- // @ts-expect-error — optional fallback dependency
+ // @ts-ignore — optional fallback dependency
const jsYaml = await import("js-yaml")
const jsYamlLib = jsYaml.default || jsYaml
parseYaml = (content: string) => jsYamlLib.load(content)
diff --git a/packages/opencode/src/altimate/tools/datamate.ts b/packages/opencode/src/altimate/tools/datamate.ts
index e15e5af450..0e731f7989 100644
--- a/packages/opencode/src/altimate/tools/datamate.ts
+++ b/packages/opencode/src/altimate/tools/datamate.ts
@@ -302,7 +302,7 @@ async function handleDelete(args: { datamate_id?: string }) {
const disconnected: string[] = []
if (serverName in allStatus) {
try {
- await MCP.remove(serverName)
+ await MCP.disconnect(serverName)
disconnected.push(serverName)
} catch {
// Log but don't fail the delete operation
@@ -383,7 +383,7 @@ async function handleRemove(args: { server_name?: string; scope?: "project" | "g
}
try {
// Fully remove from runtime state (disconnect + purge from MCP list)
- await MCP.remove(args.server_name).catch(() => {})
+ await MCP.disconnect(args.server_name).catch(() => {})
// Remove from config files — when no scope specified, try both to avoid orphaned entries
const removed: string[] = []
diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts
index 297a7ec021..ef075d732a 100644
--- a/packages/opencode/src/cli/cmd/debug/agent.ts
+++ b/packages/opencode/src/cli/cmd/debug/agent.ts
@@ -159,7 +159,7 @@ async function createToolContext(agent: Agent.Info) {
for (const pattern of req.patterns) {
const rule = PermissionNext.evaluate(req.permission, pattern, ruleset)
if (rule.action === "deny") {
- throw new PermissionNext.DeniedError(ruleset)
+ throw new PermissionNext.DeniedError({ ruleset })
}
}
},
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index 4ae8c5632c..b659d2d887 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -135,9 +135,9 @@ type IssueQueryResponse = {
}
}
-const AGENT_USERNAME = "altimate-code-agent[bot]"
+const AGENT_USERNAME = "opencode-agent[bot]"
const AGENT_REACTION = "eyes"
-const WORKFLOW_FILE = ".github/workflows/altimate-code.yml"
+const WORKFLOW_FILE = ".github/workflows/opencode.yml"
// Event categories for routing
// USER_EVENTS: triggered by user actions, have actor/issueId, support reactions/comments
@@ -244,7 +244,7 @@ export const GithubInstallCommand = cmd({
"",
" 3. Go to a GitHub issue and comment `/oc summarize` to see the agent in action",
"",
- " Learn more about the GitHub agent - https://altimate-code.dev/docs/github/#usage-examples",
+ " Learn more about the GitHub agent - https://altimate.ai/docs/github/#usage-examples",
].join("\n"),
)
}
@@ -268,7 +268,7 @@ export const GithubInstallCommand = cmd({
async function promptProvider() {
const priority: Record = {
- "altimate-code": 0,
+ opencode: 0,
anthropic: 1,
openai: 2,
google: 3,
@@ -326,7 +326,7 @@ export const GithubInstallCommand = cmd({
if (installation) return s.stop("GitHub app already installed")
// Open browser
- const url = "https://github.com/apps/altimate-code-agent"
+ const url = "https://github.com/apps/opencode-agent"
const command =
process.platform === "darwin"
? `open "${url}"`
@@ -363,7 +363,7 @@ export const GithubInstallCommand = cmd({
async function getInstallation() {
return await fetch(
- `https://api.altimate-code.dev/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,
+ `https://api.altimate.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`,
)
.then((res) => res.json())
.then((data) => data.installation)
@@ -378,7 +378,7 @@ export const GithubInstallCommand = cmd({
await Filesystem.write(
path.join(app.root, WORKFLOW_FILE),
- `name: altimate-code
+ `name: opencode
on:
issue_comment:
@@ -387,12 +387,12 @@ on:
types: [created]
jobs:
- altimate-code:
+ opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
- contains(github.event.comment.body, ' /altimate-code') ||
- startsWith(github.event.comment.body, '/altimate-code')
+ contains(github.event.comment.body, ' /opencode') ||
+ startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
@@ -405,8 +405,8 @@ jobs:
with:
persist-credentials: false
- - name: Run altimate-code
- uses: altimate/altimate-code/github@latest${envStr}
+ - name: Run opencode
+ uses: AltimateAI/altimate-code/github@latest${envStr}
with:
model: ${provider}/${model}`,
)
@@ -476,7 +476,7 @@ export const GithubRunCommand = cmd({
? (payload as IssueCommentEvent | IssuesEvent).issue.number
: (payload as PullRequestEvent | PullRequestReviewCommentEvent).pull_request.number
const runUrl = `/${owner}/${repo}/actions/runs/${runId}`
- const shareBaseUrl = isMock ? "https://dev.altimate-code.dev" : "https://altimate-code.dev"
+ const shareBaseUrl = isMock ? "https://dev.altimate.ai" : "https://altimate.ai"
let appToken: string
let octoRest: Octokit
@@ -544,7 +544,7 @@ export const GithubRunCommand = cmd({
await addReaction(commentType)
}
- // Setup altimate-code session
+ // Setup opencode session
const repoData = await fetchRepo()
session = await Session.create({
permission: [
@@ -562,7 +562,7 @@ export const GithubRunCommand = cmd({
await Session.share(session.id)
return session.id.slice(-8)
})()
- console.log("altimate-code session", session.id)
+ console.log("opencode session", session.id)
// Handle event types:
// REPO_EVENTS (schedule, workflow_dispatch): no issue/PR context, output to logs/PR only
@@ -735,7 +735,7 @@ export const GithubRunCommand = cmd({
function normalizeOidcBaseUrl(): string {
const value = process.env["OIDC_BASE_URL"]
- if (!value) return "https://api.altimate-code.dev"
+ if (!value) return "https://api.altimate.ai"
return value.replace(/\/+$/, "")
}
@@ -784,7 +784,7 @@ export const GithubRunCommand = cmd({
}
const reviewContext = getReviewCommentContext()
- const mentions = (process.env["MENTIONS"] || "/altimate-code,/oc")
+ const mentions = (process.env["MENTIONS"] || "/opencode,/oc")
.split(",")
.map((m) => m.trim().toLowerCase())
.filter(Boolean)
@@ -931,7 +931,7 @@ export const GithubRunCommand = cmd({
}
async function chat(message: string, files: PromptFiles = []) {
- console.log("Sending message to altimate-code...")
+ console.log("Sending message to opencode...")
const result = await SessionPrompt.prompt({
sessionID: session.id,
@@ -1027,7 +1027,7 @@ export const GithubRunCommand = cmd({
async function getOidcToken() {
try {
- return await core.getIDToken("altimate-code-github-action")
+ return await core.getIDToken("opencode-github-action")
} catch (error) {
console.error("Failed to get OIDC token:", error instanceof Error ? error.message : error)
throw new Error(
@@ -1129,9 +1129,9 @@ export const GithubRunCommand = cmd({
.join("")
if (type === "schedule" || type === "dispatch") {
const hex = crypto.randomUUID().slice(0, 6)
- return `altimate-code/${type}-${hex}-${timestamp}`
+ return `opencode/${type}-${hex}-${timestamp}`
}
- return `altimate-code/${type}${issueId}-${timestamp}`
+ return `opencode/${type}${issueId}-${timestamp}`
}
async function pushToNewBranch(summary: string, branch: string, commit: boolean, isSchedule: boolean) {
@@ -1403,9 +1403,9 @@ export const GithubRunCommand = cmd({
const titleAlt = encodeURIComponent(session.title.substring(0, 50))
const title64 = Buffer.from(session.title.substring(0, 700), "utf8").toString("base64")
- return `
\n`
+ return `
\n`
})()
- const shareUrl = shareId ? `[altimate-code session](${shareBaseUrl}/s/${shareId}) | ` : ""
+ const shareUrl = shareId ? `[opencode session](${shareBaseUrl}/s/${shareId}) | ` : ""
return `\n\n${image}${shareUrl}[github run](${runUrl})`
}
@@ -1466,7 +1466,7 @@ query($owner: String!, $repo: String!, $number: Int!) {
return [
"",
"You are running as a GitHub Action. Important:",
- "- Git push and PR creation are handled AUTOMATICALLY by the altimate-code infrastructure after your response",
+ "- Git push and PR creation are handled AUTOMATICALLY by the opencode infrastructure after your response",
"- Do NOT include warnings or disclaimers about GitHub tokens, workflow permissions, or PR creation capabilities",
"- Do NOT suggest manual steps for creating PRs or pushing code - this happens automatically",
"- Focus only on the code changes and your analysis/response",
@@ -1604,7 +1604,7 @@ query($owner: String!, $repo: String!, $number: Int!) {
return [
"",
"You are running as a GitHub Action. Important:",
- "- Git push and PR creation are handled AUTOMATICALLY by the altimate-code infrastructure after your response",
+ "- Git push and PR creation are handled AUTOMATICALLY by the opencode infrastructure after your response",
"- Do NOT include warnings or disclaimers about GitHub tokens, workflow permissions, or PR creation capabilities",
"- Do NOT suggest manual steps for creating PRs or pushing code - this happens automatically",
"- Focus only on the code changes and your analysis/response",
diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts
index ca9a87a320..c45b9e55d0 100644
--- a/packages/opencode/src/cli/cmd/mcp.ts
+++ b/packages/opencode/src/cli/cmd/mcp.ts
@@ -12,8 +12,9 @@ import { Instance } from "../../project/instance"
import { Installation } from "../../installation"
import path from "path"
import { Global } from "../../global"
+import { modify, applyEdits } from "jsonc-parser"
+import { Filesystem } from "../../util/filesystem"
import { Bus } from "../../bus"
-import { resolveConfigPath, addMcpToConfig, removeMcpFromConfig } from "../../mcp/config"
function getAuthStatusIcon(status: MCP.AuthStatus): string {
switch (status) {
@@ -55,7 +56,6 @@ export const McpCommand = cmd({
builder: (yargs) =>
yargs
.command(McpAddCommand)
- .command(McpRemoveCommand)
.command(McpListCommand)
.command(McpAuthCommand)
.command(McpLogoutCommand)
@@ -85,7 +85,7 @@ export const McpListCommand = cmd({
if (servers.length === 0) {
prompts.log.warn("No MCP servers configured")
- prompts.outro("Add servers with: altimate mcp add")
+ prompts.outro("Add servers with: opencode mcp add")
return
}
@@ -162,7 +162,7 @@ export const McpAuthCommand = cmd({
if (oauthServers.length === 0) {
prompts.log.warn("No OAuth-capable MCP servers configured")
- prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in altimate-code.json:")
+ prompts.log.info("Remote MCP servers support OAuth by default. Add a remote server in opencode.json:")
prompts.log.info(`
"mcp": {
"my-server": {
@@ -380,82 +380,48 @@ export const McpLogoutCommand = cmd({
},
})
-export const McpAddCommand = cmd({
- command: "add",
- describe: "add an MCP server",
- builder: (yargs) =>
- yargs
- .option("name", { type: "string", describe: "MCP server name" })
- .option("type", { type: "string", describe: "Server type", choices: ["local", "remote"] })
- .option("url", { type: "string", describe: "Server URL (for remote type)" })
- .option("command", { type: "string", describe: "Command to run (for local type)" })
- .option("header", { type: "array", string: true, describe: "HTTP headers as key=value (repeatable)" })
- .option("oauth", { type: "boolean", describe: "Enable OAuth", default: true })
- .option("global", { type: "boolean", describe: "Add to global config", default: false }),
- async handler(args) {
- await Instance.provide({
- directory: process.cwd(),
- async fn() {
- // Non-interactive mode: all required args provided via flags
- if (args.name && args.type) {
- if (!args.name.trim()) {
- console.error("MCP server name cannot be empty")
- process.exit(1)
- }
+async function resolveConfigPath(baseDir: string, global = false) {
+ // Check for existing config files (prefer .jsonc over .json, check .opencode/ subdirectory too)
+ const candidates = [path.join(baseDir, "opencode.json"), path.join(baseDir, "opencode.jsonc")]
- const useGlobal = args.global || Instance.project.vcs !== "git"
- const configPath = await resolveConfigPath(
- useGlobal ? Global.Path.config : Instance.worktree,
- useGlobal,
- )
+ if (!global) {
+ candidates.push(path.join(baseDir, ".opencode", "opencode.json"), path.join(baseDir, ".opencode", "opencode.jsonc"))
+ }
- let mcpConfig: Config.Mcp
+ for (const candidate of candidates) {
+ if (await Filesystem.exists(candidate)) {
+ return candidate
+ }
+ }
- if (args.type === "local") {
- if (!args.command?.trim()) {
- console.error("--command is required for local type")
- process.exit(1)
- }
- mcpConfig = {
- type: "local",
- command: args.command.trim().split(/\s+/).filter(Boolean),
- }
- } else {
- if (!args.url) {
- console.error("--url is required for remote type")
- process.exit(1)
- }
- if (!URL.canParse(args.url)) {
- console.error(`Invalid URL: ${args.url}`)
- process.exit(1)
- }
+ // Default to opencode.json if none exist
+ return candidates[0]
+}
- const headers: Record = {}
- if (args.header) {
- for (const h of args.header) {
- const eq = h.indexOf("=")
- if (eq === -1) {
- console.error(`Invalid header format: ${h} (expected key=value)`)
- process.exit(1)
- }
- headers[h.substring(0, eq)] = h.substring(eq + 1)
- }
- }
+async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) {
+ let text = "{}"
+ if (await Filesystem.exists(configPath)) {
+ text = await Filesystem.readText(configPath)
+ }
- mcpConfig = {
- type: "remote",
- url: args.url,
- ...(!args.oauth ? { oauth: false as const } : {}),
- ...(Object.keys(headers).length > 0 ? { headers } : {}),
- }
- }
+ // Use jsonc-parser to modify while preserving comments
+ const edits = modify(text, ["mcp", name], mcpConfig, {
+ formattingOptions: { tabSize: 2, insertSpaces: true },
+ })
+ const result = applyEdits(text, edits)
- await addMcpToConfig(args.name, mcpConfig, configPath)
- console.log(`MCP server "${args.name}" added to ${configPath}`)
- return
- }
+ await Filesystem.write(configPath, result)
+
+ return configPath
+}
- // Interactive mode
+export const McpAddCommand = cmd({
+ command: "add",
+ describe: "add an MCP server",
+ async handler() {
+ await Instance.provide({
+ directory: process.cwd(),
+ async fn() {
UI.empty()
prompts.intro("Add MCP server")
@@ -515,7 +481,7 @@ export const McpAddCommand = cmd({
if (type === "local") {
const command = await prompts.text({
message: "Enter command to run",
- placeholder: "e.g., altimate x @modelcontextprotocol/server-filesystem",
+ placeholder: "e.g., opencode x @modelcontextprotocol/server-filesystem",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(command)) throw new UI.CancelledError()
@@ -613,58 +579,6 @@ export const McpAddCommand = cmd({
},
})
-export const McpRemoveCommand = cmd({
- command: "remove ",
- aliases: ["rm"],
- describe: "remove an MCP server",
- builder: (yargs) =>
- yargs
- .positional("name", {
- describe: "name of the MCP server to remove",
- type: "string",
- demandOption: true,
- })
- .option("global", { type: "boolean", describe: "Remove from global config", default: false }),
- async handler(args) {
- await Instance.provide({
- directory: process.cwd(),
- async fn() {
- const useGlobal = args.global || Instance.project.vcs !== "git"
- const configPath = await resolveConfigPath(
- useGlobal ? Global.Path.config : Instance.worktree,
- useGlobal,
- )
-
- const removed = await removeMcpFromConfig(args.name, configPath)
- if (removed) {
- console.log(`MCP server "${args.name}" removed from ${configPath}`)
- } else if (Instance.project.vcs === "git" && !args.global) {
- const globalPath = await resolveConfigPath(Global.Path.config, true)
- const removedGlobal = await removeMcpFromConfig(args.name, globalPath)
- if (removedGlobal) {
- console.log(`MCP server "${args.name}" removed from ${globalPath}`)
- } else {
- console.error(`MCP server "${args.name}" not found in any config`)
- process.exit(1)
- }
- } else if (args.global && Instance.project.vcs === "git") {
- const localPath = await resolveConfigPath(Instance.worktree, false)
- const removedLocal = await removeMcpFromConfig(args.name, localPath)
- if (removedLocal) {
- console.log(`MCP server "${args.name}" removed from ${localPath}`)
- } else {
- console.error(`MCP server "${args.name}" not found in any config`)
- process.exit(1)
- }
- } else {
- console.error(`MCP server "${args.name}" not found in any config`)
- process.exit(1)
- }
- },
- })
- },
-})
-
export const McpDebugCommand = cmd({
command: "debug ",
describe: "debug OAuth connection for an MCP server",
@@ -748,7 +662,7 @@ export const McpDebugCommand = cmd({
params: {
protocolVersion: "2024-11-05",
capabilities: {},
- clientInfo: { name: "altimate-code-debug", version: Installation.VERSION },
+ clientInfo: { name: "opencode-debug", version: Installation.VERSION },
},
id: 1,
}),
@@ -789,7 +703,7 @@ export const McpDebugCommand = cmd({
try {
const client = new Client({
- name: "altimate-code-debug",
+ name: "opencode-debug",
version: Installation.VERSION,
})
await client.connect(transport)
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index cc8fd5dc0c..9cb08bb57d 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -27,8 +27,6 @@ import { SkillTool } from "../../tool/skill"
import { BashTool } from "../../tool/bash"
import { TodoWriteTool } from "../../tool/todo"
import { Locale } from "../../util/locale"
-import { Tracer, FileExporter, HttpExporter, type TraceExporter } from "../../altimate/observability/tracing"
-import { Config } from "../../config/config"
type ToolProps = {
input: Tool.InferParameters
@@ -214,33 +212,6 @@ function todo(info: ToolProps) {
)
}
-function splitSqlStatements(sql: string): string[] {
- const stmts: string[] = []
- const current: string[] = []
- let inStr = false
- let strChar = ""
- for (let i = 0; i < sql.length; i++) {
- const ch = sql[i]
- if (!inStr && (ch === "'" || ch === '"' || ch === "`")) {
- inStr = true
- strChar = ch
- current.push(ch)
- } else if (inStr && ch === strChar) {
- inStr = false
- current.push(ch)
- } else if (!inStr && ch === ";" ) {
- const s = current.join("").trim()
- if (s) stmts.push(s)
- current.length = 0
- } else {
- current.push(ch)
- }
- }
- const last = current.join("").trim()
- if (last) stmts.push(last)
- return stmts.length ? stmts : [sql]
-}
-
function normalizePath(input?: string) {
if (!input) return ""
if (path.isAbsolute(input)) return path.relative(process.cwd(), input) || "."
@@ -249,7 +220,7 @@ function normalizePath(input?: string) {
export const RunCommand = cmd({
command: "run [message..]",
- describe: "run altimate with a message",
+ describe: "run opencode with a message",
builder: (yargs: Argv) => {
return yargs
.positional("message", {
@@ -307,7 +278,7 @@ export const RunCommand = cmd({
})
.option("attach", {
type: "string",
- describe: "attach to a running altimate server (e.g., http://localhost:4096)",
+ describe: "attach to a running opencode server (e.g., http://localhost:4096)",
})
.option("password", {
alias: ["p"],
@@ -331,26 +302,6 @@ export const RunCommand = cmd({
describe: "show thinking blocks",
default: false,
})
- .option("output", {
- alias: ["o"],
- type: "string",
- describe: "write final assistant response to file (.md or .txt)",
- })
- .option("audience", {
- type: "string",
- choices: ["executive", "technical"] as const,
- describe: "output calibration: executive (no SQL/jargon, business framing) or technical (default)",
- })
- .option("query", {
- alias: ["q"],
- type: "number",
- describe: "when using --file with a SQL file, analyze only the Nth statement (1-indexed)",
- })
- .option("trace", {
- type: "boolean",
- describe: "enable session tracing (default: true, disable with --no-trace)",
- default: true,
- })
},
handler: async (args) => {
let message = [...args.message, ...(args["--"] || [])]
@@ -391,28 +342,6 @@ export const RunCommand = cmd({
}
}
- // --query N: extract the Nth SQL statement from attached file(s) as a text part
- if (args.query !== undefined && args.file) {
- const fileList = Array.isArray(args.file) ? args.file : [args.file]
- const extractedParts: string[] = []
- for (const filePath of fileList) {
- const resolvedPath = path.resolve(process.cwd(), filePath)
- const content = await Bun.file(resolvedPath).text()
- const stmts = splitSqlStatements(content)
- const n = args.query
- if (n < 1 || n > stmts.length) {
- UI.error(`--query ${n} is out of range (${path.basename(filePath)} has ${stmts.length} statement${stmts.length === 1 ? "" : "s"})`)
- process.exit(1)
- }
- extractedParts.push(
- `[${path.basename(filePath)}, statement ${n} of ${stmts.length}]\n\`\`\`sql\n${stmts[n - 1].trim()}\n\`\`\``
- )
- }
- // Replace file attachments with extracted statement as inline text
- files.length = 0
- message = [extractedParts.join("\n\n"), message].filter(Boolean).join("\n\n")
- }
-
if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
if (message.trim().length === 0 && !args.command) {
@@ -479,18 +408,7 @@ export const RunCommand = cmd({
}
}
- const EXECUTIVE_DIRECTIVE = `## Output Calibration — Executive Mode
-You are speaking to a non-technical business executive. Follow these rules strictly:
-- NEVER show SQL queries, column names in backticks, or code blocks
-- NEVER use engineering jargon (Cartesian product, referential integrity, column pruning, NULL, schema, index, CTE, predicate)
-- Translate ALL technical findings to business impact: revenue, cost, risk, time, compliance exposure
-- Lead with the business implication, then briefly explain the cause in plain English if needed
-- Format output for a slide deck or email: short paragraphs, simple tables with business-friendly headers
-- "Query Duration" not "total_elapsed_time" — "Data Processed" not "bytes_scanned" — "Monthly Cost" not "credits_used * 3.00"`
-
async function execute(sdk: OpencodeClient) {
- const outputParts: string[] = []
-
function tool(part: ToolPart) {
try {
if (part.tool === "bash") return bash(props(part))
@@ -523,30 +441,6 @@ You are speaking to a non-technical business executive. Follow these rules stric
const events = await sdk.event.subscribe()
let error: string | undefined
- // Build tracer from config + CLI flags — must never crash the run command
- const tracer = await (async () => {
- try {
- if (args.trace === false) return null
-
- const cfg = await Config.get()
- const tracingCfg = cfg.tracing
- if (tracingCfg?.enabled === false) return null
-
- const exporters: TraceExporter[] = [new FileExporter(tracingCfg?.dir)]
-
- if (tracingCfg?.exporters) {
- for (const exp of tracingCfg.exporters) {
- exporters.push(new HttpExporter(exp.name, exp.endpoint, exp.headers))
- }
- }
-
- return Tracer.withExporters(exporters, { maxFiles: tracingCfg?.maxFiles })
- } catch {
- // Config failure should never prevent the run command from working
- return null
- }
- })()
-
async function loop() {
const toggles = new Map()
@@ -561,15 +455,6 @@ You are speaking to a non-technical business executive. Follow these rules stric
UI.println(`> ${event.properties.info.agent} · ${event.properties.info.modelID}`)
UI.empty()
toggles.set("start", true)
-
- // Enrich trace with resolved model/provider from the first assistant message
- const info = event.properties.info
- tracer?.enrichFromAssistant({
- modelID: info.modelID,
- providerID: info.providerID,
- agent: info.agent,
- variant: info.variant,
- })
}
if (event.type === "message.part.updated") {
@@ -577,7 +462,6 @@ You are speaking to a non-technical business executive. Follow these rules stric
if (part.sessionID !== sessionID) continue
if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
- tracer?.logToolCall(part as Parameters[0])
if (emit("tool_use", { part })) continue
if (part.state.status === "completed") {
tool(part)
@@ -602,21 +486,17 @@ You are speaking to a non-technical business executive. Follow these rules stric
}
if (part.type === "step-start") {
- tracer?.logStepStart(part)
if (emit("step_start", { part })) continue
}
if (part.type === "step-finish") {
- tracer?.logStepFinish(part)
if (emit("step_finish", { part })) continue
}
if (part.type === "text" && part.time?.end) {
- tracer?.logText(part)
if (emit("text", { part })) continue
const text = part.text.trim()
if (!text) continue
- if (args.output) outputParts.push(text)
if (!process.stdout.isTTY) {
process.stdout.write(text + EOL)
continue
@@ -692,9 +572,48 @@ You are speaking to a non-technical business executive. Follow these rules stric
}
}
- // Validate agent if specified; capture audience option from agent definition
- const { agent, agentAudience } = await (async () => {
- if (!args.agent) return { agent: undefined, agentAudience: undefined }
+ // Validate agent if specified
+ const agent = await (async () => {
+ if (!args.agent) return undefined
+
+ // When attaching, validate against the running server instead of local Instance state.
+ if (args.attach) {
+ const modes = await sdk.app
+ .agents(undefined, { throwOnError: true })
+ .then((x) => x.data ?? [])
+ .catch(() => undefined)
+
+ if (!modes) {
+ UI.println(
+ UI.Style.TEXT_WARNING_BOLD + "!",
+ UI.Style.TEXT_NORMAL,
+ `failed to list agents from ${args.attach}. Falling back to default agent`,
+ )
+ return undefined
+ }
+
+ const agent = modes.find((a) => a.name === args.agent)
+ if (!agent) {
+ UI.println(
+ UI.Style.TEXT_WARNING_BOLD + "!",
+ UI.Style.TEXT_NORMAL,
+ `agent "${args.agent}" not found. Falling back to default agent`,
+ )
+ return undefined
+ }
+
+ if (agent.mode === "subagent") {
+ UI.println(
+ UI.Style.TEXT_WARNING_BOLD + "!",
+ UI.Style.TEXT_NORMAL,
+ `agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`,
+ )
+ return undefined
+ }
+
+ return args.agent
+ }
+
const entry = await Agent.get(args.agent)
if (!entry) {
UI.println(
@@ -702,7 +621,7 @@ You are speaking to a non-technical business executive. Follow these rules stric
UI.Style.TEXT_NORMAL,
`agent "${args.agent}" not found. Falling back to default agent`,
)
- return { agent: undefined, agentAudience: undefined }
+ return undefined
}
if (entry.mode === "subagent") {
UI.println(
@@ -710,16 +629,11 @@ You are speaking to a non-technical business executive. Follow these rules stric
UI.Style.TEXT_NORMAL,
`agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`,
)
- return { agent: undefined, agentAudience: undefined }
+ return undefined
}
- const aud = entry.options?.audience as string | undefined
- return { agent: args.agent, agentAudience: aud }
+ return args.agent
})()
- // Build audience system directive (--audience flag overrides agent-level setting)
- const audienceMode = args.audience ?? agentAudience
- const audienceSystem = audienceMode === "executive" ? EXECUTIVE_DIRECTIVE : undefined
-
const sessionID = await session(sdk)
if (!sessionID) {
UI.error("Session not found")
@@ -727,28 +641,7 @@ You are speaking to a non-technical business executive. Follow these rules stric
}
await share(sdk, sessionID)
- // Start trace now that sessionID is available
- tracer?.startTrace(sessionID, {
- title: title() || message.slice(0, 80),
- model: args.model,
- agent,
- variant: args.variant,
- prompt: message,
- })
- // altimate_change start - activate tracer for session
- if (tracer) Tracer.setActive(tracer)
- // altimate_change end
-
- // Register crash handlers to flush the trace on unexpected exit
- const onSigint = () => { tracer?.flushSync("Process interrupted"); process.exit(130) }
- const onSigterm = () => { tracer?.flushSync("Process interrupted"); process.exit(143) }
- const onBeforeExit = () => { tracer?.flushSync("Process exited") }
- process.on("SIGINT", onSigint)
- process.on("SIGTERM", onSigterm)
- process.on("beforeExit", onBeforeExit)
-
- // Start event listener before sending the prompt so no events are missed
- const loopPromise = loop().catch((e) => {
+ loop().catch((e) => {
console.error(e)
process.exit(1)
})
@@ -770,37 +663,8 @@ You are speaking to a non-technical business executive. Follow these rules stric
model,
variant: args.variant,
parts: [...files, { type: "text", text: message }],
- ...(audienceSystem ? { system: audienceSystem } : {}),
})
}
-
- // Wait for the event loop to drain (breaks when session reaches idle)
- await loopPromise
-
- // Remove crash handlers — trace will be finalized cleanly
- process.removeListener("SIGINT", onSigint)
- process.removeListener("SIGTERM", onSigterm)
- process.removeListener("beforeExit", onBeforeExit)
-
- // Finalize trace and save to disk
- if (tracer) {
- Tracer.setActive(null)
- const tracePath = await tracer.endTrace(error)
- if (tracePath) {
- emit("trace_saved", { path: tracePath })
- if (args.format !== "json" && process.stdout.isTTY) {
- UI.println(UI.Style.TEXT_DIM + `Trace saved: ${tracePath}` + UI.Style.TEXT_NORMAL)
- }
- }
- }
-
- // Write accumulated text output to file if --output was specified
- if (args.output) {
- const outputPath = path.resolve(args.output)
- const content = outputParts.join("\n\n") || "(no text output — tool-only response)"
- await Bun.write(outputPath, content)
- process.stderr.write(`\n✓ Output saved to: ${outputPath}\n`)
- }
}
if (args.attach) {
@@ -820,7 +684,7 @@ You are speaking to a non-technical business executive. Follow these rules stric
const request = new Request(input, init)
return Server.Default().fetch(request)
}) as typeof globalThis.fetch
- const sdk = createOpencodeClient({ baseUrl: "http://altimate-code.internal", fetch: fetchFn })
+ const sdk = createOpencodeClient({ baseUrl: "http://opencode.internal", fetch: fetchFn })
await execute(sdk)
})
},
diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts
index cef51eef41..ab51fe8c3e 100644
--- a/packages/opencode/src/cli/cmd/serve.ts
+++ b/packages/opencode/src/cli/cmd/serve.ts
@@ -2,18 +2,22 @@ import { Server } from "../../server/server"
import { cmd } from "./cmd"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
import { Flag } from "../../flag/flag"
+import { Workspace } from "../../control-plane/workspace"
+import { Project } from "../../project/project"
+import { Installation } from "../../installation"
export const ServeCommand = cmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
- describe: "starts a headless altimate server",
+ describe: "starts a headless opencode server",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
- console.log(`altimate server listening on http://${server.hostname}:${server.port}`)
+ console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
+
await new Promise(() => {})
await server.stop()
},
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index b7b75a0756..33765f4670 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -6,9 +6,7 @@ import { RouteProvider, useRoute } from "@tui/context/route"
import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32"
import { Installation } from "@/installation"
-import { UPGRADE_KV_KEY } from "./component/upgrade-indicator-utils"
import { Flag } from "@/flag/flag"
-import { Log } from "@/util/log"
import { DialogProvider, useDialog } from "@tui/ui/dialog"
import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
import { SDKProvider, useSDK } from "@tui/context/sdk"
@@ -29,12 +27,31 @@ import { Home } from "@tui/routes/home"
import { Session } from "@tui/routes/session"
import { PromptHistoryProvider } from "./component/prompt/history"
import { FrecencyProvider } from "./component/prompt/frecency"
+import { PromptStashProvider } from "./component/prompt/stash"
+import { DialogAlert } from "./ui/dialog-alert"
+import { ToastProvider, useToast } from "./ui/toast"
+import { ExitProvider, useExit } from "./context/exit"
+import { Session as SessionApi } from "@/session"
+import { TuiEvent } from "./event"
+import { KVProvider, useKV } from "./context/kv"
+import { Provider } from "@/provider/provider"
+import { ArgsProvider, useArgs, type Args } from "./context/args"
+import open from "open"
+import { writeHeapSnapshot } from "v8"
+import { PromptRefProvider, usePromptRef } from "./context/prompt"
+import { TuiConfigProvider } from "./context/tui-config"
+import { TuiConfig } from "@/config/tui"
+import { Log } from "@/util/log"
+
+// altimate_change start - shared trace viewer server
+import fs from "fs/promises"
import { Tracer } from "@/altimate/observability/tracing"
import { renderTraceViewer } from "@/altimate/observability/viewer"
-import { DialogTraceList } from "./component/dialog-trace-list"
-import fsAsync from "fs/promises"
+import { DialogTraceList } from "@tui/component/dialog-trace-list"
+import { UPGRADE_KV_KEY } from "@tui/component/upgrade-indicator-utils"
+
+const fsAsync = fs
-// altimate_change start - shared trace viewer server
let traceViewerServer: ReturnType | undefined
let traceViewerTracesDir: string | undefined
function getTraceViewerUrl(sessionID: string, tracesDir?: string): string {
@@ -86,21 +103,6 @@ function getTraceViewerUrl(sessionID: string, tracesDir?: string): string {
}
// altimate_change end — renderInlineViewer removed, now using renderTraceViewer from viewer.ts
-import { PromptStashProvider } from "./component/prompt/stash"
-import { DialogAlert } from "./ui/dialog-alert"
-import { ToastProvider, useToast } from "./ui/toast"
-import { ExitProvider, useExit } from "./context/exit"
-import { Session as SessionApi } from "@/session"
-import { TuiEvent } from "./event"
-import { KVProvider, useKV } from "./context/kv"
-import { Provider } from "@/provider/provider"
-import { ArgsProvider, useArgs, type Args } from "./context/args"
-import open from "open"
-import { writeHeapSnapshot } from "v8"
-import { PromptRefProvider, usePromptRef } from "./context/prompt"
-import { TuiConfigProvider } from "./context/tui-config"
-import { TuiConfig } from "@/config/tui"
-
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
// can't set raw mode if not a TTY
if (!process.stdin.isTTY) return "dark"
@@ -249,7 +251,7 @@ export function tui(input: {
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
onCopySelection: (text) => {
Clipboard.copy(text).catch((error) => {
- Log.Default.error(`Failed to copy console selection to clipboard: ${error}`)
+ console.error(`Failed to copy console selection to clipboard: ${error}`)
})
},
},
@@ -306,7 +308,6 @@ function App() {
}
}
// altimate_change end
-
useKeyboard((evt) => {
if (!Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) return
if (!renderer.getSelection()) return
@@ -349,7 +350,7 @@ function App() {
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
createEffect(() => {
- Log.Default.debug("route changed", { route: route.data })
+ console.log(JSON.stringify(route.data))
})
// Update terminal window title based on current route and session
diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts
index 1dc4c6d525..e892f9922d 100644
--- a/packages/opencode/src/cli/cmd/tui/attach.ts
+++ b/packages/opencode/src/cli/cmd/tui/attach.ts
@@ -2,10 +2,13 @@ import { cmd } from "../cmd"
import { UI } from "@/cli/ui"
import { tui } from "./app"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
+import { TuiConfig } from "@/config/tui"
+import { Instance } from "@/project/instance"
+import { existsSync } from "fs"
export const AttachCommand = cmd({
command: "attach ",
- describe: "attach to a running altimate server",
+ describe: "attach to a running opencode server",
builder: (yargs) =>
yargs
.positional("url", {
@@ -60,17 +63,21 @@ export const AttachCommand = cmd({
const headers = (() => {
const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
if (!password) return undefined
- const auth = `Basic ${Buffer.from(`altimate:${password}`).toString("base64")}`
+ const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
return { Authorization: auth }
})()
+ const config = await Instance.provide({
+ directory: directory && existsSync(directory) ? directory : process.cwd(),
+ fn: () => TuiConfig.get(),
+ })
await tui({
url: args.url,
+ config,
args: {
continue: args.continue,
sessionID: args.session,
fork: args.fork,
},
- config: {},
directory,
headers,
})
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx
index 4f5c3c38f1..9cfa30d4df 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-mcp.tsx
@@ -7,7 +7,6 @@ import { useTheme } from "../context/theme"
import { Keybind } from "@/util/keybind"
import { TextAttributes } from "@opentui/core"
import { useSDK } from "@tui/context/sdk"
-import { Log } from "@/util/log"
function Status(props: { enabled: boolean; loading: boolean }) {
const { theme } = useTheme()
@@ -62,10 +61,10 @@ export function DialogMcp() {
if (status.data) {
sync.set("mcp", status.data)
} else {
- Log.Default.error("Failed to refresh MCP status: no data returned")
+ console.error("Failed to refresh MCP status: no data returned")
}
} catch (error) {
- Log.Default.error("Failed to toggle MCP", { error })
+ console.error("Failed to toggle MCP:", error)
} finally {
setLoading(null)
}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
index 5b59acbd3c..3b6b5ef218 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-status.tsx
@@ -79,7 +79,7 @@ export function DialogStatus() {
{(val) => val().error}
Disabled in configuration
- Needs authentication (run: altimate mcp auth {key})
+ Needs authentication (run: opencode mcp auth {key})
{(val) => (val() as { error: string }).error}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx
index 1d9a3698a4..b11ad6a734 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-list.tsx
@@ -7,7 +7,6 @@ import type { Session } from "@opencode-ai/sdk/v2"
import { useSDK } from "../context/sdk"
import { useToast } from "../ui/toast"
import { useKeybind } from "../context/keybind"
-import { Log } from "@/util/log"
import { DialogSessionList } from "./workspace/dialog-session-list"
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
@@ -113,9 +112,10 @@ function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promi
setCreating(type)
const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch((err) => {
- Log.Default.error("workspace creation failed", { error: err })
+ console.log(err)
return undefined
})
+ console.log(JSON.stringify(result, null, 2))
const workspace = result?.data
if (!workspace) {
setCreating(undefined)
@@ -125,7 +125,6 @@ function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) => Promi
})
return
}
- Log.Default.info("workspace created", { workspaceId: workspace.id })
await sync.workspace.sync()
await props.onSelect(workspace.id)
setCreating(undefined)
diff --git a/packages/opencode/src/cli/cmd/tui/component/logo.tsx b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
index cde2a16463..8e6208b140 100644
--- a/packages/opencode/src/cli/cmd/tui/component/logo.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
@@ -75,8 +75,8 @@ export function Logo() {
{(line, index) => (
- {renderLine(line, theme.primary, false)}
- {renderLine(logo.right[index()], theme.accent, true)}
+ {renderLine(line, theme.textMuted, false)}
+ {renderLine(logo.right[index()], theme.text, true)}
)}
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 93d7016d37..1a2eb7bfcf 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -23,7 +23,6 @@ import { useExit } from "../../context/exit"
import { Clipboard } from "../../util/clipboard"
import type { FilePart } from "@opencode-ai/sdk/v2"
import { TuiEvent } from "../../event"
-import { Log } from "@/util/log"
import { iife } from "@/util/iife"
import { Locale } from "@/util/locale"
import { formatDuration } from "@/util/format"
@@ -35,6 +34,7 @@ import { useToast } from "../../ui/toast"
import { useKV } from "../../context/kv"
import { useTextareaKeybindings } from "../textarea-keybindings"
import { DialogSkill } from "../dialog-skill"
+import { Log } from "@/util/log"
// altimate_change start - import prompt enhancement
import { enhancePrompt, isAutoEnhanceEnabled } from "@/altimate/enhance-prompt"
let enhancingInProgress = false
@@ -604,7 +604,7 @@ export function Prompt(props: PromptProps) {
})
if (res.error) {
- Log.Default.error("Creating a session failed", { error: res.error })
+ console.log("Creating a session failed:", res.error)
toast.show({
message: "Creating a session failed. Open console for more details.",
@@ -647,7 +647,6 @@ export function Prompt(props: PromptProps) {
}
}
// altimate_change end
-
// Expand pasted text inline before submitting
const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
@@ -737,7 +736,6 @@ export function Prompt(props: PromptProps) {
}
history.append({
...store.prompt,
- input: inputText,
mode: currentMode,
})
input.extmarks.clear()
diff --git a/packages/opencode/src/cli/cmd/tui/component/tips.tsx b/packages/opencode/src/cli/cmd/tui/component/tips.tsx
index a005d0b2a4..689ed066aa 100644
--- a/packages/opencode/src/cli/cmd/tui/component/tips.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/tips.tsx
@@ -54,7 +54,7 @@ const TIPS = [
"Press {highlight}Tab{/highlight} to cycle between Build and Plan agents",
"Use {highlight}/undo{/highlight} to revert the last message and file changes",
"Use {highlight}/redo{/highlight} to restore previously undone messages and file changes",
- "Run {highlight}/share{/highlight} to create a public link to your conversation at altimate-code.dev",
+ "Run {highlight}/share{/highlight} to create a public link to your conversation at altimate.ai",
"Drag and drop images into the terminal to add them as context",
"Press {highlight}Ctrl+V{/highlight} to paste images from your clipboard into the prompt",
"Press {highlight}Ctrl+X E{/highlight} or {highlight}/editor{/highlight} to compose messages in your external editor",
@@ -80,46 +80,46 @@ const TIPS = [
"Switch to {highlight}Plan{/highlight} agent to get suggestions without making actual changes",
"Use {highlight}@agent-name{/highlight} in prompts to invoke specialized subagents",
"Press {highlight}Ctrl+X Right/Left{/highlight} to cycle through parent and child sessions",
- "Create {highlight}altimate-code.json{/highlight} in project root for project-specific settings",
- "Place settings in {highlight}~/.config/altimate-code/altimate-code.json{/highlight} for global config",
+ "Create {highlight}opencode.json{/highlight} for server settings and {highlight}tui.json{/highlight} for TUI settings",
+ "Place TUI settings in {highlight}~/.config/opencode/tui.json{/highlight} for global config",
"Add {highlight}$schema{/highlight} to your config for autocomplete in your editor",
"Configure {highlight}model{/highlight} in config to set your default model",
- "Override any keybind in config via the {highlight}keybinds{/highlight} section",
+ "Override any keybind in {highlight}tui.json{/highlight} via the {highlight}keybinds{/highlight} section",
"Set any keybind to {highlight}none{/highlight} to disable it completely",
"Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section",
- "Altimate CLI auto-handles OAuth for remote MCP servers requiring auth",
- "Add {highlight}.md{/highlight} files to {highlight}.altimate-code/command/{/highlight} to define reusable custom prompts",
+ "Altimate Code auto-handles OAuth for remote MCP servers requiring auth",
+ "Add {highlight}.md{/highlight} files to {highlight}.opencode/command/{/highlight} to define reusable custom prompts",
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input",
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})",
- "Add {highlight}.md{/highlight} files to {highlight}.altimate-code/agent/{/highlight} for specialized AI personas",
+ "Add {highlight}.md{/highlight} files to {highlight}.opencode/agent/{/highlight} for specialized AI personas",
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools",
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions',
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands',
'Configure {highlight}"git push": "ask"{/highlight} to require approval before pushing',
- "Altimate CLI auto-formats files using prettier, gofmt, ruff, and more",
+ "Altimate Code auto-formats files using prettier, gofmt, ruff, and more",
'Set {highlight}"formatter": false{/highlight} in config to disable all auto-formatting',
"Define custom formatter commands with file extensions in config",
- "Altimate CLI uses LSP servers for intelligent code analysis",
- "Create {highlight}.ts{/highlight} files in {highlight}.altimate-code/tools/{/highlight} to define new LLM tools",
+ "Altimate Code uses LSP servers for intelligent code analysis",
+ "Create {highlight}.ts{/highlight} files in {highlight}.opencode/tools/{/highlight} to define new LLM tools",
"Tool definitions can invoke scripts written in Python, Go, etc",
- "Add {highlight}.ts{/highlight} files to {highlight}.altimate-code/plugin/{/highlight} for event hooks",
+ "Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugin/{/highlight} for event hooks",
"Use plugins to send OS notifications when sessions complete",
- "Create a plugin to prevent Altimate CLI from reading sensitive files",
- "Use {highlight}altimate-code run{/highlight} for non-interactive scripting",
- "Use {highlight}altimate-code --continue{/highlight} to resume the last session",
- "Use {highlight}altimate-code run -f file.ts{/highlight} to attach files via CLI",
+ "Create a plugin to prevent Altimate Code from reading sensitive files",
+ "Use {highlight}opencode run{/highlight} for non-interactive scripting",
+ "Use {highlight}opencode --continue{/highlight} to resume the last session",
+ "Use {highlight}opencode run -f file.ts{/highlight} to attach files via CLI",
"Use {highlight}--format json{/highlight} for machine-readable output in scripts",
- "Run {highlight}altimate-code serve{/highlight} for headless API access to Altimate CLI",
- "Use {highlight}altimate-code run --attach{/highlight} to connect to a running server",
- "Run {highlight}altimate-code upgrade{/highlight} to update to the latest version",
- "Run {highlight}altimate-code auth list{/highlight} to see all configured providers",
- "Run {highlight}altimate-code agent create{/highlight} for guided agent creation",
- "Use {highlight}/altimate-code{/highlight} in GitHub issues/PRs to trigger AI actions",
- "Run {highlight}altimate-code github install{/highlight} to set up the GitHub workflow",
- "Comment {highlight}/altimate-code fix this{/highlight} on issues to auto-create PRs",
+ "Run {highlight}opencode serve{/highlight} for headless API access to Altimate Code",
+ "Use {highlight}opencode run --attach{/highlight} to connect to a running server",
+ "Run {highlight}opencode upgrade{/highlight} to update to the latest version",
+ "Run {highlight}opencode auth list{/highlight} to see all configured providers",
+ "Run {highlight}opencode agent create{/highlight} for guided agent creation",
+ "Use {highlight}/opencode{/highlight} in GitHub issues/PRs to trigger AI actions",
+ "Run {highlight}opencode github install{/highlight} to set up the GitHub workflow",
+ "Comment {highlight}/opencode fix this{/highlight} on issues to auto-create PRs",
"Comment {highlight}/oc{/highlight} on PR code lines for targeted code reviews",
'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors',
- "Create JSON theme files in {highlight}.altimate-code/themes/{/highlight} directory",
+ "Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory",
"Themes support dark/light variants for both modes",
"Reference ANSI colors 0-255 in custom themes",
"Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config",
@@ -135,15 +135,15 @@ const TIPS = [
"Run {highlight}/unshare{/highlight} to remove a session from public access",
"Permission {highlight}doom_loop{/highlight} prevents infinite tool call loops",
"Permission {highlight}external_directory{/highlight} protects files outside project",
- "Run {highlight}altimate-code debug config{/highlight} to troubleshoot configuration",
+ "Run {highlight}opencode debug config{/highlight} to troubleshoot configuration",
"Use {highlight}--print-logs{/highlight} flag to see detailed logs in stderr",
"Press {highlight}Ctrl+X G{/highlight} or {highlight}/timeline{/highlight} to jump to specific messages",
"Press {highlight}Ctrl+X H{/highlight} to toggle code block visibility in messages",
"Press {highlight}Ctrl+X S{/highlight} or {highlight}/status{/highlight} to see system status info",
- "Enable {highlight}tui.scroll_acceleration{/highlight} for smooth macOS-style scrolling",
+ "Enable {highlight}scroll_acceleration{/highlight} in {highlight}tui.json{/highlight} for smooth macOS-style scrolling",
"Toggle username display in chat via command palette ({highlight}Ctrl+P{/highlight})",
- "Run {highlight}docker run -it --rm ghcr.io/altimate/altimate-code{/highlight} for containerized use",
- "Use {highlight}/connect{/highlight} with Altimate CLI Zen for curated, tested models",
+ "Run {highlight}docker run -it --rm ghcr.io/AltimateAI/altimate-code{/highlight} for containerized use",
+ "Use {highlight}/connect{/highlight} with Altimate Code Zen for curated, tested models",
"Commit your project's {highlight}AGENTS.md{/highlight} file to Git for team sharing",
"Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs",
"Run {highlight}/help{/highlight} or {highlight}Ctrl+X H{/highlight} to show the help dialog",
diff --git a/packages/opencode/src/cli/cmd/tui/context/route.tsx b/packages/opencode/src/cli/cmd/tui/context/route.tsx
index ff2ed39dae..e96cd2c3a4 100644
--- a/packages/opencode/src/cli/cmd/tui/context/route.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/route.tsx
@@ -1,7 +1,6 @@
import { createStore } from "solid-js/store"
import { createSimpleContext } from "./helper"
import type { PromptInfo } from "../component/prompt/history"
-import { Log } from "@/util/log"
export type HomeRoute = {
type: "home"
@@ -33,7 +32,7 @@ export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
return store
},
navigate(route: Route) {
- Log.Default.debug("navigate", { route })
+ console.log("navigate", route)
setStore(route)
},
}
diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
index b13453d366..f9b1367c76 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx
@@ -51,44 +51,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
queue = []
timer = undefined
last = Date.now()
-
- // altimate_change start - smooth streaming: pre-merge delta events
- // When enabled, merge consecutive delta events for the same part+field
- // to reduce store updates from N-per-part to 1-per-part per flush cycle.
- if (Flag.ALTIMATE_SMOOTH_STREAMING) {
- const merged: Event[] = []
- const deltaMap = new Map()
- for (const event of events) {
- if (event.type === "message.part.delta") {
- const props = event.properties as { messageID: string; partID: string; field: string; delta: string }
- const key = `${props.messageID}:${props.partID}:${props.field}`
- const existing = deltaMap.get(key)
- if (existing !== undefined) {
- const prev = merged[existing] as typeof event
- merged[existing] = {
- ...prev,
- properties: {
- ...prev.properties,
- delta: (prev.properties as typeof props).delta + props.delta,
- },
- } as Event
- continue
- }
- deltaMap.set(key, merged.length)
- } else {
- deltaMap.clear()
- }
- merged.push(event)
- }
- batch(() => {
- for (const event of merged) {
- emitter.emit(event.type, event)
- }
- })
- return
- }
- // altimate_change end
-
// Batch all event emissions so all store updates result in a single render
batch(() => {
for (const event of events) {
diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
index 2d3710faad..8cfb3f3246 100644
--- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx
@@ -152,7 +152,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
}
}
// altimate_change end
-
sdk.event.listen((e) => {
const event = e.details
switch (event.type) {
@@ -407,30 +406,16 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
break
}
// altimate_change end
- // altimate_change start - smooth streaming: direct path update avoids produce() proxy overhead
- if (Flag.ALTIMATE_SMOOTH_STREAMING) {
- const field = event.properties.field as keyof (typeof parts)[number]
- const existing = parts[result.index][field] as string | undefined
- setStore(
- "part",
- event.properties.messageID,
- result.index,
- field as any,
- ((existing ?? "") + event.properties.delta) as any,
- )
- } else {
- setStore(
- "part",
- event.properties.messageID,
- produce((draft) => {
- const part = draft[result.index]
- const field = event.properties.field as keyof typeof part
- const existing = part[field] as string | undefined
- ;(part[field] as string) = (existing ?? "") + event.properties.delta
- }),
- )
- }
- // altimate_change end
+ setStore(
+ "part",
+ event.properties.messageID,
+ produce((draft) => {
+ const part = draft[result.index]
+ const field = event.properties.field as keyof typeof part
+ const existing = part[field] as string | undefined
+ ;(part[field] as string) = (existing ?? "") + event.properties.delta
+ }),
+ )
break
}
@@ -472,7 +457,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
const args = useArgs()
async function bootstrap() {
- Log.Default.debug("bootstrapping")
+ console.log("bootstrapping")
const start = Date.now() - 30 * 24 * 60 * 60 * 1000
const sessionListPromise = sdk.client.session
.list({ start: start })
diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
index b0d0e09240..2320c08ccc 100644
--- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
@@ -1,5 +1,4 @@
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
-import { Log } from "@/util/log"
import path from "path"
import { createEffect, createMemo, onMount } from "solid-js"
import { createSimpleContext } from "./helper"
@@ -25,7 +24,6 @@ import nightowl from "./theme/nightowl.json" with { type: "json" }
import nord from "./theme/nord.json" with { type: "json" }
import osakaJade from "./theme/osaka-jade.json" with { type: "json" }
import onedark from "./theme/one-dark.json" with { type: "json" }
-import altimateCode from "./theme/altimate-code.json" with { type: "json" }
import opencode from "./theme/opencode.json" with { type: "json" }
import orng from "./theme/orng.json" with { type: "json" }
import lucentOrng from "./theme/lucent-orng.json" with { type: "json" }
@@ -162,7 +160,6 @@ export const DEFAULT_THEMES: Record = {
nord,
["one-dark"]: onedark,
["osaka-jade"]: osakaJade,
- ["altimate-code"]: altimateCode,
opencode,
orng,
["lucent-orng"]: lucentOrng,
@@ -288,7 +285,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
const [store, setStore] = createStore({
themes: DEFAULT_THEMES,
mode: kv.get("theme_mode", props.mode),
- active: (config.theme ?? kv.get("theme", "altimate-code")) as string,
+ active: (config.theme ?? kv.get("theme", "opencode")) as string,
ready: false,
})
@@ -308,7 +305,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
)
})
.catch(() => {
- setStore("active", "altimate-code")
+ setStore("active", "opencode")
})
.finally(() => {
if (store.active !== "system") {
@@ -320,18 +317,18 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
onMount(init)
function resolveSystemTheme() {
- Log.Default.debug("resolving system theme")
+ console.log("resolveSystemTheme")
renderer
.getPalette({
size: 16,
})
.then((colors) => {
- Log.Default.debug("system theme palette", { palette: colors.palette })
+ console.log(colors.palette)
if (!colors.palette[0]) {
if (store.active === "system") {
setStore(
produce((draft) => {
- draft.active = "altimate-code"
+ draft.active = "opencode"
draft.ready = true
}),
)
@@ -356,7 +353,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
})
const values = createMemo(() => {
- return resolveTheme(store.themes[store.active] ?? store.themes["altimate-code"], store.mode)
+ return resolveTheme(store.themes[store.active] ?? store.themes.opencode, store.mode)
})
const syntax = createMemo(() => generateSyntax(values()))
@@ -400,7 +397,7 @@ async function getCustomThemes() {
Global.Path.config,
...(await Array.fromAsync(
Filesystem.up({
- targets: [".altimate-code"],
+ targets: [".opencode"],
start: process.cwd(),
}),
)),
diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
index 865553899f..2e77c2b3d1 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx
@@ -158,6 +158,7 @@ export function Home() {
{/* altimate_change start — upgrade indicator in home footer */}
{Installation.VERSION}} />
{/* altimate_change end */}
+ {Installation.VERSION}
>
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
index c7225214c0..1ecaa7bdd9 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
@@ -5,12 +5,12 @@ import { useDirectory } from "../../context/directory"
import { useConnected } from "../../component/dialog-model"
import { createStore } from "solid-js/store"
import { useRoute } from "../../context/route"
-// altimate_change start - yolo mode visual indicator
-import { Flag } from "@/flag/flag"
-// altimate_change end
// altimate_change start — upgrade indicator import
import { UpgradeIndicator } from "../../component/upgrade-indicator"
// altimate_change end
+// altimate_change start - yolo mode visual indicator
+import { Flag } from "@/flag/flag"
+// altimate_change end
export function Footer() {
const { theme } = useTheme()
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 2564660dea..d06b0c53fa 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -13,7 +13,6 @@ import {
useContext,
} from "solid-js"
import { Dynamic } from "solid-js/web"
-import { Log } from "@/util/log"
import path from "path"
import { useRoute, useRouteData } from "@tui/context/route"
import { useSync } from "@tui/context/sync"
@@ -196,7 +195,7 @@ export function Session() {
if (scroll) scroll.scrollBy(100_000)
})
.catch((e) => {
- Log.Default.error("session sync failed", { error: e })
+ console.error(e)
toast.show({
message: `Session not found: ${route.sessionID}`,
variant: "error",
@@ -254,7 +253,7 @@ export function Session() {
`${logo[3] ?? ""}`,
``,
` ${weak("Session")}${UI.Style.TEXT_NORMAL_BOLD}${title}${UI.Style.TEXT_NORMAL}`,
- ` ${weak("Continue")}${UI.Style.TEXT_NORMAL_BOLD}altimate -s ${session()?.id}${UI.Style.TEXT_NORMAL}`,
+ ` ${weak("Continue")}${UI.Style.TEXT_NORMAL_BOLD}opencode -s ${session()?.id}${UI.Style.TEXT_NORMAL}`,
``,
].join("\n"),
)
@@ -1034,6 +1033,17 @@ export function Session() {
// snap to bottom when session changes
createEffect(on(() => route.sessionID, toBottom))
+ // altimate_change start - calm mode: cap content width for readability, respect small screens
+ const cappedWidth = createMemo(() => {
+ const cap = Flag.ALTIMATE_CONTENT_MAX_WIDTH
+ if (!cap) return undefined
+ const available = contentWidth()
+ // +3 accounts for paddingLeft on this box
+ const desired = cap + 3
+ // On small screens, don't constrain — let it use full available width
+ return available <= desired ? undefined : desired
+ })
+ // altimate_change end
return (
-
+
- This will allow the following patterns until Altimate CLI is restarted
+ This will allow the following patterns until Altimate Code is restarted
{(pattern) => (
@@ -501,7 +501,7 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
Reject permission
- Tell Altimate CLI what to do differently
+ Tell Altimate Code what to do differently
- Altimate CLI includes free models so you can start immediately.
+ Altimate Code includes free models so you can start immediately.
Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc
diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts
index 1fa1540fd8..6e787c7afd 100644
--- a/packages/opencode/src/cli/cmd/tui/thread.ts
+++ b/packages/opencode/src/cli/cmd/tui/thread.ts
@@ -189,7 +189,7 @@ export const TuiThreadCommand = cmd({
events: undefined,
}
: {
- url: "http://altimate-code.internal",
+ url: "http://opencode.internal",
fetch: createWorkerFetch(client),
events: createEventSource(client),
}
diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
index f441783718..85e13d3133 100644
--- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
+++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
@@ -7,7 +7,6 @@ import fs from "fs/promises"
import { Filesystem } from "../../../../util/filesystem"
import { Process } from "../../../../util/process"
import { which } from "../../../../util/which"
-import { Log } from "../../../../util/log"
/**
* Writes text to clipboard via OSC 52 escape sequence.
@@ -96,7 +95,7 @@ export namespace Clipboard {
const os = platform()
if (os === "darwin" && which("osascript")) {
- Log.Default.debug("clipboard: using osascript")
+ console.log("clipboard: using osascript")
return async (text: string) => {
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
await Process.run(["osascript", "-e", `set the clipboard to "${escaped}"`], { nothrow: true })
@@ -105,7 +104,7 @@ export namespace Clipboard {
if (os === "linux") {
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
- Log.Default.debug("clipboard: using wl-copy")
+ console.log("clipboard: using wl-copy")
return async (text: string) => {
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
if (!proc.stdin) return
@@ -115,7 +114,7 @@ export namespace Clipboard {
}
}
if (which("xclip")) {
- Log.Default.debug("clipboard: using xclip")
+ console.log("clipboard: using xclip")
return async (text: string) => {
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
stdin: "pipe",
@@ -129,7 +128,7 @@ export namespace Clipboard {
}
}
if (which("xsel")) {
- Log.Default.debug("clipboard: using xsel")
+ console.log("clipboard: using xsel")
return async (text: string) => {
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
stdin: "pipe",
@@ -145,7 +144,7 @@ export namespace Clipboard {
}
if (os === "win32") {
- Log.Default.debug("clipboard: using powershell")
+ console.log("clipboard: using powershell")
return async (text: string) => {
// Pipe via stdin to avoid PowerShell string interpolation ($env:FOO, $(), etc.)
const proc = Process.spawn(
@@ -170,7 +169,7 @@ export namespace Clipboard {
}
}
- Log.Default.debug("clipboard: no native support")
+ console.log("clipboard: no native support")
return async (text: string) => {
await clipboardy.write(text).catch(() => {})
}
diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts
index 0cd8135f4e..9712675783 100644
--- a/packages/opencode/src/cli/cmd/tui/worker.ts
+++ b/packages/opencode/src/cli/cmd/tui/worker.ts
@@ -101,7 +101,6 @@ function getOrCreateTracer(sessionID: string): Tracer | null {
}
}
// altimate_change end
-
const startEventStream = (input: { directory: string; workspaceID?: string }) => {
if (eventStream.abort) eventStream.abort.abort()
const abort = new AbortController()
@@ -116,7 +115,7 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
}) as typeof globalThis.fetch
const sdk = createOpencodeClient({
- baseUrl: "http://altimate-code.internal",
+ baseUrl: "http://opencode.internal",
directory: input.directory,
experimental_workspaceID: input.workspaceID,
fetch: fetchFn,
@@ -124,8 +123,6 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
})
;(async () => {
- // Load tracing config once before processing events
- await loadTracingConfig()
while (!signal.aborted) {
const events = await Promise.resolve(
sdk.event.subscribe(
@@ -215,7 +212,6 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
// Tracing must never interrupt event forwarding
}
// altimate_change end
-
Rpc.emit("event", event as Event)
}
diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts
index 60f7bd5c72..4438fa3b84 100644
--- a/packages/opencode/src/cli/cmd/upgrade.ts
+++ b/packages/opencode/src/cli/cmd/upgrade.ts
@@ -2,11 +2,10 @@ import type { Argv } from "yargs"
import { UI } from "../ui"
import * as prompts from "@clack/prompts"
import { Installation } from "../../installation"
-import { extractChangelog } from "../changelog"
export const UpgradeCommand = {
command: "upgrade [target]",
- describe: "upgrade altimate to the latest or a specific version",
+ describe: "upgrade opencode to the latest or a specific version",
builder: (yargs: Argv) => {
return yargs
.positional("target", {
@@ -28,7 +27,7 @@ export const UpgradeCommand = {
const detectedMethod = await Installation.method()
const method = (args.method as Installation.Method) ?? detectedMethod
if (method === "unknown") {
- prompts.log.error(`altimate is installed to ${process.execPath} and may be managed by a package manager`)
+ prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`)
const install = await prompts.select({
message: "Install anyways?",
options: [
@@ -46,7 +45,7 @@ export const UpgradeCommand = {
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
if (Installation.VERSION === target) {
- prompts.log.warn(`altimate upgrade skipped: ${target} is already installed`)
+ prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
prompts.outro("Done")
return
}
@@ -69,12 +68,6 @@ export const UpgradeCommand = {
return
}
spinner.stop("Upgrade complete")
-
- const changelog = extractChangelog(Installation.VERSION, target)
- if (changelog) {
- prompts.log.info("What's new:\n\n" + changelog)
- }
-
prompts.outro("Done")
},
}
diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts
index c08c785e76..0fe056f21f 100644
--- a/packages/opencode/src/cli/cmd/web.ts
+++ b/packages/opencode/src/cli/cmd/web.ts
@@ -31,7 +31,7 @@ function getNetworkIPs() {
export const WebCommand = cmd({
command: "web",
builder: (yargs) => withNetworkOptions(yargs),
- describe: "start altimate server and open web interface",
+ describe: "start opencode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts
index ffb813b677..d7120aa5e9 100644
--- a/packages/opencode/src/cli/error.ts
+++ b/packages/opencode/src/cli/error.ts
@@ -6,14 +6,14 @@ import { UI } from "./ui"
export function FormatError(input: unknown) {
if (MCP.Failed.isInstance(input))
- return `MCP server "${input.data.name}" failed. Note, altimate-code does not support MCP authentication yet.`
+ return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
if (Provider.ModelNotFoundError.isInstance(input)) {
const { providerID, modelID, suggestions } = input.data
return [
`Model not found: ${providerID}/${modelID}`,
...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
- `Try: \`altimate models\` to list available models`,
- `Or check your config (altimate-code.json) provider/model names`,
+ `Try: \`opencode models\` to list available models`,
+ `Or check your config (opencode.json) provider/model names`,
].join("\n")
}
if (Provider.InitError.isInstance(input)) {
diff --git a/packages/opencode/src/cli/logo.ts b/packages/opencode/src/cli/logo.ts
index 6b6333b9b4..44fb93c15b 100644
--- a/packages/opencode/src/cli/logo.ts
+++ b/packages/opencode/src/cli/logo.ts
@@ -1,16 +1,6 @@
export const logo = {
- left: [
- " ",
- "█▀▀█ █ ████ ██ █▄ ▄█ █▀▀█ ████ █▀▀▀",
- "█^^█ █___ _██_ ██ █_^_█ █^^█ _██_ █^^^",
- "▀ ▀ ▀▀▀▀ ~▀▀~ ▀▀ ▀~~~▀ ▀ ▀ ~▀▀~ ▀▀▀▀",
- ],
- right: [
- " ",
- "█▀▀▀ █▀▀█ █▀▀█ █▀▀▀",
- "█___ █__█ █__█ █^^^",
- "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀",
- ],
+ left: [" ", "█▀▀█ █▀▀█ █▀▀█ █▀▀▄", "█__█ █__█ █^^^ █__█", "▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀"],
+ right: [" ▄ ", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
}
export const marks = "_^~"
diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts
index 28af2f9b43..9caff2cda6 100644
--- a/packages/opencode/src/command/index.ts
+++ b/packages/opencode/src/command/index.ts
@@ -5,16 +5,17 @@ import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Identifier } from "../id/id"
import PROMPT_INITIALIZE from "./template/initialize.txt"
+// altimate_change start — discover and feedback commands
import PROMPT_DISCOVER from "./template/discover.txt"
-import PROMPT_REVIEW from "./template/review.txt"
import PROMPT_FEEDBACK from "./template/feedback.txt"
+// altimate_change end
+import PROMPT_REVIEW from "./template/review.txt"
// altimate_change start — configure commands for external AI CLIs
import PROMPT_CONFIGURE_CLAUDE from "./template/configure-claude.txt"
import PROMPT_CONFIGURE_CODEX from "./template/configure-codex.txt"
// altimate_change end
import { MCP } from "../mcp"
import { Skill } from "../skill"
-import { Log } from "../util/log"
export namespace Command {
export const Event = {
@@ -61,10 +62,12 @@ export namespace Command {
export const Default = {
INIT: "init",
+ // altimate_change start — discover and feedback commands
DISCOVER: "discover",
+ // altimate_change end
REVIEW: "review",
- FEEDBACK: "feedback",
// altimate_change start
+ FEEDBACK: "feedback",
CONFIGURE_CLAUDE: "configure-claude",
CONFIGURE_CODEX: "configure-codex",
// altimate_change end
@@ -83,6 +86,7 @@ export namespace Command {
},
hints: hints(PROMPT_INITIALIZE),
},
+ // altimate_change start — discover command
[Default.DISCOVER]: {
name: Default.DISCOVER,
description: "scan data stack and set up connections",
@@ -92,25 +96,7 @@ export namespace Command {
},
hints: hints(PROMPT_DISCOVER),
},
- [Default.REVIEW]: {
- name: Default.REVIEW,
- description: "review changes [commit|branch|pr], defaults to uncommitted",
- source: "command",
- get template() {
- return PROMPT_REVIEW.replace("${path}", Instance.worktree)
- },
- subtask: true,
- hints: hints(PROMPT_REVIEW),
- },
- [Default.FEEDBACK]: {
- name: Default.FEEDBACK,
- description: "submit product feedback as a GitHub issue",
- source: "command",
- get template() {
- return PROMPT_FEEDBACK
- },
- hints: hints(PROMPT_FEEDBACK),
- },
+ // altimate_change end
// altimate_change start — configure commands for external AI CLIs
[Default.CONFIGURE_CLAUDE]: {
name: Default.CONFIGURE_CLAUDE,
@@ -131,6 +117,27 @@ export namespace Command {
hints: hints(PROMPT_CONFIGURE_CODEX),
},
// altimate_change end
+ [Default.REVIEW]: {
+ name: Default.REVIEW,
+ description: "review changes [commit|branch|pr], defaults to uncommitted",
+ source: "command",
+ get template() {
+ return PROMPT_REVIEW.replace("${path}", Instance.worktree)
+ },
+ subtask: true,
+ hints: hints(PROMPT_REVIEW),
+ },
+ // altimate_change start — feedback command
+ [Default.FEEDBACK]: {
+ name: Default.FEEDBACK,
+ description: "submit product feedback as a GitHub issue",
+ source: "command",
+ get template() {
+ return PROMPT_FEEDBACK
+ },
+ hints: hints(PROMPT_FEEDBACK),
+ },
+ // altimate_change end
}
for (const [name, command] of Object.entries(cfg.command ?? {})) {
@@ -147,58 +154,46 @@ export namespace Command {
hints: hints(command.template),
}
}
- // MCP and skill loading must not prevent default commands from being served.
- // Wrap each in try/catch so init, discover, review are always available.
- // Note: MCP prompts can overwrite defaults (by name), but skills cannot
- // (the `if (result[skill.name]) continue` guard preserves defaults over skills).
- try {
- for (const [name, prompt] of Object.entries(await MCP.prompts())) {
- result[name] = {
- name,
- source: "mcp",
- description: prompt.description,
- get template() {
- return MCP.getPrompt(
+ for (const [name, prompt] of Object.entries(await MCP.prompts())) {
+ result[name] = {
+ name,
+ source: "mcp",
+ description: prompt.description,
+ get template() {
+ // since a getter can't be async we need to manually return a promise here
+ return new Promise(async (resolve, reject) => {
+ const template = await MCP.getPrompt(
prompt.client,
prompt.name,
prompt.arguments
- ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`]))
+ ? // substitute each argument with $1, $2, etc.
+ Object.fromEntries(prompt.arguments?.map((argument, i) => [argument.name, `$${i + 1}`]))
: {},
- ).then((template) => {
- if (!template) throw new Error(`Failed to load MCP prompt: ${prompt.name}`)
- return template.messages
+ ).catch(reject)
+ resolve(
+ template?.messages
.map((message) => (message.content.type === "text" ? message.content.text : ""))
- .join("\n")
- })
- },
- hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
- }
+ .join("\n") || "",
+ )
+ })
+ },
+ hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
}
- } catch (e) {
- Log.Default.warn("MCP prompt loading failed, continuing with defaults", {
- error: e instanceof Error ? e.message : String(e),
- })
}
// Add skills as invokable commands
- try {
- for (const skill of await Skill.all()) {
- // Skip if a command with this name already exists
- if (result[skill.name]) continue
- result[skill.name] = {
- name: skill.name,
- description: skill.description,
- source: "skill",
- get template() {
- return skill.content
- },
- hints: [],
- }
+ for (const skill of await Skill.all()) {
+ // Skip if a command with this name already exists
+ if (result[skill.name]) continue
+ result[skill.name] = {
+ name: skill.name,
+ description: skill.description,
+ source: "skill",
+ get template() {
+ return skill.content
+ },
+ hints: [],
}
- } catch (e) {
- Log.Default.warn("Skill loading failed, continuing with defaults", {
- error: e instanceof Error ? e.message : String(e),
- })
}
return result
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 12e3730e43..278f766ce2 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -281,7 +281,6 @@ export namespace Config {
}
}
// altimate_change end
-
return {
config: result,
directories,
@@ -1285,18 +1284,18 @@ export namespace Config {
"Automatically enhance prompts with AI before sending (default: false). Uses a small model to rewrite rough prompts into clearer versions.",
),
// altimate_change end
+ // altimate_change start - auto MCP discovery toggle
+ auto_mcp_discovery: z
+ .boolean()
+ .optional()
+ .describe("Auto-discover MCP servers from VS Code, Claude Code, Copilot, and Gemini configs at startup. Set to false to disable."),
+ // altimate_change end
// altimate_change start - env fingerprint skill selection toggle
env_fingerprint_skill_selection: z
.boolean()
.optional()
.describe("Use environment fingerprint to select relevant skills once per session (default: false). Set to true to enable LLM-based skill filtering."),
// altimate_change end
- // altimate_change start - auto MCP discovery toggle
- auto_mcp_discovery: z
- .boolean()
- .default(true)
- .describe("Auto-discover MCP servers from VS Code, Claude Code, Copilot, and Gemini configs at startup. Set to false to disable."),
- // altimate_change end
})
.optional(),
})
diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts
index 1629df5179..73f87dbd13 100644
--- a/packages/opencode/src/config/paths.ts
+++ b/packages/opencode/src/config/paths.ts
@@ -28,7 +28,7 @@ export namespace ConfigPaths {
...(!Flag.OPENCODE_DISABLE_PROJECT_CONFIG
? await Array.fromAsync(
Filesystem.up({
- targets: configTargets,
+ targets: [".opencode"],
start: directory,
stop: worktree,
}),
@@ -36,7 +36,7 @@ export namespace ConfigPaths {
: []),
...(await Array.fromAsync(
Filesystem.up({
- targets: configTargets,
+ targets: [".opencode"],
start: Global.Path.home,
stop: Global.Path.home,
}),
diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts
index 92252c5327..f848909501 100644
--- a/packages/opencode/src/control-plane/adaptors/worktree.ts
+++ b/packages/opencode/src/control-plane/adaptors/worktree.ts
@@ -36,7 +36,7 @@ export const WorktreeAdaptor: Adaptor = {
async fetch(info, input: RequestInfo | URL, init?: RequestInit) {
const config = Config.parse(info)
const { WorkspaceServer } = await import("../workspace-server/server")
- const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://altimate-code.internal")
+ const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://opencode.internal")
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined))
headers.set("x-opencode-directory", config.directory)
diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts
index 23acff7337..4aec46befa 100644
--- a/packages/opencode/src/effect/runtime.ts
+++ b/packages/opencode/src/effect/runtime.ts
@@ -1,5 +1,9 @@
import { Layer, ManagedRuntime } from "effect"
import { AccountService } from "@/account/service"
import { AuthService } from "@/auth/service"
+import { PermissionService } from "@/permission/service"
+import { QuestionService } from "@/question/service"
-export const runtime = ManagedRuntime.make(Layer.mergeAll(AccountService.defaultLayer, AuthService.defaultLayer))
+export const runtime = ManagedRuntime.make(
+ Layer.mergeAll(AccountService.defaultLayer, AuthService.defaultLayer, PermissionService.layer, QuestionService.layer),
+)
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index a2e53b83f6..e03fc8a9f3 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -500,6 +500,8 @@ export namespace File {
const project = Instance.project
const full = path.join(Instance.directory, file)
+ // TODO: Filesystem.contains is lexical only - symlinks inside the project can escape.
+ // TODO: On Windows, cross-drive paths bypass this check. Consider realpath canonicalization.
if (!Instance.containsPath(full)) {
throw new Error(`Access denied: path escapes project directory`)
}
@@ -578,6 +580,8 @@ export namespace File {
}
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
+ // TODO: Filesystem.contains is lexical only - symlinks inside the project can escape.
+ // TODO: On Windows, cross-drive paths bypass this check. Consider realpath canonicalization.
if (!Instance.containsPath(resolved)) {
throw new Error(`Access denied: path escapes project directory`)
}
diff --git a/packages/opencode/src/file/protected.ts b/packages/opencode/src/file/protected.ts
index f33082dc60..03a48f31c5 100644
--- a/packages/opencode/src/file/protected.ts
+++ b/packages/opencode/src/file/protected.ts
@@ -37,6 +37,7 @@ const DARWIN_ROOT = ["/.DocumentRevisions-V100", "/.Spotlight-V100", "/.Trashes"
const WIN32_HOME = ["AppData", "Downloads", "Desktop", "Documents", "Pictures", "Music", "Videos", "OneDrive"]
+// altimate_change start — sensitive file/directory detection for write protection
/**
* Directories and file patterns that should require explicit permission before
* write operations, even when they are located inside the project boundary.
@@ -76,6 +77,7 @@ const SENSITIVE_EXTENSIONS = [".pem", ".key", ".p12", ".pfx"]
/** Whether the current platform uses case-insensitive filesystem by default. */
const CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32"
+// altimate_change end
export namespace Protected {
/** Directory basenames to skip when scanning the home directory. */
@@ -97,6 +99,7 @@ export namespace Protected {
return []
}
+ // altimate_change start — sensitive write detection
/**
* Check if a file path targets a sensitive directory or file that should
* require explicit user permission before modification, even inside the project.
@@ -136,4 +139,5 @@ export namespace Protected {
return undefined
}
+ // altimate_change end
}
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index a94b2cc63e..d50d86c61a 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -3,6 +3,15 @@ function truthy(key: string) {
return value === "true" || value === "1"
}
+// altimate_change start - ALTIMATE_CLI_CLIENT with OPENCODE_CLIENT fallback
+Object.defineProperty(Flag, "ALTIMATE_CLI_CLIENT", {
+ get() {
+ return process.env["ALTIMATE_CLI_CLIENT"] ?? process.env["OPENCODE_CLIENT"] ?? "cli"
+ },
+ enumerable: true,
+ configurable: false,
+})
+// altimate_change end
function falsy(key: string) {
const value = process.env[key]?.toLowerCase()
return value === "false" || value === "0"
@@ -17,7 +26,6 @@ function altEnv(altKey: string, openKey: string) {
return process.env[altKey] ?? process.env[openKey]
}
// altimate_change end
-
export namespace Flag {
// altimate_change start - ALTIMATE_CLI_CLIENT flag with OPENCODE_CLIENT fallback
export declare const ALTIMATE_CLI_CLIENT: string
@@ -30,6 +38,9 @@ export namespace Flag {
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
+ // altimate_change start - opt-out for AI Teammate training system
+ export const ALTIMATE_DISABLE_TRAINING = altTruthy("ALTIMATE_DISABLE_TRAINING", "OPENCODE_DISABLE_TRAINING")
+ // altimate_change end
// altimate_change start - global opt-out for Altimate Memory
export const ALTIMATE_DISABLE_MEMORY = altTruthy("ALTIMATE_DISABLE_MEMORY", "OPENCODE_DISABLE_MEMORY")
// altimate_change end
@@ -41,9 +52,6 @@ export namespace Flag {
// because --yolo CLI flag sets the env var in middleware after module load)
export declare const ALTIMATE_CLI_YOLO: boolean
// altimate_change end
- // altimate_change start - opt-out for AI Teammate training system
- export const ALTIMATE_DISABLE_TRAINING = altTruthy("ALTIMATE_DISABLE_TRAINING", "OPENCODE_DISABLE_TRAINING")
- // altimate_change end
export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
@@ -123,6 +131,22 @@ Object.defineProperty(Flag, "OPENCODE_DISABLE_PROJECT_CONFIG", {
configurable: false,
})
+// altimate_change start - yolo mode: dynamic getter (set at runtime via --yolo flag)
+// ALTIMATE_CLI_YOLO is authoritative when defined; only falls back to OPENCODE_YOLO when undefined
+Object.defineProperty(Flag, "ALTIMATE_CLI_YOLO", {
+ get() {
+ const alt = process.env["ALTIMATE_CLI_YOLO"]
+ if (alt !== undefined) {
+ const v = alt.toLowerCase()
+ return v === "true" || v === "1"
+ }
+ const oc = process.env["OPENCODE_YOLO"]?.toLowerCase()
+ return oc === "true" || oc === "1"
+ },
+ enumerable: true,
+ configurable: false,
+})
+// altimate_change end
// Dynamic getter for OPENCODE_TUI_CONFIG
// This must be evaluated at access time, not module load time,
// because tests and external tooling may set this env var at runtime
@@ -155,30 +179,3 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", {
enumerable: true,
configurable: false,
})
-
-// altimate_change start - yolo mode: dynamic getter (set at runtime via --yolo flag)
-// ALTIMATE_CLI_YOLO is authoritative when defined; only falls back to OPENCODE_YOLO when undefined
-Object.defineProperty(Flag, "ALTIMATE_CLI_YOLO", {
- get() {
- const alt = process.env["ALTIMATE_CLI_YOLO"]
- if (alt !== undefined) {
- const v = alt.toLowerCase()
- return v === "true" || v === "1"
- }
- const oc = process.env["OPENCODE_YOLO"]?.toLowerCase()
- return oc === "true" || oc === "1"
- },
- enumerable: true,
- configurable: false,
-})
-// altimate_change end
-
-// altimate_change start - ALTIMATE_CLI_CLIENT with OPENCODE_CLIENT fallback
-Object.defineProperty(Flag, "ALTIMATE_CLI_CLIENT", {
- get() {
- return process.env["ALTIMATE_CLI_CLIENT"] ?? process.env["OPENCODE_CLIENT"] ?? "cli"
- },
- enumerable: true,
- configurable: false,
-})
-// altimate_change end
diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts
index 8dc6f48f1d..5d86fb89dd 100644
--- a/packages/opencode/src/installation/index.ts
+++ b/packages/opencode/src/installation/index.ts
@@ -127,6 +127,10 @@ export namespace Installation {
command: () => text(["brew", "list", "--formula", "altimate-code"]),
},
// altimate_change end
+ {
+ name: "brew" as const,
+ command: () => text(["brew", "list", "--formula", "opencode"]),
+ },
{
name: "scoop" as const,
command: () => text(["scoop", "list", "opencode"]),
@@ -183,13 +187,13 @@ export namespace Installation {
result = await upgradeCurl(target)
break
case "npm":
- result = await Process.run(["npm", "install", "-g", `@altimateai/altimate-code@${target}`], { nothrow: true })
+ result = await Process.run(["npm", "install", "-g", `opencode-ai@${target}`], { nothrow: true })
break
case "pnpm":
- result = await Process.run(["pnpm", "install", "-g", `@altimateai/altimate-code@${target}`], { nothrow: true })
+ result = await Process.run(["pnpm", "install", "-g", `opencode-ai@${target}`], { nothrow: true })
break
case "bun":
- result = await Process.run(["bun", "install", "-g", `@altimateai/altimate-code@${target}`], { nothrow: true })
+ result = await Process.run(["bun", "install", "-g", `opencode-ai@${target}`], { nothrow: true })
break
case "brew": {
const formula = await getBrewFormula()
@@ -267,6 +271,19 @@ export namespace Installation {
status: "success",
})
// altimate_change end
+ if (!result || result.code !== 0) {
+ const stderr =
+ method === "choco" ? "not running from an elevated command shell" : result?.stderr.toString("utf8") || ""
+ throw new UpgradeFailedError({
+ stderr: stderr,
+ })
+ }
+ log.info("upgraded", {
+ method,
+ target,
+ stdout: result.stdout.toString(),
+ stderr: result.stderr.toString(),
+ })
await Process.text([process.execPath, "--version"], { nothrow: true })
}
@@ -274,7 +291,7 @@ export namespace Installation {
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION.trim().replace(/^v/, "") : "local"
// altimate_change end
export const CHANNEL = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local"
- export const USER_AGENT = `altimate-code/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
+ export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
export async function latest(installMethod?: Method) {
const detectedMethod = installMethod || (await method())
diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts
index 4d54a9ef94..c2430822b2 100644
--- a/packages/opencode/src/mcp/index.ts
+++ b/packages/opencode/src/mcp/index.ts
@@ -23,18 +23,11 @@ import { BusEvent } from "../bus/bus-event"
import { Bus } from "@/bus"
import { TuiEvent } from "@/cli/cmd/tui/event"
import open from "open"
-import { Telemetry } from "@/telemetry"
export namespace MCP {
const log = Log.create({ service: "mcp" })
const DEFAULT_TIMEOUT = 30_000
- const registeredMcpTools = new Set()
-
- export function isMcpTool(name: string): boolean {
- return registeredMcpTools.has(name)
- }
-
export const Resource = z
.object({
name: z.string(),
@@ -167,23 +160,34 @@ export namespace MCP {
return typeof entry === "object" && entry !== null && "type" in entry
}
+ async function descendants(pid: number): Promise {
+ if (process.platform === "win32") return []
+ const pids: number[] = []
+ const queue = [pid]
+ while (queue.length > 0) {
+ const current = queue.shift()!
+ const proc = Bun.spawn(["pgrep", "-P", String(current)], { stdout: "pipe", stderr: "pipe" })
+ const [code, out] = await Promise.all([proc.exited, new Response(proc.stdout).text()]).catch(
+ () => [-1, ""] as const,
+ )
+ if (code !== 0) continue
+ for (const tok of out.trim().split(/\s+/)) {
+ const cpid = parseInt(tok, 10)
+ if (!isNaN(cpid) && pids.indexOf(cpid) === -1) {
+ pids.push(cpid)
+ queue.push(cpid)
+ }
+ }
+ }
+ return pids
+ }
+
const state = Instance.state(
async () => {
const cfg = await Config.get()
const config = cfg.mcp ?? {}
const clients: Record = {}
const status: Record = {}
- const transports: Record = {}
-
- // altimate_change start — auto-discover MCP servers from external AI tool configs
- let discoveryResult: { serverNames: string[]; sources: string[] } | null = null
- try {
- const { consumeDiscoveryResult } = await import("./discover")
- discoveryResult = consumeDiscoveryResult()
- } catch {
- // Discovery module not loaded — skip
- }
- // altimate_change end
await Promise.all(
Object.entries(config).map(async ([key, mcp]) => {
@@ -198,21 +202,27 @@ export namespace MCP {
return
}
- const result = await create(key, mcp).catch((e) => {
- log.warn("failed to initialize MCP server", { key, error: e instanceof Error ? e.message : String(e) })
- return undefined
- })
+ const result = await create(key, mcp).catch(() => undefined)
if (!result) return
status[key] = result.status
if (result.mcpClient) {
clients[key] = result.mcpClient
- if (result.transport) transports[key] = result.transport
}
}),
)
+ // altimate_change start — auto-discover MCP servers from external AI tool configs
+ let discoveryResult: { serverNames: string[]; sources: string[] } | null = null
+ try {
+ const { consumeDiscoveryResult } = await import("./discover")
+ discoveryResult = consumeDiscoveryResult()
+ } catch {
+ // Discovery module not loaded — skip
+ }
+ // altimate_change end
+
// altimate_change start — show discovery toast after MCP connections complete
if (discoveryResult) {
const message = `Discovered ${discoveryResult.serverNames.length} new MCP server(s): ${discoveryResult.serverNames.join(", ")}. Run /discover-and-add-mcps to enable and add them.`
@@ -222,24 +232,41 @@ export namespace MCP {
variant: "info",
duration: 8000,
}).catch(() => {})
- Telemetry.track({
- type: "mcp_discovery",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_count: discoveryResult.serverNames.length,
- server_names: discoveryResult.serverNames,
- sources: discoveryResult.sources,
- })
+ try {
+ const { Telemetry } = await import("../altimate/telemetry")
+ Telemetry.track({
+ type: "mcp_discovery",
+ timestamp: Date.now(),
+ session_id: Telemetry.getContext().sessionId,
+ server_count: discoveryResult.serverNames.length,
+ server_names: discoveryResult.serverNames,
+ sources: discoveryResult.sources,
+ })
+ } catch {}
}
// altimate_change end
return {
status,
clients,
- transports,
}
},
async (state) => {
+ // The MCP SDK only signals the direct child process on close.
+ // Servers like chrome-devtools-mcp spawn grandchild processes
+ // (e.g. Chrome) that the SDK never reaches, leaving them orphaned.
+ // Kill the full descendant tree first so the server exits promptly
+ // and no processes are left behind.
+ for (const client of Object.values(state.clients)) {
+ const pid = (client.transport as any)?.pid
+ if (typeof pid !== "number") continue
+ for (const dpid of await descendants(pid)) {
+ try {
+ process.kill(dpid, "SIGTERM")
+ } catch {}
+ }
+ }
+
await Promise.all(
Object.values(state.clients).map((client) =>
client.close().catch((error) => {
@@ -255,7 +282,7 @@ export namespace MCP {
// Helper function to fetch prompts for a specific client
async function fetchPromptsForClient(clientName: string, client: Client) {
- const prompts = await withTimeout(client.listPrompts(), DEFAULT_TIMEOUT).catch((e) => {
+ const prompts = await client.listPrompts().catch((e) => {
log.error("failed to get prompts", { clientName, error: e.message })
return undefined
})
@@ -277,8 +304,8 @@ export namespace MCP {
}
async function fetchResourcesForClient(clientName: string, client: Client) {
- const resources = await withTimeout(client.listResources(), DEFAULT_TIMEOUT).catch((e) => {
- log.error("failed to get resources", { clientName, error: e.message })
+ const resources = await client.listResources().catch((e) => {
+ log.error("failed to get prompts", { clientName, error: e.message })
return undefined
})
@@ -307,14 +334,12 @@ export namespace MCP {
error: "unknown error",
}
s.status[name] = status
- Bus.publish(ToolsChanged, { server: name })
return {
status,
}
}
if (!result.mcpClient) {
s.status[name] = result.status
- Bus.publish(ToolsChanged, { server: name })
return {
status: s.status,
}
@@ -328,9 +353,6 @@ export namespace MCP {
}
s.clients[name] = result.mcpClient
s.status[name] = result.status
- if (result.transport) s.transports[name] = result.transport
-
- Bus.publish(ToolsChanged, { server: name })
return {
status: s.status,
@@ -349,7 +371,6 @@ export namespace MCP {
log.info("found", { key, type: mcp.type })
let mcpClient: MCPClient | undefined
let status: Status | undefined = undefined
- let connectedTransport: "stdio" | "sse" | "streamable-http" | undefined = undefined
if (mcp.type === "remote") {
// OAuth is enabled by default for remote servers unless explicitly disabled with oauth: false
@@ -395,43 +416,16 @@ export namespace MCP {
let lastError: Error | undefined
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
for (const { name, transport } of transports) {
- const connectStart = Date.now()
try {
const client = new Client({
- name: "altimate",
+ name: "opencode",
version: Installation.VERSION,
})
await withTimeout(client.connect(transport), connectTimeout)
registerNotificationHandlers(client, key)
mcpClient = client
- connectedTransport = name === "SSE" ? "sse" : "streamable-http"
log.info("connected", { key, transport: name })
status = { status: "connected" }
- Telemetry.track({
- type: "mcp_server_status",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: connectedTransport,
- status: "connected",
- duration_ms: Date.now() - connectStart,
- })
- // Census: collect tool and resource counts (fire-and-forget, never block connect)
- const remoteTransport = name === "SSE" ? "sse" as const : "streamable-http" as const
- void Promise.all([
- client.listTools().catch(() => ({ tools: [] })),
- client.listResources().catch(() => ({ resources: [] })),
- ]).then(([toolsList, resourcesList]) => {
- Telemetry.track({
- type: "mcp_server_census",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: remoteTransport,
- tool_count: toolsList.tools.length,
- resource_count: resourcesList.resources.length,
- })
- }).catch(() => {})
break
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error))
@@ -466,7 +460,7 @@ export namespace MCP {
// Show toast for needs_auth
Bus.publish(TuiEvent.ToastShow, {
title: "MCP Authentication Required",
- message: `Server "${key}" requires authentication. Run: altimate mcp auth ${key}`,
+ message: `Server "${key}" requires authentication. Run: opencode mcp auth ${key}`,
variant: "warning",
duration: 8000,
}).catch((e) => log.debug("failed to show toast", { error: e }))
@@ -480,16 +474,6 @@ export namespace MCP {
url: mcp.url,
error: lastError.message,
})
- Telemetry.track({
- type: "mcp_server_status",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: name === "SSE" ? "sse" : "streamable-http",
- status: "error",
- error: lastError.message.slice(0, 500),
- duration_ms: Date.now() - connectStart,
- })
status = {
status: "failed" as const,
error: lastError.message,
@@ -508,7 +492,7 @@ export namespace MCP {
cwd,
env: {
...process.env,
- ...(cmd === "altimate" || cmd === "altimate-code" ? { BUN_BE_BUN: "1" } : {}),
+ ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
...mcp.environment,
},
})
@@ -517,43 +501,17 @@ export namespace MCP {
})
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
- const localConnectStart = Date.now()
try {
const client = new Client({
- name: "altimate",
+ name: "opencode",
version: Installation.VERSION,
})
await withTimeout(client.connect(transport), connectTimeout)
registerNotificationHandlers(client, key)
mcpClient = client
- connectedTransport = "stdio"
status = {
status: "connected",
}
- Telemetry.track({
- type: "mcp_server_status",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: "stdio",
- status: "connected",
- duration_ms: Date.now() - localConnectStart,
- })
- // Census: collect tool and resource counts (fire-and-forget, never block connect)
- void Promise.all([
- client.listTools().catch(() => ({ tools: [] })),
- client.listResources().catch(() => ({ resources: [] })),
- ]).then(([toolsList, resourcesList]) => {
- Telemetry.track({
- type: "mcp_server_census",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: "stdio",
- tool_count: toolsList.tools.length,
- resource_count: resourcesList.resources.length,
- })
- }).catch(() => {})
} catch (error) {
log.error("local mcp startup failed", {
key,
@@ -561,20 +519,9 @@ export namespace MCP {
cwd,
error: error instanceof Error ? error.message : String(error),
})
- const errorMsg = error instanceof Error ? error.message : String(error)
- Telemetry.track({
- type: "mcp_server_status",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: key,
- transport: "stdio",
- status: "error",
- error: errorMsg.slice(0, 500),
- duration_ms: Date.now() - localConnectStart,
- })
status = {
status: "failed" as const,
- error: errorMsg,
+ error: error instanceof Error ? error.message : String(error),
}
}
}
@@ -620,7 +567,6 @@ export namespace MCP {
return {
mcpClient,
status,
- transport: connectedTransport,
}
}
@@ -636,13 +582,6 @@ export namespace MCP {
result[key] = s.status[key] ?? { status: "disabled" }
}
- // Include dynamically added servers not yet in cached config
- for (const [key, st] of Object.entries(s.status)) {
- if (!(key in result)) {
- result[key] = st
- }
- }
-
return result
}
@@ -686,13 +625,11 @@ export namespace MCP {
})
}
s.clients[name] = result.mcpClient
- if (result.transport) s.transports[name] = result.transport
}
}
export async function disconnect(name: string) {
const s = await state()
- const transport = s.transports[name] ?? "stdio"
const client = s.clients[name]
if (client) {
await client.close().catch((error) => {
@@ -700,26 +637,9 @@ export namespace MCP {
})
delete s.clients[name]
}
- Telemetry.track({
- type: "mcp_server_status",
- timestamp: Date.now(),
- session_id: Telemetry.getContext().sessionId,
- server_name: name,
- transport,
- status: "disconnected",
- })
- delete s.transports[name]
s.status[name] = { status: "disabled" }
}
- /** Fully remove a dynamically-added MCP server — disconnects, and purges from runtime state. */
- export async function remove(name: string) {
- await disconnect(name)
- const s = await state()
- delete s.status[name]
- Bus.publish(ToolsChanged, { server: name })
- }
-
export async function tools() {
const result: Record = {}
const s = await state()
@@ -748,7 +668,6 @@ export namespace MCP {
}),
)
- registeredMcpTools.clear()
for (const { clientName, client, toolsResult } of toolsResults) {
if (!toolsResult) continue
const mcpConfig = config[clientName]
@@ -757,9 +676,7 @@ export namespace MCP {
for (const mcpTool of toolsResult.tools) {
const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
const sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
- const toolName = sanitizedClientName + "_" + sanitizedToolName
- registeredMcpTools.add(toolName)
- result[toolName] = await convertMcpTool(mcpTool, client, timeout)
+ result[sanitizedClientName + "_" + sanitizedToolName] = await convertMcpTool(mcpTool, client, timeout)
}
}
return result
@@ -840,7 +757,7 @@ export namespace MCP {
const client = clientsSnapshot[clientName]
if (!client) {
- log.warn("client not found for resource", {
+ log.warn("client not found for prompt", {
clientName: clientName,
})
return undefined
@@ -851,7 +768,7 @@ export namespace MCP {
uri: resourceUri,
})
.catch((e) => {
- log.error("failed to read resource from MCP server", {
+ log.error("failed to get prompt from MCP server", {
clientName: clientName,
resourceUri: resourceUri,
error: e.message,
@@ -923,7 +840,7 @@ export namespace MCP {
// Try to connect - this will trigger the OAuth flow
try {
const client = new Client({
- name: "altimate",
+ name: "opencode",
version: Installation.VERSION,
})
await client.connect(transport)
diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts
index e12108b117..3316e71beb 100644
--- a/packages/opencode/src/mcp/oauth-callback.ts
+++ b/packages/opencode/src/mcp/oauth-callback.ts
@@ -24,10 +24,6 @@ const HTML_SUCCESS = `