@grinev/opencode-telegram-bot 0.11.2 → 0.11.4

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 CHANGED
@@ -41,7 +41,7 @@ OPENCODE_MODEL_ID=big-pickle
41
41
  # PROJECTS_LIST_LIMIT=10
42
42
 
43
43
  # Bot locale: supported locale code (default: en)
44
- # Supported locales: en, de, es, ru, zh
44
+ # Supported locales: en, de, es, fr, ru, zh
45
45
  # BOT_LOCALE=en
46
46
 
47
47
  # Service message batching interval in seconds (thinking + tool calls, default: 5)
package/README.md CHANGED
@@ -13,7 +13,7 @@ No open ports, no exposed APIs. The bot communicates with your local OpenCode se
13
13
 
14
14
  Platforms: macOS, Windows, Linux
15
15
 
16
- Languages: English (`en`), Deutsch (`de`), Español (`es`), Русский (`ru`), 简体中文 (`zh`)
16
+ Languages: English (`en`), Deutsch (`de`), Español (`es`), Français (`fr`), Русский (`ru`), 简体中文 (`zh`)
17
17
 
18
18
  <p align="center">
19
19
  <img src="assets/screencast.gif" width="45%" alt="OpenCode Telegram Bot screencast" />
@@ -119,7 +119,7 @@ Any regular text message is sent as a prompt to the coding agent only when no bl
119
119
 
120
120
  ### Localization
121
121
 
122
- - Supported locales: `en`, `de`, `es`, `ru`, `zh`
122
+ - Supported locales: `en`, `de`, `es`, `fr`, `ru`, `zh`
123
123
  - The setup wizard asks for language first
124
124
  - You can change locale later with `BOT_LOCALE`
125
125
 
@@ -141,7 +141,7 @@ When installed via npm, the configuration wizard handles the initial setup. The
141
141
  | `OPENCODE_SERVER_PASSWORD` | Server auth password | No | — |
142
142
  | `OPENCODE_MODEL_PROVIDER` | Default model provider | Yes | `opencode` |
143
143
  | `OPENCODE_MODEL_ID` | Default model ID | Yes | `big-pickle` |
144
- | `BOT_LOCALE` | Bot UI language (supported locale code, e.g. `en`, `de`, `es`, `ru`, `zh`) | No | `en` |
144
+ | `BOT_LOCALE` | Bot UI language (supported locale code, e.g. `en`, `de`, `es`, `fr`, `ru`, `zh`) | No | `en` |
145
145
  | `SESSIONS_LIST_LIMIT` | Sessions per page in `/sessions` | No | `10` |
146
146
  | `PROJECTS_LIST_LIMIT` | Projects per page in `/projects` | No | `10` |
147
147
  | `SERVICE_MESSAGES_INTERVAL_SEC` | Service messages interval (thinking + tool calls); keep `>=2` to avoid Telegram rate limits, `0` = immediate | No | `5` |
@@ -15,21 +15,21 @@ export async function modelsCommand(ctx) {
15
15
  }
16
16
  let message = t("legacy.models.header");
17
17
  for (const provider of providers) {
18
- message += `🔹 **\`${provider.id}\`**\n`;
18
+ message += `🔹 ${provider.id}\n`;
19
19
  const models = Object.values(provider.models);
20
20
  if (models.length === 0) {
21
21
  message += t("legacy.models.no_provider_models");
22
22
  }
23
23
  else {
24
24
  for (const model of models) {
25
- message += ` \`${model.id}\`\n`;
25
+ message += ` - ${model.id}\n`;
26
26
  }
27
27
  }
28
28
  message += "\n";
29
29
  }
30
30
  message += t("legacy.models.env_hint");
31
- message += "```\nOPENCODE_MODEL_PROVIDER=<provider.id>\nOPENCODE_MODEL_ID=<model.id>\n```";
32
- await ctx.reply(message, { parse_mode: "Markdown" });
31
+ message += "OPENCODE_MODEL_PROVIDER=<provider.id>\nOPENCODE_MODEL_ID=<model.id>";
32
+ await ctx.reply(message);
33
33
  }
