Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +12 -0
- .editorconfig +9 -0
- .gitattributes +2 -35
- .gitignore +34 -0
- .gitleaksignore +5 -0
- .husky/pre-push +20 -0
- .opencode/.gitignore +7 -0
- .opencode/env.d.ts +4 -0
- .opencode/opencode.jsonc +20 -0
- .opencode/tui.json +19 -0
- .zed/settings.json +9 -0
- AGENTS.md +161 -0
- CONTRIBUTING.md +272 -0
- README.ja.md +129 -0
- README.ko.md +129 -0
- README.md +142 -0
- README.no.md +129 -0
- README.pl.md +129 -0
- README.ru.md +129 -0
- README.th.md +129 -0
- README.vi.md +129 -0
- README.zht.md +128 -0
- SECURITY.md +47 -0
- bun.lock +0 -0
- flake.nix +73 -0
- infra/app.ts +69 -0
- infra/console.ts +324 -0
- infra/enterprise.ts +18 -0
- infra/lake.ts +331 -0
- infra/monitoring.ts +287 -0
- infra/secret.ts +15 -0
- infra/stage.ts +40 -0
- infra/stats.ts +219 -0
- nix/desktop.nix +143 -0
- nix/hashes.json +8 -0
- nix/node_modules.nix +85 -0
- nix/opencode.nix +109 -0
- package.json +168 -0
- packages/console/app/.gitignore +30 -0
- packages/console/app/README.md +32 -0
- packages/console/app/package.json +49 -0
- packages/console/app/sst-env.d.ts +10 -0
- packages/console/app/tsconfig.json +21 -0
- packages/console/app/vite.config.ts +28 -0
- packages/console/core/.gitignore +1 -0
- packages/console/core/drizzle.config.ts +20 -0
- packages/console/core/package.json +52 -0
- packages/console/core/sst-env.d.ts +10 -0
- packages/console/core/tsconfig.json +11 -0
- packages/console/function/package.json +30 -0
.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 |
-
*.
|
| 2 |
-
|
| 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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
[](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 |
+
}
|