opencode-telegram-bridge 1.0.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 (48) hide show
  1. package/.env.example +9 -0
  2. package/README.md +62 -0
  3. package/bin/opencode-telegram-bridge.js +2 -0
  4. package/dist/bot.d.ts +7 -0
  5. package/dist/bot.d.ts.map +1 -0
  6. package/dist/bot.js +337 -0
  7. package/dist/bot.js.map +1 -0
  8. package/dist/config.d.ts +14 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +49 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +15 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/opencode.d.ts +38 -0
  17. package/dist/opencode.d.ts.map +1 -0
  18. package/dist/opencode.js +142 -0
  19. package/dist/opencode.js.map +1 -0
  20. package/dist/projects.d.ts +14 -0
  21. package/dist/projects.d.ts.map +1 -0
  22. package/dist/projects.js +74 -0
  23. package/dist/projects.js.map +1 -0
  24. package/dist/prompt-guard.d.ts +7 -0
  25. package/dist/prompt-guard.d.ts.map +1 -0
  26. package/dist/prompt-guard.js +47 -0
  27. package/dist/prompt-guard.js.map +1 -0
  28. package/dist/state.d.ts +10 -0
  29. package/dist/state.d.ts.map +1 -0
  30. package/dist/state.js +56 -0
  31. package/dist/state.js.map +1 -0
  32. package/dist/storage.d.ts +8 -0
  33. package/dist/storage.d.ts.map +1 -0
  34. package/dist/storage.js +16 -0
  35. package/dist/storage.js.map +1 -0
  36. package/dist/telegram.d.ts +3 -0
  37. package/dist/telegram.d.ts.map +1 -0
  38. package/dist/telegram.js +12 -0
  39. package/dist/telegram.js.map +1 -0
  40. package/docs/configuration.md +17 -0
  41. package/docs/index.md +8 -0
  42. package/docs/installation.md +42 -0
  43. package/docs/release.md +56 -0
  44. package/docs/systemd.md +42 -0
  45. package/docs/usage.md +20 -0
  46. package/package.json +52 -0
  47. package/systemd/opencode-telegram-bridge.env.example +7 -0
  48. package/systemd/opencode-telegram-bridge.service +14 -0
