opencode-tbot 0.1.17 → 0.1.20
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/README.ja.md +164 -0
- package/README.md +51 -46
- package/README.zh-CN.md +51 -46
- package/dist/assets/{plugin-config-DA71_jD3.js → plugin-config-B8ginwol.js} +7 -51
- package/dist/assets/plugin-config-B8ginwol.js.map +1 -0
- package/dist/cli.js +5 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +263 -353
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -2
- package/tbot.config.example.json +0 -5
- package/dist/assets/plugin-config-DA71_jD3.js.map +0 -1
package/README.ja.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# opencode-tbot
|
|
2
|
+
|
|
3
|
+
チャットから [OpenCode](https://opencode.ai) を操作するための Telegram プラグインです。
|
|
4
|
+
|
|
5
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
|
|
6
|
+
|
|
7
|
+
> このプロジェクトは OpenCode チームによって開発されたものではなく、公式な関連もありません。
|
|
8
|
+
|
|
9
|
+
## 概要
|
|
10
|
+
|
|
11
|
+
`opencode-tbot` を使うと、Telegram から OpenCode を操作できます。
|
|
12
|
+
|
|
13
|
+
- テキストメッセージは現在アクティブな OpenCode セッションに転送されます。
|
|
14
|
+
- Telegram の写真と画像ドキュメントは OpenCode のファイルパートとしてアップロードされます。
|
|
15
|
+
- Telegram の音声メッセージは明示的に拒否され、ローカライズされた返信を返します。
|
|
16
|
+
- OpenCode が発行した権限リクエストは、Telegram のインラインボタンから承認または拒否できます。
|
|
17
|
+
- セッション完了やエラーイベントは、紐付けられた Telegram チャットへ通知できます。
|
|
18
|
+
- チャットの紐付け状態は JSON の state ファイルに保存されます。
|
|
19
|
+
|
|
20
|
+
## 前提条件
|
|
21
|
+
|
|
22
|
+
- このプラグインを読み込む OpenCode ホストプロセスが動作していること。
|
|
23
|
+
- Telegram bot token を用意していること。
|
|
24
|
+
- CLI とローカル開発には Node.js `>=22.12.0` が必要です。
|
|
25
|
+
|
|
26
|
+
## インストール
|
|
27
|
+
|
|
28
|
+
推奨インストール方法:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
インストーラーはプラグインをグローバルに登録し、デフォルトのランタイム設定を書き込みます。
|
|
35
|
+
|
|
36
|
+
インストール済み CLI のバージョン確認:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
OpenCode に登録済みの npm プラグイン spec を更新:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### CLI オプション
|
|
49
|
+
|
|
50
|
+
`install` で利用可能:
|
|
51
|
+
|
|
52
|
+
- `--bot-token <token>` Telegram bot token を非対話で設定
|
|
53
|
+
- `--telegram-api-root <url>` Telegram Bot API のベース URL を上書き
|
|
54
|
+
- `--plugin-spec <spec>` カスタム npm プラグイン spec を登録
|
|
55
|
+
- `--skip-register` OpenCode 側のプラグイン登録は変更せず、プラグイン設定だけを書き換え
|
|
56
|
+
- `--home-dir <path>` カスタム home ディレクトリを使用
|
|
57
|
+
|
|
58
|
+
`update` で利用可能:
|
|
59
|
+
|
|
60
|
+
- `--plugin-spec <spec>`
|
|
61
|
+
- `--home-dir <path>`
|
|
62
|
+
|
|
63
|
+
## 設定
|
|
64
|
+
|
|
65
|
+
ランタイム設定は次の順序で読み込まれます。
|
|
66
|
+
|
|
67
|
+
1. `~/.config/opencode/opencode-tbot/config.json` のグローバルデフォルト
|
|
68
|
+
2. `<worktree>/tbot.config.json` のプロジェクト上書き設定
|
|
69
|
+
|
|
70
|
+
プロジェクト設定はグローバル設定に上書きマージされます。`telegram` と `state` はセクション単位でディープマージされます。
|
|
71
|
+
|
|
72
|
+
古い `openrouter` 音声転写設定はランタイムでは無視され、インストーラーが設定を書き直す際にも削除されます。
|
|
73
|
+
|
|
74
|
+
リポジトリには最小構成の参考として [tbot.config.example.json](./tbot.config.example.json) も含まれています。
|
|
75
|
+
|
|
76
|
+
### `tbot.config.json` の例
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"telegram": {
|
|
81
|
+
"botToken": "your_telegram_bot_token",
|
|
82
|
+
"allowedChatIds": [123456789],
|
|
83
|
+
"apiRoot": "https://api.telegram.org"
|
|
84
|
+
},
|
|
85
|
+
"state": {
|
|
86
|
+
"path": "./data/opencode-tbot.state.json"
|
|
87
|
+
},
|
|
88
|
+
"logLevel": "info"
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### フィールド
|
|
93
|
+
|
|
94
|
+
| フィールド | 必須 | デフォルト | 説明 |
|
|
95
|
+
| --- | --- | --- | --- |
|
|
96
|
+
| `telegram.botToken` | はい | - | Telegram bot token。通常はインストーラーがグローバルプラグイン設定へ書き込みます。 |
|
|
97
|
+
| `telegram.allowedChatIds` | いいえ | `[]` | 許可する Telegram chat ID の配列。空の場合はすべての chat を受け付けます。 |
|
|
98
|
+
| `telegram.apiRoot` | いいえ | `https://api.telegram.org` | Telegram Bot API のベース URL。テストやセルフホストのゲートウェイ向けです。 |
|
|
99
|
+
| `state.path` | いいえ | `./data/opencode-tbot.state.json` | JSON state ファイルのパス。現在の OpenCode worktree からの相対パスとして解決されます。 |
|
|
100
|
+
| `logLevel` | いいえ | `info` | プラグインのログレベル。ログは `client.app.log()` 経由で出力されます。 |
|
|
101
|
+
|
|
102
|
+
### 補足
|
|
103
|
+
|
|
104
|
+
- `state.path` は現在の OpenCode worktree からの相対パスとして解決されます。
|
|
105
|
+
- `telegram.allowedChatIds` を空のままにすると、bot は任意の chat からのメッセージを受け付けます。本番では制限を設定してください。
|
|
106
|
+
- 権限承認とセッション通知はプラグインの hook で処理されます。
|
|
107
|
+
- `/language` は現在 English、简体中文、日本語 をサポートしています。
|
|
108
|
+
|
|
109
|
+
## クイックスタート
|
|
110
|
+
|
|
111
|
+
1. `npm exec --package opencode-tbot@latest opencode-tbot -- install` でプラグインをインストールします。
|
|
112
|
+
2. 特定の chat のみ許可したい場合は、`tbot.config.json` で `telegram.allowedChatIds` を設定します。
|
|
113
|
+
3. 対象の worktree で OpenCode を起動し、プラグインランタイムを読み込ませます。
|
|
114
|
+
4. Telegram で `/status` を実行し、接続を確認します。
|
|
115
|
+
5. `/new [title]` を実行するか、テキストメッセージを直接送信して使い始めます。
|
|
116
|
+
|
|
117
|
+
## コマンド
|
|
118
|
+
|
|
119
|
+
- `/start` 短いウェルカムメッセージとクイックスタート手順を表示
|
|
120
|
+
- `/status` OpenCode のヘルス、パス、プラグイン、LSP、MCP の状態をまとめて表示
|
|
121
|
+
- `/new [title]` 新しい OpenCode セッションを作成
|
|
122
|
+
- `/agents` または `/agent` 利用可能な agent を一覧表示し、アクティブな agent を切り替え
|
|
123
|
+
- `/sessions` 利用可能なセッションを一覧表示し、アクティブなセッションを切り替え
|
|
124
|
+
- `/cancel` セッション名の変更をキャンセルするか、現在のセッションで実行中のリクエストを中止
|
|
125
|
+
- `/model` または `/models` 利用可能なモデルを一覧表示し、アクティブなモデルを切り替え
|
|
126
|
+
- `/language` bot の表示言語を切り替え
|
|
127
|
+
|
|
128
|
+
メッセージ処理:
|
|
129
|
+
|
|
130
|
+
- コマンド以外のテキストは prompt として扱われ、OpenCode に送信されます。
|
|
131
|
+
- Telegram の写真と画像ドキュメントは OpenCode のファイルパートとして転送されます。
|
|
132
|
+
- Telegram の音声メッセージは未対応で、ローカライズされた拒否メッセージを返します。
|
|
133
|
+
|
|
134
|
+
## 開発
|
|
135
|
+
|
|
136
|
+
プラグインバンドルをビルド:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pnpm build
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
型チェック:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pnpm typecheck
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
テスト実行:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pnpm test
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
このリポジトリでソースベースのローカル読み込みを行う場合、OpenCode は [.opencode/plugins/opencode-tbot.ts](./.opencode/plugins/opencode-tbot.ts) を使って `src/plugin.ts` を再エクスポートできます。
|
|
155
|
+
|
|
156
|
+
## FAQ
|
|
157
|
+
|
|
158
|
+
### 実行中の OpenCode インスタンスは必要ですか?
|
|
159
|
+
|
|
160
|
+
はい。このリポジトリが提供するのは Telegram 連携レイヤーであり、プラグインを読み込む OpenCode ホストプロセスに依存します。
|
|
161
|
+
|
|
162
|
+
### これは OpenCode の公式プロジェクトですか?
|
|
163
|
+
|
|
164
|
+
いいえ。OpenCode と連携するプロジェクトですが、OpenCode チームが開発したものではありません。
|
package/README.md
CHANGED
|
@@ -2,24 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
A Telegram plugin for driving [OpenCode](https://opencode.ai) from chat.
|
|
4
4
|
|
|
5
|
-
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
5
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
|
|
6
6
|
|
|
7
7
|
> This project is not built by the OpenCode team and is not affiliated with them.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Overview
|
|
10
10
|
|
|
11
11
|
`opencode-tbot` lets you operate OpenCode from Telegram.
|
|
12
12
|
|
|
13
13
|
- Text messages are forwarded to the active OpenCode session.
|
|
14
|
-
- Telegram
|
|
15
|
-
- Telegram voice messages
|
|
16
|
-
- Permission requests raised by OpenCode can be approved or rejected
|
|
14
|
+
- Telegram photos and image documents are uploaded as OpenCode file parts.
|
|
15
|
+
- Telegram voice messages are explicitly rejected with a localized reply.
|
|
16
|
+
- Permission requests raised by OpenCode can be approved or rejected from Telegram inline buttons.
|
|
17
17
|
- Session completion and error events can be reported back to the bound Telegram chat.
|
|
18
|
-
- Chat
|
|
18
|
+
- Chat bindings are stored in a JSON state file.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- A running OpenCode host process that loads the plugin.
|
|
23
|
+
- A Telegram bot token.
|
|
24
|
+
- Node.js `>=22.12.0` for the CLI and local development workflow.
|
|
19
25
|
|
|
20
26
|
## Install
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
Recommended install:
|
|
23
29
|
|
|
24
30
|
```bash
|
|
25
31
|
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
@@ -39,20 +45,33 @@ Update the registered npm plugin spec in OpenCode:
|
|
|
39
45
|
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
46
|
```
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
### CLI Options
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
`install` supports:
|
|
51
|
+
|
|
52
|
+
- `--bot-token <token>` set the Telegram bot token non-interactively
|
|
53
|
+
- `--telegram-api-root <url>` override the Telegram Bot API root
|
|
54
|
+
- `--plugin-spec <spec>` register a custom npm plugin spec
|
|
55
|
+
- `--skip-register` only rewrite plugin config without touching OpenCode plugin registration
|
|
56
|
+
- `--home-dir <path>` use a custom home directory
|
|
57
|
+
|
|
58
|
+
`update` supports:
|
|
59
|
+
|
|
60
|
+
- `--plugin-spec <spec>`
|
|
61
|
+
- `--home-dir <path>`
|
|
47
62
|
|
|
48
63
|
## Configuration
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
Runtime config is loaded in this order:
|
|
66
|
+
|
|
67
|
+
1. Global defaults from `~/.config/opencode/opencode-tbot/config.json`
|
|
68
|
+
2. Project overrides from `<worktree>/tbot.config.json`
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
2. project overrides from `<worktree>/tbot.config.json`
|
|
70
|
+
Project config is merged on top of the global config. `telegram` and `state` are deep-merged by section.
|
|
54
71
|
|
|
55
|
-
|
|
72
|
+
Legacy `openrouter` voice-transcription settings are ignored at runtime. When the installer rewrites the config, it removes them.
|
|
73
|
+
|
|
74
|
+
The repository also includes [tbot.config.example.json](./tbot.config.example.json) as a minimal reference.
|
|
56
75
|
|
|
57
76
|
### Example `tbot.config.json`
|
|
58
77
|
|
|
@@ -66,12 +85,6 @@ Project config is merged on top of the global config. `telegram`, `state`, and `
|
|
|
66
85
|
"state": {
|
|
67
86
|
"path": "./data/opencode-tbot.state.json"
|
|
68
87
|
},
|
|
69
|
-
"openrouter": {
|
|
70
|
-
"apiKey": "your_openrouter_api_key",
|
|
71
|
-
"model": "openai/gpt-audio-mini",
|
|
72
|
-
"timeoutMs": 30000,
|
|
73
|
-
"transcriptionPrompt": ""
|
|
74
|
-
},
|
|
75
88
|
"logLevel": "info"
|
|
76
89
|
}
|
|
77
90
|
```
|
|
@@ -84,39 +97,27 @@ Project config is merged on top of the global config. `telegram`, `state`, and `
|
|
|
84
97
|
| `telegram.allowedChatIds` | No | `[]` | Allowed Telegram chat IDs. If empty, the bot accepts messages from any chat. |
|
|
85
98
|
| `telegram.apiRoot` | No | `https://api.telegram.org` | Telegram Bot API base URL. Useful for tests or self-hosted gateways. |
|
|
86
99
|
| `state.path` | No | `./data/opencode-tbot.state.json` | JSON state file path, resolved relative to the current OpenCode worktree. |
|
|
87
|
-
| `openrouter.apiKey` | No | `""` | OpenRouter API key. Required only when voice transcription is enabled. |
|
|
88
|
-
| `openrouter.model` | No | `openai/gpt-audio-mini` | OpenRouter model for voice transcription. |
|
|
89
|
-
| `openrouter.timeoutMs` | No | `30000` | Voice transcription timeout in milliseconds. |
|
|
90
|
-
| `openrouter.transcriptionPrompt` | No | `""` | Optional extra instruction appended to the transcription prompt. |
|
|
91
100
|
| `logLevel` | No | `info` | Plugin log level. Logs are emitted through `client.app.log()`. |
|
|
92
101
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Required:
|
|
96
|
-
|
|
97
|
-
- `telegram.botToken`
|
|
102
|
+
### Notes
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
- `
|
|
103
|
-
- `state.path`
|
|
104
|
-
- `openrouter.apiKey`
|
|
105
|
-
- `openrouter.model`
|
|
106
|
-
- `openrouter.timeoutMs`
|
|
107
|
-
- `openrouter.transcriptionPrompt`
|
|
108
|
-
- `logLevel`
|
|
104
|
+
- `state.path` is resolved relative to the current OpenCode worktree.
|
|
105
|
+
- If `telegram.allowedChatIds` is left empty, the bot accepts messages from any chat. Restrict it in production.
|
|
106
|
+
- Permission approvals and session notifications are handled through plugin hooks.
|
|
107
|
+
- `/language` currently supports English, Simplified Chinese, and Japanese.
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
## Quick Start
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
1. Install the plugin with `npm exec --package opencode-tbot@latest opencode-tbot -- install`.
|
|
112
|
+
2. Set `telegram.allowedChatIds` in `tbot.config.json` if you want to restrict the bot to specific chats.
|
|
113
|
+
3. Start OpenCode in the target worktree so the plugin runtime can load.
|
|
114
|
+
4. In Telegram, run `/status` to verify the connection.
|
|
115
|
+
5. Run `/new [title]` or send a text message directly to start working.
|
|
115
116
|
|
|
116
117
|
## Commands
|
|
117
118
|
|
|
118
119
|
- `/start` show a short welcome message and quick-start steps
|
|
119
|
-
- `/status` show aggregated OpenCode health, path, LSP, and MCP status
|
|
120
|
+
- `/status` show aggregated OpenCode health, path, plugin, LSP, and MCP status
|
|
120
121
|
- `/new [title]` create a new OpenCode session
|
|
121
122
|
- `/agents` or `/agent` list available agents and switch the active one
|
|
122
123
|
- `/sessions` list available sessions and switch the active one
|
|
@@ -124,7 +125,11 @@ Notes:
|
|
|
124
125
|
- `/model` or `/models` list available models and switch the active one
|
|
125
126
|
- `/language` switch the bot display language
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
Message handling:
|
|
129
|
+
|
|
130
|
+
- Non-command text is treated as a prompt and sent to OpenCode.
|
|
131
|
+
- Telegram photos and image documents are forwarded as OpenCode file parts.
|
|
132
|
+
- Telegram voice messages are not supported and receive a localized rejection reply.
|
|
128
133
|
|
|
129
134
|
## Development
|
|
130
135
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,24 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
一个通过 Telegram 驱动 [OpenCode](https://opencode.ai) 的插件。
|
|
4
4
|
|
|
5
|
-
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
5
|
+
[English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
|
|
6
6
|
|
|
7
7
|
> 本项目并非由 OpenCode 团队开发,也不隶属于 OpenCode 官方。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 项目概览
|
|
10
10
|
|
|
11
11
|
`opencode-tbot` 允许你直接在 Telegram 中操作 OpenCode。
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
- Telegram
|
|
15
|
-
- Telegram
|
|
16
|
-
- OpenCode 触发的权限请求可以直接在 Telegram
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- 文本消息会转发到当前激活的 OpenCode 会话。
|
|
14
|
+
- Telegram 照片和图片文档会作为 OpenCode 文件片段上传。
|
|
15
|
+
- Telegram 语音消息会被明确拒绝,并返回本地化提示。
|
|
16
|
+
- OpenCode 触发的权限请求可以直接在 Telegram 内联按钮中批准或拒绝。
|
|
17
|
+
- 会话完成和错误事件可以回推到绑定的 Telegram chat。
|
|
18
|
+
- 聊天绑定状态通过 JSON 状态文件持久化。
|
|
19
|
+
|
|
20
|
+
## 环境要求
|
|
21
|
+
|
|
22
|
+
- 一个正在运行、并会加载该插件的 OpenCode Host 进程。
|
|
23
|
+
- 一个 Telegram bot token。
|
|
24
|
+
- Node.js `>=22.12.0`,用于 CLI 和本地开发流程。
|
|
19
25
|
|
|
20
26
|
## 安装
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
推荐安装方式:
|
|
23
29
|
|
|
24
30
|
```bash
|
|
25
31
|
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
@@ -39,11 +45,20 @@ npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
|
39
45
|
npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
40
46
|
```
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
### CLI 参数
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
`install` 支持:
|
|
51
|
+
|
|
52
|
+
- `--bot-token <token>` 非交互式写入 Telegram bot token
|
|
53
|
+
- `--telegram-api-root <url>` 覆盖 Telegram Bot API 根地址
|
|
54
|
+
- `--plugin-spec <spec>` 注册自定义 npm 插件 spec
|
|
55
|
+
- `--skip-register` 只重写插件配置,不改动 OpenCode 的插件注册
|
|
56
|
+
- `--home-dir <path>` 使用自定义 home 目录
|
|
57
|
+
|
|
58
|
+
`update` 支持:
|
|
59
|
+
|
|
60
|
+
- `--plugin-spec <spec>`
|
|
61
|
+
- `--home-dir <path>`
|
|
47
62
|
|
|
48
63
|
## 配置
|
|
49
64
|
|
|
@@ -52,7 +67,11 @@ npm uninstall opencode-tbot
|
|
|
52
67
|
1. 全局默认配置 `~/.config/opencode/opencode-tbot/config.json`
|
|
53
68
|
2. 项目覆盖配置 `<worktree>/tbot.config.json`
|
|
54
69
|
|
|
55
|
-
项目配置会覆盖全局默认值;`telegram
|
|
70
|
+
项目配置会覆盖全局默认值;`telegram` 和 `state` 会按分段进行深合并。
|
|
71
|
+
|
|
72
|
+
遗留的 `openrouter` 语音转写配置在运行时会被忽略;安装器重写配置时也会自动移除这些字段。
|
|
73
|
+
|
|
74
|
+
仓库内还提供了 [tbot.config.example.json](./tbot.config.example.json) 作为最小配置参考。
|
|
56
75
|
|
|
57
76
|
### `tbot.config.json` 示例
|
|
58
77
|
|
|
@@ -66,12 +85,6 @@ npm uninstall opencode-tbot
|
|
|
66
85
|
"state": {
|
|
67
86
|
"path": "./data/opencode-tbot.state.json"
|
|
68
87
|
},
|
|
69
|
-
"openrouter": {
|
|
70
|
-
"apiKey": "your_openrouter_api_key",
|
|
71
|
-
"model": "openai/gpt-audio-mini",
|
|
72
|
-
"timeoutMs": 30000,
|
|
73
|
-
"transcriptionPrompt": ""
|
|
74
|
-
},
|
|
75
88
|
"logLevel": "info"
|
|
76
89
|
}
|
|
77
90
|
```
|
|
@@ -84,39 +97,27 @@ npm uninstall opencode-tbot
|
|
|
84
97
|
| `telegram.allowedChatIds` | 否 | `[]` | 允许访问的 Telegram chat ID 数组。为空时表示接受任意 chat。 |
|
|
85
98
|
| `telegram.apiRoot` | 否 | `https://api.telegram.org` | Telegram Bot API 根地址,适合测试或自托管网关。 |
|
|
86
99
|
| `state.path` | 否 | `./data/opencode-tbot.state.json` | JSON 状态文件路径,相对当前 OpenCode worktree 解析。 |
|
|
87
|
-
| `openrouter.apiKey` | 否 | `""` | OpenRouter API key。仅在启用语音转写时需要。 |
|
|
88
|
-
| `openrouter.model` | 否 | `openai/gpt-audio-mini` | 语音转写使用的 OpenRouter 模型。 |
|
|
89
|
-
| `openrouter.timeoutMs` | 否 | `30000` | 语音转写超时时间,单位毫秒。 |
|
|
90
|
-
| `openrouter.transcriptionPrompt` | 否 | `""` | 追加到内置转写提示词后的可选说明。 |
|
|
91
100
|
| `logLevel` | 否 | `info` | 插件日志级别。日志统一通过 `client.app.log()` 上报。 |
|
|
92
101
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
必填:
|
|
102
|
+
### 说明
|
|
96
103
|
|
|
97
|
-
- `
|
|
104
|
+
- `state.path` 会相对当前 OpenCode worktree 解析。
|
|
105
|
+
- 如果 `telegram.allowedChatIds` 为空,bot 会接受任意 chat 的消息;生产环境建议显式限制。
|
|
106
|
+
- 权限审批和会话通知通过插件 hook 处理。
|
|
107
|
+
- `/language` 当前支持 English、简体中文、日本語。
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
## 快速开始
|
|
100
110
|
|
|
101
|
-
- `
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- `openrouter.timeoutMs`
|
|
107
|
-
- `openrouter.transcriptionPrompt`
|
|
108
|
-
- `logLevel`
|
|
109
|
-
|
|
110
|
-
说明:
|
|
111
|
-
|
|
112
|
-
- `state.path` 默认是 `./data/opencode-tbot.state.json`,并相对当前 OpenCode worktree 解析
|
|
113
|
-
- 日志通过 `client.app.log()` 统一输出
|
|
114
|
-
- 权限审批和会话通知由插件 hook 处理
|
|
111
|
+
1. 使用 `npm exec --package opencode-tbot@latest opencode-tbot -- install` 安装插件。
|
|
112
|
+
2. 如果你只想允许特定聊天使用 bot,请在 `tbot.config.json` 中设置 `telegram.allowedChatIds`。
|
|
113
|
+
3. 在目标 worktree 中启动 OpenCode,让插件运行时被加载。
|
|
114
|
+
4. 在 Telegram 中执行 `/status` 验证连接是否正常。
|
|
115
|
+
5. 执行 `/new [title]`,或者直接发送文本消息开始使用。
|
|
115
116
|
|
|
116
117
|
## 命令
|
|
117
118
|
|
|
118
119
|
- `/start` 显示简短欢迎信息和快速开始说明
|
|
119
|
-
- `/status` 显示 OpenCode
|
|
120
|
+
- `/status` 显示 OpenCode 健康状态、路径、插件、LSP 和 MCP 信息
|
|
120
121
|
- `/new [title]` 创建新的 OpenCode 会话
|
|
121
122
|
- `/agents` 或 `/agent` 列出可用 agent 并切换当前 agent
|
|
122
123
|
- `/sessions` 列出会话并切换当前会话
|
|
@@ -124,7 +125,11 @@ npm uninstall opencode-tbot
|
|
|
124
125
|
- `/model` 或 `/models` 列出可用模型并切换当前模型
|
|
125
126
|
- `/language` 切换 bot 显示语言
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
消息处理规则:
|
|
129
|
+
|
|
130
|
+
- 任意非命令文本都会被当作 prompt 发送给 OpenCode。
|
|
131
|
+
- Telegram 照片和图片文档会作为 OpenCode 文件片段上传。
|
|
132
|
+
- Telegram 语音消息当前不受支持,bot 会直接返回本地化拒绝提示。
|
|
128
133
|
|
|
129
134
|
## 开发
|
|
130
135
|
|
|
@@ -156,4 +161,4 @@ pnpm test
|
|
|
156
161
|
|
|
157
162
|
### 这是 OpenCode 官方项目吗?
|
|
158
163
|
|
|
159
|
-
|
|
164
|
+
不是。它只是一个 OpenCode 集成,并非 OpenCode 官方项目。
|
|
@@ -5,7 +5,6 @@ import { z } from "zod";
|
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
//#region src/app/config.ts
|
|
8
|
-
var DEFAULT_OPENROUTER_MODEL = "openai/gpt-audio-mini";
|
|
9
8
|
var DEFAULT_STATE_FILE_PATH = "./data/opencode-tbot.state.json";
|
|
10
9
|
var DEFAULT_TELEGRAM_API_ROOT = "https://api.telegram.org";
|
|
11
10
|
var AllowedChatIdSchema = z.union([z.number().int(), z.string().regex(/^-?\d+$/u).transform((value) => Number(value))]);
|
|
@@ -15,44 +14,23 @@ var TelegramConfigSchema = z.preprocess((value) => value ?? {}, z.object({
|
|
|
15
14
|
apiRoot: z.string().trim().url().default(DEFAULT_TELEGRAM_API_ROOT)
|
|
16
15
|
}));
|
|
17
16
|
var StateConfigSchema = z.preprocess((value) => value ?? {}, z.object({ path: z.string().trim().min(1).default(DEFAULT_STATE_FILE_PATH) }));
|
|
18
|
-
var OpenRouterConfigSchema = z.preprocess((value) => value ?? {}, z.object({
|
|
19
|
-
apiKey: z.string().default(""),
|
|
20
|
-
model: z.string().default(DEFAULT_OPENROUTER_MODEL),
|
|
21
|
-
timeoutMs: z.coerce.number().int().positive().default(3e4),
|
|
22
|
-
transcriptionPrompt: z.string().default("")
|
|
23
|
-
}));
|
|
24
17
|
var AppConfigSchema = z.object({
|
|
25
18
|
telegram: TelegramConfigSchema,
|
|
26
19
|
state: StateConfigSchema,
|
|
27
|
-
openrouter: OpenRouterConfigSchema,
|
|
28
20
|
logLevel: z.string().default("info")
|
|
29
21
|
});
|
|
30
22
|
function loadAppConfig(configSource = {}, options = {}) {
|
|
31
23
|
return buildAppConfig(parseConfig(AppConfigSchema, configSource), options);
|
|
32
24
|
}
|
|
33
25
|
function buildAppConfig(data, options) {
|
|
34
|
-
const openRouterApiKey = normalizeOptionalString$1(data.openrouter.apiKey);
|
|
35
|
-
const openRouterModel = normalizeOptionalString$1(data.openrouter.model) ?? "openai/gpt-audio-mini";
|
|
36
|
-
const transcriptionPrompt = normalizeOptionalString$1(data.openrouter.transcriptionPrompt);
|
|
37
26
|
return {
|
|
38
27
|
telegramBotToken: data.telegram.botToken,
|
|
39
28
|
telegramAllowedChatIds: data.telegram.allowedChatIds,
|
|
40
29
|
telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),
|
|
41
30
|
logLevel: data.logLevel,
|
|
42
|
-
stateFilePath: resolveStatePath(data, options.cwd ?? process.cwd())
|
|
43
|
-
openrouter: {
|
|
44
|
-
configured: !!openRouterApiKey,
|
|
45
|
-
apiKey: openRouterApiKey,
|
|
46
|
-
model: openRouterModel,
|
|
47
|
-
timeoutMs: data.openrouter.timeoutMs,
|
|
48
|
-
transcriptionPrompt
|
|
49
|
-
}
|
|
31
|
+
stateFilePath: resolveStatePath(data, options.cwd ?? process.cwd())
|
|
50
32
|
};
|
|
51
33
|
}
|
|
52
|
-
function normalizeOptionalString$1(value) {
|
|
53
|
-
const normalized = value.trim();
|
|
54
|
-
return normalized.length > 0 ? normalized : null;
|
|
55
|
-
}
|
|
56
34
|
function resolveStatePath(data, cwd) {
|
|
57
35
|
return resolve(cwd, data.state.path || "./data/opencode-tbot.state.json");
|
|
58
36
|
}
|
|
@@ -92,8 +70,7 @@ async function preparePluginConfiguration(options) {
|
|
|
92
70
|
const globalConfigFilePath = getGlobalPluginConfigFilePath(options.homeDir ?? homedir());
|
|
93
71
|
const projectConfigFilePath = await resolveProjectPluginConfigFilePath(options.cwd);
|
|
94
72
|
const [globalConfig, projectConfig] = await Promise.all([loadPluginConfigFile(globalConfigFilePath), loadPluginConfigFile(projectConfigFilePath)]);
|
|
95
|
-
const config = mergePluginConfigSources(globalConfig, projectConfig, options.config);
|
|
96
|
-
applyGlobalOpenRouterApiKey(config, globalConfig);
|
|
73
|
+
const config = stripLegacyVoiceConfig(mergePluginConfigSources(globalConfig, projectConfig, options.config));
|
|
97
74
|
const configFilePath = await pathExists(projectConfigFilePath) ? projectConfigFilePath : globalConfigFilePath;
|
|
98
75
|
return {
|
|
99
76
|
cwd: options.cwd,
|
|
@@ -123,7 +100,6 @@ function mergePluginConfigSources(...sources) {
|
|
|
123
100
|
const normalized = source;
|
|
124
101
|
const previousTelegram = merged.telegram;
|
|
125
102
|
const previousState = merged.state;
|
|
126
|
-
const previousOpenRouter = merged.openrouter;
|
|
127
103
|
Object.assign(merged, normalized);
|
|
128
104
|
if (normalized.telegram) merged.telegram = {
|
|
129
105
|
...previousTelegram ?? {},
|
|
@@ -133,27 +109,9 @@ function mergePluginConfigSources(...sources) {
|
|
|
133
109
|
...previousState ?? {},
|
|
134
110
|
...normalized.state
|
|
135
111
|
};
|
|
136
|
-
if (normalized.openrouter) merged.openrouter = {
|
|
137
|
-
...previousOpenRouter ?? {},
|
|
138
|
-
...normalized.openrouter
|
|
139
|
-
};
|
|
140
112
|
}
|
|
141
113
|
return merged;
|
|
142
114
|
}
|
|
143
|
-
function applyGlobalOpenRouterApiKey(config, globalConfig) {
|
|
144
|
-
const globalApiKey = normalizeOptionalString(globalConfig.openrouter?.apiKey);
|
|
145
|
-
if (!config.openrouter) {
|
|
146
|
-
if (!globalApiKey) return;
|
|
147
|
-
config.openrouter = { apiKey: globalApiKey };
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (globalApiKey) {
|
|
151
|
-
config.openrouter.apiKey = globalApiKey;
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
delete config.openrouter.apiKey;
|
|
155
|
-
if (Object.keys(config.openrouter).length === 0) delete config.openrouter;
|
|
156
|
-
}
|
|
157
115
|
function serializePluginConfig(config) {
|
|
158
116
|
return `${JSON.stringify(orderPluginConfig(config), null, 2)}\n`;
|
|
159
117
|
}
|
|
@@ -178,13 +136,11 @@ function orderPluginConfig(config) {
|
|
|
178
136
|
const prioritizedKeys = new Set([
|
|
179
137
|
"telegram",
|
|
180
138
|
"state",
|
|
181
|
-
"openrouter",
|
|
182
139
|
"logLevel"
|
|
183
140
|
]);
|
|
184
141
|
const ordered = {};
|
|
185
142
|
if (config.telegram) ordered.telegram = config.telegram;
|
|
186
143
|
if (config.state) ordered.state = config.state;
|
|
187
|
-
if (config.openrouter) ordered.openrouter = config.openrouter;
|
|
188
144
|
if (config.logLevel !== void 0) ordered.logLevel = config.logLevel;
|
|
189
145
|
for (const [key, value] of Object.entries(config)) if (!prioritizedKeys.has(key)) ordered[key] = value;
|
|
190
146
|
return ordered;
|
|
@@ -192,10 +148,6 @@ function orderPluginConfig(config) {
|
|
|
192
148
|
function isPlainObject(value) {
|
|
193
149
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
194
150
|
}
|
|
195
|
-
function normalizeOptionalString(value) {
|
|
196
|
-
const normalized = value?.trim();
|
|
197
|
-
return normalized ? normalized : null;
|
|
198
|
-
}
|
|
199
151
|
function isMissingFileError(error) {
|
|
200
152
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
201
153
|
}
|
|
@@ -211,7 +163,11 @@ async function pathExists(filePath) {
|
|
|
211
163
|
throw error;
|
|
212
164
|
}
|
|
213
165
|
}
|
|
166
|
+
function stripLegacyVoiceConfig(config) {
|
|
167
|
+
const { openrouter: _openrouter, ...rest } = config;
|
|
168
|
+
return rest;
|
|
169
|
+
}
|
|
214
170
|
//#endregion
|
|
215
171
|
export { writePluginConfigFile as a, loadAppConfig as c, preparePluginConfiguration as i, getOpenCodeConfigFilePath as n, OPENCODE_TBOT_VERSION as o, mergePluginConfigSources as r, DEFAULT_TELEGRAM_API_ROOT as s, getGlobalPluginConfigFilePath as t };
|
|
216
172
|
|
|
217
|
-
//# sourceMappingURL=plugin-config-
|
|
173
|
+
//# sourceMappingURL=plugin-config-B8ginwol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-config-B8ginwol.js","names":[],"sources":["../../src/app/config.ts","../../src/app/package-info.ts","../../src/app/plugin-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { z } from \"zod\";\n\nexport const DEFAULT_STATE_FILE_PATH = \"./data/opencode-tbot.state.json\";\nexport const DEFAULT_TELEGRAM_API_ROOT = \"https://api.telegram.org\";\n\nconst AllowedChatIdSchema = z.union([\n z.number().int(),\n z.string().regex(/^-?\\d+$/u).transform((value) => Number(value)),\n]);\n\nconst TelegramConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n botToken: z.string().trim().min(1),\n allowedChatIds: z.array(AllowedChatIdSchema).default([]),\n apiRoot: z.string().trim().url().default(DEFAULT_TELEGRAM_API_ROOT),\n }),\n);\n\nconst StateConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n path: z.string().trim().min(1).default(DEFAULT_STATE_FILE_PATH),\n }),\n);\n\nconst AppConfigSchema = z.object({\n telegram: TelegramConfigSchema,\n state: StateConfigSchema,\n logLevel: z.string().default(\"info\"),\n});\n\nexport interface PluginConfigSource {\n telegram?: {\n botToken?: string;\n allowedChatIds?: Array<number | string>;\n apiRoot?: string;\n [key: string]: unknown;\n };\n state?: {\n path?: string;\n [key: string]: unknown;\n };\n logLevel?: string;\n [key: string]: unknown;\n}\n\nexport interface AppConfig {\n telegramBotToken: string;\n telegramAllowedChatIds: number[];\n telegramApiRoot: string;\n logLevel: string;\n stateFilePath: string;\n}\n\nexport interface LoadAppConfigOptions {\n cwd?: string;\n}\n\nexport function loadAppConfig(\n configSource: PluginConfigSource | undefined = {},\n options: LoadAppConfigOptions = {},\n): AppConfig {\n const parsed = parseConfig(AppConfigSchema, configSource);\n\n return buildAppConfig(parsed, options);\n}\n\nexport const loadPluginConfig = loadAppConfig;\n\nfunction buildAppConfig(\n data: z.infer<typeof AppConfigSchema>,\n options: LoadAppConfigOptions,\n): AppConfig {\n return {\n telegramBotToken: data.telegram.botToken,\n telegramAllowedChatIds: data.telegram.allowedChatIds,\n telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),\n logLevel: data.logLevel,\n stateFilePath: resolveStatePath(data, options.cwd ?? process.cwd()),\n };\n}\n\nfunction resolveStatePath(\n data: z.infer<typeof AppConfigSchema>,\n cwd: string,\n): string {\n return resolve(cwd, data.state.path || DEFAULT_STATE_FILE_PATH);\n}\n\nfunction normalizeApiRoot(value: string): string {\n const normalized = value.trim();\n\n return normalized.endsWith(\"/\")\n ? normalized.slice(0, -1)\n : normalized;\n}\n\nfunction parseConfig<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n configSource: PluginConfigSource | undefined,\n): z.infer<TSchema> {\n const parsed = schema.safeParse(configSource ?? {});\n\n if (parsed.success) {\n return parsed.data;\n }\n\n throw new Error(\n `Invalid plugin configuration: ${JSON.stringify(parsed.error.flatten())}`,\n );\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport const OPENCODE_TBOT_VERSION = resolvePackageVersion();\n\nfunction resolvePackageVersion(): string {\n let directory = dirname(fileURLToPath(import.meta.url));\n\n while (true) {\n const packageFilePath = join(directory, \"package.json\");\n\n if (existsSync(packageFilePath)) {\n try {\n const parsed = JSON.parse(readFileSync(packageFilePath, \"utf8\")) as {\n version?: unknown;\n };\n\n if (typeof parsed.version === \"string\" && parsed.version.trim().length > 0) {\n return parsed.version;\n }\n } catch {\n // Fall through and continue searching parent directories.\n }\n }\n\n const parentDirectory = dirname(directory);\n\n if (parentDirectory === directory) {\n break;\n }\n\n directory = parentDirectory;\n }\n\n return \"unknown\";\n}\n","import { access, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { PluginConfigSource } from \"./config.js\";\n\nexport const PLUGIN_CONFIG_FILE_NAME = \"tbot.config.json\";\nexport const GLOBAL_PLUGIN_DIRECTORY_NAME = \"opencode-tbot\";\nexport const GLOBAL_PLUGIN_CONFIG_FILE_NAME = \"config.json\";\nexport const OPENCODE_CONFIG_FILE_NAME = \"opencode.json\";\n\nexport interface PreparedPluginConfiguration {\n cwd: string;\n config: PluginConfigSource;\n globalConfigFilePath: string;\n projectConfigFilePath: string;\n configFilePath: string;\n}\n\nexport interface PreparePluginConfigurationOptions {\n cwd: string;\n config?: PluginConfigSource;\n homeDir?: string;\n}\n\nexport async function preparePluginConfiguration(\n options: PreparePluginConfigurationOptions,\n): Promise<PreparedPluginConfiguration> {\n const homeDir = options.homeDir ?? homedir();\n const globalConfigFilePath = getGlobalPluginConfigFilePath(homeDir);\n const projectConfigFilePath = await resolveProjectPluginConfigFilePath(options.cwd);\n const [globalConfig, projectConfig] = await Promise.all([\n loadPluginConfigFile(globalConfigFilePath),\n loadPluginConfigFile(projectConfigFilePath),\n ]);\n const config = stripLegacyVoiceConfig(mergePluginConfigSources(globalConfig, projectConfig, options.config));\n const configFilePath = await pathExists(projectConfigFilePath)\n ? projectConfigFilePath\n : globalConfigFilePath;\n\n return {\n cwd: options.cwd,\n config,\n globalConfigFilePath,\n projectConfigFilePath,\n configFilePath,\n };\n}\n\nexport function getOpenCodeConfigDirectory(homeDir: string = homedir()): string {\n return join(homeDir, \".config\", \"opencode\");\n}\n\nexport function getOpenCodeConfigFilePath(homeDir: string = homedir()): string {\n return join(getOpenCodeConfigDirectory(homeDir), OPENCODE_CONFIG_FILE_NAME);\n}\n\nexport function getGlobalPluginConfigFilePath(homeDir: string = homedir()): string {\n return join(\n getOpenCodeConfigDirectory(homeDir),\n GLOBAL_PLUGIN_DIRECTORY_NAME,\n GLOBAL_PLUGIN_CONFIG_FILE_NAME,\n );\n}\n\nexport async function writePluginConfigFile(\n configFilePath: string,\n config: PluginConfigSource,\n): Promise<void> {\n await mkdir(dirname(configFilePath), { recursive: true });\n await writeFile(configFilePath, serializePluginConfig(config), \"utf8\");\n}\n\nexport function mergePluginConfigSources(\n ...sources: Array<PluginConfigSource | undefined>\n): PluginConfigSource {\n const merged: PluginConfigSource = {};\n\n for (const source of sources) {\n if (!source) {\n continue;\n }\n\n const normalized = source;\n const previousTelegram = merged.telegram;\n const previousState = merged.state;\n\n Object.assign(merged, normalized);\n\n if (normalized.telegram) {\n merged.telegram = {\n ...(previousTelegram ?? {}),\n ...normalized.telegram,\n };\n }\n\n if (normalized.state) {\n merged.state = {\n ...(previousState ?? {}),\n ...normalized.state,\n };\n }\n }\n\n return merged;\n}\n\nexport function serializePluginConfig(config: PluginConfigSource): string {\n return `${JSON.stringify(orderPluginConfig(config), null, 2)}\\n`;\n}\n\nasync function loadPluginConfigFile(configFilePath: string): Promise<PluginConfigSource> {\n try {\n const content = await readFile(configFilePath, \"utf8\");\n\n return parsePluginConfigText(content, configFilePath);\n } catch (error) {\n if (isMissingFileError(error)) {\n return {};\n }\n\n throw error;\n }\n}\n\nfunction parsePluginConfigText(\n content: string,\n configFilePath: string,\n): PluginConfigSource {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n if (!isPlainObject(parsed)) {\n throw new Error(\"Config root must be a JSON object.\");\n }\n\n return parsed as PluginConfigSource;\n } catch (error) {\n throw new Error(\n [\n `Failed to parse ${configFilePath} as JSON.`,\n error instanceof Error ? error.message : String(error),\n ].join(\" \"),\n );\n }\n}\n\nfunction orderPluginConfig(config: PluginConfigSource): PluginConfigSource {\n const prioritizedKeys = new Set([\n \"telegram\",\n \"state\",\n \"logLevel\",\n ]);\n const ordered: PluginConfigSource = {};\n\n if (config.telegram) {\n ordered.telegram = config.telegram;\n }\n\n if (config.state) {\n ordered.state = config.state;\n }\n\n if (config.logLevel !== undefined) {\n ordered.logLevel = config.logLevel;\n }\n\n for (const [key, value] of Object.entries(config)) {\n if (!prioritizedKeys.has(key)) {\n ordered[key] = value;\n }\n }\n\n return ordered;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isMissingFileError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nasync function resolveProjectPluginConfigFilePath(cwd: string): Promise<string> {\n const preferredPath = join(cwd, PLUGIN_CONFIG_FILE_NAME);\n\n return preferredPath;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch (error) {\n if (isMissingFileError(error)) {\n return false;\n }\n\n throw error;\n }\n}\n\nfunction stripLegacyVoiceConfig(config: PluginConfigSource): PluginConfigSource {\n const { openrouter: _openrouter, ...rest } = config as PluginConfigSource & {\n openrouter?: unknown;\n };\n\n return rest;\n}\n"],"mappings":";;;;;;;AAGA,IAAa,0BAA0B;AACvC,IAAa,4BAA4B;AAEzC,IAAM,sBAAsB,EAAE,MAAM,CAChC,EAAE,QAAQ,CAAC,KAAK,EAChB,EAAE,QAAQ,CAAC,MAAM,WAAW,CAAC,WAAW,UAAU,OAAO,MAAM,CAAC,CACnE,CAAC;AAEF,IAAM,uBAAuB,EAAE,YAC1B,UAAU,SAAS,EAAE,EACtB,EAAE,OAAO;CACL,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE;CAClC,gBAAgB,EAAE,MAAM,oBAAoB,CAAC,QAAQ,EAAE,CAAC;CACxD,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,0BAA0B;CACtE,CAAC,CACL;AAED,IAAM,oBAAoB,EAAE,YACvB,UAAU,SAAS,EAAE,EACtB,EAAE,OAAO,EACL,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,wBAAwB,EAClE,CAAC,CACL;AAED,IAAM,kBAAkB,EAAE,OAAO;CAC7B,UAAU;CACV,OAAO;CACP,UAAU,EAAE,QAAQ,CAAC,QAAQ,OAAO;CACvC,CAAC;AA6BF,SAAgB,cACZ,eAA+C,EAAE,EACjD,UAAgC,EAAE,EACzB;AAGT,QAAO,eAFQ,YAAY,iBAAiB,aAAa,EAE3B,QAAQ;;AAK1C,SAAS,eACL,MACA,SACS;AACT,QAAO;EACH,kBAAkB,KAAK,SAAS;EAChC,wBAAwB,KAAK,SAAS;EACtC,iBAAiB,iBAAiB,KAAK,SAAS,QAAQ;EACxD,UAAU,KAAK;EACf,eAAe,iBAAiB,MAAM,QAAQ,OAAO,QAAQ,KAAK,CAAC;EACtE;;AAGL,SAAS,iBACL,MACA,KACM;AACN,QAAO,QAAQ,KAAK,KAAK,MAAM,QAAA,kCAAgC;;AAGnE,SAAS,iBAAiB,OAAuB;CAC7C,MAAM,aAAa,MAAM,MAAM;AAE/B,QAAO,WAAW,SAAS,IAAI,GACzB,WAAW,MAAM,GAAG,GAAG,GACvB;;AAGV,SAAS,YACL,QACA,cACgB;CAChB,MAAM,SAAS,OAAO,UAAU,gBAAgB,EAAE,CAAC;AAEnD,KAAI,OAAO,QACP,QAAO,OAAO;AAGlB,OAAM,IAAI,MACN,iCAAiC,KAAK,UAAU,OAAO,MAAM,SAAS,CAAC,GAC1E;;;;AC3GL,IAAa,wBAAwB,uBAAuB;AAE5D,SAAS,wBAAgC;CACrC,IAAI,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEvD,QAAO,MAAM;EACT,MAAM,kBAAkB,KAAK,WAAW,eAAe;AAEvD,MAAI,WAAW,gBAAgB,CAC3B,KAAI;GACA,MAAM,SAAS,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AAIhE,OAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,MAAM,CAAC,SAAS,EACrE,QAAO,OAAO;UAEd;EAKZ,MAAM,kBAAkB,QAAQ,UAAU;AAE1C,MAAI,oBAAoB,UACpB;AAGJ,cAAY;;AAGhB,QAAO;;;;AC9BX,IAAa,0BAA0B;AACvC,IAAa,+BAA+B;AAC5C,IAAa,iCAAiC;AAC9C,IAAa,4BAA4B;AAgBzC,eAAsB,2BAClB,SACoC;CAEpC,MAAM,uBAAuB,8BADb,QAAQ,WAAW,SAAS,CACuB;CACnE,MAAM,wBAAwB,MAAM,mCAAmC,QAAQ,IAAI;CACnF,MAAM,CAAC,cAAc,iBAAiB,MAAM,QAAQ,IAAI,CACpD,qBAAqB,qBAAqB,EAC1C,qBAAqB,sBAAsB,CAC9C,CAAC;CACF,MAAM,SAAS,uBAAuB,yBAAyB,cAAc,eAAe,QAAQ,OAAO,CAAC;CAC5G,MAAM,iBAAiB,MAAM,WAAW,sBAAsB,GACxD,wBACA;AAEN,QAAO;EACH,KAAK,QAAQ;EACb;EACA;EACA;EACA;EACH;;AAGL,SAAgB,2BAA2B,UAAkB,SAAS,EAAU;AAC5E,QAAO,KAAK,SAAS,WAAW,WAAW;;AAG/C,SAAgB,0BAA0B,UAAkB,SAAS,EAAU;AAC3E,QAAO,KAAK,2BAA2B,QAAQ,EAAE,0BAA0B;;AAG/E,SAAgB,8BAA8B,UAAkB,SAAS,EAAU;AAC/E,QAAO,KACH,2BAA2B,QAAQ,EACnC,8BACA,+BACH;;AAGL,eAAsB,sBAClB,gBACA,QACa;AACb,OAAM,MAAM,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC;AACzD,OAAM,UAAU,gBAAgB,sBAAsB,OAAO,EAAE,OAAO;;AAG1E,SAAgB,yBACZ,GAAG,SACe;CAClB,MAAM,SAA6B,EAAE;AAErC,MAAK,MAAM,UAAU,SAAS;AAC1B,MAAI,CAAC,OACD;EAGJ,MAAM,aAAa;EACnB,MAAM,mBAAmB,OAAO;EAChC,MAAM,gBAAgB,OAAO;AAE7B,SAAO,OAAO,QAAQ,WAAW;AAEjC,MAAI,WAAW,SACX,QAAO,WAAW;GACd,GAAI,oBAAoB,EAAE;GAC1B,GAAG,WAAW;GACjB;AAGL,MAAI,WAAW,MACX,QAAO,QAAQ;GACX,GAAI,iBAAiB,EAAE;GACvB,GAAG,WAAW;GACjB;;AAIT,QAAO;;AAGX,SAAgB,sBAAsB,QAAoC;AACtE,QAAO,GAAG,KAAK,UAAU,kBAAkB,OAAO,EAAE,MAAM,EAAE,CAAC;;AAGjE,eAAe,qBAAqB,gBAAqD;AACrF,KAAI;AAGA,SAAO,sBAFS,MAAM,SAAS,gBAAgB,OAAO,EAEhB,eAAe;UAChD,OAAO;AACZ,MAAI,mBAAmB,MAAM,CACzB,QAAO,EAAE;AAGb,QAAM;;;AAId,SAAS,sBACL,SACA,gBACkB;AAClB,KAAI;EACA,MAAM,SAAS,KAAK,MAAM,QAAQ;AAElC,MAAI,CAAC,cAAc,OAAO,CACtB,OAAM,IAAI,MAAM,qCAAqC;AAGzD,SAAO;UACF,OAAO;AACZ,QAAM,IAAI,MACN,CACI,mBAAmB,eAAe,YAClC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CACzD,CAAC,KAAK,IAAI,CACd;;;AAIT,SAAS,kBAAkB,QAAgD;CACvE,MAAM,kBAAkB,IAAI,IAAI;EAC5B;EACA;EACA;EACH,CAAC;CACF,MAAM,UAA8B,EAAE;AAEtC,KAAI,OAAO,SACP,SAAQ,WAAW,OAAO;AAG9B,KAAI,OAAO,MACP,SAAQ,QAAQ,OAAO;AAG3B,KAAI,OAAO,aAAa,KAAA,EACpB,SAAQ,WAAW,OAAO;AAG9B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,CAC7C,KAAI,CAAC,gBAAgB,IAAI,IAAI,CACzB,SAAQ,OAAO;AAIvB,QAAO;;AAGX,SAAS,cAAc,OAAkD;AACrE,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;AAG/E,SAAS,mBAAmB,OAAgD;AACxE,QAAO,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS;;AAGvE,eAAe,mCAAmC,KAA8B;AAG5E,QAFsB,KAAK,KAAK,wBAAwB;;AAK5D,eAAe,WAAW,UAAoC;AAC1D,KAAI;AACA,QAAM,OAAO,SAAS;AACtB,SAAO;UACF,OAAO;AACZ,MAAI,mBAAmB,MAAM,CACzB,QAAO;AAGX,QAAM;;;AAId,SAAS,uBAAuB,QAAgD;CAC5E,MAAM,EAAE,YAAY,aAAa,GAAG,SAAS;AAI7C,QAAO"}
|