@grinev/opencode-telegram-bot 0.16.1 → 0.18.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.
Files changed (46) hide show
  1. package/.env.example +5 -0
  2. package/README.md +72 -43
  3. package/dist/app/start-bot-app.js +0 -2
  4. package/dist/attach/manager.js +66 -0
  5. package/dist/attach/service.js +166 -0
  6. package/dist/bot/commands/abort.js +0 -4
  7. package/dist/bot/commands/commands.js +13 -3
  8. package/dist/bot/commands/definitions.js +2 -0
  9. package/dist/bot/commands/new.js +10 -18
  10. package/dist/bot/commands/opencode-start.js +27 -24
  11. package/dist/bot/commands/opencode-stop.js +51 -23
  12. package/dist/bot/commands/projects.js +30 -6
  13. package/dist/bot/commands/sessions.js +19 -20
  14. package/dist/bot/commands/skills.js +376 -0
  15. package/dist/bot/commands/start.js +2 -0
  16. package/dist/bot/commands/status.js +21 -14
  17. package/dist/bot/commands/worktree.js +196 -0
  18. package/dist/bot/handlers/inline-menu.js +1 -0
  19. package/dist/bot/handlers/prompt.js +26 -31
  20. package/dist/bot/handlers/voice.js +8 -1
  21. package/dist/bot/index.js +90 -10
  22. package/dist/bot/middleware/interaction-guard.js +1 -1
  23. package/dist/bot/utils/busy-guard.js +3 -2
  24. package/dist/bot/utils/external-user-input.js +46 -0
  25. package/dist/bot/utils/switch-project.js +2 -0
  26. package/dist/config.js +2 -0
  27. package/dist/external-input/suppression.js +54 -0
  28. package/dist/git/worktree.js +158 -0
  29. package/dist/i18n/de.js +54 -1
  30. package/dist/i18n/en.js +54 -1
  31. package/dist/i18n/es.js +54 -1
  32. package/dist/i18n/fr.js +54 -1
  33. package/dist/i18n/ru.js +54 -1
  34. package/dist/i18n/zh.js +54 -1
  35. package/dist/interaction/guard.js +2 -1
  36. package/dist/opencode/events.js +13 -1
  37. package/dist/opencode/process.js +182 -0
  38. package/dist/pinned/manager.js +46 -61
  39. package/dist/project/manager.js +47 -32
  40. package/dist/runtime/bootstrap.js +119 -7
  41. package/dist/scheduled-task/executor.js +137 -7
  42. package/dist/settings/manager.js +9 -12
  43. package/dist/summary/aggregator.js +54 -9
  44. package/package.json +1 -1
  45. package/dist/process/manager.js +0 -273
  46. package/dist/process/types.js +0 -1