@@ -0,0 +1,142 @@
1
+ import { createOpencodeClient } from "@opencode-ai/sdk/v2";
2
+ const buildBasicAuthHeader = (username, password) => {
3
+ const encoded = Buffer.from(`${username}:${password}`).toString("base64");
4
+ return `Basic ${encoded}`;
5
+ };
6
+ const extractText = (parts) => {
7
+ const textParts = parts
8
+ .filter((part) => part.type === "text")
9
+ .map((part) => part.text)
10
+ .filter((text) => text.trim().length > 0);
11
+ return textParts.join("\n").trim();
12
+ };
13
+ const requireData = (result, label) => {
14
+ if (result.data == null) {
15
+ throw new Error(`OpenCode ${label} failed`);
16
+ }
17
+ return result.data;
18
+ };
19
+ const buildSessionKey = (chatId, projectDir) => `${chatId}\u0000${projectDir}`;
20
+ export const createSessionStore = () => {
21
+ const sessions = new Map();
22
+ const owners = new Map();
23
+ return {
24
+ getSessionId: (chatId, projectDir) => sessions.get(buildSessionKey(chatId, projectDir)),
25
+ setSessionId: (chatId, projectDir, sessionId) => {
26
+ const sessionKey = buildSessionKey(chatId, projectDir);
27
+ const existing = sessions.get(sessionKey);
28
+ if (existing && existing !== sessionId) {
29
+ owners.delete(existing);
30
+ }
31
+ sessions.set(sessionKey, sessionId);
32
+ owners.set(sessionId, { chatId, projectDir });
33
+ },
34
+ clearSession: (chatId, projectDir) => {
35
+ const sessionKey = buildSessionKey(chatId, projectDir);
36
+ const existing = sessions.get(sessionKey);
37
+ if (existing) {
38
+ owners.delete(existing);
39
+ }
40
+ return sessions.delete(sessionKey);
41
+ },
42
+ getSessionOwner: (sessionId) => owners.get(sessionId) ?? null,
43
+ };
44
+ };
45
+ export const createOpencodeBridge = (config, options = {}) => {
46
+ const headers = {};
47
+ if (config.serverPassword) {
48
+ headers.Authorization = buildBasicAuthHeader(config.serverUsername, config.serverPassword);
49
+ }
50
+ const client = createOpencodeClient({
51
+ baseUrl: config.serverUrl,
52
+ headers,
53
+ });
54
+ const sessions = options.sessionStore ?? createSessionStore();
55
+ const ensureSession = async (chatId, projectDir) => {
56
+ const existing = sessions.getSessionId(chatId, projectDir);
57
+ if (existing) {
58
+ return existing;
59
+ }
60
+ const sessionResult = await client.session.create({
61
+ directory: projectDir,
62
+ title: `Telegram chat ${chatId}`,
63
+ });
64
+ const session = requireData(sessionResult, "session.create");
65
+ sessions.setSessionId(chatId, projectDir, session.id);
66
+ return session.id;
67
+ };
68
+ return {
69
+ async promptFromChat(chatId, text, projectDir, options = {}) {
70
+ const sessionId = await ensureSession(chatId, projectDir);
71
+ /*
72
+ * The AbortSignal only cancels the HTTP request. The server may continue
73
+ * processing, but the client stops waiting and the bot can move on.
74
+ */
75
+ const requestOptions = options.signal
76
+ ? { signal: options.signal }
77
+ : undefined;
78
+ const responseResult = await client.session.prompt({
79
+ sessionID: sessionId,
80
+ directory: projectDir,
81
+ parts: [{ type: "text", text }],
82
+ }, requestOptions);
83
+ const response = requireData(responseResult, "session.prompt");
84
+ const reply = extractText(response.parts);
85
+ if (!reply) {
86
+ return "OpenCode returned no text output.";
87
+ }
88
+ return reply;
89
+ },
90
+ resetSession(chatId, projectDir) {
91
+ return sessions.clearSession(chatId, projectDir);
92
+ },
93
+ getSessionOwner(sessionId) {
94
+ return sessions.getSessionOwner(sessionId);
95
+ },
96
+ async replyToPermission(requestId, reply, directory) {
97
+ const parameters = directory
98
+ ? { requestID: requestId, reply, directory }
99
+ : { requestID: requestId, reply };
100
+ const responseResult = await client.permission.reply(parameters);
101
+ return requireData(responseResult, "permission.reply");
102
+ },
103
+ startPermissionEventStream({ onPermissionAsked, onError }) {
104
+ const abortController = new AbortController();
105
+ const run = async () => {
106
+ while (!abortController.signal.aborted) {
107
+ try {
108
+ const stream = await client.global.event({
109
+ signal: abortController.signal,
110
+ });
111
+ for await (const event of stream.stream) {
112
+ if (abortController.signal.aborted) {
113
+ return;
114
+ }
115
+ const payload = event.payload;
116
+ if (payload?.type !== "permission.asked") {
117
+ continue;
118
+ }
119
+ const permissionEvent = payload;
120
+ await Promise.resolve(onPermissionAsked({
121
+ request: permissionEvent.properties,
122
+ directory: event.directory,
123
+ }));
124
+ }
125
+ }
126
+ catch (error) {
127
+ if (abortController.signal.aborted) {
128
+ return;
129
+ }
130
+ onError?.(error);
131
+ await new Promise((resolve) => setTimeout(resolve, 1000));
132
+ }
133
+ }
134
+ };
135
+ void run();
136
+ return {
137
+ stop: () => abortController.abort(),
138
+ };
139
+ },
140
+ };
141
+ };
142
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../src/opencode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type StoreOptions } from "./storage.js";
2
+ export type ProjectRecord = {
3
+ alias: string;
4
+ path: string;
5
+ };
6
+ export type ProjectStore = {
7
+ listProjects: () => ProjectRecord[];
8
+ getProject: (alias: string) => ProjectRecord | null;
9
+ addProject: (alias: string, projectPath: string) => ProjectRecord;
10
+ removeProject: (alias: string) => void;
11
+ };
12
+ export declare const HOME_PROJECT_ALIAS = "home";
13
+ export declare const createProjectStore: (options?: StoreOptions) => ProjectStore;
14
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhE,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,MAAM,aAAa,EAAE,CAAA;IACnC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAA;IACnD,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,aAAa,CAAA;IACjE,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACvC,CAAA;AAED,eAAO,MAAM,kBAAkB,SAAS,CAAA;AAuCxC,eAAO,MAAM,kBAAkB,GAC7B,UAAS,YAAiB,KACzB,YAoDF,CAAA"}
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createDatabase } from "./storage.js";
5
+ export const HOME_PROJECT_ALIAS = "home";
6
+ const normalizeAlias = (alias) => {
7
+ const trimmed = alias.trim();
8
+ if (!trimmed) {
9
+ throw new Error("Project alias is required");
10
+ }
11
+ return trimmed;
12
+ };
13
+ const expandHomePath = (rawPath) => {
14
+ if (rawPath === "~") {
15
+ return os.homedir();
16
+ }
17
+ if (rawPath.startsWith("~/")) {
18
+ return path.join(os.homedir(), rawPath.slice(2));
19
+ }
20
+ return rawPath;
21
+ };
22
+ const resolveProjectPath = (rawPath) => {
23
+ const trimmed = rawPath.trim();
24
+ if (!trimmed) {
25
+ throw new Error("Project path is required");
26
+ }
27
+ const expanded = expandHomePath(trimmed);
28
+ const resolved = path.resolve(expanded);
29
+ const stats = fs.statSync(resolved);
30
+ if (!stats.isDirectory()) {
31
+ throw new Error("Project path must be a directory");
32
+ }
33
+ return resolved;
34
+ };
35
+ export const createProjectStore = (options = {}) => {
36
+ const db = createDatabase(options);
37
+ db.exec("CREATE TABLE IF NOT EXISTS projects (alias TEXT PRIMARY KEY, path TEXT NOT NULL)");
38
+ const homePath = os.homedir();
39
+ db.prepare("INSERT INTO projects (alias, path) VALUES (?, ?) ON CONFLICT(alias) DO UPDATE SET path = excluded.path").run(HOME_PROJECT_ALIAS, homePath);
40
+ return {
41
+ listProjects: () => db
42
+ .prepare("SELECT alias, path FROM projects ORDER BY CASE WHEN alias = ? THEN 0 ELSE 1 END, alias")
43
+ .all(HOME_PROJECT_ALIAS),
44
+ getProject: (alias) => {
45
+ const normalized = normalizeAlias(alias);
46
+ const row = db
47
+ .prepare("SELECT alias, path FROM projects WHERE alias = ?")
48
+ .get(normalized);
49
+ return row ?? null;
50
+ },
51
+ addProject: (alias, projectPath) => {
52
+ const normalized = normalizeAlias(alias);
53
+ if (normalized === HOME_PROJECT_ALIAS) {
54
+ throw new Error("Cannot add project using reserved alias 'home'");
55
+ }
56
+ const resolved = resolveProjectPath(projectPath);
57
+ db.prepare("INSERT INTO projects (alias, path) VALUES (?, ?)").run(normalized, resolved);
58
+ return { alias: normalized, path: resolved };
59
+ },
60
+ removeProject: (alias) => {
61
+ const normalized = normalizeAlias(alias);
62
+ if (normalized === HOME_PROJECT_ALIAS) {
63
+ throw new Error("Cannot remove the home project");
64
+ }
65
+ const result = db
66
+ .prepare("DELETE FROM projects WHERE alias = ?")
67
+ .run(normalized);
68
+ if (result.changes === 0) {
69
+ throw new Error(`Project alias '${normalized}' not found`);
70
+ }
71
+ },
72
+ };
73
+ };
74
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type PromptGuard = {
2
+ tryStart: (chatId: number, onTimeout: () => void) => AbortController | null;
3
+ finish: (chatId: number) => void;
4
+ isInFlight: (chatId: number) => boolean;
5
+ };
6
+ export declare const createPromptGuard: (timeoutMs: number) => PromptGuard;
7
+ //# sourceMappingURL=prompt-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-guard.d.ts","sourceRoot":"","sources":["../src/prompt-guard.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,KAAK,eAAe,GAAG,IAAI,CAAA;IAC3E,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CACxC,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,WAuDrD,CAAA"}
@@ -0,0 +1,47 @@
1
+ export const createPromptGuard = (timeoutMs) => {
2
+ const inFlight = new Map();
3
+ const tryStart = (chatId, onTimeout) => {
4
+ /*
5
+ * This function is synchronous. It schedules a timeout and returns
6
+ * immediately with an AbortController. The timeout callback will run
7
+ * later on the event loop if it is not cleared first.
8
+ */
9
+ if (inFlight.has(chatId)) {
10
+ return null;
11
+ }
12
+ const abortController = new AbortController();
13
+ const timeoutId = setTimeout(() => {
14
+ /*
15
+ * Only fire the timeout if this prompt is still the active one for
16
+ * the chat. If it already finished or was replaced, do nothing.
17
+ */
18
+ const entry = inFlight.get(chatId);
19
+ if (!entry || entry.abortController !== abortController) {
20
+ return;
21
+ }
22
+ inFlight.delete(chatId);
23
+ abortController.abort();
24
+ onTimeout();
25
+ }, timeoutMs);
26
+ inFlight.set(chatId, { abortController, timeoutId });
27
+ return abortController;
28
+ };
29
+ const finish = (chatId) => {
30
+ /*
31
+ * Clearing the timeout cancels the scheduled callback so it never runs.
32
+ * This prevents late timeout handling after a prompt already finished.
33
+ */
34
+ const entry = inFlight.get(chatId);
35
+ if (!entry) {
36
+ return;
37
+ }
38
+ clearTimeout(entry.timeoutId);
39
+ inFlight.delete(chatId);
40
+ };
41
+ return {
42
+ tryStart,
43
+ finish,
44
+ isInFlight: (chatId) => inFlight.has(chatId),
45
+ };
46
+ };
47
+ //# sourceMappingURL=prompt-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-guard.js","sourceRoot":"","sources":["../src/prompt-guard.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,SAAiB,EAAe,EAAE;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAGrB,CAAA;IAEH,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,SAAqB,EAAE,EAAE;QACzD;;;;WAIG;QACH,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAA;QAC7C,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC;;;eAGG;YACH,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,eAAe,KAAK,eAAe,EAAE,CAAC;gBACxD,OAAM;YACR,CAAC;YAED,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACvB,eAAe,CAAC,KAAK,EAAE,CAAA;YACvB,SAAS,EAAE,CAAA;QACb,CAAC,EAAE,SAAS,CAAC,CAAA;QAEb,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC,CAAA;QACpD,OAAO,eAAe,CAAA;IACxB,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE;QAChC;;;WAGG;QACH,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAM;QACR,CAAC;QAED,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC7B,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACzB,CAAC,CAAA;IAED,OAAO;QACL,QAAQ;QACR,MAAM;QACN,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;KAC7C,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { type StoreOptions } from "./storage.js";
2
+ import type { SessionStore } from "./opencode.js";
3
+ export type ChatProjectStore = {
4
+ getActiveAlias: (chatId: number) => string | null;
5
+ setActiveAlias: (chatId: number, alias: string) => void;
6
+ clearActiveAlias: (chatId: number) => boolean;
7
+ };
8
+ export declare const createChatProjectStore: (options?: StoreOptions) => ChatProjectStore;
9
+ export declare const createPersistentSessionStore: (options?: StoreOptions) => SessionStore;
10
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,KAAK,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAA;AAE/D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACjD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CAC9C,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,UAAS,YAAiB,KACzB,gBAyBF,CAAA;AAED,eAAO,MAAM,4BAA4B,GACvC,UAAS,YAAiB,KACzB,YA6CF,CAAA"}
package/dist/state.js ADDED
@@ -0,0 +1,56 @@
1
+ import { createDatabase } from "./storage.js";
2
+ export const createChatProjectStore = (options = {}) => {
3
+ const db = createDatabase(options);
4
+ db.exec("CREATE TABLE IF NOT EXISTS chat_projects (chat_id INTEGER PRIMARY KEY, alias TEXT NOT NULL)");
5
+ return {
6
+ getActiveAlias: (chatId) => {
7
+ const row = db
8
+ .prepare("SELECT alias FROM chat_projects WHERE chat_id = ?")
9
+ .get(chatId);
10
+ return row?.alias ?? null;
11
+ },
12
+ setActiveAlias: (chatId, alias) => {
13
+ db.prepare("INSERT INTO chat_projects (chat_id, alias) VALUES (?, ?) ON CONFLICT(chat_id) DO UPDATE SET alias = excluded.alias").run(chatId, alias);
14
+ },
15
+ clearActiveAlias: (chatId) => {
16
+ const result = db
17
+ .prepare("DELETE FROM chat_projects WHERE chat_id = ?")
18
+ .run(chatId);
19
+ return result.changes > 0;
20
+ },
21
+ };
22
+ };
23
+ export const createPersistentSessionStore = (options = {}) => {
24
+ const db = createDatabase(options);
25
+ db.exec("CREATE TABLE IF NOT EXISTS chat_sessions (chat_id INTEGER NOT NULL, project_dir TEXT NOT NULL, session_id TEXT NOT NULL, PRIMARY KEY (chat_id, project_dir))");
26
+ return {
27
+ getSessionId: (chatId, projectDir) => {
28
+ const row = db
29
+ .prepare("SELECT session_id FROM chat_sessions WHERE chat_id = ? AND project_dir = ?")
30
+ .get(chatId, projectDir);
31
+ return row?.session_id;
32
+ },
33
+ setSessionId: (chatId, projectDir, sessionId) => {
34
+ db.prepare("INSERT INTO chat_sessions (chat_id, project_dir, session_id) VALUES (?, ?, ?) ON CONFLICT(chat_id, project_dir) DO UPDATE SET session_id = excluded.session_id").run(chatId, projectDir, sessionId);
35
+ },
36
+ clearSession: (chatId, projectDir) => {
37
+ const result = db
38
+ .prepare("DELETE FROM chat_sessions WHERE chat_id = ? AND project_dir = ?")
39
+ .run(chatId, projectDir);
40
+ return result.changes > 0;
41
+ },
42
+ getSessionOwner: (sessionId) => {
43
+ const row = db
44
+ .prepare("SELECT chat_id, project_dir FROM chat_sessions WHERE session_id = ? LIMIT 1")
45
+ .get(sessionId);
46
+ if (!row) {
47
+ return null;
48
+ }
49
+ return {
50
+ chatId: row.chat_id,
51
+ projectDir: row.project_dir,
52
+ };
53
+ },
54
+ };
55
+ };
56
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqB,MAAM,cAAc,CAAA;AAShE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,UAAwB,EAAE,EACR,EAAE;IACpB,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IAClC,EAAE,CAAC,IAAI,CACL,6FAA6F,CAC9F,CAAA;IAED,OAAO;QACL,cAAc,EAAE,CAAC,MAAM,EAAE,EAAE;YACzB,MAAM,GAAG,GAAG,EAAE;iBACX,OAAO,CAAC,mDAAmD,CAAC;iBAC5D,GAAG,CAAC,MAAM,CAAkC,CAAA;YAC/C,OAAO,GAAG,EAAE,KAAK,IAAI,IAAI,CAAA;QAC3B,CAAC;QACD,cAAc,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAChC,EAAE,CAAC,OAAO,CACR,oHAAoH,CACrH,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACtB,CAAC;QACD,gBAAgB,EAAE,CAAC,MAAM,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,EAAE;iBACd,OAAO,CAAC,6CAA6C,CAAC;iBACtD,GAAG,CAAC,MAAM,CAAC,CAAA;YACd,OAAO,MAAM,CAAC,OAAO,GAAG,CAAC,CAAA;QAC3B,CAAC;KACF,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,UAAwB,EAAE,EACZ,EAAE;IAChB,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IAClC,EAAE,CAAC,IAAI,CACL,8JAA8J,CAC/J,CAAA;IAED,OAAO;QACL,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;YACnC,MAAM,GAAG,GAAG,EAAE;iBACX,OAAO,CACN,4EAA4E,CAC7E;iBACA,GAAG,CAAC,MAAM,EAAE,UAAU,CAAuC,CAAA;YAChE,OAAO,GAAG,EAAE,UAAU,CAAA;QACxB,CAAC;QACD,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE;YAC9C,EAAE,CAAC,OAAO,CACR,gKAAgK,CACjK,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,CAAA;QACtC,CAAC;QACD,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;YACnC,MAAM,MAAM,GAAG,EAAE;iBACd,OAAO,CACN,iEAAiE,CAClE;iBACA,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;YAC1B,OAAO,MAAM,CAAC,OAAO,GAAG,CAAC,CAAA;QAC3B,CAAC;QACD,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE;YAC7B,MAAM,GAAG,GAAG,EAAE;iBACX,OAAO,CACN,6EAA6E,CAC9E;iBACA,GAAG,CAAC,SAAS,CAAyD,CAAA;YAEzE,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,IAAI,CAAA;YACb,CAAC;YAED,OAAO;gBACL,MAAM,EAAE,GAAG,CAAC,OAAO;gBACnB,UAAU,EAAE,GAAG,CAAC,WAAW;aACL,CAAA;QAC1B,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,8 @@
1
+ import Database from "better-sqlite3";
2
+ export type StoreOptions = {
3
+ dbPath?: string;
4
+ };
5
+ type DatabaseInstance = ReturnType<typeof Database>;
6
+ export declare const createDatabase: (options?: StoreOptions) => DatabaseInstance;
7
+ export {};
8
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAIA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAErC,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAaD,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAA;AAEnD,eAAO,MAAM,cAAc,GAAI,UAAS,YAAiB,KAAG,gBAI3D,CAAA"}
@@ -0,0 +1,16 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import Database from "better-sqlite3";
5
+ const ensureDatabaseDirectory = (dbPath) => {
6
+ const directory = path.dirname(dbPath);
7
+ fs.mkdirSync(directory, { recursive: true });
8
+ };
9
+ const getDefaultDatabasePath = () => path.join(os.homedir(), ".opencode-telegram-bridge", "projects.db");
10
+ const resolveDatabasePath = (options = {}) => options.dbPath ?? getDefaultDatabasePath();
11
+ export const createDatabase = (options = {}) => {
12
+ const dbPath = resolveDatabasePath(options);
13
+ ensureDatabaseDirectory(dbPath);
14
+ return new Database(dbPath);
15
+ };
16
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAMrC,MAAM,uBAAuB,GAAG,CAAC,MAAc,EAAE,EAAE;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;AAC9C,CAAC,CAAA;AAED,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAClC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,2BAA2B,EAAE,aAAa,CAAC,CAAA;AAErE,MAAM,mBAAmB,GAAG,CAAC,UAAwB,EAAE,EAAE,EAAE,CACzD,OAAO,CAAC,MAAM,IAAI,sBAAsB,EAAE,CAAA;AAI5C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,UAAwB,EAAE,EAAoB,EAAE;IAC7E,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAC3C,uBAAuB,CAAC,MAAM,CAAC,CAAA;IAC/B,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC7B,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare const TELEGRAM_MESSAGE_LIMIT = 4096;
2
+ export declare const splitTelegramMessage: (text: string, limit?: number) => string[];
3
+ //# sourceMappingURL=telegram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,OAAO,CAAA;AAE1C,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,EACZ,cAA8B,KAC7B,MAAM,EAWR,CAAA"}
@@ -0,0 +1,12 @@
1
+ export const TELEGRAM_MESSAGE_LIMIT = 4096;
2
+ export const splitTelegramMessage = (text, limit = TELEGRAM_MESSAGE_LIMIT) => {
3
+ if (text.length <= limit) {
4
+ return [text];
5
+ }
6
+ const chunks = [];
7
+ for (let offset = 0; offset < text.length; offset += limit) {
8
+ chunks.push(text.slice(offset, offset + limit));
9
+ }
10
+ return chunks;
11
+ };
12
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAE1C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,IAAY,EACZ,KAAK,GAAG,sBAAsB,EACpB,EAAE;IACZ,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,CAAA;IACf,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,KAAK,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
@@ -0,0 +1,17 @@
1
+ # Configuration
2
+
3
+ Set these environment variables in `.env` or in your service environment.
4
+
5
+ ## Required
6
+ - `TELEGRAM_BOT_TOKEN` - Telegram bot token.
7
+ - `TELEGRAM_ALLOWED_USER_ID` - Telegram user ID allowed to use the bot.
8
+ - `OPENCODE_SERVER_URL` - OpenCode server URL (default server is `http://127.0.0.1:4096`).
9
+
10
+ ## Optional
11
+ - `OPENCODE_SERVER_USERNAME` - Basic auth username (default: `opencode`).
12
+ - `OPENCODE_SERVER_PASSWORD` - Basic auth password.
13
+ - `OPENCODE_PROMPT_TIMEOUT_MS` - Prompt timeout in milliseconds (default: 600000).
14
+ - `TELEGRAM_HANDLER_TIMEOUT_MS` - Telegraf handler timeout in milliseconds (default: prompt timeout + 30000).
15
+
16
+ ## Data storage
17
+ - Project aliases and chat selections are stored in `~/.opencode-telegram-bridge/projects.db`.
package/docs/index.md ADDED
@@ -0,0 +1,8 @@
1
+ # OpenCode Telegram Bridge Docs
2
+
3
+ Start here:
4
+ - `installation.md`
5
+ - `configuration.md`
6
+ - `usage.md`
7
+ - `systemd.md`
8
+ - `release.md`
@@ -0,0 +1,42 @@
1
+ # Installation
2
+
3
+ ## Prerequisites
4
+ - Node.js 18+
5
+ - OpenCode CLI installed and available on PATH (see https://opencode.ai/docs/cli/)
6
+
7
+ ## Install dependencies
8
+ ```bash
9
+ npm install
10
+ ```
11
+
12
+ ## Start OpenCode server
13
+ ```bash
14
+ opencode serve
15
+ ```
16
+
17
+ ## Configure environment
18
+ Copy the example file and fill in values:
19
+ ```bash
20
+ cp .env.example .env
21
+ ```
22
+
23
+ ## Run the bot
24
+ ```bash
25
+ npm run dev
26
+ ```
27
+
28
+ For production, build and run:
29
+ ```bash
30
+ npm run build
31
+ npm start
32
+ ```
33
+
34
+ ## Install via npm
35
+ ```bash
36
+ npm install -g opencode-telegram-bridge
37
+ ```
38
+
39
+ Run:
40
+ ```bash
41
+ opencode-telegram-bridge
42
+ ```
@@ -0,0 +1,56 @@
1
+ # Release
2
+
3
+ This project is published as an npm package.
4
+
5
+ ## Changesets workflow
6
+ 1. Add a changeset for your change:
7
+
8
+ ```bash
9
+ npx changeset
10
+ ```
11
+
12
+ 2. Version the release:
13
+
14
+ ```bash
15
+ npm run version
16
+ ```
17
+
18
+ 3. Publish:
19
+
20
+ ```bash
21
+ npm run release
22
+ ```
23
+
24
+ The `prepublishOnly` script runs the build to ensure `dist/` is included.
25
+
26
+ ## CI enforcement
27
+ CI fails if a relevant code change lands without a `.changeset/*.md` file.
28
+ Docs-only updates (`docs/`, `README.md`, `.github/`) do not require a changeset.
29
+
30
+ ## FAQ
31
+
32
+ ### How is the version bump decided?
33
+ You pick it when you run `npx changeset`. The changeset file records whether the
34
+ change is a `patch`, `minor`, or `major`. When you run `npm run version`, all
35
+ pending changesets are read and the highest required bump wins.
36
+
37
+ ### How is the changelog generated?
38
+ The changelog is generated from the text in the changeset files, not from
39
+ commits. Each changeset contributes a short release note entry.
40
+
41
+ ### Do commits or pushes equal a version?
42
+ No. A version is created only when you run `npm run version` (or when an
43
+ automated release does the same). Multiple commits can be part of one release.
44
+
45
+ ### What happens if I push to main without a changeset?
46
+ CI fails. Add a changeset in a follow-up commit and push again. Do not rewrite
47
+ history on main.
48
+
49
+ ### Do I need one changeset per commit or per push?
50
+ No. You need one changeset per release-worthy change (often one per PR). A
51
+ single changeset can cover multiple commits.
52
+
53
+ ### When are tags created?
54
+ Tags are created when you run the release process (for example, after
55
+ `npm run version` and `npm publish`). If CI fails due to a missing changeset,
56
+ no tag is created yet.
@@ -0,0 +1,42 @@
1
+ # systemd Service (Linux)
2
+
3
+ This project ships a systemd unit template in `systemd/opencode-telegram-bridge.service`.
4
+
5
+ ## Install
6
+ 1. Build the project:
7
+
8
+ ```bash
9
+ npm install
10
+ npm run build
11
+ ```
12
+
13
+ 2. Copy the service and env files:
14
+
15
+ ```bash
16
+ sudo mkdir -p /etc/opencode-telegram-bridge
17
+ sudo cp systemd/opencode-telegram-bridge.service /etc/systemd/system/opencode-telegram-bridge.service
18
+ sudo cp systemd/opencode-telegram-bridge.env.example /etc/opencode-telegram-bridge.env
19
+ ```
20
+
21
+ 3. Edit the env file:
22
+
23
+ ```bash
24
+ sudo nano /etc/opencode-telegram-bridge.env
25
+ ```
26
+
27
+ 4. Update the service paths if needed:
28
+
29
+ - `ExecStart=/usr/bin/opencode-telegram-bridge`
30
+ - If you installed via npm in a non-standard location, set `ExecStart` to the output of `command -v opencode-telegram-bridge`.
31
+
32
+ 5. Enable and start:
33
+
34
+ ```bash
35
+ sudo systemctl daemon-reload
36
+ sudo systemctl enable --now opencode-telegram-bridge
37
+ ```
38
+
39
+ ## Logs
40
+ ```bash
41
+ sudo journalctl -u opencode-telegram-bridge -f
42
+ ```
package/docs/usage.md ADDED
@@ -0,0 +1,20 @@
1
+ # Usage
2
+
3
+ ## Bot commands
4
+ - `/start` - confirm the bot is online.
5
+ - `/project list` - list project aliases (active project marked with `*`).
6
+ - `/project current` - show the active project alias and path.
7
+ - `/project add <alias> <path>` - add a project alias.
8
+ - `/project remove <alias>` - remove a project alias.
9
+ - `/project set <alias>` - set the active project alias for this chat.
10
+ - `/reset` - reset the OpenCode session for the active project.
11
+
12
+ ## Notes
13
+ - Only one prompt is processed at a time per chat.
14
+ - Permission requests show inline buttons in Telegram.
15
+
16
+ ## CLI
17
+ If installed via npm, start the bot with:
18
+ ```bash
19
+ opencode-telegram-bridge
20
+ ```