opencode-tui-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +156 -0
- package/LICENSE +21 -0
- package/README.ja.md +140 -0
- package/README.ko.md +191 -0
- package/README.md +199 -0
- package/README.zh.md +140 -0
- package/docs/banner.png +0 -0
- package/docs/preview-command.png +0 -0
- package/docs/preview-result.png +0 -0
- package/package.json +53 -0
- package/src/core/api-wrapper.ts +98 -0
- package/src/index.tsx +28 -0
- package/src/plugins/disconnect.tsx +113 -0
- package/src/plugins/lsp-toggle.tsx +96 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Contributing to opencode-tui-utils
|
|
2
|
+
|
|
3
|
+
Thank you for taking the time to improve `opencode-tui-utils`.
|
|
4
|
+
|
|
5
|
+
This package is intentionally small. A good contribution should feel native to opencode's TUI, solve one clear problem, and avoid changing how opencode itself works.
|
|
6
|
+
|
|
7
|
+
## Before You Start
|
|
8
|
+
|
|
9
|
+
- Check whether the feature already exists in opencode.
|
|
10
|
+
- Check existing issues and pull requests for similar work.
|
|
11
|
+
- Keep the first version of a command small enough to review and test manually.
|
|
12
|
+
- Avoid new runtime dependencies unless they are clearly necessary.
|
|
13
|
+
|
|
14
|
+
## Project Layout
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
src/
|
|
18
|
+
core/
|
|
19
|
+
api-wrapper.ts Shared wrapper around the opencode TUI API
|
|
20
|
+
plugins/
|
|
21
|
+
disconnect.tsx Current provider disconnect command
|
|
22
|
+
your-feature.tsx New commands belong here
|
|
23
|
+
index.tsx Public plugin entry point
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Adding A Command
|
|
27
|
+
|
|
28
|
+
Create a file in `src/plugins/`:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
/** @jsxImportSource @opentui/solid */
|
|
32
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
33
|
+
import { createWrappedAPI } from "../core/api-wrapper"
|
|
34
|
+
|
|
35
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
36
|
+
id: "opencode-tui-utils.your-feature",
|
|
37
|
+
async tui(rawApi) {
|
|
38
|
+
const api = createWrappedAPI(rawApi)
|
|
39
|
+
|
|
40
|
+
api.keymap.registerLayer({
|
|
41
|
+
commands: [
|
|
42
|
+
{
|
|
43
|
+
name: "opencode-tui-utils.your-feature",
|
|
44
|
+
title: "Your Feature",
|
|
45
|
+
category: "Utility",
|
|
46
|
+
namespace: "palette",
|
|
47
|
+
slashName: "your-command",
|
|
48
|
+
async run() {
|
|
49
|
+
api.ui.toast({
|
|
50
|
+
title: "Ready",
|
|
51
|
+
message: "Your command ran successfully.",
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default plugin
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Register it in `src/index.tsx`:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import yourPlugin from "./plugins/your-feature"
|
|
67
|
+
|
|
68
|
+
const plugins: TuiPluginModule[] = [disconnectPlugin, yourPlugin]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API Wrapper
|
|
72
|
+
|
|
73
|
+
Use `createWrappedAPI(rawApi)` in plugin files instead of calling the raw opencode API directly. This keeps API-specific changes isolated to `src/core/api-wrapper.ts` when possible.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const api = createWrappedAPI(rawApi)
|
|
79
|
+
|
|
80
|
+
api.ui.toast({
|
|
81
|
+
variant: "success",
|
|
82
|
+
message: "Done",
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## KV Storage
|
|
87
|
+
|
|
88
|
+
The wrapper currently supports `get`, `set`, `getJSON`, and `setJSON`.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
await api.kv.setJSON("my-feature.settings", { enabled: true })
|
|
92
|
+
|
|
93
|
+
const settings = await api.kv.getJSON<{ enabled: boolean }>(
|
|
94
|
+
"my-feature.settings",
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The current opencode TUI KV API does not expose delete. If you need deletion semantics, store an updated object without the removed field.
|
|
99
|
+
|
|
100
|
+
## Local Testing
|
|
101
|
+
|
|
102
|
+
Type-check the package:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npm install
|
|
106
|
+
npm run build
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
To test a command in opencode before publishing, reference the source file from `~/.config/opencode/tui.json`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
114
|
+
"plugin": ["/absolute/path/to/opencode-tui-utils/src/plugins/your-feature.tsx"]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then restart opencode and run the slash command.
|
|
119
|
+
|
|
120
|
+
## Pull Requests
|
|
121
|
+
|
|
122
|
+
- Describe the user-facing problem the change solves.
|
|
123
|
+
- Include manual testing steps.
|
|
124
|
+
- Update `README.md` if the command is user-facing.
|
|
125
|
+
- Update localized README files when changing install, compatibility, or command behavior.
|
|
126
|
+
- Keep unrelated formatting and refactors out of feature PRs.
|
|
127
|
+
|
|
128
|
+
Suggested PR title format:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
feat: add /your-command
|
|
132
|
+
fix: handle missing auth file in /disconnect
|
|
133
|
+
docs: clarify plugin installation
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Command Scope
|
|
137
|
+
|
|
138
|
+
Good fits for this package:
|
|
139
|
+
|
|
140
|
+
| Idea | Why |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| Provider quick switch | Small TUI-native provider utility. |
|
|
143
|
+
| Session favorites | Helps navigate opencode sessions without changing core behavior. |
|
|
144
|
+
| Config sanity checks | Useful local diagnostics with clear output. |
|
|
145
|
+
|
|
146
|
+
Poor fits:
|
|
147
|
+
|
|
148
|
+
| Idea | Why |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| Agent orchestration | Better handled by dedicated workflow plugins. |
|
|
151
|
+
| Prompt packs | Better as opencode commands or separate repositories. |
|
|
152
|
+
| Provider backends | Should be maintained as provider-specific plugins. |
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
By contributing, you agree that your contribution will be released under the MIT License.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Blue-B
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# opencode TUI Utils
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [한국어](./README.ko.md) | 日本語 | [中文](./README.zh.md)
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/banner.png" alt="opencode-tui-utils" width="860">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-tui-utils)
|
|
10
|
+
[](https://github.com/Blue-B/opencode-tui-utils/actions)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
[opencode](https://opencode.ai) のTUIで使う小さなユーティリティ集です。最初のコマンドは、接続済みプロバイダーを安全に解除する `/disconnect` です。
|
|
14
|
+
|
|
15
|
+
このプロジェクトは、コマンドを1つずつ追加して育てる前提で構成されています。各ユーティリティは、opencode TUI内の明確な問題を1つ解決し、レビューとテストがしやすい形に保ちます。
|
|
16
|
+
|
|
17
|
+
## プレビュー
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="docs/preview-command.png" alt="/disconnect コマンド選択" width="600">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="docs/preview-result.png" alt="プロバイダー切断完了" width="600">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
実際の画面ではopencodeのTUI dialogコンポーネントを使います。外部スクリプトではなく、opencodeのコマンドパレットとdialogの流れの中で動作します。
|
|
28
|
+
|
|
29
|
+
## なぜ使うのか
|
|
30
|
+
|
|
31
|
+
| 必要なこと | 得られるもの |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| 1つのプロバイダーだけ安全に削除 | TUIで選んだ認証項目だけを削除できます |
|
|
34
|
+
| JSONの手動編集を避ける | `~/.local/share/opencode/auth.json`を直接編集する必要がありません |
|
|
35
|
+
| トークンを表示しない | プロバイダー名と認証タイプだけを表示し、トークン値は出力しません |
|
|
36
|
+
| 小さな機能を追加しやすい | 共通ローダーとAPIラッパーで新しいユーティリティを追加しやすくしています |
|
|
37
|
+
|
|
38
|
+
## クイックスタート
|
|
39
|
+
|
|
40
|
+
opencodeのプラグインインストーラーでnpmからインストールします。
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opencode plugin opencode-tui-utils
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
このコマンドはパッケージをインストールし、opencodeの設定を更新します。設定を手動で管理する場合は、`~/.config/opencode/tui.json`に次の項目があることを確認してください。
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
51
|
+
"plugin": ["opencode-tui-utils"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
opencodeを再起動して実行します。
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
/disconnect
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## コマンド
|
|
62
|
+
|
|
63
|
+
| コマンド | エイリアス | 説明 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `/disconnect` | `/dc` | 接続済みプロバイダーを1つ選び、opencodeの認証ストレージから削除します。新しいセションを開くと変更が反映されます。 |
|
|
66
|
+
| `/lsp-toggle` | — | `~/.config/opencode/opencode.json` の `lsp` 設定を true/false に切り替えます。opencodeの再起動が必要です。 |
|
|
67
|
+
|
|
68
|
+
## `/disconnect` の動作
|
|
69
|
+
|
|
70
|
+
`/disconnect` はopencodeの認証ファイルを読み、トップレベルのプロバイダーキーを一覧表示し、選択したプロバイダーだけを削除して同じファイルへ保存します。
|
|
71
|
+
|
|
72
|
+
例:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"github": { "type": "copilot-free" },
|
|
77
|
+
"anthropic": { "type": "api-key" },
|
|
78
|
+
"openai": { "type": "api-key" }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`github`を選択した場合、トップレベルの`github`キーだけが削除されます。`anthropic`や`openai`など他のプロバイダーは残ります。
|
|
83
|
+
|
|
84
|
+
> **注意:** opencodeはセション開始時に認証ファイルを読み込みます。切断後は新しいopencodeウィンドウまたはセションを開くと、更新されたプロバイダー一覧が反映されます。
|
|
85
|
+
|
|
86
|
+
出発点は [opencode issue #10494](https://github.com/anomalyco/opencode/issues/10494) で話されていたプロバイダー解除の不便さでした。
|
|
87
|
+
|
|
88
|
+
## データ保存
|
|
89
|
+
|
|
90
|
+
| データ | 場所 | 説明 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| プロバイダー認証 | `~/.local/share/opencode/auth.json` | 選択したプロバイダーキーだけを削除します |
|
|
93
|
+
| カスタム認証パス | `OPENCODE_AUTH_PATH=/path/to/auth.json` | 標準以外の場所を使う場合の上書きです |
|
|
94
|
+
| プラグインソース | npm package / opencode plugin cache | `/disconnect`は外部サービスへリクエストしません |
|
|
95
|
+
|
|
96
|
+
`/disconnect`はネットワークリクエストを送らず、トークン値をコピーしたりUIに出力したりしません。
|
|
97
|
+
|
|
98
|
+
## ユーティリティの追加
|
|
99
|
+
|
|
100
|
+
新しいコマンドは `src/plugins/` 配下に個別のプラグインモジュールとして追加し、`src/index.tsx`から登録します。
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
src/
|
|
104
|
+
core/
|
|
105
|
+
api-wrapper.ts opencode TUI APIの共通ラッパー
|
|
106
|
+
plugins/
|
|
107
|
+
disconnect.tsx プロバイダー解除コマンド
|
|
108
|
+
lsp-toggle.tsx LSP切り替えコマンド
|
|
109
|
+
your-command.tsx 新しいユーティリティの追加先
|
|
110
|
+
index.tsx 公開プラグインエントリ
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
opencode TUI APIを使う場合は `createWrappedAPI(rawApi)` を通してください。opencodeのプラグインAPIが変わった場合の修正箇所を減らすためです。
|
|
114
|
+
|
|
115
|
+
## 開発
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/Blue-B/opencode-tui-utils.git
|
|
119
|
+
cd opencode-tui-utils
|
|
120
|
+
npm install
|
|
121
|
+
npm run build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 互換性
|
|
125
|
+
|
|
126
|
+
テスト済みの環境です。
|
|
127
|
+
|
|
128
|
+
| ツール | バージョン |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| opencode | 1.14.46 |
|
|
131
|
+
|
|
132
|
+
このパッケージはopencodeのTUI Plugin APIを使うため、peer dependencyとして`@opencode-ai/plugin >=1.14.42`を指定しています。
|
|
133
|
+
|
|
134
|
+
## コントリビューション
|
|
135
|
+
|
|
136
|
+
IssueとPRを歓迎します。新しいコマンドを追加する前に[CONTRIBUTING.md](CONTRIBUTING.md)を確認してください。小さく、目的がはっきりした変更を歓迎します。
|
|
137
|
+
|
|
138
|
+
## ライセンス
|
|
139
|
+
|
|
140
|
+
MIT
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# opencode TUI Utils
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 한국어 | [日本語](./README.ja.md) | [中文](./README.zh.md)
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/banner.png" alt="opencode-tui-utils" width="860">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-tui-utils)
|
|
10
|
+
[](https://github.com/Blue-B/opencode-tui-utils/actions)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
[opencode](https://opencode.ai) TUI에서 자주 필요한 작업을 작은 명령어로 추가하는 플러그인 패키지입니다. 첫 기능은 연결된 프로바이더를 안전하게 해제하는 `/disconnect` 명령입니다.
|
|
14
|
+
|
|
15
|
+
이 프로젝트는 명령어를 하나씩 확장할 수 있도록 구성되어 있습니다. 각 유틸리티는 opencode TUI 안에서 한 가지 문제를 명확하게 해결하고, 검토와 테스트가 쉬운 형태를 유지하는 것을 기준으로 합니다.
|
|
16
|
+
|
|
17
|
+
## 미리보기
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="docs/preview-command.png" alt="/disconnect 명령 선택" width="600">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="docs/preview-result.png" alt="프로바이더 해제 완료" width="600">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
실제 화면은 opencode의 TUI dialog 컴포넌트를 사용합니다. 별도 스크립트를 실행하는 방식이 아니라 opencode 안의 명령 팔레트와 dialog 흐름에서 자연스럽게 동작합니다.
|
|
28
|
+
|
|
29
|
+
## 왜 필요한가요?
|
|
30
|
+
|
|
31
|
+
| 필요 | 제공 가치 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| 프로바이더 하나만 안전하게 제거 | TUI에서 선택한 인증 항목 하나만 삭제합니다 |
|
|
34
|
+
| 직접 JSON 수정 방지 | `~/.local/share/opencode/auth.json`을 손으로 열어 수정하지 않아도 됩니다 |
|
|
35
|
+
| 토큰 노출 방지 | 프로바이더 이름과 인증 타입만 표시하고 토큰 값은 출력하지 않습니다 |
|
|
36
|
+
| 기능 확장 | 공용 플러그인 로더와 API 래퍼로 새 유틸리티를 추가하기 쉽습니다 |
|
|
37
|
+
|
|
38
|
+
## 시작하기
|
|
39
|
+
|
|
40
|
+
opencode의 플러그인 설치 명령으로 npm에서 설치합니다.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opencode plugin opencode-tui-utils
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
이 명령은 패키지를 설치하고 opencode 설정을 갱신합니다. 직접 설정을 관리한다면 `~/.config/opencode/tui.json`에 아래 항목이 있어야 합니다.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
51
|
+
"plugin": ["opencode-tui-utils"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
opencode를 재시작한 뒤 실행합니다.
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
/disconnect
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 명령어
|
|
62
|
+
|
|
63
|
+
| 명령어 | 별칭 | 설명 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `/disconnect` | `/dc` | 연결된 프로바이더 중 하나를 선택해 opencode 인증 저장소에서 제거합니다. 새 세션을 열어야 변경 사항이 반영됩니다. |
|
|
66
|
+
| `/lsp-toggle` | — | `~/.config/opencode/opencode.json`의 `lsp` 설정을 true/false로 전환합니다. opencode 재시작 필요. |
|
|
67
|
+
|
|
68
|
+
## `/disconnect` 동작 방식
|
|
69
|
+
|
|
70
|
+
`/disconnect`는 opencode 인증 파일을 읽고, 최상위 프로바이더 키 목록을 보여준 뒤, 사용자가 선택한 프로바이더 하나만 제거한 상태로 같은 파일을 다시 저장합니다.
|
|
71
|
+
|
|
72
|
+
예시 인증 구조:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"github": { "type": "copilot-free" },
|
|
77
|
+
"anthropic": { "type": "api-key" },
|
|
78
|
+
"openai": { "type": "api-key" }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
여기서 `github`를 선택하면 최상위 `github` 키만 제거됩니다. `anthropic`, `openai` 같은 다른 프로바이더 항목은 그대로 유지됩니다.
|
|
83
|
+
|
|
84
|
+
> **참고:** opencode는 세션 시작 시 인증 파일을 읽습니다. 해제 후에는 새로운 opencode 창이나 세션을 열어야 업데이트된 프로바이더 목록이 반영됩니다.
|
|
85
|
+
|
|
86
|
+
처음 시작점은 [opencode issue #10494](https://github.com/anomalyco/opencode/issues/10494)에서 다뤄진 프로바이더 해제 불편함이었습니다.
|
|
87
|
+
|
|
88
|
+
## 데이터 저장
|
|
89
|
+
|
|
90
|
+
| 데이터 | 위치 | 설명 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| 프로바이더 인증 | `~/.local/share/opencode/auth.json` | 선택한 프로바이더 키만 이 파일에서 제거합니다 |
|
|
93
|
+
| 사용자 지정 인증 경로 | `OPENCODE_AUTH_PATH=/path/to/auth.json` | 기본 위치가 아닌 경우 사용할 수 있습니다 |
|
|
94
|
+
| 플러그인 소스 | npm 패키지 / opencode plugin cache | `/disconnect`는 외부 서비스에 요청하지 않습니다 |
|
|
95
|
+
|
|
96
|
+
`/disconnect`는 네트워크 요청을 보내지 않고, 토큰 값을 복사하거나 UI에 출력하지 않습니다.
|
|
97
|
+
|
|
98
|
+
## 유틸리티 확장 방법
|
|
99
|
+
|
|
100
|
+
새 명령어는 `src/plugins/` 아래에 별도 플러그인 모듈로 추가하고, `src/index.tsx`에서 등록합니다.
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
src/
|
|
104
|
+
core/
|
|
105
|
+
api-wrapper.ts opencode TUI API 공용 래퍼
|
|
106
|
+
plugins/
|
|
107
|
+
disconnect.tsx 프로바이더 해제 명령어
|
|
108
|
+
lsp-toggle.tsx LSP 전환 명령어
|
|
109
|
+
your-command.tsx 새 유틸리티 추가 위치
|
|
110
|
+
index.tsx 공개 플러그인 진입점
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
최소 명령어 형태:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
/** @jsxImportSource @opentui/solid */
|
|
117
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
118
|
+
import { createWrappedAPI } from "../core/api-wrapper"
|
|
119
|
+
|
|
120
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
121
|
+
id: "opencode-tui-utils.your-command",
|
|
122
|
+
async tui(rawApi) {
|
|
123
|
+
const api = createWrappedAPI(rawApi)
|
|
124
|
+
|
|
125
|
+
api.keymap.registerLayer({
|
|
126
|
+
commands: [
|
|
127
|
+
{
|
|
128
|
+
name: "opencode-tui-utils.your-command",
|
|
129
|
+
title: "Your Command",
|
|
130
|
+
category: "Utility",
|
|
131
|
+
namespace: "palette",
|
|
132
|
+
slashName: "your-command",
|
|
133
|
+
async run() {
|
|
134
|
+
api.ui.toast({ message: "Command ran successfully." })
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default plugin
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
그 다음 `src/index.tsx`에 등록합니다.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import yourCommand from "./plugins/your-command"
|
|
149
|
+
|
|
150
|
+
const plugins: TuiPluginModule[] = [disconnectPlugin, lspTogglePlugin, yourCommand]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
opencode TUI API를 사용할 때는 `createWrappedAPI(rawApi)`를 거치도록 합니다. opencode 플러그인 API가 바뀌었을 때 수정 지점을 줄이기 위한 구조입니다.
|
|
154
|
+
|
|
155
|
+
## 개발
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
git clone https://github.com/Blue-B/opencode-tui-utils.git
|
|
159
|
+
cd opencode-tui-utils
|
|
160
|
+
npm install
|
|
161
|
+
npm run build
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
로컬 소스 파일 테스트 예시:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
169
|
+
"plugin": ["/absolute/path/to/opencode-tui-utils/src/plugins/disconnect.tsx"]
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`tui.json` 변경 후에는 opencode를 재시작해야 합니다.
|
|
174
|
+
|
|
175
|
+
## 호환성
|
|
176
|
+
|
|
177
|
+
테스트한 환경입니다.
|
|
178
|
+
|
|
179
|
+
| 도구 | 버전 |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| opencode | 1.14.46 |
|
|
182
|
+
|
|
183
|
+
이 패키지는 opencode의 TUI Plugin API를 사용하므로 peer dependency로 `@opencode-ai/plugin >=1.14.42`를 선언합니다.
|
|
184
|
+
|
|
185
|
+
## 기여
|
|
186
|
+
|
|
187
|
+
이슈와 PR은 환영합니다. 새 명령어를 추가하기 전에는 [CONTRIBUTING.md](CONTRIBUTING.md)를 확인해 주세요. 작고 명확한 변경을 선호합니다.
|
|
188
|
+
|
|
189
|
+
## 라이선스
|
|
190
|
+
|
|
191
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# opencode TUI Utils
|
|
2
|
+
|
|
3
|
+
English | [한국어](./README.ko.md) | [日本語](./README.ja.md) | [中文](./README.zh.md)
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/banner.png" alt="opencode-tui-utils" width="860">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-tui-utils)
|
|
10
|
+
[](https://github.com/Blue-B/opencode-tui-utils/actions)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
Small native-feeling TUI utilities for [opencode](https://opencode.ai). The first command adds a safe provider disconnect flow, so you can remove one connected provider without editing `auth.json` by hand.
|
|
14
|
+
|
|
15
|
+
This project is built to grow command by command. Each utility should solve one focused opencode TUI problem and stay easy to review, test, and remove.
|
|
16
|
+
|
|
17
|
+
## Preview
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="docs/preview-command.png" alt="/disconnect command selection" width="600">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="docs/preview-result.png" alt="Provider disconnected confirmation" width="600">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
The command uses opencode's own TUI dialog components, so it appears inside the same command palette and dialog flow instead of launching a separate script.
|
|
28
|
+
|
|
29
|
+
## Why use opencode TUI Utils
|
|
30
|
+
|
|
31
|
+
| Need | What you get |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Remove one provider safely | Select a provider in the TUI and remove only that auth entry |
|
|
34
|
+
| Avoid manual JSON edits | No need to open or hand-edit `~/.local/share/opencode/auth.json` |
|
|
35
|
+
| Keep tokens private | Provider names and auth types are shown, token values are never printed |
|
|
36
|
+
| Add more small commands | Shared plugin loader and API wrapper make new utilities straightforward |
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
Install from npm with opencode's plugin installer:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opencode plugin opencode-tui-utils
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That command installs the package and updates your opencode config. If you manage the config manually, make sure `~/.config/opencode/tui.json` includes:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
51
|
+
"plugin": ["opencode-tui-utils"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Restart opencode and run:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
/disconnect
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Commands
|
|
62
|
+
|
|
63
|
+
| Command | Alias | Description |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `/disconnect` | `/dc` | Pick one connected provider and remove it from opencode auth storage. Open a new session to reflect the change. |
|
|
66
|
+
| `/lsp-toggle` | — | Toggle `lsp: true/false` in `~/.config/opencode/opencode.json`. Requires opencode restart. |
|
|
67
|
+
|
|
68
|
+
## How `/disconnect` works
|
|
69
|
+
|
|
70
|
+
`/disconnect` reads your opencode auth file, lists the provider keys, and rewrites the same file after removing the one provider you selected.
|
|
71
|
+
|
|
72
|
+
Example auth shape:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"github": { "type": "copilot-free" },
|
|
77
|
+
"anthropic": { "type": "api-key" },
|
|
78
|
+
"openai": { "type": "api-key" }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If you select `github`, only the top-level `github` key is removed. Other provider entries stay untouched.
|
|
83
|
+
|
|
84
|
+
> **Note:** opencode reads the auth file at session start. After disconnecting, open a new opencode window or session to see the updated provider list.
|
|
85
|
+
|
|
86
|
+
This started from the provider disconnect pain point discussed in [opencode issue #10494](https://github.com/anomalyco/opencode/issues/10494).
|
|
87
|
+
|
|
88
|
+
## Data Storage
|
|
89
|
+
|
|
90
|
+
| Data | Location | Notes |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| Provider auth | `~/.local/share/opencode/auth.json` | The selected provider key is removed from this file |
|
|
93
|
+
| Custom auth path | `OPENCODE_AUTH_PATH=/path/to/auth.json` | Optional override for non-standard setups |
|
|
94
|
+
| Plugin source | npm package / opencode plugin cache | No external service is contacted by `/disconnect` |
|
|
95
|
+
|
|
96
|
+
`/disconnect` does not send network requests, copy token values, or print token values to the UI.
|
|
97
|
+
|
|
98
|
+
## Adding More Utilities
|
|
99
|
+
|
|
100
|
+
New commands are added as separate plugin modules under `src/plugins/` and registered from `src/index.tsx`.
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
src/
|
|
104
|
+
core/
|
|
105
|
+
api-wrapper.ts Shared wrapper around the opencode TUI API
|
|
106
|
+
plugins/
|
|
107
|
+
disconnect.tsx Provider disconnect command
|
|
108
|
+
lsp-toggle.tsx LSP toggle command
|
|
109
|
+
your-command.tsx Add new utilities here
|
|
110
|
+
index.tsx Public plugin entry point
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Minimal command shape:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
/** @jsxImportSource @opentui/solid */
|
|
117
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
118
|
+
import { createWrappedAPI } from "../core/api-wrapper"
|
|
119
|
+
|
|
120
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
121
|
+
id: "opencode-tui-utils.your-command",
|
|
122
|
+
async tui(rawApi) {
|
|
123
|
+
const api = createWrappedAPI(rawApi)
|
|
124
|
+
|
|
125
|
+
api.keymap.registerLayer({
|
|
126
|
+
commands: [
|
|
127
|
+
{
|
|
128
|
+
name: "opencode-tui-utils.your-command",
|
|
129
|
+
title: "Your Command",
|
|
130
|
+
category: "Utility",
|
|
131
|
+
namespace: "palette",
|
|
132
|
+
slashName: "your-command",
|
|
133
|
+
async run() {
|
|
134
|
+
api.ui.toast({ message: "Command ran successfully." })
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default plugin
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then register it in `src/index.tsx`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import yourCommand from "./plugins/your-command"
|
|
149
|
+
|
|
150
|
+
const plugins: TuiPluginModule[] = [disconnectPlugin, lspTogglePlugin, yourCommand]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Use `createWrappedAPI(rawApi)` for opencode TUI APIs. It keeps API-specific changes easier to isolate when opencode updates its plugin API.
|
|
154
|
+
|
|
155
|
+
## Tips
|
|
156
|
+
|
|
157
|
+
### Switching model variants (xhigh / high / max)
|
|
158
|
+
|
|
159
|
+
Use `Ctrl + T` in the TUI to cycle through reasoning variants for the current model. To switch models entirely, use `Ctrl + X` then `M`, or type `/models`.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
git clone https://github.com/Blue-B/opencode-tui-utils.git
|
|
165
|
+
cd opencode-tui-utils
|
|
166
|
+
npm install
|
|
167
|
+
npm run build
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Local source-file testing:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
175
|
+
"plugin": ["/absolute/path/to/opencode-tui-utils/src/plugins/disconnect.tsx"]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Restart opencode after changing `tui.json`.
|
|
180
|
+
|
|
181
|
+
## Compatibility
|
|
182
|
+
|
|
183
|
+
Tested with:
|
|
184
|
+
|
|
185
|
+
| Tool | Version |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| opencode | 1.14.46 |
|
|
188
|
+
|
|
189
|
+
The package declares `@opencode-ai/plugin >=1.14.42` as a peer dependency because it uses opencode's TUI Plugin API.
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
Issues and PRs are welcome. Please keep changes small and focused. See [CONTRIBUTING.md](CONTRIBUTING.md) before adding a new command.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
[MIT](LICENSE) — Blue-B © 2026
|
|
198
|
+
|
|
199
|
+
You are free to use, modify, and distribute this project, including for commercial purposes. The only requirement is to keep the copyright notice and license text included in any copies or substantial portions.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# opencode TUI Utils
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [한국어](./README.ko.md) | [日本語](./README.ja.md) | 中文
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="docs/banner.png" alt="opencode-tui-utils" width="860">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-tui-utils)
|
|
10
|
+
[](https://github.com/Blue-B/opencode-tui-utils/actions)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
面向 [opencode](https://opencode.ai) TUI 的小型工具集。第一个命令是 `/disconnect`,用于安全地断开一个已连接的 provider,不需要手动编辑 `auth.json`。
|
|
14
|
+
|
|
15
|
+
这个项目按“一个命令解决一个问题”的方式扩展。每个工具都应该聚焦于一个 opencode TUI 场景,并保持容易审查、测试和移除。
|
|
16
|
+
|
|
17
|
+
## 预览
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="docs/preview-command.png" alt="/disconnect 命令选择" width="600">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="docs/preview-result.png" alt="Provider 断开确认" width="600">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
实际界面使用 opencode 的 TUI dialog 组件,所以它在 opencode 的命令面板和 dialog 流程中运行,而不是额外启动脚本。
|
|
28
|
+
|
|
29
|
+
## 为什么使用它
|
|
30
|
+
|
|
31
|
+
| 需求 | 提供的能力 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| 安全移除一个 provider | 在 TUI 中选择并只删除对应认证项 |
|
|
34
|
+
| 避免手动编辑 JSON | 不需要直接打开 `~/.local/share/opencode/auth.json` |
|
|
35
|
+
| 避免 token 暴露 | 只显示 provider 名称和认证类型,不输出 token 值 |
|
|
36
|
+
| 方便继续扩展 | 共享插件加载器和 API wrapper 让新增工具更简单 |
|
|
37
|
+
|
|
38
|
+
## 快速开始
|
|
39
|
+
|
|
40
|
+
使用 opencode 的插件安装器从 npm 安装:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
opencode plugin opencode-tui-utils
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
这个命令会安装包并更新 opencode 配置。如果你手动管理配置,请确保 `~/.config/opencode/tui.json` 包含:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
51
|
+
"plugin": ["opencode-tui-utils"]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
重启 opencode,然后运行:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
/disconnect
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 命令
|
|
62
|
+
|
|
63
|
+
| 命令 | 别名 | 说明 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `/disconnect` | `/dc` | 选择一个已连接的 provider,并从 opencode 的认证存储中移除。需要打开新会话才能看到变更。 |
|
|
66
|
+
| `/lsp-toggle` | — | 切换 `~/.config/opencode/opencode.json` 中的 `lsp` 设置。需要重启 opencode。 |
|
|
67
|
+
|
|
68
|
+
## `/disconnect` 如何工作
|
|
69
|
+
|
|
70
|
+
`/disconnect` 会读取 opencode 的认证文件,列出顶层 provider key,然后只删除你选择的那一个 provider,并把结果写回同一个文件。
|
|
71
|
+
|
|
72
|
+
示例:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"github": { "type": "copilot-free" },
|
|
77
|
+
"anthropic": { "type": "api-key" },
|
|
78
|
+
"openai": { "type": "api-key" }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
如果选择 `github`,只会删除顶层的 `github` key。`anthropic`、`openai` 等其他 provider 会保持不变。
|
|
83
|
+
|
|
84
|
+
> **注意:** opencode 在会话启动时读取认证文件。断开连接后,需要打开新的 opencode 窗口或会话才能看到更新后的 provider 列表。
|
|
85
|
+
|
|
86
|
+
它最初来自 [opencode issue #10494](https://github.com/anomalyco/opencode/issues/10494) 中提到的 provider 断开问题。
|
|
87
|
+
|
|
88
|
+
## 数据存储
|
|
89
|
+
|
|
90
|
+
| 数据 | 位置 | 说明 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| Provider 认证 | `~/.local/share/opencode/auth.json` | 只从这里删除选择的 provider key |
|
|
93
|
+
| 自定义认证路径 | `OPENCODE_AUTH_PATH=/path/to/auth.json` | 用于非默认数据目录 |
|
|
94
|
+
| 插件源码 | npm package / opencode plugin cache | `/disconnect` 不会访问外部服务 |
|
|
95
|
+
|
|
96
|
+
`/disconnect` 不会发送网络请求,不会复制 token 值,也不会把 token 值显示在 UI 中。
|
|
97
|
+
|
|
98
|
+
## 添加更多工具
|
|
99
|
+
|
|
100
|
+
新命令作为单独的插件模块放在 `src/plugins/` 下,并从 `src/index.tsx` 注册。
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
src/
|
|
104
|
+
core/
|
|
105
|
+
api-wrapper.ts opencode TUI API 的共享 wrapper
|
|
106
|
+
plugins/
|
|
107
|
+
disconnect.tsx Provider 断开命令
|
|
108
|
+
lsp-toggle.tsx LSP 切换命令
|
|
109
|
+
your-command.tsx 新工具放在这里
|
|
110
|
+
index.tsx 公开插件入口
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
使用 opencode TUI API 时请通过 `createWrappedAPI(rawApi)`。这样当 opencode 插件 API 变化时,更新位置会更集中。
|
|
114
|
+
|
|
115
|
+
## 开发
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/Blue-B/opencode-tui-utils.git
|
|
119
|
+
cd opencode-tui-utils
|
|
120
|
+
npm install
|
|
121
|
+
npm run build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 兼容性
|
|
125
|
+
|
|
126
|
+
已测试环境:
|
|
127
|
+
|
|
128
|
+
| 工具 | 版本 |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| opencode | 1.14.46 |
|
|
131
|
+
|
|
132
|
+
这个包使用 opencode 的 TUI Plugin API,因此将 `@opencode-ai/plugin >=1.14.42` 声明为 peer dependency。
|
|
133
|
+
|
|
134
|
+
## 贡献
|
|
135
|
+
|
|
136
|
+
欢迎 issue 和 PR。添加新命令前请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。更推荐小而清晰的改动。
|
|
137
|
+
|
|
138
|
+
## 许可证
|
|
139
|
+
|
|
140
|
+
MIT
|
package/docs/banner.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-tui-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Add /disconnect command to opencode - safely disconnect providers without editing auth.json",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc --noEmit",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"test": "npm run build",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"docs",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.*.md",
|
|
18
|
+
"CONTRIBUTING.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"opencode",
|
|
23
|
+
"tui",
|
|
24
|
+
"plugin",
|
|
25
|
+
"disconnect",
|
|
26
|
+
"provider",
|
|
27
|
+
"auth",
|
|
28
|
+
"copilot"
|
|
29
|
+
],
|
|
30
|
+
"author": "Blue-B <source_vs@naver.com>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@opencode-ai/plugin": ">=1.14.42",
|
|
34
|
+
"@opentui/solid": ">=0.2.6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@opencode-ai/plugin": "1.14.46",
|
|
38
|
+
"@opentui/solid": "^0.2.6",
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/Blue-B/opencode-tui-utils.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/Blue-B/opencode-tui-utils/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/Blue-B/opencode-tui-utils#readme"
|
|
53
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode TUI API Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Keeps opencode TUI API calls behind a small local layer so API-specific
|
|
5
|
+
* changes are easier to isolate.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TuiPluginApi } from "@opencode-ai/plugin/tui"
|
|
9
|
+
|
|
10
|
+
export interface WrappedAPI {
|
|
11
|
+
keymap: ReturnType<typeof createKeymapAPI>
|
|
12
|
+
ui: ReturnType<typeof createUIAPI>
|
|
13
|
+
kv: ReturnType<typeof createKVAPI>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keymap API wrapper for slash commands and palette commands.
|
|
18
|
+
*/
|
|
19
|
+
export function createKeymapAPI(api: TuiPluginApi) {
|
|
20
|
+
return {
|
|
21
|
+
registerLayer: (config: Parameters<typeof api.keymap.registerLayer>[0]) => {
|
|
22
|
+
return api.keymap.registerLayer(config)
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* UI API wrapper for dialogs and toasts.
|
|
29
|
+
*/
|
|
30
|
+
export function createUIAPI(api: TuiPluginApi) {
|
|
31
|
+
return {
|
|
32
|
+
dialog: {
|
|
33
|
+
replace: (component: Parameters<typeof api.ui.dialog.replace>[0]) => {
|
|
34
|
+
return api.ui.dialog.replace(component)
|
|
35
|
+
},
|
|
36
|
+
clear: () => {
|
|
37
|
+
return api.ui.dialog.clear()
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
toast: (options: Parameters<typeof api.ui.toast>[0]) => {
|
|
41
|
+
return api.ui.toast(options)
|
|
42
|
+
},
|
|
43
|
+
// Re-export UI components used by plugins.
|
|
44
|
+
DialogSelect: api.ui.DialogSelect,
|
|
45
|
+
DialogConfirm: api.ui.DialogConfirm,
|
|
46
|
+
DialogAlert: api.ui.DialogAlert,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* KV storage wrapper for future commands that need persistence.
|
|
52
|
+
*/
|
|
53
|
+
export function createKVAPI(api: TuiPluginApi) {
|
|
54
|
+
return {
|
|
55
|
+
get: async (key: string): Promise<string | undefined> => {
|
|
56
|
+
try {
|
|
57
|
+
return api.kv.get<string | undefined>(key)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`Failed to get KV key "${key}":`, error)
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
set: async (key: string, value: string): Promise<void> => {
|
|
64
|
+
try {
|
|
65
|
+
api.kv.set(key, value)
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Failed to set KV key "${key}":`, error)
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
getJSON: async <T = unknown>(key: string): Promise<T | undefined> => {
|
|
71
|
+
try {
|
|
72
|
+
const value = api.kv.get<string | undefined>(key)
|
|
73
|
+
return value ? JSON.parse(value) : undefined
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Failed to parse JSON from KV key "${key}":`, error)
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
setJSON: async <T = unknown>(key: string, value: T): Promise<void> => {
|
|
80
|
+
try {
|
|
81
|
+
api.kv.set(key, JSON.stringify(value))
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Failed to set JSON to KV key "${key}":`, error)
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Main wrapper used by plugins.
|
|
91
|
+
*/
|
|
92
|
+
export function createWrappedAPI(api: TuiPluginApi): WrappedAPI {
|
|
93
|
+
return {
|
|
94
|
+
keymap: createKeymapAPI(api),
|
|
95
|
+
ui: createUIAPI(api),
|
|
96
|
+
kv: createKVAPI(api),
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode TUI Utils - Plugin Loader
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
6
|
+
import disconnectPlugin from "./plugins/disconnect"
|
|
7
|
+
import lspTogglePlugin from "./plugins/lsp-toggle"
|
|
8
|
+
|
|
9
|
+
const plugins: TuiPluginModule[] = [disconnectPlugin, lspTogglePlugin]
|
|
10
|
+
|
|
11
|
+
export async function initializePlugins(...args: Parameters<TuiPluginModule["tui"]>) {
|
|
12
|
+
for (const plugin of plugins) {
|
|
13
|
+
try {
|
|
14
|
+
await plugin.tui(...args)
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(`Failed to initialize plugin:`, error)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const mainPlugin: TuiPluginModule & { id: string } = {
|
|
22
|
+
id: "opencode-tui-utils",
|
|
23
|
+
async tui(...args: Parameters<TuiPluginModule["tui"]>) {
|
|
24
|
+
await initializePlugins(...args)
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default mainPlugin
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
6
|
+
import { createWrappedAPI } from "../core/api-wrapper"
|
|
7
|
+
|
|
8
|
+
function getAuthPath() {
|
|
9
|
+
if (process.env.OPENCODE_AUTH_PATH) return process.env.OPENCODE_AUTH_PATH
|
|
10
|
+
|
|
11
|
+
const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share")
|
|
12
|
+
return join(dataHome, "opencode", "auth.json")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function loadAuth() {
|
|
16
|
+
try {
|
|
17
|
+
const content = await readFile(getAuthPath(), "utf-8")
|
|
18
|
+
return JSON.parse(content) as Record<string, { type: string }>
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Failed to load auth:", error)
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function saveAuth(data: Record<string, unknown>) {
|
|
26
|
+
try {
|
|
27
|
+
await writeFile(getAuthPath(), JSON.stringify(data, null, 2))
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("Failed to save auth:", error)
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
35
|
+
id: "opencode-tui-utils.disconnect",
|
|
36
|
+
async tui(rawApi) {
|
|
37
|
+
const api = createWrappedAPI(rawApi)
|
|
38
|
+
const { DialogSelect, DialogAlert } = api.ui
|
|
39
|
+
|
|
40
|
+
api.keymap.registerLayer({
|
|
41
|
+
commands: [
|
|
42
|
+
{
|
|
43
|
+
name: "opencode-tui-utils.disconnect",
|
|
44
|
+
title: "Disconnect Provider",
|
|
45
|
+
category: "Provider",
|
|
46
|
+
namespace: "palette",
|
|
47
|
+
slashName: "disconnect",
|
|
48
|
+
slashAliases: ["dc"],
|
|
49
|
+
async run() {
|
|
50
|
+
const data = await loadAuth()
|
|
51
|
+
if (!data) {
|
|
52
|
+
api.ui.dialog.replace(() => (
|
|
53
|
+
<DialogAlert
|
|
54
|
+
title="Error"
|
|
55
|
+
message="Could not read authentication file."
|
|
56
|
+
/>
|
|
57
|
+
))
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const providers = Object.keys(data)
|
|
62
|
+
if (providers.length === 0) {
|
|
63
|
+
api.ui.toast({
|
|
64
|
+
title: "No providers",
|
|
65
|
+
message: "No providers connected.",
|
|
66
|
+
})
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const options = providers.map((p) => ({
|
|
71
|
+
title: `${p} (${data[p]?.type ?? "unknown"})`,
|
|
72
|
+
value: p,
|
|
73
|
+
}))
|
|
74
|
+
|
|
75
|
+
api.ui.dialog.replace(() => (
|
|
76
|
+
<DialogSelect
|
|
77
|
+
title="Select provider to disconnect"
|
|
78
|
+
options={options}
|
|
79
|
+
onSelect={(option) => {
|
|
80
|
+
if (!option) return
|
|
81
|
+
|
|
82
|
+
void (async () => {
|
|
83
|
+
const nextData = { ...data }
|
|
84
|
+
delete nextData[option.value]
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await saveAuth(nextData)
|
|
88
|
+
api.ui.dialog.clear()
|
|
89
|
+
api.ui.toast({
|
|
90
|
+
variant: "success",
|
|
91
|
+
title: "Disconnected",
|
|
92
|
+
message: `Removed ${option.value}. Open a new session to reflect the change.`,
|
|
93
|
+
})
|
|
94
|
+
} catch {
|
|
95
|
+
api.ui.dialog.replace(() => (
|
|
96
|
+
<DialogAlert
|
|
97
|
+
title="Error"
|
|
98
|
+
message="Could not update authentication file."
|
|
99
|
+
/>
|
|
100
|
+
))
|
|
101
|
+
}
|
|
102
|
+
})()
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
))
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default plugin
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /lsp-toggle
|
|
3
|
+
*
|
|
4
|
+
* Toggles the `lsp` field in ~/.config/opencode/opencode.json between true/false.
|
|
5
|
+
* This controls whether opencode starts Language Server Protocol support.
|
|
6
|
+
*
|
|
7
|
+
* Important: opencode reads config at startup. Changing the JSON file does NOT
|
|
8
|
+
* hot-reload the setting. You must restart opencode after toggling for the
|
|
9
|
+
* change to take effect.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* /lsp-toggle Toggle LSP on/off
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** @jsxImportSource @opentui/solid */
|
|
16
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
17
|
+
import { homedir } from "node:os"
|
|
18
|
+
import { join } from "node:path"
|
|
19
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
20
|
+
import { createWrappedAPI } from "../core/api-wrapper"
|
|
21
|
+
|
|
22
|
+
/** Resolve opencode.json path. Honors OPENCODE_CONFIG_DIR if set. */
|
|
23
|
+
function getConfigPath() {
|
|
24
|
+
if (process.env.OPENCODE_CONFIG_DIR) {
|
|
25
|
+
return join(process.env.OPENCODE_CONFIG_DIR, "opencode.json")
|
|
26
|
+
}
|
|
27
|
+
return join(homedir(), ".config", "opencode", "opencode.json")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function loadConfig() {
|
|
31
|
+
try {
|
|
32
|
+
const content = await readFile(getConfigPath(), "utf-8")
|
|
33
|
+
return JSON.parse(content) as Record<string, unknown>
|
|
34
|
+
} catch {
|
|
35
|
+
// If file is missing or unreadable, start with an empty object.
|
|
36
|
+
return {}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function saveConfig(data: Record<string, unknown>) {
|
|
41
|
+
await writeFile(getConfigPath(), JSON.stringify(data, null, 2))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
45
|
+
id: "opencode-tui-utils.lsp-toggle",
|
|
46
|
+
async tui(rawApi) {
|
|
47
|
+
const api = createWrappedAPI(rawApi)
|
|
48
|
+
|
|
49
|
+
api.keymap.registerLayer({
|
|
50
|
+
commands: [
|
|
51
|
+
{
|
|
52
|
+
name: "opencode-tui-utils.lsp-toggle",
|
|
53
|
+
title: "Toggle LSP",
|
|
54
|
+
category: "Config",
|
|
55
|
+
namespace: "palette",
|
|
56
|
+
slashName: "lsp-toggle",
|
|
57
|
+
async run() {
|
|
58
|
+
const config = await loadConfig()
|
|
59
|
+
const isEnabled = !!config.lsp
|
|
60
|
+
const DialogConfirm = api.ui.DialogConfirm
|
|
61
|
+
|
|
62
|
+
api.ui.dialog.replace(() => (
|
|
63
|
+
<DialogConfirm
|
|
64
|
+
title="Toggle LSP"
|
|
65
|
+
message={
|
|
66
|
+
isEnabled
|
|
67
|
+
? "LSP is currently enabled. Disable it?"
|
|
68
|
+
: "LSP is currently disabled. Enable it?"
|
|
69
|
+
}
|
|
70
|
+
onConfirm={async () => {
|
|
71
|
+
config.lsp = !isEnabled
|
|
72
|
+
await saveConfig(config)
|
|
73
|
+
api.ui.dialog.clear()
|
|
74
|
+
|
|
75
|
+
const nextEnabled = !isEnabled
|
|
76
|
+
api.ui.toast({
|
|
77
|
+
variant: nextEnabled ? "success" : "info",
|
|
78
|
+
title: nextEnabled ? "LSP Enabled" : "LSP Disabled",
|
|
79
|
+
message: nextEnabled
|
|
80
|
+
? "LSP turned on. Restart opencode to apply."
|
|
81
|
+
: "LSP turned off. Restart opencode to apply.",
|
|
82
|
+
})
|
|
83
|
+
}}
|
|
84
|
+
onCancel={() => {
|
|
85
|
+
api.ui.dialog.clear()
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
))
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default plugin
|