@grinev/opencode-telegram-bot 0.1.0-rc.7 → 0.1.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/.env.example +3 -1
- package/README.md +183 -50
- package/dist/app/start-bot-app.js +15 -1
- package/dist/bot/commands/agent.js +2 -1
- package/dist/bot/commands/definitions.js +16 -15
- package/dist/bot/commands/help.js +2 -5
- package/dist/bot/commands/model.js +2 -1
- package/dist/bot/commands/models.js +7 -6
- package/dist/bot/commands/new.js +4 -3
- package/dist/bot/commands/opencode-start.js +18 -20
- package/dist/bot/commands/opencode-stop.js +7 -9
- package/dist/bot/commands/projects.js +9 -6
- package/dist/bot/commands/sessions.js +15 -13
- package/dist/bot/commands/start.js +2 -9
- package/dist/bot/commands/status.js +21 -17
- package/dist/bot/commands/stop.js +10 -9
- package/dist/bot/handlers/agent.js +6 -5
- package/dist/bot/handlers/context.js +14 -16
- package/dist/bot/handlers/model.js +7 -6
- package/dist/bot/handlers/permission.js +32 -26
- package/dist/bot/handlers/question.js +39 -32
- package/dist/bot/handlers/variant.js +10 -9
- package/dist/bot/index.js +27 -26
- package/dist/bot/utils/keyboard.js +6 -3
- package/dist/cli/args.js +7 -6
- package/dist/cli.js +7 -17
- package/dist/config.js +15 -0
- package/dist/i18n/en.js +205 -0
- package/dist/i18n/index.js +50 -0
- package/dist/i18n/ru.js +205 -0
- package/dist/keyboard/manager.js +2 -1
- package/dist/opencode/events.js +4 -4
- package/dist/pinned/manager.js +16 -11
- package/dist/question/manager.js +1 -1
- package/dist/runtime/bootstrap.js +15 -11
- package/dist/summary/aggregator.js +2 -2
- package/dist/summary/formatter.js +9 -6
- package/dist/utils/logger.js +68 -0
- package/package.json +18 -13
package/.env.example
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Telegram Bot Token (from @BotFather)
|
|
2
2
|
TELEGRAM_BOT_TOKEN=
|
|
3
3
|
|
|
4
|
-
# Allowed Telegram User ID (
|
|
4
|
+
# Allowed Telegram User ID (from @userinfobot)
|
|
5
5
|
TELEGRAM_ALLOWED_USER_ID=
|
|
6
6
|
|
|
7
7
|
# OpenCode API URL (optional, default: http://localhost:4096)
|
|
@@ -28,6 +28,8 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
28
28
|
# Bot Configuration (optional)
|
|
29
29
|
# Maximum number of sessions shown in /sessions (default: 10)
|
|
30
30
|
# SESSIONS_LIST_LIMIT=10
|
|
31
|
+
# Bot locale: en or ru (default: en)
|
|
32
|
+
# BOT_LOCALE=en
|
|
31
33
|
|
|
32
34
|
# Code File Settings (optional)
|
|
33
35
|
# Maximum file size in KB to send as document (default: 100)
|
package/README.md
CHANGED
|
@@ -1,72 +1,205 @@
|
|
|
1
|
-
# OpenCode Telegram
|
|
1
|
+
# OpenCode Telegram Bot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@grinev/opencode-telegram-bot)
|
|
4
|
+
[](https://github.com/grinev/opencode-telegram-bot/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Control your [OpenCode](https://opencode.ai) coding agent from your phone. Send tasks, switch models, monitor progress — all through Telegram.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
- Установленный OpenCode CLI (`opencode`)
|
|
10
|
+
No open ports, no exposed APIs. The bot runs on your machine alongside OpenCode and communicates exclusively through the Telegram Bot API.
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="assets/Screenshot-1.png" width="32%" alt="Sending a coding task and receiving file edit results" />
|
|
14
|
+
<img src="assets/Screenshot-2.png" width="32%" alt="Live session status with context usage and changed files" />
|
|
15
|
+
<img src="assets/Screenshot-3.png" width="32%" alt="Switching between AI models from favorites" />
|
|
16
|
+
</p>
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
2. Установите зависимости:
|
|
14
|
-
```bash
|
|
15
|
-
npm install
|
|
16
|
-
```
|
|
17
|
-
3. Скопируйте файл с переменными окружения:
|
|
18
|
-
```bash
|
|
19
|
-
cp .env.example .env
|
|
20
|
-
```
|
|
21
|
-
4. Заполните `.env` файл (токен бота, ваш ID и путь к проекту).
|
|
22
|
-
5. Настройте избранные модели в OpenCode (Desktop/TUI). Бот автоматически читает их из локального state файла OpenCode (`model.json`).
|
|
23
|
-
6. Если избранные модели недоступны или файл не читается, бот использует fallback из `.env`:
|
|
24
|
-
- `OPENCODE_MODEL_PROVIDER`
|
|
25
|
-
- `OPENCODE_MODEL_ID`
|
|
18
|
+
## Features
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
- **Remote coding** — send prompts to OpenCode from anywhere, receive complete results with code sent as files
|
|
21
|
+
- **Session management** — create new sessions or continue existing ones, just like in the TUI
|
|
22
|
+
- **Live status** — pinned message with current project, model, context usage, and changed files list, updated in real time
|
|
23
|
+
- **Model switching** — pick any model from your OpenCode favorites directly in the chat
|
|
24
|
+
- **Agent modes** — switch between Plan and Build modes on the fly
|
|
25
|
+
- **Interactive Q&A** — answer agent questions and approve permissions via inline buttons
|
|
26
|
+
- **Context control** — compact context when it gets too large, right from the chat
|
|
27
|
+
- **Security** — strict user ID whitelist; no one else can access your bot, even if they find it
|
|
28
|
+
- **Localization** — English and Russian UI (`BOT_LOCALE=en|ru`)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
## Prerequisites
|
|
30
31
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- `OPENCODE_MODEL_ID` — ID модели по умолчанию (обязательно).
|
|
35
|
-
- `SESSIONS_LIST_LIMIT` — сколько последних сессий показывать в `/sessions` (опционально, по умолчанию `10`).
|
|
32
|
+
- **Node.js 20+** — [download](https://nodejs.org)
|
|
33
|
+
- **OpenCode** — install from [opencode.ai](https://opencode.ai) or [GitHub](https://github.com/sst/opencode)
|
|
34
|
+
- **Telegram Bot** — you'll create one during setup (takes 1 minute)
|
|
36
35
|
|
|
37
|
-
##
|
|
36
|
+
## Quick Start
|
|
38
37
|
|
|
39
|
-
###
|
|
38
|
+
### 1. Create a Telegram Bot
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
1. Open [@BotFather](https://t.me/BotFather) in Telegram and send `/newbot`
|
|
41
|
+
2. Follow the prompts to choose a name and username
|
|
42
|
+
3. Copy the **bot token** you receive (e.g. `123456:ABC-DEF1234...`)
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
You'll also need your **Telegram User ID** — send any message to [@userinfobot](https://t.me/userinfobot) and it will reply with your numeric ID.
|
|
45
|
+
|
|
46
|
+
### 2. Start OpenCode Server
|
|
47
|
+
|
|
48
|
+
In your project directory, start the OpenCode server:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
opencode serve
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> The bot connects to the OpenCode API at `http://localhost:4096` by default.
|
|
55
|
+
|
|
56
|
+
### 3. Install & Run
|
|
57
|
+
|
|
58
|
+
The fastest way — run directly with `npx`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx @grinev/opencode-telegram-bot
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On first launch, an interactive wizard will guide you through the configuration — it will ask for your bot token, user ID, and OpenCode API URL. After that, you're ready to go. Open your bot in Telegram and start sending tasks.
|
|
65
|
+
|
|
66
|
+
#### Alternative: Global Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g @grinev/opencode-telegram-bot
|
|
70
|
+
opencode-telegram start
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To reconfigure at any time:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
opencode-telegram config
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Supported Platforms
|
|
80
|
+
|
|
81
|
+
| Platform | Status |
|
|
82
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
83
|
+
| macOS | Fully supported |
|
|
84
|
+
| Windows | Fully supported |
|
|
85
|
+
| Linux | Experimental — should work, but has not been extensively tested. You may need additional steps such as granting execute permissions. |
|
|
86
|
+
|
|
87
|
+
## Bot Commands
|
|
88
|
+
|
|
89
|
+
| Command | Description |
|
|
90
|
+
| ----------------- | ------------------------------------------------------- |
|
|
91
|
+
| `/status` | Server health, current project, session, and model info |
|
|
92
|
+
| `/new` | Create a new session |
|
|
93
|
+
| `/stop` | Abort the current task |
|
|
94
|
+
| `/sessions` | Browse and switch between recent sessions |
|
|
95
|
+
| `/projects` | Switch between OpenCode projects |
|
|
96
|
+
| `/model` | Choose a model from your favorites |
|
|
97
|
+
| `/agent` | Switch agent mode (Plan / Build) |
|
|
98
|
+
| `/opencode_start` | Start the OpenCode server remotely |
|
|
99
|
+
| `/opencode_stop` | Stop the OpenCode server remotely |
|
|
100
|
+
| `/help` | Show available commands |
|
|
101
|
+
|
|
102
|
+
Any regular text message is sent as a prompt to the coding agent.
|
|
103
|
+
|
|
104
|
+
> `/opencode_start` and `/opencode_stop` are intended as emergency commands — for example, if you need to restart a stuck server while away from your computer. Under normal usage, start `opencode serve` yourself before launching the bot.
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
### Environment Variables
|
|
109
|
+
|
|
110
|
+
When installed via npm, the configuration wizard handles the initial setup. The `.env` file is stored in your platform's app data directory:
|
|
111
|
+
|
|
112
|
+
- **macOS:** `~/Library/Application Support/opencode-telegram-bot/.env`
|
|
113
|
+
- **Windows:** `%APPDATA%\opencode-telegram-bot\.env`
|
|
114
|
+
- **Linux:** `~/.config/opencode-telegram-bot/.env`
|
|
115
|
+
|
|
116
|
+
| Variable | Description | Required | Default |
|
|
117
|
+
| -------------------------- | -------------------------------------------- | :------: | ----------------------- |
|
|
118
|
+
| `TELEGRAM_BOT_TOKEN` | Bot token from @BotFather | Yes | — |
|
|
119
|
+
| `TELEGRAM_ALLOWED_USER_ID` | Your numeric Telegram user ID | Yes | — |
|
|
120
|
+
| `OPENCODE_API_URL` | OpenCode server URL | No | `http://localhost:4096` |
|
|
121
|
+
| `OPENCODE_SERVER_USERNAME` | Server auth username | No | `opencode` |
|
|
122
|
+
| `OPENCODE_SERVER_PASSWORD` | Server auth password | No | — |
|
|
123
|
+
| `OPENCODE_MODEL_PROVIDER` | Default model provider | Yes | `opencode` |
|
|
124
|
+
| `OPENCODE_MODEL_ID` | Default model ID | Yes | `big-pickle` |
|
|
125
|
+
| `BOT_LOCALE` | Bot UI language (`en` or `ru`) | No | `en` |
|
|
126
|
+
| `SESSIONS_LIST_LIMIT` | Max sessions shown in `/sessions` | No | `10` |
|
|
127
|
+
| `CODE_FILE_MAX_SIZE_KB` | Max file size (KB) to send as document | No | `100` |
|
|
128
|
+
| `LOG_LEVEL` | Log level (`debug`, `info`, `warn`, `error`) | No | `info` |
|
|
129
|
+
|
|
130
|
+
> **Keep your `.env` file private.** It contains your bot token. Never commit it to version control.
|
|
131
|
+
|
|
132
|
+
### Model Configuration
|
|
133
|
+
|
|
134
|
+
The bot picks up your **favorite models** from OpenCode. To add a model to favorites:
|
|
135
|
+
|
|
136
|
+
1. Open OpenCode TUI (`opencode`)
|
|
137
|
+
2. Go to model selection
|
|
138
|
+
3. Hover over the model you want and press **Ctrl+F** to add it to favorites
|
|
139
|
+
|
|
140
|
+
These favorites will appear in the `/model` command menu in Telegram.
|
|
141
|
+
|
|
142
|
+
A free model (`opencode/big-pickle`) is configured as the default fallback — if you haven't set up any favorites yet, the bot will use it automatically.
|
|
143
|
+
|
|
144
|
+
## Security
|
|
145
|
+
|
|
146
|
+
The bot enforces a strict **user ID whitelist**. Only the Telegram user whose numeric ID matches `TELEGRAM_ALLOWED_USER_ID` can interact with the bot. Messages from any other user are silently ignored and logged as unauthorized access attempts.
|
|
147
|
+
|
|
148
|
+
Since the bot runs locally on your machine and connects to your local OpenCode server, there is no external attack surface beyond the Telegram Bot API itself.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
### Running from Source
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
git clone https://github.com/grinev/opencode-telegram-bot.git
|
|
156
|
+
cd opencode-telegram-bot
|
|
157
|
+
npm install
|
|
158
|
+
cp .env.example .env
|
|
159
|
+
# Edit .env with your bot token, user ID, and model settings
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Build and run:
|
|
45
163
|
|
|
46
164
|
```bash
|
|
47
165
|
npm run dev
|
|
48
166
|
```
|
|
49
167
|
|
|
50
|
-
###
|
|
168
|
+
### Available Scripts
|
|
169
|
+
|
|
170
|
+
| Script | Description |
|
|
171
|
+
| ----------------------- | ----------------------------------- |
|
|
172
|
+
| `npm run dev` | Build and start (development) |
|
|
173
|
+
| `npm run build` | Compile TypeScript |
|
|
174
|
+
| `npm start` | Run compiled code |
|
|
175
|
+
| `npm run lint` | ESLint check (zero warnings policy) |
|
|
176
|
+
| `npm run format` | Format code with Prettier |
|
|
177
|
+
| `npm test` | Run tests (Vitest) |
|
|
178
|
+
| `npm run test:coverage` | Tests with coverage report |
|
|
179
|
+
|
|
180
|
+
> **Note:** No file watcher or auto-restart is used. The bot maintains persistent SSE and long-polling connections — automatic restarts would break them mid-task. After making changes, restart manually with `npm run dev`.
|
|
181
|
+
|
|
182
|
+
## Troubleshooting
|
|
183
|
+
|
|
184
|
+
**Bot doesn't respond to messages**
|
|
185
|
+
|
|
186
|
+
- Make sure `TELEGRAM_ALLOWED_USER_ID` matches your actual Telegram user ID (check with [@userinfobot](https://t.me/userinfobot))
|
|
187
|
+
- Verify the bot token is correct
|
|
188
|
+
|
|
189
|
+
**"OpenCode server is not available"**
|
|
190
|
+
|
|
191
|
+
- Ensure `opencode serve` is running in your project directory
|
|
192
|
+
- Check that `OPENCODE_API_URL` points to the correct address (default: `http://localhost:4096`)
|
|
193
|
+
|
|
194
|
+
**No models in `/model` menu**
|
|
51
195
|
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npm run build
|
|
55
|
-
```
|
|
56
|
-
2. Запустите собранный код:
|
|
57
|
-
```bash
|
|
58
|
-
npm start
|
|
59
|
-
```
|
|
196
|
+
- Add models to your OpenCode favorites: open OpenCode TUI, go to model selection, press **Ctrl+F** on desired models
|
|
60
197
|
|
|
61
|
-
|
|
198
|
+
**Linux: permission denied errors**
|
|
62
199
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
- `npm run test` — запуск тестов через Vitest.
|
|
66
|
-
- `npm run test:coverage` — запуск тестов с coverage-отчётом.
|
|
200
|
+
- Make sure the CLI binary has execute permission: `chmod +x $(which opencode-telegram)`
|
|
201
|
+
- Check that the config directory is writable: `~/.config/opencode-telegram-bot/`
|
|
67
202
|
|
|
68
|
-
##
|
|
203
|
+
## License
|
|
69
204
|
|
|
70
|
-
|
|
71
|
-
План технических улучшений (архитектура + тесты) — в [TECHNICAL_IMPROVEMENTS.md](./TECHNICAL_IMPROVEMENTS.md).
|
|
72
|
-
Инструкции для разработчиков и описание архитектуры — в [AGENTS.md](./AGENTS.md).
|
|
205
|
+
[MIT](LICENSE) © Ruslan Grinev
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { createBot } from "../bot/index.js";
|
|
2
3
|
import { config } from "../config.js";
|
|
3
4
|
import { loadSettings } from "../settings/manager.js";
|
|
4
5
|
import { processManager } from "../process/manager.js";
|
|
5
6
|
import { getRuntimeMode } from "../runtime/mode.js";
|
|
6
7
|
import { logger } from "../utils/logger.js";
|
|
8
|
+
async function getBotVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
11
|
+
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
12
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
13
|
+
return packageJson.version ?? "unknown";
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
logger.warn("[App] Failed to read bot version", error);
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
export async function startBotApp() {
|
|
8
21
|
const mode = getRuntimeMode();
|
|
9
|
-
|
|
22
|
+
const version = await getBotVersion();
|
|
23
|
+
logger.info(`Starting OpenCode Telegram Bot v${version}...`);
|
|
10
24
|
logger.info(`Allowed User ID: ${config.telegram.allowedUserId}`);
|
|
11
25
|
logger.debug(`[Runtime] Application start mode: ${mode}`);
|
|
12
26
|
await loadSettings();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { showAgentSelectionMenu } from "../handlers/agent.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Handler for /agent command
|
|
5
6
|
* Shows inline menu to select agent mode
|
|
@@ -11,6 +12,6 @@ export async function handleAgentCommand(ctx) {
|
|
|
11
12
|
}
|
|
12
13
|
catch (err) {
|
|
13
14
|
logger.error("[AgentCommand] Error showing agent menu:", err);
|
|
14
|
-
await ctx.reply("
|
|
15
|
+
await ctx.reply(t("error.load_agents"));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
* Centralized bot commands definitions
|
|
3
|
-
* Used for both Telegram API setMyCommands and command handler registration
|
|
4
|
-
*/
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
5
2
|
/**
|
|
6
3
|
* List of all bot commands
|
|
7
4
|
* Update this array when adding new commands
|
|
8
5
|
*/
|
|
9
|
-
|
|
10
|
-
{ command: "status",
|
|
11
|
-
{ command: "new",
|
|
12
|
-
{ command: "stop",
|
|
13
|
-
{ command: "sessions",
|
|
14
|
-
{ command: "projects",
|
|
15
|
-
{ command: "model",
|
|
16
|
-
{ command: "agent",
|
|
17
|
-
{ command: "opencode_start",
|
|
18
|
-
{ command: "opencode_stop",
|
|
19
|
-
{ command: "help",
|
|
6
|
+
const COMMAND_DEFINITIONS = [
|
|
7
|
+
{ command: "status", descriptionKey: "cmd.description.status" },
|
|
8
|
+
{ command: "new", descriptionKey: "cmd.description.new" },
|
|
9
|
+
{ command: "stop", descriptionKey: "cmd.description.stop" },
|
|
10
|
+
{ command: "sessions", descriptionKey: "cmd.description.sessions" },
|
|
11
|
+
{ command: "projects", descriptionKey: "cmd.description.projects" },
|
|
12
|
+
{ command: "model", descriptionKey: "cmd.description.model" },
|
|
13
|
+
{ command: "agent", descriptionKey: "cmd.description.agent" },
|
|
14
|
+
{ command: "opencode_start", descriptionKey: "cmd.description.opencode_start" },
|
|
15
|
+
{ command: "opencode_stop", descriptionKey: "cmd.description.opencode_stop" },
|
|
16
|
+
{ command: "help", descriptionKey: "cmd.description.help" },
|
|
20
17
|
];
|
|
18
|
+
export const BOT_COMMANDS = COMMAND_DEFINITIONS.map(({ command, descriptionKey }) => ({
|
|
19
|
+
command,
|
|
20
|
+
description: t(descriptionKey),
|
|
21
|
+
}));
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
1
2
|
export async function helpCommand(ctx) {
|
|
2
|
-
await ctx.reply("
|
|
3
|
-
"/status - Проверить статус сервера\n" +
|
|
4
|
-
"/sessions - Список сессий\n" +
|
|
5
|
-
"/new - Создать новую сессию\n" +
|
|
6
|
-
"/help - Справка", { parse_mode: "Markdown" });
|
|
3
|
+
await ctx.reply(t("help.text"), { parse_mode: "Markdown" });
|
|
7
4
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { showModelSelectionMenu } from "../handlers/model.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* Handler for /model command
|
|
5
6
|
* Shows inline menu to select model from favorites
|
|
@@ -11,6 +12,6 @@ export async function handleModelCommand(ctx) {
|
|
|
11
12
|
}
|
|
12
13
|
catch (err) {
|
|
13
14
|
logger.error("[ModelCommand] Error showing model menu:", err);
|
|
14
|
-
await ctx.reply("
|
|
15
|
+
await ctx.reply(t("error.load_models"));
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { t } from "../../i18n/index.js";
|
|
3
4
|
export async function modelsCommand(ctx) {
|
|
4
5
|
try {
|
|
5
6
|
const { data: providersData, error } = await opencodeClient.config.providers();
|
|
6
7
|
if (error || !providersData) {
|
|
7
|
-
await ctx.reply("
|
|
8
|
+
await ctx.reply(t("legacy.models.fetch_error"));
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
const providers = providersData.providers;
|
|
11
12
|
if (!providers || providers.length === 0) {
|
|
12
|
-
await ctx.reply("
|
|
13
|
+
await ctx.reply(t("legacy.models.empty"));
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
let message = "
|
|
16
|
+
let message = t("legacy.models.header");
|
|
16
17
|
for (const provider of providers) {
|
|
17
18
|
message += `🔹 **\`${provider.id}\`**\n`;
|
|
18
19
|
const models = Object.values(provider.models);
|
|
19
20
|
if (models.length === 0) {
|
|
20
|
-
message += "
|
|
21
|
+
message += t("legacy.models.no_provider_models");
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
24
|
for (const model of models) {
|
|
@@ -26,12 +27,12 @@ export async function modelsCommand(ctx) {
|
|
|
26
27
|
}
|
|
27
28
|
message += "\n";
|
|
28
29
|
}
|
|
29
|
-
message += "
|
|
30
|
+
message += t("legacy.models.env_hint");
|
|
30
31
|
message += "```\nOPENCODE_MODEL_PROVIDER=<provider.id>\nOPENCODE_MODEL_ID=<model.id>\n```";
|
|
31
32
|
await ctx.reply(message, { parse_mode: "Markdown" });
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
35
|
logger.error("[ModelsCommand] Error listing models:", error);
|
|
35
|
-
await ctx.reply("
|
|
36
|
+
await ctx.reply(t("legacy.models.error"));
|
|
36
37
|
}
|
|
37
38
|
}
|
package/dist/bot/commands/new.js
CHANGED
|
@@ -8,11 +8,12 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
8
8
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
9
9
|
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
|
+
import { t } from "../../i18n/index.js";
|
|
11
12
|
export async function newCommand(ctx) {
|
|
12
13
|
try {
|
|
13
14
|
const currentProject = getCurrentProject();
|
|
14
15
|
if (!currentProject) {
|
|
15
|
-
await ctx.reply("
|
|
16
|
+
await ctx.reply(t("new.project_not_selected"));
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
logger.debug("[Bot] Creating new session for directory:", currentProject.worktree);
|
|
@@ -47,12 +48,12 @@ export async function newCommand(ctx) {
|
|
|
47
48
|
const contextInfo = pinnedMessageManager.getContextInfo();
|
|
48
49
|
const variantName = formatVariantForButton(currentModel.variant || "default");
|
|
49
50
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo ?? undefined, variantName);
|
|
50
|
-
await ctx.reply(
|
|
51
|
+
await ctx.reply(t("new.created", { title: session.title }), {
|
|
51
52
|
reply_markup: keyboard,
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
56
|
logger.error("[Bot] Error creating session:", error);
|
|
56
|
-
await ctx.reply("
|
|
57
|
+
await ctx.reply(t("new.create_error"));
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { processManager } from "../../process/manager.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Wait for OpenCode server to become ready by polling health endpoint
|
|
6
7
|
* @param maxWaitMs Maximum time to wait in milliseconds
|
|
@@ -33,18 +34,19 @@ export async function opencodeStartCommand(ctx) {
|
|
|
33
34
|
if (processManager.isRunning()) {
|
|
34
35
|
const uptime = processManager.getUptime();
|
|
35
36
|
const uptimeStr = uptime ? Math.floor(uptime / 1000) : 0;
|
|
36
|
-
await ctx.reply(
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
await ctx.reply(t("opencode_start.already_running_managed", {
|
|
38
|
+
pid: processManager.getPID() ?? "-",
|
|
39
|
+
seconds: uptimeStr,
|
|
40
|
+
}));
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
// 2. Check if server is accessible (external process)
|
|
42
44
|
try {
|
|
43
45
|
const { data, error } = await opencodeClient.global.health();
|
|
44
46
|
if (!error && data?.healthy) {
|
|
45
|
-
await ctx.reply(
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
await ctx.reply(t("opencode_start.already_running_external", {
|
|
48
|
+
version: data.version || t("common.unknown"),
|
|
49
|
+
}));
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -52,36 +54,32 @@ export async function opencodeStartCommand(ctx) {
|
|
|
52
54
|
// Server not accessible, continue with start
|
|
53
55
|
}
|
|
54
56
|
// 3. Notify user that we're starting the server
|
|
55
|
-
const statusMessage = await ctx.reply("
|
|
57
|
+
const statusMessage = await ctx.reply(t("opencode_start.starting"));
|
|
56
58
|
// 4. Start the process
|
|
57
59
|
const { success, error } = await processManager.start();
|
|
58
60
|
if (!success) {
|
|
59
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
60
|
-
`Ошибка: ${error || "неизвестная ошибка"}\n\n` +
|
|
61
|
-
`Проверьте, что OpenCode CLI установлен и доступен в PATH:\n` +
|
|
62
|
-
`\`opencode --version\`\n` +
|
|
63
|
-
`\`npm install -g @opencode-ai/cli\``, { parse_mode: "Markdown" });
|
|
61
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.start_error", { error: error || t("common.unknown_error") }), { parse_mode: "Markdown" });
|
|
64
62
|
return;
|
|
65
63
|
}
|
|
66
64
|
// 5. Wait for server to become ready
|
|
67
65
|
logger.info("[Bot] Waiting for OpenCode server to become ready...");
|
|
68
66
|
const ready = await waitForServerReady(10000);
|
|
69
67
|
if (!ready) {
|
|
70
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.started_not_ready", {
|
|
69
|
+
pid: processManager.getPID() ?? "-",
|
|
70
|
+
}));
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
75
73
|
// 6. Get server version and send success message
|
|
76
74
|
const { data: health } = await opencodeClient.global.health();
|
|
77
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.success", {
|
|
76
|
+
pid: processManager.getPID() ?? "-",
|
|
77
|
+
version: health?.version || t("common.unknown"),
|
|
78
|
+
}));
|
|
80
79
|
logger.info(`[Bot] OpenCode server started successfully, PID=${processManager.getPID()}`);
|
|
81
80
|
}
|
|
82
81
|
catch (err) {
|
|
83
82
|
logger.error("[Bot] Error in /opencode-start command:", err);
|
|
84
|
-
await ctx.reply("
|
|
85
|
-
"Проверьте логи приложения для подробностей.");
|
|
83
|
+
await ctx.reply(t("opencode_start.error"));
|
|
86
84
|
}
|
|
87
85
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { opencodeClient } from "../../opencode/client.js";
|
|
2
2
|
import { processManager } from "../../process/manager.js";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Command handler for /opencode-stop
|
|
6
7
|
* Stops the OpenCode server process
|
|
@@ -13,34 +14,31 @@ export async function opencodeStopCommand(ctx) {
|
|
|
13
14
|
try {
|
|
14
15
|
const { data, error } = await opencodeClient.global.health();
|
|
15
16
|
if (!error && data?.healthy) {
|
|
16
|
-
await ctx.reply(
|
|
17
|
-
`Этот сервер не был запущен через /opencode-start.\n` +
|
|
18
|
-
`Остановите его вручную или используйте /status для проверки состояния.`);
|
|
17
|
+
await ctx.reply(t("opencode_stop.external_running"));
|
|
19
18
|
return;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
catch {
|
|
23
22
|
// Server not accessible
|
|
24
23
|
}
|
|
25
|
-
await ctx.reply("
|
|
24
|
+
await ctx.reply(t("opencode_stop.not_running"));
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
27
|
// 2. Notify user that we're stopping the server
|
|
29
28
|
const pid = processManager.getPID();
|
|
30
|
-
const statusMessage = await ctx.reply(
|
|
29
|
+
const statusMessage = await ctx.reply(t("opencode_stop.stopping", { pid: pid ?? "-" }));
|
|
31
30
|
// 3. Stop the process
|
|
32
31
|
const { success, error } = await processManager.stop(5000);
|
|
33
32
|
if (!success) {
|
|
34
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
33
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_stop.stop_error", { error: error || t("common.unknown_error") }));
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
36
|
// 4. Success - process has been stopped
|
|
38
|
-
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id,
|
|
37
|
+
await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_stop.success"));
|
|
39
38
|
logger.info("[Bot] OpenCode server stopped successfully");
|
|
40
39
|
}
|
|
41
40
|
catch (err) {
|
|
42
41
|
logger.error("[Bot] Error in /opencode-stop command:", err);
|
|
43
|
-
await ctx.reply("
|
|
44
|
-
"Проверьте логи приложения для подробностей.");
|
|
42
|
+
await ctx.reply(t("opencode_stop.error"));
|
|
45
43
|
}
|
|
46
44
|
}
|
|
@@ -10,6 +10,7 @@ import { getStoredModel } from "../../model/manager.js";
|
|
|
10
10
|
import { formatVariantForButton } from "../../variant/manager.js";
|
|
11
11
|
import { createMainKeyboard } from "../utils/keyboard.js";
|
|
12
12
|
import { logger } from "../../utils/logger.js";
|
|
13
|
+
import { t } from "../../i18n/index.js";
|
|
13
14
|
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
14
15
|
function formatProjectButtonLabel(label, isActive) {
|
|
15
16
|
const prefix = isActive ? "✅ " : "";
|
|
@@ -23,7 +24,7 @@ export async function projectsCommand(ctx) {
|
|
|
23
24
|
try {
|
|
24
25
|
const projects = await getProjects();
|
|
25
26
|
if (projects.length === 0) {
|
|
26
|
-
await ctx.reply("
|
|
27
|
+
await ctx.reply(t("projects.empty"));
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
const keyboard = new InlineKeyboard();
|
|
@@ -39,15 +40,17 @@ export async function projectsCommand(ctx) {
|
|
|
39
40
|
});
|
|
40
41
|
if (currentProject) {
|
|
41
42
|
const projectName = currentProject.name || currentProject.worktree;
|
|
42
|
-
await ctx.reply(
|
|
43
|
+
await ctx.reply(t("projects.select_with_current", { project: projectName }), {
|
|
44
|
+
reply_markup: keyboard,
|
|
45
|
+
});
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
|
-
await ctx.reply("
|
|
48
|
+
await ctx.reply(t("projects.select"), { reply_markup: keyboard });
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
catch (error) {
|
|
49
52
|
logger.error("[Bot] Error fetching projects:", error);
|
|
50
|
-
await ctx.reply("
|
|
53
|
+
await ctx.reply(t("projects.fetch_error"));
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
export async function handleProjectSelect(ctx) {
|
|
@@ -90,7 +93,7 @@ export async function handleProjectSelect(ctx) {
|
|
|
90
93
|
const keyboard = createMainKeyboard(currentAgent, currentModel, contextInfo, variantName);
|
|
91
94
|
const projectName = selectedProject.name || selectedProject.worktree;
|
|
92
95
|
await ctx.answerCallbackQuery();
|
|
93
|
-
await ctx.reply(
|
|
96
|
+
await ctx.reply(t("projects.selected", { project: projectName }), {
|
|
94
97
|
reply_markup: keyboard,
|
|
95
98
|
});
|
|
96
99
|
await ctx.deleteMessage();
|
|
@@ -98,7 +101,7 @@ export async function handleProjectSelect(ctx) {
|
|
|
98
101
|
catch (error) {
|
|
99
102
|
logger.error("[Bot] Error selecting project:", error);
|
|
100
103
|
await ctx.answerCallbackQuery();
|
|
101
|
-
await ctx.reply("
|
|
104
|
+
await ctx.reply(t("projects.select_error"));
|
|
102
105
|
}
|
|
103
106
|
return true;
|
|
104
107
|
}
|