@grinev/opencode-telegram-bot 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/i18n/fr.js CHANGED
@@ -5,9 +5,11 @@ export const fr = {
5
5
  "cmd.description.sessions": "Lister les sessions",
6
6
  "cmd.description.tts": "Basculer les réponses audio",
7
7
  "cmd.description.projects": "Lister les projets",
8
+ "cmd.description.worktree": "Changer de worktree git",
8
9
  "cmd.description.task": "Créer une tâche planifiée",
9
10
  "cmd.description.tasklist": "Afficher les tâches planifiées",
10
11
  "cmd.description.commands": "Commandes personnalisées",
12
+ "cmd.description.skills": "Catalogue de skills",
11
13
  "cmd.description.opencode_start": "Démarrer le serveur OpenCode",
12
14
  "cmd.description.opencode_stop": "Arrêter le serveur OpenCode",
13
15
  "cmd.description.help": "Aide",
@@ -33,7 +35,7 @@ export const fr = {
33
35
  "inline.cancelled_callback": "Annulé",
34
36
  "common.unknown": "inconnu",
35
37
  "common.unknown_error": "erreur inconnue",
36
- "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/task — tâche planifiée\n/tasklist — tâches planifiées\n/status — statut\n/help — aide\n\nUtilisez les boutons du bas pour choisir l'agent, le modèle et la variante.",
38
+ "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/commands — commandes personnalisées\n/skills — catalogue de skills\n/task — tâche planifiée\n/tasklist — tâches planifiées\n/status — statut\n/help — aide\n\nUtilisez les boutons du bas pour choisir l'agent, le modèle et la variante.",
37
39
  "help.keyboard_hint": "💡 Utilisez les boutons du bas pour l'agent, le modèle, la variante et les actions de contexte.",
38
40
  "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",
39
41
  "bot.thinking": "💭 Réflexion en cours...",
@@ -73,6 +75,7 @@ export const fr = {
73
75
  "status.tts.off": "Désactivées",
74
76
  "status.agent_not_set": "non défini",
75
77
  "status.project_selected": "Projet : {project}",
78
+ "status.worktree_selected": "Worktree : {worktree}",
76
79
  "status.project_not_selected": "Projet : non sélectionné",
77
80
  "status.project_hint": "Utilisez /projects pour sélectionner un projet",
78
81
  "status.session_selected": "Session actuelle : {title}",
@@ -124,15 +127,20 @@ export const fr = {
124
127
  "stop.error": "🔴 Impossible d'arrêter l'action.\n\nLe flux d'événements est arrêté, essayez /abort à nouveau.",
125
128
  "opencode_start.already_running_managed": "⚠️ Le serveur OpenCode est déjà en cours d'exécution\n\nPID : {pid}\nTemps de fonctionnement : {seconds} secondes",
126
129
  "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.",
130
+ "opencode_start.already_running": "✅ Le serveur OpenCode est déjà en cours d'exécution\n\nVersion : {version}",
131
+ "opencode_start.remote_configured": "⚠️ /opencode_start fonctionne uniquement avec un serveur OpenCode local.",
127
132
  "opencode_start.starting": "🔄 Démarrage du serveur OpenCode...",
128
133
  "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",
129
134
  "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.",
130
135
  "opencode_start.success": "✅ Serveur OpenCode démarré avec succès\n\nPID : {pid}\nVersion : {version}",
131
136
  "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.",
132
137
  "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.",
138
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop fonctionne uniquement avec un serveur OpenCode local.",
133
139
  "opencode_stop.not_running": "⚠️ Le serveur OpenCode n'est pas en cours d'exécution",
140
+ "opencode_stop.pid_not_found": "⚠️ Le serveur OpenCode répond sur le port {port}, mais aucun processus local à arrêter n'a été trouvé.",
134
141
  "opencode_stop.stopping": "🛑 Arrêt du serveur OpenCode...\n\nPID : {pid}",
135
142
  "opencode_stop.stop_error": "🔴 Impossible d'arrêter le serveur OpenCode\n\nErreur : {error}",
143
+ "opencode_stop.still_running": "Le serveur répond toujours après la demande d'arrêt.",
136
144
  "opencode_stop.success": "✅ Serveur OpenCode arrêté avec succès",
137
145
  "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.",
138
146
  "agent.changed_callback": "Agent modifié : {name}",
@@ -222,6 +230,7 @@ export const fr = {
222
230
  "pinned.default_session_title": "nouvelle session",
223
231
  "pinned.unknown": "Inconnu",
224
232
  "pinned.line.project": "Projet : {project}",
233
+ "pinned.line.worktree": "Worktree : {worktree}",
225
234
  "pinned.line.model": "Modèle : {model}",
226
235
  "pinned.line.context": "Contexte : {used} / {limit} ({percent}%)",
227
236
  "pinned.line.cost": "Coût : {cost} dépensé",
@@ -323,17 +332,24 @@ export const fr = {
323
332
  "commands.button.next_page": "Suivant ➡️",
324
333
  "commands.page_empty_callback": "Aucune commande sur cette page",
325
334
  "commands.page_load_error_callback": "Impossible de charger cette page. Veuillez réessayer.",
335
+ "skills.select": "Choisissez un skill OpenCode :",
336
+ "skills.empty": "📭 Aucun skill OpenCode n'est disponible pour ce projet.",
337
+ "skills.fetch_error": "🔴 Impossible de charger les skills OpenCode.",
338
+ "skills.no_description": "Aucune description",
339
+ "skills.button.execute": "✅ Exécuter",
340
+ "skills.button.cancel": "❌ Annuler",
341
+ "skills.confirm": "Confirmez l'exécution du skill {skill}. Pour l'exécuter avec des arguments, envoyez-les dans un message.",
342
+ "skills.inactive_callback": "Ce menu de skills est inactif",
343
+ "skills.cancelled_callback": "Annulé",
344
+ "skills.execute_callback": "Utilisation du skill...",
345
+ "skills.executing_prefix": "⚡ Utilisation du skill :",
346
+ "skills.arguments_empty": "⚠️ Les arguments ne peuvent pas être vides. Envoyez du texte ou appuyez sur Exécuter.",
347
+ "skills.select_page": "Choisissez un skill OpenCode (page {page}) :",
348
+ "skills.button.prev_page": "⬅️ Précédent",
349
+ "skills.button.next_page": "Suivant ➡️",
350
+ "skills.page_empty_callback": "Aucun skill sur cette page",
351
+ "skills.page_load_error_callback": "Impossible de charger cette page. Veuillez réessayer.",
326
352
  "cmd.description.rename": "Renommer la session actuelle",
327
- "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`",
328
- "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).",
329
- "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).",
330
- "cli.placeholder.unavailable": "Commande indisponible.",
331
- "cli.error.prefix": "Erreur CLI : {message}",
332
- "cli.args.unknown_command": "Commande inconnue : {value}",
333
- "cli.args.mode_requires_value": "L'option --mode nécessite une valeur : sources|installed",
334
- "cli.args.invalid_mode": "Valeur de mode invalide : {value}. Attendu : sources|installed",
335
- "cli.args.unknown_option": "Option inconnue : {value}",
336
- "cli.args.mode_only_start": "L'option --mode est prise en charge uniquement pour la commande start",
337
353
  "legacy.models.fetch_error": "🔴 Impossible de récupérer la liste des modèles. Vérifiez l'état du serveur avec /status.",
338
354
  "legacy.models.empty": "📋 Aucun modèle disponible. Configurez les fournisseurs dans OpenCode.",
339
355
  "legacy.models.header": "📋 Modèles disponibles :\n\n",
@@ -346,6 +362,18 @@ export const fr = {
346
362
  "stt.error": "🔴 Impossible de reconnaître l'audio : {error}",
347
363
  "stt.empty_result": "🎤 Aucune parole détectée dans le message audio.",
348
364
  "cmd.description.open": "Ajouter un projet en parcourant les dossiers",
365
+ "worktree.branch_detached": "detached HEAD",
366
+ "worktree.select_with_current": "Sélectionnez un worktree :",
367
+ "worktree.project_not_selected": "🏗 Aucun projet sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
368
+ "worktree.not_git_repo": "🌿 Les git worktrees ne sont pas disponibles pour le projet actuel. Sélectionnez d'abord un dépôt git.",
369
+ "worktree.not_git_repo_callback": "Le projet actuel n'est pas un dépôt git",
370
+ "worktree.empty": "📭 Aucun git worktree trouvé pour le dépôt actuel.",
371
+ "worktree.fetch_error": "🔴 Impossible de charger les git worktrees.",
372
+ "worktree.page_empty_callback": "Aucun worktree sur cette page",
373
+ "worktree.selection_missing_callback": "Le worktree sélectionné n'est plus disponible",
374
+ "worktree.already_selected_callback": "Ce worktree est déjà sélectionné",
375
+ "worktree.selected": "✅ Worktree sélectionné : {worktree}\n\n📋 La session a été réinitialisée. Utilisez /sessions ou /new pour continuer.",
376
+ "worktree.select_error": "🔴 Impossible de sélectionner le worktree.",
349
377
  "open.back": "⬆️ Remonter",
350
378
  "open.roots": "📋 Retour aux racines",
351
379
  "open.prev_page": "⬅️ Précédent",
package/dist/i18n/ru.js CHANGED
@@ -5,9 +5,11 @@ export const ru = {
5
5
  "cmd.description.sessions": "Список сессий",
6
6
  "cmd.description.tts": "Переключить аудиоответы",
7
7
  "cmd.description.projects": "Список проектов",
8
+ "cmd.description.worktree": "Переключить git worktree",
8
9
  "cmd.description.task": "Создать задачу по расписанию",
9
10
  "cmd.description.tasklist": "Список задач по расписанию",
10
11
  "cmd.description.commands": "Пользовательские команды",
12
+ "cmd.description.skills": "Каталог скиллов",
11
13
  "cmd.description.opencode_start": "Запустить OpenCode сервер",
12
14
  "cmd.description.opencode_stop": "Остановить OpenCode сервер",
13
15
  "cmd.description.help": "Справка",
@@ -33,7 +35,7 @@ export const ru = {
33
35
  "inline.cancelled_callback": "Отменено",
34
36
  "common.unknown": "неизвестна",
35
37
  "common.unknown_error": "неизвестная ошибка",
36
- "start.welcome": "👋 Добро пожаловать в OpenCode Telegram Bot!\n\nИспользуйте команды:\n/projects — выбрать проект\n/sessions — список сессий\n/new — новая сессия\n/task — задача по расписанию\n/tasklist — список задач по расписанию\n/status — статус\n/help — справка\n\nАгент, модель и вариант выбираются кнопками внизу.",
38
+ "start.welcome": "👋 Добро пожаловать в OpenCode Telegram Bot!\n\nИспользуйте команды:\n/projects — выбрать проект\n/sessions — список сессий\n/new — новая сессия\n/commands — пользовательские команды\n/skills — каталог скиллов\n/task — задача по расписанию\n/tasklist — список задач по расписанию\n/status — статус\n/help — справка\n\nАгент, модель и вариант выбираются кнопками внизу.",
37
39
  "help.keyboard_hint": "💡 Агент, модель, вариант и действия с контекстом доступны через нижние кнопки клавиатуры.",
38
40
  "help.text": "📖 **Справка**\n\n/status - Проверить статус сервера\n/sessions - Список сессий\n/new - Создать новую сессию\n/help - Справка",
39
41
  "bot.thinking": "💭 Думаю...",
@@ -73,6 +75,7 @@ export const ru = {
73
75
  "status.tts.off": "Выкл",
74
76
  "status.agent_not_set": "не установлен",
75
77
  "status.project_selected": "Проект: {project}",
78
+ "status.worktree_selected": "Worktree: {worktree}",
76
79
  "status.project_not_selected": "Проект: не выбран",
77
80
  "status.project_hint": "Используйте /projects для выбора проекта",
78
81
  "status.session_selected": "Текущая сессия: {title}",
@@ -124,15 +127,20 @@ export const ru = {
124
127
  "stop.error": "🔴 Ошибка при прерывании действия.\n\nПоток событий остановлен, попробуйте /abort еще раз.",
125
128
  "opencode_start.already_running_managed": "⚠️ OpenCode Server уже запущен\n\nPID: {pid}\nUptime: {seconds} секунд",
126
129
  "opencode_start.already_running_external": "✅ OpenCode Server уже запущен внешним процессом\n\nВерсия: {version}\n\nЭтот сервер не был запущен через бота, поэтому команда /opencode-stop не сможет его остановить.",
130
+ "opencode_start.already_running": "✅ OpenCode Server уже запущен\n\nВерсия: {version}",
131
+ "opencode_start.remote_configured": "⚠️ /opencode_start работает только с локальным OpenCode Server.",
127
132
  "opencode_start.starting": "🔄 Запускаю OpenCode Server...",
128
133
  "opencode_start.start_error": "🔴 Не удалось запустить OpenCode Server\n\nОшибка: {error}\n\nПроверьте, что OpenCode CLI установлен и доступен в PATH:\nopencode --version\nnpm install -g @opencode-ai/cli",
129
134
  "opencode_start.started_not_ready": "⚠️ OpenCode Server запущен, но не отвечает\n\nPID: {pid}\n\nСервер может запускаться. Попробуйте /status через несколько секунд.",
130
135
  "opencode_start.success": "✅ OpenCode Server успешно запущен\n\nPID: {pid}\nВерсия: {version}",
131
136
  "opencode_start.error": "🔴 Произошла ошибка при запуске сервера.\n\nПроверьте логи приложения для подробностей.",
132
137
  "opencode_stop.external_running": "⚠️ OpenCode Server запущен внешним процессом\n\nЭтот сервер не был запущен через /opencode-start.\nОстановите его вручную или используйте /status для проверки состояния.",
138
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop работает только с локальным OpenCode Server.",
133
139
  "opencode_stop.not_running": "⚠️ OpenCode Server не запущен",
140
+ "opencode_stop.pid_not_found": "⚠️ OpenCode Server отвечает на порту {port}, но локальный процесс для остановки найти не удалось.",
134
141
  "opencode_stop.stopping": "🛑 Останавливаю OpenCode Server...\n\nPID: {pid}",
135
142
  "opencode_stop.stop_error": "🔴 Не удалось остановить OpenCode Server\n\nОшибка: {error}",
143
+ "opencode_stop.still_running": "Сервер все еще отвечает после запроса на остановку.",
136
144
  "opencode_stop.success": "✅ OpenCode Server успешно остановлен",
137
145
  "opencode_stop.error": "🔴 Произошла ошибка при остановке сервера.\n\nПроверьте логи приложения для подробностей.",
138
146
  "agent.changed_callback": "Агент изменен: {name}",
@@ -222,6 +230,7 @@ export const ru = {
222
230
  "pinned.default_session_title": "новая сессия",
223
231
  "pinned.unknown": "Неизвестно",
224
232
  "pinned.line.project": "Проект: {project}",
233
+ "pinned.line.worktree": "Worktree: {worktree}",
225
234
  "pinned.line.model": "Модель: {model}",
226
235
  "pinned.line.context": "Контекст: {used} / {limit} ({percent}%)",
227
236
  "pinned.line.cost": "Стоимость: {cost} потрачено",
@@ -323,17 +332,24 @@ export const ru = {
323
332
  "commands.button.next_page": "Вперёд ➡️",
324
333
  "commands.page_empty_callback": "На этой странице нет команд",
325
334
  "commands.page_load_error_callback": "Не удалось загрузить эту страницу. Пожалуйста, попробуйте снова.",
335
+ "skills.select": "Выберите скилл OpenCode:",
336
+ "skills.empty": "📭 Для этого проекта нет доступных скиллов OpenCode.",
337
+ "skills.fetch_error": "🔴 Не удалось загрузить список скиллов OpenCode.",
338
+ "skills.no_description": "Без описания",
339
+ "skills.button.execute": "✅ Выполнить",
340
+ "skills.button.cancel": "❌ Отмена",
341
+ "skills.confirm": "Подтвердите запуск скилла {skill}. Чтобы запустить его с аргументами, отправьте аргументы следующим сообщением.",
342
+ "skills.inactive_callback": "Это меню скиллов уже неактивно",
343
+ "skills.cancelled_callback": "Отменено",
344
+ "skills.execute_callback": "Использую скилл...",
345
+ "skills.executing_prefix": "⚡ Использую скилл:",
346
+ "skills.arguments_empty": "⚠️ Аргументы не могут быть пустыми. Отправьте текст или нажмите Выполнить.",
347
+ "skills.select_page": "Выберите скилл OpenCode (страница {page}):",
348
+ "skills.button.prev_page": "⬅️ Назад",
349
+ "skills.button.next_page": "Вперёд ➡️",
350
+ "skills.page_empty_callback": "На этой странице нет скиллов",
351
+ "skills.page_load_error_callback": "Не удалось загрузить эту страницу. Пожалуйста, попробуйте снова.",
326
352
  "cmd.description.rename": "Переименовать текущую сессию",
327
- "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`",
328
- "cli.placeholder.status": "Команда `status` пока работает как заглушка. Реальная проверка статуса появится на этапе service-слоя (Этап 5).",
329
- "cli.placeholder.stop": "Команда `stop` пока работает как заглушка. Реальная остановка фонового процесса появится на этапе service-слоя (Этап 5).",
330
- "cli.placeholder.unavailable": "Команда недоступна.",
331
- "cli.error.prefix": "CLI error: {message}",
332
- "cli.args.unknown_command": "Неизвестная команда: {value}",
333
- "cli.args.mode_requires_value": "Опция --mode требует значение: sources|installed",
334
- "cli.args.invalid_mode": "Некорректное значение --mode: {value}. Ожидается sources|installed",
335
- "cli.args.unknown_option": "Неизвестная опция: {value}",
336
- "cli.args.mode_only_start": "Опция --mode поддерживается только для команды start",
337
353
  "legacy.models.fetch_error": "🔴 Не удалось получить список моделей. Проверьте статус сервера /status.",
338
354
  "legacy.models.empty": "📋 Нет доступных моделей. Настройте провайдеры через OpenCode.",
339
355
  "legacy.models.header": "📋 Доступные модели:\n\n",
@@ -346,6 +362,18 @@ export const ru = {
346
362
  "stt.error": "🔴 Не удалось распознать аудио: {error}",
347
363
  "stt.empty_result": "🎤 В аудиосообщении не обнаружена речь.",
348
364
  "cmd.description.open": "Добавить проект через обзор папок",
365
+ "worktree.branch_detached": "detached HEAD",
366
+ "worktree.select_with_current": "Выберите worktree:",
367
+ "worktree.project_not_selected": "🏗 Проект не выбран.\n\nСначала выберите проект командой /projects.",
368
+ "worktree.not_git_repo": "🌿 Git worktree недоступны для текущего проекта. Сначала выберите git-репозиторий.",
369
+ "worktree.not_git_repo_callback": "Текущий проект не является git-репозиторием",
370
+ "worktree.empty": "📭 Для текущего репозитория не найдено ни одного worktree.",
371
+ "worktree.fetch_error": "🔴 Не удалось загрузить git worktree.",
372
+ "worktree.page_empty_callback": "На этой странице нет worktree",
373
+ "worktree.selection_missing_callback": "Выбранный worktree больше недоступен",
374
+ "worktree.already_selected_callback": "Этот worktree уже выбран",
375
+ "worktree.selected": "✅ Выбран worktree: {worktree}\n\n📋 Сессия была сброшена. Используйте /sessions или /new для продолжения.",
376
+ "worktree.select_error": "🔴 Не удалось выбрать worktree.",
349
377
  "open.back": "⬆️ Наверх",
350
378
  "open.roots": "📋 К списку корней",
351
379
  "open.prev_page": "⬅️ Назад",
package/dist/i18n/zh.js CHANGED
@@ -5,9 +5,11 @@ export const zh = {
5
5
  "cmd.description.sessions": "列出会话",
6
6
  "cmd.description.tts": "切换语音回复",
7
7
  "cmd.description.projects": "列出项目",
8
+ "cmd.description.worktree": "切换 git worktree",
8
9
  "cmd.description.task": "创建定时任务",
9
10
  "cmd.description.tasklist": "查看定时任务",
10
11
  "cmd.description.commands": "自定义命令",
12
+ "cmd.description.skills": "技能目录",
11
13
  "cmd.description.opencode_start": "启动 OpenCode 服务器",
12
14
  "cmd.description.opencode_stop": "停止 OpenCode 服务器",
13
15
  "cmd.description.help": "帮助",
@@ -33,7 +35,7 @@ export const zh = {
33
35
  "inline.cancelled_callback": "已取消",
34
36
  "common.unknown": "未知",
35
37
  "common.unknown_error": "未知错误",
36
- "start.welcome": "👋 欢迎使用 OpenCode Telegram Bot!\n\n可用命令:\n/projects — 选择项目\n/sessions — 会话列表\n/new — 新建会话\n/task — 定时任务\n/tasklist — 定时任务列表\n/status — 状态\n/help — 帮助\n\n请使用底部按钮选择 Agent、模型和变体。",
38
+ "start.welcome": "👋 欢迎使用 OpenCode Telegram Bot!\n\n可用命令:\n/projects — 选择项目\n/sessions — 会话列表\n/new — 新建会话\n/commands — 自定义命令\n/skills — 技能目录\n/task — 定时任务\n/tasklist — 定时任务列表\n/status — 状态\n/help — 帮助\n\n请使用底部按钮选择 Agent、模型和变体。",
37
39
  "help.keyboard_hint": "💡 Agent、模型、变体和上下文操作请使用底部键盘按钮。",
38
40
  "help.text": "📖 **帮助**\n\n/status - 查看服务器状态\n/sessions - 会话列表\n/new - 创建新会话\n/help - 帮助",
39
41
  "bot.thinking": "💭 思考中...",
@@ -73,6 +75,7 @@ export const zh = {
73
75
  "status.tts.off": "关闭",
74
76
  "status.agent_not_set": "未设置",
75
77
  "status.project_selected": "项目:{project}",
78
+ "status.worktree_selected": "Worktree:{worktree}",
76
79
  "status.project_not_selected": "项目:未选择",
77
80
  "status.project_hint": "使用 /projects 选择项目",
78
81
  "status.session_selected": "当前会话:{title}",
@@ -124,15 +127,20 @@ export const zh = {
124
127
  "stop.error": "🔴 停止操作失败。\n\n事件流已停止,请再次尝试 /abort。",
125
128
  "opencode_start.already_running_managed": "⚠️ OpenCode 服务器已在运行\n\nPID:{pid}\n运行时间:{seconds} 秒",
126
129
  "opencode_start.already_running_external": "✅ OpenCode 服务器正作为外部进程运行\n\n版本:{version}\n\n该服务器不是由机器人启动,因此 /opencode-stop 无法停止它。",
130
+ "opencode_start.already_running": "✅ OpenCode 服务器已在运行\n\n版本:{version}",
131
+ "opencode_start.remote_configured": "⚠️ /opencode_start 仅适用于本地 OpenCode 服务器。",
127
132
  "opencode_start.starting": "🔄 正在启动 OpenCode 服务器...",
128
133
  "opencode_start.start_error": "🔴 启动 OpenCode 服务器失败\n\n错误:{error}\n\n请检查 OpenCode CLI 已安装且在 PATH 中可用:\nopencode --version\nnpm install -g @opencode-ai/cli",
129
134
  "opencode_start.started_not_ready": "⚠️ OpenCode 服务器已启动,但未响应\n\nPID:{pid}\n\n服务器可能仍在启动中。几秒后试试 /status。",
130
135
  "opencode_start.success": "✅ OpenCode 服务器启动成功\n\nPID:{pid}\n版本:{version}",
131
136
  "opencode_start.error": "🔴 启动服务器时发生错误。\n\n请查看应用日志了解详情。",
132
137
  "opencode_stop.external_running": "⚠️ OpenCode 服务器正作为外部进程运行\n\n该服务器不是通过 /opencode-start 启动的。\n请手动停止它,或使用 /status 检查状态。",
138
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop 仅适用于本地 OpenCode 服务器。",
133
139
  "opencode_stop.not_running": "⚠️ OpenCode 服务器未运行",
140
+ "opencode_stop.pid_not_found": "⚠️ OpenCode 服务器正在端口 {port} 上响应,但未找到可停止的本地进程。",
134
141
  "opencode_stop.stopping": "🛑 正在停止 OpenCode 服务器...\n\nPID:{pid}",
135
142
  "opencode_stop.stop_error": "🔴 停止 OpenCode 服务器失败\n\n错误:{error}",
143
+ "opencode_stop.still_running": "停止请求后服务器仍在响应。",
136
144
  "opencode_stop.success": "✅ OpenCode 服务器已成功停止",
137
145
  "opencode_stop.error": "🔴 停止服务器时发生错误。\n\n请查看应用日志了解详情。",
138
146
  "agent.changed_callback": "Agent 已更改:{name}",
@@ -222,6 +230,7 @@ export const zh = {
222
230
  "pinned.default_session_title": "新会话",
223
231
  "pinned.unknown": "未知",
224
232
  "pinned.line.project": "项目: {project}",
233
+ "pinned.line.worktree": "Worktree: {worktree}",
225
234
  "pinned.line.model": "模型: {model}",
226
235
  "pinned.line.context": "上下文: {used} / {limit} ({percent}%)",
227
236
  "pinned.line.cost": "费用: {cost}",
@@ -323,17 +332,24 @@ export const zh = {
323
332
  "commands.button.next_page": "下一页 ➡️",
324
333
  "commands.page_empty_callback": "这一页没有命令",
325
334
  "commands.page_load_error_callback": "无法加载此页面。请重试。",
335
+ "skills.select": "请选择一个 OpenCode 技能:",
336
+ "skills.empty": "📭 当前项目没有可用的 OpenCode 技能。",
337
+ "skills.fetch_error": "🔴 加载 OpenCode 技能失败。",
338
+ "skills.no_description": "无描述",
339
+ "skills.button.execute": "✅ 执行",
340
+ "skills.button.cancel": "❌ 取消",
341
+ "skills.confirm": "请确认执行技能 {skill}。若需带参数执行,请发送一条包含参数的消息。",
342
+ "skills.inactive_callback": "该技能菜单已失效",
343
+ "skills.cancelled_callback": "已取消",
344
+ "skills.execute_callback": "正在使用技能...",
345
+ "skills.executing_prefix": "⚡ 使用技能:",
346
+ "skills.arguments_empty": "⚠️ 参数不能为空。请发送文本或点击执行。",
347
+ "skills.select_page": "请选择一个 OpenCode 技能(第 {page} 页):",
348
+ "skills.button.prev_page": "⬅️ 上一页",
349
+ "skills.button.next_page": "下一页 ➡️",
350
+ "skills.page_empty_callback": "这一页没有技能",
351
+ "skills.page_load_error_callback": "无法加载此页面。请重试。",
326
352
  "cmd.description.rename": "重命名当前会话",
327
- "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`",
328
- "cli.placeholder.status": "`status` 命令当前为占位符。实际状态检查将在服务层中添加(第5阶段)。",
329
- "cli.placeholder.stop": "`stop` 命令当前为占位符。实际后台进程停止功能将在服务层中添加(第5阶段)。",
330
- "cli.placeholder.unavailable": "命令不可用。",
331
- "cli.error.prefix": "CLI 错误:{message}",
332
- "cli.args.unknown_command": "未知命令:{value}",
333
- "cli.args.mode_requires_value": "选项 --mode 需要一个值:sources|installed",
334
- "cli.args.invalid_mode": "无效的 --mode 值:{value}。期望 sources|installed",
335
- "cli.args.unknown_option": "未知选项:{value}",
336
- "cli.args.mode_only_start": "选项 --mode 仅支持 start 命令",
337
353
  "legacy.models.fetch_error": "🔴 获取模型列表失败。请使用 /status 检查服务器状态。",
338
354
  "legacy.models.empty": "📋 没有可用模型。请在 OpenCode 中配置 providers。",
339
355
  "legacy.models.header": "📋 可用模型:\n\n",
@@ -346,6 +362,18 @@ export const zh = {
346
362
  "stt.error": "🔴 识别音频失败:{error}",
347
363
  "stt.empty_result": "🎤 音频消息中未检测到语音。",
348
364
  "cmd.description.open": "通过浏览目录添加项目",
365
+ "worktree.branch_detached": "detached HEAD",
366
+ "worktree.select_with_current": "选择一个 worktree:",
367
+ "worktree.project_not_selected": "🏗 尚未选择项目。\n\n请先使用 /projects 选择一个项目。",
368
+ "worktree.not_git_repo": "🌿 当前项目不支持 git worktree。请先选择一个 git 仓库。",
369
+ "worktree.not_git_repo_callback": "当前项目不是 git 仓库",
370
+ "worktree.empty": "📭 当前仓库未找到任何 git worktree。",
371
+ "worktree.fetch_error": "🔴 加载 git worktree 失败。",
372
+ "worktree.page_empty_callback": "此页面没有 worktree",
373
+ "worktree.selection_missing_callback": "所选 worktree 已不可用",
374
+ "worktree.already_selected_callback": "该 worktree 已被选中",
375
+ "worktree.selected": "✅ 已选择 worktree:{worktree}\n\n📋 会话已重置。请使用 /sessions 或 /new 继续。",
376
+ "worktree.select_error": "🔴 选择 worktree 失败。",
349
377
  "open.back": "⬆️ 上级",
350
378
  "open.roots": "📋 返回根目录",
351
379
  "open.prev_page": "⬅️ 上一页",
@@ -0,0 +1,182 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execAsync = promisify(exec);
4
+ const DEFAULT_OPENCODE_PORT = 4096;
5
+ const PROCESS_EXIT_POLL_MS = 100;
6
+ function isLocalHostname(hostname) {
7
+ return ["localhost", "127.0.0.1", "::1", "0.0.0.0"].includes(hostname.toLowerCase());
8
+ }
9
+ export function resolveLocalOpencodeTarget(apiUrl) {
10
+ try {
11
+ const parsedUrl = new URL(apiUrl);
12
+ if (!isLocalHostname(parsedUrl.hostname)) {
13
+ return null;
14
+ }
15
+ const port = parsedUrl.port ? Number.parseInt(parsedUrl.port, 10) : DEFAULT_OPENCODE_PORT;
16
+ if (!Number.isInteger(port) || port <= 0) {
17
+ return null;
18
+ }
19
+ return {
20
+ host: parsedUrl.hostname,
21
+ port,
22
+ };
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function parsePid(value) {
29
+ const pid = Number.parseInt(value.trim(), 10);
30
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
31
+ }
32
+ function parseSocketPort(value) {
33
+ const trimmedValue = value.trim();
34
+ const match = trimmedValue.match(/:(\d+)$/);
35
+ if (!match) {
36
+ return null;
37
+ }
38
+ const port = Number.parseInt(match[1], 10);
39
+ return Number.isInteger(port) && port > 0 ? port : null;
40
+ }
41
+ export function findWindowsListeningPidInNetstat(stdout, port) {
42
+ for (const line of stdout.split(/\r?\n/)) {
43
+ const trimmedLine = line.trim();
44
+ if (!trimmedLine) {
45
+ continue;
46
+ }
47
+ const columns = trimmedLine.split(/\s+/);
48
+ const localAddress = columns[1] ?? "";
49
+ const localPort = parseSocketPort(localAddress);
50
+ if (localPort !== port) {
51
+ continue;
52
+ }
53
+ const pid = parsePid(columns[columns.length - 1] ?? "");
54
+ if (pid !== null) {
55
+ return pid;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ export function findUnixListeningPidInSs(stdout, port) {
61
+ for (const line of stdout.split(/\r?\n/)) {
62
+ const trimmedLine = line.trim();
63
+ if (!trimmedLine) {
64
+ continue;
65
+ }
66
+ const columns = trimmedLine.split(/\s+/);
67
+ const localAddress = columns[3] ?? "";
68
+ const localPort = parseSocketPort(localAddress);
69
+ if (localPort !== port) {
70
+ continue;
71
+ }
72
+ const pidMatch = trimmedLine.match(/pid=(\d+)/);
73
+ const pid = pidMatch ? parsePid(pidMatch[1]) : null;
74
+ if (pid !== null) {
75
+ return pid;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ async function findWindowsServerPid(port) {
81
+ try {
82
+ const { stdout } = await execAsync("netstat -ano | findstr LISTENING");
83
+ return findWindowsListeningPidInNetstat(stdout, port);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
89
+ function parseUnixPidList(stdout) {
90
+ for (const line of stdout.split(/\r?\n/)) {
91
+ const pid = parsePid(line);
92
+ if (pid !== null) {
93
+ return pid;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ async function findUnixServerPid(port) {
99
+ try {
100
+ const { stdout } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`);
101
+ const pid = parseUnixPidList(stdout);
102
+ if (pid !== null) {
103
+ return pid;
104
+ }
105
+ }
106
+ catch {
107
+ // Fall back to ss when lsof is unavailable.
108
+ }
109
+ try {
110
+ const { stdout } = await execAsync("ss -ltnp");
111
+ return findUnixListeningPidInSs(stdout, port);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ export async function findServerPid(port) {
118
+ return process.platform === "win32" ? findWindowsServerPid(port) : findUnixServerPid(port);
119
+ }
120
+ function isProcessAlive(pid) {
121
+ try {
122
+ process.kill(pid, 0);
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ async function waitForProcessExit(pid, timeoutMs) {
130
+ const startTime = Date.now();
131
+ while (Date.now() - startTime < timeoutMs) {
132
+ if (!isProcessAlive(pid)) {
133
+ return true;
134
+ }
135
+ await new Promise((resolve) => setTimeout(resolve, PROCESS_EXIT_POLL_MS));
136
+ }
137
+ return !isProcessAlive(pid);
138
+ }
139
+ async function killWindowsProcess(pid, timeoutMs) {
140
+ try {
141
+ await execAsync(`taskkill /PID ${pid} /T`);
142
+ }
143
+ catch {
144
+ // Continue with forced stop if the process is still alive.
145
+ }
146
+ if (await waitForProcessExit(pid, timeoutMs)) {
147
+ return true;
148
+ }
149
+ try {
150
+ await execAsync(`taskkill /F /PID ${pid} /T`);
151
+ }
152
+ catch {
153
+ return !isProcessAlive(pid);
154
+ }
155
+ return waitForProcessExit(pid, timeoutMs);
156
+ }
157
+ async function killUnixProcess(pid, timeoutMs) {
158
+ try {
159
+ process.kill(pid, "SIGTERM");
160
+ }
161
+ catch {
162
+ return !isProcessAlive(pid);
163
+ }
164
+ if (await waitForProcessExit(pid, timeoutMs)) {
165
+ return true;
166
+ }
167
+ try {
168
+ process.kill(pid, "SIGKILL");
169
+ }
170
+ catch {
171
+ return !isProcessAlive(pid);
172
+ }
173
+ return waitForProcessExit(pid, timeoutMs);
174
+ }
175
+ export async function killServerProcess(pid, timeoutMs = 5000) {
176
+ if (!isProcessAlive(pid)) {
177
+ return true;
178
+ }
179
+ return process.platform === "win32"
180
+ ? killWindowsProcess(pid, timeoutMs)
181
+ : killUnixProcess(pid, timeoutMs);
182
+ }