@@ -0,0 +1,158 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+ const GIT_HEADS_PREFIX = "refs/heads/";
5
+ const GIT_WORKTREES_MARKER = `${path.sep}.git${path.sep}worktrees${path.sep}`;
6
+ const GIT_WORKTREE_LIST_MAX_BUFFER = 1024 * 1024;
7
+ function normalizePathKey(value) {
8
+ const normalized = path.resolve(value).replace(/[\\/]+$/, "");
9
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
10
+ }
11
+ function normalizeBranchName(value) {
12
+ return value.startsWith(GIT_HEADS_PREFIX) ? value.slice(GIT_HEADS_PREFIX.length) : value;
13
+ }
14
+ function parseGitWorktreeList(stdout) {
15
+ const entries = [];
16
+ let currentPath = null;
17
+ let currentBranch = null;
18
+ const pushCurrent = () => {
19
+ if (!currentPath) {
20
+ return;
21
+ }
22
+ entries.push({
23
+ path: currentPath,
24
+ branch: currentBranch,
25
+ });
26
+ currentPath = null;
27
+ currentBranch = null;
28
+ };
29
+ for (const rawLine of stdout.split(/\r?\n/)) {
30
+ const line = rawLine.trim();
31
+ if (!line) {
32
+ pushCurrent();
33
+ continue;
34
+ }
35
+ if (line.startsWith("worktree ")) {
36
+ pushCurrent();
37
+ currentPath = line.slice("worktree ".length);
38
+ continue;
39
+ }
40
+ if (line.startsWith("branch ")) {
41
+ currentBranch = normalizeBranchName(line.slice("branch ".length));
42
+ continue;
43
+ }
44
+ if (line === "detached") {
45
+ currentBranch = null;
46
+ }
47
+ }
48
+ pushCurrent();
49
+ return entries;
50
+ }
51
+ async function runGitWorktreeList(worktree) {
52
+ const stdout = await new Promise((resolve, reject) => {
53
+ execFile("git", ["worktree", "list", "--porcelain"], {
54
+ cwd: worktree,
55
+ windowsHide: true,
56
+ maxBuffer: GIT_WORKTREE_LIST_MAX_BUFFER,
57
+ }, (error, output) => {
58
+ if (error) {
59
+ reject(error);
60
+ return;
61
+ }
62
+ resolve(output);
63
+ });
64
+ });
65
+ return parseGitWorktreeList(stdout);
66
+ }
67
+ async function readHeadBranch(gitDir) {
68
+ try {
69
+ const headPath = path.join(gitDir, "HEAD");
70
+ const headContent = (await readFile(headPath, "utf-8")).trim();
71
+ const match = headContent.match(/^ref:\s+(.+)$/);
72
+ return match ? normalizeBranchName(match[1]) : null;
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ function deriveMainProjectPath(activeWorktreePath, gitDir) {
79
+ const normalizedGitDir = path.resolve(gitDir);
80
+ if (path.basename(normalizedGitDir).toLowerCase() === ".git") {
81
+ return path.resolve(activeWorktreePath);
82
+ }
83
+ if (normalizedGitDir.includes(GIT_WORKTREES_MARKER)) {
84
+ return path.resolve(normalizedGitDir, "..", "..", "..");
85
+ }
86
+ return path.resolve(activeWorktreePath);
87
+ }
88
+ export async function resolveGitDir(worktree) {
89
+ const gitPath = path.join(worktree, ".git");
90
+ try {
91
+ const gitStat = await stat(gitPath);
92
+ if (gitStat.isDirectory()) {
93
+ return gitPath;
94
+ }
95
+ if (!gitStat.isFile()) {
96
+ return null;
97
+ }
98
+ const gitPointer = (await readFile(gitPath, "utf-8")).trim();
99
+ const match = gitPointer.match(/^gitdir:\s*(.+)$/i);
100
+ if (!match) {
101
+ return null;
102
+ }
103
+ return path.resolve(worktree, match[1].trim());
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ export async function getGitWorktreeContext(worktree) {
110
+ const activeWorktreePath = path.resolve(worktree);
111
+ const gitDir = await resolveGitDir(activeWorktreePath);
112
+ if (!gitDir) {
113
+ return null;
114
+ }
115
+ const mainProjectPath = deriveMainProjectPath(activeWorktreePath, gitDir);
116
+ const activeWorktreeKey = normalizePathKey(activeWorktreePath);
117
+ const mainProjectKey = normalizePathKey(mainProjectPath);
118
+ const parsedEntries = await runGitWorktreeList(activeWorktreePath);
119
+ const worktrees = parsedEntries.map((entry) => {
120
+ const entryPath = path.resolve(entry.path);
121
+ const entryKey = normalizePathKey(entryPath);
122
+ return {
123
+ path: entryPath,
124
+ branch: entry.branch,
125
+ isCurrent: entryKey === activeWorktreeKey,
126
+ isMain: entryKey === mainProjectKey,
127
+ };
128
+ });
129
+ let currentEntry = worktrees.find((entry) => entry.isCurrent) ?? null;
130
+ if (!currentEntry) {
131
+ currentEntry = {
132
+ path: activeWorktreePath,
133
+ branch: await readHeadBranch(gitDir),
134
+ isCurrent: true,
135
+ isMain: activeWorktreeKey === mainProjectKey,
136
+ };
137
+ worktrees.push(currentEntry);
138
+ }
139
+ else if (!currentEntry.branch) {
140
+ currentEntry.branch = await readHeadBranch(gitDir);
141
+ }
142
+ worktrees.sort((left, right) => {
143
+ if (left.isMain !== right.isMain) {
144
+ return left.isMain ? -1 : 1;
145
+ }
146
+ if (left.path === right.path) {
147
+ return 0;
148
+ }
149
+ return left.path.localeCompare(right.path);
150
+ });
151
+ return {
152
+ mainProjectPath,
153
+ activeWorktreePath,
154
+ branch: currentEntry.branch,
155
+ isLinkedWorktree: activeWorktreeKey !== mainProjectKey,
156
+ worktrees,
157
+ };
158
+ }
package/dist/i18n/de.js CHANGED
@@ -5,9 +5,11 @@ export const de = {
5
5
  "cmd.description.sessions": "Sitzungen auflisten",
6
6
  "cmd.description.tts": "Audioantworten umschalten",
7
7
  "cmd.description.projects": "Projekte auflisten",
8
+ "cmd.description.worktree": "Git-Worktrees wechseln",
8
9
  "cmd.description.task": "Geplante Aufgabe erstellen",
9
10
  "cmd.description.tasklist": "Geplante Aufgaben anzeigen",
10
11
  "cmd.description.commands": "Benutzerdefinierte Befehle",
12
+ "cmd.description.skills": "Skill-Katalog",
11
13
  "cmd.description.opencode_start": "OpenCode-Server starten",
12
14
  "cmd.description.opencode_stop": "OpenCode-Server stoppen",
13
15
  "cmd.description.help": "Hilfe",
@@ -33,7 +35,7 @@ export const de = {
33
35
  "inline.cancelled_callback": "Abgebrochen",
34
36
  "common.unknown": "unbekannt",
35
37
  "common.unknown_error": "unbekannter Fehler",
36
- "start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/task — geplante Aufgabe\n/tasklist — geplante Aufgaben\n/status — Status\n/help — Hilfe\n\nNutze die unteren Buttons, um Agent, Modell und Variante zu wählen.",
38
+ "start.welcome": "👋 Willkommen beim OpenCode Telegram Bot!\n\nNutze Befehle:\n/projects — Projekt auswählen\n/sessions — Sitzungsliste\n/new — neue Sitzung\n/commands — benutzerdefinierte Befehle\n/skills — Skill-Katalog\n/task — geplante Aufgabe\n/tasklist — geplante Aufgaben\n/status — Status\n/help — Hilfe\n\nNutze die unteren Buttons, um Agent, Modell und Variante zu wählen.",
37
39
  "help.keyboard_hint": "💡 Nutze die unteren Buttons für Agent, Modell, Variante und Kontextaktionen.",
38
40
  "help.text": "📖 **Hilfe**\n\n/status - Serverstatus prüfen\n/sessions - Sitzungsliste\n/new - Neue Sitzung erstellen\n/help - Hilfe",
39
41
  "bot.thinking": "💭 Denke...",
@@ -46,6 +48,7 @@ export const de = {
46
48
  "bot.prompt_send_error": "Anfrage konnte nicht an OpenCode gesendet werden.",
47
49
  "bot.session_error": "🔴 OpenCode meldete einen Fehler: {message}",
48
50
  "bot.session_retry": "🔁 {message}\n\nDer Provider liefert bei wiederholten Versuchen immer wieder denselben Fehler. Mit /abort abbrechen.",
51
+ "bot.external_user_input": "Externe Benutzereingabe",
49
52
  "bot.unknown_command": "⚠️ Unbekannter Befehl: {command}. Nutze /help, um verfügbare Befehle zu sehen.",
50
53
  "bot.photo_downloading": "⏳ Lade Foto herunter...",
51
54
  "bot.photo_too_large": "⚠️ Foto ist zu groß (max. {maxSizeMb}MB)",
@@ -73,6 +76,7 @@ export const de = {
73
76
  "status.tts.off": "Aus",
74
77
  "status.agent_not_set": "nicht gesetzt",
75
78
  "status.project_selected": "Projekt: {project}",
79
+ "status.worktree_selected": "Worktree: {worktree}",
76
80
  "status.project_not_selected": "Projekt: nicht ausgewählt",
77
81
  "status.project_hint": "Nutze /projects, um ein Projekt auszuwahlen",
78
82
  "status.session_selected": "Aktuelle Sitzung: {title}",
@@ -110,6 +114,17 @@ export const de = {
110
114
  "sessions.preview.title": "Letzte Nachrichten:",
111
115
  "sessions.preview.you": "Du:",
112
116
  "sessions.preview.agent": "Agent:",
117
+ "attach.project_not_selected": "🏗 Projekt ist nicht ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
118
+ "attach.session_not_selected": "💬 Keine Sitzung ausgewählt.\n\nWähle zuerst mit /sessions eine Sitzung aus.",
119
+ "attach.session_project_mismatch": "⚠️ Die ausgewählte Sitzung passt nicht zum aktuellen Projekt. Wähle sie über /sessions erneut aus.",
120
+ "attach.connected": "✅ Mit Sitzung verbunden: {title}",
121
+ "attach.already_connected": "ℹ️ Bereits mit Sitzung verbunden: {title}",
122
+ "attach.status.idle_message": "Status: idle. Warte auf neue Ereignisse.",
123
+ "attach.status.busy_message": "Status: busy. Neue Prompts sind vorübergehend blockiert.",
124
+ "attach.restored_question": "Eine ausstehende Frage für diese Sitzung wurde wiederhergestellt.",
125
+ "attach.restored_permissions": "Ausstehende Berechtigungsanfragen wiederhergestellt: {count}.",
126
+ "attach.disconnect_hint": "Zum Trennen einfach zu einer anderen Sitzung oder einem anderen Projekt wechseln.",
127
+ "attach.error": "🔴 Verbindung mit der aktuellen Sitzung fehlgeschlagen.",
113
128
  "new.project_not_selected": "🏗 Projekt ist nicht ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
114
129
  "new.created": "✅ Neue Sitzung erstellt: {title}",
115
130
  "new.create_error": "🔴 OpenCode-Server ist nicht verfügbar oder beim Erstellen der Sitzung ist ein Fehler aufgetreten.",
@@ -124,15 +139,20 @@ export const de = {
124
139
  "stop.error": "🔴 Aktion konnte nicht gestoppt werden.\n\nEvent-Stream ist gestoppt, versuche /abort erneut.",
125
140
  "opencode_start.already_running_managed": "⚠️ OpenCode-Server läuft bereits\n\nPID: {pid}\nBetriebszeit: {seconds} Sekunden",
126
141
  "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.",
142
+ "opencode_start.already_running": "✅ OpenCode-Server läuft bereits\n\nVersion: {version}",
143
+ "opencode_start.remote_configured": "⚠️ /opencode_start funktioniert nur mit einem lokalen OpenCode-Server.",
127
144
  "opencode_start.starting": "🔄 Starte OpenCode-Server...",
128
145
  "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",
129
146
  "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.",
130
147
  "opencode_start.success": "✅ OpenCode-Server erfolgreich gestartet\n\nPID: {pid}\nVersion: {version}",
131
148
  "opencode_start.error": "🔴 Beim Starten des Servers ist ein Fehler aufgetreten.\n\nSiehe Anwendungslogs für Details.",
132
149
  "opencode_stop.external_running": "⚠️ OpenCode-Server läuft als externer Prozess\n\nDieser Server wurde nicht über /opencode-start gestartet.\nStoppe ihn manuell oder nutze /status, um den Zustand zu prüfen.",
150
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop funktioniert nur mit einem lokalen OpenCode-Server.",
133
151
  "opencode_stop.not_running": "⚠️ OpenCode-Server läuft nicht",
152
+ "opencode_stop.pid_not_found": "⚠️ OpenCode-Server antwortet auf Port {port}, aber es wurde kein lokaler Prozess zum Stoppen gefunden.",
134
153
  "opencode_stop.stopping": "🛑 Stoppe OpenCode-Server...\n\nPID: {pid}",
135
154
  "opencode_stop.stop_error": "🔴 OpenCode-Server konnte nicht gestoppt werden\n\nFehler: {error}",
155
+ "opencode_stop.still_running": "Der Server antwortet nach der Stop-Anfrage weiterhin.",
136
156
  "opencode_stop.success": "✅ OpenCode-Server erfolgreich gestoppt",
137
157
  "opencode_stop.error": "🔴 Beim Stoppen des Servers ist ein Fehler aufgetreten.\n\nSiehe Anwendungslogs für Details.",
138
158
  "agent.changed_callback": "Agent geändert: {name}",
@@ -222,7 +242,11 @@ export const de = {
222
242
  "pinned.default_session_title": "neue Sitzung",
223
243
  "pinned.unknown": "Unbekannt",
224
244
  "pinned.line.project": "Projekt: {project}",
245
+ "pinned.line.worktree": "Worktree: {worktree}",
225
246
  "pinned.line.model": "Modell: {model}",
247
+ "pinned.line.attach": "Tracking: {status}",
248
+ "pinned.attach.status.idle": "aktiv, idle",
249
+ "pinned.attach.status.busy": "aktiv, busy",
226
250
  "pinned.line.context": "Kontext: {used} / {limit} ({percent}%)",
227
251
  "pinned.line.cost": "Kosten: {cost} ausgegeben",
228
252
  "subagent.header": "Subagent {agent}: {description}",
@@ -323,6 +347,23 @@ export const de = {
323
347
  "commands.button.next_page": "Weiter ➡️",
324
348
  "commands.page_empty_callback": "Keine Befehle auf dieser Seite",
325
349
  "commands.page_load_error_callback": "Diese Seite konnte nicht geladen werden. Bitte versuche es erneut.",
350
+ "skills.select": "Wähle einen OpenCode-Skill:",
351
+ "skills.empty": "📭 Für dieses Projekt sind keine OpenCode-Skills verfügbar.",
352
+ "skills.fetch_error": "🔴 OpenCode-Skills konnten nicht geladen werden.",
353
+ "skills.no_description": "Keine Beschreibung",
354
+ "skills.button.execute": "✅ Ausführen",
355
+ "skills.button.cancel": "❌ Abbrechen",
356
+ "skills.confirm": "Bestätige die Ausführung des Skills {skill}. Für die Ausführung mit Argumenten sende die Argumente als Nachricht.",
357
+ "skills.inactive_callback": "Dieses Skill-Menü ist inaktiv",
358
+ "skills.cancelled_callback": "Abgebrochen",
359
+ "skills.execute_callback": "Skill wird verwendet...",
360
+ "skills.executing_prefix": "⚡ Skill wird verwendet:",
361
+ "skills.arguments_empty": "⚠️ Argumente dürfen nicht leer sein. Sende Text oder tippe auf Ausführen.",
362
+ "skills.select_page": "Wähle einen OpenCode-Skill (Seite {page}):",
363
+ "skills.button.prev_page": "⬅️ Zurück",
364
+ "skills.button.next_page": "Weiter ➡️",
365
+ "skills.page_empty_callback": "Keine Skills auf dieser Seite",
366
+ "skills.page_load_error_callback": "Diese Seite konnte nicht geladen werden. Bitte versuche es erneut.",
326
367
  "cmd.description.rename": "Aktuelle Sitzung umbenennen",
327
368
  "legacy.models.fetch_error": "🔴 Modellliste konnte nicht geladen werden. Prüfe den Serverstatus mit /status.",
328
369
  "legacy.models.empty": "📋 Keine verfügbaren Modelle. Konfiguriere Provider in OpenCode.",
@@ -336,6 +377,18 @@ export const de = {
336
377
  "stt.error": "🔴 Audio konnte nicht erkannt werden: {error}",
337
378
  "stt.empty_result": "🎤 Keine Sprache in der Audionachricht erkannt.",
338
379
  "cmd.description.open": "Projekt durch Ordner-Auswahl hinzufügen",
380
+ "worktree.branch_detached": "detached HEAD",
381
+ "worktree.select_with_current": "Worktree auswählen:",
382
+ "worktree.project_not_selected": "🏗 Es ist kein Projekt ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
383
+ "worktree.not_git_repo": "🌿 Git-Worktrees sind für das aktuelle Projekt nicht verfügbar. Wähle zuerst ein Git-Repository.",
384
+ "worktree.not_git_repo_callback": "Aktuelles Projekt ist kein Git-Repository",
385
+ "worktree.empty": "📭 Für das aktuelle Repository wurden keine Git-Worktrees gefunden.",
386
+ "worktree.fetch_error": "🔴 Git-Worktrees konnten nicht geladen werden.",
387
+ "worktree.page_empty_callback": "Keine Worktrees auf dieser Seite",
388
+ "worktree.selection_missing_callback": "Der ausgewählte Worktree ist nicht mehr verfügbar",
389
+ "worktree.already_selected_callback": "Dieser Worktree ist bereits ausgewählt",
390
+ "worktree.selected": "✅ Worktree ausgewählt: {worktree}\n\n📋 Die Sitzung wurde zurückgesetzt. Nutze /sessions oder /new, um fortzufahren.",
391
+ "worktree.select_error": "🔴 Worktree konnte nicht ausgewählt werden.",
339
392
  "open.back": "⬆️ Hoch",
340
393
  "open.roots": "📋 Zurück zur Auswahl",
341
394
  "open.prev_page": "⬅️ Zurück",
package/dist/i18n/en.js CHANGED
@@ -5,9 +5,11 @@ export const en = {
5
5
  "cmd.description.sessions": "List sessions",
6
6
  "cmd.description.tts": "Toggle audio replies",
7
7
  "cmd.description.projects": "List projects",
8
+ "cmd.description.worktree": "Switch git worktrees",
8
9
  "cmd.description.task": "Create a scheduled task",
9
10
  "cmd.description.tasklist": "List scheduled tasks",
10
11
  "cmd.description.commands": "Custom commands",
12
+ "cmd.description.skills": "Skills catalog",
11
13
  "cmd.description.opencode_start": "Start OpenCode server",
12
14
  "cmd.description.opencode_stop": "Stop OpenCode server",
13
15
  "cmd.description.help": "Help",
@@ -33,7 +35,7 @@ export const en = {
33
35
  "inline.cancelled_callback": "Cancelled",
34
36
  "common.unknown": "unknown",
35
37
  "common.unknown_error": "unknown error",
36
- "start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/task — scheduled task\n/tasklist — scheduled tasks\n/status — status\n/help — help\n\nUse the bottom buttons to select the agent, model, and variant.",
38
+ "start.welcome": "👋 Welcome to OpenCode Telegram Bot!\n\nUse commands:\n/projects — select project\n/sessions — session list\n/new — new session\n/commands — custom commands\n/skills — skills catalog\n/task — scheduled task\n/tasklist — scheduled tasks\n/status — status\n/help — help\n\nUse the bottom buttons to select the agent, model, and variant.",
37
39
  "help.keyboard_hint": "💡 Use the bottom keyboard buttons for the agent, model, variant, and context actions.",
38
40
  "help.text": "📖 **Help**\n\n/status - Check server status\n/sessions - Session list\n/new - Create new session\n/help - Help",
39
41
  "bot.thinking": "💭 Thinking...",
@@ -46,6 +48,7 @@ export const en = {
46
48
  "bot.prompt_send_error": "Failed to send request to OpenCode.",
47
49
  "bot.session_error": "🔴 OpenCode returned an error: {message}",
48
50
  "bot.session_retry": "🔁 {message}\n\nProvider keeps returning the same error on repeated retries. Use /abort to abort.",
51
+ "bot.external_user_input": "External user input",
49
52
  "bot.unknown_command": "⚠️ Unknown command: {command}. Use /help to see available commands.",
50
53
  "bot.photo_downloading": "⏳ Downloading photo...",
51
54
  "bot.photo_too_large": "⚠️ Photo is too large (max {maxSizeMb}MB)",
@@ -73,6 +76,7 @@ export const en = {
73
76
  "status.tts.off": "Off",
74
77
  "status.agent_not_set": "not set",
75
78
  "status.project_selected": "Project: {project}",
79
+ "status.worktree_selected": "Worktree: {worktree}",
76
80
  "status.project_not_selected": "Project: not selected",
77
81
  "status.project_hint": "Use /projects to select a project",
78
82
  "status.session_selected": "Current session: {title}",
@@ -110,6 +114,17 @@ export const en = {
110
114
  "sessions.preview.title": "Recent messages:",
111
115
  "sessions.preview.you": "You:",
112
116
  "sessions.preview.agent": "Agent:",
117
+ "attach.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
118
+ "attach.session_not_selected": "💬 Session is not selected.\n\nFirst choose a session with /sessions.",
119
+ "attach.session_project_mismatch": "⚠️ The selected session does not match the current project. Choose the session again via /sessions.",
120
+ "attach.connected": "✅ Connected to session: {title}",
121
+ "attach.already_connected": "ℹ️ Already connected to session: {title}",
122
+ "attach.status.idle_message": "Status: idle. Waiting for new events.",
123
+ "attach.status.busy_message": "Status: busy. New prompts are temporarily blocked.",
124
+ "attach.restored_question": "Recovered a pending question for this session.",
125
+ "attach.restored_permissions": "Recovered pending permission requests: {count}.",
126
+ "attach.disconnect_hint": "To disconnect, switch to another session or project.",
127
+ "attach.error": "🔴 Failed to attach to the current session.",
113
128
  "new.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
114
129
  "new.created": "✅ New session created: {title}",
115
130
  "new.create_error": "🔴 OpenCode Server is unavailable or an error occurred while creating session.",
@@ -124,15 +139,20 @@ export const en = {
124
139
  "stop.error": "🔴 Failed to stop action.\n\nEvent stream is stopped, try /abort again.",
125
140
  "opencode_start.already_running_managed": "⚠️ OpenCode Server is already running\n\nPID: {pid}\nUptime: {seconds} seconds",
126
141
  "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.",
142
+ "opencode_start.already_running": "✅ OpenCode Server is already running\n\nVersion: {version}",
143
+ "opencode_start.remote_configured": "⚠️ /opencode_start works only with a local OpenCode Server.",
127
144
  "opencode_start.starting": "🔄 Starting OpenCode Server...",
128
145
  "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",
129
146
  "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.",
130
147
  "opencode_start.success": "✅ OpenCode Server started successfully\n\nPID: {pid}\nVersion: {version}",
131
148
  "opencode_start.error": "🔴 An error occurred while starting server.\n\nCheck application logs for details.",
132
149
  "opencode_stop.external_running": "⚠️ OpenCode Server is running as an external process\n\nThis server was not started via /opencode-start.\nStop it manually or use /status to check state.",
150
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop works only with a local OpenCode Server.",
133
151
  "opencode_stop.not_running": "⚠️ OpenCode Server is not running",
152
+ "opencode_stop.pid_not_found": "⚠️ OpenCode Server responds on port {port}, but no local process was found to stop.",
134
153
  "opencode_stop.stopping": "🛑 Stopping OpenCode Server...\n\nPID: {pid}",
135
154
  "opencode_stop.stop_error": "🔴 Failed to stop OpenCode Server\n\nError: {error}",
155
+ "opencode_stop.still_running": "Server is still responding after the stop request.",
136
156
  "opencode_stop.success": "✅ OpenCode Server stopped successfully",
137
157
  "opencode_stop.error": "🔴 An error occurred while stopping server.\n\nCheck application logs for details.",
138
158
  "agent.changed_callback": "Agent changed: {name}",
@@ -222,7 +242,11 @@ export const en = {
222
242
  "pinned.default_session_title": "new session",
223
243
  "pinned.unknown": "Unknown",
224
244
  "pinned.line.project": "Project: {project}",
245
+ "pinned.line.worktree": "Worktree: {worktree}",
225
246
  "pinned.line.model": "Model: {model}",
247
+ "pinned.line.attach": "Tracking: {status}",
248
+ "pinned.attach.status.idle": "active, idle",
249
+ "pinned.attach.status.busy": "active, busy",
226
250
  "pinned.line.context": "Context: {used} / {limit} ({percent}%)",
227
251
  "pinned.line.cost": "Cost: {cost} spent",
228
252
  "subagent.header": "Subagent {agent}: {description}",
@@ -323,6 +347,23 @@ export const en = {
323
347
  "commands.button.next_page": "Next ➡️",
324
348
  "commands.page_empty_callback": "No commands on this page",
325
349
  "commands.page_load_error_callback": "Cannot load this page. Please try again.",
350
+ "skills.select": "Choose an OpenCode skill:",
351
+ "skills.empty": "📭 No OpenCode skills are available for this project.",
352
+ "skills.fetch_error": "🔴 Failed to load OpenCode skills.",
353
+ "skills.no_description": "No description",
354
+ "skills.button.execute": "✅ Execute",
355
+ "skills.button.cancel": "❌ Cancel",
356
+ "skills.confirm": "Confirm execution of skill {skill}. To run it with arguments, send the arguments as a message.",
357
+ "skills.inactive_callback": "This skill menu is inactive",
358
+ "skills.cancelled_callback": "Cancelled",
359
+ "skills.execute_callback": "Using skill...",
360
+ "skills.executing_prefix": "⚡ Using skill:",
361
+ "skills.arguments_empty": "⚠️ Arguments cannot be empty. Send text or tap Execute.",
362
+ "skills.select_page": "Choose an OpenCode skill (page {page}):",
363
+ "skills.button.prev_page": "⬅️ Prev",
364
+ "skills.button.next_page": "Next ➡️",
365
+ "skills.page_empty_callback": "No skills on this page",
366
+ "skills.page_load_error_callback": "Cannot load this page. Please try again.",
326
367
  "cmd.description.rename": "Rename current session",
327
368
  "legacy.models.fetch_error": "🔴 Failed to get models list. Check server status with /status.",
328
369
  "legacy.models.empty": "📋 No available models. Configure providers in OpenCode.",
@@ -336,6 +377,18 @@ export const en = {
336
377
  "stt.error": "🔴 Failed to recognize audio: {error}",
337
378
  "stt.empty_result": "🎤 No speech detected in the audio message.",
338
379
  "cmd.description.open": "Add a project by browsing directories",
380
+ "worktree.branch_detached": "detached HEAD",
381
+ "worktree.select_with_current": "Select a worktree:",
382
+ "worktree.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
383
+ "worktree.not_git_repo": "🌿 Git worktrees are unavailable for the current project. Select a git repository first.",
384
+ "worktree.not_git_repo_callback": "Current project is not a git repository",
385
+ "worktree.empty": "📭 No git worktrees found for the current repository.",
386
+ "worktree.fetch_error": "🔴 Failed to load git worktrees.",
387
+ "worktree.page_empty_callback": "No worktrees on this page",
388
+ "worktree.selection_missing_callback": "Selected worktree is no longer available",
389
+ "worktree.already_selected_callback": "This worktree is already selected",
390
+ "worktree.selected": "✅ Worktree selected: {worktree}\n\n📋 Session was reset. Use /sessions or /new to continue.",
391
+ "worktree.select_error": "🔴 Failed to select worktree.",
339
392
  "open.back": "⬆️ Up",
340
393
  "open.roots": "📋 Back to roots",
341
394
  "open.prev_page": "⬅️ Previous",
package/dist/i18n/es.js CHANGED
@@ -5,9 +5,11 @@ export const es = {
5
5
  "cmd.description.sessions": "Listar sesiones",
6
6
  "cmd.description.tts": "Alternar respuestas de audio",
7
7
  "cmd.description.projects": "Listar proyectos",
8
+ "cmd.description.worktree": "Cambiar worktrees de git",
8
9
  "cmd.description.task": "Crear tarea programada",
9
10
  "cmd.description.tasklist": "Ver tareas programadas",
10
11
  "cmd.description.commands": "Comandos personalizados",
12
+ "cmd.description.skills": "Catálogo de skills",
11
13
  "cmd.description.opencode_start": "Iniciar servidor OpenCode",
12
14
  "cmd.description.opencode_stop": "Detener servidor OpenCode",
13
15
  "cmd.description.help": "Ayuda",
@@ -33,7 +35,7 @@ export const es = {
33
35
  "inline.cancelled_callback": "Cancelado",
34
36
  "common.unknown": "desconocido",
35
37
  "common.unknown_error": "error desconocido",
36
- "start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/task — tarea programada\n/tasklist — tareas programadas\n/status — estado\n/help — ayuda\n\nUsa los botones inferiores para elegir agente, modelo y variante.",
38
+ "start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/commands — comandos personalizados\n/skills — catálogo de skills\n/task — tarea programada\n/tasklist — tareas programadas\n/status — estado\n/help — ayuda\n\nUsa los botones inferiores para elegir agente, modelo y variante.",
37
39
  "help.keyboard_hint": "💡 Usa los botones inferiores para agente, modelo, variante y acciones de contexto.",
38
40
  "help.text": "📖 **Ayuda**\n\n/status - Ver estado del servidor\n/sessions - Lista de sesiones\n/new - Crear una sesión nueva\n/help - Ayuda",
39
41
  "bot.thinking": "💭 Pensando...",
@@ -46,6 +48,7 @@ export const es = {
46
48
  "bot.prompt_send_error": "No se pudo enviar la solicitud a OpenCode.",
47
49
  "bot.session_error": "🔴 OpenCode devolvió un error: {message}",
48
50
  "bot.session_retry": "🔁 {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /abort para detenerlo.",
51
+ "bot.external_user_input": "Entrada externa del usuario",
49
52
  "bot.unknown_command": "⚠️ Comando desconocido: {command}. Usa /help para ver los comandos disponibles.",
50
53
  "bot.photo_downloading": "⏳ Descargando foto...",
51
54
  "bot.photo_too_large": "⚠️ La foto es demasiado grande (max {maxSizeMb}MB)",
@@ -73,6 +76,7 @@ export const es = {
73
76
  "status.tts.off": "Desactivadas",
74
77
  "status.agent_not_set": "no configurado",
75
78
  "status.project_selected": "Proyecto: {project}",
79
+ "status.worktree_selected": "Worktree: {worktree}",
76
80
  "status.project_not_selected": "Proyecto: no seleccionado",
77
81
  "status.project_hint": "Usa /projects para seleccionar un proyecto",
78
82
  "status.session_selected": "Sesión actual: {title}",
@@ -110,6 +114,17 @@ export const es = {
110
114
  "sessions.preview.title": "Mensajes recientes:",
111
115
  "sessions.preview.you": "Tú:",
112
116
  "sessions.preview.agent": "Agente:",
117
+ "attach.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
118
+ "attach.session_not_selected": "💬 No hay una sesión seleccionada.\n\nPrimero selecciona una sesión con /sessions.",
119
+ "attach.session_project_mismatch": "⚠️ La sesión seleccionada no coincide con el proyecto actual. Vuelve a seleccionarla con /sessions.",
120
+ "attach.connected": "✅ Conectado a la sesión: {title}",
121
+ "attach.already_connected": "ℹ️ Ya estás conectado a la sesión: {title}",
122
+ "attach.status.idle_message": "Estado: idle. Esperando nuevos eventos.",
123
+ "attach.status.busy_message": "Estado: busy. Los nuevos prompts están bloqueados temporalmente.",
124
+ "attach.restored_question": "Se restauró una pregunta pendiente para esta sesión.",
125
+ "attach.restored_permissions": "Se restauraron solicitudes de permiso pendientes: {count}.",
126
+ "attach.disconnect_hint": "Para desconectarte, cambia a otra sesión o proyecto.",
127
+ "attach.error": "🔴 No se pudo conectar a la sesión actual.",
113
128
  "new.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
114
129
  "new.created": "✅ Sesión nueva creada: {title}",
115
130
  "new.create_error": "🔴 OpenCode Server no está disponible u ocurrió un error al crear la sesión.",
@@ -124,15 +139,20 @@ export const es = {
124
139
  "stop.error": "🔴 No se pudo detener la acción.\n\nEl flujo de eventos está detenido; prueba /abort otra vez.",
125
140
  "opencode_start.already_running_managed": "⚠️ OpenCode Server ya está en ejecución\n\nPID: {pid}\nTiempo activo: {seconds} segundos",
126
141
  "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.",
142
+ "opencode_start.already_running": "✅ OpenCode Server ya está en ejecución\n\nVersión: {version}",
143
+ "opencode_start.remote_configured": "⚠️ /opencode_start solo funciona con un OpenCode Server local.",
127
144
  "opencode_start.starting": "🔄 Iniciando OpenCode Server...",
128
145
  "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",
129
146
  "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.",
130
147
  "opencode_start.success": "✅ OpenCode Server iniciado correctamente\n\nPID: {pid}\nVersión: {version}",
131
148
  "opencode_start.error": "🔴 Ocurrió un error al iniciar el servidor.\n\nRevisa los logs de la aplicación para más detalles.",
132
149
  "opencode_stop.external_running": "⚠️ OpenCode Server está en ejecución como un proceso externo\n\nEste servidor no fue iniciado con /opencode-start.\nDeténlo manualmente o usa /status para revisar el estado.",
150
+ "opencode_stop.remote_configured": "⚠️ /opencode_stop solo funciona con un OpenCode Server local.",
133
151
  "opencode_stop.not_running": "⚠️ OpenCode Server no está en ejecución",
152
+ "opencode_stop.pid_not_found": "⚠️ OpenCode Server responde en el puerto {port}, pero no se encontró ningún proceso local para detenerlo.",
134
153
  "opencode_stop.stopping": "🛑 Deteniendo OpenCode Server...\n\nPID: {pid}",
135
154
  "opencode_stop.stop_error": "🔴 No se pudo detener OpenCode Server\n\nError: {error}",
155
+ "opencode_stop.still_running": "El servidor sigue respondiendo después de la solicitud de detención.",
136
156
  "opencode_stop.success": "✅ OpenCode Server detenido correctamente",
137
157
  "opencode_stop.error": "🔴 Ocurrió un error al detener el servidor.\n\nRevisa los logs de la aplicación para más detalles.",
138
158
  "agent.changed_callback": "Agente cambiado: {name}",
@@ -222,7 +242,11 @@ export const es = {
222
242
  "pinned.default_session_title": "sesión nueva",
223
243
  "pinned.unknown": "Desconocido",
224
244
  "pinned.line.project": "Proyecto: {project}",
245
+ "pinned.line.worktree": "Worktree: {worktree}",
225
246
  "pinned.line.model": "Modelo: {model}",
247
+ "pinned.line.attach": "Tracking: {status}",
248
+ "pinned.attach.status.idle": "activo, idle",
249
+ "pinned.attach.status.busy": "activo, busy",
226
250
  "pinned.line.context": "Contexto: {used} / {limit} ({percent}%)",
227
251
  "pinned.line.cost": "Costo: {cost} gastado",
228
252
  "subagent.header": "Subagente {agent}: {description}",
@@ -323,6 +347,23 @@ export const es = {
323
347
  "commands.button.next_page": "Siguiente ➡️",
324
348
  "commands.page_empty_callback": "No hay comandos en esta página",
325
349
  "commands.page_load_error_callback": "No se pudo cargar esta página. Por favor, inténtalo de nuevo.",
350
+ "skills.select": "Elige un skill de OpenCode:",
351
+ "skills.empty": "📭 No hay skills de OpenCode disponibles para este proyecto.",
352
+ "skills.fetch_error": "🔴 No se pudieron cargar los skills de OpenCode.",
353
+ "skills.no_description": "Sin descripción",
354
+ "skills.button.execute": "✅ Ejecutar",
355
+ "skills.button.cancel": "❌ Cancelar",
356
+ "skills.confirm": "Confirma la ejecución del skill {skill}. Para ejecutarlo con argumentos, envía los argumentos como mensaje.",
357
+ "skills.inactive_callback": "Este menú de skills está inactivo",
358
+ "skills.cancelled_callback": "Cancelado",
359
+ "skills.execute_callback": "Usando skill...",
360
+ "skills.executing_prefix": "⚡ Usando skill:",
361
+ "skills.arguments_empty": "⚠️ Los argumentos no pueden estar vacíos. Envía texto o toca Ejecutar.",
362
+ "skills.select_page": "Elige un skill de OpenCode (página {page}):",
363
+ "skills.button.prev_page": "⬅️ Anterior",
364
+ "skills.button.next_page": "Siguiente ➡️",
365
+ "skills.page_empty_callback": "No hay skills en esta página",
366
+ "skills.page_load_error_callback": "No se pudo cargar esta página. Por favor, inténtalo de nuevo.",
326
367
  "cmd.description.rename": "Renombrar la sesión actual",
327
368
  "legacy.models.fetch_error": "🔴 No se pudo obtener la lista de modelos. Revisa el estado del servidor con /status.",
328
369
  "legacy.models.empty": "📋 No hay modelos disponibles. Configura los proveedores en OpenCode.",
@@ -336,6 +377,18 @@ export const es = {
336
377
  "stt.error": "🔴 No se pudo reconocer el audio: {error}",
337
378
  "stt.empty_result": "🎤 No se detectó voz en el mensaje de audio.",
338
379
  "cmd.description.open": "Añadir proyecto navegando directorios",
380
+ "worktree.branch_detached": "detached HEAD",
381
+ "worktree.select_with_current": "Selecciona un worktree:",
382
+ "worktree.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
383
+ "worktree.not_git_repo": "🌿 Los git worktrees no están disponibles para el proyecto actual. Selecciona primero un repositorio git.",
384
+ "worktree.not_git_repo_callback": "El proyecto actual no es un repositorio git",
385
+ "worktree.empty": "📭 No se encontraron git worktrees para el repositorio actual.",
386
+ "worktree.fetch_error": "🔴 No se pudieron cargar los git worktrees.",
387
+ "worktree.page_empty_callback": "No hay worktrees en esta página",
388
+ "worktree.selection_missing_callback": "El worktree seleccionado ya no está disponible",
389
+ "worktree.already_selected_callback": "Este worktree ya está seleccionado",
390
+ "worktree.selected": "✅ Worktree seleccionado: {worktree}\n\n📋 La sesión se reinició. Usa /sessions o /new para continuar.",
391
+ "worktree.select_error": "🔴 No se pudo seleccionar el worktree.",
339
392
  "open.back": "⬆️ Subir",
340
393
  "open.roots": "📋 Volver a raíces",
341
394
  "open.prev_page": "⬅️ Anterior",