34
34
  catch (error) {
35
35
  logger.error("[ModelsCommand] Error listing models:", error);
@@ -2,6 +2,7 @@ import { opencodeClient } from "../../opencode/client.js";
2
2
  import { processManager } from "../../process/manager.js";
3
3
  import { logger } from "../../utils/logger.js";
4
4
  import { t } from "../../i18n/index.js";
5
+ import { editBotText } from "../utils/telegram-text.js";
5
6
  /**
6
7
  * Wait for OpenCode server to become ready by polling health endpoint
7
8
  * @param maxWaitMs Maximum time to wait in milliseconds
@@ -58,24 +59,39 @@ export async function opencodeStartCommand(ctx) {
58
59
  // 4. Start the process
59
60
  const { success, error } = await processManager.start();
60
61
  if (!success) {
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" });
62
+ await editBotText({
63
+ api: ctx.api,
64
+ chatId: ctx.chat.id,
65
+ messageId: statusMessage.message_id,
66
+ text: t("opencode_start.start_error", { error: error || t("common.unknown_error") }),
67
+ });
62
68
  return;
63
69
  }
64
70
  // 5. Wait for server to become ready
65
71
  logger.info("[Bot] Waiting for OpenCode server to become ready...");
66
72
  const ready = await waitForServerReady(10000);
67
73
  if (!ready) {
68
- await ctx.api.editMessageText(ctx.chat.id, statusMessage.message_id, t("opencode_start.started_not_ready", {
69
- pid: processManager.getPID() ?? "-",
70
- }));
74
+ await editBotText({
75
+ api: ctx.api,
76
+ chatId: ctx.chat.id,
77
+ messageId: statusMessage.message_id,
78
+ text: t("opencode_start.started_not_ready", {
79
+ pid: processManager.getPID() ?? "-",
80
+ }),
81
+ });
71
82
  return;
72
83
  }
73
84
  // 6. Get server version and send success message
74
85
  const { data: health } = await opencodeClient.global.health();
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
- }));
86
+ await editBotText({
87
+ api: ctx.api,
88
+ chatId: ctx.chat.id,
89
+ messageId: statusMessage.message_id,
90
+ text: t("opencode_start.success", {
91
+ pid: processManager.getPID() ?? "-",
92
+ version: health?.version || t("common.unknown"),
93
+ }),
94
+ });
79
95
  logger.info(`[Bot] OpenCode server started successfully, PID=${processManager.getPID()}`);
80
96
  }
81
97
  catch (err) {
@@ -9,7 +9,7 @@ import { keyboardManager } from "../../keyboard/manager.js";
9
9
  import { pinnedMessageManager } from "../../pinned/manager.js";
10
10
  import { logger } from "../../utils/logger.js";
11
11
  import { t } from "../../i18n/index.js";
12
- import { sendMessageWithMarkdownFallback } from "../utils/send-with-markdown-fallback.js";
12
+ import { sendBotText } from "../utils/telegram-text.js";
13
13
  export async function statusCommand(ctx) {
14
14
  try {
15
15
  const { data, error } = await opencodeClient.global.health();
@@ -77,12 +77,11 @@ export async function statusCommand(ctx) {
77
77
  }
78
78
  const keyboard = keyboardManager.getKeyboard();
79
79
  if (ctx.chat) {
80
- await sendMessageWithMarkdownFallback({
80
+ await sendBotText({
81
81
  api: ctx.api,
82
82
  chatId: ctx.chat.id,
83
83
  text: message,
84
84
  options: { reply_markup: keyboard },
85
- parseMode: "Markdown",
86
85
  });
87
86
  }
88
87
  else {
@@ -21,6 +21,7 @@ const PERMISSION_NAME_KEYS = {
21
21
  list: "permission.name.list",
22
22
  task: "permission.name.task",
23
23
  lsp: "permission.name.lsp",
24
+ external_directory: "permission.name.external_directory",
24
25
  };
25
26
  // Permission type emojis
26
27
  const PERMISSION_EMOJIS = {
@@ -35,6 +36,7 @@ const PERMISSION_EMOJIS = {
35
36
  list: "📂",
36
37
  task: "⚙️",
37
38
  lsp: "🔧",
39
+ external_directory: "📁",
38
40
  };
39
41
  function getCallbackMessageId(ctx) {
40
42
  const message = ctx.callbackQuery?.message;
@@ -191,7 +193,6 @@ export async function showPermissionRequest(bot, chatId, request) {
191
193
  try {
192
194
  const message = await bot.sendMessage(chatId, text, {
193
195
  reply_markup: keyboard,
194
- parse_mode: "Markdown",
195
196
  });
196
197
  logger.debug(`[PermissionHandler] Message sent, messageId=${message.message_id}`);
197
198
  permissionManager.startPermission(request, message.message_id);
@@ -217,9 +218,7 @@ function formatPermissionText(request) {
217
218
  // Show patterns (commands/files)
218
219
  if (request.patterns.length > 0) {
219
220
  request.patterns.forEach((pattern) => {
220
- // Escape backticks for Markdown code
221
- const escapedPattern = pattern.replace(/`/g, "\\`");
222
- text += `\`${escapedPattern}\`\n`;
221
+ text += `• ${pattern}\n`;
223
222
  });
224
223
  }
225
224
  return text;
@@ -192,7 +192,6 @@ async function updateQuestionMessage(ctx) {
192
192
  try {
193
193
  await ctx.editMessageText(text, {
194
194
  reply_markup: keyboard,
195
- parse_mode: "Markdown",
196
195
  });
197
196
  }
198
197
  catch (err) {
@@ -212,7 +211,6 @@ export async function showCurrentQuestion(bot, chatId) {
212
211
  try {
213
212
  const message = await bot.sendMessage(chatId, text, {
214
213
  reply_markup: keyboard,
215
- parse_mode: "Markdown",
216
214
  });
217
215
  logger.debug(`[QuestionHandler] Message sent, messageId=${message.message_id}`);
218
216
  questionManager.addMessageId(message.message_id);
@@ -342,7 +340,7 @@ function formatQuestionText(question) {
342
340
  const totalQuestions = questionManager.getTotalQuestions();
343
341
  const progressText = totalQuestions > 0 ? `${currentIndex + 1}/${totalQuestions}` : "";
344
342
  const headerTitle = [progressText, question.header].filter(Boolean).join(" ");
345
- const header = headerTitle ? `**${headerTitle}**\n\n` : "";
343
+ const header = headerTitle ? `${headerTitle}\n\n` : "";
346
344
  const multiple = question.multiple ? t("question.multi_hint") : "";
347
345
  return `${header}${question.question}${multiple}`;
348
346
  }
package/dist/bot/index.js CHANGED
@@ -46,7 +46,7 @@ import { processUserPrompt } from "./handlers/prompt.js";
46
46
  import { handleVoiceMessage } from "./handlers/voice.js";
47
47
  import { handleDocumentMessage } from "./handlers/document.js";
48
48
  import { downloadTelegramFile, toDataUri } from "./utils/file-download.js";
49
- import { sendMessageWithMarkdownFallback } from "./utils/send-with-markdown-fallback.js";
49
+ import { sendBotText } from "./utils/telegram-text.js";
50
50
  import { getModelCapabilities, supportsInput } from "../model/capabilities.js";
51
51
  import { getStoredModel } from "../model/manager.js";
52
52
  let botInstance = null;
@@ -151,17 +151,18 @@ async function ensureEventSubscription(directory) {
151
151
  try {
152
152
  const parts = formatSummary(messageText);
153
153
  const assistantParseMode = getAssistantParseMode();
154
+ const assistantMessageFormat = assistantParseMode === "MarkdownV2" ? "markdown_v2" : "raw";
154
155
  logger.debug(`[Bot] Sending completed message to Telegram (chatId=${chatIdInstance}, parts=${parts.length})`);
155
156
  for (let i = 0; i < parts.length; i++) {
156
157
  const isLastPart = i === parts.length - 1;
157
158
  const keyboard = isLastPart && keyboardManager.isInitialized() ? keyboardManager.getKeyboard() : undefined;
158
159
  const options = keyboard ? { reply_markup: keyboard } : undefined;
159
- await sendMessageWithMarkdownFallback({
160
+ await sendBotText({
160
161
  api: botInstance.api,
161
162
  chatId: chatIdInstance,
162
163
  text: parts[i],
163
164
  options,
164
- parseMode: assistantParseMode,
165
+ format: assistantMessageFormat,
165
166
  });
166
167
  }
167
168
  }
@@ -56,3 +56,23 @@ export async function sendMessageWithMarkdownFallback({ api, chatId, text, optio
56
56
  await api.sendMessage(chatId, text, options);
57
57
  }
58
58
  }
59
+ export async function editMessageWithMarkdownFallback({ api, chatId, messageId, text, options, parseMode, }) {
60
+ if (!parseMode) {
61
+ await api.editMessageText(chatId, messageId, text, options);
62
+ return;
63
+ }
64
+ const markdownOptions = {
65
+ ...(options || {}),
66
+ parse_mode: parseMode,
67
+ };
68
+ try {
69
+ await api.editMessageText(chatId, messageId, text, markdownOptions);
70
+ }
71
+ catch (error) {
72
+ if (!isTelegramMarkdownParseError(error)) {
73
+ throw error;
74
+ }
75
+ logger.warn("[Bot] Markdown parse failed, retrying edited message in raw mode", error);
76
+ await api.editMessageText(chatId, messageId, text, options);
77
+ }
78
+ }
@@ -0,0 +1,26 @@
1
+ import { editMessageWithMarkdownFallback, sendMessageWithMarkdownFallback, } from "./send-with-markdown-fallback.js";
2
+ function resolveParseMode(format) {
3
+ if (format === "markdown_v2") {
4
+ return "MarkdownV2";
5
+ }
6
+ return undefined;
7
+ }
8
+ export async function sendBotText({ api, chatId, text, options, format = "raw", }) {
9
+ await sendMessageWithMarkdownFallback({
10
+ api,
11
+ chatId,
12
+ text,
13
+ options,
14
+ parseMode: resolveParseMode(format),
15
+ });
16
+ }
17
+ export async function editBotText({ api, chatId, messageId, text, options, format = "raw", }) {
18
+ await editMessageWithMarkdownFallback({
19
+ api,
20
+ chatId,
21
+ messageId,
22
+ text,
23
+ options,
24
+ parseMode: resolveParseMode(format),
25
+ });
26
+ }
package/dist/i18n/de.js CHANGED
@@ -54,7 +54,7 @@ export const de = {
54
54
  "bot.file_download_error": "🔴 Datei konnte nicht heruntergeladen werden",
55
55
  "bot.model_no_pdf": "⚠️ Das aktuelle Modell unterstützt keine PDF-Eingabe. Sende nur Text.",
56
56
  "bot.text_file_too_large": "⚠️ Textdatei ist zu groß (max. {maxSizeKb}KB)",
57
- "status.header_running": "🟢 **OpenCode-Server läuft**",
57
+ "status.header_running": "🟢 OpenCode-Server läuft",
58
58
  "status.health.healthy": "OK",
59
59
  "status.health.unhealthy": "Nicht OK",
60
60
  "status.line.health": "Status: {health}",
@@ -115,7 +115,7 @@ export const de = {
115
115
  "opencode_start.already_running_managed": "⚠️ OpenCode-Server läuft bereits\n\nPID: {pid}\nBetriebszeit: {seconds} Sekunden",
116
116
  "opencode_start.already_running_external": "✅ OpenCode-Server läuft bereits als externer Prozess\n\nVersion: {version}\n\nDieser Server wurde nicht vom Bot gestartet, daher kann /opencode-stop ihn nicht stoppen.",
117
117
  "opencode_start.starting": "🔄 Starte OpenCode-Server...",
118
- "opencode_start.start_error": "🔴 OpenCode-Server konnte nicht gestartet werden\n\nFehler: {error}\n\nPrüfe, ob OpenCode CLI installiert und im PATH verfügbar ist:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
118
+ "opencode_start.start_error": "🔴 OpenCode-Server konnte nicht gestartet werden\n\nFehler: {error}\n\nPrüfe, ob OpenCode CLI installiert und im PATH verfügbar ist:\nopencode --version\nnpm install -g @opencode-ai/cli",
119
119
  "opencode_start.started_not_ready": "⚠️ OpenCode-Server gestartet, aber reagiert nicht\n\nPID: {pid}\n\nDer Server startet möglicherweise noch. Versuche /status in ein paar Sekunden.",
120
120
  "opencode_start.success": "✅ OpenCode-Server erfolgreich gestartet\n\nPID: {pid}\nVersion: {version}",
121
121
  "opencode_start.error": "🔴 Beim Starten des Servers ist ein Fehler aufgetreten.\n\nSiehe Anwendungslogs für Details.",
@@ -169,7 +169,7 @@ export const de = {
169
169
  "permission.send_reply_error": "❌ Antwort auf Berechtigungsanfrage konnte nicht gesendet werden",
170
170
  "permission.blocked.expected_reply": "⚠️ Bitte beantworte zuerst die Berechtigungsanfrage mit den Buttons oben.",
171
171
  "permission.blocked.command_not_allowed": "⚠️ Dieser Befehl ist erst verfügbar, wenn du die Berechtigungsanfrage beantwortet hast.",
172
- "permission.header": "{emoji} **Berechtigungsanfrage: {name}**\n\n",
172
+ "permission.header": "{emoji} Berechtigungsanfrage: {name}\n\n",
173
173
  "permission.button.allow": "✅ Einmal erlauben",
174
174
  "permission.button.always": "🔓 Immer erlauben",
175
175
  "permission.button.reject": "❌ Ablehnen",
@@ -184,6 +184,7 @@ export const de = {
184
184
  "permission.name.list": "Verzeichnis auflisten",
185
185
  "permission.name.task": "Task",
186
186
  "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "Externes Verzeichnis",
187
188
  "question.inactive_callback": "Umfrage ist inaktiv",
188
189
  "question.processing_error_callback": "Verarbeitungsfehler",
189
190
  "question.select_one_required_callback": "Wähle mindestens eine Option",
@@ -194,7 +195,7 @@ export const de = {
194
195
  "question.no_active_project": "❌ Kein aktives Projekt",
195
196
  "question.no_active_request": "❌ Keine aktive Anfrage",
196
197
  "question.send_answers_error": "❌ Antworten konnten nicht an den Agenten gesendet werden",
197
- "question.multi_hint": "\n*Du kannst mehrere Optionen auswählen*",
198
+ "question.multi_hint": "\n(Du kannst mehrere Optionen auswählen)",
198
199
  "question.button.submit": "✅ Fertig",
199
200
  "question.button.custom": "🔤 Eigene Antwort",
200
201
  "question.button.cancel": "❌ Abbrechen",
@@ -272,7 +273,7 @@ export const de = {
272
273
  "cli.args.mode_only_start": "Option --mode wird nur für den start-Befehl unterstützt",
273
274
  "legacy.models.fetch_error": "🔴 Modellliste konnte nicht geladen werden. Prüfe den Serverstatus mit /status.",
274
275
  "legacy.models.empty": "📋 Keine verfügbaren Modelle. Konfiguriere Provider in OpenCode.",
275
- "legacy.models.header": "📋 **Verfügbare Modelle:**\n\n",
276
+ "legacy.models.header": "📋 Verfügbare Modelle:\n\n",
276
277
  "legacy.models.no_provider_models": " ⚠️ Keine verfügbaren Modelle\n",
277
278
  "legacy.models.env_hint": "💡 Um ein Modell in .env zu nutzen:\n",
278
279
  "legacy.models.error": "🔴 Beim Laden der Modellliste ist ein Fehler aufgetreten.",
package/dist/i18n/en.js CHANGED
@@ -54,7 +54,7 @@ export const en = {
54
54
  "bot.file_download_error": "🔴 Failed to download file",
55
55
  "bot.model_no_pdf": "⚠️ Current model doesn't support PDF input. Sending text only.",
56
56
  "bot.text_file_too_large": "⚠️ Text file is too large (max {maxSizeKb}KB)",
57
- "status.header_running": "🟢 **OpenCode Server is running**",
57
+ "status.header_running": "🟢 OpenCode Server is running",
58
58
  "status.health.healthy": "Healthy",
59
59
  "status.health.unhealthy": "Unhealthy",
60
60
  "status.line.health": "Status: {health}",
@@ -115,7 +115,7 @@ export const en = {
115
115
  "opencode_start.already_running_managed": "⚠️ OpenCode Server is already running\n\nPID: {pid}\nUptime: {seconds} seconds",
116
116
  "opencode_start.already_running_external": "✅ OpenCode Server is already running as an external process\n\nVersion: {version}\n\nThis server was not started by bot, so /opencode-stop cannot stop it.",
117
117
  "opencode_start.starting": "🔄 Starting OpenCode Server...",
118
- "opencode_start.start_error": "🔴 Failed to start OpenCode Server\n\nError: {error}\n\nCheck that OpenCode CLI is installed and available in PATH:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
118
+ "opencode_start.start_error": "🔴 Failed to start OpenCode Server\n\nError: {error}\n\nCheck that OpenCode CLI is installed and available in PATH:\nopencode --version\nnpm install -g @opencode-ai/cli",
119
119
  "opencode_start.started_not_ready": "⚠️ OpenCode Server started, but is not responding\n\nPID: {pid}\n\nServer may still be starting. Try /status in a few seconds.",
120
120
  "opencode_start.success": "✅ OpenCode Server started successfully\n\nPID: {pid}\nVersion: {version}",
121
121
  "opencode_start.error": "🔴 An error occurred while starting server.\n\nCheck application logs for details.",
@@ -169,7 +169,7 @@ export const en = {
169
169
  "permission.send_reply_error": "❌ Failed to send permission reply",
170
170
  "permission.blocked.expected_reply": "⚠️ Please answer the permission request first using the buttons above.",
171
171
  "permission.blocked.command_not_allowed": "⚠️ This command is not available until you answer the permission request.",
172
- "permission.header": "{emoji} **Permission request: {name}**\n\n",
172
+ "permission.header": "{emoji} Permission request: {name}\n\n",
173
173
  "permission.button.allow": "✅ Allow once",
174
174
  "permission.button.always": "🔓 Allow always",
175
175
  "permission.button.reject": "❌ Reject",
@@ -184,6 +184,7 @@ export const en = {
184
184
  "permission.name.list": "List Directory",
185
185
  "permission.name.task": "Task",
186
186
  "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "External Directory",
187
188
  "question.inactive_callback": "Poll is inactive",
188
189
  "question.processing_error_callback": "Processing error",
189
190
  "question.select_one_required_callback": "Select at least one option",
@@ -194,7 +195,7 @@ export const en = {
194
195
  "question.no_active_project": "❌ No active project",
195
196
  "question.no_active_request": "❌ No active request",
196
197
  "question.send_answers_error": "❌ Failed to send answers to agent",
197
- "question.multi_hint": "\n*You can select multiple options*",
198
+ "question.multi_hint": "\n(You can select multiple options)",
198
199
  "question.button.submit": "✅ Done",
199
200
  "question.button.custom": "🔤 Custom answer",
200
201
  "question.button.cancel": "❌ Cancel",
@@ -272,7 +273,7 @@ export const en = {
272
273
  "cli.args.mode_only_start": "Option --mode is supported only for the start command",
273
274
  "legacy.models.fetch_error": "🔴 Failed to get models list. Check server status with /status.",
274
275
  "legacy.models.empty": "📋 No available models. Configure providers in OpenCode.",
275
- "legacy.models.header": "📋 **Available models:**\n\n",
276
+ "legacy.models.header": "📋 Available models:\n\n",
276
277
  "legacy.models.no_provider_models": " ⚠️ No available models\n",
277
278
  "legacy.models.env_hint": "💡 To use model in .env:\n",
278
279
  "legacy.models.error": "🔴 An error occurred while loading models list.",
package/dist/i18n/es.js CHANGED
@@ -54,7 +54,7 @@ export const es = {
54
54
  "bot.file_download_error": "🔴 No se pudo descargar el archivo",
55
55
  "bot.model_no_pdf": "⚠️ El modelo actual no admite entrada PDF. Enviaré solo texto.",
56
56
  "bot.text_file_too_large": "⚠️ El archivo de texto es demasiado grande (max {maxSizeKb}KB)",
57
- "status.header_running": "🟢 **OpenCode Server está en ejecución**",
57
+ "status.header_running": "🟢 OpenCode Server está en ejecución",
58
58
  "status.health.healthy": "Saludable",
59
59
  "status.health.unhealthy": "No saludable",
60
60
  "status.line.health": "Estado: {health}",
@@ -115,7 +115,7 @@ export const es = {
115
115
  "opencode_start.already_running_managed": "⚠️ OpenCode Server ya está en ejecución\n\nPID: {pid}\nTiempo activo: {seconds} segundos",
116
116
  "opencode_start.already_running_external": "✅ OpenCode Server ya está en ejecución como un proceso externo\n\nVersión: {version}\n\nEste servidor no fue iniciado por el bot, por lo que /opencode-stop no puede detenerlo.",
117
117
  "opencode_start.starting": "🔄 Iniciando OpenCode Server...",
118
- "opencode_start.start_error": "🔴 No se pudo iniciar OpenCode Server\n\nError: {error}\n\nRevisa que OpenCode CLI esté instalado y disponible en PATH:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
118
+ "opencode_start.start_error": "🔴 No se pudo iniciar OpenCode Server\n\nError: {error}\n\nRevisa que OpenCode CLI esté instalado y disponible en PATH:\nopencode --version\nnpm install -g @opencode-ai/cli",
119
119
  "opencode_start.started_not_ready": "⚠️ OpenCode Server se inició, pero no responde\n\nPID: {pid}\n\nEl servidor puede estar iniciando. Prueba /status en unos segundos.",
120
120
  "opencode_start.success": "✅ OpenCode Server iniciado correctamente\n\nPID: {pid}\nVersión: {version}",
121
121
  "opencode_start.error": "🔴 Ocurrió un error al iniciar el servidor.\n\nRevisa los logs de la aplicación para más detalles.",
@@ -169,7 +169,7 @@ export const es = {
169
169
  "permission.send_reply_error": "❌ No se pudo enviar la respuesta de permisos",
170
170
  "permission.blocked.expected_reply": "⚠️ Primero responde a la solicitud de permisos usando los botones de arriba.",
171
171
  "permission.blocked.command_not_allowed": "⚠️ Este comando no está disponible hasta que respondas a la solicitud de permisos.",
172
- "permission.header": "{emoji} **Solicitud de permisos: {name}**\n\n",
172
+ "permission.header": "{emoji} Solicitud de permisos: {name}\n\n",
173
173
  "permission.button.allow": "✅ Permitir una vez",
174
174
  "permission.button.always": "🔓 Permitir siempre",
175
175
  "permission.button.reject": "❌ Rechazar",
@@ -184,6 +184,7 @@ export const es = {
184
184
  "permission.name.list": "Listar directorio",
185
185
  "permission.name.task": "Tarea",
186
186
  "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "Directorio externo",
187
188
  "question.inactive_callback": "La encuesta está inactiva",
188
189
  "question.processing_error_callback": "Error de procesamiento",
189
190
  "question.select_one_required_callback": "Selecciona al menos una opción",
@@ -194,7 +195,7 @@ export const es = {
194
195
  "question.no_active_project": "❌ No hay un proyecto activo",
195
196
  "question.no_active_request": "❌ No hay una solicitud activa",
196
197
  "question.send_answers_error": "❌ No se pudieron enviar las respuestas al agente",
197
- "question.multi_hint": "\n*Puedes seleccionar varias opciones*",
198
+ "question.multi_hint": "\n(Puedes seleccionar varias opciones)",
198
199
  "question.button.submit": "✅ Listo",
199
200
  "question.button.custom": "🔤 Respuesta personalizada",
200
201
  "question.button.cancel": "❌ Cancelar",
@@ -272,7 +273,7 @@ export const es = {
272
273
  "cli.args.mode_only_start": "La opción --mode solo se admite para el comando start",
273
274
  "legacy.models.fetch_error": "🔴 No se pudo obtener la lista de modelos. Revisa el estado del servidor con /status.",
274
275
  "legacy.models.empty": "📋 No hay modelos disponibles. Configura los proveedores en OpenCode.",
275
- "legacy.models.header": "📋 **Modelos disponibles:**\n\n",
276
+ "legacy.models.header": "📋 Modelos disponibles:\n\n",
276
277
  "legacy.models.no_provider_models": " ⚠️ No hay modelos disponibles\n",
277
278
  "legacy.models.env_hint": "💡 Para usar el modelo en .env:\n",
278
279
  "legacy.models.error": "🔴 Ocurrió un error al cargar la lista de modelos.",
@@ -0,0 +1,285 @@
1
+ export const fr = {
2
+ "cmd.description.status": "Statut du serveur et de la session",
3
+ "cmd.description.new": "Créer une nouvelle session",
4
+ "cmd.description.stop": "Arrêter l'action en cours",
5
+ "cmd.description.sessions": "Lister les sessions",
6
+ "cmd.description.projects": "Lister les projets",
7
+ "cmd.description.commands": "Commandes personnalisées",
8
+ "cmd.description.opencode_start": "Démarrer le serveur OpenCode",
9
+ "cmd.description.opencode_stop": "Arrêter le serveur OpenCode",
10
+ "cmd.description.help": "Aide",
11
+ "callback.unknown_command": "Commande inconnue",
12
+ "callback.processing_error": "Erreur de traitement",
13
+ "error.load_agents": "❌ Impossible de charger la liste des modes",
14
+ "error.load_models": "❌ Impossible de charger la liste des modèles",
15
+ "error.load_variants": "❌ Impossible de charger la liste des variantes",
16
+ "error.context_button": "❌ Impossible de traiter le bouton de contexte",
17
+ "error.generic": "🔴 Une erreur s'est produite.",
18
+ "interaction.blocked.expired": "⚠️ Cette interaction a expiré. Veuillez la relancer.",
19
+ "interaction.blocked.expected_callback": "⚠️ Veuillez utiliser les boutons inline pour cette étape ou appuyer sur Annuler.",
20
+ "interaction.blocked.expected_text": "⚠️ Veuillez envoyer un message texte pour cette étape.",
21
+ "interaction.blocked.expected_command": "⚠️ Veuillez envoyer une commande pour cette étape.",
22
+ "interaction.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible à l'étape actuelle.",
23
+ "interaction.blocked.finish_current": "⚠️ Terminez d'abord l'interaction en cours (réponse ou annulation), puis ouvrez un autre menu.",
24
+ "inline.blocked.expected_choice": "⚠️ Choisissez une option avec les boutons inline ou appuyez sur Annuler.",
25
+ "inline.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible tant que le menu inline est actif.",
26
+ "question.blocked.expected_answer": "⚠️ Répondez à la question en cours avec les boutons, Réponse personnalisée ou Annuler.",
27
+ "question.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible tant que le flux de question actuel n'est pas terminé.",
28
+ "inline.button.cancel": "❌ Annuler",
29
+ "inline.inactive_callback": "Ce menu est inactif",
30
+ "inline.cancelled_callback": "Annulé",
31
+ "common.unknown": "inconnu",
32
+ "common.unknown_error": "erreur inconnue",
33
+ "start.welcome": "👋 Bienvenue dans OpenCode Telegram Bot !\n\nUtilisez les commandes :\n/projects — sélectionner un projet\n/sessions — liste des sessions\n/new — nouvelle session\n/status — statut\n/help — aide\n\nUtilisez les boutons du bas pour choisir le mode d'agent, le modèle et la variante.",
34
+ "help.keyboard_hint": "💡 Utilisez les boutons du bas pour le mode d'agent, le modèle, la variante et les actions de contexte.",
35
+ "help.text": "📖 **Aide**\n\n/status - Vérifier l'état du serveur\n/sessions - Liste des sessions\n/new - Créer une nouvelle session\n/help - Aide",
36
+ "bot.thinking": "💭 Réflexion en cours...",
37
+ "bot.project_not_selected": "🏗 Aucun projet n'est sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
38
+ "bot.creating_session": "🔄 Création d'une nouvelle session...",
39
+ "bot.create_session_error": "🔴 Impossible de créer la session. Essayez /new ou vérifiez l'état du serveur avec /status.",
40
+ "bot.session_created": "✅ Session créée : {title}",
41
+ "bot.session_busy": "⏳ L'agent exécute déjà une tâche. Attendez la fin ou utilisez /abort pour interrompre l'exécution en cours.",
42
+ "bot.session_reset_project_mismatch": "⚠️ La session active ne correspond pas au projet sélectionné, elle a donc été réinitialisée. Utilisez /sessions pour en choisir une ou /new pour créer une nouvelle session.",
43
+ "bot.prompt_send_error": "Impossible d'envoyer la requête à OpenCode.",
44
+ "bot.session_error": "🔴 OpenCode a renvoyé une erreur : {message}",
45
+ "bot.session_retry": "🔁 {message}\n\nLe fournisseur renvoie la même erreur à chaque nouvelle tentative. Utilisez /abort pour arrêter.",
46
+ "bot.unknown_command": "⚠️ Commande inconnue : {command}. Utilisez /help pour voir les commandes disponibles.",
47
+ "bot.photo_downloading": "⏳ Téléchargement de la photo...",
48
+ "bot.photo_too_large": "⚠️ La photo est trop volumineuse (max {maxSizeMb}MB)",
49
+ "bot.photo_model_no_image": "⚠️ Le modèle actuel ne prend pas en charge les images. Envoi du texte uniquement.",
50
+ "bot.photo_download_error": "🔴 Impossible de télécharger la photo",
51
+ "bot.photo_no_caption": "💡 Conseil : ajoutez une légende pour décrire ce que vous voulez faire avec cette photo.",
52
+ "bot.file_downloading": "⏳ Téléchargement du fichier...",
53
+ "bot.file_too_large": "⚠️ Le fichier est trop volumineux (max {maxSizeMb}MB)",
54
+ "bot.file_download_error": "🔴 Impossible de télécharger le fichier",
55
+ "bot.model_no_pdf": "⚠️ Le modèle actuel ne prend pas en charge les PDF. Envoi du texte uniquement.",
56
+ "bot.text_file_too_large": "⚠️ Le fichier texte est trop volumineux (max {maxSizeKb}KB)",
57
+ "status.header_running": "🟢 Le serveur OpenCode est en cours d'exécution",
58
+ "status.health.healthy": "Sain",
59
+ "status.health.unhealthy": "Dégradé",
60
+ "status.line.health": "Statut : {health}",
61
+ "status.line.version": "Version : {version}",
62
+ "status.line.managed_yes": "Démarré par le bot : Oui",
63
+ "status.line.managed_no": "Démarré par le bot : Non",
64
+ "status.line.pid": "PID : {pid}",
65
+ "status.line.uptime_sec": "Temps de fonctionnement : {seconds} sec",
66
+ "status.line.mode": "Mode : {mode}",
67
+ "status.line.model": "Modèle : {model}",
68
+ "status.agent_not_set": "non défini",
69
+ "status.project_selected": "Projet : {project}",
70
+ "status.project_not_selected": "Projet : non sélectionné",
71
+ "status.project_hint": "Utilisez /projects pour sélectionner un projet",
72
+ "status.session_selected": "Session actuelle : {title}",
73
+ "status.session_not_selected": "Session actuelle : non sélectionnée",
74
+ "status.session_hint": "Utilisez /sessions pour en sélectionner une ou /new pour en créer une",
75
+ "status.server_unavailable": "🔴 Le serveur OpenCode est indisponible\n\nUtilisez /opencode_start pour démarrer le serveur.",
76
+ "projects.empty": "📭 Aucun projet trouvé.\n\nOuvrez un répertoire dans OpenCode et créez au moins une session, il apparaîtra ensuite ici.",
77
+ "projects.select": "Sélectionnez un projet :",
78
+ "projects.select_with_current": "Sélectionnez un projet :\n\nActuel : 🏗 {project}",
79
+ "projects.page_indicator": "Page {current}/{total}",
80
+ "projects.prev_page": "⬅️ Précédent",
81
+ "projects.next_page": "Suivant ➡️",
82
+ "projects.fetch_error": "🔴 Le serveur OpenCode est indisponible ou une erreur s'est produite lors du chargement des projets.",
83
+ "projects.page_load_error": "Impossible de charger cette page. Veuillez réessayer.",
84
+ "projects.selected": "✅ Projet sélectionné : {project}\n\n📋 La session a été réinitialisée. Utilisez /sessions ou /new pour ce projet.",
85
+ "projects.select_error": "🔴 Impossible de sélectionner le projet.",
86
+ "sessions.project_not_selected": "🏗 Aucun projet n'est sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
87
+ "sessions.empty": "📭 Aucune session trouvée.\n\nCréez une nouvelle session avec /new.",
88
+ "sessions.select": "Sélectionnez une session :",
89
+ "sessions.select_page": "Sélectionnez une session (page {page}) :",
90
+ "sessions.fetch_error": "🔴 Le serveur OpenCode est indisponible ou une erreur s'est produite lors du chargement des sessions.",
91
+ "sessions.select_project_first": "🔴 Aucun projet n'est sélectionné. Utilisez /projects.",
92
+ "sessions.page_empty_callback": "Aucune session sur cette page",
93
+ "sessions.page_load_error_callback": "Impossible de charger cette page. Veuillez réessayer.",
94
+ "sessions.button.prev_page": "⬅️ Préc.",
95
+ "sessions.button.next_page": "Suiv. ➡️",
96
+ "sessions.loading_context": "⏳ Chargement du contexte et des derniers messages...",
97
+ "sessions.selected": "✅ Session sélectionnée : {title}",
98
+ "sessions.select_error": "🔴 Impossible de sélectionner la session.",
99
+ "sessions.preview.empty": "Aucun message récent.",
100
+ "sessions.preview.title": "Messages récents :",
101
+ "sessions.preview.you": "Vous :",
102
+ "sessions.preview.agent": "Agent :",
103
+ "new.project_not_selected": "🏗 Aucun projet n'est sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
104
+ "new.created": "✅ Nouvelle session créée : {title}",
105
+ "new.create_error": "🔴 Le serveur OpenCode est indisponible ou une erreur s'est produite lors de la création de la session.",
106
+ "stop.no_active_session": "🛑 L'agent n'a pas été démarré\n\nCréez une session avec /new ou sélectionnez-en une via /sessions.",
107
+ "stop.in_progress": "🛑 Flux d'événements arrêté, envoi du signal d'abandon...\n\nEn attente de l'arrêt de l'agent.",
108
+ "stop.warn_unconfirmed": "⚠️ Le flux d'événements a été arrêté, mais le serveur n'a pas confirmé l'abandon.\n\nVérifiez /status et réessayez /abort dans quelques secondes.",
109
+ "stop.warn_maybe_finished": "⚠️ Le flux d'événements a été arrêté, mais l'agent a peut-être déjà terminé.",
110
+ "stop.success": "✅ Action de l'agent interrompue. Aucun autre message de cette exécution ne sera envoyé.",
111
+ "stop.warn_still_busy": "⚠️ Le signal a été envoyé, mais l'agent est toujours occupé.\n\nLe flux d'événements est déjà désactivé, donc aucun message intermédiaire ne sera envoyé.",
112
+ "stop.warn_timeout": "⚠️ Délai dépassé pour la requête d'abandon.\n\nLe flux d'événements est déjà arrêté, réessayez /abort dans quelques secondes.",
113
+ "stop.warn_local_only": "⚠️ Le flux d'événements a été arrêté localement, mais l'abandon côté serveur a échoué.",
114
+ "stop.error": "🔴 Impossible d'arrêter l'action.\n\nLe flux d'événements est arrêté, essayez /abort à nouveau.",
115
+ "opencode_start.already_running_managed": "⚠️ Le serveur OpenCode est déjà en cours d'exécution\n\nPID : {pid}\nTemps de fonctionnement : {seconds} secondes",
116
+ "opencode_start.already_running_external": "✅ Le serveur OpenCode est déjà en cours d'exécution en tant que processus externe\n\nVersion : {version}\n\nCe serveur n'a pas été démarré par le bot, donc /opencode-stop ne peut pas l'arrêter.",
117
+ "opencode_start.starting": "🔄 Démarrage du serveur OpenCode...",
118
+ "opencode_start.start_error": "🔴 Impossible de démarrer le serveur OpenCode\n\nErreur : {error}\n\nVérifiez que l'interface en ligne de commande OpenCode est installée et disponible dans le PATH :\nopencode --version\nnpm install -g @opencode-ai/cli",
119
+ "opencode_start.started_not_ready": "⚠️ Le serveur OpenCode a démarré, mais ne répond pas encore\n\nPID : {pid}\n\nLe serveur est peut-être encore en cours de démarrage. Essayez /status dans quelques secondes.",
120
+ "opencode_start.success": "✅ Serveur OpenCode démarré avec succès\n\nPID : {pid}\nVersion : {version}",
121
+ "opencode_start.error": "🔴 Une erreur s'est produite lors du démarrage du serveur.\n\nConsultez les logs de l'application pour plus de détails.",
122
+ "opencode_stop.external_running": "⚠️ Le serveur OpenCode s'exécute comme processus externe\n\nCe serveur n'a pas été démarré via /opencode-start.\nArrêtez-le manuellement ou utilisez /status pour vérifier son état.",
123
+ "opencode_stop.not_running": "⚠️ Le serveur OpenCode n'est pas en cours d'exécution",
124
+ "opencode_stop.stopping": "🛑 Arrêt du serveur OpenCode...\n\nPID : {pid}",
125
+ "opencode_stop.stop_error": "🔴 Impossible d'arrêter le serveur OpenCode\n\nErreur : {error}",
126
+ "opencode_stop.success": "✅ Serveur OpenCode arrêté avec succès",
127
+ "opencode_stop.error": "🔴 Une erreur s'est produite lors de l'arrêt du serveur.\n\nConsultez les logs de l'application pour plus de détails.",
128
+ "agent.changed_callback": "Mode modifié : {name}",
129
+ "agent.changed_message": "✅ Mode défini sur : {name}",
130
+ "agent.change_error_callback": "Impossible de modifier le mode",
131
+ "agent.menu.current": "Mode actuel : {name}\n\nSélectionnez un mode :",
132
+ "agent.menu.select": "Sélectionnez un mode de travail :",
133
+ "agent.menu.empty": "⚠️ Aucun mode disponible",
134
+ "agent.menu.error": "🔴 Impossible de récupérer la liste des modes",
135
+ "model.changed_callback": "Modèle modifié : {name}",
136
+ "model.changed_message": "✅ Modèle défini sur : {name}",
137
+ "model.change_error_callback": "Impossible de modifier le modèle",
138
+ "model.menu.empty": "⚠️ Aucun modèle disponible",
139
+ "model.menu.select": "Sélectionnez un modèle :",
140
+ "model.menu.current": "Modèle actuel : {name}\n\nSélectionnez un modèle :",
141
+ "model.menu.favorites_title": "⭐ Favoris (ajoutez des modèles aux favoris dans l'interface OpenCode)",
142
+ "model.menu.favorites_empty": "— Vide.",
143
+ "model.menu.recent_title": "🕘 Récents",
144
+ "model.menu.recent_empty": "— Vide.",
145
+ "model.menu.favorites_hint": "ℹ️ Ajoutez des modèles aux favoris dans l'interface OpenCode pour les garder en tête de liste.",
146
+ "model.menu.error": "🔴 Impossible de récupérer la liste des modèles",
147
+ "variant.model_not_selected_callback": "Erreur : aucun modèle sélectionné",
148
+ "variant.changed_callback": "Variante modifiée : {name}",
149
+ "variant.changed_message": "✅ Variante définie sur : {name}",
150
+ "variant.change_error_callback": "Impossible de modifier la variante",
151
+ "variant.select_model_first": "⚠️ Sélectionnez d'abord un modèle",
152
+ "variant.menu.empty": "⚠️ Aucune variante disponible",
153
+ "variant.menu.current": "Variante actuelle : {name}\n\nSélectionnez une variante :",
154
+ "variant.menu.error": "🔴 Impossible de récupérer la liste des variantes",
155
+ "context.button.confirm": "✅ Oui, compacter le contexte",
156
+ "context.no_active_session": "⚠️ Aucune session active. Créez une session avec /new",
157
+ "context.confirm_text": "📊 Réduction du contexte pour la session \"{title}\"\n\nCela réduira l'utilisation du contexte en supprimant les anciens messages de l'historique. La tâche en cours ne sera pas interrompue.\n\nContinuer ?",
158
+ "context.callback_session_not_found": "Session introuvable",
159
+ "context.callback_compacting": "Réduction du contexte en cours...",
160
+ "context.progress": "⏳ Réduction du contexte en cours...",
161
+ "context.error": "❌ La réduction du contexte a échoué",
162
+ "context.success": "✅ Contexte compacté avec succès",
163
+ "permission.inactive_callback": "La demande d'autorisation est inactive",
164
+ "permission.processing_error_callback": "Erreur de traitement",
165
+ "permission.no_active_request_callback": "Erreur : aucune demande active",
166
+ "permission.reply.once": "Autorisé une fois",
167
+ "permission.reply.always": "Toujours autorisé",
168
+ "permission.reply.reject": "Refusé",
169
+ "permission.send_reply_error": "❌ Impossible d'envoyer la réponse d'autorisation",
170
+ "permission.blocked.expected_reply": "⚠️ Veuillez d'abord répondre à la demande d'autorisation avec les boutons ci-dessus.",
171
+ "permission.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible tant que vous n'avez pas répondu à la demande d'autorisation.",
172
+ "permission.header": "{emoji} Demande d'autorisation : {name}\n\n",
173
+ "permission.button.allow": "✅ Autoriser une fois",
174
+ "permission.button.always": "🔓 Toujours autoriser",
175
+ "permission.button.reject": "❌ Refuser",
176
+ "permission.name.bash": "Bash",
177
+ "permission.name.edit": "Modifier",
178
+ "permission.name.write": "Écrire",
179
+ "permission.name.read": "Lire",
180
+ "permission.name.webfetch": "Récupération web",
181
+ "permission.name.websearch": "Recherche web",
182
+ "permission.name.glob": "Recherche de fichiers",
183
+ "permission.name.grep": "Recherche de contenu",
184
+ "permission.name.list": "Lister le répertoire",
185
+ "permission.name.task": "Tâche",
186
+ "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "Répertoire externe",
188
+ "question.inactive_callback": "Le sondage est inactif",
189
+ "question.processing_error_callback": "Erreur de traitement",
190
+ "question.select_one_required_callback": "Sélectionnez au moins une option",
191
+ "question.enter_custom_callback": "Envoyez votre réponse personnalisée sous forme de message",
192
+ "question.cancelled": "❌ Sondage annulé",
193
+ "question.answer_already_received": "Réponse déjà reçue, veuillez patienter...",
194
+ "question.completed_no_answers": "✅ Sondage terminé (aucune réponse)",
195
+ "question.no_active_project": "❌ Aucun projet actif",
196
+ "question.no_active_request": "❌ Aucune demande active",
197
+ "question.send_answers_error": "❌ Impossible d'envoyer les réponses à l'agent",
198
+ "question.multi_hint": "\n(Vous pouvez sélectionner plusieurs options)",
199
+ "question.button.submit": "✅ Terminer",
200
+ "question.button.custom": "🔤 Réponse personnalisée",
201
+ "question.button.cancel": "❌ Annuler",
202
+ "question.use_custom_button_first": "⚠️ Pour envoyer du texte, appuyez d'abord sur « Réponse personnalisée » pour la question actuelle.",
203
+ "question.summary.title": "✅ Sondage terminé !\n\n",
204
+ "question.summary.question": "Question {index} :\n{question}\n\n",
205
+ "question.summary.answer": "Réponse :\n{answer}\n\n",
206
+ "keyboard.agent_mode": "{emoji} Mode {name}",
207
+ "keyboard.context": "📊 {used} / {limit} ({percent}%)",
208
+ "keyboard.context_empty": "📊 0",
209
+ "keyboard.variant": "💭 {name}",
210
+ "keyboard.variant_default": "💡 Par défaut",
211
+ "keyboard.updated": "⌨️ Clavier mis à jour",
212
+ "pinned.default_session_title": "nouvelle session",
213
+ "pinned.unknown": "Inconnu",
214
+ "pinned.line.project": "Projet : {project}",
215
+ "pinned.line.model": "Modèle : {model}",
216
+ "pinned.line.context": "Contexte : {used} / {limit} ({percent}%)",
217
+ "pinned.files.title": "Fichiers ({count}) :",
218
+ "pinned.files.item": " {path}{diff}",
219
+ "pinned.files.more": " ... et encore {count}",
220
+ "tool.todo.overflow": "*({count} tâches supplémentaires)*",
221
+ "tool.file_header.write": "Écrire Fichier/Chemin : {path}\n============================================================\n\n",
222
+ "tool.file_header.edit": "Modifier Fichier/Chemin : {path}\n============================================================\n\n",
223
+ "runtime.wizard.ask_token": "Entrez le token du bot Telegram (obtenez-le auprès de @BotFather).\n> ",
224
+ "runtime.wizard.ask_language": "Sélectionnez la langue de l'interface.\nEntrez le numéro de la langue dans la liste ou le code locale.\nAppuyez sur Entrée pour conserver la langue par défaut : {defaultLocale}\n{options}\n> ",
225
+ "runtime.wizard.language_invalid": "Entrez un numéro de langue de la liste ou un code locale pris en charge.\n",
226
+ "runtime.wizard.language_selected": "Langue sélectionnée : {language}\n",
227
+ "runtime.wizard.token_required": "Le token est requis. Veuillez réessayer.\n",
228
+ "runtime.wizard.token_invalid": "Le token semble invalide (format attendu <id>:<secret>). Veuillez réessayer.\n",
229
+ "runtime.wizard.ask_user_id": "Entrez votre identifiant utilisateur Telegram (vous pouvez l'obtenir auprès de @userinfobot).\n> ",
230
+ "runtime.wizard.user_id_invalid": "Entrez un entier positif (> 0).\n",
231
+ "runtime.wizard.ask_api_url": "Entrez l'URL de l'API OpenCode (optionnel).\nAppuyez sur Entrée pour utiliser la valeur par défaut : {defaultUrl}\n> ",
232
+ "runtime.wizard.ask_server_username": "Entrez le nom d'utilisateur du serveur OpenCode (optionnel).\nAppuyez sur Entrée pour utiliser la valeur par défaut : {defaultUsername}\n> ",
233
+ "runtime.wizard.ask_server_password": "Entrez le mot de passe du serveur OpenCode (optionnel).\nAppuyez sur Entrée pour le laisser vide.\n> ",
234
+ "runtime.wizard.api_url_invalid": "Entrez une URL valide (http/https) ou appuyez sur Entrée pour la valeur par défaut.\n",
235
+ "runtime.wizard.start": "Configuration d'OpenCode Telegram Bot.\n",
236
+ "runtime.wizard.saved": "Configuration enregistrée :\n- {envPath}\n- {settingsPath}\n",
237
+ "runtime.wizard.not_configured_starting": "L'application n'est pas encore configurée. Lancement de l'assistant...\n",
238
+ "runtime.wizard.tty_required": "L'assistant interactif nécessite un terminal TTY. Exécutez `opencode-telegram config` dans un shell interactif.",
239
+ "rename.no_session": "⚠️ Aucune session active. Créez ou sélectionnez d'abord une session.",
240
+ "rename.prompt": "📝 Entrez le nouveau titre de la session :\n\nActuel : {title}",
241
+ "rename.empty_title": "⚠️ Le titre ne peut pas être vide.",
242
+ "rename.success": "✅ Session renommée en : {title}",
243
+ "rename.error": "🔴 Impossible de renommer la session.",
244
+ "rename.cancelled": "❌ Renommage annulé.",
245
+ "rename.inactive_callback": "La demande de renommage est inactive",
246
+ "rename.inactive": "⚠️ La demande de renommage n'est pas active. Exécutez /rename à nouveau.",
247
+ "rename.blocked.expected_name": "⚠️ Entrez le nouveau nom de la session sous forme de texte ou appuyez sur Annuler dans le message de renommage.",
248
+ "rename.blocked.command_not_allowed": "⚠️ Cette commande n'est pas disponible tant que le renommage attend un nouveau nom.",
249
+ "rename.button.cancel": "❌ Annuler",
250
+ "commands.select": "Choisissez une commande OpenCode :",
251
+ "commands.empty": "📭 Aucune commande OpenCode n'est disponible pour ce projet.",
252
+ "commands.fetch_error": "🔴 Impossible de charger les commandes OpenCode.",
253
+ "commands.no_description": "Aucune description",
254
+ "commands.button.execute": "✅ Exécuter",
255
+ "commands.button.cancel": "❌ Annuler",
256
+ "commands.confirm": "Confirmez l'exécution de la commande {command}. Pour l'exécuter avec des arguments, envoyez-les dans un message.",
257
+ "commands.inactive_callback": "Ce menu de commandes est inactif",
258
+ "commands.cancelled_callback": "Annulé",
259
+ "commands.execute_callback": "Exécution de la commande...",
260
+ "commands.executing_prefix": "⚡ Exécution de la commande :",
261
+ "commands.arguments_empty": "⚠️ Les arguments ne peuvent pas être vides. Envoyez du texte ou appuyez sur Exécuter.",
262
+ "commands.execute_error": "🔴 Impossible d'exécuter la commande OpenCode.",
263
+ "cmd.description.rename": "Renommer la session actuelle",
264
+ "cli.usage": "Utilisation :\n opencode-telegram [start] [--mode sources|installed]\n opencode-telegram status\n opencode-telegram stop\n opencode-telegram config\n\nNotes :\n - Sans commande, `start` est utilisé par défaut\n - `--mode` n'est actuellement pris en charge que pour `start`",
265
+ "cli.placeholder.status": "La commande `status` est actuellement un placeholder. Les vraies vérifications d'état seront ajoutées dans la couche service (Phase 5).",
266
+ "cli.placeholder.stop": "La commande `stop` est actuellement un placeholder. Le véritable arrêt du processus en arrière-plan sera ajouté dans la couche service (Phase 5).",
267
+ "cli.placeholder.unavailable": "Commande indisponible.",
268
+ "cli.error.prefix": "Erreur CLI : {message}",
269
+ "cli.args.unknown_command": "Commande inconnue : {value}",
270
+ "cli.args.mode_requires_value": "L'option --mode nécessite une valeur : sources|installed",
271
+ "cli.args.invalid_mode": "Valeur de mode invalide : {value}. Attendu : sources|installed",
272
+ "cli.args.unknown_option": "Option inconnue : {value}",
273
+ "cli.args.mode_only_start": "L'option --mode est prise en charge uniquement pour la commande start",
274
+ "legacy.models.fetch_error": "🔴 Impossible de récupérer la liste des modèles. Vérifiez l'état du serveur avec /status.",
275
+ "legacy.models.empty": "📋 Aucun modèle disponible. Configurez les fournisseurs dans OpenCode.",
276
+ "legacy.models.header": "📋 Modèles disponibles :\n\n",
277
+ "legacy.models.no_provider_models": " ⚠️ Aucun modèle disponible\n",
278
+ "legacy.models.env_hint": "💡 Pour utiliser le modèle dans .env :\n",
279
+ "legacy.models.error": "🔴 Une erreur s'est produite lors du chargement de la liste des modèles.",
280
+ "stt.recognizing": "🎤 Reconnaissance audio en cours...",
281
+ "stt.recognized": "🎤 Reconnu :\n{text}",
282
+ "stt.not_configured": "🎤 La reconnaissance vocale n'est pas configurée.\n\nDéfinissez STT_API_URL et STT_API_KEY dans .env pour l'activer.",
283
+ "stt.error": "🔴 Impossible de reconnaître l'audio : {error}",
284
+ "stt.empty_result": "🎤 Aucune parole détectée dans le message audio.",
285
+ };
@@ -1,6 +1,7 @@
1
1
  import { en } from "./en.js";
2
2
  import { de } from "./de.js";
3
3
  import { es } from "./es.js";
4
+ import { fr } from "./fr.js";
4
5
  import { ru } from "./ru.js";
5
6
  import { zh } from "./zh.js";
6
7
  const LOCALE_DEFINITIONS = [
@@ -22,6 +23,12 @@ const LOCALE_DEFINITIONS = [
22
23
  dateLocale: "es-ES",
23
24
  dictionary: es,
24
25
  },
26
+ {
27
+ code: "fr",
28
+ label: "Français",
29
+ dateLocale: "fr-FR",
30
+ dictionary: fr,
31
+ },
25
32
  {
26
33
  code: "ru",
27
34
  label: "Русский",
package/dist/i18n/ru.js CHANGED
@@ -54,7 +54,7 @@ export const ru = {
54
54
  "bot.file_download_error": "🔴 Не удалось скачать файл",
55
55
  "bot.model_no_pdf": "⚠️ Текущая модель не поддерживает PDF. Отправляю только текст.",
56
56
  "bot.text_file_too_large": "⚠️ Текстовый файл слишком большой (макс. {maxSizeKb}КБ)",
57
- "status.header_running": "🟢 **OpenCode Server запущен**",
57
+ "status.header_running": "🟢 OpenCode Server запущен",
58
58
  "status.health.healthy": "Healthy",
59
59
  "status.health.unhealthy": "Unhealthy",
60
60
  "status.line.health": "Статус: {health}",
@@ -115,7 +115,7 @@ export const ru = {
115
115
  "opencode_start.already_running_managed": "⚠️ OpenCode Server уже запущен\n\nPID: {pid}\nUptime: {seconds} секунд",
116
116
  "opencode_start.already_running_external": "✅ OpenCode Server уже запущен внешним процессом\n\nВерсия: {version}\n\nЭтот сервер не был запущен через бота, поэтому команда /opencode-stop не сможет его остановить.",
117
117
  "opencode_start.starting": "🔄 Запускаю OpenCode Server...",
118
- "opencode_start.start_error": "🔴 Не удалось запустить OpenCode Server\n\nОшибка: {error}\n\nПроверьте, что OpenCode CLI установлен и доступен в PATH:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
118
+ "opencode_start.start_error": "🔴 Не удалось запустить OpenCode Server\n\nОшибка: {error}\n\nПроверьте, что OpenCode CLI установлен и доступен в PATH:\nopencode --version\nnpm install -g @opencode-ai/cli",
119
119
  "opencode_start.started_not_ready": "⚠️ OpenCode Server запущен, но не отвечает\n\nPID: {pid}\n\nСервер может запускаться. Попробуйте /status через несколько секунд.",
120
120
  "opencode_start.success": "✅ OpenCode Server успешно запущен\n\nPID: {pid}\nВерсия: {version}",
121
121
  "opencode_start.error": "🔴 Произошла ошибка при запуске сервера.\n\nПроверьте логи приложения для подробностей.",
@@ -169,7 +169,7 @@ export const ru = {
169
169
  "permission.send_reply_error": "❌ Не удалось отправить ответ на запрос разрешения",
170
170
  "permission.blocked.expected_reply": "⚠️ Сначала ответьте на запрос разрешения кнопками выше.",
171
171
  "permission.blocked.command_not_allowed": "⚠️ Эта команда недоступна, пока вы не ответите на запрос разрешения.",
172
- "permission.header": "{emoji} **Запрос разрешения: {name}**\n\n",
172
+ "permission.header": "{emoji} Запрос разрешения: {name}\n\n",
173
173
  "permission.button.allow": "✅ Разрешить один раз",
174
174
  "permission.button.always": "🔓 Разрешить всегда",
175
175
  "permission.button.reject": "❌ Отклонить",
@@ -184,6 +184,7 @@ export const ru = {
184
184
  "permission.name.list": "List Directory",
185
185
  "permission.name.task": "Task",
186
186
  "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "Внешняя директория",
187
188
  "question.inactive_callback": "Опрос неактивен",
188
189
  "question.processing_error_callback": "Ошибка при обработке",
189
190
  "question.select_one_required_callback": "Выберите хотя бы один вариант",
@@ -194,7 +195,7 @@ export const ru = {
194
195
  "question.no_active_project": "❌ Нет активного проекта",
195
196
  "question.no_active_request": "❌ Нет активного запроса",
196
197
  "question.send_answers_error": "❌ Не удалось отправить ответы агенту",
197
- "question.multi_hint": "\n*Можно выбрать несколько вариантов*",
198
+ "question.multi_hint": "\n(Можно выбрать несколько вариантов)",
198
199
  "question.button.submit": "✅ Готово",
199
200
  "question.button.custom": "🔤 Свой ответ",
200
201
  "question.button.cancel": "❌ Отмена",
@@ -272,7 +273,7 @@ export const ru = {
272
273
  "cli.args.mode_only_start": "Опция --mode поддерживается только для команды start",
273
274
  "legacy.models.fetch_error": "🔴 Не удалось получить список моделей. Проверьте статус сервера /status.",
274
275
  "legacy.models.empty": "📋 Нет доступных моделей. Настройте провайдеры через OpenCode.",
275
- "legacy.models.header": "📋 **Доступные модели:**\n\n",
276
+ "legacy.models.header": "📋 Доступные модели:\n\n",
276
277
  "legacy.models.no_provider_models": " ⚠️ Нет доступных моделей\n",
277
278
  "legacy.models.env_hint": "💡 Для использования модели в .env:\n",
278
279
  "legacy.models.error": "🔴 Произошла ошибка при получении списка моделей.",
package/dist/i18n/zh.js CHANGED
@@ -54,7 +54,7 @@ export const zh = {
54
54
  "bot.file_download_error": "🔴 下载文件失败",
55
55
  "bot.model_no_pdf": "⚠️ 当前模型不支持PDF输入。将仅发送文本。",
56
56
  "bot.text_file_too_large": "⚠️ 文本文件过大(最大 {maxSizeKb}KB)",
57
- "status.header_running": "🟢 **OpenCode 服务器正在运行**",
57
+ "status.header_running": "🟢 OpenCode 服务器正在运行",
58
58
  "status.health.healthy": "健康",
59
59
  "status.health.unhealthy": "不健康",
60
60
  "status.line.health": "状态:{health}",
@@ -115,7 +115,7 @@ export const zh = {
115
115
  "opencode_start.already_running_managed": "⚠️ OpenCode 服务器已在运行\n\nPID:{pid}\n运行时间:{seconds} 秒",
116
116
  "opencode_start.already_running_external": "✅ OpenCode 服务器正作为外部进程运行\n\n版本:{version}\n\n该服务器不是由机器人启动,因此 /opencode-stop 无法停止它。",
117
117
  "opencode_start.starting": "🔄 正在启动 OpenCode 服务器...",
118
- "opencode_start.start_error": "🔴 启动 OpenCode 服务器失败\n\n错误:{error}\n\n请检查 OpenCode CLI 已安装且在 PATH 中可用:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
118
+ "opencode_start.start_error": "🔴 启动 OpenCode 服务器失败\n\n错误:{error}\n\n请检查 OpenCode CLI 已安装且在 PATH 中可用:\nopencode --version\nnpm install -g @opencode-ai/cli",
119
119
  "opencode_start.started_not_ready": "⚠️ OpenCode 服务器已启动,但未响应\n\nPID:{pid}\n\n服务器可能仍在启动中。几秒后试试 /status。",
120
120
  "opencode_start.success": "✅ OpenCode 服务器启动成功\n\nPID:{pid}\n版本:{version}",
121
121
  "opencode_start.error": "🔴 启动服务器时发生错误。\n\n请查看应用日志了解详情。",
@@ -169,7 +169,7 @@ export const zh = {
169
169
  "permission.send_reply_error": "❌ 发送权限回复失败",
170
170
  "permission.blocked.expected_reply": "⚠️ 请先使用上方按钮回答权限请求。",
171
171
  "permission.blocked.command_not_allowed": "⚠️ 在你回答权限请求之前不可用此命令。",
172
- "permission.header": "{emoji} **权限请求:{name}**\n\n",
172
+ "permission.header": "{emoji} 权限请求:{name}\n\n",
173
173
  "permission.button.allow": "✅ 允许一次",
174
174
  "permission.button.always": "🔓 始终允许",
175
175
  "permission.button.reject": "❌ 拒绝",
@@ -184,6 +184,7 @@ export const zh = {
184
184
  "permission.name.list": "列出目录",
185
185
  "permission.name.task": "任务",
186
186
  "permission.name.lsp": "LSP",
187
+ "permission.name.external_directory": "外部目录",
187
188
  "question.inactive_callback": "投票已失效",
188
189
  "question.processing_error_callback": "处理错误",
189
190
  "question.select_one_required_callback": "请至少选择一个选项",
@@ -194,7 +195,7 @@ export const zh = {
194
195
  "question.no_active_project": "❌ 没有活动项目",
195
196
  "question.no_active_request": "❌ 没有活动请求",
196
197
  "question.send_answers_error": "❌ 向代理发送答案失败",
197
- "question.multi_hint": "\n*你可以选择多个选项*",
198
+ "question.multi_hint": "\n(你可以选择多个选项)",
198
199
  "question.button.submit": "✅ 完成",
199
200
  "question.button.custom": "🔤 自定义回答",
200
201
  "question.button.cancel": "❌ 取消",
@@ -272,7 +273,7 @@ export const zh = {
272
273
  "cli.args.mode_only_start": "选项 --mode 仅支持 start 命令",
273
274
  "legacy.models.fetch_error": "🔴 获取模型列表失败。请使用 /status 检查服务器状态。",
274
275
  "legacy.models.empty": "📋 没有可用模型。请在 OpenCode 中配置 providers。",
275
- "legacy.models.header": "📋 **可用模型:**\n\n",
276
+ "legacy.models.header": "📋 可用模型:\n\n",
276
277
  "legacy.models.no_provider_models": " ⚠️ 没有可用模型\n",
277
278
  "legacy.models.env_hint": "💡 在 .env 中使用该模型:\n",
278
279
  "legacy.models.error": "🔴 加载模型列表时发生错误。",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grinev/opencode-telegram-bot",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "Telegram bot client for OpenCode to run and monitor coding tasks from chat.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",