SaylorTwift HF Staff commited on
Commit
251a574
·
verified ·
1 Parent(s): e467551

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.dockerignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .opencode
3
+ .sst
4
+ .turbo
5
+ .wrangler
6
+ node_modules
7
+ **/node_modules
8
+ **/.output
9
+ **/dist
10
+ **/.turbo
11
+ **/.vite
12
+ **/coverage
.editorconfig ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ insert_final_newline = true
6
+ end_of_line = lf
7
+ indent_style = space
8
+ indent_size = 2
9
+ max_line_length = 80
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ packages/core/migration/**/snapshot.json linguist-generated
2
+ packages/core/src/database/migration.gen.ts linguist-generated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+ node_modules
3
+ .worktrees
4
+ .sst
5
+ .env
6
+ .env.local
7
+ .idea
8
+ .vscode
9
+ .codex
10
+ *~
11
+ playground
12
+ tmp
13
+ dist
14
+ ts-dist
15
+ .turbo
16
+ **/.serena
17
+ .serena/
18
+ **/.omo
19
+ .omo/
20
+ /result
21
+ refs
22
+ Session.vim
23
+ /opencode.json
24
+ a.out
25
+ target
26
+ .scripts
27
+ .direnv/
28
+
29
+ # Local dev files
30
+ opencode-dev
31
+ UPCOMING_CHANGELOG.md
32
+ logs/
33
+ *.bun-build
34
+ tsconfig.tsbuildinfo
.gitleaksignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Fake secret-looking strings used by HTTP recorder redaction tests.
2
+ afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:69
3
+ afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:92
4
+ afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:146
5
+ afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:gcp-api-key:71
.husky/pre-push ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -e
3
+ # Check if bun version matches package.json
4
+ # keep in sync with packages/script/src/index.ts semver qualifier
5
+ bun -e '
6
+ import { semver } from "bun";
7
+ const pkg = await Bun.file("package.json").json();
8
+ const expectedBunVersion = pkg.packageManager?.split("@")[1];
9
+ if (!expectedBunVersion) {
10
+ throw new Error("packageManager field not found in root package.json");
11
+ }
12
+ const expectedBunVersionRange = `^${expectedBunVersion}`;
13
+ if (!semver.satisfies(process.versions.bun, expectedBunVersionRange)) {
14
+ throw new Error(`This script requires bun@${expectedBunVersionRange}, but you are using bun@${process.versions.bun}`);
15
+ }
16
+ if (process.versions.bun !== expectedBunVersion) {
17
+ console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
18
+ }
19
+ '
20
+ bun typecheck
.opencode/.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ plans
3
+ package.json
4
+ bun.lock
5
+ .gitignore
6
+ package-lock.json
7
+ references/
.opencode/env.d.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ declare module "*.txt" {
2
+ const content: string
3
+ export default content
4
+ }
.opencode/opencode.jsonc ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "provider": {},
4
+ "permission": {},
5
+ "references": {
6
+ "effect": {
7
+ "repository": "github.com/Effect-TS/effect-smol",
8
+ "description": "Use for Effect v4 and effect-smol implementation details",
9
+ },
10
+ "opencode-local": {
11
+ "path": "~/.local/share/opencode",
12
+ "description": "Contains opencode logs and data",
13
+ },
14
+ },
15
+ "mcp": {},
16
+ "tools": {
17
+ "github-triage": false,
18
+ "github-pr-search": false,
19
+ },
20
+ }
.opencode/tui.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://opencode.ai/tui.json",
3
+ "plugin": [
4
+ [
5
+ "./plugins/tui-smoke.tsx",
6
+ {
7
+ "enabled": false,
8
+ "label": "workspace",
9
+ "keybinds": {
10
+ "smoke_modal": "ctrl+alt+m",
11
+ "smoke_screen": "ctrl+alt+o",
12
+ "smoke_screen_home": "escape,ctrl+shift+h",
13
+ "smoke_screen_modal": "ctrl+alt+m",
14
+ "smoke_dialog_close": "escape,q"
15
+ }
16
+ }
17
+ ]
18
+ ]
19
+ }
.zed/settings.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format_on_save": "on",
3
+ "formatter": {
4
+ "external": {
5
+ "command": "bunx",
6
+ "arguments": ["prettier", "--stdin-filepath", "{buffer_path}"]
7
+ }
8
+ }
9
+ }
AGENTS.md ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
2
+ - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
3
+ - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
4
+ - The default branch in this repo is `dev`.
5
+ - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
6
+
7
+ ## Branch Names
8
+
9
+ Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`.
10
+
11
+ Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`.
12
+
13
+ ## Commits and PR Titles
14
+
15
+ Use conventional commit-style messages and PR titles: `type(scope): summary`.
16
+
17
+ Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes are optional; use the affected package or area when helpful, e.g. `core`, `opencode`, `tui`, `app`, `desktop`, `sdk`, or `plugin`.
18
+
19
+ Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
20
+
21
+ ## Style Guide
22
+
23
+ ### General Principles
24
+
25
+ - Keep things in one function unless composable or reusable
26
+ - Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
27
+ - Avoid `try`/`catch` where possible
28
+ - Avoid using the `any` type
29
+ - Use Bun APIs when possible, like `Bun.file()`
30
+ - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
31
+ - Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
32
+ - In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
33
+ - In Effect generators, bind services to named variables before calling methods. Do not use nested service yields such as `yield* (yield* Foo.Service).bar()`.
34
+
35
+ Reduce total variable count by inlining when a value is only used once.
36
+
37
+ ```ts
38
+ // Good
39
+ const journal = await Bun.file(path.join(dir, "journal.json")).json()
40
+
41
+ // Bad
42
+ const journalPath = path.join(dir, "journal.json")
43
+ const journal = await Bun.file(journalPath).json()
44
+ ```
45
+
46
+ ### Destructuring
47
+
48
+ Avoid unnecessary destructuring. Use dot notation to preserve context.
49
+
50
+ ```ts
51
+ // Good
52
+ obj.a
53
+ obj.b
54
+
55
+ // Bad
56
+ const { a, b } = obj
57
+ ```
58
+
59
+ ### Imports
60
+
61
+ - Never alias imports. Do not use `import { foo as bar } from "..."` or renamed imports like `resolve as pathResolve`.
62
+ - Never use star imports. Do not use `import * as Foo from "..."` or `import type * as Foo from "..."`.
63
+ - If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode-ai/core/project"`, then reference `Project.ID`.
64
+ - Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as `await import("./module").then((mod) => mod.value())` or `(await import("./module")).value()`. Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
65
+
66
+ ### Variables
67
+
68
+ Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
69
+
70
+ ```ts
71
+ // Good
72
+ const foo = condition ? 1 : 2
73
+
74
+ // Bad
75
+ let foo
76
+ if (condition) foo = 1
77
+ else foo = 2
78
+ ```
79
+
80
+ ### Control Flow
81
+
82
+ Avoid `else` statements. Prefer early returns.
83
+
84
+ ```ts
85
+ // Good
86
+ function foo() {
87
+ if (condition) return 1
88
+ return 2
89
+ }
90
+
91
+ // Bad
92
+ function foo() {
93
+ if (condition) return 1
94
+ else return 2
95
+ }
96
+ ```
97
+
98
+ ### Complex Logic
99
+
100
+ When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
101
+
102
+ ```ts
103
+ // Good
104
+ export function loadThing(input: unknown) {
105
+ const config = requireConfig(input)
106
+ const metadata = readMetadata(input)
107
+ return createThing({ config, metadata })
108
+ }
109
+
110
+ function requireConfig(input: unknown) {
111
+ ...
112
+ }
113
+ ```
114
+
115
+ - Keep helpers close to the code they support, below the main export when that improves readability.
116
+ - Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like `requireConfig` or `readMetadata`.
117
+ - Do not return `Effect` from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous.
118
+ - Prefer Effect schema helpers such as `Schema.UnknownFromJsonString` and `Schema.decodeUnknownOption` over manual `JSON.parse` wrapped in `Effect.try` when parsing untrusted JSON strings.
119
+ - Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
120
+
121
+ ### Schema Definitions (Drizzle)
122
+
123
+ Use snake_case for field names so column names don't need to be redefined as strings.
124
+
125
+ ```ts
126
+ // Good
127
+ const table = sqliteTable("session", {
128
+ id: text().primaryKey(),
129
+ project_id: text().notNull(),
130
+ created_at: integer().notNull(),
131
+ })
132
+
133
+ // Bad
134
+ const table = sqliteTable("session", {
135
+ id: text("id").primaryKey(),
136
+ projectID: text("project_id").notNull(),
137
+ createdAt: integer("created_at").notNull(),
138
+ })
139
+ ```
140
+
141
+ ## Testing
142
+
143
+ - Avoid mocks as much as possible, you shouldn't be using globalThis.\* at all unless it's the only option.
144
+ - Test actual implementation, do not duplicate logic into tests
145
+ - Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
146
+
147
+ ## Type Checking
148
+
149
+ - Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.
150
+
151
+ ## V2 Session Core
152
+
153
+ - Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries.
154
+ - Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry.
155
+ - Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
156
+ - Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
157
+ - Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
158
+ - Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.
159
+ - Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
160
+ - Keep EventV2 replay owner claims separate from clustered Session execution ownership.
161
+ - Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
CONTRIBUTING.md ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to OpenCode
2
+
3
+ We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged:
4
+
5
+ - Bug fixes
6
+ - Additional LSPs / Formatters
7
+ - Improvements to LLM performance
8
+ - Support for new providers
9
+ - Fixes for environment-specific quirks
10
+ - Missing standard behavior
11
+ - Documentation improvements
12
+
13
+ However, any UI or core product feature must go through a design review with the core team before implementation.
14
+
15
+ If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels:
16
+
17
+ - [`help wanted`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted)
18
+ - [`good first issue`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)
19
+ - [`bug`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug)
20
+ - [`perf`](https://github.com/anomalyco/opencode/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22perf%22)
21
+
22
+ > [!NOTE]
23
+ > PRs that ignore these guardrails will likely be closed.
24
+
25
+ Want to take on an issue? Leave a comment and a maintainer may assign it to you unless it is something we are already working on.
26
+
27
+ ## Adding New Providers
28
+
29
+ New providers shouldn't require many if ANY code changes, but if you want to add support for a new provider first make a PR to:
30
+ https://github.com/anomalyco/models.dev
31
+
32
+ ## Developing OpenCode
33
+
34
+ - Requirements: Bun 1.3+
35
+ - Install dependencies and start the dev server from the repo root:
36
+
37
+ ```bash
38
+ bun install
39
+ bun dev
40
+ ```
41
+
42
+ ### Running against a different directory
43
+
44
+ By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository:
45
+
46
+ ```bash
47
+ bun dev <directory>
48
+ ```
49
+
50
+ To run OpenCode in the root of the opencode repo itself:
51
+
52
+ ```bash
53
+ bun dev .
54
+ ```
55
+
56
+ ### Building a "localcode"
57
+
58
+ To compile a standalone executable:
59
+
60
+ ```bash
61
+ ./packages/opencode/script/build.ts --single
62
+ ```
63
+
64
+ Then run it with:
65
+
66
+ ```bash
67
+ ./packages/opencode/dist/opencode-<platform>/bin/opencode
68
+ ```
69
+
70
+ Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
71
+
72
+ - Core pieces:
73
+ - `packages/opencode`: OpenCode core business logic & server.
74
+ - `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
75
+ - `packages/app`: The shared web UI components, written in SolidJS
76
+ - `packages/desktop`: The native desktop app, built with Electron (wraps `packages/app`)
77
+ - `packages/plugin`: Source for `@opencode-ai/plugin`
78
+
79
+ ### Understanding bun dev vs opencode
80
+
81
+ During development, `bun dev` is the local equivalent of the built `opencode` command. Both run the same CLI interface:
82
+
83
+ ```bash
84
+ # Development (from project root)
85
+ bun dev --help # Show all available commands
86
+ bun dev serve # Start headless API server
87
+ bun dev web # Start server + open web interface
88
+ bun dev <directory> # Start TUI in specific directory
89
+
90
+ # Production
91
+ opencode --help # Show all available commands
92
+ opencode serve # Start headless API server
93
+ opencode web # Start server + open web interface
94
+ opencode <directory> # Start TUI in specific directory
95
+ ```
96
+
97
+ ### Running the API Server
98
+
99
+ To start the OpenCode headless API server:
100
+
101
+ ```bash
102
+ bun dev serve
103
+ ```
104
+
105
+ This starts the headless server on port 4096 by default. You can specify a different port:
106
+
107
+ ```bash
108
+ bun dev serve --port 8080
109
+ ```
110
+
111
+ ### Running the Web App
112
+
113
+ To test UI changes during development:
114
+
115
+ 1. **First, start the OpenCode server** (see [Running the API Server](#running-the-api-server) section above)
116
+ 2. **Then run the web app:**
117
+
118
+ ```bash
119
+ bun run --cwd packages/app dev
120
+ ```
121
+
122
+ This starts a local dev server at http://localhost:5173 (or similar port shown in output). Most UI changes can be tested here, but the server must be running for full functionality.
123
+
124
+ ### Running the Desktop App
125
+
126
+ The desktop app is an Electron application that wraps the web UI.
127
+
128
+ To run the desktop app in development:
129
+
130
+ ```bash
131
+ bun run --cwd packages/desktop dev
132
+ ```
133
+
134
+ To create a production build and package the app:
135
+
136
+ ```bash
137
+ bun run --cwd packages/desktop build
138
+ bun run --cwd packages/desktop package
139
+ ```
140
+
141
+ > [!NOTE]
142
+ > If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
143
+
144
+ Please try to follow the [style guide](./AGENTS.md)
145
+
146
+ ### Setting up a Debugger
147
+
148
+ Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.
149
+
150
+ The most reliable way to debug OpenCode is to run it manually in a terminal via `bun run --inspect=<url> dev ...` and attach
151
+ your debugger via that URL. Other methods can result in breakpoints being mapped incorrectly, at least in VSCode (YMMV).
152
+
153
+ Caveats:
154
+
155
+ - If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of
156
+ the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there.
157
+ - If `spawn` does not work for you, you can debug the server separately:
158
+ - Debug server: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode ./src/index.ts serve --port 4096`,
159
+ then attach TUI with `opencode attach http://localhost:4096`
160
+ - Debug TUI: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode ./src/index.ts`
161
+
162
+ Other tips and tricks:
163
+
164
+ - You might want to use `--inspect-wait` or `--inspect-brk` instead of `--inspect`, depending on your workflow
165
+ - Specifying `--inspect=ws://localhost:6499/` on every invocation can be tiresome, you may want to `export BUN_OPTIONS=--inspect=ws://localhost:6499/` instead
166
+
167
+ #### VSCode Setup
168
+
169
+ If you use VSCode, you can use our example configurations [.vscode/settings.example.json](.vscode/settings.example.json) and [.vscode/launch.example.json](.vscode/launch.example.json).
170
+
171
+ Some debug methods that can be problematic:
172
+
173
+ - Debug configurations with `"request": "launch"` can have breakpoints incorrectly mapped and thus unusable
174
+ - The same problem arises when running OpenCode in the VSCode `JavaScript Debug Terminal`
175
+
176
+ With that said, you may want to try these methods, as they might work for you.
177
+
178
+ ## Pull Request Expectations
179
+
180
+ ### Issue First Policy
181
+
182
+ **All PRs must reference an existing issue.** Before opening a PR, open an issue describing the bug or feature. This helps maintainers triage and prevents duplicate work. PRs without a linked issue may be closed without review.
183
+
184
+ - Use `Fixes #123` or `Closes #123` in your PR description to link the issue
185
+ - For small fixes, a brief issue is fine - just enough context for maintainers to understand the problem
186
+
187
+ ### General Requirements
188
+
189
+ - Keep pull requests small and focused
190
+ - Explain the issue and why your change fixes it
191
+ - Before adding new functionality, ensure it doesn't already exist elsewhere in the codebase
192
+
193
+ ### UI Changes
194
+
195
+ If your PR includes UI changes, please include screenshots or videos showing the before and after. This helps maintainers review faster and gives you quicker feedback.
196
+
197
+ ### Logic Changes
198
+
199
+ For non-UI changes (bug fixes, new features, refactors), explain **how you verified it works**:
200
+
201
+ - What did you test?
202
+ - How can a reviewer reproduce/confirm the fix?
203
+
204
+ ### No AI-Generated Walls of Text
205
+
206
+ Long, AI-generated PR descriptions and issues are not acceptable and may be ignored. Respect the maintainers' time:
207
+
208
+ - Write short, focused descriptions
209
+ - Explain what changed and why in your own words
210
+ - If you can't explain it briefly, your PR might be too large
211
+
212
+ ### PR Titles
213
+
214
+ PR titles should follow conventional commit standards:
215
+
216
+ - `feat:` new feature or functionality
217
+ - `fix:` bug fix
218
+ - `docs:` documentation or README changes
219
+ - `chore:` maintenance tasks, dependency updates, etc.
220
+ - `refactor:` code refactoring without changing behavior
221
+ - `test:` adding or updating tests
222
+
223
+ You can optionally include a scope to indicate which package is affected:
224
+
225
+ - `feat(app):` feature in the app package
226
+ - `fix(desktop):` bug fix in the desktop package
227
+ - `chore(opencode):` maintenance in the opencode package
228
+
229
+ Examples:
230
+
231
+ - `docs: update contributing guidelines`
232
+ - `fix: resolve crash on startup`
233
+ - `feat: add dark mode support`
234
+ - `feat(app): add dark mode support`
235
+ - `fix(desktop): resolve crash on startup`
236
+ - `chore: bump dependency versions`
237
+
238
+ ### Style Preferences
239
+
240
+ These are not strictly enforced, they are just general guidelines:
241
+
242
+ - **Functions:** Keep logic within a single function unless breaking it out adds clear reuse or composition benefits.
243
+ - **Destructuring:** Do not do unnecessary destructuring of variables.
244
+ - **Control flow:** Avoid `else` statements.
245
+ - **Error handling:** Prefer `.catch(...)` instead of `try`/`catch` when possible.
246
+ - **Types:** Reach for precise types and avoid `any`.
247
+ - **Variables:** Stick to immutable patterns and avoid `let`.
248
+ - **Naming:** Choose concise single-word identifiers when they remain descriptive.
249
+ - **Runtime APIs:** Use Bun helpers such as `Bun.file()` when they fit the use case.
250
+
251
+ ## Feature Requests
252
+
253
+ For net-new functionality, start with a design conversation. Open an issue describing the problem, your proposed approach (optional), and why it belongs in OpenCode. The core team will help decide whether it should move forward; please wait for that approval instead of opening a feature PR directly.
254
+
255
+ ## Issue Requirements
256
+
257
+ All issues **must** use one of our issue templates:
258
+
259
+ - **Bug report** — for reporting bugs (requires a description)
260
+ - **Feature request** — for suggesting enhancements (requires verification checkbox and description)
261
+ - **Question** — for asking questions (requires the question)
262
+
263
+ Blank issues are not allowed. When a new issue is opened, an automated check verifies that it follows a template and meets our contributing guidelines. If an issue doesn't meet the requirements, you'll receive a comment explaining what needs to be fixed and have **2 hours** to edit the issue. After that, it will be automatically closed.
264
+
265
+ Issues may be flagged for:
266
+
267
+ - Not using a template
268
+ - Required fields left empty or filled with placeholder text
269
+ - AI-generated walls of text
270
+ - Missing meaningful content
271
+
272
+ If you believe your issue was incorrectly flagged, let a maintainer know.
README.ja.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">オープンソースのAIコーディングエージェント。</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### インストール
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # パッケージマネージャー
53
+ npm i -g opencode-ai@latest # bun/pnpm/yarn でもOK
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS と Linux(推奨。常に最新)
57
+ brew install opencode # macOS と Linux(公式 brew formula。更新頻度は低め)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # どのOSでも
61
+ nix run nixpkgs#opencode # または github:anomalyco/opencode で最新 dev ブランチ
62
+ ```
63
+
64
+ > [!TIP]
65
+ > インストール前に 0.1.x より古いバージョンを削除してください。
66
+
67
+ ### デスクトップアプリ (BETA)
68
+
69
+ OpenCode はデスクトップアプリとしても利用できます。[releases page](https://github.com/anomalyco/opencode/releases) から直接ダウンロードするか、[opencode.ai/download](https://opencode.ai/download) を利用してください。
70
+
71
+ | プラットフォーム | ダウンロード |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`、`.rpm`、または AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### インストールディレクトリ
86
+
87
+ インストールスクリプトは、インストール先パスを次の優先順位で決定します。
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - カスタムのインストールディレクトリ
90
+ 2. `$XDG_BIN_DIR` - XDG Base Directory Specification に準拠したパス
91
+ 3. `$HOME/bin` - 標準のユーザー用バイナリディレクトリ(存在する場合、または作成できる場合)
92
+ 4. `$HOME/.opencode/bin` - デフォルトのフォールバック
93
+
94
+ ```bash
95
+ # 例
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ OpenCode には組み込みの Agent が2つあり、`Tab` キーで切り替えられます。
103
+
104
+ - **build** - デフォルト。開発向けのフルアクセス Agent
105
+ - **plan** - 分析とコード探索向けの読み取り専用 Agent
106
+ - デフォルトでファイル編集を拒否
107
+ - bash コマンド実行前に確認
108
+ - 未知のコードベース探索や変更計画に最適
109
+
110
+ また、複雑な検索やマルチステップのタスク向けに **general** サブ Agent も含まれています。
111
+ 内部的に使用されており、メッセージで `@general` と入力して呼び出せます。
112
+
113
+ [agents](https://opencode.ai/docs/agents) の詳細はこちら。
114
+
115
+ ### ドキュメント
116
+
117
+ OpenCode の設定については [**ドキュメント**](https://opencode.ai/docs) を参照してください。
118
+
119
+ ### コントリビュート
120
+
121
+ OpenCode に貢献したい場合は、Pull Request を送る前に [contributing docs](./CONTRIBUTING.md) を読んでください。
122
+
123
+ ### OpenCode の上に構築する
124
+
125
+ OpenCode に関連するプロジェクトで、名前に "opencode"(例: "opencode-dashboard" や "opencode-mobile")を含める場合は、そのプロジェクトが OpenCode チームによって作られたものではなく、いかなる形でも関係がないことを README に明記してください。
126
+
127
+ ---
128
+
129
+ **コミュニティに参加** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.ko.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">오픈 소스 AI 코딩 에이전트.</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### 설치
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # 패키지 매니저
53
+ npm i -g opencode-ai@latest # bun/pnpm/yarn 도 가능
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS 및 Linux (권장, 항상 최신)
57
+ brew install opencode # macOS 및 Linux (공식 brew formula, 업데이트 빈도 낮음)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # 어떤 OS든
61
+ nix run nixpkgs#opencode # 또는 github:anomalyco/opencode 로 최신 dev 브랜치
62
+ ```
63
+
64
+ > [!TIP]
65
+ > 설치 전에 0.1.x 보다 오래된 버전을 제거하세요.
66
+
67
+ ### 데스크톱 앱 (BETA)
68
+
69
+ OpenCode 는 데스크톱 앱으로도 제공됩니다. [releases page](https://github.com/anomalyco/opencode/releases) 에서 직접 다운로드하거나 [opencode.ai/download](https://opencode.ai/download) 를 이용하세요.
70
+
71
+ | 플랫폼 | 다운로드 |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm`, 또는 AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### 설치 디렉터리
86
+
87
+ 설치 스크립트는 설치 경로를 다음 우선순위로 결정합니다.
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - 사용자 지정 설치 디렉터리
90
+ 2. `$XDG_BIN_DIR` - XDG Base Directory Specification 준수 경로
91
+ 3. `$HOME/bin` - 표준 사용자 바이너리 디렉터리 (존재하거나 생성 가능할 경우)
92
+ 4. `$HOME/.opencode/bin` - 기본 폴백
93
+
94
+ ```bash
95
+ # 예시
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ OpenCode 에는 내장 에이전트 2개가 있으며 `Tab` 키로 전환할 수 있습니다.
103
+
104
+ - **build** - 기본값, 개발 작업을 위한 전체 권한 에이전트
105
+ - **plan** - 분석 및 코드 탐색을 위한 읽기 전용 에이전트
106
+ - 기본적으로 파일 편집을 거부
107
+ - bash 명령 실행 전에 권한을 요청
108
+ - 낯선 코드베이스를 탐색하거나 변경을 계획할 때 적합
109
+
110
+ 또한 복잡한 검색과 여러 단계 작업을 위한 **general** 서브 에이전트가 포함되어 있습니다.
111
+ 내부적으로 사용되며, 메시지에서 `@general` 로 호출할 수 있습니다.
112
+
113
+ [agents](https://opencode.ai/docs/agents) 에 대해 더 알아보세요.
114
+
115
+ ### 문서
116
+
117
+ OpenCode 설정에 대한 자세한 ��용은 [**문서**](https://opencode.ai/docs) 를 참고하세요.
118
+
119
+ ### 기여하기
120
+
121
+ OpenCode 에 기여하고 싶다면, Pull Request 를 제출하기 전에 [contributing docs](./CONTRIBUTING.md) 를 읽어주세요.
122
+
123
+ ### OpenCode 기반으로 만들기
124
+
125
+ OpenCode 와 관련된 프로젝트를 진행하면서 이름에 "opencode"(예: "opencode-dashboard" 또는 "opencode-mobile") 를 포함한다면, README 에 해당 프로젝트가 OpenCode 팀이 만든 것이 아니며 어떤 방식으로도 우리와 제휴되어 있지 않다는 점을 명시해 주세요.
126
+
127
+ ---
128
+
129
+ **커뮤니티에 참여하기** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.md ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - agent
5
+ - coding-agent
6
+ - cli
7
+ - llm
8
+ - typescript
9
+ - tool-use
10
+ ---
11
+
12
+ > **This is a source mirror.** This Hugging Face repository is not a model checkpoint — it's a read-only mirror of the [`anomalyco/opencode`](https://github.com/anomalyco/opencode) GitHub repository (`dev` branch), the open source AI coding agent (opencode.ai). It is hosted here for visibility on the Hub; the canonical repository, issue tracker, and pull requests all live on GitHub. Please file issues and contribute there, not here.
13
+
14
+ <p align="center">
15
+ <a href="https://opencode.ai">
16
+ <picture>
17
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
18
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
19
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
20
+ </picture>
21
+ </a>
22
+ </p>
23
+ <p align="center">The open source AI coding agent.</p>
24
+ <p align="center">
25
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
26
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
27
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
28
+ </p>
29
+
30
+ <p align="center">
31
+ <a href="README.md">English</a> |
32
+ <a href="README.zh.md">简体中文</a> |
33
+ <a href="README.zht.md">繁體中文</a> |
34
+ <a href="README.ko.md">한국어</a> |
35
+ <a href="README.de.md">Deutsch</a> |
36
+ <a href="README.es.md">Español</a> |
37
+ <a href="README.fr.md">Français</a> |
38
+ <a href="README.it.md">Italiano</a> |
39
+ <a href="README.da.md">Dansk</a> |
40
+ <a href="README.ja.md">日本語</a> |
41
+ <a href="README.pl.md">Polski</a> |
42
+ <a href="README.ru.md">Русский</a> |
43
+ <a href="README.bs.md">Bosanski</a> |
44
+ <a href="README.ar.md">العربية</a> |
45
+ <a href="README.no.md">Norsk</a> |
46
+ <a href="README.br.md">Português (Brasil)</a> |
47
+ <a href="README.th.md">ไทย</a> |
48
+ <a href="README.tr.md">Türkçe</a> |
49
+ <a href="README.uk.md">Українська</a> |
50
+ <a href="README.bn.md">বাংলা</a> |
51
+ <a href="README.gr.md">Ελληνικά</a> |
52
+ <a href="README.vi.md">Tiếng Việt</a>
53
+ </p>
54
+
55
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
56
+
57
+ ---
58
+
59
+ ### Installation
60
+
61
+ ```bash
62
+ # YOLO
63
+ curl -fsSL https://opencode.ai/install | bash
64
+
65
+ # Package managers
66
+ npm i -g opencode-ai@latest # or bun/pnpm/yarn
67
+ scoop install opencode # Windows
68
+ choco install opencode # Windows
69
+ brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
70
+ brew install opencode # macOS and Linux (official brew formula, updated less)
71
+ sudo pacman -S opencode # Arch Linux (Stable)
72
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
73
+ mise use -g opencode # Any OS
74
+ nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
75
+ ```
76
+
77
+ > [!TIP]
78
+ > Remove versions older than 0.1.x before installing.
79
+
80
+ ### Desktop App (BETA)
81
+
82
+ OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
83
+
84
+ | Platform | Download |
85
+ | --------------------- | ---------------------------------- |
86
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
87
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
88
+ | Windows | `opencode-desktop-windows-x64.exe` |
89
+ | Linux | `.deb`, `.rpm`, or `.AppImage` |
90
+
91
+ ```bash
92
+ # macOS (Homebrew)
93
+ brew install --cask opencode-desktop
94
+ # Windows (Scoop)
95
+ scoop bucket add extras; scoop install extras/opencode-desktop
96
+ ```
97
+
98
+ #### Installation Directory
99
+
100
+ The install script respects the following priority order for the installation path:
101
+
102
+ 1. `$OPENCODE_INSTALL_DIR` - Custom installation directory
103
+ 2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path
104
+ 3. `$HOME/bin` - Standard user binary directory (if it exists or can be created)
105
+ 4. `$HOME/.opencode/bin` - Default fallback
106
+
107
+ ```bash
108
+ # Examples
109
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
110
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
111
+ ```
112
+
113
+ ### Agents
114
+
115
+ OpenCode includes two built-in agents you can switch between with the `Tab` key.
116
+
117
+ - **build** - Default, full-access agent for development work
118
+ - **plan** - Read-only agent for analysis and code exploration
119
+ - Denies file edits by default
120
+ - Asks permission before running bash commands
121
+ - Ideal for exploring unfamiliar codebases or planning changes
122
+
123
+ Also included is a **general** subagent for complex searches and multistep tasks.
124
+ This is used internally and can be invoked using `@general` in messages.
125
+
126
+ Learn more about [agents](https://opencode.ai/docs/agents).
127
+
128
+ ### Documentation
129
+
130
+ For more info on how to configure OpenCode, [**head over to our docs**](https://opencode.ai/docs).
131
+
132
+ ### Contributing
133
+
134
+ If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.
135
+
136
+ ### Building on OpenCode
137
+
138
+ If you are working on a project that's related to OpenCode and is using "opencode" as part of its name, for example "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way.
139
+
140
+ ---
141
+
142
+ **Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.no.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">AI-kodeagent med åpen kildekode.</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### Installasjon
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # Pakkehåndterere
53
+ npm i -g opencode-ai@latest # eller bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS og Linux (anbefalt, alltid oppdatert)
57
+ brew install opencode # macOS og Linux (offisiell brew-formel, oppdateres sjeldnere)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # alle OS
61
+ nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste dev-branch
62
+ ```
63
+
64
+ > [!TIP]
65
+ > Fjern versjoner eldre enn 0.1.x før du installerer.
66
+
67
+ ### Desktop-app (BETA)
68
+
69
+ OpenCode er også tilgjengelig som en desktop-app. Last ned direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
70
+
71
+ | Plattform | Nedlasting |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm` eller AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### Installasjonsmappe
86
+
87
+ Installasjonsskriptet bruker følgende prioritet for installasjonsstien:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - Egendefinert installasjonsmappe
90
+ 2. `$XDG_BIN_DIR` - Sti som følger XDG Base Directory Specification
91
+ 3. `$HOME/bin` - Standard brukerbinar-mappe (hvis den finnes eller kan opprettes)
92
+ 4. `$HOME/.opencode/bin` - Standard fallback
93
+
94
+ ```bash
95
+ # Eksempler
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ OpenCode har to innebygde agents du kan bytte mellom med `Tab`-tasten.
103
+
104
+ - **build** - Standard, agent med full tilgang for utviklingsarbeid
105
+ - **plan** - Skrivebeskyttet agent for analyse og kodeutforsking
106
+ - Nekter filendringer som standard
107
+ - Spør om tillatelse før bash-kommandoer
108
+ - Ideell for å utforske ukjente kodebaser eller planlegge endringer
109
+
110
+ Det finnes også en **general**-subagent for komplekse søk og flertrinnsoppgaver.
111
+ Den brukes internt og kan kalles via `@general` i meldinger.
112
+
113
+ Les mer om [agents](https://opencode.ai/docs/agents).
114
+
115
+ ### Dokumentasjon
116
+
117
+ For mer info om hvordan du konfigurerer OpenCode, [**se dokumentasjonen**](https://opencode.ai/docs).
118
+
119
+ ### Bidra
120
+
121
+ Hvis du vil bidra til OpenCode, les [contributing docs](./CONTRIBUTING.md) før du sender en pull request.
122
+
123
+ ### Bygge på OpenCode
124
+
125
+ Hvis du jobber med et prosjekt som er relatert til OpenCode og bruker "opencode" som en del av navnet; for eksempel "opencode-dashboard" eller "opencode-mobile", legg inn en merknad i README som presiserer at det ikke er bygget av OpenCode-teamet og ikke er tilknyttet oss på noen måte.
126
+
127
+ ---
128
+
129
+ **Bli med i fellesskapet** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.pl.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">Otwartoźródłowy agent kodujący AI.</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### Instalacja
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # Menedżery pakietów
53
+ npm i -g opencode-ai@latest # albo bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS i Linux (polecane, zawsze aktualne)
57
+ brew install opencode # macOS i Linux (oficjalna formuła brew, rzadziej aktualizowana)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # dowolny system
61
+ nix run nixpkgs#opencode # lub github:anomalyco/opencode dla najnowszej gałęzi dev
62
+ ```
63
+
64
+ > [!TIP]
65
+ > Przed instalacją usuń wersje starsze niż 0.1.x.
66
+
67
+ ### Aplikacja desktopowa (BETA)
68
+
69
+ OpenCode jest także dostępny jako aplikacja desktopowa. Pobierz ją bezpośrednio ze strony [releases](https://github.com/anomalyco/opencode/releases) lub z [opencode.ai/download](https://opencode.ai/download).
70
+
71
+ | Platforma | Pobieranie |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm` lub AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### Katalog instalacji
86
+
87
+ Skrypt instalacyjny stosuje następujący priorytet wyboru ścieżki instalacji:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - Własny katalog instalacji
90
+ 2. `$XDG_BIN_DIR` - Ścieżka zgodna ze specyfikacją XDG Base Directory
91
+ 3. `$HOME/bin` - Standardowy katalog binarny użytkownika (jeśli istnieje lub można go utworzyć)
92
+ 4. `$HOME/.opencode/bin` - Domyślny fallback
93
+
94
+ ```bash
95
+ # Przykłady
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ OpenCode zawiera dwóch wbudowanych agentów, między którymi możesz przełączać się klawiszem `Tab`.
103
+
104
+ - **build** - Domyślny agent z pełnym dostępem do pracy developerskiej
105
+ - **plan** - Agent tylko do odczytu do analizy i eksploracji kodu
106
+ - Domyślnie odmawia edycji plików
107
+ - Pyta o zgodę przed uruchomieniem komend bash
108
+ - Idealny do poznawania nieznanych baz kodu lub planowania zmian
109
+
110
+ Dodatkowo jest subagent **general** do złożonych wyszukiwań i wieloetapowych zadań.
111
+ Jest używany wewnętrznie i można go wywołać w wiadomościach przez `@general`.
112
+
113
+ Dowiedz się więcej o [agents](https://opencode.ai/docs/agents).
114
+
115
+ ### Dokumentacja
116
+
117
+ Więcej informacji o konfiguracji OpenCode znajdziesz w [**dokumentacji**](https://opencode.ai/docs).
118
+
119
+ ### Współtworzenie
120
+
121
+ Jeśli chcesz współtworzyć OpenCode, przeczytaj [contributing docs](./CONTRIBUTING.md) przed wysłaniem pull requesta.
122
+
123
+ ### Budowanie na OpenCode
124
+
125
+ Jeśli pracujesz nad projektem związanym z OpenCode i używasz "opencode" jako części nazwy (na przykład "opencode-dashboard" lub "opencode-mobile"), dodaj proszę notatkę do swojego README, aby wyjaśnić, że projekt nie jest tworzony przez zespół OpenCode i nie jest z nami w żaden sposób powiązany.
126
+
127
+ ---
128
+
129
+ **Dołącz do naszej społeczności** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.ru.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">Открытый AI-агент для программирования.</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### Установка
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # Менеджеры пакетов
53
+ npm i -g opencode-ai@latest # или bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS и Linux (рекомендуем, всегда актуально)
57
+ brew install opencode # macOS и Linux (официальная формула brew, обновляется реже)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # любая ОС
61
+ nix run nixpkgs#opencode # или github:anomalyco/opencode для самой свежей ветки dev
62
+ ```
63
+
64
+ > [!TIP]
65
+ > Перед установкой удалите версии старше 0.1.x.
66
+
67
+ ### Десктопное приложение (BETA)
68
+
69
+ OpenCode также доступен как десктопное приложение. Скачайте его со [страницы релизов](https://github.com/anomalyco/opencode/releases) или с [opencode.ai/download](https://opencode.ai/download).
70
+
71
+ | Платформа | Загрузка |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm` или AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### Каталог установки
86
+
87
+ Скрипт установки выбирает путь установки в следующем порядке приоритета:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - Пользовательский каталог установки
90
+ 2. `$XDG_BIN_DIR` - Путь, совместимый со спецификацией XDG Base Directory
91
+ 3. `$HOME/bin` - Стандартный каталог пользовательских бинарников (если существует или можно создать)
92
+ 4. `$HOME/.opencode/bin` - Fallback по умолчанию
93
+
94
+ ```bash
95
+ # Примеры
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ В OpenCode есть два встроенных агента, между которыми можно переключаться клавишей `Tab`.
103
+
104
+ - **build** - По умолчанию, агент с полным доступом для разработки
105
+ - **plan** - Агент только ��ля чтения для анализа и изучения кода
106
+ - По умолчанию запрещает редактирование файлов
107
+ - Запрашивает разрешение перед выполнением bash-команд
108
+ - Идеален для изучения незнакомых кодовых баз или планирования изменений
109
+
110
+ Также включен сабагент **general** для сложных поисков и многошаговых задач.
111
+ Он используется внутренне и может быть вызван в сообщениях через `@general`.
112
+
113
+ Подробнее об [agents](https://opencode.ai/docs/agents).
114
+
115
+ ### Документация
116
+
117
+ Больше информации о том, как настроить OpenCode: [**наши docs**](https://opencode.ai/docs).
118
+
119
+ ### Вклад
120
+
121
+ Если вы хотите внести вклад в OpenCode, прочитайте [contributing docs](./CONTRIBUTING.md) перед тем, как отправлять pull request.
122
+
123
+ ### Разработка на базе OpenCode
124
+
125
+ Если вы делаете проект, связанный с OpenCode, и используете "opencode" как часть имени (например, "opencode-dashboard" или "opencode-mobile"), добавьте примечание в README, чтобы уточнить, что проект не создан командой OpenCode и не аффилирован с нами.
126
+
127
+ ---
128
+
129
+ **Присоединяйтесь к нашему сообществу** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.th.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">เอเจนต์การเขียนโค้ดด้วย AI แบบโอเพนซอร์ส</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="สถานะการสร้าง" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### การติดตั้ง
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # ตัวจัดการแพ็กเกจ
53
+ npm i -g opencode-ai@latest # หรือ bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS และ Linux (แนะนำ อัปเดตเสมอ)
57
+ brew install opencode # macOS และ Linux (brew formula อย่างเป็นทางการ อัปเดตน้อยกว่า)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # ระบบปฏิบัติการใดก็ได้
61
+ nix run nixpkgs#opencode # หรือ github:anomalyco/opencode สำหรับสาขาพัฒนาล่าสุด
62
+ ```
63
+
64
+ > [!TIP]
65
+ > ลบเวอร์ชันที่เก่ากว่า 0.1.x ก่อนติดตั้ง
66
+
67
+ ### แอปพลิเคชันเดสก์ท็อป (เบต้า)
68
+
69
+ OpenCode มีให้ใช้งานเป็นแอปพลิเคชันเดสก์ท็อป ดาวน์โหลดโดยตรงจาก [หน้ารุ่น](https://github.com/anomalyco/opencode/releases) หรือ [opencode.ai/download](https://opencode.ai/download)
70
+
71
+ | แพลตฟอร์ม | ดาวน์โหลด |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm`, หรือ AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### ไดเรกทอรีการติดตั้ง
86
+
87
+ สคริปต์การติดตั้งจะใช้ลำดับความสำคัญตามเส้นทางการติดตั้ง:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - ไดเรกทอรีการติดตั้งที่กำหนดเอง
90
+ 2. `$XDG_BIN_DIR` - เส้นทางที่สอดคล้องกับ XDG Base Directory Specification
91
+ 3. `$HOME/bin` - ไดเรกทอรีไบนารีผู้ใช้มาตรฐาน (หากมีอยู่หรือสามารถสร้างได้)
92
+ 4. `$HOME/.opencode/bin` - ค่าสำรองเริ่มต้น
93
+
94
+ ```bash
95
+ # ตัวอย่าง
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### เอเจนต์
101
+
102
+ OpenCode รวมเอเจนต์ในตัวสองตัวที่คุณสามารถสลับได้ด้วยปุ่ม `Tab`
103
+
104
+ - **build** - เอเจนต์เริ่มต้น มีสิทธิ์เข้าถึงแบบเต็มสำหรับงานพัฒนา
105
+ - **plan** - เอเจนต์อ่านอย่างเดียวสำหรับการวิเคราะห์และการสำรวจโค้ด
106
+ - ปฏิเสธการแก้ไขไฟล์โดยค่าเริ่มต้น
107
+ - ขอสิทธิ์ก่อนเรียกใช้คำสั่ง bash
108
+ - เหมาะสำหรับสำรวจโค้ดเบสที่ไม่คุ้นเคยหรือวางแผนการเปลี่ยนแปลง
109
+
110
+ นอกจากนี้ยังมีเอเจนต์ย่อย **general** สำหรับการค้นหาที่ซับซ้อนและงานหลายขั้นตอน
111
+ ใช้ภายในและสามารถเรียกใช้ได้โดยใช้ `@general` ในข้อความ
112
+
113
+ เรียนรู้เพิ่มเติมเกี่ยวกับ [เอเจนต์](https://opencode.ai/docs/agents)
114
+
115
+ ### เอกสารประกอบ
116
+
117
+ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีกำหนดค่า OpenCode [**ไปที่เอกสารของเรา**](https://opencode.ai/docs)
118
+
119
+ ### การมีส่วนร่วม
120
+
121
+ หากคุณสนใจที่จะมีส่วนร่วมใน OpenCode โปรดอ่าน [เอกสารการมีส่วนร่วม](./CONTRIBUTING.md) ก่อนส่ง Pull Request
122
+
123
+ ### การสร้างบน OpenCode
124
+
125
+ หากคุณทำงานในโปรเจกต์ที่เกี่ยวข้องกับ OpenCode และใช้ "opencode" เป็นส่วนหนึ่งของชื่อ เช่น "opencode-dashboard" หรือ "opencode-mobile" โปรดเพิ่มหมายเหตุใน README ของคุณเพื่อชี้แจงว่าไม่ได้สร้างโดยทีม OpenCode และไม่ได้เกี่ยวข้องกับเราในทางใด
126
+
127
+ ---
128
+
129
+ **ร่วมชุมชนของเรา** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.vi.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">Trợ lý lập trình AI mã nguồn mở.</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### Cài đặt
47
+
48
+ ```bash
49
+ # YOLO
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # Các trình quản lý gói (Package managers)
53
+ npm i -g opencode-ai@latest # hoặc bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS và Linux (khuyên dùng, luôn cập nhật)
57
+ brew install opencode # macOS và Linux (công thức brew chính thức, ít cập nhật hơn)
58
+ sudo pacman -S opencode # Arch Linux (Bản ổn định)
59
+ paru -S opencode-bin # Arch Linux (Bản mới nhất từ AUR)
60
+ mise use -g opencode # Mọi hệ điều hành
61
+ nix run nixpkgs#opencode # hoặc github:anomalyco/opencode cho nhánh dev mới nhất
62
+ ```
63
+
64
+ > [!TIP]
65
+ > Hãy xóa các phiên bản cũ hơn 0.1.x trước khi cài đặt.
66
+
67
+ ### Ứng dụng Desktop (BETA)
68
+
69
+ OpenCode cũng có sẵn dưới dạng ứng dụng desktop. Tải trực tiếp từ [trang releases](https://github.com/anomalyco/opencode/releases) hoặc [opencode.ai/download](https://opencode.ai/download).
70
+
71
+ | Nền tảng | Tải xuống |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm`, hoặc AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### Thư mục cài đặt
86
+
87
+ Tập lệnh cài đặt tuân theo thứ tự ưu tiên sau cho đường dẫn cài đặt:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - Thư mục cài đặt tùy chỉnh
90
+ 2. `$XDG_BIN_DIR` - Đường dẫn tuân thủ XDG Base Directory Specification
91
+ 3. `$HOME/bin` - Thư mục nhị phân tiêu chuẩn của người dùng (nếu tồn tại hoặc có thể tạo)
92
+ 4. `$HOME/.opencode/bin` - Mặc định dự phòng
93
+
94
+ ```bash
95
+ # Ví dụ
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents (Đại diện)
101
+
102
+ OpenCode bao gồm hai agent được tích hợp sẵn mà bạn có thể chuyển đổi bằng phím `Tab`.
103
+
104
+ - **build** - Agent mặc định, có toàn quyền truy cập cho công việc lập trình
105
+ - **plan** - Agent chỉ đọc dùng để phân tích và khám phá mã nguồn
106
+ - Mặc định từ chối việc chỉnh sửa tệp
107
+ - Hỏi quyền trước khi chạy các lệnh bash
108
+ - Lý tưởng để khám phá các codebase lạ hoặc lên kế hoạch thay đổi
109
+
110
+ Ngoài ra còn có một subagent **general** dùng cho các tìm kiếm phức tạp và tác vụ nhi���u bước.
111
+ Agent này được sử dụng nội bộ và có thể gọi bằng cách dùng `@general` trong tin nhắn.
112
+
113
+ Tìm hiểu thêm về [agents](https://opencode.ai/docs/agents).
114
+
115
+ ### Tài liệu
116
+
117
+ Để biết thêm thông tin về cách cấu hình OpenCode, [**hãy truy cập tài liệu của chúng tôi**](https://opencode.ai/docs).
118
+
119
+ ### Đóng góp
120
+
121
+ Nếu bạn muốn đóng góp cho OpenCode, vui lòng đọc [tài liệu hướng dẫn đóng góp](./CONTRIBUTING.md) trước khi gửi pull request.
122
+
123
+ ### Xây dựng trên nền tảng OpenCode
124
+
125
+ Nếu bạn đang làm việc trên một dự án liên quan đến OpenCode và sử dụng "opencode" như một phần của tên dự án, ví dụ "opencode-dashboard" hoặc "opencode-mobile", vui lòng thêm một ghi chú vào README của bạn để làm rõ rằng dự án đó không được xây dựng bởi đội ngũ OpenCode và không liên kết với chúng tôi dưới bất kỳ hình thức nào.
126
+
127
+ ---
128
+
129
+ **Tham gia cộng đồng của chúng tôi** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
README.zht.md ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p align="center">
2
+ <a href="https://opencode.ai">
3
+ <picture>
4
+ <source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
5
+ <source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
6
+ <img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <p align="center">開源的 AI Coding Agent。</p>
11
+ <p align="center">
12
+ <a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
13
+ <a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
14
+ <a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> |
19
+ <a href="README.zh.md">简体中文</a> |
20
+ <a href="README.zht.md">繁體中文</a> |
21
+ <a href="README.ko.md">한국어</a> |
22
+ <a href="README.de.md">Deutsch</a> |
23
+ <a href="README.es.md">Español</a> |
24
+ <a href="README.fr.md">Français</a> |
25
+ <a href="README.it.md">Italiano</a> |
26
+ <a href="README.da.md">Dansk</a> |
27
+ <a href="README.ja.md">日本語</a> |
28
+ <a href="README.pl.md">Polski</a> |
29
+ <a href="README.ru.md">Русский</a> |
30
+ <a href="README.bs.md">Bosanski</a> |
31
+ <a href="README.ar.md">العربية</a> |
32
+ <a href="README.no.md">Norsk</a> |
33
+ <a href="README.br.md">Português (Brasil)</a> |
34
+ <a href="README.th.md">ไทย</a> |
35
+ <a href="README.tr.md">Türkçe</a> |
36
+ <a href="README.uk.md">Українська</a> |
37
+ <a href="README.bn.md">বাংলা</a> |
38
+ <a href="README.gr.md">Ελληνικά</a> |
39
+ <a href="README.vi.md">Tiếng Việt</a>
40
+ </p>
41
+
42
+ [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
43
+
44
+ ---
45
+
46
+ ### 安裝
47
+
48
+ ```bash
49
+ # 直接安裝 (YOLO)
50
+ curl -fsSL https://opencode.ai/install | bash
51
+
52
+ # 套件管理員
53
+ npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
54
+ scoop install opencode # Windows
55
+ choco install opencode # Windows
56
+ brew install anomalyco/tap/opencode # macOS 與 Linux(推薦,始終保持最新)
57
+ brew install opencode # macOS 與 Linux(官方 brew formula,更新頻率較低)
58
+ sudo pacman -S opencode # Arch Linux (Stable)
59
+ paru -S opencode-bin # Arch Linux (Latest from AUR)
60
+ mise use -g opencode # 任何作業系統
61
+ nix run nixpkgs#opencode # 或使用 github:anomalyco/opencode 以取得最新開發分支
62
+ ```
63
+
64
+ > [!TIP]
65
+ > 安裝前請先移除 0.1.x 以前的舊版本。
66
+
67
+ ### 桌面應用程式 (BETA)
68
+
69
+ OpenCode 也提供桌面版應用程式。您可以直接從 [發佈頁面 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下載。
70
+
71
+ | 平台 | 下載連結 |
72
+ | --------------------- | ---------------------------------- |
73
+ | macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
74
+ | macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
75
+ | Windows | `opencode-desktop-windows-x64.exe` |
76
+ | Linux | `.deb`, `.rpm`, 或 AppImage |
77
+
78
+ ```bash
79
+ # macOS (Homebrew Cask)
80
+ brew install --cask opencode-desktop
81
+ # Windows (Scoop)
82
+ scoop bucket add extras; scoop install extras/opencode-desktop
83
+ ```
84
+
85
+ #### 安裝目錄
86
+
87
+ 安裝腳本會依據以下優先順序決定安裝路徑:
88
+
89
+ 1. `$OPENCODE_INSTALL_DIR` - 自定義安裝目錄
90
+ 2. `$XDG_BIN_DIR` - 符合 XDG 基礎目錄規範的路徑
91
+ 3. `$HOME/bin` - 標準使用者執行檔目錄 (若存在或可建立)
92
+ 4. `$HOME/.opencode/bin` - 預設備用路徑
93
+
94
+ ```bash
95
+ # 範例
96
+ OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
97
+ XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
98
+ ```
99
+
100
+ ### Agents
101
+
102
+ OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。
103
+
104
+ - **build** - 預設模式,具備完整權限的 Agent,適用於開發工作。
105
+ - **plan** - 唯讀模式,適用於程式碼分析與探索。
106
+ - 預設禁止修改檔案。
107
+ - 執行 bash 指令前會詢問權限。
108
+ - 非常適合用來探索陌生的程式碼庫或規劃變更。
109
+
110
+ 此外,OpenCode 還包含一個 **general** 子 Agent,用於處理複雜搜尋與多步驟任務。此 Agent 供系統內部使用,亦可透過在訊息中輸入 `@general` 來呼叫。
111
+
112
+ 了解更多關於 [Agents](https://opencode.ai/docs/agents) 的資訊。
113
+
114
+ ### 線上文件
115
+
116
+ 關於如何設定 OpenCode 的詳細資訊,請參閱我們的 [**官方文件**](https://opencode.ai/docs)。
117
+
118
+ ### 參與貢獻
119
+
120
+ 如果您有興趣參與 OpenCode 的開發,請在提交 Pull Request 前先閱讀我們的 [貢獻指南 (Contributing Docs)](./CONTRIBUTING.md)。
121
+
122
+ ### 基於 OpenCode 進行開發
123
+
124
+ 如果您正在開發與 OpenCode 相關的專案,並在名稱中使用了 "opencode"(例如 "opencode-dashboard" 或 "opencode-mobile"),請在您的 README 中加入聲明,說明該專案並非由 OpenCode 團隊開發,且與我們沒有任何隸屬關係。
125
+
126
+ ---
127
+
128
+ **加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=52ao9352-5623-4fa0-b7dd-3407c392c1af&qr_code=true) | [X.com](https://x.com/opencode)
SECURITY.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Security
2
+
3
+ ## IMPORTANT
4
+
5
+ We do not accept AI generated security reports. We receive a large number of
6
+ these and we absolutely do not have the resources to review them all. If you
7
+ submit one that will be an automatic ban from the project.
8
+
9
+ ## Threat Model
10
+
11
+ ### Overview
12
+
13
+ OpenCode is an AI-powered coding assistant that runs locally on your machine. It provides an agent system with access to powerful tools including shell execution, file operations, and web access.
14
+
15
+ ### No Sandbox
16
+
17
+ OpenCode does **not** sandbox the agent. The permission system exists as a UX feature to help users stay aware of what actions the agent is taking - it prompts for confirmation before executing commands, writing files, etc. However, it is not designed to provide security isolation.
18
+
19
+ If you need true isolation, run OpenCode inside a Docker container or VM.
20
+
21
+ ### Server Mode
22
+
23
+ Server mode is opt-in only. When enabled, set `OPENCODE_SERVER_PASSWORD` to require HTTP Basic Auth. Without this, the server runs unauthenticated (with a warning). It is the end user's responsibility to secure the server - any functionality it provides is not a vulnerability.
24
+
25
+ ### Out of Scope
26
+
27
+ | Category | Rationale |
28
+ | ------------------------------- | ----------------------------------------------------------------------- |
29
+ | **Server access when opted-in** | If you enable server mode, API access is expected behavior |
30
+ | **Sandbox escapes** | The permission system is not a sandbox (see above) |
31
+ | **LLM provider data handling** | Data sent to your configured LLM provider is governed by their policies |
32
+ | **MCP server behavior** | External MCP servers you configure are outside our trust boundary |
33
+ | **Malicious config files** | Users control their own config; modifying it is not an attack vector |
34
+
35
+ ---
36
+
37
+ # Reporting Security Issues
38
+
39
+ We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
40
+
41
+ To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/anomalyco/opencode/security/advisories/new) tab.
42
+
43
+ The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
44
+
45
+ ## Escalation
46
+
47
+ If you do not receive an acknowledgement of your report within 6 business days, you may send an email to security@anoma.ly
bun.lock ADDED
The diff for this file is too large to render. See raw diff
 
flake.nix ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ description = "OpenCode development flake";
3
+
4
+ inputs = {
5
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
6
+ };
7
+
8
+ outputs =
9
+ { self, nixpkgs, ... }:
10
+ let
11
+ systems = [
12
+ "aarch64-linux"
13
+ "x86_64-linux"
14
+ "aarch64-darwin"
15
+ "x86_64-darwin"
16
+ ];
17
+ forEachSystem = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
18
+ rev = self.shortRev or self.dirtyShortRev or "dirty";
19
+ in
20
+ {
21
+ devShells = forEachSystem (pkgs: {
22
+ default = pkgs.mkShell {
23
+ packages = with pkgs; [
24
+ bun
25
+ nodejs_20
26
+ pkg-config
27
+ openssl
28
+ git
29
+ ];
30
+ };
31
+ });
32
+
33
+ overlays = {
34
+ default =
35
+ final: _prev:
36
+ let
37
+ node_modules = final.callPackage ./nix/node_modules.nix {
38
+ inherit rev;
39
+ };
40
+ in
41
+ rec {
42
+ opencode = final.callPackage ./nix/opencode.nix {
43
+ inherit node_modules;
44
+ };
45
+ opencode-desktop = final.callPackage ./nix/desktop.nix {
46
+ inherit opencode;
47
+ };
48
+ };
49
+ };
50
+
51
+ packages = forEachSystem (
52
+ pkgs:
53
+ let
54
+ node_modules = pkgs.callPackage ./nix/node_modules.nix {
55
+ inherit rev;
56
+ };
57
+ in
58
+ rec {
59
+ default = opencode;
60
+ opencode = pkgs.callPackage ./nix/opencode.nix {
61
+ inherit node_modules;
62
+ };
63
+ opencode-desktop = pkgs.callPackage ./nix/desktop.nix {
64
+ inherit opencode;
65
+ };
66
+ # Updater derivation with fakeHash - build fails and reveals correct hash
67
+ node_modules_updater = node_modules.override {
68
+ hash = pkgs.lib.fakeHash;
69
+ };
70
+ }
71
+ );
72
+ };
73
+ }
infra/app.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { domain } from "./stage"
2
+
3
+ const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
4
+ const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
5
+ export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
6
+ const ADMIN_SECRET = new sst.Secret("ADMIN_SECRET")
7
+ const DISCORD_SUPPORT_BOT_TOKEN = new sst.Secret("DISCORD_SUPPORT_BOT_TOKEN")
8
+ const DISCORD_SUPPORT_CHANNEL_ID = new sst.Secret("DISCORD_SUPPORT_CHANNEL_ID")
9
+ const FEISHU_APP_ID = new sst.Secret("FEISHU_APP_ID")
10
+ const FEISHU_APP_SECRET = new sst.Secret("FEISHU_APP_SECRET")
11
+ const bucket = new sst.cloudflare.Bucket("Bucket")
12
+
13
+ export const api = new sst.cloudflare.Worker("Api", {
14
+ domain: `api.${domain}`,
15
+ handler: "packages/function/src/api.ts",
16
+ environment: {
17
+ WEB_DOMAIN: domain,
18
+ },
19
+ url: true,
20
+ link: [
21
+ bucket,
22
+ GITHUB_APP_ID,
23
+ GITHUB_APP_PRIVATE_KEY,
24
+ ADMIN_SECRET,
25
+ DISCORD_SUPPORT_BOT_TOKEN,
26
+ DISCORD_SUPPORT_CHANNEL_ID,
27
+ FEISHU_APP_ID,
28
+ FEISHU_APP_SECRET,
29
+ ],
30
+ transform: {
31
+ worker: (args) => {
32
+ args.logpush = true
33
+ if ($app.stage === "vimtor" || $app.stage === "adam") return
34
+ args.bindings = $resolve(args.bindings).apply((bindings) => [
35
+ ...bindings,
36
+ {
37
+ name: "SYNC_SERVER",
38
+ type: "durable_object_namespace",
39
+ className: "SyncServer",
40
+ },
41
+ ])
42
+ args.migrations = {
43
+ // Note: when releasing the next tag, make sure all stages use tag v2
44
+ oldTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
45
+ newTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
46
+ //newSqliteClasses: ["SyncServer"],
47
+ }
48
+ },
49
+ },
50
+ })
51
+
52
+ new sst.cloudflare.x.Astro("Web", {
53
+ domain: "docs." + domain,
54
+ path: "packages/web",
55
+ environment: {
56
+ // For astro config
57
+ SST_STAGE: $app.stage,
58
+ VITE_API_URL: api.url.apply((url) => url!),
59
+ },
60
+ })
61
+
62
+ new sst.cloudflare.StaticSite("WebApp", {
63
+ domain: "app." + domain,
64
+ path: "packages/app",
65
+ build: {
66
+ command: "bun turbo build",
67
+ output: "./dist",
68
+ },
69
+ })
infra/console.ts ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { deployAws, domain } from "./stage"
2
+ import { EMAILOCTOPUS_API_KEY } from "./app"
3
+ import { SECRET } from "./secret"
4
+
5
+ const lake = deployAws ? await import("./lake") : undefined
6
+
7
+ ////////////////
8
+ // DATABASE
9
+ ////////////////
10
+
11
+ const cluster = planetscale.getDatabaseOutput({
12
+ name: "opencode",
13
+ organization: "anomalyco",
14
+ })
15
+
16
+ const branch =
17
+ $app.stage === "production"
18
+ ? planetscale.getBranchOutput({
19
+ name: "production",
20
+ organization: cluster.organization,
21
+ database: cluster.name,
22
+ })
23
+ : new planetscale.Branch("DatabaseBranch", {
24
+ database: cluster.name,
25
+ organization: cluster.organization,
26
+ name: $app.stage,
27
+ parentBranch: "production",
28
+ })
29
+ const password = new planetscale.Password("DatabasePassword", {
30
+ name: $app.stage,
31
+ database: cluster.name,
32
+ organization: cluster.organization,
33
+ branch: branch.name,
34
+ })
35
+
36
+ export const database = new sst.Linkable("Database", {
37
+ properties: {
38
+ host: password.accessHostUrl,
39
+ database: cluster.name,
40
+ username: password.username,
41
+ password: password.plaintext,
42
+ port: 3306,
43
+ },
44
+ })
45
+
46
+ new sst.x.DevCommand("Studio", {
47
+ link: [database],
48
+ dev: {
49
+ command: "bun db studio",
50
+ directory: "packages/console/core",
51
+ autostart: true,
52
+ },
53
+ })
54
+
55
+ ////////////////
56
+ // AUTH
57
+ ////////////////
58
+
59
+ const GITHUB_CLIENT_ID_CONSOLE = new sst.Secret("GITHUB_CLIENT_ID_CONSOLE")
60
+ const GITHUB_CLIENT_SECRET_CONSOLE = new sst.Secret("GITHUB_CLIENT_SECRET_CONSOLE")
61
+ const GOOGLE_CLIENT_ID = new sst.Secret("GOOGLE_CLIENT_ID")
62
+ const authStorage = new sst.cloudflare.Kv("AuthStorage")
63
+ export const auth = new sst.cloudflare.Worker("AuthApi", {
64
+ domain: `auth.${domain}`,
65
+ handler: "packages/console/function/src/auth.ts",
66
+ url: true,
67
+ link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID],
68
+ })
69
+
70
+ ////////////////
71
+ // GATEWAY
72
+ ////////////////
73
+
74
+ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint", {
75
+ url: $interpolate`https://${domain}/stripe/webhook`,
76
+ enabledEvents: [
77
+ "checkout.session.async_payment_failed",
78
+ "checkout.session.async_payment_succeeded",
79
+ "checkout.session.completed",
80
+ "checkout.session.expired",
81
+ "charge.refunded",
82
+ "invoice.payment_succeeded",
83
+ "invoice.payment_failed",
84
+ "invoice.payment_action_required",
85
+ "customer.created",
86
+ "customer.deleted",
87
+ "customer.updated",
88
+ "customer.discount.created",
89
+ "customer.discount.deleted",
90
+ "customer.discount.updated",
91
+ "customer.source.created",
92
+ "customer.source.deleted",
93
+ "customer.source.expiring",
94
+ "customer.source.updated",
95
+ "customer.subscription.created",
96
+ "customer.subscription.deleted",
97
+ "customer.subscription.paused",
98
+ "customer.subscription.pending_update_applied",
99
+ "customer.subscription.pending_update_expired",
100
+ "customer.subscription.resumed",
101
+ "customer.subscription.trial_will_end",
102
+ "customer.subscription.updated",
103
+ ],
104
+ })
105
+
106
+ const zenLiteProduct = new stripe.Product("ZenLite", {
107
+ name: "OpenCode Go",
108
+ })
109
+ const zenLiteCouponFirstMonth50 = new stripe.Coupon("ZenLiteCouponFirstMonth50", {
110
+ name: "First month 50% off",
111
+ percentOff: 50,
112
+ appliesToProducts: [zenLiteProduct.id],
113
+ duration: "once",
114
+ })
115
+ const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100", {
116
+ name: "First month 100% off",
117
+ percentOff: 100,
118
+ appliesToProducts: [zenLiteProduct.id],
119
+ duration: "once",
120
+ })
121
+ const zenLiteCouponThreeMonths100 = new stripe.Coupon("ZenLiteCoupon3Months100", {
122
+ name: "3 months 100% off",
123
+ percentOff: 100,
124
+ appliesToProducts: [zenLiteProduct.id],
125
+ duration: "repeating",
126
+ durationInMonths: 3,
127
+ })
128
+ const zenLiteCouponSixMonths100 = new stripe.Coupon("ZenLiteCoupon6Months100", {
129
+ name: "6 months 100% off",
130
+ percentOff: 100,
131
+ appliesToProducts: [zenLiteProduct.id],
132
+ duration: "repeating",
133
+ durationInMonths: 6,
134
+ })
135
+ const zenLiteCouponTwelveMonths100 = new stripe.Coupon("ZenLiteCoupon12Months100", {
136
+ name: "12 months 100% off",
137
+ percentOff: 100,
138
+ appliesToProducts: [zenLiteProduct.id],
139
+ duration: "repeating",
140
+ durationInMonths: 12,
141
+ })
142
+ const zenLitePrice = new stripe.Price("ZenLitePrice", {
143
+ product: zenLiteProduct.id,
144
+ currency: "usd",
145
+ recurring: {
146
+ interval: "month",
147
+ intervalCount: 1,
148
+ },
149
+ unitAmount: 1000,
150
+ })
151
+ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
152
+ properties: {
153
+ // Use existing Go resources in dev's checkout Stripe account.
154
+ product: $app.stage === "dev" ? "prod_U1tUscpmwtV2bG" : zenLiteProduct.id,
155
+ price: $app.stage === "dev" ? "price_1T3phhE7fOCwHSD4zS6w2NPy" : zenLitePrice.id,
156
+ priceInr: 92900,
157
+ firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
158
+ firstMonth100Coupon: zenLiteCouponFirstMonth100.id,
159
+ threeMonths100Coupon: zenLiteCouponThreeMonths100.id,
160
+ sixMonths100Coupon: zenLiteCouponSixMonths100.id,
161
+ twelveMonths100Coupon: zenLiteCouponTwelveMonths100.id,
162
+ },
163
+ })
164
+
165
+ const zenBlackProduct = new stripe.Product("ZenBlack", {
166
+ name: "OpenCode Black",
167
+ })
168
+ const zenBlackPriceProps = {
169
+ product: zenBlackProduct.id,
170
+ currency: "usd",
171
+ recurring: {
172
+ interval: "month",
173
+ intervalCount: 1,
174
+ },
175
+ }
176
+ const zenBlackPrice200 = new stripe.Price("ZenBlackPrice", { ...zenBlackPriceProps, unitAmount: 20000 })
177
+ const zenBlackPrice100 = new stripe.Price("ZenBlack100Price", { ...zenBlackPriceProps, unitAmount: 10000 })
178
+ const zenBlackPrice20 = new stripe.Price("ZenBlack20Price", { ...zenBlackPriceProps, unitAmount: 2000 })
179
+ const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
180
+ properties: {
181
+ product: zenBlackProduct.id,
182
+ plan200: zenBlackPrice200.id,
183
+ plan100: zenBlackPrice100.id,
184
+ plan20: zenBlackPrice20.id,
185
+ },
186
+ })
187
+
188
+ const ZEN_MODELS = [
189
+ new sst.Secret("ZEN_MODELS1"),
190
+ new sst.Secret("ZEN_MODELS2"),
191
+ new sst.Secret("ZEN_MODELS3"),
192
+ new sst.Secret("ZEN_MODELS4"),
193
+ new sst.Secret("ZEN_MODELS5"),
194
+ new sst.Secret("ZEN_MODELS6"),
195
+ new sst.Secret("ZEN_MODELS7"),
196
+ new sst.Secret("ZEN_MODELS8"),
197
+ new sst.Secret("ZEN_MODELS9"),
198
+ new sst.Secret("ZEN_MODELS10"),
199
+ new sst.Secret("ZEN_MODELS11"),
200
+ new sst.Secret("ZEN_MODELS12"),
201
+ new sst.Secret("ZEN_MODELS13"),
202
+ new sst.Secret("ZEN_MODELS14"),
203
+ new sst.Secret("ZEN_MODELS15"),
204
+ new sst.Secret("ZEN_MODELS16"),
205
+ new sst.Secret("ZEN_MODELS17"),
206
+ new sst.Secret("ZEN_MODELS18"),
207
+ new sst.Secret("ZEN_MODELS19"),
208
+ new sst.Secret("ZEN_MODELS20"),
209
+ new sst.Secret("ZEN_MODELS21"),
210
+ new sst.Secret("ZEN_MODELS22"),
211
+ new sst.Secret("ZEN_MODELS23"),
212
+ new sst.Secret("ZEN_MODELS24"),
213
+ new sst.Secret("ZEN_MODELS25"),
214
+ new sst.Secret("ZEN_MODELS26"),
215
+ new sst.Secret("ZEN_MODELS27"),
216
+ new sst.Secret("ZEN_MODELS28"),
217
+ new sst.Secret("ZEN_MODELS29"),
218
+ new sst.Secret("ZEN_MODELS30"),
219
+ ]
220
+ const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
221
+ const STRIPE_PUBLISHABLE_KEY = new sst.Secret("STRIPE_PUBLISHABLE_KEY")
222
+ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
223
+ properties: { value: auth.url.apply((url) => url!) },
224
+ })
225
+ // Preview branches have independent databases; do not send their workspaces to shared dev.
226
+ const migrationDomain =
227
+ $app.stage === "production" ? "opencode.ai" : $app.stage === "dev" ? "dev.opencode.ai" : undefined
228
+ const consoleMigration = new sst.Linkable("ConsoleMigration", {
229
+ properties: {
230
+ consoleUrl: migrationDomain ? `https://${migrationDomain}/console` : "",
231
+ inferenceUrl: migrationDomain ? `https://${migrationDomain}/inference` : "",
232
+ },
233
+ })
234
+ const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
235
+ properties: { value: stripeWebhook.secret },
236
+ })
237
+
238
+ ////////////////
239
+ // CONSOLE
240
+ ////////////////
241
+
242
+ const bucket = new sst.cloudflare.Bucket("ZenData")
243
+ const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
244
+
245
+ const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL")
246
+ const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
247
+ const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
248
+ const ENTERPRISE_SALES_INBOX_EMAIL = new sst.Secret("ENTERPRISE_SALES_INBOX_EMAIL")
249
+
250
+ const SALESFORCE_CLIENT_ID = new sst.Secret("SALESFORCE_CLIENT_ID")
251
+ const SALESFORCE_CLIENT_SECRET = new sst.Secret("SALESFORCE_CLIENT_SECRET")
252
+ const SALESFORCE_INSTANCE_URL = new sst.Secret("SALESFORCE_INSTANCE_URL")
253
+
254
+ const logProcessor = new sst.cloudflare.Worker("LogProcessor", {
255
+ handler: "packages/console/function/src/log-processor.ts",
256
+ link: [SECRET.HoneycombApiKey, ...(lake?.lakeIngest ? [lake.lakeIngest] : [])],
257
+ })
258
+
259
+ new sst.cloudflare.x.SolidStart("Console", {
260
+ domain,
261
+ path: "packages/console/app",
262
+ link: [
263
+ bucket,
264
+ bucketNew,
265
+ database,
266
+ SECRET.UpstashRedisRestUrl,
267
+ SECRET.UpstashRedisRestToken,
268
+ AUTH_API_URL,
269
+ consoleMigration,
270
+ STRIPE_WEBHOOK_SECRET,
271
+ SECRET.SupportApiKey,
272
+ DISCORD_INCIDENT_WEBHOOK_URL,
273
+ SECRET.HoneycombWebhookSecret,
274
+ STRIPE_SECRET_KEY,
275
+ EMAILOCTOPUS_API_KEY,
276
+ AWS_SES_ACCESS_KEY_ID,
277
+ AWS_SES_SECRET_ACCESS_KEY,
278
+ ENTERPRISE_SALES_INBOX_EMAIL,
279
+ SALESFORCE_CLIENT_ID,
280
+ SALESFORCE_CLIENT_SECRET,
281
+ SALESFORCE_INSTANCE_URL,
282
+ ZEN_BLACK_PRICE,
283
+ ZEN_LITE_PRICE,
284
+ new sst.Secret("ZEN_LIMITS"),
285
+ new sst.Secret("ZEN_SESSION_SECRET"),
286
+ ...ZEN_MODELS,
287
+ ...($dev
288
+ ? [
289
+ new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
290
+ new sst.Secret("CLOUDFLARE_API_TOKEN", process.env.CLOUDFLARE_API_TOKEN!),
291
+ ]
292
+ : []),
293
+ ],
294
+ environment: {
295
+ //VITE_DOCS_URL: web.url.apply((url) => url!),
296
+ //VITE_API_URL: gateway.url.apply((url) => url!),
297
+ VITE_AUTH_URL: auth.url.apply((url) => url!),
298
+ VITE_STRIPE_PUBLISHABLE_KEY: STRIPE_PUBLISHABLE_KEY.value,
299
+ },
300
+ transform: {
301
+ server: {
302
+ placement: { region: "aws:us-east-2" },
303
+ transform: {
304
+ worker: (args) => {
305
+ args.compatibilityFlags = $resolve(args.compatibilityFlags).apply((flags) => [
306
+ ...(flags ?? []),
307
+ "global_fetch_strictly_public",
308
+ ])
309
+ args.tailConsumers = [{ service: logProcessor.nodes.worker.scriptName }]
310
+ },
311
+ },
312
+ },
313
+ },
314
+ })
315
+
316
+ ////////////////
317
+ // HELPERS
318
+ ////////////////
319
+
320
+ export const stat = new sst.cloudflare.Worker("Stat", {
321
+ handler: "packages/console/function/src/stat.ts",
322
+ link: [database],
323
+ url: true,
324
+ })
infra/enterprise.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SECRET } from "./secret"
2
+ import { shortDomain } from "./stage"
3
+
4
+ const storage = new sst.cloudflare.Bucket("EnterpriseStorage")
5
+
6
+ new sst.cloudflare.x.SolidStart("Teams", {
7
+ domain: shortDomain,
8
+ path: "packages/enterprise",
9
+ buildCommand: "bun run build:cloudflare",
10
+ link: [SECRET.SupportApiKey],
11
+ environment: {
12
+ OPENCODE_STORAGE_ADAPTER: "r2",
13
+ OPENCODE_STORAGE_ACCOUNT_ID: sst.cloudflare.DEFAULT_ACCOUNT_ID,
14
+ OPENCODE_STORAGE_ACCESS_KEY_ID: SECRET.R2AccessKey.value,
15
+ OPENCODE_STORAGE_SECRET_ACCESS_KEY: SECRET.R2SecretKey.value,
16
+ OPENCODE_STORAGE_BUCKET: storage.name,
17
+ },
18
+ })
infra/lake.ts ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { domain } from "./stage"
2
+
3
+ const current = aws.getCallerIdentityOutput({})
4
+ const partition = aws.getPartitionOutput({})
5
+ const region = aws.getRegionOutput({})
6
+
7
+ const tableBucketName = `opencode-${$app.stage}-lake`
8
+ const glueCatalogName = "s3tablescatalog"
9
+ const glueCatalogArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:catalog`
10
+ const glueS3TablesCatalogArn = $interpolate`${glueCatalogArn}/${glueCatalogName}`
11
+ const glueS3TablesChildCatalogArn = $interpolate`${glueS3TablesCatalogArn}/${tableBucketName}`
12
+ const glueS3TablesDatabaseWildcardArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/${glueCatalogName}/${tableBucketName}/*`
13
+ const glueS3TablesTableWildcardArn = $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/${tableBucketName}/*/*`
14
+ const s3TablesBucketWildcardArn = $interpolate`arn:${partition.partition}:s3tables:${region.region}:${current.accountId}:bucket/*`
15
+
16
+ export const tableBucket = new aws.s3tables.TableBucket("LakeTableBucket", {
17
+ name: tableBucketName,
18
+ forceDestroy: $app.stage !== "production",
19
+ })
20
+
21
+ const s3TablesCatalog = new aws.cloudcontrol.Resource(
22
+ "LakeS3TablesCatalog",
23
+ {
24
+ typeName: "AWS::Glue::Catalog",
25
+ desiredState: $jsonStringify({
26
+ Name: glueCatalogName,
27
+ Description: "Federated catalog for S3 Tables",
28
+ FederatedCatalog: {
29
+ Identifier: s3TablesBucketWildcardArn,
30
+ ConnectionName: "aws:s3tables",
31
+ },
32
+ CreateDatabaseDefaultPermissions: [
33
+ {
34
+ Principal: {
35
+ DataLakePrincipalIdentifier: "IAM_ALLOWED_PRINCIPALS",
36
+ },
37
+ Permissions: ["ALL"],
38
+ },
39
+ ],
40
+ CreateTableDefaultPermissions: [
41
+ {
42
+ Principal: {
43
+ DataLakePrincipalIdentifier: "IAM_ALLOWED_PRINCIPALS",
44
+ },
45
+ Permissions: ["ALL"],
46
+ },
47
+ ],
48
+ AllowFullTableExternalDataAccess: "True",
49
+ }),
50
+ },
51
+ { dependsOn: [tableBucket] },
52
+ )
53
+
54
+ const athenaResultsBucket = new aws.s3.Bucket("LakeAthenaResults", {
55
+ bucket: `opencode-${$app.stage}-lake-athena-results`,
56
+ forceDestroy: $app.stage !== "production",
57
+ })
58
+
59
+ const firehoseErrorBucket = new aws.s3.Bucket("LakeFirehoseErrors", {
60
+ bucket: `opencode-${$app.stage}-lake-firehose-errors`,
61
+ forceDestroy: $app.stage !== "production",
62
+ })
63
+
64
+ const athenaWorkgroup = new aws.athena.Workgroup("LakeAthenaWorkgroup", {
65
+ name: `opencode-${$app.stage}-lake-workgroup`,
66
+ forceDestroy: $app.stage !== "production",
67
+ configuration: {
68
+ enforceWorkgroupConfiguration: true,
69
+ publishCloudwatchMetricsEnabled: true,
70
+ // Athena bills $5/TB scanned; kill any query that would scan more than 2 TB
71
+ // so a regression cannot silently burn money. Stats sync full passes scan
72
+ // ~250 GB as of 2026-07.
73
+ bytesScannedCutoffPerQuery: 2 * 1024 ** 4,
74
+ resultConfiguration: {
75
+ outputLocation: $interpolate`s3://${athenaResultsBucket.bucket}/`,
76
+ },
77
+ },
78
+ })
79
+
80
+ const firehoseRole = new aws.iam.Role("LakeFirehoseRole", {
81
+ assumeRolePolicy: aws.iam.getPolicyDocumentOutput({
82
+ statements: [
83
+ {
84
+ effect: "Allow",
85
+ actions: ["sts:AssumeRole"],
86
+ principals: [
87
+ {
88
+ type: "Service",
89
+ identifiers: ["firehose.amazonaws.com"],
90
+ },
91
+ ],
92
+ },
93
+ ],
94
+ }).json,
95
+ })
96
+
97
+ const firehosePolicy = new aws.iam.RolePolicy("LakeFirehosePolicy", {
98
+ role: firehoseRole.id,
99
+ policy: aws.iam.getPolicyDocumentOutput({
100
+ statements: [
101
+ {
102
+ effect: "Allow",
103
+ actions: [
104
+ "s3tables:ListTableBuckets",
105
+ "s3tables:GetTableBucket",
106
+ "s3tables:GetNamespace",
107
+ "s3tables:GetTable",
108
+ "s3tables:GetTableData",
109
+ "s3tables:GetTableMetadataLocation",
110
+ "s3tables:ListNamespaces",
111
+ "s3tables:ListTables",
112
+ "s3tables:PutTableData",
113
+ "s3tables:UpdateTableMetadataLocation",
114
+ ],
115
+ resources: ["*"],
116
+ },
117
+ {
118
+ effect: "Allow",
119
+ actions: [
120
+ "glue:GetCatalog",
121
+ "glue:GetCatalogs",
122
+ "glue:GetDatabase",
123
+ "glue:GetDatabases",
124
+ "glue:GetTable",
125
+ "glue:GetTables",
126
+ "glue:UpdateTable",
127
+ ],
128
+ resources: [
129
+ glueCatalogArn,
130
+ glueS3TablesCatalogArn,
131
+ $interpolate`${glueS3TablesCatalogArn}/*`,
132
+ glueS3TablesDatabaseWildcardArn,
133
+ glueS3TablesTableWildcardArn,
134
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/*`,
135
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/*/*`,
136
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/*`,
137
+ ],
138
+ },
139
+ {
140
+ effect: "Allow",
141
+ actions: [
142
+ "s3:AbortMultipartUpload",
143
+ "s3:GetBucketLocation",
144
+ "s3:GetObject",
145
+ "s3:ListBucket",
146
+ "s3:ListBucketMultipartUploads",
147
+ "s3:PutObject",
148
+ ],
149
+ resources: [firehoseErrorBucket.arn, $interpolate`${firehoseErrorBucket.arn}/*`],
150
+ },
151
+ {
152
+ effect: "Allow",
153
+ actions: ["lakeformation:GetDataAccess"],
154
+ resources: ["*"],
155
+ },
156
+ ],
157
+ }).json,
158
+ })
159
+
160
+ const firehose = new aws.kinesis.FirehoseDeliveryStream(
161
+ "LakeFirehose",
162
+ {
163
+ name: `opencode-${$app.stage}-lake-ingest`,
164
+ destination: "iceberg",
165
+ icebergConfiguration: {
166
+ appendOnly: true,
167
+ bufferingInterval: 60,
168
+ bufferingSize: 1,
169
+ catalogArn: glueS3TablesChildCatalogArn,
170
+ processingConfiguration: {
171
+ enabled: true,
172
+ processors: [
173
+ {
174
+ type: "MetadataExtraction",
175
+ parameters: [
176
+ { parameterName: "JsonParsingEngine", parameterValue: "JQ-1.6" },
177
+ {
178
+ parameterName: "MetadataExtractionQuery",
179
+ parameterValue:
180
+ '{destinationDatabaseName:._lake_database,destinationTableName:._lake_table,operation:(._lake_operation // "insert")}',
181
+ },
182
+ ],
183
+ },
184
+ ],
185
+ },
186
+ roleArn: firehoseRole.arn,
187
+ s3BackupMode: "FailedDataOnly",
188
+ s3Configuration: {
189
+ roleArn: firehoseRole.arn,
190
+ bucketArn: firehoseErrorBucket.arn,
191
+ errorOutputPrefix: "errors/!{firehose:error-output-type}/",
192
+ },
193
+ },
194
+ },
195
+ { dependsOn: [s3TablesCatalog, firehosePolicy] },
196
+ )
197
+
198
+ export const lakeVpc = new sst.aws.Vpc("LakeVpc")
199
+ export const lakeCluster = new sst.aws.Cluster("LakeCluster", { vpc: lakeVpc })
200
+ export const lakeRegion = region.region
201
+ export const lakeCatalog = $interpolate`${glueCatalogName}/${tableBucket.name}`
202
+ export const lakeAthenaWorkgroup = athenaWorkgroup
203
+
204
+ const ingestSecret = new random.RandomPassword("LakeIngestSecret", { length: 32 })
205
+ export const ingestSecretSsm = new aws.ssm.Parameter("LakeIngestSecretSsm", {
206
+ name: $interpolate`/${$app.name}/${$app.stage}/lake/ingest/secret`,
207
+ type: "SecureString",
208
+ value: ingestSecret.result,
209
+ })
210
+
211
+ const ingestConfig = new sst.Linkable("LakeIngestConfig", {
212
+ properties: {
213
+ streamName: firehose.name,
214
+ secret: ingestSecret.result,
215
+ },
216
+ })
217
+
218
+ const ingestService = new sst.aws.Service("LakeIngestService", {
219
+ cluster: lakeCluster,
220
+ architecture: "arm64",
221
+ cpu: "1 vCPU",
222
+ memory: "4 GB",
223
+ image: {
224
+ context: ".",
225
+ dockerfile: "packages/stats/server/Dockerfile",
226
+ },
227
+ link: [ingestConfig],
228
+ permissions: [
229
+ {
230
+ actions: ["firehose:PutRecord", "firehose:PutRecordBatch"],
231
+ resources: [firehose.arn],
232
+ },
233
+ ],
234
+ scaling: {
235
+ min: $app.stage === "production" ? 2 : 1,
236
+ max: $app.stage === "production" ? 32 : 4,
237
+ cpuUtilization: 60,
238
+ memoryUtilization: 70,
239
+ },
240
+ loadBalancer: {
241
+ domain: {
242
+ name: `lake.${domain}`,
243
+ dns: sst.cloudflare.dns(),
244
+ },
245
+ rules: [
246
+ { listen: "80/http", redirect: "443/https" },
247
+ { listen: "443/https", forward: "3000/http" },
248
+ ],
249
+ health: {
250
+ "3000/http": {
251
+ path: "/ready",
252
+ successCodes: "200-299",
253
+ },
254
+ },
255
+ },
256
+ health: {
257
+ command: [
258
+ "CMD-SHELL",
259
+ "bun --eval \"fetch('http://localhost:3000/health').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\"",
260
+ ],
261
+ interval: "30 seconds",
262
+ retries: 3,
263
+ startPeriod: "30 seconds",
264
+ timeout: "5 seconds",
265
+ },
266
+ dev: {
267
+ command: "bun run start",
268
+ directory: "packages/stats/server",
269
+ url: "http://localhost:3000",
270
+ },
271
+ wait: $app.stage === "production",
272
+ })
273
+
274
+ export const lakeIngest = new sst.Linkable("LakeIngest", {
275
+ properties: {
276
+ url: ingestService.url,
277
+ secret: ingestSecret.result,
278
+ },
279
+ })
280
+
281
+ export const lakeQueryPermissions = [
282
+ {
283
+ actions: ["athena:StartQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResults"],
284
+ resources: [athenaWorkgroup.arn],
285
+ },
286
+ {
287
+ actions: [
288
+ "glue:GetCatalog",
289
+ "glue:GetCatalogs",
290
+ "glue:GetDatabase",
291
+ "glue:GetDatabases",
292
+ "glue:GetTable",
293
+ "glue:GetTables",
294
+ "glue:GetPartitions",
295
+ ],
296
+ resources: [
297
+ glueCatalogArn,
298
+ glueS3TablesCatalogArn,
299
+ $interpolate`${glueS3TablesCatalogArn}/*`,
300
+ glueS3TablesDatabaseWildcardArn,
301
+ glueS3TablesTableWildcardArn,
302
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:database/*`,
303
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/*/*`,
304
+ $interpolate`arn:${partition.partition}:glue:${region.region}:${current.accountId}:table/${glueCatalogName}/*`,
305
+ ],
306
+ },
307
+ {
308
+ actions: ["s3:GetBucketLocation", "s3:ListBucket"],
309
+ resources: [athenaResultsBucket.arn],
310
+ },
311
+ {
312
+ actions: ["s3:GetObject", "s3:PutObject", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads"],
313
+ resources: [$interpolate`${athenaResultsBucket.arn}/*`],
314
+ },
315
+ {
316
+ actions: [
317
+ "s3tables:GetTableBucket",
318
+ "s3tables:GetNamespace",
319
+ "s3tables:GetTable",
320
+ "s3tables:GetTableData",
321
+ "s3tables:GetTableMetadataLocation",
322
+ "s3tables:ListNamespaces",
323
+ "s3tables:ListTables",
324
+ ],
325
+ resources: ["*"],
326
+ },
327
+ {
328
+ actions: ["lakeformation:GetDataAccess"],
329
+ resources: ["*"],
330
+ },
331
+ ]
infra/monitoring.ts ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SECRET } from "./secret"
2
+ import { domain } from "./stage"
3
+
4
+ const description = "Managed by SST (Don't edit in Honeycomb UI)"
5
+ const alertsDisabled = $app.stage !== "production"
6
+
7
+ const webhookRecipient = new honeycombio.WebhookRecipient("DiscordAlerts", {
8
+ name: $app.stage === "production" ? "Discord Alerts" : `Discord Alerts (${$app.stage})`,
9
+ url: `https://${domain}/honeycomb/webhook`,
10
+ secret: SECRET.HoneycombWebhookSecret.result,
11
+ templates: [
12
+ {
13
+ type: "trigger",
14
+ body: `{
15
+ "url": {{ .Result.URL | quote }},
16
+ "type": {{ .Vars.type | quote }},
17
+ "name": {{ .Name | quote }},
18
+ "status": {{ .Alert.Status | quote }},
19
+ "isTest": {{ .Alert.IsTest }},
20
+ "groups": {{ .Result.GroupsTriggered | toJson }}
21
+ }`,
22
+ },
23
+ ],
24
+ variables: [
25
+ {
26
+ name: "type",
27
+ },
28
+ ],
29
+ })
30
+
31
+ // Honeycomb can keep stale query-local calculated fields when the name is unchanged,
32
+ // so tie the field name to the expression while avoiding deploy-to-deploy churn.
33
+ // https://github.com/honeycombio/terraform-provider-honeycombio/issues/852
34
+ const calculatedField = (field: { name: string; expression: string }) => ({
35
+ ...field,
36
+ name: `${field.name}_${(
37
+ Array.from(field.expression).reduce((result, char) => Math.imul(31, result) + char.charCodeAt(0), 0) >>> 0
38
+ ).toString(36)}`,
39
+ })
40
+
41
+ const modelHttpErrorsQuery = (product: "go" | "zen") => {
42
+ const filters = [
43
+ { column: "model", op: "exists" },
44
+ { column: "event_type", op: "=", value: "completions" },
45
+ { column: "user_agent", op: "contains", value: "opencode" },
46
+ { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
47
+ ]
48
+ const failedHttpStatus = calculatedField({
49
+ name: "is_failed_http_status",
50
+ expression: `
51
+ IF(
52
+ AND(
53
+ GTE($status, "400"),
54
+ NOT(EQUALS($status, "401")),
55
+ NOT(
56
+ AND(
57
+ EQUALS($status, "429"),
58
+ OR(
59
+ EQUALS($error.type, "GoUsageLimitError"),
60
+ EQUALS($error.type, "FreeUsageLimitError")
61
+ )
62
+ )
63
+ )
64
+ ),
65
+ 1,
66
+ 0
67
+ )`,
68
+ })
69
+
70
+ return honeycombio.getQuerySpecificationOutput({
71
+ breakdowns: ["model"],
72
+ calculatedFields: [failedHttpStatus],
73
+ calculations: [
74
+ { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
75
+ {
76
+ op: "SUM",
77
+ name: "FAILED",
78
+ column: failedHttpStatus.name,
79
+ filterCombination: "AND",
80
+ filters,
81
+ },
82
+ ],
83
+ formulas: [{ name: "ERROR", expression: "IF(GTE($TOTAL, 150), DIV($FAILED, $TOTAL), 0)" }],
84
+ timeRange: 900,
85
+ }).json
86
+ }
87
+
88
+ const providerHttpErrorsQuery = () => {
89
+ const filters = [
90
+ { column: "provider", op: "exists" },
91
+ { column: "user_agent", op: "contains", value: "opencode" },
92
+ ]
93
+ const successHttpStatus = calculatedField({
94
+ name: "is_success_http_status",
95
+ expression: `IF(AND(GTE($status, "200"), LT($status, "400")), 1, 0)`,
96
+ })
97
+ const failedProviderHttpStatus = calculatedField({
98
+ name: "is_failed_provider_http_status",
99
+ expression: `IF(GT($llm.error.code, "400"), 1, 0)`,
100
+ })
101
+
102
+ return honeycombio.getQuerySpecificationOutput({
103
+ breakdowns: ["provider"],
104
+ calculatedFields: [successHttpStatus, failedProviderHttpStatus],
105
+ calculations: [
106
+ {
107
+ op: "SUM",
108
+ name: "SUCCESS",
109
+ column: successHttpStatus.name,
110
+ filterCombination: "AND",
111
+ filters: [...filters, { column: "event_type", op: "=", value: "completions" }],
112
+ },
113
+ {
114
+ op: "SUM",
115
+ name: "FAILED",
116
+ column: failedProviderHttpStatus.name,
117
+ filterCombination: "AND",
118
+ filters: [
119
+ ...filters,
120
+ { column: "event_type", op: "=", value: "llm.error" },
121
+ { column: "llm.error.code", op: "!=", value: "404" },
122
+ ],
123
+ },
124
+ ],
125
+ formulas: [
126
+ { name: "ERROR", expression: "IF(GTE(SUM($SUCCESS, $FAILED), 150), DIV($FAILED, SUM($SUCCESS, $FAILED)), 0)" },
127
+ ],
128
+ timeRange: 900,
129
+ }).json
130
+ }
131
+
132
+ const modelLowTpsQuery = (product: "go" | "zen") => {
133
+ const filters = [
134
+ { column: "model", op: "exists" },
135
+ { column: "event_type", op: "=", value: "completions" },
136
+ { column: "user_agent", op: "contains", value: "opencode" },
137
+ { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
138
+ { column: "status", op: ">=", value: "200" },
139
+ { column: "status", op: "<", value: "400" },
140
+ { column: "tps.output", op: "exists" },
141
+ ]
142
+
143
+ return honeycombio.getQuerySpecificationOutput({
144
+ breakdowns: ["model"],
145
+ calculations: [
146
+ { op: "COUNT", name: "TOTAL", filterCombination: "AND", filters },
147
+ {
148
+ op: "P50",
149
+ name: "TPS",
150
+ column: "tps.output",
151
+ filterCombination: "AND",
152
+ filters,
153
+ },
154
+ ],
155
+ formulas: [{ name: "LOW_TPS", expression: "IF(GTE($TOTAL, 100), $TPS, 999)" }],
156
+ timeRange: 1800,
157
+ }).json
158
+ }
159
+
160
+ new honeycombio.Trigger("IncreasedModelHttpErrorsGo", {
161
+ name: "Increased Model HTTP Errors [Go]",
162
+ description,
163
+ disabled: alertsDisabled,
164
+ queryJson: modelHttpErrorsQuery("go"),
165
+ alertType: "on_change",
166
+ frequency: 300,
167
+ thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
168
+ recipients: [
169
+ {
170
+ id: webhookRecipient.id,
171
+ notificationDetails: [
172
+ {
173
+ variables: [{ name: "type", value: "model_http_errors" }],
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ })
179
+
180
+ new honeycombio.Trigger("IncreasedModelHttpErrorsZen", {
181
+ name: "Increased Model HTTP Errors [Zen]",
182
+ description,
183
+ disabled: alertsDisabled,
184
+ queryJson: modelHttpErrorsQuery("zen"),
185
+ alertType: "on_change",
186
+ frequency: 300,
187
+ thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
188
+ recipients: [
189
+ {
190
+ id: webhookRecipient.id,
191
+ notificationDetails: [
192
+ {
193
+ variables: [{ name: "type", value: "model_http_errors" }],
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ })
199
+
200
+ new honeycombio.Trigger("LowModelTpsGo", {
201
+ name: "Low Model TPS [Go]",
202
+ description,
203
+ disabled: alertsDisabled,
204
+ queryJson: modelLowTpsQuery("go"),
205
+ alertType: "on_change",
206
+ frequency: 600,
207
+ thresholds: [{ op: "<=", value: 10, exceededLimit: 1 }],
208
+ recipients: [
209
+ {
210
+ id: webhookRecipient.id,
211
+ notificationDetails: [
212
+ {
213
+ variables: [{ name: "type", value: "model_low_tps" }],
214
+ },
215
+ ],
216
+ },
217
+ ],
218
+ })
219
+
220
+ new honeycombio.Trigger("LowModelTpsZen", {
221
+ name: "Low Model TPS [Zen]",
222
+ description,
223
+ disabled: alertsDisabled,
224
+ queryJson: modelLowTpsQuery("zen"),
225
+ alertType: "on_change",
226
+ frequency: 600,
227
+ thresholds: [{ op: "<=", value: 10, exceededLimit: 1 }],
228
+ recipients: [
229
+ {
230
+ id: webhookRecipient.id,
231
+ notificationDetails: [
232
+ {
233
+ variables: [{ name: "type", value: "model_low_tps" }],
234
+ },
235
+ ],
236
+ },
237
+ ],
238
+ })
239
+
240
+ new honeycombio.Trigger("IncreasedProviderHttpErrors", {
241
+ name: "Increased Provider HTTP Errors",
242
+ description,
243
+ disabled: alertsDisabled,
244
+ queryJson: providerHttpErrorsQuery(),
245
+ alertType: "on_change",
246
+ frequency: 300,
247
+ thresholds: [{ op: ">=", value: 0.7, exceededLimit: 1 }],
248
+ recipients: [
249
+ {
250
+ id: webhookRecipient.id,
251
+ notificationDetails: [
252
+ {
253
+ variables: [{ name: "type", value: "provider_http_errors" }],
254
+ },
255
+ ],
256
+ },
257
+ ],
258
+ })
259
+
260
+ new honeycombio.Trigger("IncreasedFreeTierRequests", {
261
+ name: "Increased Free Tier Requests",
262
+ description,
263
+ disabled: alertsDisabled,
264
+ queryJson: honeycombio.getQuerySpecificationOutput({
265
+ calculations: [{ op: "COUNT" }],
266
+ filters: [
267
+ { column: "event_type", op: "=", value: "completions" },
268
+ { column: "user_agent", op: "contains", value: "opencode" },
269
+ { column: "isFreeTier", op: "=", value: "true" },
270
+ ],
271
+ timeRange: 3600,
272
+ }).json,
273
+ alertType: "on_change",
274
+ frequency: 900,
275
+ thresholds: [{ op: ">=", value: 50, exceededLimit: 1 }],
276
+ baselineDetails: [{ type: "percentage", offsetMinutes: 1440 }],
277
+ recipients: [
278
+ {
279
+ id: webhookRecipient.id,
280
+ notificationDetails: [
281
+ {
282
+ variables: [{ name: "type", value: "custom" }],
283
+ },
284
+ ],
285
+ },
286
+ ],
287
+ })
infra/secret.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ sst.Linkable.wrap(random.RandomPassword, (resource) => ({
2
+ properties: {
3
+ value: resource.result,
4
+ },
5
+ }))
6
+
7
+ export const SECRET = {
8
+ R2AccessKey: new sst.Secret("R2AccessKey", "unknown"),
9
+ R2SecretKey: new sst.Secret("R2SecretKey", "unknown"),
10
+ HoneycombApiKey: new sst.Secret("HONEYCOMB_API_KEY"),
11
+ HoneycombWebhookSecret: new random.RandomPassword("HoneycombWebhookSecret", { length: 24 }),
12
+ SupportApiKey: new sst.Secret("SUPPORT_API_KEY"),
13
+ UpstashRedisRestUrl: new sst.Secret("UpstashRedisRestUrl"),
14
+ UpstashRedisRestToken: new sst.Secret("UpstashRedisRestToken"),
15
+ }
infra/stage.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const domain = (() => {
2
+ if ($app.stage === "production") return "opencode.ai"
3
+ if ($app.stage === "dev") return "dev.opencode.ai"
4
+ return `${$app.stage}.dev.opencode.ai`
5
+ })()
6
+
7
+ export const zoneID = "430ba34c138cfb5360826c4909f99be8"
8
+ export const awsStage = $app.stage === "production" ? "production" : "dev"
9
+ export const deployAws = $app.stage === awsStage
10
+
11
+ if ($app.stage === "production") {
12
+ new cloudflare.DnsRecord("TrustCenter", {
13
+ zoneId: zoneID,
14
+ name: "trust.opencode.ai",
15
+ type: "CNAME",
16
+ content: "3a69a5bb27875189.vercel-dns-016.com",
17
+ proxied: false,
18
+ ttl: 60,
19
+ })
20
+
21
+ new cloudflare.DnsRecord("TrustCenterVerification", {
22
+ zoneId: zoneID,
23
+ name: "opencode.ai",
24
+ type: "TXT",
25
+ content: "compai-domain-verification=org_6993a99c6200a2d642bb115d",
26
+ ttl: 60,
27
+ })
28
+ }
29
+
30
+ new cloudflare.RegionalHostname("RegionalHostname", {
31
+ hostname: domain,
32
+ regionKey: "us",
33
+ zoneId: zoneID,
34
+ })
35
+
36
+ export const shortDomain = (() => {
37
+ if ($app.stage === "production") return "opncd.ai"
38
+ if ($app.stage === "dev") return "dev.opncd.ai"
39
+ return `${$app.stage}.dev.opncd.ai`
40
+ })()
infra/stats.ts ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { lakeAthenaWorkgroup, lakeCatalog, lakeCluster, lakeQueryPermissions, lakeRegion, tableBucket } from "./lake"
2
+ import { EMAILOCTOPUS_API_KEY } from "./app"
3
+ import { domain } from "./stage"
4
+
5
+ ////////////////
6
+ // LAKE
7
+ ////////////////
8
+
9
+ const inferenceNamespace = new aws.s3tables.Namespace("LakeInferenceNamespace", {
10
+ namespace: "inference",
11
+ tableBucketArn: tableBucket.arn,
12
+ })
13
+
14
+ const inferenceEventTable = new aws.s3tables.Table(
15
+ "LakeInferenceEventTable",
16
+ {
17
+ name: "event",
18
+ namespace: inferenceNamespace.namespace,
19
+ tableBucketArn: inferenceNamespace.tableBucketArn,
20
+ format: "ICEBERG",
21
+ metadata: {
22
+ iceberg: {
23
+ schema: {
24
+ fields: [
25
+ { name: "event_timestamp", type: "string", required: false },
26
+ { name: "event_date", type: "string", required: false },
27
+ { name: "event_type", type: "string", required: false },
28
+ { name: "dataset", type: "string", required: false },
29
+ { name: "cf_continent", type: "string", required: false },
30
+ { name: "cf_country", type: "string", required: false },
31
+ { name: "cf_city", type: "string", required: false },
32
+ { name: "cf_region", type: "string", required: false },
33
+ { name: "cf_latitude", type: "double", required: false },
34
+ { name: "cf_longitude", type: "double", required: false },
35
+ { name: "cf_timezone", type: "string", required: false },
36
+ { name: "duration", type: "double", required: false },
37
+ { name: "request_length", type: "long", required: false },
38
+ { name: "status", type: "int", required: false },
39
+ { name: "ip", type: "string", required: false },
40
+ { name: "is_stream", type: "boolean", required: false },
41
+ { name: "session", type: "string", required: false },
42
+ { name: "request", type: "string", required: false },
43
+ { name: "client", type: "string", required: false },
44
+ { name: "user_agent", type: "string", required: false },
45
+ { name: "model", type: "string", required: false },
46
+ { name: "model_tier", type: "string", required: false },
47
+ { name: "model_variant", type: "string", required: false },
48
+ { name: "source", type: "string", required: false },
49
+ { name: "provider", type: "string", required: false },
50
+ { name: "provider_model", type: "string", required: false },
51
+ { name: "llm_error_code", type: "int", required: false },
52
+ { name: "llm_error_message", type: "string", required: false },
53
+ { name: "error_response", type: "string", required: false },
54
+ { name: "error_type", type: "string", required: false },
55
+ { name: "error_message", type: "string", required: false },
56
+ { name: "error_cause", type: "string", required: false },
57
+ { name: "error_cause2", type: "string", required: false },
58
+ { name: "api_key", type: "string", required: false },
59
+ { name: "workspace", type: "string", required: false },
60
+ { name: "user_id", type: "string", required: false },
61
+ { name: "is_subscription", type: "boolean", required: false },
62
+ { name: "subscription", type: "string", required: false },
63
+ { name: "response_length", type: "long", required: false },
64
+ { name: "time_to_first_byte", type: "long", required: false },
65
+ { name: "timestamp_first_byte", type: "long", required: false },
66
+ { name: "timestamp_last_byte", type: "long", required: false },
67
+ { name: "tokens_input", type: "long", required: false },
68
+ { name: "tokens_output", type: "long", required: false },
69
+ { name: "tokens_reasoning", type: "long", required: false },
70
+ { name: "tokens_cache_read", type: "long", required: false },
71
+ { name: "tokens_cache_write_5m", type: "long", required: false },
72
+ { name: "tokens_cache_write_1h", type: "long", required: false },
73
+ { name: "cost_input_microcents", type: "long", required: false },
74
+ { name: "cost_output_microcents", type: "long", required: false },
75
+ { name: "cost_cache_read_microcents", type: "long", required: false },
76
+ { name: "cost_cache_write_microcents", type: "long", required: false },
77
+ { name: "cost_total_microcents", type: "long", required: false },
78
+ { name: "cost_input", type: "long", required: false },
79
+ { name: "cost_output", type: "long", required: false },
80
+ { name: "cost_cache_read", type: "long", required: false },
81
+ { name: "cost_cache_write_5m", type: "long", required: false },
82
+ { name: "cost_cache_write_1h", type: "long", required: false },
83
+ { name: "cost_total", type: "long", required: false },
84
+ ],
85
+ },
86
+ },
87
+ },
88
+ },
89
+ { deleteBeforeReplace: $app.stage !== "production", ignoreChanges: ["metadata"] },
90
+ )
91
+
92
+ export const inferenceEvent = new sst.Linkable("InferenceEvent", {
93
+ properties: {
94
+ region: lakeRegion,
95
+ catalog: lakeCatalog,
96
+ database: inferenceNamespace.namespace,
97
+ table: inferenceEventTable.name,
98
+ tableBucket: tableBucket.name,
99
+ workgroup: lakeAthenaWorkgroup.name,
100
+ },
101
+ })
102
+
103
+ ////////////////
104
+ // DATABASE
105
+ ////////////////
106
+
107
+ const cluster = planetscale.getDatabaseOutput({
108
+ name: "opencode-stats",
109
+ organization: "anomalyco",
110
+ })
111
+
112
+ const branch =
113
+ $app.stage === "production"
114
+ ? planetscale.getBranchOutput({
115
+ name: "production",
116
+ organization: cluster.organization,
117
+ database: cluster.name,
118
+ })
119
+ : new planetscale.Branch("StatsDatabaseBranch", {
120
+ database: cluster.name,
121
+ organization: cluster.organization,
122
+ name: $app.stage,
123
+ parentBranch: "production",
124
+ })
125
+
126
+ const password = new planetscale.Password("StatsDatabasePassword", {
127
+ name: $app.stage,
128
+ database: cluster.name,
129
+ organization: cluster.organization,
130
+ branch: branch.name,
131
+ })
132
+
133
+ const databaseUrl = $interpolate`mysql://${password.username.apply(encodeURIComponent)}:${password.plaintext.apply(
134
+ encodeURIComponent,
135
+ )}@${password.accessHostUrl}/${cluster.name}`
136
+
137
+ export const database = new sst.Linkable("StatsDatabase", {
138
+ properties: {
139
+ host: password.accessHostUrl,
140
+ database: cluster.name,
141
+ username: password.username,
142
+ password: password.plaintext,
143
+ port: 3306,
144
+ url: databaseUrl,
145
+ },
146
+ })
147
+
148
+ new sst.x.DevCommand("StatsStudio", {
149
+ link: [database],
150
+ environment: {
151
+ DATABASE_URL: databaseUrl,
152
+ },
153
+ dev: {
154
+ command: "bun db:studio",
155
+ directory: "packages/stats/core",
156
+ autostart: false,
157
+ },
158
+ })
159
+
160
+ ////////////////
161
+ // APP
162
+ ////////////////
163
+
164
+ export const app = new sst.cloudflare.x.SolidStart("Stats", {
165
+ path: "packages/stats/app",
166
+ buildCommand: "bun run build",
167
+ domain: `stats.${domain}`,
168
+ link: [database, EMAILOCTOPUS_API_KEY],
169
+ environment: {
170
+ PUBLIC_URL: `https://${domain}/data`,
171
+ },
172
+ })
173
+
174
+ ////////////////
175
+ // SERVICES
176
+ ////////////////
177
+
178
+ const statsSyncConfig = new sst.Linkable("StatsSyncConfig", {
179
+ properties: {
180
+ dataset: "zen",
181
+ },
182
+ })
183
+
184
+ const r2SqlAuthToken = new sst.Secret("R2SqlAuthToken")
185
+ const r2Sql = new sst.Linkable("R2Sql", {
186
+ properties: {
187
+ accountId: "15d29c8639fd3733b1b5486a2acfd968",
188
+ bucket: `platform-${$app.stage}-lake`,
189
+ namespace: "inference",
190
+ table: "generation",
191
+ },
192
+ })
193
+
194
+ export const statSync = new sst.aws.Service("StatsSyncService", {
195
+ cluster: lakeCluster,
196
+ architecture: "arm64",
197
+ cpu: "0.25 vCPU",
198
+ // 0.5 GB caused an OOM crash loop: every restart immediately re-ran the 4 Athena
199
+ // stats queries (~$5/pass) every ~5 minutes instead of hourly.
200
+ memory: "2 GB",
201
+ image: {
202
+ context: ".",
203
+ dockerfile: "packages/stats/server/Dockerfile",
204
+ },
205
+ command: ["bun", "src/stat-sync.ts"],
206
+ // Keep the legacy Athena link and IAM permissions during the first R2-backed
207
+ // release so reverting the application code remains a one-deploy rollback.
208
+ link: [database, inferenceEvent, r2Sql, r2SqlAuthToken, statsSyncConfig],
209
+ permissions: lakeQueryPermissions,
210
+ scaling: {
211
+ min: 1,
212
+ max: 1,
213
+ },
214
+ dev: {
215
+ command: "bun src/stat-sync.ts",
216
+ directory: "packages/stats/server",
217
+ autostart: false,
218
+ },
219
+ })
nix/desktop.nix ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ lib,
3
+ stdenv,
4
+ bun,
5
+ nodejs,
6
+ darwin,
7
+ electron_41,
8
+ makeWrapper,
9
+ writableTmpDirAsHomeHook,
10
+ autoPatchelfHook,
11
+ copyDesktopItems,
12
+ makeDesktopItem,
13
+ opencode,
14
+ }:
15
+ let
16
+ electron = electron_41;
17
+ in
18
+ stdenv.mkDerivation (finalAttrs: {
19
+ pname = "opencode-desktop";
20
+ inherit (opencode)
21
+ version
22
+ src
23
+ node_modules
24
+ patches
25
+ ;
26
+
27
+ nativeBuildInputs = [
28
+ bun
29
+ nodejs
30
+ makeWrapper
31
+ writableTmpDirAsHomeHook
32
+ ]
33
+ ++ lib.optionals stdenv.hostPlatform.isLinux [
34
+ autoPatchelfHook
35
+ copyDesktopItems
36
+ ]
37
+ ++ lib.optionals stdenv.hostPlatform.isDarwin [
38
+ # Ad-hoc sign the .app: --config.mac.identity=null below skips signing.
39
+ darwin.autoSignDarwinBinariesHook
40
+ ];
41
+
42
+ buildInputs = lib.optionals stdenv.hostPlatform.isLinux [
43
+ (lib.getLib stdenv.cc.cc)
44
+ ];
45
+
46
+ desktopItems = lib.optional stdenv.hostPlatform.isLinux (makeDesktopItem {
47
+ name = "ai.opencode.desktop";
48
+ desktopName = "OpenCode";
49
+ exec = "opencode-desktop %U";
50
+ icon = "ai.opencode.desktop";
51
+ # Electron 41 derives X11 WM_CLASS from app.name.
52
+ startupWMClass = "OpenCode";
53
+ categories = [ "Development" ];
54
+ });
55
+
56
+ env = opencode.env // {
57
+ ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
58
+ };
59
+
60
+ postPatch =
61
+ # NOTE: Relax Bun version check to be a warning instead of an error
62
+ ''
63
+ substituteInPlace packages/script/src/index.ts \
64
+ --replace-fail 'throw new Error(`This script requires bun@''${expectedBunVersionRange}' \
65
+ 'console.warn(`Warning: This script requires bun@''${expectedBunVersionRange}'
66
+ ''
67
+ # https://github.com/electron/electron/issues/31121
68
+ # mac builds use a .app bundle which doesnt have this issue
69
+ + lib.optionalString stdenv.isLinux ''
70
+ BASE_PATH=packages/desktop
71
+ FILES=(src/main/windows.ts)
72
+ for file in "''${FILES[@]}"; do
73
+ substituteInPlace $BASE_PATH/$file \
74
+ --replace-fail "process.resourcesPath" "'$out/opt/opencode-desktop/resources'"
75
+ done
76
+ '';
77
+
78
+ preBuild = ''
79
+ cp -r "${electron.dist}" $HOME/.electron-dist
80
+ chmod -R u+w $HOME/.electron-dist
81
+
82
+ cp -R ${finalAttrs.node_modules}/. .
83
+ patchShebangs node_modules
84
+ patchShebangs packages/*/node_modules
85
+ '';
86
+
87
+ buildPhase = ''
88
+ runHook preBuild
89
+
90
+ cd packages/desktop
91
+
92
+ bun run build
93
+ npx electron-builder --dir \
94
+ --config electron-builder.config.ts \
95
+ --config.mac.identity=null \
96
+ --config.electronDist="$HOME/.electron-dist"
97
+
98
+ runHook postBuild
99
+ '';
100
+
101
+ installPhase = ''
102
+ runHook preInstall
103
+ ''
104
+ + lib.optionalString stdenv.hostPlatform.isDarwin ''
105
+ mkdir -p $out/Applications
106
+ mv dist/mac*/*.app $out/Applications
107
+ makeWrapper "$out/Applications/OpenCode.app/Contents/MacOS/OpenCode" $out/bin/opencode-desktop
108
+ ''
109
+ + lib.optionalString stdenv.hostPlatform.isLinux ''
110
+ mkdir -p $out/opt/opencode-desktop
111
+ cp -r dist/linux*-unpacked/{resources,LICENSE*} $out/opt/opencode-desktop
112
+ install -Dm644 resources/icons/32x32.png \
113
+ "$out/share/icons/hicolor/32x32/apps/ai.opencode.desktop.png"
114
+ install -Dm644 resources/icons/64x64.png \
115
+ "$out/share/icons/hicolor/64x64/apps/ai.opencode.desktop.png"
116
+ install -Dm644 resources/icons/128x128.png \
117
+ "$out/share/icons/hicolor/128x128/apps/ai.opencode.desktop.png"
118
+ install -Dm644 resources/icons/128x128@2x.png \
119
+ "$out/share/icons/hicolor/256x256/apps/ai.opencode.desktop.png"
120
+ install -Dm644 resources/icons/icon.png \
121
+ "$out/share/icons/hicolor/512x512/apps/ai.opencode.desktop.png"
122
+ install -Dm644 resources/ai.opencode.desktop.metainfo.xml \
123
+ "$out/share/metainfo/ai.opencode.desktop.metainfo.xml"
124
+ makeWrapper ${lib.getExe electron} $out/bin/opencode-desktop \
125
+ --inherit-argv0 \
126
+ --set ELECTRON_FORCE_IS_PACKAGED 1 \
127
+ --add-flags $out/opt/opencode-desktop/resources/app.asar \
128
+ --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}"
129
+ ''
130
+ + ''
131
+ runHook postInstall
132
+ '';
133
+
134
+ autoPatchelfIgnoreMissingDeps = [
135
+ "libc.musl-x86_64.so.1"
136
+ ];
137
+
138
+ meta = {
139
+ description = "OpenCode Desktop App";
140
+ mainProgram = "opencode-desktop";
141
+ inherit (opencode.meta) homepage license platforms;
142
+ };
143
+ })
nix/hashes.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodeModules": {
3
+ "x86_64-linux": "sha256-FY/I7zxmWA4tMvFZG5WijdqBcDc0No3a/YmKuxlluNg=",
4
+ "aarch64-linux": "sha256-a8FrM4BYf4u5MIswlWz6Jg+WX3yRyGwwBaBnRssSkAo=",
5
+ "aarch64-darwin": "sha256-/njY2BbhqncqQ+gODoTmhBJKTT22VCFAhh8L3kwgHfU=",
6
+ "x86_64-darwin": "sha256-KGCH9XI/es3r93wwObkQr2+P6J9LaQG8lSvpehG0ebI="
7
+ }
8
+ }
nix/node_modules.nix ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ lib,
3
+ stdenvNoCC,
4
+ bun,
5
+ rev ? "dirty",
6
+ hash ?
7
+ (lib.pipe ./hashes.json [
8
+ builtins.readFile
9
+ builtins.fromJSON
10
+ ]).nodeModules.${stdenvNoCC.hostPlatform.system},
11
+ }:
12
+ let
13
+ packageJson = lib.pipe ../packages/opencode/package.json [
14
+ builtins.readFile
15
+ builtins.fromJSON
16
+ ];
17
+ platform = stdenvNoCC.hostPlatform;
18
+ bunCpu = if platform.isAarch64 then "arm64" else "x64";
19
+ bunOs = if platform.isLinux then "linux" else "darwin";
20
+ in
21
+ stdenvNoCC.mkDerivation {
22
+ pname = "opencode-node_modules";
23
+ version = "${packageJson.version}+${lib.replaceString "-" "." rev}";
24
+
25
+ src = lib.fileset.toSource {
26
+ root = ../.;
27
+ fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
28
+ lib.fileset.unions [
29
+ ../packages
30
+ ../bun.lock
31
+ ../package.json
32
+ ../patches
33
+ ../install # required by desktop build (cli.rs include_str!)
34
+ ../.github/TEAM_MEMBERS # required by @opencode-ai/script
35
+ ]
36
+ );
37
+ };
38
+
39
+ impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
40
+ "GIT_PROXY_COMMAND"
41
+ "SOCKS_SERVER"
42
+ ];
43
+
44
+ nativeBuildInputs = [ bun ];
45
+
46
+ dontConfigure = true;
47
+
48
+ buildPhase = ''
49
+ runHook preBuild
50
+ export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
51
+ bun install \
52
+ --cpu="${bunCpu}" \
53
+ --os="${bunOs}" \
54
+ --filter '!./' \
55
+ --filter './packages/opencode' \
56
+ --filter './packages/desktop' \
57
+ --filter './packages/app' \
58
+ --frozen-lockfile \
59
+ --ignore-scripts \
60
+ --no-progress
61
+ bun --bun ${./scripts/canonicalize-node-modules.ts}
62
+ bun --bun ${./scripts/normalize-bun-binaries.ts}
63
+ runHook postBuild
64
+ '';
65
+
66
+ installPhase = ''
67
+ runHook preInstall
68
+ mkdir -p $out
69
+ find . -type d -name node_modules -exec cp -R --parents {} $out \;
70
+ runHook postInstall
71
+ '';
72
+
73
+ dontFixup = true;
74
+
75
+ outputHashAlgo = "sha256";
76
+ outputHashMode = "recursive";
77
+ outputHash = hash;
78
+
79
+ meta.platforms = [
80
+ "aarch64-linux"
81
+ "x86_64-linux"
82
+ "aarch64-darwin"
83
+ "x86_64-darwin"
84
+ ];
85
+ }
nix/opencode.nix ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ lib,
3
+ stdenvNoCC,
4
+ callPackage,
5
+ bun,
6
+ nodejs,
7
+ sysctl,
8
+ makeBinaryWrapper,
9
+ models-dev,
10
+ ripgrep,
11
+ installShellFiles,
12
+ versionCheckHook,
13
+ writableTmpDirAsHomeHook,
14
+ node_modules ? callPackage ./node-modules.nix { },
15
+ }:
16
+ stdenvNoCC.mkDerivation (finalAttrs: {
17
+ pname = "opencode";
18
+ inherit (node_modules) version src;
19
+ inherit node_modules;
20
+
21
+ nativeBuildInputs = [
22
+ bun
23
+ nodejs # for patchShebangs node_modules
24
+ installShellFiles
25
+ makeBinaryWrapper
26
+ models-dev
27
+ writableTmpDirAsHomeHook
28
+ ];
29
+
30
+ postPatch = ''
31
+ # NOTE: Relax Bun version check to be a warning instead of an error
32
+ substituteInPlace packages/script/src/index.ts \
33
+ --replace-fail 'throw new Error(`This script requires bun@''${expectedBunVersionRange}' \
34
+ 'console.warn(`Warning: This script requires bun@''${expectedBunVersionRange}'
35
+ '';
36
+
37
+ configurePhase = ''
38
+ runHook preConfigure
39
+
40
+ cp -R ${finalAttrs.node_modules}/. .
41
+ patchShebangs node_modules
42
+ patchShebangs packages/*/node_modules
43
+
44
+ runHook postConfigure
45
+ '';
46
+
47
+ env.MODELS_DEV_API_JSON = "${models-dev}/dist/_api.json";
48
+ env.OPENCODE_DISABLE_MODELS_FETCH = true;
49
+ env.OPENCODE_VERSION = finalAttrs.version;
50
+ env.OPENCODE_CHANNEL = "prod";
51
+
52
+ buildPhase = ''
53
+ runHook preBuild
54
+
55
+ cd ./packages/opencode
56
+ bun --bun ./script/build.ts --single --skip-install
57
+ bun --bun ./script/schema.ts schema.json
58
+
59
+ runHook postBuild
60
+ '';
61
+
62
+ installPhase = ''
63
+ runHook preInstall
64
+
65
+ install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode
66
+ install -Dm644 schema.json $out/share/opencode/schema.json
67
+
68
+ wrapProgram $out/bin/opencode \
69
+ --prefix PATH : ${
70
+ lib.makeBinPath (
71
+ [
72
+ ripgrep
73
+ ]
74
+ # bun runs sysctl to detect if running on rosetta2
75
+ ++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
76
+ )
77
+ }
78
+
79
+ runHook postInstall
80
+ '';
81
+
82
+ postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
83
+ # trick yargs into also generating zsh completions
84
+ installShellCompletion --cmd opencode \
85
+ --bash <($out/bin/opencode completion) \
86
+ --zsh <(SHELL=/bin/zsh $out/bin/opencode completion)
87
+ '';
88
+
89
+ nativeInstallCheckInputs = [
90
+ versionCheckHook
91
+ writableTmpDirAsHomeHook
92
+ ];
93
+ doInstallCheck = true;
94
+ versionCheckKeepEnvironment = [ "HOME" "OPENCODE_DISABLE_MODELS_FETCH" ];
95
+ versionCheckProgramArg = "--version";
96
+
97
+ passthru = {
98
+ jsonschema = "${placeholder "out"}/share/opencode/schema.json";
99
+ env = finalAttrs.env;
100
+ };
101
+
102
+ meta = {
103
+ description = "The open source coding agent";
104
+ homepage = "https://opencode.ai";
105
+ license = lib.licenses.mit;
106
+ mainProgram = "opencode";
107
+ inherit (node_modules.meta) platforms;
108
+ };
109
+ })
package.json ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "opencode",
4
+ "description": "AI-powered development tool",
5
+ "private": true,
6
+ "type": "module",
7
+ "packageManager": "bun@1.3.14",
8
+ "scripts": {
9
+ "dev": "bun run --cwd packages/opencode src/index.ts",
10
+ "dev:desktop": "bun --cwd packages/desktop dev",
11
+ "dev:web": "bun --cwd packages/app dev",
12
+ "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
13
+ "dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
14
+ "dev:storybook": "bun --cwd packages/storybook storybook",
15
+ "lint": "oxlint",
16
+ "typecheck": "bun turbo typecheck",
17
+ "upgrade-opentui": "bun run script/upgrade-opentui.ts",
18
+ "postinstall": "bun run --cwd packages/core fix-node-pty",
19
+ "prepare": "husky",
20
+ "random": "echo 'Random script'",
21
+ "sso": "aws sso login --sso-session=opencode --no-browser",
22
+ "translate:app": "bun run script/translate-app.ts",
23
+ "test": "echo 'do not run tests from root' && exit 1"
24
+ },
25
+ "workspaces": {
26
+ "packages": [
27
+ "packages/*",
28
+ "packages/console/*",
29
+ "packages/stats/*",
30
+ "packages/sdk/js",
31
+ "packages/slack"
32
+ ],
33
+ "catalog": {
34
+ "@effect/opentelemetry": "4.0.0-beta.83",
35
+ "@effect/platform-node": "4.0.0-beta.83",
36
+ "@effect/sql-sqlite-bun": "4.0.0-beta.83",
37
+ "@npmcli/arborist": "9.4.0",
38
+ "@types/bun": "1.3.13",
39
+ "@types/cross-spawn": "6.0.6",
40
+ "@octokit/rest": "22.0.0",
41
+ "@hono/standard-validator": "0.2.0",
42
+ "@hono/zod-validator": "0.4.2",
43
+ "@opentui/core": "0.4.5",
44
+ "@opentui/keymap": "0.4.5",
45
+ "@opentui/solid": "0.4.5",
46
+ "@tanstack/solid-virtual": "3.13.32",
47
+ "@shikijs/stream": "4.2.0",
48
+ "ulid": "3.0.1",
49
+ "@kobalte/core": "0.13.11",
50
+ "@corvu/drawer": "0.2.4",
51
+ "@types/luxon": "3.7.1",
52
+ "@types/node": "24.12.2",
53
+ "@types/semver": "7.7.1",
54
+ "@tsconfig/node22": "22.0.2",
55
+ "@tsconfig/bun": "1.0.9",
56
+ "@cloudflare/workers-types": "4.20251008.0",
57
+ "@openauthjs/openauth": "0.0.0-20250322224806",
58
+ "@pierre/diffs": "1.2.10",
59
+ "opentui-spinner": "0.0.7",
60
+ "@solid-primitives/storage": "4.3.3",
61
+ "@tailwindcss/vite": "4.1.11",
62
+ "diff": "8.0.2",
63
+ "dompurify": "3.3.1",
64
+ "drizzle-kit": "1.0.0-rc.2",
65
+ "drizzle-orm": "1.0.0-rc.2",
66
+ "effect": "4.0.0-beta.83",
67
+ "ai": "6.0.168",
68
+ "cross-spawn": "7.0.6",
69
+ "hono": "4.10.7",
70
+ "hono-openapi": "1.1.2",
71
+ "fuzzysort": "3.1.0",
72
+ "luxon": "3.6.1",
73
+ "marked": "18.0.7",
74
+ "marked-shiki": "1.2.1",
75
+ "remend": "1.3.0",
76
+ "@playwright/test": "1.59.1",
77
+ "semver": "7.7.4",
78
+ "typescript": "5.8.2",
79
+ "@typescript/native-preview": "7.0.0-dev.20251207.1",
80
+ "zod": "4.1.8",
81
+ "remeda": "2.26.0",
82
+ "sst": "4.13.1",
83
+ "shiki": "4.2.0",
84
+ "solid-list": "0.3.0",
85
+ "tailwindcss": "4.1.11",
86
+ "vite": "7.1.4",
87
+ "@solidjs/meta": "0.29.4",
88
+ "@solidjs/router": "0.15.4",
89
+ "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
90
+ "@sentry/solid": "10.36.0",
91
+ "@sentry/vite-plugin": "4.6.0",
92
+ "solid-js": "1.9.10",
93
+ "solid-sonner": "0.3.1",
94
+ "vite-plugin-solid": "2.11.10",
95
+ "@lydell/node-pty": "1.2.0-beta.12"
96
+ }
97
+ },
98
+ "devDependencies": {
99
+ "@actions/artifact": "5.0.1",
100
+ "@tsconfig/bun": "catalog:",
101
+ "@types/mime-types": "3.0.1",
102
+ "@typescript/native-preview": "catalog:",
103
+ "glob": "13.0.5",
104
+ "husky": "9.1.7",
105
+ "oxlint": "1.60.0",
106
+ "oxlint-tsgolint": "0.21.0",
107
+ "prettier": "3.6.2",
108
+ "semver": "^7.6.0",
109
+ "sst": "catalog:",
110
+ "turbo": "2.10.2"
111
+ },
112
+ "dependencies": {
113
+ "@aws-sdk/client-s3": "3.933.0",
114
+ "@opencode-ai/plugin": "workspace:*",
115
+ "@opencode-ai/script": "workspace:*",
116
+ "@opencode-ai/sdk": "workspace:*",
117
+ "heap-snapshot-toolkit": "1.1.3",
118
+ "typescript": "catalog:"
119
+ },
120
+ "repository": {
121
+ "type": "git",
122
+ "url": "https://github.com/anomalyco/opencode"
123
+ },
124
+ "license": "MIT",
125
+ "prettier": {
126
+ "semi": false,
127
+ "printWidth": 120
128
+ },
129
+ "trustedDependencies": [
130
+ "esbuild",
131
+ "node-pty",
132
+ "protobufjs",
133
+ "tree-sitter",
134
+ "tree-sitter-bash",
135
+ "tree-sitter-powershell",
136
+ "web-tree-sitter",
137
+ "electron"
138
+ ],
139
+ "overrides": {
140
+ "@ai-sdk/anthropic": "3.0.111",
141
+ "@opentui/core": "catalog:",
142
+ "@opentui/keymap": "catalog:",
143
+ "@opentui/solid": "catalog:",
144
+ "@types/bun": "catalog:",
145
+ "@types/node": "catalog:"
146
+ },
147
+ "patchedDependencies": {
148
+ "@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
149
+ "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
150
+ "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
151
+ "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
152
+ "solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
153
+ "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
154
+ "@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch",
155
+ "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
156
+ "pacote@21.5.0": "patches/pacote@21.5.0.patch",
157
+ "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
158
+ "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
159
+ "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
160
+ "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
161
+ "@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
162
+ "@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch",
163
+ "@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch",
164
+ "@ai-sdk/anthropic@3.0.111": "patches/@ai-sdk%2Fanthropic@3.0.111.patch",
165
+ "@ai-sdk/amazon-bedrock@4.0.166": "patches/@ai-sdk%2Famazon-bedrock@4.0.166.patch",
166
+ "@ai-sdk/openai@3.0.88": "patches/@ai-sdk%2Fopenai@3.0.88.patch"
167
+ }
168
+ }
packages/console/app/.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dist
2
+ .wrangler
3
+ .output
4
+ .vercel
5
+ .netlify
6
+ app.config.timestamp_*.js
7
+
8
+ # Environment
9
+ .env
10
+ .env*.local
11
+
12
+ # dependencies
13
+ /node_modules
14
+
15
+ # IDEs and editors
16
+ /.idea
17
+ .project
18
+ .classpath
19
+ *.launch
20
+ .settings/
21
+
22
+ # Temp
23
+ gitignore
24
+
25
+ # Generated files
26
+ public/sitemap.xml
27
+
28
+ # System Files
29
+ .DS_Store
30
+ Thumbs.db
packages/console/app/README.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SolidStart
2
+
3
+ Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
4
+
5
+ ## Creating a project
6
+
7
+ ```bash
8
+ # create a new project in the current directory
9
+ npm init solid@latest
10
+
11
+ # create a new project in my-app
12
+ npm init solid@latest my-app
13
+ ```
14
+
15
+ ## Developing
16
+
17
+ Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
18
+
19
+ ```bash
20
+ npm run dev
21
+
22
+ # or start the server and open the app in a new browser tab
23
+ npm run dev -- --open
24
+ ```
25
+
26
+ ## Building
27
+
28
+ Solid apps are built with _presets_, which optimise your project for deployment to different environments.
29
+
30
+ By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
31
+
32
+ ## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)
packages/console/app/package.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@opencode-ai/console-app",
3
+ "version": "1.18.31",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "typecheck": "tsgo --noEmit",
8
+ "dev": "vite dev --host 0.0.0.0",
9
+ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51RtuLNE7fOCwHSD4mewwzFejyytjdGoSDK7CAvhbffwaZnPbNb2rwJICw6LTOXCmWO320fSNXvb5NzI08RZVkAxd00syfqrW7t bun sst shell --stage=dev bun dev",
10
+ "build": "bun ./script/generate-sitemap.ts && vite build && bun ../../opencode/script/schema.ts ./.output/public/config.json ./.output/public/tui.json",
11
+ "start": "vite start"
12
+ },
13
+ "dependencies": {
14
+ "@cloudflare/vite-plugin": "1.15.2",
15
+ "@ibm/plex": "6.4.1",
16
+ "@jsx-email/render": "1.1.1",
17
+ "@kobalte/core": "catalog:",
18
+ "@openauthjs/openauth": "catalog:",
19
+ "@opencode-ai/console-core": "workspace:*",
20
+ "@opencode-ai/console-mail": "workspace:*",
21
+ "@opencode-ai/console-resource": "workspace:*",
22
+ "@opencode-ai/ui": "workspace:*",
23
+ "@smithy/eventstream-codec": "4.2.7",
24
+ "@smithy/util-utf8": "4.2.0",
25
+ "@solidjs/meta": "catalog:",
26
+ "@solidjs/router": "catalog:",
27
+ "@solidjs/start": "catalog:",
28
+ "@stripe/stripe-js": "8.6.1",
29
+ "@upstash/redis": "1.38.0",
30
+ "chart.js": "4.5.1",
31
+ "nitro": "3.0.1-alpha.1",
32
+ "number-flow": "0.6.2",
33
+ "solid-js": "catalog:",
34
+ "solid-list": "0.3.0",
35
+ "solid-stripe": "0.8.1",
36
+ "vite": "catalog:",
37
+ "zod": "catalog:"
38
+ },
39
+ "devDependencies": {
40
+ "@types/bun": "catalog:",
41
+ "@typescript/native-preview": "catalog:",
42
+ "@webgpu/types": "0.1.54",
43
+ "typescript": "catalog:",
44
+ "wrangler": "4.50.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=22"
48
+ }
49
+ }
packages/console/app/sst-env.d.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+ /* biome-ignore-all lint: auto-generated */
6
+
7
+ /// <reference path="../../../sst-env.d.ts" />
8
+
9
+ import "sst"
10
+ export {}
packages/console/app/tsconfig.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "skipLibCheck": true,
7
+ "moduleResolution": "bundler",
8
+ "allowSyntheticDefaultImports": true,
9
+ "esModuleInterop": true,
10
+ "jsx": "preserve",
11
+ "jsxImportSource": "solid-js",
12
+ "allowJs": true,
13
+ "strict": true,
14
+ "noEmit": true,
15
+ "types": ["vite/client", "@webgpu/types", "bun"],
16
+ "isolatedModules": true,
17
+ "paths": {
18
+ "~/*": ["./src/*"]
19
+ }
20
+ }
21
+ }
packages/console/app/vite.config.ts ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, PluginOption } from "vite"
2
+ import { solidStart } from "@solidjs/start/config"
3
+ import { nitro } from "nitro/vite"
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ solidStart({
8
+ middleware: "./src/middleware.ts",
9
+ }) as PluginOption,
10
+ nitro({
11
+ compatibilityDate: "2024-09-19",
12
+ preset: "cloudflare-module",
13
+ cloudflare: {
14
+ nodeCompat: true,
15
+ },
16
+ }),
17
+ ],
18
+ server: {
19
+ allowedHosts: true,
20
+ port: 3001,
21
+ },
22
+ build: {
23
+ rollupOptions: {
24
+ external: ["cloudflare:workers"],
25
+ },
26
+ minify: false,
27
+ },
28
+ })
packages/console/core/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ script/scrap.ts
packages/console/core/drizzle.config.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Resource } from "sst"
2
+ import { defineConfig } from "drizzle-kit"
3
+
4
+ export default defineConfig({
5
+ out: "./migrations/",
6
+ strict: true,
7
+ schema: ["./src/**/*.sql.ts"],
8
+ verbose: true,
9
+ dialect: "mysql",
10
+ dbCredentials: {
11
+ database: Resource.Database.database,
12
+ host: Resource.Database.host,
13
+ user: Resource.Database.username,
14
+ password: Resource.Database.password,
15
+ port: Resource.Database.port,
16
+ ssl: {
17
+ rejectUnauthorized: false,
18
+ },
19
+ },
20
+ })
packages/console/core/package.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@opencode-ai/console-core",
4
+ "version": "1.18.31",
5
+ "private": true,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "dependencies": {
9
+ "@aws-sdk/client-sts": "3.782.0",
10
+ "@jsx-email/render": "1.1.1",
11
+ "@opencode-ai/console-mail": "workspace:*",
12
+ "@opencode-ai/console-resource": "workspace:*",
13
+ "@planetscale/database": "1.19.0",
14
+ "aws4fetch": "1.0.20",
15
+ "drizzle-orm": "catalog:",
16
+ "postgres": "3.4.7",
17
+ "stripe": "18.0.0",
18
+ "ulid": "catalog:",
19
+ "zod": "catalog:"
20
+ },
21
+ "exports": {
22
+ "./*.js": "./src/*.ts",
23
+ "./*": "./src/*"
24
+ },
25
+ "scripts": {
26
+ "db": "sst shell drizzle-kit",
27
+ "db-dev": "sst shell --stage dev -- drizzle-kit",
28
+ "db-prod": "sst shell --stage production -- drizzle-kit",
29
+ "shell": "sst shell",
30
+ "shell-dev": "sst shell --stage dev",
31
+ "shell-prod": "sst shell --stage production",
32
+ "update-models": "script/update-models.ts",
33
+ "promote-models-to-dev": "script/promote-models.ts dev",
34
+ "promote-models-to-prod": "script/promote-models.ts production",
35
+ "pull-models-from-dev": "script/pull-models.ts dev",
36
+ "pull-models-from-prod": "script/pull-models.ts production",
37
+ "update-limits": "script/update-limits.ts",
38
+ "promote-limits-to-dev": "script/promote-limits.ts dev",
39
+ "promote-limits-to-prod": "script/promote-limits.ts production",
40
+ "typecheck": "tsgo --noEmit"
41
+ },
42
+ "devDependencies": {
43
+ "@cloudflare/workers-types": "catalog:",
44
+ "@tsconfig/node22": "22.0.2",
45
+ "@types/bun": "catalog:",
46
+ "@types/node": "catalog:",
47
+ "drizzle-kit": "catalog:",
48
+ "mysql2": "3.14.4",
49
+ "typescript": "catalog:",
50
+ "@typescript/native-preview": "catalog:"
51
+ }
52
+ }
packages/console/core/sst-env.d.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+ /* biome-ignore-all lint: auto-generated */
6
+
7
+ /// <reference path="../../../sst-env.d.ts" />
8
+
9
+ import "sst"
10
+ export {}
packages/console/core/tsconfig.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@tsconfig/node22/tsconfig.json",
4
+ "compilerOptions": {
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "preserve",
8
+ "jsxImportSource": "react",
9
+ "types": ["@cloudflare/workers-types", "node"]
10
+ }
11
+ }
packages/console/function/package.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@opencode-ai/console-function",
3
+ "version": "1.18.31",
4
+ "$schema": "https://json.schemastore.org/package.json",
5
+ "private": true,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "typecheck": "tsgo --noEmit"
10
+ },
11
+ "devDependencies": {
12
+ "@cloudflare/workers-types": "catalog:",
13
+ "@tsconfig/node22": "22.0.2",
14
+ "@types/bun": "catalog:",
15
+ "@types/node": "catalog:",
16
+ "openai": "5.11.0",
17
+ "typescript": "catalog:",
18
+ "@typescript/native-preview": "catalog:"
19
+ },
20
+ "dependencies": {
21
+ "@ai-sdk/anthropic": "3.0.82",
22
+ "@ai-sdk/openai": "3.0.48",
23
+ "@ai-sdk/openai-compatible": "2.0.37",
24
+ "@opencode-ai/console-core": "workspace:*",
25
+ "@opencode-ai/console-resource": "workspace:*",
26
+ "@openauthjs/openauth": "0.0.0-20250322224806",
27
+ "ai": "catalog:",
28
+ "zod": "catalog:"
29
+ }
30
+ }