mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-23 09:14:23 +00:00
Compare commits
73 Commits
snapshot-n
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e96447960 | ||
|
|
9f4fc5b72a | ||
|
|
d3ecc5a0d9 | ||
|
|
a5a70fa05b | ||
|
|
5596775c35 | ||
|
|
5712cff5c4 | ||
|
|
ee754c46f9 | ||
|
|
0042a07052 | ||
|
|
ab75ef8140 | ||
|
|
a4ed020a94 | ||
|
|
faa63227ac | ||
|
|
a74fedd23b | ||
|
|
eb64ce08b8 | ||
|
|
aaf8317c82 | ||
|
|
e70d2b27de | ||
|
|
b16f7b426c | ||
|
|
13616e3459 | ||
|
|
a41c81dcd2 | ||
|
|
dbf2c45869 | ||
|
|
c45ab712d2 | ||
|
|
206d81e02c | ||
|
|
6d58d899f7 | ||
|
|
b75a27d43e | ||
|
|
e77b2cfd61 | ||
|
|
d0ce2950e4 | ||
|
|
5a1aca9189 | ||
|
|
f07e877204 | ||
|
|
58ad4359da | ||
|
|
ce2763720e | ||
|
|
950df3de19 | ||
|
|
1d9f05e4f5 | ||
|
|
46361cf35c | ||
|
|
c09d3dd5a7 | ||
|
|
fe89bedfcc | ||
|
|
1e48d7fe82 | ||
|
|
2a904ec56f | ||
|
|
0ce61c817b | ||
|
|
1ffed2fa6c | ||
|
|
c79f1a72d8 | ||
|
|
9c5bbba6ea | ||
|
|
ce17f9dd94 | ||
|
|
92ab4217c2 | ||
|
|
7867ba441f | ||
|
|
7419ebc872 | ||
|
|
7e681b0bc0 | ||
|
|
4e9ef3ecc1 | ||
|
|
7e0e35af3f | ||
|
|
2410593023 | ||
|
|
1de12604cf | ||
|
|
ac0b37a7b7 | ||
|
|
7e1051af07 | ||
|
|
93615bef28 | ||
|
|
a04e4e81fb | ||
|
|
296250f1b7 | ||
|
|
443214871e | ||
|
|
1c2416b6de | ||
|
|
d86c10816d | ||
|
|
04a634a80d | ||
|
|
1eb6caa3c6 | ||
|
|
1a329ba47d | ||
|
|
8d781b08ce | ||
|
|
8b99ac6513 | ||
|
|
63a469d0ce | ||
|
|
ae98be83b3 | ||
|
|
a3181d5fbd | ||
|
|
998c8bf3a5 | ||
|
|
d2d7a37bca | ||
|
|
8ad60b1ec2 | ||
|
|
01d518708a | ||
|
|
ae50f24c06 | ||
|
|
d32dd4d7fd | ||
|
|
cb5a0de42f | ||
|
|
f2090b26c1 |
2
.github/VOUCHED.td
vendored
2
.github/VOUCHED.td
vendored
@@ -8,7 +8,9 @@
|
||||
# - Denounce with minus prefix: -username or -platform:username.
|
||||
# - Optional details after a space following the handle.
|
||||
adamdotdevin
|
||||
-agusbasari29 AI PR slop
|
||||
ariane-emory
|
||||
edemaine
|
||||
-florianleibert
|
||||
fwang
|
||||
iamdavidhill
|
||||
|
||||
59
.github/workflows/docs-locale-sync.yml
vendored
59
.github/workflows/docs-locale-sync.yml
vendored
@@ -12,13 +12,14 @@ jobs:
|
||||
if: github.actor != 'opencode-agent[bot]'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
@@ -51,9 +52,54 @@ jobs:
|
||||
uses: sst/opencode/github@latest
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
OPENCODE_CONFIG_CONTENT: |
|
||||
{
|
||||
"permission": {
|
||||
"*": "deny",
|
||||
"read": {
|
||||
"*": "deny",
|
||||
"packages/web/src/content/docs": "allow",
|
||||
"packages/web/src/content/docs/*": "allow",
|
||||
"packages/web/src/content/docs/*.mdx": "allow",
|
||||
"packages/web/src/content/docs/*/*.mdx": "allow",
|
||||
".opencode": "allow",
|
||||
".opencode/agent": "allow",
|
||||
".opencode/agent/glossary": "allow",
|
||||
".opencode/agent/translator.md": "allow",
|
||||
".opencode/agent/glossary/*.md": "allow"
|
||||
},
|
||||
"edit": {
|
||||
"*": "deny",
|
||||
"packages/web/src/content/docs/*/*.mdx": "allow"
|
||||
},
|
||||
"glob": {
|
||||
"*": "deny",
|
||||
"packages/web/src/content/docs*": "allow",
|
||||
".opencode/agent/glossary*": "allow"
|
||||
},
|
||||
"task": {
|
||||
"*": "deny",
|
||||
"translator": "allow"
|
||||
}
|
||||
},
|
||||
"agent": {
|
||||
"translator": {
|
||||
"permission": {
|
||||
"*": "deny",
|
||||
"read": {
|
||||
"*": "deny",
|
||||
".opencode/agent/translator.md": "allow",
|
||||
".opencode/agent/glossary/*.md": "allow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
with:
|
||||
model: opencode/gpt-5.2
|
||||
model: opencode/gpt-5.3-codex
|
||||
agent: docs
|
||||
use_github_token: true
|
||||
prompt: |
|
||||
Update localized docs to match the latest English docs changes.
|
||||
|
||||
@@ -67,10 +113,11 @@ jobs:
|
||||
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
|
||||
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
|
||||
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
|
||||
5. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
||||
6. Keep locale docs structure aligned with their corresponding English pages.
|
||||
7. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||
8. If no locale updates are needed, make no changes.
|
||||
5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
|
||||
6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
|
||||
7. Keep locale docs structure aligned with their corresponding English pages.
|
||||
8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
|
||||
9. If no locale updates are needed, make no changes.
|
||||
|
||||
- name: Commit and push locale docs updates
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
|
||||
35
.github/workflows/publish.yml
vendored
35
.github/workflows/publish.yml
vendored
@@ -41,6 +41,13 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Install OpenCode
|
||||
if: inputs.bump || inputs.version
|
||||
run: bun i -g opencode-ai
|
||||
@@ -49,14 +56,16 @@ jobs:
|
||||
run: |
|
||||
./script/version.ts
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
OPENCODE_BUMP: ${{ inputs.bump }}
|
||||
OPENCODE_VERSION: ${{ inputs.version }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo }}
|
||||
|
||||
build-cli:
|
||||
needs: version
|
||||
@@ -69,6 +78,13 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
@@ -76,7 +92,8 @@ jobs:
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -189,6 +206,13 @@ jobs:
|
||||
if: contains(matrix.settings.host, 'ubuntu')
|
||||
run: cargo tauri --version
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Build and upload artifacts
|
||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
timeout-minutes: 60
|
||||
@@ -196,14 +220,16 @@ jobs:
|
||||
projectPath: packages/desktop
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
||||
args: --target ${{ matrix.settings.target }} --config ${{ (github.ref_name == 'beta' && './src-tauri/tauri.beta.conf.json') || './src-tauri/tauri.prod.conf.json' }} --verbose
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.version.outputs.release }}
|
||||
tagName: ${{ needs.version.outputs.tag }}
|
||||
releaseDraft: true
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
repo: ${{ (github.ref_name == 'beta' && 'opencode-beta') || '' }}
|
||||
releaseCommitish: ${{ github.sha }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
@@ -280,4 +306,5 @@ jobs:
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
NPM_CONFIG_PROVENANCE: false
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ target
|
||||
opencode-dev
|
||||
logs/
|
||||
*.bun-build
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
63
.opencode/agent/glossary/README.md
Normal file
63
.opencode/agent/glossary/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Locale Glossaries
|
||||
|
||||
Use this folder for locale-specific translation guidance that supplements `.opencode/agent/translator.md`.
|
||||
|
||||
The global glossary in `translator.md` remains the source of truth for shared do-not-translate terms (commands, code, paths, product names, etc.). These locale files capture community learnings about phrasing and terminology preferences.
|
||||
|
||||
## File Naming
|
||||
|
||||
- One file per locale
|
||||
- Use lowercase locale slugs that match docs locales when possible (for example, `zh-cn.md`, `zh-tw.md`)
|
||||
- If only language-level guidance exists, use the language code (for example, `fr.md`)
|
||||
- Some repo locale slugs may be aliases/non-BCP47 for consistency (for example, `br` for Brazilian Portuguese / `pt-BR`)
|
||||
|
||||
## What To Put In A Locale File
|
||||
|
||||
- **Sources**: PRs/issues/discussions that motivated the guidance
|
||||
- **Do Not Translate (Locale Additions)**: locale-specific terms or casing decisions
|
||||
- **Preferred Terms**: recurring UI/docs words with preferred translations
|
||||
- **Guidance**: tone, style, and consistency notes
|
||||
- **Avoid** (optional): common literal translations or wording we should avoid
|
||||
- If the repo uses a locale alias slug, document the alias in **Guidance** (for example, prose may mention `pt-BR` while config/examples use `br`)
|
||||
|
||||
Prefer guidance that is:
|
||||
|
||||
- Repeated across multiple docs/screens
|
||||
- Easy to apply consistently
|
||||
- Backed by a community contribution or review discussion
|
||||
|
||||
## Template
|
||||
|
||||
```md
|
||||
# <locale> Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #12345: https://github.com/anomalyco/opencode/pull/12345
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
| English | Preferred | Notes |
|
||||
| ------- | --------- | --------- |
|
||||
| prompt | ... | preferred |
|
||||
| session | ... | preferred |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural phrasing over literal translation
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid ... when ...
|
||||
```
|
||||
|
||||
## Contribution Notes
|
||||
|
||||
- Mark entries as preferred when they may evolve
|
||||
- Keep examples short
|
||||
- Add or update the `Sources` section whenever you add a new rule
|
||||
- Prefer PR-backed guidance over invented term mappings; start with general guidance if no term-level corrections exist yet
|
||||
28
.opencode/agent/glossary/ar.md
Normal file
28
.opencode/agent/glossary/ar.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# ar Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9947: https://github.com/anomalyco/opencode/pull/9947
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Arabic phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- For RTL text, treat code, commands, and paths as LTR artifacts and keep their character order unchanged
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Arabic terms for the same recurring UI action once a preferred term is established
|
||||
34
.opencode/agent/glossary/br.md
Normal file
34
.opencode/agent/glossary/br.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# br Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #10086: https://github.com/anomalyco/opencode/pull/10086
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Locale code `br` in repo config, code, and paths (repo alias for Brazilian Portuguese)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are PR-backed locale naming preferences and may evolve.
|
||||
|
||||
| English / Context | Preferred | Notes |
|
||||
| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------- |
|
||||
| Brazilian Portuguese (prose locale name) | `pt-BR` | Use standard locale naming in prose when helpful |
|
||||
| Repo locale slug (code/config) | `br` | PR #10086 uses `br` for consistency/simplicity |
|
||||
| Browser locale detection | `pt`, `pt-br`, `pt-BR` -> `br` | Preserve this mapping in docs/examples about locale detection |
|
||||
|
||||
## Guidance
|
||||
|
||||
- This file covers Brazilian Portuguese (`pt-BR`), but the repo locale code is `br`
|
||||
- Use natural Brazilian Portuguese phrasing over literal translation
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- Keep repo locale identifiers as implemented in code/config (`br`) even when prose mentions `pt-BR`
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid changing repo locale code references from `br` to `pt-br` in code snippets, paths, or config examples
|
||||
- Avoid mixing Portuguese variants when a Brazilian Portuguese form is established
|
||||
33
.opencode/agent/glossary/bs.md
Normal file
33
.opencode/agent/glossary/bs.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# bs Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #12283: https://github.com/anomalyco/opencode/pull/12283
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are PR-backed locale naming preferences and may evolve.
|
||||
|
||||
| English / Context | Preferred | Notes |
|
||||
| ---------------------------------- | ---------- | ------------------------------------------------- |
|
||||
| Bosnian language label (UI) | `Bosanski` | PR #12283 tested switching language to `Bosanski` |
|
||||
| Repo locale slug (code/config) | `bs` | Preserve in code, config, paths, and examples |
|
||||
| Browser locale detection (Bosnian) | `bs` | PR #12283 added `bs` locale auto-detection |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Use natural Bosnian phrasing over literal translation
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- Keep repo locale references as `bs` in code/config, and use `Bosanski` for the user-facing language name when applicable
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid changing repo locale references from `bs` to another slug in code snippets or config examples
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
27
.opencode/agent/glossary/da.md
Normal file
27
.opencode/agent/glossary/da.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# da Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Danish phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Danish terms for the same recurring UI action once a preferred term is established
|
||||
27
.opencode/agent/glossary/de.md
Normal file
27
.opencode/agent/glossary/de.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# de Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural German phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple German terms for the same recurring UI action once a preferred term is established
|
||||
27
.opencode/agent/glossary/es.md
Normal file
27
.opencode/agent/glossary/es.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# es Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Spanish phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Spanish terms for the same recurring UI action once a preferred term is established
|
||||
27
.opencode/agent/glossary/fr.md
Normal file
27
.opencode/agent/glossary/fr.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# fr Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural French phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple French terms for the same recurring UI action once a preferred term is established
|
||||
33
.opencode/agent/glossary/ja.md
Normal file
33
.opencode/agent/glossary/ja.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# ja Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
|
||||
- PR #13160: https://github.com/anomalyco/opencode/pull/13160
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are PR-backed wording preferences and may evolve.
|
||||
|
||||
| English / Context | Preferred | Notes |
|
||||
| --------------------------- | ----------------------- | ------------------------------------- |
|
||||
| WSL integration (UI label) | `WSL連携` | PR #13160 prefers this over `WSL統合` |
|
||||
| WSL integration description | `WindowsのWSL環境で...` | PR #13160 improved phrasing naturally |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Japanese phrasing over literal translation
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- In WSL integration text, follow PR #13160 wording direction for more natural Japanese phrasing
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid `WSL統合` in the WSL integration UI context where `WSL連携` is the reviewed wording
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
27
.opencode/agent/glossary/ko.md
Normal file
27
.opencode/agent/glossary/ko.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# ko Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Korean phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Korean terms for the same recurring UI action once a preferred term is established
|
||||
38
.opencode/agent/glossary/no.md
Normal file
38
.opencode/agent/glossary/no.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# no Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #10018: https://github.com/anomalyco/opencode/pull/10018
|
||||
- PR #12935: https://github.com/anomalyco/opencode/pull/12935
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Sound names (PR #10018 notes these were intentionally left untranslated)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are PR-backed corrections and may evolve.
|
||||
|
||||
| English / Context | Preferred | Notes |
|
||||
| ----------------------------------- | ------------ | ----------------------------- |
|
||||
| Save (data persistence action) | `Lagre` | Prefer over `Spare` |
|
||||
| Disabled (feature/state) | `deaktivert` | Prefer over `funksjonshemmet` |
|
||||
| API keys | `API Nøkler` | Prefer over `API Taster` |
|
||||
| Cost (noun) | `Kostnad` | Prefer over verb form `Koste` |
|
||||
| Show/View (imperative button label) | `Vis` | Prefer over `Utsikt` |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Norwegian Bokmal (Bokmål) wording over literal translation
|
||||
- Keep tone clear and practical in UI labels
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- Keep recurring UI terms consistent once a preferred term is chosen
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid `Spare` for save actions in persistence contexts
|
||||
- Avoid `funksjonshemmet` for disabled feature states
|
||||
- Avoid `API Taster`, `Koste`, and `Utsikt` in the corrected contexts above
|
||||
27
.opencode/agent/glossary/pl.md
Normal file
27
.opencode/agent/glossary/pl.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# pl Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9884: https://github.com/anomalyco/opencode/pull/9884
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Polish phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Polish terms for the same recurring UI action once a preferred term is established
|
||||
27
.opencode/agent/glossary/ru.md
Normal file
27
.opencode/agent/glossary/ru.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# ru Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #9882: https://github.com/anomalyco/opencode/pull/9882
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Russian phrasing over literal translation
|
||||
- Keep tone clear and direct in UI labels and docs prose
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating product and protocol names that are fixed identifiers
|
||||
- Avoid mixing multiple Russian terms for the same recurring UI action once a preferred term is established
|
||||
34
.opencode/agent/glossary/th.md
Normal file
34
.opencode/agent/glossary/th.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# th Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #10809: https://github.com/anomalyco/opencode/pull/10809
|
||||
- PR #11496: https://github.com/anomalyco/opencode/pull/11496
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- Commands, flags, file paths, and code literals (keep exactly as written)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are PR-backed preferences and may evolve.
|
||||
|
||||
| English / Context | Preferred | Notes |
|
||||
| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
|
||||
| Thai language label in language lists | `ไทย` | PR #10809 standardized this across locales |
|
||||
| Language names in language pickers | Native names (static) | PR #11496: keep names like `English`, `Deutsch`, `ไทย` consistent across locales |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural Thai phrasing over literal translation
|
||||
- Keep tone short and clear for buttons and labels
|
||||
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
|
||||
- Keep language names static/native in language pickers instead of translating them per current locale (PR #11496)
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid translating language names differently per current locale in language lists
|
||||
- Avoid changing `ไทย` to another display form for the Thai language option unless the product standard changes
|
||||
42
.opencode/agent/glossary/zh-cn.md
Normal file
42
.opencode/agent/glossary/zh-cn.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# zh-cn Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
|
||||
- `OpenCode Zen`
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are preferred terms for docs/UI prose and may evolve.
|
||||
|
||||
| English | Preferred | Notes |
|
||||
| ----------------------- | --------- | ------------------------------------------- |
|
||||
| prompt | 提示词 | Keep `--prompt` unchanged in flags/code |
|
||||
| session | 会话 | |
|
||||
| provider | 提供商 | |
|
||||
| share link / shared URL | 分享链接 | Prefer `分享` for user-facing share actions |
|
||||
| headless (server) | 无界面 | Docs wording |
|
||||
| authentication | 认证 | Prefer in auth/OAuth contexts |
|
||||
| cache | 缓存 | |
|
||||
| keybind / shortcut | 快捷键 | User-facing docs wording |
|
||||
| workflow | 工作流 | e.g. GitHub Actions workflow |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural, concise phrasing over literal translation
|
||||
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
|
||||
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
|
||||
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
|
||||
- Prefer consistent terminology across pages once a term is chosen (`会话`, `提供商`, `提示词`, etc.)
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
|
||||
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
|
||||
42
.opencode/agent/glossary/zh-tw.md
Normal file
42
.opencode/agent/glossary/zh-tw.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# zh-tw Glossary
|
||||
|
||||
## Sources
|
||||
|
||||
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
|
||||
|
||||
## Do Not Translate (Locale Additions)
|
||||
|
||||
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
|
||||
- `OpenCode Zen`
|
||||
- `OpenCode CLI`
|
||||
- `CLI`, `TUI`, `MCP`, `OAuth`
|
||||
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
|
||||
|
||||
## Preferred Terms
|
||||
|
||||
These are preferred terms for docs/UI prose and may evolve.
|
||||
|
||||
| English | Preferred | Notes |
|
||||
| ----------------------- | --------- | ------------------------------------------- |
|
||||
| prompt | 提示詞 | Keep `--prompt` unchanged in flags/code |
|
||||
| session | 工作階段 | |
|
||||
| provider | 供應商 | |
|
||||
| share link / shared URL | 分享連結 | Prefer `分享` for user-facing share actions |
|
||||
| headless (server) | 無介面 | Docs wording |
|
||||
| authentication | 認證 | Prefer in auth/OAuth contexts |
|
||||
| cache | 快取 | |
|
||||
| keybind / shortcut | 快捷鍵 | User-facing docs wording |
|
||||
| workflow | 工作流程 | e.g. GitHub Actions workflow |
|
||||
|
||||
## Guidance
|
||||
|
||||
- Prefer natural, concise phrasing over literal translation
|
||||
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
|
||||
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
|
||||
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
|
||||
- Prefer consistent terminology across pages once a term is chosen (`工作階段`, `供應商`, `提示詞`, etc.)
|
||||
|
||||
## Avoid
|
||||
|
||||
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
|
||||
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Translate content for a specified locale while preserving technical terms
|
||||
mode: subagent
|
||||
model: opencode/gemini-3-pro
|
||||
model: opencode/gemini-3.1-pro
|
||||
---
|
||||
|
||||
You are a professional translator and localization specialist.
|
||||
@@ -13,10 +13,25 @@ Requirements:
|
||||
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
|
||||
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
|
||||
- Also preserve every term listed in the Do-Not-Translate glossary below.
|
||||
- Also apply locale-specific guidance from `.opencode/agent/glossary/<locale>.md` when available (for example, `zh-cn.md`).
|
||||
- Do not modify fenced code blocks.
|
||||
- Output ONLY the translation (no commentary).
|
||||
|
||||
If the target locale is missing, ask the user to provide it.
|
||||
If no locale-specific glossary exists, use the global glossary only.
|
||||
|
||||
---
|
||||
|
||||
# Locale-Specific Glossaries
|
||||
|
||||
When a locale glossary exists, use it to:
|
||||
|
||||
- Apply preferred wording for recurring UI/docs terms in that locale
|
||||
- Preserve locale-specific do-not-translate terms and casing decisions
|
||||
- Prefer natural phrasing over literal translation when the locale file calls it out
|
||||
- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo)
|
||||
|
||||
Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,8 +5,16 @@ import DESCRIPTION from "./github-triage.txt"
|
||||
const TEAM = {
|
||||
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
|
||||
zen: ["fwang", "MrMushrooooom"],
|
||||
tui: ["thdxr", "kommander", "rekram1-node"],
|
||||
core: ["thdxr", "rekram1-node", "jlongster"],
|
||||
tui: [
|
||||
"thdxr",
|
||||
"kommander",
|
||||
// "rekram1-node" (on vacation)
|
||||
],
|
||||
core: [
|
||||
"thdxr",
|
||||
// "rekram1-node", (on vacation)
|
||||
"jlongster",
|
||||
],
|
||||
docs: ["R44VC0RP"],
|
||||
windows: ["Hona"],
|
||||
} as const
|
||||
@@ -42,10 +50,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
export default tool({
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
assignee: tool.schema
|
||||
.enum(ASSIGNEES as [string, ...string[]])
|
||||
.describe("The username of the assignee")
|
||||
.default("rekram1-node"),
|
||||
assignee: tool.schema.enum(ASSIGNEES as [string, ...string[]]).describe("The username of the assignee"),
|
||||
labels: tool.schema
|
||||
.array(tool.schema.enum(["nix", "opentui", "perf", "web", "desktop", "zen", "docs", "windows", "core"]))
|
||||
.describe("The labels(s) to add to the issue")
|
||||
@@ -68,7 +73,8 @@ export default tool({
|
||||
results.push("Dropped label: nix (issue does not mention nix)")
|
||||
}
|
||||
|
||||
const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
|
||||
// const assignee = nix ? "rekram1-node" : web ? pick(TEAM.desktop) : args.assignee
|
||||
const assignee = web ? pick(TEAM.desktop) : args.assignee
|
||||
|
||||
if (labels.includes("zen") && !zen) {
|
||||
throw new Error("Only add the zen label when issue title/body contains 'zen'")
|
||||
|
||||
@@ -4,3 +4,5 @@ Choose labels and assignee using the current triage policy and ownership rules.
|
||||
Pick the most fitting labels for the issue and assign one owner.
|
||||
|
||||
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.
|
||||
|
||||
(Note: rekram1-node is on vacation, do not assign issues to him.)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
139
README.bn.md
Normal file
139
README.bn.md
Normal file
@@ -0,0 +1,139 @@
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai">
|
||||
<picture>
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">ওপেন সোর্স এআই কোডিং এজেন্ট।</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||
<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>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> |
|
||||
<a href="README.zh.md">简体中文</a> |
|
||||
<a href="README.zht.md">繁體中文</a> |
|
||||
<a href="README.ko.md">한국어</a> |
|
||||
<a href="README.de.md">Deutsch</a> |
|
||||
<a href="README.es.md">Español</a> |
|
||||
<a href="README.fr.md">Français</a> |
|
||||
<a href="README.it.md">Italiano</a> |
|
||||
<a href="README.da.md">Dansk</a> |
|
||||
<a href="README.ja.md">日本語</a> |
|
||||
<a href="README.pl.md">Polski</a> |
|
||||
<a href="README.ru.md">Русский</a> |
|
||||
<a href="README.bs.md">Bosanski</a> |
|
||||
<a href="README.ar.md">العربية</a> |
|
||||
<a href="README.no.md">Norsk</a> |
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
---
|
||||
|
||||
### ইনস্টলেশন (Installation)
|
||||
|
||||
```bash
|
||||
# YOLO
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# Package managers
|
||||
npm i -g opencode-ai@latest # or bun/pnpm/yarn
|
||||
scoop install opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
|
||||
brew install opencode # macOS and Linux (official brew formula, updated less)
|
||||
sudo pacman -S opencode # Arch Linux (Stable)
|
||||
paru -S opencode-bin # Arch Linux (Latest from AUR)
|
||||
mise use -g opencode # Any OS
|
||||
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> ইনস্টল করার আগে ০.১.x এর চেয়ে পুরোনো ভার্সনগুলো মুছে ফেলুন।
|
||||
|
||||
### ডেস্কটপ অ্যাপ (BETA)
|
||||
|
||||
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
|
||||
|
||||
| প্ল্যাটফর্ম | ডাউনলোড |
|
||||
| --------------------- | ------------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`, `.rpm`, or AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
brew install --cask opencode-desktop
|
||||
# Windows (Scoop)
|
||||
scoop bucket add extras; scoop install extras/opencode-desktop
|
||||
```
|
||||
|
||||
#### ইনস্টলেশন ডিরেক্টরি (Installation Directory)
|
||||
|
||||
ইনস্টল স্ক্রিপ্টটি ইনস্টলেশন পাতের জন্য নিম্নলিখিত অগ্রাধিকার ক্রম মেনে চলে:
|
||||
|
||||
1. `$OPENCODE_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি
|
||||
2. `$XDG_BIN_DIR` - XDG বেস ডিরেক্টরি স্পেসিফিকেশন সমর্থিত পাথ
|
||||
3. `$HOME/bin` - সাধারণ ব্যবহারকারী বাইনারি ডিরেক্টরি (যদি বিদ্যমান থাকে বা তৈরি করা যায়)
|
||||
4. `$HOME/.opencode/bin` - ডিফল্ট ফলব্যাক
|
||||
|
||||
```bash
|
||||
# উদাহরণ
|
||||
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
|
||||
### এজেন্টস (Agents)
|
||||
|
||||
OpenCode এ দুটি বিল্ট-ইন এজেন্ট রয়েছে যা আপনি `Tab` কি(key) দিয়ে পরিবর্তন করতে পারবেন।
|
||||
|
||||
- **build** - ডিফল্ট, ডেভেলপমেন্টের কাজের জন্য সম্পূর্ণ অ্যাক্সেসযুক্ত এজেন্ট
|
||||
- **plan** - বিশ্লেষণ এবং কোড এক্সপ্লোরেশনের জন্য রিড-ওনলি এজেন্ট
|
||||
- ডিফল্টভাবে ফাইল এডিট করতে দেয় না
|
||||
- ব্যাশ কমান্ড চালানোর আগে অনুমতি চায়
|
||||
- অপরিচিত কোডবেস এক্সপ্লোর করা বা পরিবর্তনের পরিকল্পনা করার জন্য আদর্শ
|
||||
|
||||
এছাড়াও জটিল অনুসন্ধান এবং মাল্টিস্টেপ টাস্কের জন্য একটি **general** সাবএজেন্ট অন্তর্ভুক্ত রয়েছে।
|
||||
এটি অভ্যন্তরীণভাবে ব্যবহৃত হয় এবং মেসেজে `@general` লিখে ব্যবহার করা যেতে পারে।
|
||||
|
||||
এজেন্টদের সম্পর্কে আরও জানুন: [docs](https://opencode.ai/docs/agents)।
|
||||
|
||||
### ডকুমেন্টেশন (Documentation)
|
||||
|
||||
কিভাবে OpenCode কনফিগার করবেন সে সম্পর্কে আরও তথ্যের জন্য, [**আমাদের ডকস দেখুন**](https://opencode.ai/docs)।
|
||||
|
||||
### অবদান (Contributing)
|
||||
|
||||
আপনি যদি OpenCode এ অবদান রাখতে চান, অনুগ্রহ করে একটি পুল রিকোয়েস্ট সাবমিট করার আগে আমাদের [কন্ট্রিবিউটিং ডকস](./CONTRIBUTING.md) পড়ে নিন।
|
||||
|
||||
### OpenCode এর উপর বিল্ডিং (Building on OpenCode)
|
||||
|
||||
আপনি যদি এমন প্রজেক্টে কাজ করেন যা OpenCode এর সাথে সম্পর্কিত এবং প্রজেক্টের নামের অংশ হিসেবে "opencode" ব্যবহার করেন, উদাহরণস্বরূপ "opencode-dashboard" বা "opencode-mobile", তবে দয়া করে আপনার README তে একটি নোট যোগ করে স্পষ্ট করুন যে এই প্রজেক্টটি OpenCode দল দ্বারা তৈরি হয়নি এবং আমাদের সাথে এর কোনো সরাসরি সম্পর্ক নেই।
|
||||
|
||||
### সচরাচর জিজ্ঞাসিত প্রশ্নাবলী (FAQ)
|
||||
|
||||
#### এটি ক্লড কোড (Claude Code) থেকে কীভাবে আলাদা?
|
||||
|
||||
ক্যাপাবিলিটির দিক থেকে এটি ক্লড কোডের (Claude Code) মতই। এখানে মূল পার্থক্যগুলো দেওয়া হলো:
|
||||
|
||||
- ১০০% ওপেন সোর্স
|
||||
- কোনো প্রোভাইডারের সাথে আবদ্ধ নয়। যদিও আমরা [OpenCode Zen](https://opencode.ai/zen) এর মাধ্যমে মডেলসমূহ ব্যবহারের পরামর্শ দিই, OpenCode ক্লড (Claude), ওপেনএআই (OpenAI), গুগল (Google), অথবা লোকাল মডেলগুলোর সাথেও ব্যবহার করা যেতে পারে। যেমন যেমন মডেলগুলো উন্নত হবে, তাদের মধ্যকার পার্থক্য কমে আসবে এবং দামও কমবে, তাই প্রোভাইডার-অজ্ঞাস্টিক হওয়া খুবই গুরুত্বপূর্ণ।
|
||||
- আউট-অফ-দ্য-বক্স LSP সাপোর্ট
|
||||
- TUI এর উপর ফোকাস। OpenCode নিওভিম (neovim) ব্যবহারকারী এবং [terminal.shop](https://terminal.shop) এর নির্মাতাদের দ্বারা তৈরি; আমরা টার্মিনালে কী কী সম্ভব তার সীমাবদ্ধতা ছাড়িয়ে যাওয়ার চেষ্টা করছি।
|
||||
- ক্লায়েন্ট/সার্ভার আর্কিটেকচার। এটি যেমন OpenCode কে আপনার কম্পিউটারে চালানোর সুযোগ দেয়, তেমনি আপনি মোবাইল অ্যাপ থেকে রিমোটলি এটি নিয়ন্ত্রণ করতে পারবেন, অর্থাৎ TUI ফ্রন্টএন্ড কেবল সম্ভাব্য ক্লায়েন্টগুলোর মধ্যে একটি।
|
||||
|
||||
---
|
||||
|
||||
**আমাদের কমিউনিটিতে যুক্ত হোন** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a>
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
|
||||
58
bun.lock
58
bun.lock
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -136,7 +136,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -160,7 +160,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -184,7 +184,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -246,7 +246,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -262,7 +262,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -304,8 +304,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.4",
|
||||
"@opentui/core": "0.1.79",
|
||||
"@opentui/solid": "0.1.79",
|
||||
"@opentui/core": "0.1.81",
|
||||
"@opentui/solid": "0.1.81",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -315,6 +315,7 @@
|
||||
"ai": "catalog:",
|
||||
"ai-gateway-provider": "2.3.1",
|
||||
"bonjour-service": "1.3.0",
|
||||
"bun-pty": "0.4.8",
|
||||
"chokidar": "4.0.3",
|
||||
"clipboardy": "4.0.0",
|
||||
"decimal.js": "10.5.0",
|
||||
@@ -330,7 +331,6 @@
|
||||
"jsonc-parser": "3.3.1",
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"node-pty": "1.2.0-beta.11",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
@@ -376,7 +376,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -396,7 +396,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -407,7 +407,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -420,7 +420,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -462,7 +462,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -473,7 +473,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1314,21 +1314,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.79", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.79", "@opentui/core-darwin-x64": "0.1.79", "@opentui/core-linux-arm64": "0.1.79", "@opentui/core-linux-x64": "0.1.79", "@opentui/core-win32-arm64": "0.1.79", "@opentui/core-win32-x64": "0.1.79", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-job/t09w8A/aHb/WuaVbimu5fIffyN+PCuVO5cYhXEg/NkOkC/WdFi80B8bwncR/DBPyLAh6oJ3EG86grOVo5g=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.81", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.81", "@opentui/core-darwin-x64": "0.1.81", "@opentui/core-linux-arm64": "0.1.81", "@opentui/core-linux-x64": "0.1.81", "@opentui/core-win32-arm64": "0.1.81", "@opentui/core-win32-x64": "0.1.81", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ooFjkkQ80DDC4X5eLvH8dBcLAtWwGp9RTaWsaeWet3GOv4N0SDcN8mi1XGhYnUlTuxmofby5eQrPegjtWHODlA=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.79", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kgsGniV+DM5G1P3GideyJhvfnthNKcVCAm2mPTIr9InQ3L0gS/Feh7zgwOS/jxDvdlQbOWGKMk2Z3JApeC1MLw=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.81", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I3Ry5JbkSQXs2g1me8yYr0v3CUcIIfLHzbWz9WMFla8kQDSa+HOr8IpZbqZDeIFgOVzolAXBmZhg0VJI3bZ7MA=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.79", "", { "os": "darwin", "cpu": "x64" }, "sha512-OpyAmFqAAKQ2CeFmf/oLWcNksmP6Ryx/3R5dbKXThOudMCeQvfvInJTRbc2jTn9VFpf+Qj4BgHkJg1h90tf/EA=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.81", "", { "os": "darwin", "cpu": "x64" }, "sha512-CrtNKu41D6+bOQdUOmDX4Q3hTL6p+sT55wugPzbDq7cdqFZabCeguBAyOlvRl2g2aJ93kmOWW6MXG0bPPklEFg=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.79", "", { "os": "linux", "cpu": "arm64" }, "sha512-DCa5YaknS4bWhFt8TMEGH+qmTinyzuY8hoZbO4crtWXAxofPP7Pas76Cwxlvis/PyLffA+pPxAl1l5sUZpsvqw=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.81", "", { "os": "linux", "cpu": "arm64" }, "sha512-FJw9zmJop9WiMvtT07nSrfBLPLqskxL6xfV3GNft0mSYV+C3hdJ0qkiczGSHUX/6V7fmouM84RWwmY53Rb6hYQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.79", "", { "os": "linux", "cpu": "x64" }, "sha512-V6xjvFfHh3NGvsuuDae1KHPRZXHMEE8XL0A/GM6v4I4OCC23kDmkK60Vn6OptQwAzwwbz0X0IX+Ut/GQU9qGgA=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.81", "", { "os": "linux", "cpu": "x64" }, "sha512-Rj2AFIiuWI0BEMIvh/Jeuxty9Gp5ZhLuQU7ZHJJhojKo/mpBpMs9X+5kwZPZya/tyR8uVDAVyB6AOLkhdRW5lw=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.79", "", { "os": "win32", "cpu": "arm64" }, "sha512-sPRKnVzOdT5szI59tte7pxwwkYA+07EQN+6miFAvkFuiLmRUngONUD8HVjL7nCnxcPFqxaU4Rvl1y40ST86g8g=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.81", "", { "os": "win32", "cpu": "arm64" }, "sha512-AiZB+mZ1cVr8plAPrPT98e3kw6D0OdOSe2CQYLgJRbfRlPqq3jl26lHPzDb3ZO2OR0oVGRPJvXraus939mvoiQ=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.79", "", { "os": "win32", "cpu": "x64" }, "sha512-vmQcFTvKf9fqajnDtgU6/uAsiTGwx8//khqHVBmiTEXUsiT792Ki9l8sgNughbuldqG5iZOiF6IaAWU1H67UpA=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.81", "", { "os": "win32", "cpu": "x64" }, "sha512-l8R2Ni1CR4eHi3DTmSkEL/EjHAtOZ/sndYs3VVw+Ej2esL3Mf0W7qSO5S0YNBanz2VXZhbkmM6ERm9keH8RD3w=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.79", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.79", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-c5+0jexKxb8GwRDDkQ/U6isZZqClAzHccXmYiLYmSnqdoQQp2lIGHLartL+K8lfIQrsKClzP2ZHumN6nexRfRg=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.81", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.81", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-QRjS0wPuIhBRdY8tpG3yprCM4ZnOxWWHTuaZ4hhia2wFZygf7Ome6EuZnLXmtuOQjkjCwu0if8Yik6toc6QylA=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
@@ -2222,9 +2222,11 @@
|
||||
|
||||
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
|
||||
|
||||
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||
|
||||
"bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="],
|
||||
"bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
|
||||
|
||||
"bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="],
|
||||
|
||||
@@ -3296,8 +3298,6 @@
|
||||
|
||||
"node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
|
||||
|
||||
"node-pty": ["node-pty@1.2.0-beta.11", "", { "dependencies": { "node-addon-api": "^7.1.0" } }, "sha512-THcUyu1WwdgoIyUvgXOZ70EOMXzheGa0q3tbEb5kUIfKgcpBJ+AJ9Q1kq0bKtYmQzr77usXiTORZTLmAUQlnoQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
|
||||
|
||||
@@ -30,6 +30,10 @@ inputs:
|
||||
description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/opencode,/oc'"
|
||||
required: false
|
||||
|
||||
variant:
|
||||
description: "Model variant for provider-specific reasoning effort (e.g., high, max, minimal)"
|
||||
required: false
|
||||
|
||||
oidc_base_url:
|
||||
description: "Base URL for OIDC token exchange API. Only required when running a custom GitHub App install. Defaults to https://api.opencode.ai"
|
||||
required: false
|
||||
@@ -71,4 +75,5 @@ runs:
|
||||
PROMPT: ${{ inputs.prompt }}
|
||||
USE_GITHUB_TOKEN: ${{ inputs.use_github_token }}
|
||||
MENTIONS: ${{ inputs.mentions }}
|
||||
VARIANT: ${{ inputs.variant }}
|
||||
OIDC_BASE_URL: ${{ inputs.oidc_base_url }}
|
||||
|
||||
@@ -100,26 +100,46 @@ export const stripeWebhook = new stripe.WebhookEndpoint("StripeWebhookEndpoint",
|
||||
],
|
||||
})
|
||||
|
||||
const zenProduct = new stripe.Product("ZenBlack", {
|
||||
const zenLiteProduct = new stripe.Product("ZenLite", {
|
||||
name: "OpenCode Lite",
|
||||
})
|
||||
const zenLitePrice = new stripe.Price("ZenLitePrice", {
|
||||
product: zenLiteProduct.id,
|
||||
currency: "usd",
|
||||
recurring: {
|
||||
interval: "month",
|
||||
intervalCount: 1,
|
||||
},
|
||||
unitAmount: 1000,
|
||||
})
|
||||
const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
|
||||
properties: {
|
||||
product: zenLiteProduct.id,
|
||||
price: zenLitePrice.id,
|
||||
},
|
||||
})
|
||||
const ZEN_LITE_LIMITS = new sst.Secret("ZEN_LITE_LIMITS")
|
||||
|
||||
const zenBlackProduct = new stripe.Product("ZenBlack", {
|
||||
name: "OpenCode Black",
|
||||
})
|
||||
const zenPriceProps = {
|
||||
product: zenProduct.id,
|
||||
const zenBlackPriceProps = {
|
||||
product: zenBlackProduct.id,
|
||||
currency: "usd",
|
||||
recurring: {
|
||||
interval: "month",
|
||||
intervalCount: 1,
|
||||
},
|
||||
}
|
||||
const zenPrice200 = new stripe.Price("ZenBlackPrice", { ...zenPriceProps, unitAmount: 20000 })
|
||||
const zenPrice100 = new stripe.Price("ZenBlack100Price", { ...zenPriceProps, unitAmount: 10000 })
|
||||
const zenPrice20 = new stripe.Price("ZenBlack20Price", { ...zenPriceProps, unitAmount: 2000 })
|
||||
const zenBlackPrice200 = new stripe.Price("ZenBlackPrice", { ...zenBlackPriceProps, unitAmount: 20000 })
|
||||
const zenBlackPrice100 = new stripe.Price("ZenBlack100Price", { ...zenBlackPriceProps, unitAmount: 10000 })
|
||||
const zenBlackPrice20 = new stripe.Price("ZenBlack20Price", { ...zenBlackPriceProps, unitAmount: 2000 })
|
||||
const ZEN_BLACK_PRICE = new sst.Linkable("ZEN_BLACK_PRICE", {
|
||||
properties: {
|
||||
product: zenProduct.id,
|
||||
plan200: zenPrice200.id,
|
||||
plan100: zenPrice100.id,
|
||||
plan20: zenPrice20.id,
|
||||
product: zenBlackProduct.id,
|
||||
plan200: zenBlackPrice200.id,
|
||||
plan100: zenBlackPrice100.id,
|
||||
plan20: zenBlackPrice20.id,
|
||||
},
|
||||
})
|
||||
const ZEN_BLACK_LIMITS = new sst.Secret("ZEN_BLACK_LIMITS")
|
||||
@@ -196,6 +216,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||
AWS_SES_SECRET_ACCESS_KEY,
|
||||
ZEN_BLACK_PRICE,
|
||||
ZEN_BLACK_LIMITS,
|
||||
ZEN_LITE_PRICE,
|
||||
ZEN_LITE_LIMITS,
|
||||
new sst.Secret("ZEN_SESSION_SECRET"),
|
||||
...ZEN_MODELS,
|
||||
...($dev
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-fjrvCgQ2PHYxzw8NsiEHOcor46qN95/cfilFHFqCp/k=",
|
||||
"aarch64-linux": "sha256-xWp4LLJrbrCPFL1F6SSbProq/t/az4CqhTcymPvjOBQ=",
|
||||
"aarch64-darwin": "sha256-Wbfyy/bruFHKUWsyJ2aiPXAzLkk5MNBfN6QdGPQwZS0=",
|
||||
"x86_64-darwin": "sha256-wDnMbiaBCRj5STkaLoVCZTdXVde+/YKfwWzwJZ1AJXQ="
|
||||
"x86_64-linux": "sha256-3hfy6nfEnGq4J6inH0pXANw05oas+81iuayn7J0pj9c=",
|
||||
"aarch64-linux": "sha256-dxWaLtzSeI5NfHwB6u0K10yxoA0ESz/r+zTEQ3FdKFY=",
|
||||
"aarch64-darwin": "sha256-kkK4rj4g0j2jJFXVmVH7CJcXlI8Dj/KmL/VC3iE4Z+8=",
|
||||
"x86_64-darwin": "sha256-jt51irxZd48kb0BItd8InP7lfsELUh0unVYO2es+a98="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"dev:web": "bun --cwd packages/app dev",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"prepare": "husky",
|
||||
"postinstall": "bun packages/opencode/script/node-pty-helper.ts",
|
||||
"random": "echo 'Random script'",
|
||||
"hello": "echo 'Hello World!'",
|
||||
"test": "echo 'do not run tests from root' && exit 1"
|
||||
},
|
||||
"workspaces": {
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function hoverSessionItem(page: Page, sessionID: string) {
|
||||
export async function openSessionMoreMenu(page: Page, sessionID: string) {
|
||||
await expect(page).toHaveURL(new RegExp(`/session/${sessionID}(?:[/?#]|$)`))
|
||||
|
||||
const scroller = page.locator(".session-scroller").first()
|
||||
const scroller = page.locator(".scroll-view__viewport").first()
|
||||
await expect(scroller).toBeVisible()
|
||||
await expect(scroller.getByRole("heading", { level: 1 }).first()).toBeVisible({ timeout: 30_000 })
|
||||
|
||||
|
||||
@@ -20,11 +20,8 @@ export const settingsNotificationsAgentSelector = '[data-action="settings-notifi
|
||||
export const settingsNotificationsPermissionsSelector = '[data-action="settings-notifications-permissions"]'
|
||||
export const settingsNotificationsErrorsSelector = '[data-action="settings-notifications-errors"]'
|
||||
export const settingsSoundsAgentSelector = '[data-action="settings-sounds-agent"]'
|
||||
export const settingsSoundsAgentEnabledSelector = '[data-action="settings-sounds-agent-enabled"]'
|
||||
export const settingsSoundsPermissionsSelector = '[data-action="settings-sounds-permissions"]'
|
||||
export const settingsSoundsPermissionsEnabledSelector = '[data-action="settings-sounds-permissions-enabled"]'
|
||||
export const settingsSoundsErrorsSelector = '[data-action="settings-sounds-errors"]'
|
||||
export const settingsSoundsErrorsEnabledSelector = '[data-action="settings-sounds-errors-enabled"]'
|
||||
export const settingsUpdatesStartupSelector = '[data-action="settings-updates-startup"]'
|
||||
export const settingsReleaseNotesSelector = '[data-action="settings-release-notes"]'
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ test("session can be renamed via header menu", async ({ page, sdk, gotoSession }
|
||||
const menu = await openSessionMoreMenu(page, session.id)
|
||||
await clickMenuItem(menu, /rename/i)
|
||||
|
||||
const input = page.locator(".session-scroller").locator(inlineInputSelector).first()
|
||||
const input = page.locator(".scroll-view__viewport").locator(inlineInputSelector).first()
|
||||
await expect(input).toBeVisible()
|
||||
await expect(input).toBeFocused()
|
||||
await input.fill(renamedTitle)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
settingsNotificationsPermissionsSelector,
|
||||
settingsReleaseNotesSelector,
|
||||
settingsSoundsAgentSelector,
|
||||
settingsSoundsAgentEnabledSelector,
|
||||
settingsSoundsErrorsSelector,
|
||||
settingsSoundsPermissionsSelector,
|
||||
settingsThemeSelector,
|
||||
@@ -336,21 +335,19 @@ test("changing sound agent selection persists in localStorage", async ({ page, g
|
||||
expect(stored?.sounds?.agent).not.toBe("staplebops-01")
|
||||
})
|
||||
|
||||
test("disabling agent sound disables sound selection", async ({ page, gotoSession }) => {
|
||||
test("selecting none disables agent sound", async ({ page, gotoSession }) => {
|
||||
await gotoSession()
|
||||
|
||||
const dialog = await openSettings(page)
|
||||
const select = dialog.locator(settingsSoundsAgentSelector)
|
||||
const switchContainer = dialog.locator(settingsSoundsAgentEnabledSelector)
|
||||
const trigger = select.locator('[data-slot="select-select-trigger"]')
|
||||
await expect(select).toBeVisible()
|
||||
await expect(switchContainer).toBeVisible()
|
||||
await expect(trigger).toBeEnabled()
|
||||
|
||||
await switchContainer.locator('[data-slot="switch-control"]').click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await expect(trigger).toBeDisabled()
|
||||
await trigger.click()
|
||||
const items = page.locator('[data-slot="select-select-item"]')
|
||||
await expect(items.first()).toBeVisible()
|
||||
await items.first().click()
|
||||
|
||||
const stored = await page.evaluate((key) => {
|
||||
const raw = localStorage.getItem(key)
|
||||
|
||||
@@ -6,6 +6,7 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes
|
||||
await gotoSession()
|
||||
|
||||
const terminals = page.locator(terminalSelector)
|
||||
const tabs = page.locator('#terminal-panel [data-slot="tabs-trigger"]')
|
||||
const opened = await terminals.first().isVisible()
|
||||
|
||||
if (!opened) {
|
||||
@@ -21,6 +22,7 @@ test("smoke terminal mounts and can create a second tab", async ({ page, gotoSes
|
||||
await page.locator(promptSelector).click()
|
||||
await page.keyboard.press("Control+Alt+T")
|
||||
|
||||
await expect(terminals).toHaveCount(2)
|
||||
await expect(terminals.nth(1).locator("textarea")).toHaveCount(1)
|
||||
await expect(tabs).toHaveCount(2)
|
||||
await expect(terminals).toHaveCount(1)
|
||||
await expect(terminals.first().locator("textarea")).toHaveCount(1)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -89,6 +89,8 @@ const EXAMPLES = [
|
||||
"prompt.example.25",
|
||||
] as const
|
||||
|
||||
const NON_EMPTY_TEXT = /[^\s\u200B]/
|
||||
|
||||
export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
@@ -636,7 +638,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
let buffer = ""
|
||||
|
||||
const flushText = () => {
|
||||
const content = buffer.replace(/\r\n?/g, "\n").replace(/\u200B/g, "")
|
||||
let content = buffer
|
||||
if (content.includes("\r")) content = content.replace(/\r\n?/g, "\n")
|
||||
if (content.includes("\u200B")) content = content.replace(/\u200B/g, "")
|
||||
buffer = ""
|
||||
if (!content) return
|
||||
parts.push({ type: "text", content, start: position, end: position + content.length })
|
||||
@@ -714,10 +718,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const rawParts = parseFromDOM()
|
||||
const images = imageAttachments()
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = rawParts.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const trimmed = rawText.replace(/\u200B/g, "").trim()
|
||||
const rawText =
|
||||
rawParts.length === 1 && rawParts[0]?.type === "text"
|
||||
? rawParts[0].content
|
||||
: rawParts.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const hasNonText = rawParts.some((part) => part.type !== "text")
|
||||
const shouldReset = trimmed.length === 0 && !hasNonText && images.length === 0
|
||||
const shouldReset = !NON_EMPTY_TEXT.test(rawText) && !hasNonText && images.length === 0
|
||||
|
||||
if (shouldReset) {
|
||||
closePopover()
|
||||
@@ -757,19 +763,31 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const addPart = (part: ContentPart) => {
|
||||
const selection = window.getSelection()
|
||||
if (!selection || selection.rangeCount === 0) return
|
||||
if (part.type === "image") return false
|
||||
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const currentPrompt = prompt.current()
|
||||
const rawText = currentPrompt.map((p) => ("content" in p ? p.content : "")).join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
const selection = window.getSelection()
|
||||
if (!selection) return false
|
||||
|
||||
if (selection.rangeCount === 0 || !editorRef.contains(selection.anchorNode)) {
|
||||
editorRef.focus()
|
||||
const cursor = prompt.cursor() ?? promptLength(prompt.current())
|
||||
setCursorPosition(editorRef, cursor)
|
||||
}
|
||||
|
||||
if (selection.rangeCount === 0) return false
|
||||
const range = selection.getRangeAt(0)
|
||||
if (!editorRef.contains(range.startContainer)) return false
|
||||
|
||||
if (part.type === "file" || part.type === "agent") {
|
||||
const cursorPosition = getCursorPosition(editorRef)
|
||||
const rawText = prompt
|
||||
.current()
|
||||
.map((p) => ("content" in p ? p.content : ""))
|
||||
.join("")
|
||||
const textBeforeCursor = rawText.substring(0, cursorPosition)
|
||||
const atMatch = textBeforeCursor.match(/@(\S*)$/)
|
||||
const pill = createPill(part)
|
||||
const gap = document.createTextNode(" ")
|
||||
const range = selection.getRangeAt(0)
|
||||
|
||||
if (atMatch) {
|
||||
const start = atMatch.index ?? cursorPosition - atMatch[0].length
|
||||
@@ -784,8 +802,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else if (part.type === "text") {
|
||||
const range = selection.getRangeAt(0)
|
||||
}
|
||||
|
||||
if (part.type === "text") {
|
||||
const fragment = createTextFragment(part.content)
|
||||
const last = fragment.lastChild
|
||||
range.deleteContents()
|
||||
@@ -821,6 +840,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
handleInput()
|
||||
closePopover()
|
||||
return true
|
||||
}
|
||||
|
||||
const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
|
||||
|
||||
@@ -7,6 +7,19 @@ import { getCursorPosition } from "./editor-dom"
|
||||
|
||||
export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
|
||||
export const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]
|
||||
const LARGE_PASTE_CHARS = 8000
|
||||
const LARGE_PASTE_BREAKS = 120
|
||||
|
||||
function largePaste(text: string) {
|
||||
if (text.length >= LARGE_PASTE_CHARS) return true
|
||||
let breaks = 0
|
||||
for (const char of text) {
|
||||
if (char !== "\n") continue
|
||||
breaks += 1
|
||||
if (breaks >= LARGE_PASTE_BREAKS) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type PromptAttachmentsInput = {
|
||||
editor: () => HTMLDivElement | undefined
|
||||
@@ -14,7 +27,7 @@ type PromptAttachmentsInput = {
|
||||
isDialogActive: () => boolean
|
||||
setDraggingType: (type: "image" | "@mention" | null) => void
|
||||
focusEditor: () => void
|
||||
addPart: (part: ContentPart) => void
|
||||
addPart: (part: ContentPart) => boolean
|
||||
readClipboardImage?: () => Promise<File | null>
|
||||
}
|
||||
|
||||
@@ -89,6 +102,13 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
|
||||
}
|
||||
|
||||
if (!plainText) return
|
||||
|
||||
if (largePaste(plainText)) {
|
||||
if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
|
||||
input.focusEditor()
|
||||
if (input.addPart({ type: "text", content: plainText, start: 0, end: 0 })) return
|
||||
}
|
||||
|
||||
const inserted = typeof document.execCommand === "function" && document.execCommand("insertText", false, plainText)
|
||||
if (inserted) return
|
||||
|
||||
|
||||
@@ -24,6 +24,28 @@ describe("prompt-input editor dom", () => {
|
||||
expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
|
||||
})
|
||||
|
||||
test("createTextFragment avoids break-node explosion for large multiline content", () => {
|
||||
const content = Array.from({ length: 220 }, () => "line").join("\n")
|
||||
const fragment = createTextFragment(content)
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(fragment)
|
||||
|
||||
expect(container.childNodes.length).toBe(1)
|
||||
expect(container.childNodes[0]?.nodeType).toBe(Node.TEXT_NODE)
|
||||
expect(container.textContent).toBe(content)
|
||||
})
|
||||
|
||||
test("createTextFragment keeps terminal break in large multiline fallback", () => {
|
||||
const content = `${Array.from({ length: 220 }, () => "line").join("\n")}\n`
|
||||
const fragment = createTextFragment(content)
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(fragment)
|
||||
|
||||
expect(container.childNodes.length).toBe(2)
|
||||
expect(container.childNodes[0]?.textContent).toBe(content.slice(0, -1))
|
||||
expect((container.childNodes[1] as HTMLElement).tagName).toBe("BR")
|
||||
})
|
||||
|
||||
test("length helpers treat breaks as one char and ignore zero-width chars", () => {
|
||||
const container = document.createElement("div")
|
||||
container.appendChild(document.createTextNode("ab\u200B"))
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
const MAX_BREAKS = 200
|
||||
|
||||
export function createTextFragment(content: string): DocumentFragment {
|
||||
const fragment = document.createDocumentFragment()
|
||||
let breaks = 0
|
||||
for (const char of content) {
|
||||
if (char !== "\n") continue
|
||||
breaks += 1
|
||||
if (breaks > MAX_BREAKS) {
|
||||
const tail = content.endsWith("\n")
|
||||
const text = tail ? content.slice(0, -1) : content
|
||||
if (text) fragment.appendChild(document.createTextNode(text))
|
||||
if (tail) fragment.appendChild(document.createElement("br"))
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
const segments = content.split("\n")
|
||||
segments.forEach((segment, index) => {
|
||||
if (segment) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Accordion } from "@opencode-ai/ui/accordion"
|
||||
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
||||
import { Code } from "@opencode-ai/ui/code"
|
||||
import { Markdown } from "@opencode-ai/ui/markdown"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import type { Message, Part, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { getSessionContextMetrics } from "./session-context-metrics"
|
||||
@@ -268,9 +269,9 @@ export function SessionContextTab() {
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class="@container h-full overflow-y-auto no-scrollbar pb-10"
|
||||
ref={(el) => {
|
||||
<ScrollView
|
||||
class="@container h-full pb-10"
|
||||
viewportRef={(el) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
@@ -336,6 +337,6 @@ export function SessionContextTab() {
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -452,7 +452,10 @@ export function SessionHeader() {
|
||||
variant: "ghost",
|
||||
class:
|
||||
"rounded-md h-[24px] px-3 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active",
|
||||
classList: { "rounded-r-none": share.shareUrl() !== undefined },
|
||||
classList: {
|
||||
"rounded-r-none": share.shareUrl() !== undefined,
|
||||
"border-r-0": share.shareUrl() !== undefined,
|
||||
},
|
||||
style: { scale: 1 },
|
||||
}}
|
||||
trigger={<span class="text-12-regular">{language.t("session.share.action.share")}</span>}
|
||||
|
||||
@@ -20,12 +20,17 @@ let demoSoundState = {
|
||||
|
||||
// To prevent audio from overlapping/playing very quickly when navigating the settings menus,
|
||||
// delay the playback by 100ms during quick selection changes and pause existing sounds.
|
||||
const playDemoSound = (src: string) => {
|
||||
const stopDemoSound = () => {
|
||||
if (demoSoundState.cleanup) {
|
||||
demoSoundState.cleanup()
|
||||
}
|
||||
|
||||
clearTimeout(demoSoundState.timeout)
|
||||
demoSoundState.cleanup = undefined
|
||||
}
|
||||
|
||||
const playDemoSound = (src: string | undefined) => {
|
||||
stopDemoSound()
|
||||
if (!src) return
|
||||
|
||||
demoSoundState.timeout = setTimeout(() => {
|
||||
demoSoundState.cleanup = playSound(src)
|
||||
@@ -132,11 +137,17 @@ export const SettingsGeneral: Component = () => {
|
||||
] as const
|
||||
const fontOptionsList = [...fontOptions]
|
||||
|
||||
const soundOptions = [...SOUND_OPTIONS]
|
||||
const noneSound = { id: "none", label: "sound.option.none", src: undefined } as const
|
||||
const soundOptions = [noneSound, ...SOUND_OPTIONS]
|
||||
|
||||
const soundSelectProps = (current: () => string, set: (id: string) => void) => ({
|
||||
const soundSelectProps = (
|
||||
enabled: () => boolean,
|
||||
current: () => string,
|
||||
setEnabled: (value: boolean) => void,
|
||||
set: (id: string) => void,
|
||||
) => ({
|
||||
options: soundOptions,
|
||||
current: soundOptions.find((o) => o.id === current()),
|
||||
current: enabled() ? (soundOptions.find((o) => o.id === current()) ?? noneSound) : noneSound,
|
||||
value: (o: (typeof soundOptions)[number]) => o.id,
|
||||
label: (o: (typeof soundOptions)[number]) => language.t(o.label),
|
||||
onHighlight: (option: (typeof soundOptions)[number] | undefined) => {
|
||||
@@ -145,6 +156,12 @@ export const SettingsGeneral: Component = () => {
|
||||
},
|
||||
onSelect: (option: (typeof soundOptions)[number] | undefined) => {
|
||||
if (!option) return
|
||||
if (option.id === "none") {
|
||||
setEnabled(false)
|
||||
stopDemoSound()
|
||||
return
|
||||
}
|
||||
setEnabled(true)
|
||||
set(option.id)
|
||||
playDemoSound(option.src)
|
||||
},
|
||||
@@ -254,6 +271,50 @@ export const SettingsGeneral: Component = () => {
|
||||
</div>
|
||||
)
|
||||
|
||||
const FeedSection = () => (
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.feed")}</h3>
|
||||
|
||||
<div class="bg-surface-raised-base px-4 rounded-lg">
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.reasoningSummaries.title")}
|
||||
description={language.t("settings.general.row.reasoningSummaries.description")}
|
||||
>
|
||||
<div data-action="settings-feed-reasoning-summaries">
|
||||
<Switch
|
||||
checked={settings.general.showReasoningSummaries()}
|
||||
onChange={(checked) => settings.general.setShowReasoningSummaries(checked)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.shellToolPartsExpanded.title")}
|
||||
description={language.t("settings.general.row.shellToolPartsExpanded.description")}
|
||||
>
|
||||
<div data-action="settings-feed-shell-tool-parts-expanded">
|
||||
<Switch
|
||||
checked={settings.general.shellToolPartsExpanded()}
|
||||
onChange={(checked) => settings.general.setShellToolPartsExpanded(checked)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.row.editToolPartsExpanded.title")}
|
||||
description={language.t("settings.general.row.editToolPartsExpanded.description")}
|
||||
>
|
||||
<div data-action="settings-feed-edit-tool-parts-expanded">
|
||||
<Switch
|
||||
checked={settings.general.editToolPartsExpanded()}
|
||||
onChange={(checked) => settings.general.setEditToolPartsExpanded(checked)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const NotificationsSection = () => (
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.notifications")}</h3>
|
||||
@@ -307,66 +368,45 @@ export const SettingsGeneral: Component = () => {
|
||||
title={language.t("settings.general.sounds.agent.title")}
|
||||
description={language.t("settings.general.sounds.agent.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-agent-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.agentEnabled()}
|
||||
onChange={(checked) => settings.sounds.setAgentEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.agentEnabled()}
|
||||
data-action="settings-sounds-agent"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.agent(),
|
||||
(id) => settings.sounds.setAgent(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-agent"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.agentEnabled(),
|
||||
() => settings.sounds.agent(),
|
||||
(value) => settings.sounds.setAgentEnabled(value),
|
||||
(id) => settings.sounds.setAgent(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.sounds.permissions.title")}
|
||||
description={language.t("settings.general.sounds.permissions.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-permissions-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.permissionsEnabled()}
|
||||
onChange={(checked) => settings.sounds.setPermissionsEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.permissionsEnabled()}
|
||||
data-action="settings-sounds-permissions"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.permissions(),
|
||||
(id) => settings.sounds.setPermissions(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-permissions"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.permissionsEnabled(),
|
||||
() => settings.sounds.permissions(),
|
||||
(value) => settings.sounds.setPermissionsEnabled(value),
|
||||
(id) => settings.sounds.setPermissions(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.general.sounds.errors.title")}
|
||||
description={language.t("settings.general.sounds.errors.description")}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div data-action="settings-sounds-errors-enabled">
|
||||
<Switch
|
||||
checked={settings.sounds.errorsEnabled()}
|
||||
onChange={(checked) => settings.sounds.setErrorsEnabled(checked)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!settings.sounds.errorsEnabled()}
|
||||
data-action="settings-sounds-errors"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.errors(),
|
||||
(id) => settings.sounds.setErrors(id),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
data-action="settings-sounds-errors"
|
||||
{...soundSelectProps(
|
||||
() => settings.sounds.errorsEnabled(),
|
||||
() => settings.sounds.errors(),
|
||||
(value) => settings.sounds.setErrorsEnabled(value),
|
||||
(id) => settings.sounds.setErrors(id),
|
||||
)}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</div>
|
||||
</div>
|
||||
@@ -418,7 +458,7 @@ export const SettingsGeneral: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="flex flex-col gap-1 pt-6 pb-8">
|
||||
<h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
|
||||
</div>
|
||||
@@ -427,6 +467,8 @@ export const SettingsGeneral: Component = () => {
|
||||
<div class="flex flex-col gap-8 w-full">
|
||||
<AppearanceSection />
|
||||
|
||||
<FeedSection />
|
||||
|
||||
<NotificationsSection />
|
||||
|
||||
<SoundsSection />
|
||||
|
||||
@@ -370,7 +370,7 @@ export const SettingsKeybinds: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2>
|
||||
|
||||
@@ -59,7 +59,7 @@ export const SettingsModels: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
|
||||
<h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
|
||||
<div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">
|
||||
|
||||
@@ -177,7 +177,7 @@ export const SettingsPermissions: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="flex flex-col gap-1 px-4 py-8 sm:p-8 max-w-[720px]">
|
||||
<h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
|
||||
<p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>
|
||||
|
||||
@@ -132,7 +132,7 @@ export const SettingsProviders: Component = () => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
|
||||
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
|
||||
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
|
||||
</div>
|
||||
|
||||
@@ -15,40 +15,6 @@ import { terminalWriter } from "@/utils/terminal-writer"
|
||||
|
||||
const TOGGLE_TERMINAL_ID = "terminal.toggle"
|
||||
const DEFAULT_TOGGLE_TERMINAL_KEYBIND = "ctrl+`"
|
||||
const FRAME_META = 0
|
||||
const FRAME_OUTPUT = 1
|
||||
const FRAME_INPUT = 2
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
const connection = () => {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
|
||||
}
|
||||
|
||||
const frameInput = (id: string, data: string) => {
|
||||
const channel = encoder.encode(id)
|
||||
const body = encoder.encode(data)
|
||||
const out = new Uint8Array(2 + channel.length + body.length)
|
||||
out[0] = FRAME_INPUT
|
||||
out[1] = channel.length
|
||||
out.set(channel, 2)
|
||||
out.set(body, 2 + channel.length)
|
||||
return out
|
||||
}
|
||||
|
||||
const frameOutput = (bytes: Uint8Array, decoder: TextDecoder) => {
|
||||
if (bytes[0] !== FRAME_OUTPUT) return
|
||||
const size = bytes[1]
|
||||
if (!Number.isSafeInteger(size) || size < 0) return
|
||||
if (bytes.length < 2 + size) return
|
||||
return {
|
||||
connection: decoder.decode(bytes.subarray(2, 2 + size)),
|
||||
data: decoder.decode(bytes.subarray(2 + size)),
|
||||
}
|
||||
}
|
||||
|
||||
export interface TerminalProps extends ComponentProps<"div"> {
|
||||
pty: LocalPTY
|
||||
onSubmit?: () => void
|
||||
@@ -430,10 +396,8 @@ export const Terminal = (props: TerminalProps) => {
|
||||
scheduleSize(size.cols, size.rows)
|
||||
})
|
||||
cleanups.push(() => disposeIfDisposable(onResize))
|
||||
const connectionID = connection()
|
||||
const onData = t.onData((data) => {
|
||||
if (ws?.readyState !== WebSocket.OPEN) return
|
||||
ws.send(frameInput(connectionID, data))
|
||||
if (ws?.readyState === WebSocket.OPEN) ws.send(data)
|
||||
})
|
||||
cleanups.push(() => disposeIfDisposable(onData))
|
||||
const onKey = t.onKey((key) => {
|
||||
@@ -486,7 +450,6 @@ export const Terminal = (props: TerminalProps) => {
|
||||
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
|
||||
url.searchParams.set("directory", sdk.directory)
|
||||
url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
|
||||
url.searchParams.set("connection", connectionID)
|
||||
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
|
||||
url.username = server.current?.http.username ?? ""
|
||||
url.password = server.current?.http.password ?? ""
|
||||
@@ -508,33 +471,24 @@ export const Terminal = (props: TerminalProps) => {
|
||||
if (closing) return
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
const bytes = new Uint8Array(event.data)
|
||||
if (bytes[0] === FRAME_META) {
|
||||
const json = decoder.decode(bytes.subarray(1))
|
||||
try {
|
||||
const meta = JSON.parse(json) as { cursor?: unknown; connection?: unknown }
|
||||
if (typeof meta?.connection === "string" && meta.connection !== connectionID) return
|
||||
const next = meta?.cursor
|
||||
if (typeof next === "number" && Number.isSafeInteger(next) && next >= 0) {
|
||||
cursor = next
|
||||
}
|
||||
} catch (err) {
|
||||
debugTerminal("invalid websocket control frame", err)
|
||||
if (bytes[0] !== 0) return
|
||||
const json = decoder.decode(bytes.subarray(1))
|
||||
try {
|
||||
const meta = JSON.parse(json) as { cursor?: unknown }
|
||||
const next = meta?.cursor
|
||||
if (typeof next === "number" && Number.isSafeInteger(next) && next >= 0) {
|
||||
cursor = next
|
||||
}
|
||||
return
|
||||
} catch (err) {
|
||||
debugTerminal("invalid websocket control frame", err)
|
||||
}
|
||||
|
||||
const frame = frameOutput(bytes, decoder)
|
||||
if (!frame) return
|
||||
if (frame.connection !== connectionID) return
|
||||
if (!frame.data) return
|
||||
output?.push(frame.data)
|
||||
cursor += frame.data.length
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof event.data === "string") {
|
||||
debugTerminal("ignoring unframed websocket output")
|
||||
}
|
||||
const data = typeof event.data === "string" ? event.data : ""
|
||||
if (!data) return
|
||||
output?.push(data)
|
||||
cursor += data.length
|
||||
}
|
||||
socket.addEventListener("message", handleMessage)
|
||||
|
||||
@@ -586,7 +540,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
disposed = true
|
||||
if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
|
||||
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
||||
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close()
|
||||
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close(1000)
|
||||
|
||||
const finalize = () => {
|
||||
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
||||
|
||||
@@ -13,6 +13,14 @@ describe("file path helpers", () => {
|
||||
expect(path.pathFromTab("other://src/app.ts")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("normalizes Windows absolute paths with mixed separators", () => {
|
||||
const path = createPathHelpers(() => "C:\\repo")
|
||||
expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src/app.ts")
|
||||
expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts")
|
||||
expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts")
|
||||
expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src/app.ts")
|
||||
})
|
||||
|
||||
test("keeps query/hash stripping behavior stable", () => {
|
||||
expect(stripQueryAndHash("a/b.ts#L12?x=1")).toBe("a/b.ts")
|
||||
expect(stripQueryAndHash("a/b.ts?x=1#L12")).toBe("a/b.ts")
|
||||
|
||||
@@ -103,16 +103,21 @@ export function encodeFilePath(filepath: string): string {
|
||||
|
||||
export function createPathHelpers(scope: () => string) {
|
||||
const normalize = (input: string) => {
|
||||
const root = scope()
|
||||
const prefix = root.endsWith("/") ? root : root + "/"
|
||||
const root = scope().replace(/\\/g, "/")
|
||||
|
||||
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
|
||||
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))).replace(/\\/g, "/")
|
||||
|
||||
if (path.startsWith(prefix)) {
|
||||
path = path.slice(prefix.length)
|
||||
}
|
||||
|
||||
if (path.startsWith(root)) {
|
||||
// Remove initial root prefix, if it's a complete match or followed by /
|
||||
// (don't want /foo/bar to root of /f).
|
||||
// For Windows paths, also check for case-insensitive match.
|
||||
const windows = /^[A-Za-z]:/.test(root)
|
||||
const canonRoot = windows ? root.toLowerCase() : root
|
||||
const canonPath = windows ? path.toLowerCase() : path
|
||||
if (
|
||||
canonPath.startsWith(canonRoot) &&
|
||||
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath.startsWith(canonRoot + "/"))
|
||||
) {
|
||||
// If we match canonRoot + "/", the slash will be removed below.
|
||||
path = path.slice(root.length)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ export interface Settings {
|
||||
general: {
|
||||
autoSave: boolean
|
||||
releaseNotes: boolean
|
||||
showReasoningSummaries: boolean
|
||||
shellToolPartsExpanded: boolean
|
||||
editToolPartsExpanded: boolean
|
||||
}
|
||||
updates: {
|
||||
startup: boolean
|
||||
@@ -42,6 +45,9 @@ const defaultSettings: Settings = {
|
||||
general: {
|
||||
autoSave: true,
|
||||
releaseNotes: true,
|
||||
showReasoningSummaries: false,
|
||||
shellToolPartsExpanded: true,
|
||||
editToolPartsExpanded: false,
|
||||
},
|
||||
updates: {
|
||||
startup: true,
|
||||
@@ -120,6 +126,27 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||
setReleaseNotes(value: boolean) {
|
||||
setStore("general", "releaseNotes", value)
|
||||
},
|
||||
showReasoningSummaries: withFallback(
|
||||
() => store.general?.showReasoningSummaries,
|
||||
defaultSettings.general.showReasoningSummaries,
|
||||
),
|
||||
setShowReasoningSummaries(value: boolean) {
|
||||
setStore("general", "showReasoningSummaries", value)
|
||||
},
|
||||
shellToolPartsExpanded: withFallback(
|
||||
() => store.general?.shellToolPartsExpanded,
|
||||
defaultSettings.general.shellToolPartsExpanded,
|
||||
),
|
||||
setShellToolPartsExpanded(value: boolean) {
|
||||
setStore("general", "shellToolPartsExpanded", value)
|
||||
},
|
||||
editToolPartsExpanded: withFallback(
|
||||
() => store.general?.editToolPartsExpanded,
|
||||
defaultSettings.general.editToolPartsExpanded,
|
||||
),
|
||||
setEditToolPartsExpanded(value: boolean) {
|
||||
setStore("general", "editToolPartsExpanded", value)
|
||||
},
|
||||
},
|
||||
updates: {
|
||||
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
|
||||
|
||||
@@ -529,6 +529,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "إشعارات النظام",
|
||||
"settings.general.section.updates": "التحديثات",
|
||||
"settings.general.section.sounds": "المؤثرات الصوتية",
|
||||
"settings.general.section.feed": "الخلاصة",
|
||||
"settings.general.section.display": "شاشة العرض",
|
||||
"settings.general.row.language.title": "اللغة",
|
||||
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
||||
@@ -538,6 +539,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||
"settings.general.row.font.title": "الخط",
|
||||
"settings.general.row.font.description": "تخصيص الخط الأحادي المستخدم في كتل التعليمات البرمجية",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"إظهار أجزاء أداة shell موسعة بشكل افتراضي في الشريط الزمني",
|
||||
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
|
||||
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
||||
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -565,6 +572,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "بلا",
|
||||
"sound.option.alert01": "تنبيه 01",
|
||||
"sound.option.alert02": "تنبيه 02",
|
||||
"sound.option.alert03": "تنبيه 03",
|
||||
|
||||
@@ -535,6 +535,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Notificações do sistema",
|
||||
"settings.general.section.updates": "Atualizações",
|
||||
"settings.general.section.sounds": "Efeitos sonoros",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Tela",
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
||||
@@ -544,6 +545,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
||||
"settings.general.row.font.title": "Fonte",
|
||||
"settings.general.row.font.description": "Personalize a fonte monoespaçada usada em blocos de código",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Mostrar partes da ferramenta shell expandidas por padrão na linha do tempo",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -571,6 +578,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Nenhum",
|
||||
"sound.option.alert01": "Alerta 01",
|
||||
"sound.option.alert02": "Alerta 02",
|
||||
"sound.option.alert03": "Alerta 03",
|
||||
|
||||
@@ -599,6 +599,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Sistemske obavijesti",
|
||||
"settings.general.section.updates": "Ažuriranja",
|
||||
"settings.general.section.sounds": "Zvučni efekti",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Prikaz",
|
||||
|
||||
"settings.general.row.language.title": "Jezik",
|
||||
@@ -610,6 +611,12 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Font",
|
||||
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Proširi dijelove shell alata",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Prikaži dijelove shell alata podrazumijevano proširene na vremenskoj traci",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
|
||||
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
||||
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -639,6 +646,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Nijedan",
|
||||
"sound.option.alert01": "Upozorenje 01",
|
||||
"sound.option.alert02": "Upozorenje 02",
|
||||
"sound.option.alert03": "Upozorenje 03",
|
||||
|
||||
@@ -594,6 +594,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Systemmeddelelser",
|
||||
"settings.general.section.updates": "Opdateringer",
|
||||
"settings.general.section.sounds": "Lydeffekter",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Skærm",
|
||||
|
||||
"settings.general.row.language.title": "Sprog",
|
||||
@@ -605,6 +606,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Skrifttype",
|
||||
"settings.general.row.font.description": "Tilpas mono-skrifttypen brugt i kodeblokke",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Udvid shell-værktøjsdele",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "Vis shell-værktøjsdele udvidet som standard i tidslinjen",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
|
||||
"settings.general.row.wayland.title": "Brug native Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -635,6 +641,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ingen",
|
||||
"sound.option.alert01": "Alarm 01",
|
||||
"sound.option.alert02": "Alarm 02",
|
||||
"sound.option.alert03": "Alarm 03",
|
||||
|
||||
@@ -544,6 +544,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Systembenachrichtigungen",
|
||||
"settings.general.section.updates": "Updates",
|
||||
"settings.general.section.sounds": "Soundeffekte",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Anzeige",
|
||||
"settings.general.row.language.title": "Sprache",
|
||||
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
||||
@@ -553,6 +554,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
||||
"settings.general.row.font.title": "Schriftart",
|
||||
"settings.general.row.font.description": "Die in Codeblöcken verwendete Monospace-Schriftart anpassen",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Shell-Tool-Abschnitte ausklappen",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Shell-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
||||
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -580,6 +587,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Keine",
|
||||
"sound.option.alert01": "Alarm 01",
|
||||
"sound.option.alert02": "Alarm 02",
|
||||
"sound.option.alert03": "Alarm 03",
|
||||
|
||||
@@ -495,6 +495,7 @@ export const dict = {
|
||||
"session.review.change.other": "Changes",
|
||||
"session.review.loadingChanges": "Loading changes...",
|
||||
"session.review.empty": "No changes in this session yet",
|
||||
"session.review.noVcs": "No git VCS detected, so session changes will not be detected",
|
||||
"session.review.noChanges": "No changes",
|
||||
|
||||
"session.files.selectToOpen": "Select a file to open",
|
||||
@@ -600,6 +601,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "System notifications",
|
||||
"settings.general.section.updates": "Updates",
|
||||
"settings.general.section.sounds": "Sound effects",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Display",
|
||||
|
||||
"settings.general.row.language.title": "Language",
|
||||
@@ -610,6 +612,14 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
|
||||
"settings.general.row.font.title": "Font",
|
||||
"settings.general.row.font.description": "Customise the mono font used in code blocks",
|
||||
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
|
||||
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Show shell tool parts expanded by default in the timeline",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expand edit tool parts",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Show edit, write, and patch tool parts expanded by default in the timeline",
|
||||
|
||||
"settings.general.row.wayland.title": "Use native Wayland",
|
||||
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",
|
||||
@@ -640,6 +650,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "None",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -602,6 +602,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Notificaciones del sistema",
|
||||
"settings.general.section.updates": "Actualizaciones",
|
||||
"settings.general.section.sounds": "Efectos de sonido",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Pantalla",
|
||||
|
||||
"settings.general.row.language.title": "Idioma",
|
||||
@@ -613,6 +614,12 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Fuente",
|
||||
"settings.general.row.font.description": "Personaliza la fuente monoespaciada usada en bloques de código",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes de la herramienta shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Mostrar las partes de la herramienta shell expandidas por defecto en la línea de tiempo",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
|
||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -643,6 +650,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ninguno",
|
||||
"sound.option.alert01": "Alerta 01",
|
||||
"sound.option.alert02": "Alerta 02",
|
||||
"sound.option.alert03": "Alerta 03",
|
||||
|
||||
@@ -543,6 +543,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Notifications système",
|
||||
"settings.general.section.updates": "Mises à jour",
|
||||
"settings.general.section.sounds": "Effets sonores",
|
||||
"settings.general.section.feed": "Flux",
|
||||
"settings.general.section.display": "Affichage",
|
||||
"settings.general.row.language.title": "Langue",
|
||||
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
||||
@@ -552,6 +553,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
||||
"settings.general.row.font.title": "Police",
|
||||
"settings.general.row.font.description": "Personnaliser la police mono utilisée dans les blocs de code",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Développer les parties de l'outil shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Afficher les parties de l'outil shell développées par défaut dans la chronologie",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
|
||||
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
||||
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -579,6 +586,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Aucun",
|
||||
"sound.option.alert01": "Alerte 01",
|
||||
"sound.option.alert02": "Alerte 02",
|
||||
"sound.option.alert03": "Alerte 03",
|
||||
|
||||
@@ -533,6 +533,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "システム通知",
|
||||
"settings.general.section.updates": "アップデート",
|
||||
"settings.general.section.sounds": "効果音",
|
||||
"settings.general.section.feed": "フィード",
|
||||
"settings.general.section.display": "ディスプレイ",
|
||||
"settings.general.row.language.title": "言語",
|
||||
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
||||
@@ -542,6 +543,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||
"settings.general.row.font.title": "フォント",
|
||||
"settings.general.row.font.description": "コードブロックで使用する等幅フォントをカスタマイズします",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"タイムラインで shell ツールパーツをデフォルトで展開して表示します",
|
||||
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
|
||||
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
||||
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -569,6 +576,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "なし",
|
||||
"sound.option.alert01": "アラート 01",
|
||||
"sound.option.alert02": "アラート 02",
|
||||
"sound.option.alert03": "アラート 03",
|
||||
|
||||
@@ -534,6 +534,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "시스템 알림",
|
||||
"settings.general.section.updates": "업데이트",
|
||||
"settings.general.section.sounds": "효과음",
|
||||
"settings.general.section.feed": "피드",
|
||||
"settings.general.section.display": "디스플레이",
|
||||
"settings.general.row.language.title": "언어",
|
||||
"settings.general.row.language.description": "OpenCode 표시 언어 변경",
|
||||
@@ -543,6 +544,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||
"settings.general.row.font.title": "글꼴",
|
||||
"settings.general.row.font.description": "코드 블록에 사용되는 고정폭 글꼴 사용자 지정",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "shell 도구 파트 펼치기",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"타임라인에서 기본적으로 shell 도구 파트를 펼친 상태로 표시합니다",
|
||||
"settings.general.row.editToolPartsExpanded.title": "edit 도구 파트 펼치기",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"타임라인에서 기본적으로 edit, write, patch 도구 파트를 펼친 상태로 표시합니다",
|
||||
"settings.general.row.wayland.title": "네이티브 Wayland 사용",
|
||||
"settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -570,6 +577,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "없음",
|
||||
"sound.option.alert01": "알림 01",
|
||||
"sound.option.alert02": "알림 02",
|
||||
"sound.option.alert03": "알림 03",
|
||||
|
||||
@@ -602,6 +602,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Systemvarsler",
|
||||
"settings.general.section.updates": "Oppdateringer",
|
||||
"settings.general.section.sounds": "Lydeffekter",
|
||||
"settings.general.section.feed": "Feed",
|
||||
"settings.general.section.display": "Skjerm",
|
||||
|
||||
"settings.general.row.language.title": "Språk",
|
||||
@@ -613,6 +614,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Skrift",
|
||||
"settings.general.row.font.description": "Tilpass mono-skriften som brukes i kodeblokker",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Utvid shell-verktøydeler",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "Vis shell-verktøydeler utvidet som standard i tidslinjen",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Vis edit-, write- og patch-verktøydeler utvidet som standard i tidslinjen",
|
||||
"settings.general.row.wayland.title": "Bruk innebygd Wayland",
|
||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -642,6 +648,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Ingen",
|
||||
"sound.option.alert01": "Varsel 01",
|
||||
"sound.option.alert02": "Varsel 02",
|
||||
"sound.option.alert03": "Varsel 03",
|
||||
|
||||
@@ -534,6 +534,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Powiadomienia systemowe",
|
||||
"settings.general.section.updates": "Aktualizacje",
|
||||
"settings.general.section.sounds": "Efekty dźwiękowe",
|
||||
"settings.general.section.feed": "Kanał",
|
||||
"settings.general.section.display": "Ekran",
|
||||
"settings.general.row.language.title": "Język",
|
||||
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
||||
@@ -543,6 +544,12 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
||||
"settings.general.row.font.title": "Czcionka",
|
||||
"settings.general.row.font.description": "Dostosuj czcionkę mono używaną w blokach kodu",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Domyślnie pokazuj rozwinięte elementy narzędzia shell na osi czasu",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
|
||||
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
||||
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -570,6 +577,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Brak",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -600,6 +600,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "Системные уведомления",
|
||||
"settings.general.section.updates": "Обновления",
|
||||
"settings.general.section.sounds": "Звуковые эффекты",
|
||||
"settings.general.section.feed": "Лента",
|
||||
"settings.general.section.display": "Дисплей",
|
||||
|
||||
"settings.general.row.language.title": "Язык",
|
||||
@@ -611,6 +612,12 @@ export const dict = {
|
||||
"settings.general.row.font.title": "Шрифт",
|
||||
"settings.general.row.font.description": "Настройте моноширинный шрифт для блоков кода",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "Разворачивать элементы инструмента shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description":
|
||||
"Показывать элементы инструмента shell в ленте развернутыми по умолчанию",
|
||||
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
|
||||
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
||||
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||
"settings.general.row.wayland.tooltip":
|
||||
@@ -640,6 +647,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "Нет",
|
||||
"sound.option.alert01": "Alert 01",
|
||||
"sound.option.alert02": "Alert 02",
|
||||
"sound.option.alert03": "Alert 03",
|
||||
|
||||
@@ -594,6 +594,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||
"settings.general.section.updates": "การอัปเดต",
|
||||
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||
"settings.general.section.feed": "ฟีด",
|
||||
"settings.general.section.display": "การแสดงผล",
|
||||
|
||||
"settings.general.row.language.title": "ภาษา",
|
||||
@@ -605,6 +606,11 @@ export const dict = {
|
||||
"settings.general.row.font.title": "ฟอนต์",
|
||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์โมโนที่ใช้ในบล็อกโค้ด",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "แสดงส่วนเครื่องมือ shell แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
||||
"settings.general.row.editToolPartsExpanded.description":
|
||||
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
||||
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||
@@ -634,6 +640,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "ไม่มี",
|
||||
"sound.option.alert01": "เสียงเตือน 01",
|
||||
"sound.option.alert02": "เสียงเตือน 02",
|
||||
"sound.option.alert03": "เสียงเตือน 03",
|
||||
|
||||
@@ -595,6 +595,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "系统通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
"settings.general.section.feed": "动态",
|
||||
"settings.general.section.display": "显示",
|
||||
"settings.general.row.language.title": "语言",
|
||||
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
||||
@@ -604,6 +605,10 @@ export const dict = {
|
||||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||
"settings.general.row.font.title": "字体",
|
||||
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
||||
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
||||
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||
@@ -633,6 +638,7 @@ export const dict = {
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
|
||||
"sound.option.none": "无",
|
||||
"sound.option.alert01": "警报 01",
|
||||
"sound.option.alert02": "警报 02",
|
||||
"sound.option.alert03": "警报 03",
|
||||
|
||||
@@ -589,6 +589,7 @@ export const dict = {
|
||||
"settings.general.section.notifications": "系統通知",
|
||||
"settings.general.section.updates": "更新",
|
||||
"settings.general.section.sounds": "音效",
|
||||
"settings.general.section.feed": "資訊流",
|
||||
"settings.general.section.display": "顯示",
|
||||
|
||||
"settings.general.row.language.title": "語言",
|
||||
@@ -600,6 +601,10 @@ export const dict = {
|
||||
"settings.general.row.font.title": "字型",
|
||||
"settings.general.row.font.description": "自訂程式碼區塊使用的等寬字型",
|
||||
|
||||
"settings.general.row.shellToolPartsExpanded.title": "展開 shell 工具區塊",
|
||||
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
||||
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
||||
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
|
||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||
@@ -629,6 +634,7 @@ export const dict = {
|
||||
"font.option.sourceCodePro": "Source Code Pro",
|
||||
"font.option.ubuntuMono": "Ubuntu Mono",
|
||||
"font.option.geistMono": "Geist Mono",
|
||||
"sound.option.none": "無",
|
||||
"sound.option.alert01": "警報 01",
|
||||
"sound.option.alert02": "警報 02",
|
||||
"sound.option.alert03": "警報 03",
|
||||
|
||||
@@ -274,6 +274,11 @@ export default function Page() {
|
||||
if (!hasReview()) return true
|
||||
return sync.data.session_diff[id] !== undefined
|
||||
})
|
||||
const reviewEmptyKey = createMemo(() => {
|
||||
const project = sync.project
|
||||
if (!project || project.vcs) return "session.review.empty"
|
||||
return "session.review.noVcs"
|
||||
})
|
||||
|
||||
let inputRef!: HTMLDivElement
|
||||
let promptDock: HTMLDivElement | undefined
|
||||
@@ -531,7 +536,7 @@ export default function Page() {
|
||||
) : (
|
||||
<div class={input.emptyClass}>
|
||||
<Mark class="w-14 opacity-10" />
|
||||
<div class="text-14-regular text-text-weak max-w-56">{language.t("session.review.empty")}</div>
|
||||
<div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -943,15 +948,12 @@ export default function Page() {
|
||||
if (next === dockHeight) return
|
||||
|
||||
const el = scroller
|
||||
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 : false
|
||||
const delta = next - dockHeight
|
||||
const stick = el ? el.scrollHeight - el.clientHeight - el.scrollTop < 10 + Math.max(0, delta) : false
|
||||
|
||||
dockHeight = next
|
||||
|
||||
if (stick && el) {
|
||||
requestAnimationFrame(() => {
|
||||
el.scrollTo({ top: el.scrollHeight, behavior: "auto" })
|
||||
})
|
||||
}
|
||||
if (stick) autoScroll.forceScrollToBottom()
|
||||
|
||||
if (el) scheduleScrollState(el)
|
||||
scrollSpy.markDirty()
|
||||
|
||||
@@ -62,7 +62,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
const measure = () => {
|
||||
if (!root) return
|
||||
|
||||
const scroller = document.querySelector(".session-scroller")
|
||||
const scroller = document.querySelector(".scroll-view__viewport")
|
||||
const head = scroller instanceof HTMLElement ? scroller.firstElementChild : undefined
|
||||
const top =
|
||||
head instanceof HTMLElement && head.classList.contains("sticky") ? head.getBoundingClientRect().bottom : 0
|
||||
@@ -95,7 +95,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
|
||||
window.addEventListener("resize", update)
|
||||
|
||||
const dock = root?.closest('[data-component="session-prompt-dock"]')
|
||||
const scroller = document.querySelector(".session-scroller")
|
||||
const scroller = document.querySelector(".scroll-view__viewport")
|
||||
const observer = new ResizeObserver(update)
|
||||
if (dock instanceof HTMLElement) observer.observe(dock)
|
||||
if (scroller instanceof HTMLElement) observer.observe(scroller)
|
||||
|
||||
@@ -87,7 +87,7 @@ export function SessionTodoDock(props: { todos: Todo[]; title: string; collapseL
|
||||
icon="chevron-down"
|
||||
size="normal"
|
||||
variant="ghost"
|
||||
classList={{ "rotate-180": !store.collapsed }}
|
||||
classList={{ "rotate-180": store.collapsed }}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@@ -9,6 +9,7 @@ import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { LineComment as LineCommentView, LineCommentEditor } from "@opencode-ai/ui/line-comment"
|
||||
import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||
import { useComments } from "@/context/comments"
|
||||
@@ -509,51 +510,52 @@ export function FileTabContent(props: { tab: string }) {
|
||||
)
|
||||
|
||||
return (
|
||||
<Tabs.Content
|
||||
value={props.tab}
|
||||
class="mt-3 relative"
|
||||
ref={(el: HTMLDivElement) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={state()?.loaded && isImage()}>
|
||||
<div class="px-6 py-4 pb-40">
|
||||
<img
|
||||
src={imageDataUrl()}
|
||||
alt={path()}
|
||||
class="max-w-full"
|
||||
onLoad={() => requestAnimationFrame(restoreScroll)}
|
||||
/>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isSvg()}>
|
||||
<div class="flex flex-col gap-4 px-6 py-4">
|
||||
{renderCode(svgContent() ?? "", "")}
|
||||
<Show when={svgPreviewUrl()}>
|
||||
<div class="flex justify-center pb-40">
|
||||
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isBinary()}>
|
||||
<div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
|
||||
<Mark class="w-14 opacity-10" />
|
||||
<div class="flex flex-col gap-2 max-w-md">
|
||||
<div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div>
|
||||
<div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div>
|
||||
<Tabs.Content value={props.tab} class="mt-3 relative h-full">
|
||||
<ScrollView
|
||||
class="h-full"
|
||||
viewportRef={(el: HTMLDivElement) => {
|
||||
scroll = el
|
||||
restoreScroll()
|
||||
}}
|
||||
onScroll={handleScroll as any}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={state()?.loaded && isImage()}>
|
||||
<div class="px-6 py-4 pb-40">
|
||||
<img
|
||||
src={imageDataUrl()}
|
||||
alt={path()}
|
||||
class="max-w-full"
|
||||
onLoad={() => requestAnimationFrame(restoreScroll)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match>
|
||||
<Match when={state()?.loading}>
|
||||
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||
</Match>
|
||||
<Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match>
|
||||
</Switch>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isSvg()}>
|
||||
<div class="flex flex-col gap-4 px-6 py-4">
|
||||
{renderCode(svgContent() ?? "", "")}
|
||||
<Show when={svgPreviewUrl()}>
|
||||
<div class="flex justify-center pb-40">
|
||||
<img src={svgPreviewUrl()} alt={path()} class="max-w-full max-h-96" />
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded && isBinary()}>
|
||||
<div class="h-full px-6 pb-42 flex flex-col items-center justify-center text-center gap-6">
|
||||
<Mark class="w-14 opacity-10" />
|
||||
<div class="flex flex-col gap-2 max-w-md">
|
||||
<div class="text-14-semibold text-text-strong truncate">{path()?.split("/").pop()}</div>
|
||||
<div class="text-14-regular text-text-weak">{language.t("session.files.binaryContent")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={state()?.loaded}>{renderCode(contents(), "pb-40")}</Match>
|
||||
<Match when={state()?.loading}>
|
||||
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||
</Match>
|
||||
<Match when={state()?.error}>{(err) => <div class="px-6 py-4 text-text-weak">{err()}</div>}</Match>
|
||||
</Switch>
|
||||
</ScrollView>
|
||||
</Tabs.Content>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
|
||||
import { SessionContextUsage } from "@/components/session-context-usage"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
|
||||
@@ -80,6 +82,7 @@ export function MessageTimeline(props: {
|
||||
const navigate = useNavigate()
|
||||
const sdk = useSDK()
|
||||
const sync = useSync()
|
||||
const settings = useSettings()
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
|
||||
@@ -320,8 +323,8 @@ export function MessageTimeline(props: {
|
||||
<Icon name="arrow-down-to-line" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
ref={props.setScrollRef}
|
||||
<ScrollView
|
||||
viewportRef={props.setScrollRef}
|
||||
onWheel={(e) => {
|
||||
const root = e.currentTarget
|
||||
const delta = normalizeWheelDelta({
|
||||
@@ -365,7 +368,7 @@ export function MessageTimeline(props: {
|
||||
if (props.isDesktop) props.onScrollSpyScroll()
|
||||
}}
|
||||
onClick={props.onAutoScrollInteraction}
|
||||
class="relative min-w-0 w-full h-full overflow-y-auto session-scroller"
|
||||
class="relative min-w-0 w-full h-full"
|
||||
style={{
|
||||
"--session-title-height": showHeader() ? "40px" : "0px",
|
||||
"--sticky-accordion-top": showHeader() ? "48px" : "0px",
|
||||
@@ -535,6 +538,9 @@ export function MessageTimeline(props: {
|
||||
sessionID={sessionID() ?? ""}
|
||||
messageID={message.id}
|
||||
lastUserMessageID={props.lastUserMessageID}
|
||||
showReasoningSummaries={settings.general.showReasoningSummaries()}
|
||||
shellToolDefaultOpen={settings.general.shellToolPartsExpanded()}
|
||||
editToolDefaultOpen={settings.general.editToolPartsExpanded()}
|
||||
classes={{
|
||||
root: "min-w-0 w-full relative",
|
||||
content: "flex flex-col justify-between !overflow-visible",
|
||||
@@ -545,7 +551,7 @@ export function MessageTimeline(props: {
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
||||
@@ -143,9 +143,9 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
|
||||
open={props.view().review.open()}
|
||||
onOpenChange={props.view().review.setOpen}
|
||||
classes={{
|
||||
root: props.classes?.root ?? "pb-6",
|
||||
root: props.classes?.root ?? "pb-6 pr-3",
|
||||
header: props.classes?.header ?? "px-3",
|
||||
container: props.classes?.container ?? "px-3",
|
||||
container: props.classes?.container ?? "pl-3",
|
||||
}}
|
||||
diffs={props.diffs()}
|
||||
diffStyle={props.diffStyle}
|
||||
|
||||
@@ -67,11 +67,11 @@ export function TerminalPanel() {
|
||||
on(
|
||||
() => terminal.active(),
|
||||
(activeId) => {
|
||||
if (!activeId || !opened()) return
|
||||
if (!activeId || !open()) return
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
focusTerminalById(activeId)
|
||||
setTimeout(() => focusTerminalById(activeId), 0)
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -209,21 +209,17 @@ export function TerminalPanel() {
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<For each={all()}>
|
||||
{(pty) => (
|
||||
<div
|
||||
id={`terminal-wrapper-${pty.id}`}
|
||||
class="absolute inset-0"
|
||||
style={{
|
||||
display: terminal.active() === pty.id ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Show when={pty.id} keyed>
|
||||
<Terminal pty={pty} onCleanup={terminal.update} onConnectError={() => terminal.clone(pty.id)} />
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={terminal.active()} keyed>
|
||||
{(id) => (
|
||||
<Show when={byId().get(id)}>
|
||||
{(pty) => (
|
||||
<div id={`terminal-wrapper-${id}`} class="absolute inset-0">
|
||||
<Terminal pty={pty()} onCleanup={terminal.update} onConnectError={() => terminal.clone(id)} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<DragOverlay>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -337,6 +337,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "الدخل",
|
||||
"workspace.usage.table.output": "الخرج",
|
||||
"workspace.usage.table.cost": "التكلفة",
|
||||
"workspace.usage.table.session": "الجلسة",
|
||||
"workspace.usage.breakdown.input": "الدخل",
|
||||
"workspace.usage.breakdown.cacheRead": "قراءة الكاش",
|
||||
"workspace.usage.breakdown.cacheWrite": "كتابة الكاش",
|
||||
|
||||
@@ -342,6 +342,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Entrada",
|
||||
"workspace.usage.table.output": "Saída",
|
||||
"workspace.usage.table.cost": "Custo",
|
||||
"workspace.usage.table.session": "Sessão",
|
||||
"workspace.usage.breakdown.input": "Entrada",
|
||||
"workspace.usage.breakdown.cacheRead": "Leitura de Cache",
|
||||
"workspace.usage.breakdown.cacheWrite": "Escrita em Cache",
|
||||
|
||||
@@ -340,6 +340,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Input",
|
||||
"workspace.usage.table.output": "Output",
|
||||
"workspace.usage.table.cost": "Omkostning",
|
||||
"workspace.usage.table.session": "Session",
|
||||
"workspace.usage.breakdown.input": "Input",
|
||||
"workspace.usage.breakdown.cacheRead": "Cache læst",
|
||||
"workspace.usage.breakdown.cacheWrite": "Cache skriv",
|
||||
|
||||
@@ -342,6 +342,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Input",
|
||||
"workspace.usage.table.output": "Output",
|
||||
"workspace.usage.table.cost": "Kosten",
|
||||
"workspace.usage.table.session": "Sitzung",
|
||||
"workspace.usage.breakdown.input": "Input",
|
||||
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
||||
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
||||
|
||||
@@ -334,6 +334,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Input",
|
||||
"workspace.usage.table.output": "Output",
|
||||
"workspace.usage.table.cost": "Cost",
|
||||
"workspace.usage.table.session": "Session",
|
||||
"workspace.usage.breakdown.input": "Input",
|
||||
"workspace.usage.breakdown.cacheRead": "Cache Read",
|
||||
"workspace.usage.breakdown.cacheWrite": "Cache Write",
|
||||
|
||||
@@ -343,6 +343,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Entrada",
|
||||
"workspace.usage.table.output": "Salida",
|
||||
"workspace.usage.table.cost": "Costo",
|
||||
"workspace.usage.table.session": "Sesión",
|
||||
"workspace.usage.breakdown.input": "Entrada",
|
||||
"workspace.usage.breakdown.cacheRead": "Lectura de Caché",
|
||||
"workspace.usage.breakdown.cacheWrite": "Escritura de Caché",
|
||||
|
||||
@@ -348,6 +348,7 @@ export const dict = {
|
||||
"workspace.usage.table.input": "Entrée",
|
||||
"workspace.usage.table.output": "Sortie",
|
||||
"workspace.usage.table.cost": "Coût",
|
||||
"workspace.usage.table.session": "Session",
|
||||
"workspace.usage.breakdown.input": "Entrée",
|
||||
"workspace.usage.breakdown.cacheRead": "Lecture cache",
|
||||
"workspace.usage.breakdown.cacheWrite": "Écriture cache",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user