@sarkar-ai/deskmate 0.2.1 → 0.3.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/.env.example +8 -10
- package/README.md +95 -32
- package/dist/cli/init.js +180 -57
- package/dist/cli.js +47 -28
- package/dist/index.js +49 -27
- package/install.sh +103 -24
- package/package.json +4 -4
- package/dist/telegram/bot.js +0 -333
package/dist/telegram/bot.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.startTelegramBot = startTelegramBot;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
|
-
const grammy_1 = require("grammy");
|
|
39
|
-
const executor_1 = require("../core/executor");
|
|
40
|
-
const approval_1 = require("../core/approval");
|
|
41
|
-
const agent_1 = require("../core/agent");
|
|
42
|
-
const logger_1 = require("../core/logger");
|
|
43
|
-
const platform_1 = require("../core/platform");
|
|
44
|
-
const session_1 = require("../gateway/session");
|
|
45
|
-
const log = (0, logger_1.createLogger)("TelegramBot");
|
|
46
|
-
const BOT_NAME = process.env.BOT_NAME || "Deskmate";
|
|
47
|
-
const SCREENSHOT_DIR = process.env.TMPDIR ? `${process.env.TMPDIR}deskmate-screenshots` : "/tmp/deskmate-screenshots";
|
|
48
|
-
const SYSTEM_PROMPT = `You are a local machine assistant named ${BOT_NAME}. Users will ask you to perform tasks on their computer via Telegram.
|
|
49
|
-
|
|
50
|
-
You have access to tools to execute commands, read/write files, and explore the filesystem. Use them to help users accomplish their tasks.
|
|
51
|
-
|
|
52
|
-
SCREENSHOT CAPABILITY:
|
|
53
|
-
When the user asks to see the screen, take a screenshot, or wants visual feedback, use this command:
|
|
54
|
-
${(0, platform_1.getScreenshotHint)(SCREENSHOT_DIR)}
|
|
55
|
-
The screenshot will automatically be sent to the user via Telegram after your response.
|
|
56
|
-
|
|
57
|
-
IMPORTANT RULES:
|
|
58
|
-
- Be concise in your responses (Telegram messages should be brief)
|
|
59
|
-
- Use the available tools to accomplish tasks
|
|
60
|
-
- For dangerous operations, explain what you're about to do before doing it
|
|
61
|
-
- Never use sudo unless explicitly asked
|
|
62
|
-
- Keep responses under 4000 characters (Telegram limit)
|
|
63
|
-
- When asked for screenshots, always use the screenshot command above`;
|
|
64
|
-
async function startTelegramBot() {
|
|
65
|
-
const token = process.env.TELEGRAM_BOT_TOKEN;
|
|
66
|
-
const allowedUserId = parseInt(process.env.ALLOWED_USER_ID || "0", 10);
|
|
67
|
-
if (!token || !allowedUserId) {
|
|
68
|
-
throw new Error("TELEGRAM_BOT_TOKEN and ALLOWED_USER_ID required for Telegram bot");
|
|
69
|
-
}
|
|
70
|
-
const bot = new grammy_1.Bot(token);
|
|
71
|
-
const executor = new executor_1.Executor();
|
|
72
|
-
const workingDir = process.env.WORKING_DIR || process.env.HOME || "/";
|
|
73
|
-
// Create the agent provider (abstracted - can be swapped)
|
|
74
|
-
const agentProvider = (0, agent_1.createAgentProvider)();
|
|
75
|
-
log.info("Using agent provider", { name: agentProvider.name, version: agentProvider.version });
|
|
76
|
-
// Verify provider is available
|
|
77
|
-
const providerAvailable = await agentProvider.isAvailable();
|
|
78
|
-
if (!providerAvailable) {
|
|
79
|
-
log.warn("Agent provider may not be fully available", { provider: agentProvider.name });
|
|
80
|
-
}
|
|
81
|
-
// Store session IDs per chat for context/memory (persisted to disk)
|
|
82
|
-
const chatSessions = new session_1.SessionManager({
|
|
83
|
-
storagePath: path.join(workingDir, "data", "sessions-telegram.json"),
|
|
84
|
-
});
|
|
85
|
-
// Register Telegram as an approval notifier
|
|
86
|
-
approval_1.approvalManager.addNotifier(async (action) => {
|
|
87
|
-
// This will be called when approval is needed
|
|
88
|
-
// We send a Telegram message for the user to approve/reject
|
|
89
|
-
try {
|
|
90
|
-
const keyboard = new grammy_1.InlineKeyboard()
|
|
91
|
-
.text("✅ Approve", `approve:${action.id}`)
|
|
92
|
-
.text("❌ Reject", `reject:${action.id}`);
|
|
93
|
-
let emoji = "🔐";
|
|
94
|
-
let details = "";
|
|
95
|
-
switch (action.type) {
|
|
96
|
-
case "command":
|
|
97
|
-
emoji = "⚡";
|
|
98
|
-
details = `\`\`\`bash\n${action.details.command}\n\`\`\``;
|
|
99
|
-
break;
|
|
100
|
-
case "write_file":
|
|
101
|
-
emoji = "📝";
|
|
102
|
-
details = `Path: \`${action.details.path}\`\nPreview: ${(action.details.contentPreview || "").slice(0, 100)}...`;
|
|
103
|
-
break;
|
|
104
|
-
case "folder_access":
|
|
105
|
-
emoji = "📁";
|
|
106
|
-
details = `Folder: \`${action.details.baseFolder}\`\nFile: \`${action.details.path}\``;
|
|
107
|
-
break;
|
|
108
|
-
case "read_file":
|
|
109
|
-
emoji = "👁️";
|
|
110
|
-
details = `Path: \`${action.details.path}\``;
|
|
111
|
-
break;
|
|
112
|
-
default:
|
|
113
|
-
details = JSON.stringify(action.details, null, 2);
|
|
114
|
-
}
|
|
115
|
-
const timeLeft = Math.ceil((action.expiresAt.getTime() - Date.now()) / 1000);
|
|
116
|
-
await bot.api.sendMessage(allowedUserId, `${emoji} *Approval Required*\n\n` +
|
|
117
|
-
`Type: \`${action.type}\`\n` +
|
|
118
|
-
`${action.description}\n\n` +
|
|
119
|
-
`${details}\n\n` +
|
|
120
|
-
`⏱️ Expires in ${timeLeft}s`, { parse_mode: "Markdown", reply_markup: keyboard });
|
|
121
|
-
log.info("Approval notification sent to Telegram", { actionId: action.id, type: action.type });
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
log.error("Failed to send Telegram notification", { error: error.message });
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
// Auth middleware
|
|
128
|
-
bot.use(async (ctx, next) => {
|
|
129
|
-
if (ctx.from?.id !== allowedUserId) {
|
|
130
|
-
console.log(`Unauthorized: ${ctx.from?.id}`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
await next();
|
|
134
|
-
});
|
|
135
|
-
// Commands
|
|
136
|
-
bot.command("start", (ctx) => ctx.reply(`👋 *${BOT_NAME} Ready*\n\n` +
|
|
137
|
-
"Send me any task and I'll execute it on your local machine.\n\n" +
|
|
138
|
-
"I remember our conversation, so you can ask follow-up questions!\n\n" +
|
|
139
|
-
"*Examples:*\n" +
|
|
140
|
-
"• `list all docker containers`\n" +
|
|
141
|
-
"• `what's using port 3000?`\n" +
|
|
142
|
-
"• `show disk usage`\n" +
|
|
143
|
-
"• `take a screenshot`\n\n" +
|
|
144
|
-
"*Commands:*\n" +
|
|
145
|
-
"• /screenshot - Take a screenshot\n" +
|
|
146
|
-
"• /status - System info\n" +
|
|
147
|
-
"• /reset - Clear memory & start fresh", { parse_mode: "Markdown" }));
|
|
148
|
-
// Quick screenshot command
|
|
149
|
-
bot.command("screenshot", async (ctx) => {
|
|
150
|
-
const chatId = ctx.chat.id;
|
|
151
|
-
await ctx.reply("📸 Taking screenshot...");
|
|
152
|
-
try {
|
|
153
|
-
const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
154
|
-
const { promisify } = await Promise.resolve().then(() => __importStar(require("util")));
|
|
155
|
-
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
156
|
-
const path = await Promise.resolve().then(() => __importStar(require("path")));
|
|
157
|
-
const execAsync = promisify(exec);
|
|
158
|
-
await fs.mkdir(SCREENSHOT_DIR, { recursive: true });
|
|
159
|
-
const filename = `screenshot-${Date.now()}.png`;
|
|
160
|
-
const filepath = path.join(SCREENSHOT_DIR, filename);
|
|
161
|
-
await execAsync((0, platform_1.getScreenshotCommand)(filepath));
|
|
162
|
-
await bot.api.sendPhoto(chatId, new grammy_1.InputFile(filepath), {
|
|
163
|
-
caption: "📸 Screenshot",
|
|
164
|
-
});
|
|
165
|
-
await fs.unlink(filepath).catch(() => { });
|
|
166
|
-
log.info("Screenshot command completed", { chatId });
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
log.error("Screenshot command failed", { error: error.message });
|
|
170
|
-
await ctx.reply(`❌ Screenshot failed: ${error.message}`);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
bot.command("status", async (ctx) => {
|
|
174
|
-
const pending = approval_1.approvalManager.getPendingActions();
|
|
175
|
-
const info = await executor.getSystemInfo();
|
|
176
|
-
const hasSession = chatSessions.has("telegram", String(ctx.chat.id));
|
|
177
|
-
await ctx.reply(`🖥️ *System Status*\n\n` +
|
|
178
|
-
`• Host: ${info.hostname || "unknown"}\n` +
|
|
179
|
-
`• Platform: ${info.platform}\n` +
|
|
180
|
-
`• Agent: ${agentProvider.name} v${agentProvider.version}\n` +
|
|
181
|
-
`• Pending approvals: ${pending.length}\n` +
|
|
182
|
-
`• Working dir: \`${executor.getWorkingDir()}\`\n` +
|
|
183
|
-
`• Session active: ${hasSession ? "Yes ✅" : "No"}`, { parse_mode: "Markdown" });
|
|
184
|
-
});
|
|
185
|
-
// Reset session - start fresh conversation
|
|
186
|
-
bot.command("reset", async (ctx) => {
|
|
187
|
-
const chatId = ctx.chat.id;
|
|
188
|
-
const hadSession = chatSessions.has("telegram", String(chatId));
|
|
189
|
-
chatSessions.delete("telegram", String(chatId));
|
|
190
|
-
log.info("Session reset", { chatId, hadSession });
|
|
191
|
-
await ctx.reply(hadSession
|
|
192
|
-
? "🔄 Session cleared! Starting fresh conversation."
|
|
193
|
-
: "ℹ️ No active session to clear.");
|
|
194
|
-
});
|
|
195
|
-
// Approval callbacks
|
|
196
|
-
bot.callbackQuery(/^approve:(.+)$/, async (ctx) => {
|
|
197
|
-
const actionId = ctx.match[1];
|
|
198
|
-
const success = approval_1.approvalManager.approve(actionId);
|
|
199
|
-
await ctx.answerCallbackQuery({ text: success ? "Approved!" : "Action not found" });
|
|
200
|
-
if (success) {
|
|
201
|
-
await ctx.editMessageText("✅ *Approved* - executing...", { parse_mode: "Markdown" });
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
bot.callbackQuery(/^reject:(.+)$/, async (ctx) => {
|
|
205
|
-
const actionId = ctx.match[1];
|
|
206
|
-
approval_1.approvalManager.reject(actionId);
|
|
207
|
-
await ctx.answerCallbackQuery({ text: "Rejected" });
|
|
208
|
-
await ctx.editMessageText("❌ *Rejected*", { parse_mode: "Markdown" });
|
|
209
|
-
});
|
|
210
|
-
// Helper to send screenshots
|
|
211
|
-
async function sendScreenshots(chatId, since) {
|
|
212
|
-
try {
|
|
213
|
-
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
214
|
-
const path = await Promise.resolve().then(() => __importStar(require("path")));
|
|
215
|
-
await fs.mkdir(SCREENSHOT_DIR, { recursive: true }).catch(() => { });
|
|
216
|
-
const files = await fs.readdir(SCREENSHOT_DIR).catch(() => []);
|
|
217
|
-
let sent = 0;
|
|
218
|
-
for (const file of files) {
|
|
219
|
-
if (!file.endsWith(".png"))
|
|
220
|
-
continue;
|
|
221
|
-
const filepath = path.join(SCREENSHOT_DIR, file);
|
|
222
|
-
const stats = await fs.stat(filepath).catch(() => null);
|
|
223
|
-
if (stats && stats.mtime >= since) {
|
|
224
|
-
log.info("Sending screenshot", { filepath });
|
|
225
|
-
await bot.api.sendPhoto(chatId, new grammy_1.InputFile(filepath), {
|
|
226
|
-
caption: "📸 Screenshot",
|
|
227
|
-
});
|
|
228
|
-
// Clean up after sending
|
|
229
|
-
await fs.unlink(filepath).catch(() => { });
|
|
230
|
-
sent++;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return sent;
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
log.error("Failed to send screenshots", { error: error.message });
|
|
237
|
-
return 0;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// Main message handler - uses abstract agent provider
|
|
241
|
-
bot.on("message:text", async (ctx) => {
|
|
242
|
-
const userMessage = ctx.message.text;
|
|
243
|
-
const chatId = ctx.chat.id;
|
|
244
|
-
const thinkingMsg = await ctx.reply("🤔 Thinking...");
|
|
245
|
-
// Record time before execution for screenshot detection
|
|
246
|
-
const executionStartTime = new Date();
|
|
247
|
-
// Get existing session for this chat (if any)
|
|
248
|
-
const existingSessionId = chatSessions.get("telegram", String(chatId));
|
|
249
|
-
log.info("Received message", {
|
|
250
|
-
userId: ctx.from?.id,
|
|
251
|
-
chatId,
|
|
252
|
-
hasSession: !!existingSessionId,
|
|
253
|
-
message: userMessage.slice(0, 100),
|
|
254
|
-
});
|
|
255
|
-
try {
|
|
256
|
-
let result = "";
|
|
257
|
-
let lastUpdate = Date.now();
|
|
258
|
-
let newSessionId;
|
|
259
|
-
// Use the abstract agent provider
|
|
260
|
-
for await (const event of agentProvider.queryStream(userMessage, {
|
|
261
|
-
systemPrompt: SYSTEM_PROMPT,
|
|
262
|
-
workingDir,
|
|
263
|
-
sessionId: existingSessionId,
|
|
264
|
-
maxTurns: 10,
|
|
265
|
-
})) {
|
|
266
|
-
log.debug("Agent event", { type: event.type });
|
|
267
|
-
switch (event.type) {
|
|
268
|
-
case "text":
|
|
269
|
-
if (event.text) {
|
|
270
|
-
result = event.text;
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
|
-
case "tool_use":
|
|
274
|
-
log.debug("Tool use", { tool: event.toolName });
|
|
275
|
-
break;
|
|
276
|
-
case "done":
|
|
277
|
-
if (event.response) {
|
|
278
|
-
result = event.response.text;
|
|
279
|
-
newSessionId = event.response.sessionId;
|
|
280
|
-
}
|
|
281
|
-
break;
|
|
282
|
-
case "error":
|
|
283
|
-
throw new Error(event.error || "Unknown agent error");
|
|
284
|
-
}
|
|
285
|
-
// Update thinking message periodically to show progress
|
|
286
|
-
if (Date.now() - lastUpdate > 3000) {
|
|
287
|
-
try {
|
|
288
|
-
await ctx.api.editMessageText(chatId, thinkingMsg.message_id, "⏳ Working...");
|
|
289
|
-
lastUpdate = Date.now();
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// Ignore edit errors
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// Store the session ID for future messages
|
|
297
|
-
if (newSessionId) {
|
|
298
|
-
chatSessions.set("telegram", String(chatId), newSessionId);
|
|
299
|
-
log.info("Session stored", { chatId, sessionId: newSessionId });
|
|
300
|
-
}
|
|
301
|
-
// Send final result
|
|
302
|
-
const finalMessage = result || "Task completed (no output)";
|
|
303
|
-
const truncated = finalMessage.slice(0, 4000); // Telegram limit
|
|
304
|
-
log.info("Agent completed", { resultLength: finalMessage.length, hasSession: !!newSessionId });
|
|
305
|
-
await ctx.api.editMessageText(chatId, thinkingMsg.message_id, truncated, {
|
|
306
|
-
parse_mode: "Markdown",
|
|
307
|
-
}).catch(async () => {
|
|
308
|
-
// If Markdown fails, try without
|
|
309
|
-
await ctx.api.editMessageText(chatId, thinkingMsg.message_id, truncated);
|
|
310
|
-
});
|
|
311
|
-
// Check for and send any screenshots taken during execution
|
|
312
|
-
const screenshotsSent = await sendScreenshots(chatId, executionStartTime);
|
|
313
|
-
if (screenshotsSent > 0) {
|
|
314
|
-
log.info("Screenshots sent", { count: screenshotsSent });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
log.error("Agent error", { error: error.message });
|
|
319
|
-
// If session error, clear the session and retry might help
|
|
320
|
-
if (error.message?.includes("session")) {
|
|
321
|
-
chatSessions.delete("telegram", String(chatId));
|
|
322
|
-
log.warn("Cleared invalid session", { chatId });
|
|
323
|
-
}
|
|
324
|
-
await ctx.api.editMessageText(chatId, thinkingMsg.message_id, `❌ Error: ${error.message}`);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
// Start polling
|
|
328
|
-
console.log("🤖 Starting Telegram bot...");
|
|
329
|
-
bot.start({
|
|
330
|
-
drop_pending_updates: true, // Don't process old updates on restart
|
|
331
|
-
onStart: (info) => console.log(`✅ Telegram bot @${info.username} running (${agentProvider.name})`),
|
|
332
|
-
});
|
|
333
|
-
}
|