@grinev/opencode-telegram-bot 0.10.1 → 0.11.1
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 +6 -2
- package/dist/app/start-bot-app.js +3 -0
- package/dist/bot/commands/commands.js +379 -0
- package/dist/bot/commands/definitions.js +1 -0
- package/dist/bot/commands/status.js +1 -2
- package/dist/bot/handlers/document.js +65 -0
- package/dist/bot/index.js +30 -9
- package/dist/bot/message-patterns.js +2 -1
- package/dist/bot/utils/file-download.js +16 -0
- package/dist/config.js +1 -1
- package/dist/i18n/de.js +27 -6
- package/dist/i18n/en.js +27 -6
- package/dist/i18n/es.js +27 -6
- package/dist/i18n/ru.js +34 -13
- package/dist/i18n/zh.js +27 -6
- package/dist/runtime/bootstrap.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,14 +26,17 @@ Languages: English (`en`), Deutsch (`de`), Español (`es`), Русский (`ru`
|
|
|
26
26
|
- **Live status** — pinned message with current project, model, context usage, and changed files list, updated in real time
|
|
27
27
|
- **Model switching** — pick models from OpenCode favorites and recent history directly in the chat (favorites are shown first)
|
|
28
28
|
- **Agent modes** — switch between Plan and Build modes on the fly
|
|
29
|
+
- **Custom Commands** — run OpenCode custom commands (and built-ins like `init`/`review`) from an inline menu with confirmation
|
|
29
30
|
- **Interactive Q&A** — answer agent questions and approve permissions via inline buttons
|
|
30
31
|
- **Voice prompts** — send voice/audio messages, transcribe them via a Whisper-compatible API, then forward recognized text to OpenCode
|
|
32
|
+
- **File attachments** — send images, PDF documents, and any text-based files to OpenCode (code, logs, configs etc.)
|
|
31
33
|
- **Context control** — compact context when it gets too large, right from the chat
|
|
32
34
|
- **Input flow control** — when an interactive flow is active, the bot accepts only relevant input to keep context consistent and avoid accidental actions
|
|
33
|
-
- **Configurable reply formatting** — assistant replies use Telegram MarkdownV2 by default, with optional raw mode (`MESSAGE_FORMAT_MODE=markdown|raw`)
|
|
34
35
|
- **Security** — strict user ID whitelist; no one else can access your bot, even if they find it
|
|
35
36
|
- **Localization** — UI localization is supported for multiple languages (`BOT_LOCALE`)
|
|
36
37
|
|
|
38
|
+
Planned features currently in development are listed in [Current Task List](PRODUCT.md#current-task-list).
|
|
39
|
+
|
|
37
40
|
## Prerequisites
|
|
38
41
|
|
|
39
42
|
- **Node.js 20+** — [download](https://nodejs.org)
|
|
@@ -70,7 +73,7 @@ npx @grinev/opencode-telegram-bot
|
|
|
70
73
|
|
|
71
74
|
> Quick start is for npm usage. You do not need to clone this repository. If you run this command from the source directory (repository root), it may fail with `opencode-telegram: not found`. To run from sources, use the [Development](#development) section.
|
|
72
75
|
|
|
73
|
-
On first launch, an interactive wizard will guide you through the configuration — it asks for interface language first, then your bot token, user ID, and OpenCode
|
|
76
|
+
On first launch, an interactive wizard will guide you through the configuration — it asks for interface language first, then your bot token, user ID, OpenCode API URL, and optional OpenCode server credentials (username/password). After that, you're ready to go. Open your bot in Telegram and start sending tasks.
|
|
74
77
|
|
|
75
78
|
#### Alternative: Global Install
|
|
76
79
|
|
|
@@ -103,6 +106,7 @@ opencode-telegram config
|
|
|
103
106
|
| `/sessions` | Browse and switch between recent sessions |
|
|
104
107
|
| `/projects` | Switch between OpenCode projects |
|
|
105
108
|
| `/rename` | Rename the current session |
|
|
109
|
+
| `/commands` | Browse and run custom commands |
|
|
106
110
|
| `/opencode_start` | Start the OpenCode server remotely |
|
|
107
111
|
| `/opencode_stop` | Stop the OpenCode server remotely |
|
|
108
112
|
| `/help` | Show available commands |
|
|
@@ -5,6 +5,7 @@ import { loadSettings } from "../settings/manager.js";
|
|
|
5
5
|
import { processManager } from "../process/manager.js";
|
|
6
6
|
import { warmupSessionDirectoryCache } from "../session/cache-manager.js";
|
|
7
7
|
import { getRuntimeMode } from "../runtime/mode.js";
|
|
8
|
+
import { getRuntimePaths } from "../runtime/paths.js";
|
|
8
9
|
import { logger } from "../utils/logger.js";
|
|
9
10
|
async function getBotVersion() {
|
|
10
11
|
try {
|
|
@@ -20,8 +21,10 @@ async function getBotVersion() {
|
|
|
20
21
|
}
|
|
21
22
|
export async function startBotApp() {
|
|
22
23
|
const mode = getRuntimeMode();
|
|
24
|
+
const runtimePaths = getRuntimePaths();
|
|
23
25
|
const version = await getBotVersion();
|
|
24
26
|
logger.info(`Starting OpenCode Telegram Bot v${version}...`);
|
|
27
|
+
logger.info(`Config loaded from ${runtimePaths.envFilePath}`);
|
|
25
28
|
logger.info(`Allowed User ID: ${config.telegram.allowedUserId}`);
|
|
26
29
|
logger.debug(`[Runtime] Application start mode: ${mode}`);
|
|
27
30
|
await loadSettings();
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { opencodeClient } from "../../opencode/client.js";
|
|
3
|
+
import { getCurrentProject } from "../../settings/manager.js";
|
|
4
|
+
import { clearSession, getCurrentSession, setCurrentSession, } from "../../session/manager.js";
|
|
5
|
+
import { ingestSessionInfoForCache } from "../../session/cache-manager.js";
|
|
6
|
+
import { interactionManager } from "../../interaction/manager.js";
|
|
7
|
+
import { summaryAggregator } from "../../summary/aggregator.js";
|
|
8
|
+
import { getStoredAgent } from "../../agent/manager.js";
|
|
9
|
+
import { getStoredModel } from "../../model/manager.js";
|
|
10
|
+
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
11
|
+
import { logger } from "../../utils/logger.js";
|
|
12
|
+
import { t } from "../../i18n/index.js";
|
|
13
|
+
const COMMANDS_CALLBACK_PREFIX = "commands:";
|
|
14
|
+
const COMMANDS_CALLBACK_SELECT_PREFIX = `${COMMANDS_CALLBACK_PREFIX}select:`;
|
|
15
|
+
const COMMANDS_CALLBACK_CANCEL = `${COMMANDS_CALLBACK_PREFIX}cancel`;
|
|
16
|
+
const COMMANDS_CALLBACK_EXECUTE = `${COMMANDS_CALLBACK_PREFIX}execute`;
|
|
17
|
+
const MAX_INLINE_BUTTON_LABEL_LENGTH = 64;
|
|
18
|
+
function formatExecutingCommandMessage(commandName, args) {
|
|
19
|
+
const commandText = `/${commandName}`;
|
|
20
|
+
const argsSuffix = args ? ` ${args}` : "";
|
|
21
|
+
return `${t("commands.executing_prefix")}\n${commandText}${argsSuffix}`;
|
|
22
|
+
}
|
|
23
|
+
function normalizeDirectoryForCommandApi(directory) {
|
|
24
|
+
return directory.replace(/\\/g, "/");
|
|
25
|
+
}
|
|
26
|
+
function getCallbackMessageId(ctx) {
|
|
27
|
+
const message = ctx.callbackQuery?.message;
|
|
28
|
+
if (!message || !("message_id" in message)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const messageId = message.message_id;
|
|
32
|
+
return typeof messageId === "number" ? messageId : null;
|
|
33
|
+
}
|
|
34
|
+
function formatCommandButtonLabel(command) {
|
|
35
|
+
const description = command.description?.trim() || t("commands.no_description");
|
|
36
|
+
const rawLabel = `/${command.name} - ${description}`;
|
|
37
|
+
if (rawLabel.length <= MAX_INLINE_BUTTON_LABEL_LENGTH) {
|
|
38
|
+
return rawLabel;
|
|
39
|
+
}
|
|
40
|
+
return `${rawLabel.slice(0, MAX_INLINE_BUTTON_LABEL_LENGTH - 3)}...`;
|
|
41
|
+
}
|
|
42
|
+
function buildCommandsListKeyboard(commands) {
|
|
43
|
+
const keyboard = new InlineKeyboard();
|
|
44
|
+
commands.forEach((command, index) => {
|
|
45
|
+
keyboard
|
|
46
|
+
.text(formatCommandButtonLabel(command), `${COMMANDS_CALLBACK_SELECT_PREFIX}${index}`)
|
|
47
|
+
.row();
|
|
48
|
+
});
|
|
49
|
+
keyboard.text(t("commands.button.cancel"), COMMANDS_CALLBACK_CANCEL);
|
|
50
|
+
return keyboard;
|
|
51
|
+
}
|
|
52
|
+
function buildCommandsConfirmKeyboard() {
|
|
53
|
+
return new InlineKeyboard()
|
|
54
|
+
.text(t("commands.button.execute"), COMMANDS_CALLBACK_EXECUTE)
|
|
55
|
+
.text(t("commands.button.cancel"), COMMANDS_CALLBACK_CANCEL);
|
|
56
|
+
}
|
|
57
|
+
function parseCommandItems(value) {
|
|
58
|
+
if (!Array.isArray(value)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const commands = [];
|
|
62
|
+
for (const item of value) {
|
|
63
|
+
if (!item || typeof item !== "object") {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const commandName = item.name;
|
|
67
|
+
if (typeof commandName !== "string" || !commandName.trim()) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const description = item.description;
|
|
71
|
+
commands.push({
|
|
72
|
+
name: commandName,
|
|
73
|
+
description: typeof description === "string" ? description : undefined,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return commands;
|
|
77
|
+
}
|
|
78
|
+
function parseCommandsMetadata(state) {
|
|
79
|
+
if (!state || state.kind !== "custom") {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const flow = state.metadata.flow;
|
|
83
|
+
const stage = state.metadata.stage;
|
|
84
|
+
const messageId = state.metadata.messageId;
|
|
85
|
+
const projectDirectory = state.metadata.projectDirectory;
|
|
86
|
+
if (flow !== "commands" ||
|
|
87
|
+
typeof messageId !== "number" ||
|
|
88
|
+
typeof projectDirectory !== "string") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (stage === "list") {
|
|
92
|
+
const commands = parseCommandItems(state.metadata.commands);
|
|
93
|
+
if (!commands) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
flow,
|
|
98
|
+
stage,
|
|
99
|
+
messageId,
|
|
100
|
+
projectDirectory,
|
|
101
|
+
commands,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (stage === "confirm") {
|
|
105
|
+
const commandName = state.metadata.commandName;
|
|
106
|
+
if (typeof commandName !== "string" || !commandName.trim()) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
flow,
|
|
111
|
+
stage,
|
|
112
|
+
messageId,
|
|
113
|
+
projectDirectory,
|
|
114
|
+
commandName,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function clearCommandsInteraction(reason) {
|
|
120
|
+
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
121
|
+
if (metadata) {
|
|
122
|
+
interactionManager.clear(reason);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function getCommandList(projectDirectory) {
|
|
126
|
+
const { data, error } = await opencodeClient.command.list({
|
|
127
|
+
directory: normalizeDirectoryForCommandApi(projectDirectory),
|
|
128
|
+
});
|
|
129
|
+
if (error || !data) {
|
|
130
|
+
throw error || new Error("No command data received");
|
|
131
|
+
}
|
|
132
|
+
return data
|
|
133
|
+
.filter((command) => typeof command.name === "string" && command.name.trim().length > 0)
|
|
134
|
+
.map((command) => ({
|
|
135
|
+
name: command.name,
|
|
136
|
+
description: command.description,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function parseSelectIndex(data) {
|
|
140
|
+
if (!data.startsWith(COMMANDS_CALLBACK_SELECT_PREFIX)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const rawIndex = data.slice(COMMANDS_CALLBACK_SELECT_PREFIX.length);
|
|
144
|
+
const index = Number(rawIndex);
|
|
145
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return index;
|
|
149
|
+
}
|
|
150
|
+
async function isSessionBusy(sessionId, directory) {
|
|
151
|
+
try {
|
|
152
|
+
const { data, error } = await opencodeClient.session.status({ directory });
|
|
153
|
+
if (error || !data) {
|
|
154
|
+
logger.warn("[Commands] Failed to check session status before command:", error);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const sessionStatus = data[sessionId];
|
|
158
|
+
if (!sessionStatus) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return sessionStatus.type === "busy";
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
logger.warn("[Commands] Error checking session status before command:", err);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function ensureSessionForProject(ctx, projectDirectory) {
|
|
169
|
+
let currentSession = getCurrentSession();
|
|
170
|
+
if (currentSession && currentSession.directory !== projectDirectory) {
|
|
171
|
+
logger.warn(`[Commands] Session/project mismatch detected. sessionDirectory=${currentSession.directory}, projectDirectory=${projectDirectory}. Resetting session context.`);
|
|
172
|
+
clearSession();
|
|
173
|
+
summaryAggregator.clear();
|
|
174
|
+
await ctx.reply(t("bot.session_reset_project_mismatch"));
|
|
175
|
+
currentSession = null;
|
|
176
|
+
}
|
|
177
|
+
if (currentSession) {
|
|
178
|
+
return currentSession;
|
|
179
|
+
}
|
|
180
|
+
await ctx.reply(t("bot.creating_session"));
|
|
181
|
+
const { data: session, error } = await opencodeClient.session.create({
|
|
182
|
+
directory: projectDirectory,
|
|
183
|
+
});
|
|
184
|
+
if (error || !session) {
|
|
185
|
+
await ctx.reply(t("bot.create_session_error"));
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const sessionInfo = {
|
|
189
|
+
id: session.id,
|
|
190
|
+
title: session.title,
|
|
191
|
+
directory: projectDirectory,
|
|
192
|
+
};
|
|
193
|
+
setCurrentSession(sessionInfo);
|
|
194
|
+
await ingestSessionInfoForCache(session);
|
|
195
|
+
await ctx.reply(t("bot.session_created", { title: session.title }));
|
|
196
|
+
return sessionInfo;
|
|
197
|
+
}
|
|
198
|
+
async function executeCommand(ctx, deps, params) {
|
|
199
|
+
if (!ctx.chat) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const args = params.argumentsText.trim();
|
|
203
|
+
await ctx.reply(formatExecutingCommandMessage(params.commandName, args));
|
|
204
|
+
const session = await ensureSessionForProject(ctx, params.projectDirectory);
|
|
205
|
+
if (!session) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
await deps.ensureEventSubscription(session.directory);
|
|
209
|
+
summaryAggregator.setSession(session.id);
|
|
210
|
+
summaryAggregator.setBotAndChatId(deps.bot, ctx.chat.id);
|
|
211
|
+
const sessionIsBusy = await isSessionBusy(session.id, session.directory);
|
|
212
|
+
if (sessionIsBusy) {
|
|
213
|
+
await ctx.reply(t("bot.session_busy"));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const currentAgent = getStoredAgent();
|
|
217
|
+
const storedModel = getStoredModel();
|
|
218
|
+
const model = storedModel.providerID && storedModel.modelID
|
|
219
|
+
? `${storedModel.providerID}/${storedModel.modelID}`
|
|
220
|
+
: undefined;
|
|
221
|
+
safeBackgroundTask({
|
|
222
|
+
taskName: "session.command",
|
|
223
|
+
task: () => opencodeClient.session.command({
|
|
224
|
+
sessionID: session.id,
|
|
225
|
+
directory: session.directory,
|
|
226
|
+
command: params.commandName,
|
|
227
|
+
arguments: args,
|
|
228
|
+
agent: currentAgent,
|
|
229
|
+
model,
|
|
230
|
+
variant: storedModel.variant,
|
|
231
|
+
}),
|
|
232
|
+
onSuccess: ({ error }) => {
|
|
233
|
+
if (error) {
|
|
234
|
+
logger.error("[Commands] OpenCode API returned an error for session.command", {
|
|
235
|
+
sessionId: session.id,
|
|
236
|
+
command: params.commandName,
|
|
237
|
+
args,
|
|
238
|
+
});
|
|
239
|
+
logger.error("[Commands] session.command error details:", error);
|
|
240
|
+
void ctx.api.sendMessage(ctx.chat.id, t("commands.execute_error")).catch(() => { });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
logger.info(`[Commands] session.command completed: session=${session.id}, command=/${params.commandName}`);
|
|
244
|
+
},
|
|
245
|
+
onError: (error) => {
|
|
246
|
+
logger.error("[Commands] session.command background task failed", {
|
|
247
|
+
sessionId: session.id,
|
|
248
|
+
command: params.commandName,
|
|
249
|
+
args,
|
|
250
|
+
});
|
|
251
|
+
logger.error("[Commands] session.command background failure details:", error);
|
|
252
|
+
void ctx.api.sendMessage(ctx.chat.id, t("commands.execute_error")).catch(() => { });
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
export async function commandsCommand(ctx) {
|
|
257
|
+
try {
|
|
258
|
+
const currentProject = getCurrentProject();
|
|
259
|
+
if (!currentProject) {
|
|
260
|
+
await ctx.reply(t("bot.project_not_selected"));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const commands = await getCommandList(currentProject.worktree);
|
|
264
|
+
if (commands.length === 0) {
|
|
265
|
+
await ctx.reply(t("commands.empty"));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const keyboard = buildCommandsListKeyboard(commands);
|
|
269
|
+
const message = await ctx.reply(t("commands.select"), {
|
|
270
|
+
reply_markup: keyboard,
|
|
271
|
+
});
|
|
272
|
+
interactionManager.start({
|
|
273
|
+
kind: "custom",
|
|
274
|
+
expectedInput: "callback",
|
|
275
|
+
metadata: {
|
|
276
|
+
flow: "commands",
|
|
277
|
+
stage: "list",
|
|
278
|
+
messageId: message.message_id,
|
|
279
|
+
projectDirectory: currentProject.worktree,
|
|
280
|
+
commands,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
logger.error("[Commands] Error fetching commands list:", error);
|
|
286
|
+
await ctx.reply(t("commands.fetch_error"));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export async function handleCommandsCallback(ctx, deps) {
|
|
290
|
+
const data = ctx.callbackQuery?.data;
|
|
291
|
+
if (!data || !data.startsWith(COMMANDS_CALLBACK_PREFIX)) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
295
|
+
const callbackMessageId = getCallbackMessageId(ctx);
|
|
296
|
+
if (!metadata || callbackMessageId === null || metadata.messageId !== callbackMessageId) {
|
|
297
|
+
await ctx.answerCallbackQuery({ text: t("commands.inactive_callback"), show_alert: true });
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
if (data === COMMANDS_CALLBACK_CANCEL) {
|
|
302
|
+
clearCommandsInteraction("commands_cancelled");
|
|
303
|
+
await ctx.answerCallbackQuery({ text: t("commands.cancelled_callback") });
|
|
304
|
+
await ctx.deleteMessage().catch(() => { });
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
if (data === COMMANDS_CALLBACK_EXECUTE) {
|
|
308
|
+
if (metadata.stage !== "confirm") {
|
|
309
|
+
await ctx.answerCallbackQuery({ text: t("commands.inactive_callback"), show_alert: true });
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
clearCommandsInteraction("commands_execute_clicked");
|
|
313
|
+
await ctx.answerCallbackQuery({ text: t("commands.execute_callback") });
|
|
314
|
+
await ctx.deleteMessage().catch(() => { });
|
|
315
|
+
await executeCommand(ctx, deps, {
|
|
316
|
+
projectDirectory: metadata.projectDirectory,
|
|
317
|
+
commandName: metadata.commandName,
|
|
318
|
+
argumentsText: "",
|
|
319
|
+
});
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
const commandIndex = parseSelectIndex(data);
|
|
323
|
+
if (commandIndex === null || metadata.stage !== "list") {
|
|
324
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error"), show_alert: true });
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
const selectedCommand = metadata.commands[commandIndex];
|
|
328
|
+
if (!selectedCommand) {
|
|
329
|
+
await ctx.answerCallbackQuery({ text: t("commands.inactive_callback"), show_alert: true });
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
await ctx.answerCallbackQuery();
|
|
333
|
+
await ctx.editMessageText(t("commands.confirm", { command: `/${selectedCommand.name}` }), {
|
|
334
|
+
reply_markup: buildCommandsConfirmKeyboard(),
|
|
335
|
+
});
|
|
336
|
+
interactionManager.transition({
|
|
337
|
+
expectedInput: "mixed",
|
|
338
|
+
metadata: {
|
|
339
|
+
flow: "commands",
|
|
340
|
+
stage: "confirm",
|
|
341
|
+
messageId: metadata.messageId,
|
|
342
|
+
projectDirectory: metadata.projectDirectory,
|
|
343
|
+
commandName: selectedCommand.name,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
logger.error("[Commands] Error handling command callback:", error);
|
|
350
|
+
clearCommandsInteraction("commands_callback_error");
|
|
351
|
+
await ctx.answerCallbackQuery({ text: t("callback.processing_error") }).catch(() => { });
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
export async function handleCommandTextArguments(ctx, deps) {
|
|
356
|
+
const text = ctx.message?.text;
|
|
357
|
+
if (!text || text.startsWith("/")) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const metadata = parseCommandsMetadata(interactionManager.getSnapshot());
|
|
361
|
+
if (!metadata || metadata.stage !== "confirm") {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
const argumentsText = text.trim();
|
|
365
|
+
if (!argumentsText) {
|
|
366
|
+
await ctx.reply(t("commands.arguments_empty"));
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
clearCommandsInteraction("commands_arguments_submitted");
|
|
370
|
+
if (ctx.chat) {
|
|
371
|
+
await ctx.api.deleteMessage(ctx.chat.id, metadata.messageId).catch(() => { });
|
|
372
|
+
}
|
|
373
|
+
await executeCommand(ctx, deps, {
|
|
374
|
+
projectDirectory: metadata.projectDirectory,
|
|
375
|
+
commandName: metadata.commandName,
|
|
376
|
+
argumentsText,
|
|
377
|
+
});
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
@@ -10,6 +10,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
10
10
|
{ command: "sessions", descriptionKey: "cmd.description.sessions" },
|
|
11
11
|
{ command: "projects", descriptionKey: "cmd.description.projects" },
|
|
12
12
|
{ command: "rename", descriptionKey: "cmd.description.rename" },
|
|
13
|
+
{ command: "commands", descriptionKey: "cmd.description.commands" },
|
|
13
14
|
{ command: "opencode_start", descriptionKey: "cmd.description.opencode_start" },
|
|
14
15
|
{ command: "opencode_stop", descriptionKey: "cmd.description.opencode_stop" },
|
|
15
16
|
{ command: "help", descriptionKey: "cmd.description.help" },
|
|
@@ -4,7 +4,6 @@ import { getCurrentProject } from "../../settings/manager.js";
|
|
|
4
4
|
import { fetchCurrentAgent } from "../../agent/manager.js";
|
|
5
5
|
import { getAgentDisplayName } from "../../agent/types.js";
|
|
6
6
|
import { fetchCurrentModel } from "../../model/manager.js";
|
|
7
|
-
import { formatModelForDisplay } from "../../model/types.js";
|
|
8
7
|
import { processManager } from "../../process/manager.js";
|
|
9
8
|
import { keyboardManager } from "../../keyboard/manager.js";
|
|
10
9
|
import { pinnedMessageManager } from "../../pinned/manager.js";
|
|
@@ -42,7 +41,7 @@ export async function statusCommand(ctx) {
|
|
|
42
41
|
message += `${t("status.line.mode", { mode: agentDisplay })}\n`;
|
|
43
42
|
// Add model information
|
|
44
43
|
const currentModel = fetchCurrentModel();
|
|
45
|
-
const modelDisplay =
|
|
44
|
+
const modelDisplay = `🤖 ${currentModel.providerID}/${currentModel.modelID}`;
|
|
46
45
|
message += `${t("status.line.model", { model: modelDisplay })}\n`;
|
|
47
46
|
const currentProject = getCurrentProject();
|
|
48
47
|
if (currentProject) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { config } from "../../config.js";
|
|
2
|
+
import { processUserPrompt } from "./prompt.js";
|
|
3
|
+
import { downloadTelegramFile, toDataUri, isTextMimeType, isFileSizeAllowed, } from "../utils/file-download.js";
|
|
4
|
+
import { getModelCapabilities, supportsInput } from "../../model/capabilities.js";
|
|
5
|
+
import { getStoredModel } from "../../model/manager.js";
|
|
6
|
+
import { logger } from "../../utils/logger.js";
|
|
7
|
+
import { t } from "../../i18n/index.js";
|
|
8
|
+
export async function handleDocumentMessage(ctx, deps) {
|
|
9
|
+
const downloadFile = deps.downloadFile ?? downloadTelegramFile;
|
|
10
|
+
const getCapabilities = deps.getModelCapabilities ?? getModelCapabilities;
|
|
11
|
+
const getStored = deps.getStoredModel ?? getStoredModel;
|
|
12
|
+
const processPrompt = deps.processPrompt ?? processUserPrompt;
|
|
13
|
+
const doc = ctx.message?.document;
|
|
14
|
+
if (!doc) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const caption = ctx.message.caption || "";
|
|
18
|
+
const mimeType = doc.mime_type || "";
|
|
19
|
+
const filename = doc.file_name || "document";
|
|
20
|
+
try {
|
|
21
|
+
if (isTextMimeType(mimeType)) {
|
|
22
|
+
if (!isFileSizeAllowed(doc.file_size, config.files.maxFileSizeKb)) {
|
|
23
|
+
logger.warn(`[Document] Text file too large: ${filename} (${doc.file_size} bytes > ${config.files.maxFileSizeKb}KB)`);
|
|
24
|
+
await ctx.reply(t("bot.text_file_too_large", { maxSizeKb: String(config.files.maxFileSizeKb) }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await ctx.reply(t("bot.file_downloading"));
|
|
28
|
+
const downloadedFile = await downloadFile(ctx.api, doc.file_id);
|
|
29
|
+
const textContent = downloadedFile.buffer.toString("utf-8");
|
|
30
|
+
const promptWithFile = `--- Content of ${filename} ---\n${textContent}\n--- End of file ---\n\n${caption}`;
|
|
31
|
+
logger.info(`[Document] Sending text file (${downloadedFile.buffer.length} bytes, ${filename}) as prompt`);
|
|
32
|
+
await processPrompt(ctx, promptWithFile, deps);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (mimeType === "application/pdf") {
|
|
36
|
+
const storedModel = getStored();
|
|
37
|
+
const capabilities = await getCapabilities(storedModel.providerID, storedModel.modelID);
|
|
38
|
+
if (!supportsInput(capabilities, "pdf")) {
|
|
39
|
+
logger.warn(`[Document] Model ${storedModel.providerID}/${storedModel.modelID} doesn't support PDF input`);
|
|
40
|
+
await ctx.reply(t("bot.model_no_pdf"));
|
|
41
|
+
if (caption.trim().length > 0) {
|
|
42
|
+
await processPrompt(ctx, caption, deps);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await ctx.reply(t("bot.file_downloading"));
|
|
47
|
+
const downloadedFile = await downloadFile(ctx.api, doc.file_id);
|
|
48
|
+
const dataUri = toDataUri(downloadedFile.buffer, mimeType);
|
|
49
|
+
const filePart = {
|
|
50
|
+
type: "file",
|
|
51
|
+
mime: mimeType,
|
|
52
|
+
filename: filename,
|
|
53
|
+
url: dataUri,
|
|
54
|
+
};
|
|
55
|
+
logger.info(`[Document] Sending PDF (${downloadedFile.buffer.length} bytes, ${filename}) with prompt`);
|
|
56
|
+
await processPrompt(ctx, caption, deps, [filePart]);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
logger.debug(`[Document] Unsupported document MIME type: ${mimeType}, ignoring`);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error("[Document] Error handling document message:", err);
|
|
63
|
+
await ctx.reply(t("bot.file_download_error"));
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/bot/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { BOT_COMMANDS } from "./commands/definitions.js";
|
|
|
12
12
|
import { startCommand } from "./commands/start.js";
|
|
13
13
|
import { helpCommand } from "./commands/help.js";
|
|
14
14
|
import { statusCommand } from "./commands/status.js";
|
|
15
|
-
import { MODEL_BUTTON_TEXT_PATTERN, VARIANT_BUTTON_TEXT_PATTERN } from "./message-patterns.js";
|
|
15
|
+
import { AGENT_MODE_BUTTON_TEXT_PATTERN, MODEL_BUTTON_TEXT_PATTERN, VARIANT_BUTTON_TEXT_PATTERN, } from "./message-patterns.js";
|
|
16
16
|
import { sessionsCommand, handleSessionSelect } from "./commands/sessions.js";
|
|
17
17
|
import { newCommand } from "./commands/new.js";
|
|
18
18
|
import { projectsCommand, handleProjectSelect } from "./commands/projects.js";
|
|
@@ -20,6 +20,7 @@ import { stopCommand } from "./commands/stop.js";
|
|
|
20
20
|
import { opencodeStartCommand } from "./commands/opencode-start.js";
|
|
21
21
|
import { opencodeStopCommand } from "./commands/opencode-stop.js";
|
|
22
22
|
import { renameCommand, handleRenameCancel, handleRenameTextAnswer } from "./commands/rename.js";
|
|
23
|
+
import { commandsCommand, handleCommandsCallback, handleCommandTextArguments, } from "./commands/commands.js";
|
|
23
24
|
import { handleQuestionCallback, showCurrentQuestion, handleQuestionTextAnswer, } from "./handlers/question.js";
|
|
24
25
|
import { handlePermissionCallback, showPermissionRequest } from "./handlers/permission.js";
|
|
25
26
|
import { handleAgentSelect, showAgentSelectionMenu } from "./handlers/agent.js";
|
|
@@ -43,6 +44,7 @@ import { pinnedMessageManager } from "../pinned/manager.js";
|
|
|
43
44
|
import { t } from "../i18n/index.js";
|
|
44
45
|
import { processUserPrompt } from "./handlers/prompt.js";
|
|
45
46
|
import { handleVoiceMessage } from "./handlers/voice.js";
|
|
47
|
+
import { handleDocumentMessage } from "./handlers/document.js";
|
|
46
48
|
import { downloadTelegramFile, toDataUri } from "./utils/file-download.js";
|
|
47
49
|
import { sendMessageWithMarkdownFallback } from "./utils/send-with-markdown-fallback.js";
|
|
48
50
|
import { getModelCapabilities, supportsInput } from "../model/capabilities.js";
|
|
@@ -120,7 +122,7 @@ async function ensureCommandsInitialized(ctx, next) {
|
|
|
120
122
|
},
|
|
121
123
|
});
|
|
122
124
|
commandsInitialized = true;
|
|
123
|
-
logger.
|
|
125
|
+
logger.debug(`[Bot] Commands initialized for authorized user (chat_id=${ctx.chat.id})`);
|
|
124
126
|
}
|
|
125
127
|
catch (err) {
|
|
126
128
|
logger.error("[Bot] Failed to set commands:", err);
|
|
@@ -383,7 +385,7 @@ async function ensureEventSubscription(directory) {
|
|
|
383
385
|
export function createBot() {
|
|
384
386
|
clearAllInteractionState("bot_startup");
|
|
385
387
|
toolMessageBatcher.setIntervalSeconds(config.bot.serviceMessagesIntervalSec);
|
|
386
|
-
logger.
|
|
388
|
+
logger.debug(`[ToolBatcher] Service messages interval: ${config.bot.serviceMessagesIntervalSec}s`);
|
|
387
389
|
const botOptions = {};
|
|
388
390
|
if (config.telegram.proxyUrl) {
|
|
389
391
|
const proxyUrl = config.telegram.proxyUrl;
|
|
@@ -456,10 +458,15 @@ export function createBot() {
|
|
|
456
458
|
bot.command("new", newCommand);
|
|
457
459
|
bot.command("stop", stopCommand);
|
|
458
460
|
bot.command("rename", renameCommand);
|
|
461
|
+
bot.command("commands", commandsCommand);
|
|
459
462
|
bot.on("message:text", unknownCommandMiddleware);
|
|
460
463
|
bot.on("callback_query:data", async (ctx) => {
|
|
461
464
|
logger.debug(`[Bot] Received callback_query:data: ${ctx.callbackQuery?.data}`);
|
|
462
465
|
logger.debug(`[Bot] Callback context: from=${ctx.from?.id}, chat=${ctx.chat?.id}`);
|
|
466
|
+
if (ctx.chat) {
|
|
467
|
+
botInstance = bot;
|
|
468
|
+
chatIdInstance = ctx.chat.id;
|
|
469
|
+
}
|
|
463
470
|
try {
|
|
464
471
|
const handledInlineCancel = await handleInlineMenuCancel(ctx);
|
|
465
472
|
const handledSession = await handleSessionSelect(ctx);
|
|
@@ -471,7 +478,8 @@ export function createBot() {
|
|
|
471
478
|
const handledVariant = await handleVariantSelect(ctx);
|
|
472
479
|
const handledCompactConfirm = await handleCompactConfirm(ctx);
|
|
473
480
|
const handledRenameCancel = await handleRenameCancel(ctx);
|
|
474
|
-
|
|
481
|
+
const handledCommands = await handleCommandsCallback(ctx, { bot, ensureEventSubscription });
|
|
482
|
+
logger.debug(`[Bot] Callback handled: inlineCancel=${handledInlineCancel}, session=${handledSession}, project=${handledProject}, question=${handledQuestion}, permission=${handledPermission}, agent=${handledAgent}, model=${handledModel}, variant=${handledVariant}, compactConfirm=${handledCompactConfirm}, rename=${handledRenameCancel}, commands=${handledCommands}`);
|
|
475
483
|
if (!handledInlineCancel &&
|
|
476
484
|
!handledSession &&
|
|
477
485
|
!handledProject &&
|
|
@@ -481,7 +489,8 @@ export function createBot() {
|
|
|
481
489
|
!handledModel &&
|
|
482
490
|
!handledVariant &&
|
|
483
491
|
!handledCompactConfirm &&
|
|
484
|
-
!handledRenameCancel
|
|
492
|
+
!handledRenameCancel &&
|
|
493
|
+
!handledCommands) {
|
|
485
494
|
logger.debug("Unknown callback query:", ctx.callbackQuery?.data);
|
|
486
495
|
await ctx.answerCallbackQuery({ text: t("callback.unknown_command") });
|
|
487
496
|
}
|
|
@@ -493,7 +502,7 @@ export function createBot() {
|
|
|
493
502
|
}
|
|
494
503
|
});
|
|
495
504
|
// Handle Reply Keyboard button press (agent mode indicator)
|
|
496
|
-
bot.hears(
|
|
505
|
+
bot.hears(AGENT_MODE_BUTTON_TEXT_PATTERN, async (ctx) => {
|
|
497
506
|
logger.debug(`[Bot] Agent mode button pressed: ${ctx.message?.text}`);
|
|
498
507
|
try {
|
|
499
508
|
if (await blockMenuWhileInteractionActive(ctx)) {
|
|
@@ -575,7 +584,7 @@ export function createBot() {
|
|
|
575
584
|
},
|
|
576
585
|
onSuccess: (result) => {
|
|
577
586
|
if (result.success) {
|
|
578
|
-
logger.
|
|
587
|
+
logger.debug("[Bot] Cleared global commands (default and all_private_chats scopes)");
|
|
579
588
|
return;
|
|
580
589
|
}
|
|
581
590
|
logger.warn("[Bot] Could not clear global commands:", result.error);
|
|
@@ -645,11 +654,21 @@ export function createBot() {
|
|
|
645
654
|
await ctx.reply(t("bot.photo_download_error"));
|
|
646
655
|
}
|
|
647
656
|
});
|
|
657
|
+
// Document message handler (PDF and text files)
|
|
658
|
+
bot.on("message:document", async (ctx) => {
|
|
659
|
+
logger.debug(`[Bot] Received document message, chatId=${ctx.chat.id}`);
|
|
660
|
+
botInstance = bot;
|
|
661
|
+
chatIdInstance = ctx.chat.id;
|
|
662
|
+
const deps = { bot, ensureEventSubscription };
|
|
663
|
+
await handleDocumentMessage(ctx, deps);
|
|
664
|
+
});
|
|
648
665
|
bot.on("message:text", async (ctx) => {
|
|
649
666
|
const text = ctx.message?.text;
|
|
650
667
|
if (!text) {
|
|
651
668
|
return;
|
|
652
669
|
}
|
|
670
|
+
botInstance = bot;
|
|
671
|
+
chatIdInstance = ctx.chat.id;
|
|
653
672
|
if (text.startsWith("/")) {
|
|
654
673
|
return;
|
|
655
674
|
}
|
|
@@ -661,9 +680,11 @@ export function createBot() {
|
|
|
661
680
|
if (handledRename) {
|
|
662
681
|
return;
|
|
663
682
|
}
|
|
664
|
-
botInstance = bot;
|
|
665
|
-
chatIdInstance = ctx.chat.id;
|
|
666
683
|
const promptDeps = { bot, ensureEventSubscription };
|
|
684
|
+
const handledCommandArgs = await handleCommandTextArguments(ctx, promptDeps);
|
|
685
|
+
if (handledCommandArgs) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
667
688
|
await processUserPrompt(ctx, text, promptDeps);
|
|
668
689
|
logger.debug("[Bot] message:text handler completed (prompt sent in background)");
|
|
669
690
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const AGENT_MODE_BUTTON_TEXT_PATTERN = /^(📋|🛠️|💬|🔍|📝|📄|📦|🤖)\s.+\sMode$/;
|
|
2
|
+
export const MODEL_BUTTON_TEXT_PATTERN = /^🤖\s(?!.*\sMode$)[\s\S]+$/;
|
|
2
3
|
// Keep support for both legacy "💭" and current "💡" prefix.
|
|
3
4
|
export const VARIANT_BUTTON_TEXT_PATTERN = /^(💡|💭)\s.+$/;
|
|
@@ -73,3 +73,19 @@ export function formatFileSize(bytes) {
|
|
|
73
73
|
}
|
|
74
74
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
75
75
|
}
|
|
76
|
+
const APPLICATION_TEXT_MIME_TYPES = new Set([
|
|
77
|
+
"application/json",
|
|
78
|
+
"application/xml",
|
|
79
|
+
"application/javascript",
|
|
80
|
+
"application/x-yaml",
|
|
81
|
+
"application/sql",
|
|
82
|
+
]);
|
|
83
|
+
export function isTextMimeType(mimeType) {
|
|
84
|
+
if (!mimeType) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (mimeType.startsWith("text/")) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return APPLICATION_TEXT_MIME_TYPES.has(mimeType);
|
|
91
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import dotenv from "dotenv";
|
|
|
2
2
|
import { getRuntimePaths } from "./runtime/paths.js";
|
|
3
3
|
import { normalizeLocale } from "./i18n/index.js";
|
|
4
4
|
const runtimePaths = getRuntimePaths();
|
|
5
|
-
dotenv.config({ path: runtimePaths.envFilePath });
|
|
5
|
+
dotenv.config({ path: runtimePaths.envFilePath, quiet: true });
|
|
6
6
|
function getEnvVar(key, required = true) {
|
|
7
7
|
const value = process.env[key];
|
|
8
8
|
if (required && !value) {
|
package/dist/i18n/de.js
CHANGED
|
@@ -4,6 +4,7 @@ export const de = {
|
|
|
4
4
|
"cmd.description.stop": "Aktuelle Aktion stoppen",
|
|
5
5
|
"cmd.description.sessions": "Sitzungen auflisten",
|
|
6
6
|
"cmd.description.projects": "Projekte auflisten",
|
|
7
|
+
"cmd.description.commands": "Benutzerdefinierte Befehle",
|
|
7
8
|
"cmd.description.opencode_start": "OpenCode-Server starten",
|
|
8
9
|
"cmd.description.opencode_stop": "OpenCode-Server stoppen",
|
|
9
10
|
"cmd.description.help": "Hilfe",
|
|
@@ -48,23 +49,28 @@ export const de = {
|
|
|
48
49
|
"bot.photo_model_no_image": "⚠️ Das aktuelle Modell unterstützt keine Bildeingabe. Sende nur Text.",
|
|
49
50
|
"bot.photo_download_error": "🔴 Foto konnte nicht heruntergeladen werden",
|
|
50
51
|
"bot.photo_no_caption": "💡 Tipp: Füge eine Bildunterschrift hinzu, um zu beschreiben, was du mit diesem Foto tun möchtest.",
|
|
52
|
+
"bot.file_downloading": "⏳ Lade Datei herunter...",
|
|
53
|
+
"bot.file_too_large": "⚠️ Datei ist zu groß (max. {maxSizeMb}MB)",
|
|
54
|
+
"bot.file_download_error": "🔴 Datei konnte nicht heruntergeladen werden",
|
|
55
|
+
"bot.model_no_pdf": "⚠️ Das aktuelle Modell unterstützt keine PDF-Eingabe. Sende nur Text.",
|
|
56
|
+
"bot.text_file_too_large": "⚠️ Textdatei ist zu groß (max. {maxSizeKb}KB)",
|
|
51
57
|
"status.header_running": "🟢 **OpenCode-Server läuft**",
|
|
52
58
|
"status.health.healthy": "OK",
|
|
53
59
|
"status.health.unhealthy": "Nicht OK",
|
|
54
60
|
"status.line.health": "Status: {health}",
|
|
55
61
|
"status.line.version": "Version: {version}",
|
|
56
|
-
"status.line.managed_yes": "Vom Bot
|
|
57
|
-
"status.line.managed_no": "Vom Bot
|
|
62
|
+
"status.line.managed_yes": "Vom Bot gestartet: Ja",
|
|
63
|
+
"status.line.managed_no": "Vom Bot gestartet: Nein",
|
|
58
64
|
"status.line.pid": "PID: {pid}",
|
|
59
65
|
"status.line.uptime_sec": "Betriebszeit: {seconds} s",
|
|
60
66
|
"status.line.mode": "Modus: {mode}",
|
|
61
67
|
"status.line.model": "Modell: {model}",
|
|
62
68
|
"status.agent_not_set": "nicht gesetzt",
|
|
63
|
-
"status.project_selected": "
|
|
64
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Projekt: {project}",
|
|
70
|
+
"status.project_not_selected": "Projekt: nicht ausgewählt",
|
|
65
71
|
"status.project_hint": "Nutze /projects, um ein Projekt auszuwahlen",
|
|
66
|
-
"status.session_selected": "
|
|
67
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Aktuelle Sitzung: {title}",
|
|
73
|
+
"status.session_not_selected": "Aktuelle Sitzung: nicht ausgewählt",
|
|
68
74
|
"status.session_hint": "Nutze /sessions zur Auswahl oder /new zum Erstellen",
|
|
69
75
|
"status.server_unavailable": "🔴 OpenCode-Server ist nicht verfügbar\n\nNutze /opencode_start, um den Server zu starten.",
|
|
70
76
|
"projects.empty": "📭 Keine Projekte gefunden.\n\nÖffne ein Verzeichnis in OpenCode und erstelle mindestens eine Sitzung, dann erscheint es hier.",
|
|
@@ -222,6 +228,8 @@ export const de = {
|
|
|
222
228
|
"runtime.wizard.ask_user_id": "Gib deine Telegram User ID ein (du bekommst sie bei @userinfobot).\n> ",
|
|
223
229
|
"runtime.wizard.user_id_invalid": "Gib eine positive ganze Zahl ein (> 0).\n",
|
|
224
230
|
"runtime.wizard.ask_api_url": "OpenCode API URL eingeben (optional).\nEnter drücken für Standard: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "OpenCode-Server-Benutzername eingeben (optional).\nEnter drücken für Standard: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "OpenCode-Server-Passwort eingeben (optional).\nEnter drücken, um es leer zu lassen.\n> ",
|
|
225
233
|
"runtime.wizard.api_url_invalid": "Gib eine gültige URL (http/https) ein oder drücke Enter für Standard.\n",
|
|
226
234
|
"runtime.wizard.start": "OpenCode Telegram Bot Einrichtung.\n",
|
|
227
235
|
"runtime.wizard.saved": "Konfiguration gespeichert:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -238,6 +246,19 @@ export const de = {
|
|
|
238
246
|
"rename.blocked.expected_name": "⚠️ Sende den neuen Sitzungsnamen als Text oder tippe in der Umbenennen-Nachricht auf Abbrechen.",
|
|
239
247
|
"rename.blocked.command_not_allowed": "⚠️ Dieser Befehl ist nicht verfügbar, solange beim Umbenennen auf einen neuen Namen gewartet wird.",
|
|
240
248
|
"rename.button.cancel": "❌ Abbrechen",
|
|
249
|
+
"commands.select": "Wähle einen OpenCode-Befehl:",
|
|
250
|
+
"commands.empty": "📭 Für dieses Projekt sind keine OpenCode-Befehle verfügbar.",
|
|
251
|
+
"commands.fetch_error": "🔴 OpenCode-Befehle konnten nicht geladen werden.",
|
|
252
|
+
"commands.no_description": "Keine Beschreibung",
|
|
253
|
+
"commands.button.execute": "✅ Ausführen",
|
|
254
|
+
"commands.button.cancel": "❌ Abbrechen",
|
|
255
|
+
"commands.confirm": "Bestätige die Ausführung des Befehls {command}. Für die Ausführung mit Argumenten sende die Argumente als Nachricht.",
|
|
256
|
+
"commands.inactive_callback": "Dieses Befehlsmenü ist inaktiv",
|
|
257
|
+
"commands.cancelled_callback": "Abgebrochen",
|
|
258
|
+
"commands.execute_callback": "Befehl wird ausgeführt...",
|
|
259
|
+
"commands.executing_prefix": "⚡ Befehl wird ausgeführt:",
|
|
260
|
+
"commands.arguments_empty": "⚠️ Argumente dürfen nicht leer sein. Sende Text oder tippe auf Ausführen.",
|
|
261
|
+
"commands.execute_error": "🔴 OpenCode-Befehl konnte nicht ausgeführt werden.",
|
|
241
262
|
"cmd.description.rename": "Aktuelle Sitzung umbenennen",
|
|
242
263
|
"cli.usage": "Verwendung:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nHinweise:\n - Ohne Befehl wird standardmäßig `start` verwendet\n - `--mode` wird derzeit nur für `start` unterstützt",
|
|
243
264
|
"cli.placeholder.status": "Befehl `status` ist derzeit ein Platzhalter. Echte Statusprüfungen werden in der Service-Schicht hinzugefügt (Phase 5).",
|
package/dist/i18n/en.js
CHANGED
|
@@ -4,6 +4,7 @@ export const en = {
|
|
|
4
4
|
"cmd.description.stop": "Stop current action",
|
|
5
5
|
"cmd.description.sessions": "List sessions",
|
|
6
6
|
"cmd.description.projects": "List projects",
|
|
7
|
+
"cmd.description.commands": "Custom commands",
|
|
7
8
|
"cmd.description.opencode_start": "Start OpenCode server",
|
|
8
9
|
"cmd.description.opencode_stop": "Stop OpenCode server",
|
|
9
10
|
"cmd.description.help": "Help",
|
|
@@ -48,23 +49,28 @@ export const en = {
|
|
|
48
49
|
"bot.photo_model_no_image": "⚠️ Current model doesn't support image input. Sending text only.",
|
|
49
50
|
"bot.photo_download_error": "🔴 Failed to download photo",
|
|
50
51
|
"bot.photo_no_caption": "💡 Tip: Add a caption to describe what you want to do with this photo.",
|
|
52
|
+
"bot.file_downloading": "⏳ Downloading file...",
|
|
53
|
+
"bot.file_too_large": "⚠️ File is too large (max {maxSizeMb}MB)",
|
|
54
|
+
"bot.file_download_error": "🔴 Failed to download file",
|
|
55
|
+
"bot.model_no_pdf": "⚠️ Current model doesn't support PDF input. Sending text only.",
|
|
56
|
+
"bot.text_file_too_large": "⚠️ Text file is too large (max {maxSizeKb}KB)",
|
|
51
57
|
"status.header_running": "🟢 **OpenCode Server is running**",
|
|
52
58
|
"status.health.healthy": "Healthy",
|
|
53
59
|
"status.health.unhealthy": "Unhealthy",
|
|
54
60
|
"status.line.health": "Status: {health}",
|
|
55
61
|
"status.line.version": "Version: {version}",
|
|
56
|
-
"status.line.managed_yes": "
|
|
57
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Started by bot: Yes",
|
|
63
|
+
"status.line.managed_no": "Started by bot: No",
|
|
58
64
|
"status.line.pid": "PID: {pid}",
|
|
59
65
|
"status.line.uptime_sec": "Uptime: {seconds} sec",
|
|
60
66
|
"status.line.mode": "Mode: {mode}",
|
|
61
67
|
"status.line.model": "Model: {model}",
|
|
62
68
|
"status.agent_not_set": "not set",
|
|
63
|
-
"status.project_selected": "
|
|
64
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Project: {project}",
|
|
70
|
+
"status.project_not_selected": "Project: not selected",
|
|
65
71
|
"status.project_hint": "Use /projects to select a project",
|
|
66
|
-
"status.session_selected": "
|
|
67
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Current session: {title}",
|
|
73
|
+
"status.session_not_selected": "Current session: not selected",
|
|
68
74
|
"status.session_hint": "Use /sessions to select one or /new to create one",
|
|
69
75
|
"status.server_unavailable": "🔴 OpenCode Server is unavailable\n\nUse /opencode_start to start the server.",
|
|
70
76
|
"projects.empty": "📭 No projects found.\n\nOpen a directory in OpenCode and create at least one session, then it will appear here.",
|
|
@@ -222,6 +228,8 @@ export const en = {
|
|
|
222
228
|
"runtime.wizard.ask_user_id": "Enter your Telegram User ID (you can get it from @userinfobot).\n> ",
|
|
223
229
|
"runtime.wizard.user_id_invalid": "Enter a positive integer (> 0).\n",
|
|
224
230
|
"runtime.wizard.ask_api_url": "Enter OpenCode API URL (optional).\nPress Enter to use default: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Enter OpenCode server username (optional).\nPress Enter to use default: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Enter OpenCode server password (optional).\nPress Enter to keep it empty.\n> ",
|
|
225
233
|
"runtime.wizard.api_url_invalid": "Enter a valid URL (http/https) or press Enter for default.\n",
|
|
226
234
|
"runtime.wizard.start": "OpenCode Telegram Bot setup.\n",
|
|
227
235
|
"runtime.wizard.saved": "Configuration saved:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -238,6 +246,19 @@ export const en = {
|
|
|
238
246
|
"rename.blocked.expected_name": "⚠️ Enter a new session name as text or tap Cancel in rename message.",
|
|
239
247
|
"rename.blocked.command_not_allowed": "⚠️ This command is not available while rename is waiting for a new name.",
|
|
240
248
|
"rename.button.cancel": "❌ Cancel",
|
|
249
|
+
"commands.select": "Choose an OpenCode command:",
|
|
250
|
+
"commands.empty": "📭 No OpenCode commands are available for this project.",
|
|
251
|
+
"commands.fetch_error": "🔴 Failed to load OpenCode commands.",
|
|
252
|
+
"commands.no_description": "No description",
|
|
253
|
+
"commands.button.execute": "✅ Execute",
|
|
254
|
+
"commands.button.cancel": "❌ Cancel",
|
|
255
|
+
"commands.confirm": "Confirm execution of command {command}. To run it with arguments, send the arguments as a message.",
|
|
256
|
+
"commands.inactive_callback": "This command menu is inactive",
|
|
257
|
+
"commands.cancelled_callback": "Cancelled",
|
|
258
|
+
"commands.execute_callback": "Executing command...",
|
|
259
|
+
"commands.executing_prefix": "⚡ Executing command:",
|
|
260
|
+
"commands.arguments_empty": "⚠️ Arguments cannot be empty. Send text or tap Execute.",
|
|
261
|
+
"commands.execute_error": "🔴 Failed to execute OpenCode command.",
|
|
241
262
|
"cmd.description.rename": "Rename current session",
|
|
242
263
|
"cli.usage": "Usage:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotes:\n - No command defaults to `start`\n - `--mode` is currently supported for `start` only",
|
|
243
264
|
"cli.placeholder.status": "Command `status` is currently a placeholder. Real status checks will be added in service layer (Phase 5).",
|
package/dist/i18n/es.js
CHANGED
|
@@ -4,6 +4,7 @@ export const es = {
|
|
|
4
4
|
"cmd.description.stop": "Detener la acción actual",
|
|
5
5
|
"cmd.description.sessions": "Listar sesiones",
|
|
6
6
|
"cmd.description.projects": "Listar proyectos",
|
|
7
|
+
"cmd.description.commands": "Comandos personalizados",
|
|
7
8
|
"cmd.description.opencode_start": "Iniciar servidor OpenCode",
|
|
8
9
|
"cmd.description.opencode_stop": "Detener servidor OpenCode",
|
|
9
10
|
"cmd.description.help": "Ayuda",
|
|
@@ -48,23 +49,28 @@ export const es = {
|
|
|
48
49
|
"bot.photo_model_no_image": "⚠️ El modelo actual no admite entrada de imagen. Enviaré solo texto.",
|
|
49
50
|
"bot.photo_download_error": "🔴 No se pudo descargar la foto",
|
|
50
51
|
"bot.photo_no_caption": "💡 Consejo: agrega un pie de foto para describir que quieres hacer con esta foto.",
|
|
52
|
+
"bot.file_downloading": "⏳ Descargando archivo...",
|
|
53
|
+
"bot.file_too_large": "⚠️ El archivo es demasiado grande (max {maxSizeMb}MB)",
|
|
54
|
+
"bot.file_download_error": "🔴 No se pudo descargar el archivo",
|
|
55
|
+
"bot.model_no_pdf": "⚠️ El modelo actual no admite entrada PDF. Enviaré solo texto.",
|
|
56
|
+
"bot.text_file_too_large": "⚠️ El archivo de texto es demasiado grande (max {maxSizeKb}KB)",
|
|
51
57
|
"status.header_running": "🟢 **OpenCode Server está en ejecución**",
|
|
52
58
|
"status.health.healthy": "Saludable",
|
|
53
59
|
"status.health.unhealthy": "No saludable",
|
|
54
60
|
"status.line.health": "Estado: {health}",
|
|
55
61
|
"status.line.version": "Versión: {version}",
|
|
56
|
-
"status.line.managed_yes": "
|
|
57
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Iniciado por el bot: Sí",
|
|
63
|
+
"status.line.managed_no": "Iniciado por el bot: No",
|
|
58
64
|
"status.line.pid": "PID: {pid}",
|
|
59
65
|
"status.line.uptime_sec": "Tiempo activo: {seconds} s",
|
|
60
66
|
"status.line.mode": "Modo: {mode}",
|
|
61
67
|
"status.line.model": "Modelo: {model}",
|
|
62
68
|
"status.agent_not_set": "no configurado",
|
|
63
|
-
"status.project_selected": "
|
|
64
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Proyecto: {project}",
|
|
70
|
+
"status.project_not_selected": "Proyecto: no seleccionado",
|
|
65
71
|
"status.project_hint": "Usa /projects para seleccionar un proyecto",
|
|
66
|
-
"status.session_selected": "
|
|
67
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Sesión actual: {title}",
|
|
73
|
+
"status.session_not_selected": "Sesión actual: no seleccionada",
|
|
68
74
|
"status.session_hint": "Usa /sessions para elegir una o /new para crear una",
|
|
69
75
|
"status.server_unavailable": "🔴 OpenCode Server no está disponible\n\nUsa /opencode_start para iniciar el servidor.",
|
|
70
76
|
"projects.empty": "📭 No se encontraron proyectos.\n\nAbre un directorio en OpenCode y crea al menos una sesión; entonces aparecerá aquí.",
|
|
@@ -222,6 +228,8 @@ export const es = {
|
|
|
222
228
|
"runtime.wizard.ask_user_id": "Introduce tu Telegram User ID (puedes obtenerlo de @userinfobot).\n> ",
|
|
223
229
|
"runtime.wizard.user_id_invalid": "Introduce un entero positivo (> 0).\n",
|
|
224
230
|
"runtime.wizard.ask_api_url": "Introduce la URL de la API de OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Introduce el nombre de usuario del servidor OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Introduce la contrasena del servidor OpenCode (opcional).\nPulsa Enter para dejarla vacia.\n> ",
|
|
225
233
|
"runtime.wizard.api_url_invalid": "Introduce una URL válida (http/https) o pulsa Enter para usar el valor por defecto.\n",
|
|
226
234
|
"runtime.wizard.start": "Configuración de OpenCode Telegram Bot.\n",
|
|
227
235
|
"runtime.wizard.saved": "Configuración guardada:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -238,6 +246,19 @@ export const es = {
|
|
|
238
246
|
"rename.blocked.expected_name": "⚠️ Introduce el nuevo nombre de la sesión como texto o toca Cancelar en el mensaje de cambio de nombre.",
|
|
239
247
|
"rename.blocked.command_not_allowed": "⚠️ Este comando no está disponible mientras el cambio de nombre espera un nuevo nombre.",
|
|
240
248
|
"rename.button.cancel": "❌ Cancelar",
|
|
249
|
+
"commands.select": "Elige un comando de OpenCode:",
|
|
250
|
+
"commands.empty": "📭 No hay comandos de OpenCode disponibles para este proyecto.",
|
|
251
|
+
"commands.fetch_error": "🔴 No se pudieron cargar los comandos de OpenCode.",
|
|
252
|
+
"commands.no_description": "Sin descripción",
|
|
253
|
+
"commands.button.execute": "✅ Ejecutar",
|
|
254
|
+
"commands.button.cancel": "❌ Cancelar",
|
|
255
|
+
"commands.confirm": "Confirma la ejecución del comando {command}. Para ejecutarlo con argumentos, envía los argumentos como mensaje.",
|
|
256
|
+
"commands.inactive_callback": "Este menú de comandos está inactivo",
|
|
257
|
+
"commands.cancelled_callback": "Cancelado",
|
|
258
|
+
"commands.execute_callback": "Ejecutando comando...",
|
|
259
|
+
"commands.executing_prefix": "⚡ Ejecutando comando:",
|
|
260
|
+
"commands.arguments_empty": "⚠️ Los argumentos no pueden estar vacíos. Envía texto o toca Ejecutar.",
|
|
261
|
+
"commands.execute_error": "🔴 No se pudo ejecutar el comando de OpenCode.",
|
|
241
262
|
"cmd.description.rename": "Renombrar la sesión actual",
|
|
242
263
|
"cli.usage": "Uso:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotas:\n - Sin comando, el valor por defecto es `start`\n - `--mode` actualmente solo se admite para `start`",
|
|
243
264
|
"cli.placeholder.status": "El comando `status` es actualmente un marcador de posición. Las comprobaciones reales de estado se agregarán en la capa de servicio (Fase 5).",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -4,6 +4,7 @@ export const ru = {
|
|
|
4
4
|
"cmd.description.stop": "Прервать текущее действие",
|
|
5
5
|
"cmd.description.sessions": "Список сессий",
|
|
6
6
|
"cmd.description.projects": "Список проектов",
|
|
7
|
+
"cmd.description.commands": "Пользовательские команды",
|
|
7
8
|
"cmd.description.opencode_start": "Запустить OpenCode сервер",
|
|
8
9
|
"cmd.description.opencode_stop": "Остановить OpenCode сервер",
|
|
9
10
|
"cmd.description.help": "Справка",
|
|
@@ -48,23 +49,28 @@ export const ru = {
|
|
|
48
49
|
"bot.photo_model_no_image": "⚠️ Текущая модель не поддерживает изображения. Отправляю только текст.",
|
|
49
50
|
"bot.photo_download_error": "🔴 Не удалось скачать фото",
|
|
50
51
|
"bot.photo_no_caption": "💡 Совет: Добавьте подпись, чтобы описать, что делать с этим фото.",
|
|
52
|
+
"bot.file_downloading": "⏳ Скачиваю файл...",
|
|
53
|
+
"bot.file_too_large": "⚠️ Файл слишком большой (макс. {maxSizeMb}МБ)",
|
|
54
|
+
"bot.file_download_error": "🔴 Не удалось скачать файл",
|
|
55
|
+
"bot.model_no_pdf": "⚠️ Текущая модель не поддерживает PDF. Отправляю только текст.",
|
|
56
|
+
"bot.text_file_too_large": "⚠️ Текстовый файл слишком большой (макс. {maxSizeKb}КБ)",
|
|
51
57
|
"status.header_running": "🟢 **OpenCode Server запущен**",
|
|
52
58
|
"status.health.healthy": "Healthy",
|
|
53
59
|
"status.health.unhealthy": "Unhealthy",
|
|
54
60
|
"status.line.health": "Статус: {health}",
|
|
55
61
|
"status.line.version": "Версия: {version}",
|
|
56
|
-
"status.line.managed_yes": "
|
|
57
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "Запущен ботом: Да",
|
|
63
|
+
"status.line.managed_no": "Запущен ботом: Нет",
|
|
58
64
|
"status.line.pid": "PID: {pid}",
|
|
59
65
|
"status.line.uptime_sec": "Uptime: {seconds} сек",
|
|
60
66
|
"status.line.mode": "Режим: {mode}",
|
|
61
67
|
"status.line.model": "Модель: {model}",
|
|
62
68
|
"status.agent_not_set": "не установлен",
|
|
63
|
-
"status.project_selected": "
|
|
64
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "Проект: {project}",
|
|
70
|
+
"status.project_not_selected": "Проект: не выбран",
|
|
65
71
|
"status.project_hint": "Используйте /projects для выбора проекта",
|
|
66
|
-
"status.session_selected": "
|
|
67
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "Текущая сессия: {title}",
|
|
73
|
+
"status.session_not_selected": "Текущая сессия: не выбрана",
|
|
68
74
|
"status.session_hint": "Используйте /sessions для выбора или /new для создания",
|
|
69
75
|
"status.server_unavailable": "🔴 OpenCode Server недоступен\n\nИспользуйте /opencode_start для запуска сервера.",
|
|
70
76
|
"projects.empty": "📭 Проектов нет.\n\nОткройте директорию в OpenCode и создайте хотя бы одну сессию, после этого она появится здесь.",
|
|
@@ -202,14 +208,14 @@ export const ru = {
|
|
|
202
208
|
"keyboard.variant": "💭 {name}",
|
|
203
209
|
"keyboard.variant_default": "💡 Default",
|
|
204
210
|
"keyboard.updated": "⌨️ Клавиатура обновлена",
|
|
205
|
-
"pinned.default_session_title": "
|
|
206
|
-
"pinned.unknown": "
|
|
207
|
-
"pinned.line.project": "
|
|
208
|
-
"pinned.line.model": "
|
|
209
|
-
"pinned.line.context": "
|
|
210
|
-
"pinned.files.title": "
|
|
211
|
+
"pinned.default_session_title": "новая сессия",
|
|
212
|
+
"pinned.unknown": "Неизвестно",
|
|
213
|
+
"pinned.line.project": "Проект: {project}",
|
|
214
|
+
"pinned.line.model": "Модель: {model}",
|
|
215
|
+
"pinned.line.context": "Контекст: {used} / {limit} ({percent}%)",
|
|
216
|
+
"pinned.files.title": "Файлы ({count}):",
|
|
211
217
|
"pinned.files.item": " {path}{diff}",
|
|
212
|
-
"pinned.files.more": " ...
|
|
218
|
+
"pinned.files.more": " ... и еще {count}",
|
|
213
219
|
"tool.todo.overflow": "*(ещё {count} задач)*",
|
|
214
220
|
"tool.file_header.write": "Write File/Path: {path}\n============================================================\n\n",
|
|
215
221
|
"tool.file_header.edit": "Edit File/Path: {path}\n============================================================\n\n",
|
|
@@ -222,6 +228,8 @@ export const ru = {
|
|
|
222
228
|
"runtime.wizard.ask_user_id": "Введите ваш Telegram User ID (можно узнать у @userinfobot).\n> ",
|
|
223
229
|
"runtime.wizard.user_id_invalid": "Введите положительное целое число (> 0).\n",
|
|
224
230
|
"runtime.wizard.ask_api_url": "Введите URL OpenCode API (опционально).\nНажмите Enter для значения по умолчанию: {defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "Введите логин OpenCode сервера (опционально).\nНажмите Enter для значения по умолчанию: {defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "Введите пароль OpenCode сервера (опционально).\nНажмите Enter, чтобы оставить пустым.\n> ",
|
|
225
233
|
"runtime.wizard.api_url_invalid": "Введите корректный URL (http/https) или нажмите Enter для значения по умолчанию.\n",
|
|
226
234
|
"runtime.wizard.start": "Настройка OpenCode Telegram Bot.\n",
|
|
227
235
|
"runtime.wizard.saved": "Конфигурация сохранена:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -238,6 +246,19 @@ export const ru = {
|
|
|
238
246
|
"rename.blocked.expected_name": "⚠️ Введите новое название текстом или нажмите Отмена в сообщении переименования.",
|
|
239
247
|
"rename.blocked.command_not_allowed": "⚠️ Эта команда недоступна, пока ожидается новое название сессии.",
|
|
240
248
|
"rename.button.cancel": "❌ Отмена",
|
|
249
|
+
"commands.select": "Выберите команду OpenCode:",
|
|
250
|
+
"commands.empty": "📭 Для этого проекта нет доступных команд OpenCode.",
|
|
251
|
+
"commands.fetch_error": "🔴 Не удалось загрузить список команд OpenCode.",
|
|
252
|
+
"commands.no_description": "Без описания",
|
|
253
|
+
"commands.button.execute": "✅ Выполнить",
|
|
254
|
+
"commands.button.cancel": "❌ Отмена",
|
|
255
|
+
"commands.confirm": "Подтвердите выполнение команды {command}. Для выполнения с аргументами отправьте аргументы отдельным сообщением.",
|
|
256
|
+
"commands.inactive_callback": "Это меню команд уже неактивно",
|
|
257
|
+
"commands.cancelled_callback": "Отменено",
|
|
258
|
+
"commands.execute_callback": "Запускаю команду...",
|
|
259
|
+
"commands.executing_prefix": "⚡ Выполнение команды:",
|
|
260
|
+
"commands.arguments_empty": "⚠️ Аргументы не могут быть пустыми. Отправьте текст или нажмите Выполнить.",
|
|
261
|
+
"commands.execute_error": "🔴 Не удалось выполнить команду OpenCode.",
|
|
241
262
|
"cmd.description.rename": "Переименовать текущую сессию",
|
|
242
263
|
"cli.usage": "Использование:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nЗаметки:\n - Без команды по умолчанию используется `start`\n - `--mode` сейчас поддерживается только для `start`",
|
|
243
264
|
"cli.placeholder.status": "Команда `status` пока работает как заглушка. Реальная проверка статуса появится на этапе service-слоя (Этап 5).",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -4,6 +4,7 @@ export const zh = {
|
|
|
4
4
|
"cmd.description.stop": "停止当前操作",
|
|
5
5
|
"cmd.description.sessions": "列出会话",
|
|
6
6
|
"cmd.description.projects": "列出项目",
|
|
7
|
+
"cmd.description.commands": "自定义命令",
|
|
7
8
|
"cmd.description.opencode_start": "启动 OpenCode 服务器",
|
|
8
9
|
"cmd.description.opencode_stop": "停止 OpenCode 服务器",
|
|
9
10
|
"cmd.description.help": "帮助",
|
|
@@ -48,23 +49,28 @@ export const zh = {
|
|
|
48
49
|
"bot.photo_model_no_image": "⚠️ 当前模型不支持图像输入。将仅发送文本。",
|
|
49
50
|
"bot.photo_download_error": "🔴 下载照片失败",
|
|
50
51
|
"bot.photo_no_caption": "💡 提示:添加说明文字以描述你希望对这张照片做什么。",
|
|
52
|
+
"bot.file_downloading": "⏳ 正在下载文件...",
|
|
53
|
+
"bot.file_too_large": "⚠️ 文件过大(最大 {maxSizeMb}MB)",
|
|
54
|
+
"bot.file_download_error": "🔴 下载文件失败",
|
|
55
|
+
"bot.model_no_pdf": "⚠️ 当前模型不支持PDF输入。将仅发送文本。",
|
|
56
|
+
"bot.text_file_too_large": "⚠️ 文本文件过大(最大 {maxSizeKb}KB)",
|
|
51
57
|
"status.header_running": "🟢 **OpenCode 服务器正在运行**",
|
|
52
58
|
"status.health.healthy": "健康",
|
|
53
59
|
"status.health.unhealthy": "不健康",
|
|
54
60
|
"status.line.health": "状态:{health}",
|
|
55
61
|
"status.line.version": "版本:{version}",
|
|
56
|
-
"status.line.managed_yes": "
|
|
57
|
-
"status.line.managed_no": "
|
|
62
|
+
"status.line.managed_yes": "由机器人启动:是",
|
|
63
|
+
"status.line.managed_no": "由机器人启动:否",
|
|
58
64
|
"status.line.pid": "PID:{pid}",
|
|
59
65
|
"status.line.uptime_sec": "运行时间:{seconds} 秒",
|
|
60
66
|
"status.line.mode": "模式:{mode}",
|
|
61
67
|
"status.line.model": "模型:{model}",
|
|
62
68
|
"status.agent_not_set": "未设置",
|
|
63
|
-
"status.project_selected": "
|
|
64
|
-
"status.project_not_selected": "
|
|
69
|
+
"status.project_selected": "项目:{project}",
|
|
70
|
+
"status.project_not_selected": "项目:未选择",
|
|
65
71
|
"status.project_hint": "使用 /projects 选择项目",
|
|
66
|
-
"status.session_selected": "
|
|
67
|
-
"status.session_not_selected": "
|
|
72
|
+
"status.session_selected": "当前会话:{title}",
|
|
73
|
+
"status.session_not_selected": "当前会话:未选择",
|
|
68
74
|
"status.session_hint": "使用 /sessions 选择一个会话,或 /new 创建",
|
|
69
75
|
"status.server_unavailable": "🔴 OpenCode 服务器不可用\n\n使用 /opencode_start 启动服务器。",
|
|
70
76
|
"projects.empty": "📭 未找到项目。\n\n在 OpenCode 中打开一个目录并至少创建一个会话,然后它会出现在这里。",
|
|
@@ -222,6 +228,8 @@ export const zh = {
|
|
|
222
228
|
"runtime.wizard.ask_user_id": "请输入你的 Telegram User ID(可从 @userinfobot 获取)。\n> ",
|
|
223
229
|
"runtime.wizard.user_id_invalid": "请输入一个正整数(> 0)。\n",
|
|
224
230
|
"runtime.wizard.ask_api_url": "请输入 OpenCode API URL(可选)。\n按 Enter 使用默认值:{defaultUrl}\n> ",
|
|
231
|
+
"runtime.wizard.ask_server_username": "请输入 OpenCode 服务器用户名(可选)。\n按 Enter 使用默认值:{defaultUsername}\n> ",
|
|
232
|
+
"runtime.wizard.ask_server_password": "请输入 OpenCode 服务器密码(可选)。\n按 Enter 保持为空。\n> ",
|
|
225
233
|
"runtime.wizard.api_url_invalid": "请输入有效 URL(http/https),或按 Enter 使用默认值。\n",
|
|
226
234
|
"runtime.wizard.start": "OpenCode Telegram Bot 设置。\n",
|
|
227
235
|
"runtime.wizard.saved": "配置已保存:\n- {envPath}\n- {settingsPath}\n",
|
|
@@ -238,6 +246,19 @@ export const zh = {
|
|
|
238
246
|
"rename.blocked.expected_name": "⚠️ 请以文本输入新会话名称,或在重命名消息中点击取消。",
|
|
239
247
|
"rename.blocked.command_not_allowed": "⚠️ 重命名等待新名称期间不可用此命令。",
|
|
240
248
|
"rename.button.cancel": "❌ 取消",
|
|
249
|
+
"commands.select": "请选择一个 OpenCode 命令:",
|
|
250
|
+
"commands.empty": "📭 当前项目没有可用的 OpenCode 命令。",
|
|
251
|
+
"commands.fetch_error": "🔴 加载 OpenCode 命令失败。",
|
|
252
|
+
"commands.no_description": "无描述",
|
|
253
|
+
"commands.button.execute": "✅ 执行",
|
|
254
|
+
"commands.button.cancel": "❌ 取消",
|
|
255
|
+
"commands.confirm": "请确认执行命令 {command}。若需带参数执行,请发送一条包含参数的消息。",
|
|
256
|
+
"commands.inactive_callback": "该命令菜单已失效",
|
|
257
|
+
"commands.cancelled_callback": "已取消",
|
|
258
|
+
"commands.execute_callback": "正在执行命令...",
|
|
259
|
+
"commands.executing_prefix": "⚡ 执行命令:",
|
|
260
|
+
"commands.arguments_empty": "⚠️ 参数不能为空。请发送文本或点击执行。",
|
|
261
|
+
"commands.execute_error": "🔴 执行 OpenCode 命令失败。",
|
|
241
262
|
"cmd.description.rename": "重命名当前会话",
|
|
242
263
|
"cli.usage": "用法:\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\n说明:\n - 不带命令时默认执行 `start`\n - `--mode` 目前仅支持 `start` 命令",
|
|
243
264
|
"cli.placeholder.status": "命令 `status` 目前是占位符。真实状态检查将会在 service 层(阶段 5)添加。",
|
|
@@ -7,6 +7,7 @@ import dotenv from "dotenv";
|
|
|
7
7
|
import { getRuntimePaths } from "./paths.js";
|
|
8
8
|
import { getLocale, getLocaleOptions, resolveSupportedLocale, setRuntimeLocale, t, } from "../i18n/index.js";
|
|
9
9
|
const DEFAULT_API_URL = "http://localhost:4096";
|
|
10
|
+
const DEFAULT_SERVER_USERNAME = "opencode";
|
|
10
11
|
const FALLBACK_MODEL_PROVIDER = "opencode";
|
|
11
12
|
const FALLBACK_MODEL_ID = "big-pickle";
|
|
12
13
|
function isPositiveInteger(value) {
|
|
@@ -64,6 +65,8 @@ export function buildEnvFileContent(existingContent, values) {
|
|
|
64
65
|
["TELEGRAM_BOT_TOKEN", values.TELEGRAM_BOT_TOKEN],
|
|
65
66
|
["TELEGRAM_ALLOWED_USER_ID", values.TELEGRAM_ALLOWED_USER_ID],
|
|
66
67
|
["OPENCODE_API_URL", values.OPENCODE_API_URL],
|
|
68
|
+
["OPENCODE_SERVER_USERNAME", values.OPENCODE_SERVER_USERNAME],
|
|
69
|
+
["OPENCODE_SERVER_PASSWORD", values.OPENCODE_SERVER_PASSWORD],
|
|
67
70
|
["OPENCODE_MODEL_PROVIDER", values.OPENCODE_MODEL_PROVIDER],
|
|
68
71
|
["OPENCODE_MODEL_ID", values.OPENCODE_MODEL_ID],
|
|
69
72
|
];
|
|
@@ -242,6 +245,23 @@ async function askApiUrl() {
|
|
|
242
245
|
return apiUrl;
|
|
243
246
|
}
|
|
244
247
|
}
|
|
248
|
+
async function askServerUsername() {
|
|
249
|
+
const prompt = t("runtime.wizard.ask_server_username", {
|
|
250
|
+
defaultUsername: DEFAULT_SERVER_USERNAME,
|
|
251
|
+
});
|
|
252
|
+
const username = await askVisible(prompt);
|
|
253
|
+
if (!username) {
|
|
254
|
+
return DEFAULT_SERVER_USERNAME;
|
|
255
|
+
}
|
|
256
|
+
return username;
|
|
257
|
+
}
|
|
258
|
+
async function askServerPassword() {
|
|
259
|
+
const password = await askHidden(t("runtime.wizard.ask_server_password"));
|
|
260
|
+
if (!password) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return password;
|
|
264
|
+
}
|
|
245
265
|
async function collectWizardValues() {
|
|
246
266
|
const locale = await askLocale();
|
|
247
267
|
setRuntimeLocale(locale);
|
|
@@ -258,12 +278,16 @@ async function collectWizardValues() {
|
|
|
258
278
|
const token = await askToken();
|
|
259
279
|
const allowedUserId = await askAllowedUserId();
|
|
260
280
|
const apiUrl = await askApiUrl();
|
|
281
|
+
const serverUsername = await askServerUsername();
|
|
282
|
+
const serverPassword = await askServerPassword();
|
|
261
283
|
process.stdout.write("\n");
|
|
262
284
|
return {
|
|
263
285
|
locale,
|
|
264
286
|
token,
|
|
265
287
|
allowedUserId,
|
|
266
288
|
apiUrl,
|
|
289
|
+
serverUsername,
|
|
290
|
+
serverPassword,
|
|
267
291
|
};
|
|
268
292
|
}
|
|
269
293
|
function ensureInteractiveTty() {
|
|
@@ -294,6 +318,8 @@ async function runWizardAndPersist(runtimePaths) {
|
|
|
294
318
|
TELEGRAM_BOT_TOKEN: wizardValues.token,
|
|
295
319
|
TELEGRAM_ALLOWED_USER_ID: wizardValues.allowedUserId,
|
|
296
320
|
OPENCODE_API_URL: wizardValues.apiUrl,
|
|
321
|
+
OPENCODE_SERVER_USERNAME: wizardValues.serverUsername,
|
|
322
|
+
OPENCODE_SERVER_PASSWORD: wizardValues.serverPassword,
|
|
297
323
|
OPENCODE_MODEL_PROVIDER: provider,
|
|
298
324
|
OPENCODE_MODEL_ID: modelId,
|
|
299
325
|
};
|