opencode-telegram-bot 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/app.d.ts +46 -1
- package/dist/index.js +185 -22
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -107,6 +107,11 @@ These are handled directly by the Telegram bot:
|
|
|
107
107
|
| `/export full` | Export with all details (thinking, costs, steps) |
|
|
108
108
|
| `/verbose` | Toggle verbose mode (show thinking and tool calls in chat) |
|
|
109
109
|
| `/verbose on|off` | Explicitly enable/disable verbose mode |
|
|
110
|
+
| `/model` | Show current model and usage hints |
|
|
111
|
+
| `/model <keyword>` | Search models by keyword |
|
|
112
|
+
| `/model <number>` | Switch to a model from the last search |
|
|
113
|
+
| `/model default` | Reset to the default model |
|
|
114
|
+
| `/usage` | Show token and cost usage for this session |
|
|
110
115
|
| `/help` | Show available commands |
|
|
111
116
|
|
|
112
117
|
### Verbose Mode
|
|
@@ -129,6 +134,39 @@ Example with verbose mode on:
|
|
|
129
134
|
Here's what I found in the auth module...
|
|
130
135
|
```
|
|
131
136
|
|
|
137
|
+
### Model Switching
|
|
138
|
+
|
|
139
|
+
Use `/model` to search and switch models without typing long names:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
You: /model sonnet
|
|
143
|
+
Bot: Models matching "sonnet":
|
|
144
|
+
1. claude-sonnet-4-5 (google-vertex-anthropic)
|
|
145
|
+
2. claude-sonnet-4 (anthropic)
|
|
146
|
+
|
|
147
|
+
Use /model <number> to select.
|
|
148
|
+
|
|
149
|
+
You: /model 1
|
|
150
|
+
Bot: Switched to claude-sonnet-4-5 (google-vertex-anthropic)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Other commands:
|
|
154
|
+
|
|
155
|
+
- `/model` shows the current model and usage hints
|
|
156
|
+
- `/model default` resets to the server default
|
|
157
|
+
|
|
158
|
+
## Usage
|
|
159
|
+
|
|
160
|
+
Use `/usage` to see the current session's token counts and estimated cost:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Session usage:
|
|
164
|
+
- Assistant responses: 4
|
|
165
|
+
- Tokens: 1200 total (input 600, output 500, reasoning 100)
|
|
166
|
+
- Cache: read 1200, write 80
|
|
167
|
+
- Cost: $0.0123
|
|
168
|
+
```
|
|
169
|
+
|
|
132
170
|
### Session Export
|
|
133
171
|
|
|
134
172
|
The `/export` command builds a markdown file from the current session and saves it to the directory where OpenCode is running. The file is also sent back to you as a Telegram document.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
|
+
interface OpencodeClientLike {
|
|
2
|
+
session: {
|
|
3
|
+
list: (options?: any) => Promise<any>;
|
|
4
|
+
create: (options: any) => Promise<any>;
|
|
5
|
+
update: (options: any) => Promise<any>;
|
|
6
|
+
delete: (options: any) => Promise<any>;
|
|
7
|
+
get: (options: any) => Promise<any>;
|
|
8
|
+
messages: (options: any) => Promise<any>;
|
|
9
|
+
command: (options: any) => Promise<any>;
|
|
10
|
+
promptAsync: (options: any) => Promise<any>;
|
|
11
|
+
};
|
|
12
|
+
command: {
|
|
13
|
+
list: (options?: any) => Promise<any>;
|
|
14
|
+
};
|
|
15
|
+
provider: {
|
|
16
|
+
list: (options?: any) => Promise<any>;
|
|
17
|
+
};
|
|
18
|
+
path: {
|
|
19
|
+
get: (options?: any) => Promise<any>;
|
|
20
|
+
};
|
|
21
|
+
event: {
|
|
22
|
+
subscribe: (options?: any) => Promise<any>;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
1
25
|
interface StartOptions {
|
|
2
26
|
url: string;
|
|
3
27
|
model?: string;
|
|
28
|
+
launch?: boolean;
|
|
29
|
+
client?: OpencodeClientLike;
|
|
30
|
+
botFactory?: (token: string) => TelegramBot;
|
|
31
|
+
sessionsFilePath?: string;
|
|
32
|
+
}
|
|
33
|
+
interface TelegramBot {
|
|
34
|
+
use: (fn: (ctx: any, next: () => Promise<void>) => Promise<void> | void) => void;
|
|
35
|
+
start: (fn: (ctx: any) => void | Promise<void>) => void;
|
|
36
|
+
help: (fn: (ctx: any) => void | Promise<void>) => void;
|
|
37
|
+
command: (command: string, fn: (ctx: any) => void | Promise<void>) => void;
|
|
38
|
+
on: (event: string, fn: (ctx: any) => void | Promise<void>) => void;
|
|
39
|
+
launch: () => Promise<void>;
|
|
40
|
+
stop: (reason?: string) => void;
|
|
41
|
+
telegram: {
|
|
42
|
+
sendMessage: (chatId: number, text: string, options?: Record<string, unknown>) => Promise<void>;
|
|
43
|
+
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
44
|
+
sendDocument: (chatId: number, file: {
|
|
45
|
+
source: Buffer;
|
|
46
|
+
filename: string;
|
|
47
|
+
}) => Promise<void>;
|
|
48
|
+
};
|
|
4
49
|
}
|
|
5
|
-
export declare function startTelegram(options: StartOptions): Promise<
|
|
50
|
+
export declare function startTelegram(options: StartOptions): Promise<TelegramBot>;
|
|
6
51
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -18584,8 +18584,9 @@ async function startTelegram(options) {
|
|
|
18584
18584
|
else {
|
|
18585
18585
|
console.log(`[Telegram] Bot is restricted to user ID: ${authorizedUserId}`);
|
|
18586
18586
|
}
|
|
18587
|
+
const sessionsFile = options.sessionsFilePath || SESSIONS_FILE;
|
|
18587
18588
|
// Initialize OpenCode client
|
|
18588
|
-
const client = client_createOpencodeClient({ baseUrl: url });
|
|
18589
|
+
const client = options.client || client_createOpencodeClient({ baseUrl: url });
|
|
18589
18590
|
// Verify connection to the OpenCode server and fetch available commands
|
|
18590
18591
|
const opencodeCommands = new Set();
|
|
18591
18592
|
let projectDirectory = "";
|
|
@@ -18615,7 +18616,7 @@ async function startTelegram(options) {
|
|
|
18615
18616
|
}
|
|
18616
18617
|
// Telegram-only commands that should not be forwarded to OpenCode
|
|
18617
18618
|
const telegramCommands = new Set([
|
|
18618
|
-
"start", "help", "new", "sessions", "switch", "title", "delete", "export", "verbose",
|
|
18619
|
+
"start", "help", "new", "sessions", "switch", "title", "delete", "export", "verbose", "model", "usage",
|
|
18619
18620
|
]);
|
|
18620
18621
|
// Map of chatId -> sessionId for the active session per chat
|
|
18621
18622
|
const chatSessions = new Map();
|
|
@@ -18623,6 +18624,10 @@ async function startTelegram(options) {
|
|
|
18623
18624
|
const knownSessionIds = new Set();
|
|
18624
18625
|
// Set of chatIds with verbose mode enabled
|
|
18625
18626
|
const chatVerboseMode = new Set();
|
|
18627
|
+
// Map of chatId -> model override (provider/model)
|
|
18628
|
+
const chatModelOverride = new Map();
|
|
18629
|
+
// Map of chatId -> last search results (in-memory only)
|
|
18630
|
+
const chatModelSearchResults = new Map();
|
|
18626
18631
|
/**
|
|
18627
18632
|
* Save the chat-to-session mapping and known session IDs to disk.
|
|
18628
18633
|
*/
|
|
@@ -18632,8 +18637,9 @@ async function startTelegram(options) {
|
|
|
18632
18637
|
active: Object.fromEntries(chatSessions),
|
|
18633
18638
|
known: [...knownSessionIds],
|
|
18634
18639
|
verbose: [...chatVerboseMode],
|
|
18640
|
+
models: Object.fromEntries(chatModelOverride),
|
|
18635
18641
|
};
|
|
18636
|
-
(0,external_node_fs_.writeFileSync)(
|
|
18642
|
+
(0,external_node_fs_.writeFileSync)(sessionsFile, JSON.stringify(data, null, 2));
|
|
18637
18643
|
}
|
|
18638
18644
|
catch (err) {
|
|
18639
18645
|
console.error("[Telegram] Failed to save sessions file:", err);
|
|
@@ -18648,21 +18654,23 @@ async function startTelegram(options) {
|
|
|
18648
18654
|
let storedActive = {};
|
|
18649
18655
|
let storedKnown = [];
|
|
18650
18656
|
let storedVerbose = [];
|
|
18651
|
-
|
|
18657
|
+
let storedModels = {};
|
|
18658
|
+
if ((0,external_node_fs_.existsSync)(sessionsFile)) {
|
|
18652
18659
|
try {
|
|
18653
|
-
const raw = (0,external_node_fs_.readFileSync)(
|
|
18660
|
+
const raw = (0,external_node_fs_.readFileSync)(sessionsFile, "utf-8");
|
|
18654
18661
|
const parsed = JSON.parse(raw);
|
|
18655
18662
|
if (parsed.active && typeof parsed.active === "object") {
|
|
18656
18663
|
// New format: { active: {...}, known: [...], verbose: [...] }
|
|
18657
18664
|
storedActive = parsed.active;
|
|
18658
18665
|
storedKnown = parsed.known || [];
|
|
18659
18666
|
storedVerbose = parsed.verbose || [];
|
|
18667
|
+
storedModels = parsed.models || {};
|
|
18660
18668
|
}
|
|
18661
18669
|
else {
|
|
18662
18670
|
// Old format: flat { chatId: sessionId }
|
|
18663
18671
|
storedActive = parsed;
|
|
18664
18672
|
}
|
|
18665
|
-
console.log(`[Telegram] Loaded ${Object.keys(storedActive).length} active session(s) and ${storedKnown.length} known session(s) from ${
|
|
18673
|
+
console.log(`[Telegram] Loaded ${Object.keys(storedActive).length} active session(s) and ${storedKnown.length} known session(s) from ${sessionsFile}`);
|
|
18666
18674
|
}
|
|
18667
18675
|
catch (err) {
|
|
18668
18676
|
console.warn("[Telegram] Failed to parse sessions file:", err);
|
|
@@ -18675,6 +18683,12 @@ async function startTelegram(options) {
|
|
|
18675
18683
|
if (storedVerbose.length > 0) {
|
|
18676
18684
|
console.log(`[Telegram] Restored verbose mode for ${storedVerbose.length} chat(s)`);
|
|
18677
18685
|
}
|
|
18686
|
+
// Restore model overrides
|
|
18687
|
+
for (const [chatId, modelId] of Object.entries(storedModels)) {
|
|
18688
|
+
if (modelId) {
|
|
18689
|
+
chatModelOverride.set(chatId, modelId);
|
|
18690
|
+
}
|
|
18691
|
+
}
|
|
18678
18692
|
// Fetch all server sessions once for validation and fallback matching
|
|
18679
18693
|
let serverSessions = [];
|
|
18680
18694
|
try {
|
|
@@ -19010,7 +19024,9 @@ async function startTelegram(options) {
|
|
|
19010
19024
|
}
|
|
19011
19025
|
}
|
|
19012
19026
|
// Initialize Telegram bot
|
|
19013
|
-
const bot =
|
|
19027
|
+
const bot = options.botFactory
|
|
19028
|
+
? options.botFactory(token)
|
|
19029
|
+
: new lib.Telegraf(token);
|
|
19014
19030
|
// Middleware to check if the user is authorized
|
|
19015
19031
|
bot.use((ctx, next) => {
|
|
19016
19032
|
if (!authorizedUserId) {
|
|
@@ -19039,6 +19055,8 @@ async function startTelegram(options) {
|
|
|
19039
19055
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19040
19056
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
19041
19057
|
"/verbose on|off - Set verbose mode explicitly\n" +
|
|
19058
|
+
"/model - Show or search available models\n" +
|
|
19059
|
+
"/usage - Show token and cost usage for this session\n" +
|
|
19042
19060
|
"/help - Show this help message\n";
|
|
19043
19061
|
if (opencodeCommands.size > 0) {
|
|
19044
19062
|
msg +=
|
|
@@ -19060,6 +19078,8 @@ async function startTelegram(options) {
|
|
|
19060
19078
|
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
19061
19079
|
"/verbose - Toggle verbose mode (show thinking and tool calls)\n" +
|
|
19062
19080
|
"/verbose on|off - Set verbose mode explicitly\n" +
|
|
19081
|
+
"/model - Show or search available models\n" +
|
|
19082
|
+
"/usage - Show token and cost usage for this session\n" +
|
|
19063
19083
|
"/help - Show this help message\n";
|
|
19064
19084
|
if (opencodeCommands.size > 0) {
|
|
19065
19085
|
msg +=
|
|
@@ -19081,9 +19101,10 @@ async function startTelegram(options) {
|
|
|
19081
19101
|
*/
|
|
19082
19102
|
async function getKnownSessions() {
|
|
19083
19103
|
const list = await client.session.list();
|
|
19084
|
-
|
|
19104
|
+
const data = (list.data || []);
|
|
19105
|
+
if (data.length === 0)
|
|
19085
19106
|
return [];
|
|
19086
|
-
return
|
|
19107
|
+
return data
|
|
19087
19108
|
.filter((s) => knownSessionIds.has(s.id))
|
|
19088
19109
|
.sort((a, b) => b.time.updated - a.time.updated);
|
|
19089
19110
|
}
|
|
@@ -19245,6 +19266,144 @@ async function startTelegram(options) {
|
|
|
19245
19266
|
? "Verbose mode enabled. Responses will include thinking and tool calls."
|
|
19246
19267
|
: "Verbose mode disabled. Responses will only show the assistant's text.");
|
|
19247
19268
|
});
|
|
19269
|
+
/**
|
|
19270
|
+
* Fetch and search available models from connected providers.
|
|
19271
|
+
*/
|
|
19272
|
+
async function searchModels(keyword) {
|
|
19273
|
+
const list = await client.provider.list();
|
|
19274
|
+
if (list.error || !list.data) {
|
|
19275
|
+
throw new Error(`Failed to list providers: ${JSON.stringify(list.error)}`);
|
|
19276
|
+
}
|
|
19277
|
+
const connected = new Set(list.data.connected || []);
|
|
19278
|
+
const results = [];
|
|
19279
|
+
const query = keyword.toLowerCase();
|
|
19280
|
+
for (const provider of list.data.all || []) {
|
|
19281
|
+
if (!connected.has(provider.id))
|
|
19282
|
+
continue;
|
|
19283
|
+
const providerName = (provider.name || provider.id).toLowerCase();
|
|
19284
|
+
const models = provider.models;
|
|
19285
|
+
for (const [modelID, model] of Object.entries(models || {})) {
|
|
19286
|
+
const modelName = ((model && model.name) || modelID).toLowerCase();
|
|
19287
|
+
if (modelID.toLowerCase().includes(query) ||
|
|
19288
|
+
modelName.includes(query) ||
|
|
19289
|
+
providerName.includes(query)) {
|
|
19290
|
+
const displayName = `${(model && model.name) || modelID} (${provider.id})`;
|
|
19291
|
+
results.push({ providerID: provider.id, modelID, displayName });
|
|
19292
|
+
}
|
|
19293
|
+
}
|
|
19294
|
+
}
|
|
19295
|
+
return results;
|
|
19296
|
+
}
|
|
19297
|
+
// Handle /model command
|
|
19298
|
+
// Usage: /model, /model <keyword>, /model <number>, /model default
|
|
19299
|
+
bot.command("model", async (ctx) => {
|
|
19300
|
+
const chatId = ctx.chat.id.toString();
|
|
19301
|
+
const args = ctx.message.text.replace(/^\/model\s*/, "").trim();
|
|
19302
|
+
if (!args) {
|
|
19303
|
+
const current = chatModelOverride.get(chatId) || model || "server default";
|
|
19304
|
+
await ctx.reply(`Current model: ${current}\n\n` +
|
|
19305
|
+
"Use /model <keyword> to search available models.\n" +
|
|
19306
|
+
"Use /model default to reset to the default model.");
|
|
19307
|
+
return;
|
|
19308
|
+
}
|
|
19309
|
+
if (args.toLowerCase() === "default") {
|
|
19310
|
+
chatModelOverride.delete(chatId);
|
|
19311
|
+
saveSessions();
|
|
19312
|
+
await ctx.reply("Model reset to the default model.");
|
|
19313
|
+
return;
|
|
19314
|
+
}
|
|
19315
|
+
const asNumber = Number.parseInt(args, 10);
|
|
19316
|
+
if (!Number.isNaN(asNumber) && String(asNumber) === args) {
|
|
19317
|
+
const results = chatModelSearchResults.get(chatId) || [];
|
|
19318
|
+
if (results.length === 0) {
|
|
19319
|
+
await ctx.reply("No recent search results. Use /model <keyword> first.");
|
|
19320
|
+
return;
|
|
19321
|
+
}
|
|
19322
|
+
if (asNumber < 1 || asNumber > results.length) {
|
|
19323
|
+
await ctx.reply("Invalid selection. Use /model <number> from the latest search results.");
|
|
19324
|
+
return;
|
|
19325
|
+
}
|
|
19326
|
+
const selection = results[asNumber - 1];
|
|
19327
|
+
const value = `${selection.providerID}/${selection.modelID}`;
|
|
19328
|
+
chatModelOverride.set(chatId, value);
|
|
19329
|
+
saveSessions();
|
|
19330
|
+
await ctx.reply(`Switched to ${selection.displayName}`);
|
|
19331
|
+
return;
|
|
19332
|
+
}
|
|
19333
|
+
try {
|
|
19334
|
+
const results = await searchModels(args);
|
|
19335
|
+
if (results.length === 0) {
|
|
19336
|
+
await ctx.reply(`No models found matching "${args}".`);
|
|
19337
|
+
return;
|
|
19338
|
+
}
|
|
19339
|
+
const limited = results.slice(0, 10);
|
|
19340
|
+
chatModelSearchResults.set(chatId, limited);
|
|
19341
|
+
let msg = `Models matching "${args}":\n\n`;
|
|
19342
|
+
for (const [index, item] of limited.entries()) {
|
|
19343
|
+
msg += `${index + 1}. ${item.displayName}\n`;
|
|
19344
|
+
}
|
|
19345
|
+
if (results.length > limited.length) {
|
|
19346
|
+
msg += `\nFound ${results.length} models. Refine your search to narrow the list.`;
|
|
19347
|
+
}
|
|
19348
|
+
msg += "\nUse /model <number> to select.";
|
|
19349
|
+
await ctx.reply(msg);
|
|
19350
|
+
}
|
|
19351
|
+
catch (err) {
|
|
19352
|
+
console.error("[Telegram] Error searching models:", err);
|
|
19353
|
+
await ctx.reply("Failed to list models. Try again later.");
|
|
19354
|
+
}
|
|
19355
|
+
});
|
|
19356
|
+
// Handle /usage command - show token and cost usage for current session
|
|
19357
|
+
bot.command("usage", async (ctx) => {
|
|
19358
|
+
const chatId = ctx.chat.id.toString();
|
|
19359
|
+
const sessionId = chatSessions.get(chatId);
|
|
19360
|
+
if (!sessionId) {
|
|
19361
|
+
await ctx.reply("No active session. Send a message first to create one.");
|
|
19362
|
+
return;
|
|
19363
|
+
}
|
|
19364
|
+
try {
|
|
19365
|
+
const messagesResult = await client.session.messages({
|
|
19366
|
+
path: { id: sessionId },
|
|
19367
|
+
});
|
|
19368
|
+
if (messagesResult.error || !messagesResult.data) {
|
|
19369
|
+
throw new Error(`Failed to get messages: ${JSON.stringify(messagesResult.error)}`);
|
|
19370
|
+
}
|
|
19371
|
+
let assistantCount = 0;
|
|
19372
|
+
let costTotal = 0;
|
|
19373
|
+
let inputTokens = 0;
|
|
19374
|
+
let outputTokens = 0;
|
|
19375
|
+
let reasoningTokens = 0;
|
|
19376
|
+
let cacheRead = 0;
|
|
19377
|
+
let cacheWrite = 0;
|
|
19378
|
+
for (const msg of messagesResult.data) {
|
|
19379
|
+
const info = msg.info;
|
|
19380
|
+
if (info.role !== "assistant")
|
|
19381
|
+
continue;
|
|
19382
|
+
assistantCount += 1;
|
|
19383
|
+
costTotal += info.cost || 0;
|
|
19384
|
+
if (info.tokens) {
|
|
19385
|
+
inputTokens += info.tokens.input || 0;
|
|
19386
|
+
outputTokens += info.tokens.output || 0;
|
|
19387
|
+
reasoningTokens += info.tokens.reasoning || 0;
|
|
19388
|
+
cacheRead += info.tokens.cache?.read || 0;
|
|
19389
|
+
cacheWrite += info.tokens.cache?.write || 0;
|
|
19390
|
+
}
|
|
19391
|
+
}
|
|
19392
|
+
const totalTokens = inputTokens + outputTokens + reasoningTokens;
|
|
19393
|
+
const lines = [
|
|
19394
|
+
`Session usage:`,
|
|
19395
|
+
`- Assistant responses: ${assistantCount}`,
|
|
19396
|
+
`- Tokens: ${totalTokens} total (input ${inputTokens}, output ${outputTokens}, reasoning ${reasoningTokens})`,
|
|
19397
|
+
`- Cache: read ${cacheRead}, write ${cacheWrite}`,
|
|
19398
|
+
`- Cost: $${costTotal.toFixed(4)}`,
|
|
19399
|
+
];
|
|
19400
|
+
await ctx.reply(lines.join("\n"));
|
|
19401
|
+
}
|
|
19402
|
+
catch (err) {
|
|
19403
|
+
console.error("[Telegram] Error getting usage:", err);
|
|
19404
|
+
await ctx.reply("Failed to fetch usage. Try again later.");
|
|
19405
|
+
}
|
|
19406
|
+
});
|
|
19248
19407
|
/**
|
|
19249
19408
|
* Render a message part to markdown.
|
|
19250
19409
|
* In default mode: only text and tool calls (name + input/output).
|
|
@@ -19531,8 +19690,9 @@ async function startTelegram(options) {
|
|
|
19531
19690
|
const promptBody = {
|
|
19532
19691
|
parts: [{ type: "text", text: userText }],
|
|
19533
19692
|
};
|
|
19534
|
-
|
|
19535
|
-
|
|
19693
|
+
const modelOverride = chatModelOverride.get(chatId) || model;
|
|
19694
|
+
if (modelOverride) {
|
|
19695
|
+
const [providerID, ...modelParts] = modelOverride.split("/");
|
|
19536
19696
|
const modelID = modelParts.join("/");
|
|
19537
19697
|
promptBody.model = { providerID, modelID };
|
|
19538
19698
|
}
|
|
@@ -19545,18 +19705,21 @@ async function startTelegram(options) {
|
|
|
19545
19705
|
await ctx.reply("Sorry, there was an error processing your request. Try again or use /new to start a fresh session.");
|
|
19546
19706
|
}
|
|
19547
19707
|
});
|
|
19548
|
-
|
|
19549
|
-
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19708
|
+
if (options.launch !== false) {
|
|
19709
|
+
try {
|
|
19710
|
+
// Start the bot
|
|
19711
|
+
await bot.launch();
|
|
19712
|
+
console.log("[Telegram] Bot is running");
|
|
19713
|
+
// Enable graceful stop
|
|
19714
|
+
process.once("SIGINT", () => bot.stop("SIGINT"));
|
|
19715
|
+
process.once("SIGTERM", () => bot.stop("SIGTERM"));
|
|
19716
|
+
}
|
|
19717
|
+
catch (error) {
|
|
19718
|
+
console.error("Unable to start the Telegram bot:", error);
|
|
19719
|
+
throw error;
|
|
19720
|
+
}
|
|
19559
19721
|
}
|
|
19722
|
+
return bot;
|
|
19560
19723
|
}
|
|
19561
19724
|
/**
|
|
19562
19725
|
* Format a Unix timestamp (seconds) into a human-readable relative time.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-telegram-bot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Telegram bot that forwards messages to an OpenCode agent",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"start": "tsx src/index.ts",
|
|
15
15
|
"serve": "opencode serve",
|
|
16
16
|
"build": "ncc build src/index.ts --out dist",
|
|
17
|
-
"prepublishOnly": "npm run build"
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"test:run": "vitest run",
|
|
20
|
+
"test:integration": "vitest run tests/integration.test.ts"
|
|
18
21
|
},
|
|
19
22
|
"keywords": [
|
|
20
23
|
"opencode",
|
|
@@ -35,6 +38,7 @@
|
|
|
35
38
|
"devDependencies": {
|
|
36
39
|
"@vercel/ncc": "^0.38.3",
|
|
37
40
|
"tsx": "^4.0.0",
|
|
38
|
-
"@types/node": "^20.0.0"
|
|
41
|
+
"@types/node": "^20.0.0",
|
|
42
|
+
"vitest": "^2.1.5"
|
|
39
43
|
}
|
|
40
44
|
}
|