opencode-tbot 0.1.28 → 0.1.31
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.md +37 -2
- package/README.zh-CN.md +84 -50
- package/dist/assets/{plugin-config-DNeV2Ckw.js → plugin-config-jkAZYbFW.js} +91 -18
- package/dist/assets/plugin-config-jkAZYbFW.js.map +1 -0
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +1186 -338
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/plugin-config-DNeV2Ckw.js.map +0 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Recommended install:
|
|
|
32
32
|
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
The installer registers the plugin globally
|
|
35
|
+
The installer registers the plugin globally, writes the default runtime config, and prints both the config path and the default plugin log directory.
|
|
36
36
|
|
|
37
37
|
Check the installed CLI version:
|
|
38
38
|
|
|
@@ -86,6 +86,20 @@ Legacy `openrouter` voice-transcription settings are ignored at runtime. When th
|
|
|
86
86
|
"pollRequestTimeoutMs": 15000,
|
|
87
87
|
"recoveryInactivityTimeoutMs": 120000
|
|
88
88
|
},
|
|
89
|
+
"logging": {
|
|
90
|
+
"level": "info",
|
|
91
|
+
"sinks": {
|
|
92
|
+
"host": true,
|
|
93
|
+
"file": true
|
|
94
|
+
},
|
|
95
|
+
"file": {
|
|
96
|
+
"dir": "C:/Users/your-user/.local/share/opencode/log/plugins/opencode-tbot",
|
|
97
|
+
"retention": {
|
|
98
|
+
"maxFiles": 30,
|
|
99
|
+
"maxTotalBytes": 314572800
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
89
103
|
"logLevel": "info"
|
|
90
104
|
}
|
|
91
105
|
```
|
|
@@ -101,7 +115,13 @@ Legacy `openrouter` voice-transcription settings are ignored at runtime. When th
|
|
|
101
115
|
| `prompt.waitTimeoutMs` | No | `1800000` | Maximum total wait for one async prompt lifecycle before the plugin stops waiting for OpenCode recovery. |
|
|
102
116
|
| `prompt.pollRequestTimeoutMs` | No | `15000` | Timeout for each individual recovery poll request to OpenCode. |
|
|
103
117
|
| `prompt.recoveryInactivityTimeoutMs` | No | `120000` | Recovery timeout that only applies when prompt progress stops advancing. |
|
|
104
|
-
| `
|
|
118
|
+
| `logging.level` | No | `info` | Structured log level for both host and file sinks. |
|
|
119
|
+
| `logging.sinks.host` | No | `true` | Enable OpenCode host logging through `client.app.log({ body: ... })`. |
|
|
120
|
+
| `logging.sinks.file` | No | `true` | Enable plugin-owned JSONL file logs. |
|
|
121
|
+
| `logging.file.dir` | No | `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot` | Plugin JSONL log directory. |
|
|
122
|
+
| `logging.file.retention.maxFiles` | No | `30` | Maximum number of retained plugin log files. |
|
|
123
|
+
| `logging.file.retention.maxTotalBytes` | No | `314572800` | Maximum total size of retained plugin log files. |
|
|
124
|
+
| `logLevel` | No | `info` | Legacy alias for `logging.level`. Still accepted for compatibility. |
|
|
105
125
|
|
|
106
126
|
### Notes
|
|
107
127
|
|
|
@@ -111,6 +131,19 @@ Legacy `openrouter` voice-transcription settings are ignored at runtime. When th
|
|
|
111
131
|
- If `telegram.allowedChatIds` is left empty, the bot accepts messages from any chat. Restrict it in production.
|
|
112
132
|
- Permission approvals and session notifications are handled through plugin hooks.
|
|
113
133
|
- `/language` currently supports English, Simplified Chinese, and Japanese.
|
|
134
|
+
- Plugin logs are dual-written by default: OpenCode host logs through `client.app.log({ body: ... })`, plus plugin JSONL files.
|
|
135
|
+
- OpenCode host logs live under `%USERPROFILE%/.local/share/opencode/log`.
|
|
136
|
+
- The default plugin JSONL log directory is `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot`.
|
|
137
|
+
- File logs are metadata-only by default. Prompt bodies, attachment contents, raw URLs, and secrets are not written.
|
|
138
|
+
|
|
139
|
+
## Logging
|
|
140
|
+
|
|
141
|
+
- Host log directory: `%USERPROFILE%/.local/share/opencode/log`
|
|
142
|
+
- Plugin log directory: `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot`
|
|
143
|
+
- File format: append-only JSONL, one structured event per line
|
|
144
|
+
- Correlation fields: `runtimeId`, `operationId`, `correlationId`, `updateId`, `chatId`, `sessionId`, `projectId`
|
|
145
|
+
- Common components: `runtime`, `telegram`, `opencode`, `prompt`, `plugin-event`, `storage`
|
|
146
|
+
- Retention: keep the newest 30 files and at most 300 MB by default
|
|
114
147
|
|
|
115
148
|
## Quick Start
|
|
116
149
|
|
|
@@ -161,6 +194,8 @@ pnpm test
|
|
|
161
194
|
|
|
162
195
|
For local source-based loading in this repository, OpenCode can use [.opencode/plugins/opencode-tbot.ts](./.opencode/plugins/opencode-tbot.ts), which re-exports `src/plugin.ts`.
|
|
163
196
|
|
|
197
|
+
OpenCode may load both the local `.opencode/plugins/` bridge and an installed npm plugin such as `opencode-tbot@latest` in the same process. This repository now guards against duplicate Telegram runners, but local development is still clearer if you keep only one loading path enabled at a time.
|
|
198
|
+
|
|
164
199
|
## FAQ
|
|
165
200
|
|
|
166
201
|
### Do I need a running OpenCode instance?
|
package/README.zh-CN.md
CHANGED
|
@@ -4,24 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
[English](./README.md) | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
|
|
6
6
|
|
|
7
|
-
> 本项目并非由 OpenCode
|
|
7
|
+
> 本项目并非由 OpenCode 官方团队开发。
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 概览
|
|
10
10
|
|
|
11
11
|
`opencode-tbot` 允许你直接在 Telegram 中操作 OpenCode。
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
- Telegram
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
13
|
+
- 文本消息会转发到当前 OpenCode 会话。
|
|
14
|
+
- Telegram 图片会作为 OpenCode 文件片段上传。
|
|
15
|
+
- 图片轮次会在临时 fork 会话中执行,避免后续纯文本上下文被污染。
|
|
16
|
+
- Telegram 语音消息会被明确拒绝。
|
|
17
|
+
- OpenCode 的权限请求可以直接在 Telegram 中审批。
|
|
18
|
+
- 会话完成和错误事件可以回推到已绑定的 Telegram chat。
|
|
19
|
+
- 聊天绑定状态会保存在 JSON 状态文件中。
|
|
19
20
|
|
|
20
21
|
## 环境要求
|
|
21
22
|
|
|
22
23
|
- 一个正在运行、并会加载该插件的 OpenCode Host 进程。
|
|
23
24
|
- 一个 Telegram bot token。
|
|
24
|
-
- Node.js `>=22.12.0
|
|
25
|
+
- Node.js `>=22.12.0`。
|
|
25
26
|
|
|
26
27
|
## 安装
|
|
27
28
|
|
|
@@ -31,9 +32,9 @@
|
|
|
31
32
|
npm exec --package opencode-tbot@latest opencode-tbot -- install
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
安装器会全局注册插件、写入默认运行时配置,并打印插件配置路径与默认日志目录。
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
查看 CLI 版本:
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
npm exec --package opencode-tbot@latest opencode-tbot -- --version
|
|
@@ -49,10 +50,10 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
|
49
50
|
|
|
50
51
|
`install` 支持:
|
|
51
52
|
|
|
52
|
-
- `--bot-token <token>`
|
|
53
|
+
- `--bot-token <token>` 非交互写入 Telegram bot token
|
|
53
54
|
- `--telegram-api-root <url>` 覆盖 Telegram Bot API 根地址
|
|
54
55
|
- `--plugin-spec <spec>` 注册自定义 npm 插件 spec
|
|
55
|
-
- `--skip-register`
|
|
56
|
+
- `--skip-register` 只重写插件配置,不改 OpenCode 插件注册
|
|
56
57
|
- `--home-dir <path>` 使用自定义 home 目录
|
|
57
58
|
|
|
58
59
|
`update` 支持:
|
|
@@ -62,11 +63,11 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
|
62
63
|
|
|
63
64
|
## 配置
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
运行时配置只从 `~/.config/opencode/opencode-tbot/config.json` 加载。
|
|
66
67
|
|
|
67
|
-
遗留的 `<worktree>/tbot.config.json`
|
|
68
|
+
遗留的 `<worktree>/tbot.config.json` 会在运行时被忽略;如果检测到,插件会记录一条警告,提示你把配置迁移到全局文件。
|
|
68
69
|
|
|
69
|
-
遗留的 `openrouter`
|
|
70
|
+
遗留的 `openrouter` 语音转写配置会在运行时被忽略;安装器重写配置时也会自动移除它们。
|
|
70
71
|
|
|
71
72
|
### 全局 `config.json` 示例
|
|
72
73
|
|
|
@@ -85,6 +86,20 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
|
85
86
|
"pollRequestTimeoutMs": 15000,
|
|
86
87
|
"recoveryInactivityTimeoutMs": 120000
|
|
87
88
|
},
|
|
89
|
+
"logging": {
|
|
90
|
+
"level": "info",
|
|
91
|
+
"sinks": {
|
|
92
|
+
"host": true,
|
|
93
|
+
"file": true
|
|
94
|
+
},
|
|
95
|
+
"file": {
|
|
96
|
+
"dir": "C:/Users/your-user/.local/share/opencode/log/plugins/opencode-tbot",
|
|
97
|
+
"retention": {
|
|
98
|
+
"maxFiles": 30,
|
|
99
|
+
"maxTotalBytes": 314572800
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
88
103
|
"logLevel": "info"
|
|
89
104
|
}
|
|
90
105
|
```
|
|
@@ -93,53 +108,70 @@ npm exec --package opencode-tbot@latest opencode-tbot -- update
|
|
|
93
108
|
|
|
94
109
|
| 字段 | 必填 | 默认值 | 说明 |
|
|
95
110
|
| --- | --- | --- | --- |
|
|
96
|
-
| `telegram.botToken` | 是 | - | Telegram bot token
|
|
97
|
-
| `telegram.allowedChatIds` | 否 | `[]` | 允许访问的 Telegram chat ID
|
|
98
|
-
| `telegram.apiRoot` | 否 | `https://api.telegram.org` | Telegram Bot API
|
|
111
|
+
| `telegram.botToken` | 是 | - | Telegram bot token。通常由安装器写入。 |
|
|
112
|
+
| `telegram.allowedChatIds` | 否 | `[]` | 允许访问的 Telegram chat ID。为空时接受任意 chat。 |
|
|
113
|
+
| `telegram.apiRoot` | 否 | `https://api.telegram.org` | Telegram Bot API 根地址。 |
|
|
99
114
|
| `state.path` | 否 | `./data/opencode-tbot.state.json` | JSON 状态文件路径,相对当前 OpenCode worktree 解析。 |
|
|
100
|
-
| `prompt.waitTimeoutMs` | 否 | `1800000` | 单次异步 prompt
|
|
101
|
-
| `prompt.pollRequestTimeoutMs` | 否 | `15000` |
|
|
102
|
-
| `prompt.recoveryInactivityTimeoutMs` | 否 | `120000` | 仅在
|
|
103
|
-
| `
|
|
115
|
+
| `prompt.waitTimeoutMs` | 否 | `1800000` | 单次异步 prompt 生命周期的最大等待时间。 |
|
|
116
|
+
| `prompt.pollRequestTimeoutMs` | 否 | `15000` | 每次恢复轮询请求的超时时间。 |
|
|
117
|
+
| `prompt.recoveryInactivityTimeoutMs` | 否 | `120000` | 仅在 OpenCode 长时间没有新进展时生效的恢复超时。 |
|
|
118
|
+
| `logging.level` | 否 | `info` | 结构化日志级别。 |
|
|
119
|
+
| `logging.sinks.host` | 否 | `true` | 是否通过 `client.app.log({ body: ... })` 写入 OpenCode Host 日志。 |
|
|
120
|
+
| `logging.sinks.file` | 否 | `true` | 是否写入插件自己的 JSONL 文件日志。 |
|
|
121
|
+
| `logging.file.dir` | 否 | `%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot` | 插件日志目录。 |
|
|
122
|
+
| `logging.file.retention.maxFiles` | 否 | `30` | 最多保留多少个插件日志文件。 |
|
|
123
|
+
| `logging.file.retention.maxTotalBytes` | 否 | `314572800` | 插件日志文件总大小上限。 |
|
|
124
|
+
| `logLevel` | 否 | `info` | `logging.level` 的兼容别名。 |
|
|
104
125
|
|
|
105
126
|
### 说明
|
|
106
127
|
|
|
107
128
|
- `state.path` 会相对当前 OpenCode worktree 解析。
|
|
108
|
-
- Telegram prompt
|
|
109
|
-
- `prompt.waitTimeoutMs` 是整体安全上限;`prompt.recoveryInactivityTimeoutMs` 只在 OpenCode 长时间没有新进展时生效。
|
|
129
|
+
- Telegram prompt 采用 async-first:插件先提交异步 prompt,再通过消息与状态恢复最终回复。
|
|
110
130
|
- 如果 `telegram.allowedChatIds` 为空,bot 会接受任意 chat 的消息;生产环境建议显式限制。
|
|
111
|
-
-
|
|
112
|
-
- `/language` 当前支持 English
|
|
131
|
+
- 权限审批与会话通知通过插件 hooks 处理。
|
|
132
|
+
- `/language` 当前支持 English、简体中文、日语。
|
|
133
|
+
- 默认会双写日志:OpenCode Host 日志 + 插件 JSONL 文件日志。
|
|
134
|
+
- 文件日志默认只记录元数据,不记录 prompt 正文、附件内容、原始 URL 或 secrets。
|
|
135
|
+
|
|
136
|
+
## 日志
|
|
137
|
+
|
|
138
|
+
- OpenCode Host 日志目录:`%USERPROFILE%/.local/share/opencode/log`
|
|
139
|
+
- 插件日志目录:`%USERPROFILE%/.local/share/opencode/log/plugins/opencode-tbot`
|
|
140
|
+
- 文件格式:JSONL,每行一条结构化事件
|
|
141
|
+
- 关键关联字段:`runtimeId`、`operationId`、`correlationId`、`updateId`、`chatId`、`sessionId`、`projectId`
|
|
142
|
+
- 常见组件:`runtime`、`telegram`、`opencode`、`prompt`、`plugin-event`、`storage`
|
|
143
|
+
- 默认保留策略:最多 30 个文件,总大小不超过 300 MB
|
|
113
144
|
|
|
114
145
|
## 快速开始
|
|
115
146
|
|
|
116
|
-
1.
|
|
117
|
-
2.
|
|
118
|
-
3. 在目标 worktree 中启动 OpenCode
|
|
119
|
-
4. 在 Telegram 中执行 `/status`
|
|
120
|
-
5. 执行 `/new [title]
|
|
147
|
+
1. 运行 `npm exec --package opencode-tbot@latest opencode-tbot -- install`。
|
|
148
|
+
2. 如需限制可访问 chat,在 `~/.config/opencode/opencode-tbot/config.json` 中设置 `telegram.allowedChatIds`。
|
|
149
|
+
3. 在目标 worktree 中启动 OpenCode。
|
|
150
|
+
4. 在 Telegram 中执行 `/status` 验证连通性。
|
|
151
|
+
5. 执行 `/new [title]`,或者直接发送文本/图片开始使用。
|
|
121
152
|
|
|
122
153
|
## 命令
|
|
123
154
|
|
|
124
|
-
- `/start`
|
|
125
|
-
- `/status` 显示 OpenCode
|
|
126
|
-
- `/new [title]`
|
|
127
|
-
- `/agents` 或 `/agent`
|
|
128
|
-
- `/sessions`
|
|
129
|
-
- `/cancel`
|
|
130
|
-
- `/model` 或 `/models`
|
|
131
|
-
- `/language`
|
|
155
|
+
- `/start` 显示欢迎信息和快速开始说明
|
|
156
|
+
- `/status` 显示 OpenCode 健康状态、路径、LSP、MCP 等信息
|
|
157
|
+
- `/new [title]` 创建新会话
|
|
158
|
+
- `/agents` 或 `/agent` 列出并切换 agent
|
|
159
|
+
- `/sessions` 列出并切换会话
|
|
160
|
+
- `/cancel` 取消重命名,或中止当前正在运行的请求
|
|
161
|
+
- `/model` 或 `/models` 列出并切换模型
|
|
162
|
+
- `/language` 切换显示语言
|
|
132
163
|
|
|
133
164
|
消息处理规则:
|
|
134
165
|
|
|
135
|
-
-
|
|
136
|
-
- Telegram
|
|
137
|
-
-
|
|
138
|
-
- Telegram
|
|
166
|
+
- 非命令文本会作为 prompt 发送给 OpenCode。
|
|
167
|
+
- Telegram 图片会作为 OpenCode 文件片段上传。
|
|
168
|
+
- 图片消息会在临时 fork 会话中处理。
|
|
169
|
+
- `/cancel` 会同时中止 OpenCode 会话和本地 Telegram 等待状态。
|
|
170
|
+
- Telegram 语音消息不受支持,会收到本地化拒绝提示。
|
|
139
171
|
|
|
140
172
|
## 开发
|
|
141
173
|
|
|
142
|
-
|
|
174
|
+
构建:
|
|
143
175
|
|
|
144
176
|
```bash
|
|
145
177
|
pnpm build
|
|
@@ -157,14 +189,16 @@ pnpm typecheck
|
|
|
157
189
|
pnpm test
|
|
158
190
|
```
|
|
159
191
|
|
|
160
|
-
|
|
192
|
+
本仓库本地开发时,OpenCode 可以通过 [.opencode/plugins/opencode-tbot.ts](./.opencode/plugins/opencode-tbot.ts) 直接加载 `src/plugin.ts`。
|
|
193
|
+
|
|
194
|
+
OpenCode 可能会在同一进程中同时加载本地 `.opencode/plugins/` bridge 和已安装的 `opencode-tbot@latest` npm 插件。当前实现已经防止重复启动 Telegram runner,但本地开发仍建议只保留一种加载方式。
|
|
161
195
|
|
|
162
|
-
##
|
|
196
|
+
## FAQ
|
|
163
197
|
|
|
164
|
-
###
|
|
198
|
+
### 需要一个正在运行的 OpenCode 实例吗?
|
|
165
199
|
|
|
166
|
-
|
|
200
|
+
需要。这个仓库只提供 Telegram 集成层,依赖加载它的 OpenCode Host 进程。
|
|
167
201
|
|
|
168
202
|
### 这是 OpenCode 官方项目吗?
|
|
169
203
|
|
|
170
|
-
|
|
204
|
+
不是。它集成 OpenCode,但不是 OpenCode 官方项目。
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
//#region src/app/opencode-paths.ts
|
|
8
|
+
var GLOBAL_PLUGIN_DIRECTORY_NAME = "opencode-tbot";
|
|
9
|
+
var GLOBAL_PLUGIN_CONFIG_FILE_NAME = "config.json";
|
|
10
|
+
var OPENCODE_CONFIG_FILE_NAME = "opencode.json";
|
|
11
|
+
function getOpenCodeConfigDirectory(homeDir = homedir()) {
|
|
12
|
+
return join(homeDir, ".config", "opencode");
|
|
13
|
+
}
|
|
14
|
+
function getOpenCodeConfigFilePath(homeDir = homedir()) {
|
|
15
|
+
return join(getOpenCodeConfigDirectory(homeDir), OPENCODE_CONFIG_FILE_NAME);
|
|
16
|
+
}
|
|
17
|
+
function getGlobalPluginConfigFilePath(homeDir = homedir()) {
|
|
18
|
+
return join(getOpenCodeConfigDirectory(homeDir), GLOBAL_PLUGIN_DIRECTORY_NAME, GLOBAL_PLUGIN_CONFIG_FILE_NAME);
|
|
19
|
+
}
|
|
20
|
+
function getOpenCodeLogDirectory(homeDir = homedir()) {
|
|
21
|
+
return join(homeDir, ".local", "share", "opencode", "log");
|
|
22
|
+
}
|
|
23
|
+
function getDefaultPluginLogDirectory(homeDir = homedir()) {
|
|
24
|
+
return join(getOpenCodeLogDirectory(homeDir), "plugins", "opencode-tbot");
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
7
27
|
//#region src/app/config.ts
|
|
8
28
|
var DEFAULT_STATE_FILE_PATH = "./data/opencode-tbot.state.json";
|
|
9
29
|
var DEFAULT_TELEGRAM_API_ROOT = "https://api.telegram.org";
|
|
10
30
|
var DEFAULT_PROMPT_WAIT_TIMEOUT_MS = 18e5;
|
|
11
31
|
var DEFAULT_PROMPT_POLL_REQUEST_TIMEOUT_MS = 15e3;
|
|
12
32
|
var DEFAULT_PROMPT_RECOVERY_INACTIVITY_TIMEOUT_MS = 12e4;
|
|
33
|
+
var DEFAULT_LOG_LEVEL = "info";
|
|
34
|
+
var DEFAULT_LOG_RETENTION_MAX_TOTAL_BYTES = 314572800;
|
|
13
35
|
var AllowedChatIdSchema = z.union([z.number().int(), z.string().regex(/^-?\d+$/u).transform((value) => Number(value))]);
|
|
14
36
|
var TelegramConfigSchema = z.preprocess((value) => value ?? {}, z.object({
|
|
15
37
|
botToken: z.string().trim().min(1),
|
|
@@ -22,25 +44,49 @@ var PromptConfigSchema = z.preprocess((value) => value ?? {}, z.object({
|
|
|
22
44
|
pollRequestTimeoutMs: z.number().int().positive().default(DEFAULT_PROMPT_POLL_REQUEST_TIMEOUT_MS),
|
|
23
45
|
recoveryInactivityTimeoutMs: z.number().int().positive().default(DEFAULT_PROMPT_RECOVERY_INACTIVITY_TIMEOUT_MS)
|
|
24
46
|
}));
|
|
47
|
+
var LoggingConfigSchema = z.preprocess((value) => value ?? {}, z.object({
|
|
48
|
+
level: z.string().trim().min(1).optional(),
|
|
49
|
+
sinks: z.preprocess((value) => value ?? {}, z.object({
|
|
50
|
+
host: z.boolean().default(true),
|
|
51
|
+
file: z.boolean().default(true)
|
|
52
|
+
})),
|
|
53
|
+
file: z.preprocess((value) => value ?? {}, z.object({
|
|
54
|
+
dir: z.string().trim().min(1).optional(),
|
|
55
|
+
retention: z.preprocess((value) => value ?? {}, z.object({
|
|
56
|
+
maxFiles: z.number().int().positive().default(30),
|
|
57
|
+
maxTotalBytes: z.number().int().positive().default(DEFAULT_LOG_RETENTION_MAX_TOTAL_BYTES)
|
|
58
|
+
}))
|
|
59
|
+
}))
|
|
60
|
+
}));
|
|
25
61
|
var AppConfigSchema = z.object({
|
|
26
62
|
telegram: TelegramConfigSchema,
|
|
27
63
|
state: StateConfigSchema,
|
|
28
64
|
prompt: PromptConfigSchema,
|
|
29
|
-
|
|
65
|
+
logging: LoggingConfigSchema.default({}),
|
|
66
|
+
logLevel: z.string().trim().min(1).optional()
|
|
30
67
|
});
|
|
31
68
|
function loadAppConfig(configSource = {}, options = {}) {
|
|
32
69
|
return buildAppConfig(parseConfig(AppConfigSchema, configSource), options);
|
|
33
70
|
}
|
|
34
71
|
function buildAppConfig(data, options) {
|
|
72
|
+
const cwd = options.cwd ?? process.cwd();
|
|
73
|
+
const loggingLevel = normalizeLogLevelValue(data.logging.level ?? data.logLevel);
|
|
35
74
|
return {
|
|
36
75
|
telegramBotToken: data.telegram.botToken,
|
|
37
76
|
telegramAllowedChatIds: data.telegram.allowedChatIds,
|
|
38
77
|
telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),
|
|
39
|
-
logLevel:
|
|
78
|
+
logLevel: loggingLevel,
|
|
79
|
+
loggingLevel,
|
|
80
|
+
loggingHostSinkEnabled: data.logging.sinks.host,
|
|
81
|
+
loggingFileSinkEnabled: data.logging.sinks.file,
|
|
82
|
+
loggingFileDir: resolveLoggingDirectory(data.logging.file.dir, cwd),
|
|
83
|
+
loggingRetentionMaxFiles: data.logging.file.retention.maxFiles,
|
|
84
|
+
loggingRetentionMaxTotalBytes: data.logging.file.retention.maxTotalBytes,
|
|
40
85
|
promptWaitTimeoutMs: data.prompt.waitTimeoutMs,
|
|
41
86
|
promptPollRequestTimeoutMs: data.prompt.pollRequestTimeoutMs,
|
|
42
87
|
promptRecoveryInactivityTimeoutMs: data.prompt.recoveryInactivityTimeoutMs,
|
|
43
|
-
stateFilePath: resolveStatePath(data,
|
|
88
|
+
stateFilePath: resolveStatePath(data, cwd),
|
|
89
|
+
worktreePath: cwd
|
|
44
90
|
};
|
|
45
91
|
}
|
|
46
92
|
function resolveStatePath(data, cwd) {
|
|
@@ -50,6 +96,21 @@ function normalizeApiRoot(value) {
|
|
|
50
96
|
const normalized = value.trim();
|
|
51
97
|
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
52
98
|
}
|
|
99
|
+
function normalizeLogLevelValue(value) {
|
|
100
|
+
const normalized = value?.trim().toLowerCase();
|
|
101
|
+
switch (normalized) {
|
|
102
|
+
case "debug":
|
|
103
|
+
case "warn":
|
|
104
|
+
case "error":
|
|
105
|
+
case "info": return normalized;
|
|
106
|
+
default: return DEFAULT_LOG_LEVEL;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function resolveLoggingDirectory(value, cwd) {
|
|
110
|
+
const normalized = value?.trim();
|
|
111
|
+
if (!normalized) return getDefaultPluginLogDirectory(homedir());
|
|
112
|
+
return isAbsolute(normalized) ? normalized : resolve(cwd, normalized);
|
|
113
|
+
}
|
|
53
114
|
function parseConfig(schema, configSource) {
|
|
54
115
|
const parsed = schema.safeParse(configSource ?? {});
|
|
55
116
|
if (parsed.success) return parsed.data;
|
|
@@ -75,9 +136,6 @@ function resolvePackageVersion() {
|
|
|
75
136
|
//#endregion
|
|
76
137
|
//#region src/app/plugin-config.ts
|
|
77
138
|
var PLUGIN_CONFIG_FILE_NAME = "tbot.config.json";
|
|
78
|
-
var GLOBAL_PLUGIN_DIRECTORY_NAME = "opencode-tbot";
|
|
79
|
-
var GLOBAL_PLUGIN_CONFIG_FILE_NAME = "config.json";
|
|
80
|
-
var OPENCODE_CONFIG_FILE_NAME = "opencode.json";
|
|
81
139
|
async function preparePluginConfiguration(options) {
|
|
82
140
|
const globalConfigFilePath = getGlobalPluginConfigFilePath(options.homeDir ?? homedir());
|
|
83
141
|
const projectConfigFilePath = await resolveProjectPluginConfigFilePath(options.cwd);
|
|
@@ -94,15 +152,6 @@ async function preparePluginConfiguration(options) {
|
|
|
94
152
|
configFilePath
|
|
95
153
|
};
|
|
96
154
|
}
|
|
97
|
-
function getOpenCodeConfigDirectory(homeDir = homedir()) {
|
|
98
|
-
return join(homeDir, ".config", "opencode");
|
|
99
|
-
}
|
|
100
|
-
function getOpenCodeConfigFilePath(homeDir = homedir()) {
|
|
101
|
-
return join(getOpenCodeConfigDirectory(homeDir), OPENCODE_CONFIG_FILE_NAME);
|
|
102
|
-
}
|
|
103
|
-
function getGlobalPluginConfigFilePath(homeDir = homedir()) {
|
|
104
|
-
return join(getOpenCodeConfigDirectory(homeDir), GLOBAL_PLUGIN_DIRECTORY_NAME, GLOBAL_PLUGIN_CONFIG_FILE_NAME);
|
|
105
|
-
}
|
|
106
155
|
async function writePluginConfigFile(configFilePath, config) {
|
|
107
156
|
await mkdir(dirname(configFilePath), { recursive: true });
|
|
108
157
|
await writeFile(configFilePath, serializePluginConfig(config), "utf8");
|
|
@@ -115,6 +164,7 @@ function mergePluginConfigSources(...sources) {
|
|
|
115
164
|
const previousTelegram = merged.telegram;
|
|
116
165
|
const previousState = merged.state;
|
|
117
166
|
const previousPrompt = merged.prompt;
|
|
167
|
+
const previousLogging = merged.logging;
|
|
118
168
|
Object.assign(merged, normalized);
|
|
119
169
|
if (normalized.telegram) merged.telegram = {
|
|
120
170
|
...previousTelegram ?? {},
|
|
@@ -128,6 +178,27 @@ function mergePluginConfigSources(...sources) {
|
|
|
128
178
|
...previousPrompt ?? {},
|
|
129
179
|
...normalized.prompt
|
|
130
180
|
};
|
|
181
|
+
if (normalized.logging) {
|
|
182
|
+
const previousLoggingSinks = previousLogging?.sinks;
|
|
183
|
+
const previousLoggingFile = previousLogging?.file;
|
|
184
|
+
const previousRetention = previousLoggingFile?.retention;
|
|
185
|
+
merged.logging = {
|
|
186
|
+
...previousLogging ?? {},
|
|
187
|
+
...normalized.logging,
|
|
188
|
+
...normalized.logging.sinks || previousLoggingSinks ? { sinks: {
|
|
189
|
+
...previousLoggingSinks ?? {},
|
|
190
|
+
...normalized.logging.sinks ?? {}
|
|
191
|
+
} } : {},
|
|
192
|
+
...normalized.logging.file || previousLoggingFile ? { file: {
|
|
193
|
+
...previousLoggingFile ?? {},
|
|
194
|
+
...normalized.logging.file ?? {},
|
|
195
|
+
...normalized.logging.file?.retention || previousRetention ? { retention: {
|
|
196
|
+
...previousRetention ?? {},
|
|
197
|
+
...normalized.logging.file?.retention ?? {}
|
|
198
|
+
} } : {}
|
|
199
|
+
} } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
131
202
|
}
|
|
132
203
|
return merged;
|
|
133
204
|
}
|
|
@@ -156,12 +227,14 @@ function orderPluginConfig(config) {
|
|
|
156
227
|
"telegram",
|
|
157
228
|
"state",
|
|
158
229
|
"prompt",
|
|
230
|
+
"logging",
|
|
159
231
|
"logLevel"
|
|
160
232
|
]);
|
|
161
233
|
const ordered = {};
|
|
162
234
|
if (config.telegram) ordered.telegram = config.telegram;
|
|
163
235
|
if (config.state) ordered.state = config.state;
|
|
164
236
|
if (config.prompt) ordered.prompt = config.prompt;
|
|
237
|
+
if (config.logging) ordered.logging = config.logging;
|
|
165
238
|
if (config.logLevel !== void 0) ordered.logLevel = config.logLevel;
|
|
166
239
|
for (const [key, value] of Object.entries(config)) if (!prioritizedKeys.has(key)) ordered[key] = value;
|
|
167
240
|
return ordered;
|
|
@@ -189,6 +262,6 @@ function stripLegacyVoiceConfig(config) {
|
|
|
189
262
|
return rest;
|
|
190
263
|
}
|
|
191
264
|
//#endregion
|
|
192
|
-
export {
|
|
265
|
+
export { DEFAULT_TELEGRAM_API_ROOT as a, getGlobalPluginConfigFilePath as c, OPENCODE_TBOT_VERSION as i, getOpenCodeConfigFilePath as l, preparePluginConfiguration as n, loadAppConfig as o, writePluginConfigFile as r, getDefaultPluginLogDirectory as s, mergePluginConfigSources as t };
|
|
193
266
|
|
|
194
|
-
//# sourceMappingURL=plugin-config-
|
|
267
|
+
//# sourceMappingURL=plugin-config-jkAZYbFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-config-jkAZYbFW.js","names":[],"sources":["../../src/app/opencode-paths.ts","../../src/app/config.ts","../../src/app/package-info.ts","../../src/app/plugin-config.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\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 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 function getOpenCodeLogDirectory(homeDir: string = homedir()): string {\n return join(homeDir, \".local\", \"share\", \"opencode\", \"log\");\n}\n\nexport function getDefaultPluginLogDirectory(homeDir: string = homedir()): string {\n return join(getOpenCodeLogDirectory(homeDir), \"plugins\", \"opencode-tbot\");\n}\n","import { homedir } from \"node:os\";\nimport { isAbsolute, resolve } from \"node:path\";\nimport { z } from \"zod\";\nimport { getDefaultPluginLogDirectory } from \"./opencode-paths.js\";\n\nexport const DEFAULT_STATE_FILE_PATH = \"./data/opencode-tbot.state.json\";\nexport const DEFAULT_TELEGRAM_API_ROOT = \"https://api.telegram.org\";\nexport const DEFAULT_PROMPT_WAIT_TIMEOUT_MS = 1_800_000;\nexport const DEFAULT_PROMPT_POLL_REQUEST_TIMEOUT_MS = 15_000;\nexport const DEFAULT_PROMPT_RECOVERY_INACTIVITY_TIMEOUT_MS = 120_000;\nexport const DEFAULT_LOG_LEVEL = \"info\";\nexport const DEFAULT_LOG_RETENTION_MAX_FILES = 30;\nexport const DEFAULT_LOG_RETENTION_MAX_TOTAL_BYTES = 314_572_800;\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 PromptConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n waitTimeoutMs: z.number().int().positive().default(DEFAULT_PROMPT_WAIT_TIMEOUT_MS),\n pollRequestTimeoutMs: z.number().int().positive().default(DEFAULT_PROMPT_POLL_REQUEST_TIMEOUT_MS),\n recoveryInactivityTimeoutMs: z.number().int().positive().default(DEFAULT_PROMPT_RECOVERY_INACTIVITY_TIMEOUT_MS),\n }),\n);\n\nconst LoggingConfigSchema = z.preprocess(\n (value) => value ?? {},\n z.object({\n level: z.string().trim().min(1).optional(),\n sinks: z.preprocess(\n (value) => value ?? {},\n z.object({\n host: z.boolean().default(true),\n file: z.boolean().default(true),\n }),\n ),\n file: z.preprocess(\n (value) => value ?? {},\n z.object({\n dir: z.string().trim().min(1).optional(),\n retention: z.preprocess(\n (value) => value ?? {},\n z.object({\n maxFiles: z.number().int().positive().default(DEFAULT_LOG_RETENTION_MAX_FILES),\n maxTotalBytes: z.number().int().positive().default(DEFAULT_LOG_RETENTION_MAX_TOTAL_BYTES),\n }),\n ),\n }),\n ),\n }),\n);\n\nconst AppConfigSchema = z.object({\n telegram: TelegramConfigSchema,\n state: StateConfigSchema,\n prompt: PromptConfigSchema,\n logging: LoggingConfigSchema.default({}),\n logLevel: z.string().trim().min(1).optional(),\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 prompt?: {\n waitTimeoutMs?: number;\n pollRequestTimeoutMs?: number;\n recoveryInactivityTimeoutMs?: number;\n [key: string]: unknown;\n };\n logging?: {\n level?: string;\n sinks?: {\n host?: boolean;\n file?: boolean;\n [key: string]: unknown;\n };\n file?: {\n dir?: string;\n retention?: {\n maxFiles?: number;\n maxTotalBytes?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n };\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 loggingLevel: string;\n loggingHostSinkEnabled: boolean;\n loggingFileSinkEnabled: boolean;\n loggingFileDir: string;\n loggingRetentionMaxFiles: number;\n loggingRetentionMaxTotalBytes: number;\n promptWaitTimeoutMs: number;\n promptPollRequestTimeoutMs: number;\n promptRecoveryInactivityTimeoutMs: number;\n stateFilePath: string;\n worktreePath: 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 const cwd = options.cwd ?? process.cwd();\n const loggingLevel = normalizeLogLevelValue(data.logging.level ?? data.logLevel);\n\n return {\n telegramBotToken: data.telegram.botToken,\n telegramAllowedChatIds: data.telegram.allowedChatIds,\n telegramApiRoot: normalizeApiRoot(data.telegram.apiRoot),\n logLevel: loggingLevel,\n loggingLevel,\n loggingHostSinkEnabled: data.logging.sinks.host,\n loggingFileSinkEnabled: data.logging.sinks.file,\n loggingFileDir: resolveLoggingDirectory(\n data.logging.file.dir,\n cwd,\n ),\n loggingRetentionMaxFiles: data.logging.file.retention.maxFiles,\n loggingRetentionMaxTotalBytes: data.logging.file.retention.maxTotalBytes,\n promptWaitTimeoutMs: data.prompt.waitTimeoutMs,\n promptPollRequestTimeoutMs: data.prompt.pollRequestTimeoutMs,\n promptRecoveryInactivityTimeoutMs: data.prompt.recoveryInactivityTimeoutMs,\n stateFilePath: resolveStatePath(data, cwd),\n worktreePath: 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 normalizeLogLevelValue(value: string | undefined): string {\n const normalized = value?.trim().toLowerCase();\n\n switch (normalized) {\n case \"debug\":\n case \"warn\":\n case \"error\":\n case \"info\":\n return normalized;\n default:\n return DEFAULT_LOG_LEVEL;\n }\n}\n\nfunction resolveLoggingDirectory(value: string | undefined, cwd: string): string {\n const normalized = value?.trim();\n\n if (!normalized) {\n return getDefaultPluginLogDirectory(homedir());\n }\n\n return isAbsolute(normalized)\n ? normalized\n : resolve(cwd, 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\";\nimport {\n getDefaultPluginLogDirectory,\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigDirectory,\n getOpenCodeConfigFilePath,\n GLOBAL_PLUGIN_CONFIG_FILE_NAME,\n GLOBAL_PLUGIN_DIRECTORY_NAME,\n OPENCODE_CONFIG_FILE_NAME,\n} from \"./opencode-paths.js\";\n\nexport const PLUGIN_CONFIG_FILE_NAME = \"tbot.config.json\";\n\nexport interface PreparedPluginConfiguration {\n cwd: string;\n config: PluginConfigSource;\n globalConfigFilePath: string;\n projectConfigFilePath: string;\n ignoredProjectConfigFilePath?: 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, hasIgnoredProjectConfig] = await Promise.all([\n loadPluginConfigFile(globalConfigFilePath),\n pathExists(projectConfigFilePath),\n ]);\n const config = stripLegacyVoiceConfig(mergePluginConfigSources(globalConfig, options.config));\n const ignoredProjectConfigFilePath = hasIgnoredProjectConfig\n ? projectConfigFilePath\n : undefined;\n const configFilePath = globalConfigFilePath;\n\n return {\n cwd: options.cwd,\n config,\n globalConfigFilePath,\n projectConfigFilePath,\n ...(ignoredProjectConfigFilePath ? { ignoredProjectConfigFilePath } : {}),\n configFilePath,\n };\n}\n\nexport {\n getDefaultPluginLogDirectory,\n getGlobalPluginConfigFilePath,\n getOpenCodeConfigDirectory,\n getOpenCodeConfigFilePath,\n GLOBAL_PLUGIN_CONFIG_FILE_NAME,\n GLOBAL_PLUGIN_DIRECTORY_NAME,\n OPENCODE_CONFIG_FILE_NAME,\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 const previousPrompt = merged.prompt;\n const previousLogging = merged.logging;\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 if (normalized.prompt) {\n merged.prompt = {\n ...(previousPrompt ?? {}),\n ...normalized.prompt,\n };\n }\n\n if (normalized.logging) {\n const previousLoggingSinks = previousLogging?.sinks;\n const previousLoggingFile = previousLogging?.file;\n const previousRetention = previousLoggingFile?.retention;\n\n merged.logging = {\n ...(previousLogging ?? {}),\n ...normalized.logging,\n ...(normalized.logging.sinks || previousLoggingSinks\n ? {\n sinks: {\n ...(previousLoggingSinks ?? {}),\n ...(normalized.logging.sinks ?? {}),\n },\n }\n : {}),\n ...(normalized.logging.file || previousLoggingFile\n ? {\n file: {\n ...(previousLoggingFile ?? {}),\n ...(normalized.logging.file ?? {}),\n ...(normalized.logging.file?.retention || previousRetention\n ? {\n retention: {\n ...(previousRetention ?? {}),\n ...(normalized.logging.file?.retention ?? {}),\n },\n }\n : {}),\n },\n }\n : {}),\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 \"prompt\",\n \"logging\",\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.prompt) {\n ordered.prompt = config.prompt;\n }\n\n if (config.logging) {\n ordered.logging = config.logging;\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":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as getGlobalPluginConfigFilePath, i as OPENCODE_TBOT_VERSION, l as getOpenCodeConfigFilePath, r as writePluginConfigFile, s as getDefaultPluginLogDirectory, t as mergePluginConfigSources } from "./assets/plugin-config-jkAZYbFW.js";
|
|
2
2
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -53,6 +53,8 @@ async function installPlugin(options = {}) {
|
|
|
53
53
|
else await writeJsonFile(openCodeConfigFilePath, nextOpenCodeConfig);
|
|
54
54
|
await writePluginConfigFile(globalPluginConfigFilePath, nextPluginConfig);
|
|
55
55
|
stdout.write("Success.\n");
|
|
56
|
+
stdout.write(`Plugin config: ${globalPluginConfigFilePath}\n`);
|
|
57
|
+
stdout.write(`Plugin logs: ${getDefaultPluginLogDirectory(homeDir)}\n`);
|
|
56
58
|
await warnAboutLegacyLocalInstallations(homeDir);
|
|
57
59
|
} finally {
|
|
58
60
|
prompt.close();
|
|
@@ -63,6 +65,8 @@ async function updatePlugin(options = {}) {
|
|
|
63
65
|
const openCodeConfigFilePath = getOpenCodeConfigFilePath(homeDir);
|
|
64
66
|
await writeJsonFile(openCodeConfigFilePath, replacePluginRegistration(await readJsoncObject(openCodeConfigFilePath), normalizeOptionalString(options.pluginSpec) ?? DEFAULT_PLUGIN_SPEC));
|
|
65
67
|
stdout.write("Success.\n");
|
|
68
|
+
stdout.write(`Plugin config: ${getGlobalPluginConfigFilePath(homeDir)}\n`);
|
|
69
|
+
stdout.write(`Plugin logs: ${getDefaultPluginLogDirectory(homeDir)}\n`);
|
|
66
70
|
await warnAboutLegacyLocalInstallations(homeDir);
|
|
67
71
|
}
|
|
68
72
|
function parseCliOptions(argv) {
|