@ebowwa/daemons 0.5.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 (42) hide show
  1. package/README.md +264 -0
  2. package/dist/bin/discord-cli.js +124118 -0
  3. package/dist/bin/manager.js +143 -0
  4. package/dist/bin/telegram-cli.js +124114 -0
  5. package/dist/index.js +125340 -0
  6. package/package.json +94 -0
  7. package/src/agent.ts +111 -0
  8. package/src/channels/base.ts +573 -0
  9. package/src/channels/discord.ts +306 -0
  10. package/src/channels/index.ts +169 -0
  11. package/src/channels/telegram.ts +315 -0
  12. package/src/daemon.ts +534 -0
  13. package/src/hooks.ts +97 -0
  14. package/src/index.ts +111 -0
  15. package/src/memory.ts +369 -0
  16. package/src/skills/coding/commit.ts +202 -0
  17. package/src/skills/coding/execute-subtask.ts +136 -0
  18. package/src/skills/coding/fix-issues.ts +126 -0
  19. package/src/skills/coding/index.ts +26 -0
  20. package/src/skills/coding/plan-task.ts +158 -0
  21. package/src/skills/coding/quality-check.ts +155 -0
  22. package/src/skills/index.ts +65 -0
  23. package/src/skills/registry.ts +380 -0
  24. package/src/skills/shared/index.ts +21 -0
  25. package/src/skills/shared/reflect.ts +156 -0
  26. package/src/skills/shared/review.ts +201 -0
  27. package/src/skills/shared/trajectory.ts +319 -0
  28. package/src/skills/trading/analyze-market.ts +144 -0
  29. package/src/skills/trading/check-risk.ts +176 -0
  30. package/src/skills/trading/execute-trade.ts +185 -0
  31. package/src/skills/trading/generate-signal.ts +160 -0
  32. package/src/skills/trading/index.ts +26 -0
  33. package/src/skills/trading/monitor-position.ts +179 -0
  34. package/src/skills/types.ts +235 -0
  35. package/src/skills/workflows.ts +340 -0
  36. package/src/state.ts +77 -0
  37. package/src/tools.ts +134 -0
  38. package/src/types.ts +314 -0
  39. package/src/workflow.ts +341 -0
  40. package/src/workflows/coding.ts +580 -0
  41. package/src/workflows/index.ts +61 -0
  42. package/src/workflows/trading.ts +608 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Telegram Channel for GLM Daemon
3
+ *
4
+ * Wraps @ebowwa/channel-telegram (pure protocol adapter)
5
+ * with GLM Daemon intelligence (GLM client, daemon delegation, routing).
6
+ */
7
+
8
+ import {
9
+ type ChannelId,
10
+ type ChannelCapabilities,
11
+ } from "@ebowwa/channel-types";
12
+ import {
13
+ TelegramChannel as TelegramProtocolAdapter,
14
+ type TelegramConfig as ProtocolConfig,
15
+ // Telegram target normalization utilities
16
+ normalizeTelegramMessagingTarget,
17
+ looksLikeTelegramTargetId,
18
+ extractRawTarget,
19
+ isUsernameTarget,
20
+ isNumericIdTarget,
21
+ } from "@ebowwa/channel-telegram";
22
+ import {
23
+ BaseChannel,
24
+ type GLMChannelConfig,
25
+ } from "./base.js";
26
+
27
+ // Re-export normalization utilities for consumers
28
+ export {
29
+ normalizeTelegramMessagingTarget,
30
+ looksLikeTelegramTargetId,
31
+ extractRawTarget,
32
+ isUsernameTarget,
33
+ isNumericIdTarget,
34
+ };
35
+
36
+ // ============================================================
37
+ // TELEGRAM CHANNEL CONFIG
38
+ // ============================================================
39
+
40
+ export interface TelegramChannelConfig extends GLMChannelConfig {
41
+ platform: "telegram";
42
+ /** Bot token from @BotFather */
43
+ botToken: string;
44
+ /** Test chat ID for startup notification */
45
+ testChatId?: string;
46
+ /** Allowed user IDs (empty = all) */
47
+ allowedUsers?: number[];
48
+ /** Allowed chat IDs (empty = all) */
49
+ allowedChats?: number[];
50
+ }
51
+
52
+ // Legacy type alias for backwards compat
53
+ export type { TelegramChannelConfig as TelegramConfig };
54
+
55
+ // ============================================================
56
+ // TELEGRAM CHANNEL (Wraps @ebowwa/channel-telegram)
57
+ // ============================================================
58
+
59
+ export class GLMTelegramChannel extends BaseChannel {
60
+ readonly id: ChannelId;
61
+ readonly label = "Telegram";
62
+ readonly capabilities: ChannelCapabilities = {
63
+ supports: {
64
+ text: true,
65
+ media: true,
66
+ replies: true,
67
+ threads: false,
68
+ reactions: true,
69
+ editing: true,
70
+ streaming: false,
71
+ },
72
+ media: {
73
+ maxFileSize: 50 * 1024 * 1024, // 50MB
74
+ supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
75
+ },
76
+ rateLimits: {
77
+ messagesPerMinute: 30,
78
+ charactersPerMessage: 4096,
79
+ },
80
+ };
81
+
82
+ private protocolAdapter: TelegramProtocolAdapter;
83
+ private telegramConfig: {
84
+ botToken: string;
85
+ testChatId?: string;
86
+ allowedUsers?: number[];
87
+ allowedChats?: number[];
88
+ };
89
+
90
+ constructor(config: TelegramChannelConfig) {
91
+ super(config);
92
+
93
+ this.telegramConfig = {
94
+ botToken: config.botToken,
95
+ testChatId: config.testChatId,
96
+ allowedUsers: config.allowedUsers,
97
+ allowedChats: config.allowedChats,
98
+ };
99
+
100
+ this.id = this.createId();
101
+ this.protocolAdapter = new TelegramProtocolAdapter(this.telegramConfig);
102
+ }
103
+
104
+ // ============================================================
105
+ // ChannelConnector Implementation
106
+ // ============================================================
107
+
108
+ /**
109
+ * Send response to Telegram via protocol adapter
110
+ */
111
+ async send(response: Parameters<TelegramProtocolAdapter['send']>[0]): Promise<void> {
112
+ await this.protocolAdapter.send(response);
113
+ }
114
+
115
+ // ============================================================
116
+ // Platform-Specific Implementation (BaseChannel abstract methods)
117
+ // ============================================================
118
+
119
+ /**
120
+ * Start Telegram bot via protocol adapter
121
+ */
122
+ protected async startPlatform(): Promise<void> {
123
+ console.log("[TelegramChannel] Starting via protocol adapter...");
124
+
125
+ // Connect protocol adapter to our routing logic
126
+ this.protocolAdapter.onMessage(async (message) => {
127
+ // Route through BaseChannel's routing logic (GLM + daemon)
128
+ return this.routeChannelMessage(message);
129
+ });
130
+
131
+ await this.protocolAdapter.start();
132
+ console.log("[TelegramChannel] Protocol adapter started");
133
+ }
134
+
135
+ /**
136
+ * Stop Telegram bot via protocol adapter
137
+ */
138
+ protected async stopPlatform(): Promise<void> {
139
+ console.log("[TelegramChannel] Stopping protocol adapter...");
140
+ await this.protocolAdapter.stop();
141
+ }
142
+
143
+ // ============================================================
144
+ // Telegram-Specific Helpers (delegated to protocol adapter)
145
+ // ============================================================
146
+
147
+ /**
148
+ * Get underlying bot instance for advanced operations
149
+ */
150
+ getBot() {
151
+ return this.protocolAdapter.getBot();
152
+ }
153
+
154
+ /**
155
+ * Send a simple message
156
+ */
157
+ async sendMessage(chatId: number, text: string) {
158
+ await this.protocolAdapter.sendMessage(chatId, text);
159
+ }
160
+
161
+ /**
162
+ * Start typing indicator
163
+ */
164
+ startTypingIndicator(chatId: number) {
165
+ this.protocolAdapter.startTypingIndicator(chatId);
166
+ }
167
+
168
+ /**
169
+ * Stop typing indicator
170
+ */
171
+ stopTypingIndicator(chatId: number) {
172
+ this.protocolAdapter.stopTypingIndicator(chatId);
173
+ }
174
+
175
+ /**
176
+ * Check if user/chat is allowed
177
+ */
178
+ isAllowed(userId?: number, chatId?: number): boolean {
179
+ return this.protocolAdapter.isAllowed(userId, chatId);
180
+ }
181
+
182
+ // ============================================================
183
+ // Target Normalization Helpers
184
+ // ============================================================
185
+
186
+ /**
187
+ * Normalize a Telegram target (username, URL, or ID) to standard format.
188
+ *
189
+ * @param raw - Raw input (@username, t.me/user, 123456, etc.)
190
+ * @returns Normalized target or undefined if invalid
191
+ *
192
+ * @example
193
+ * channel.normalizeTarget("@durov") // "telegram:@durov"
194
+ * channel.normalizeTarget("t.me/durov") // "telegram:@durov"
195
+ * channel.normalizeTarget("123456") // "telegram:123456"
196
+ */
197
+ normalizeTarget(raw: string): string | undefined {
198
+ return normalizeTelegramMessagingTarget(raw);
199
+ }
200
+
201
+ /**
202
+ * Check if a string looks like a Telegram target.
203
+ *
204
+ * @param raw - Raw input string
205
+ * @returns true if it looks like a Telegram target
206
+ */
207
+ isValidTarget(raw: string): boolean {
208
+ return looksLikeTelegramTargetId(raw);
209
+ }
210
+
211
+ /**
212
+ * Parse a target from user input and extract the usable form.
213
+ *
214
+ * @param input - User input like "send to @user" or "dm t.me/user"
215
+ * @returns Extracted raw target (@username or numeric ID) or undefined
216
+ *
217
+ * @example
218
+ * channel.parseTargetFromInput("send to @durov") // "@durov"
219
+ * channel.parseTargetFromInput("dm 123456") // "123456"
220
+ */
221
+ parseTargetFromInput(input: string): string | undefined {
222
+ const words = input.split(/\s+/);
223
+ for (const word of words) {
224
+ if (looksLikeTelegramTargetId(word)) {
225
+ const normalized = normalizeTelegramMessagingTarget(word);
226
+ if (normalized) {
227
+ return extractRawTarget(normalized);
228
+ }
229
+ }
230
+ }
231
+ return undefined;
232
+ }
233
+
234
+ /**
235
+ * Send a message to a target (username or chat ID).
236
+ *
237
+ * @param target - Telegram target (@username, numeric ID, or normalized format)
238
+ * @param text - Message text
239
+ *
240
+ * @example
241
+ * await channel.sendToTarget("@durov", "Hello!")
242
+ * await channel.sendToTarget("123456", "Hello!")
243
+ */
244
+ async sendToTarget(target: string, text: string): Promise<void> {
245
+ const normalized = normalizeTelegramMessagingTarget(target);
246
+ if (!normalized) {
247
+ throw new Error(`Invalid Telegram target: ${target}`);
248
+ }
249
+
250
+ const rawTarget = extractRawTarget(normalized);
251
+ if (!rawTarget) {
252
+ throw new Error(`Failed to extract target from: ${normalized}`);
253
+ }
254
+
255
+ // Parse chat ID (numeric or resolve username)
256
+ let chatId: number;
257
+
258
+ if (isNumericIdTarget(normalized)) {
259
+ chatId = parseInt(rawTarget, 10);
260
+ } else {
261
+ // Username target - need to resolve via bot API
262
+ // Note: This requires the bot to have seen the user/channel before
263
+ // For now, we'll try direct message with username (may fail if not cached)
264
+ throw new Error(
265
+ `Username targets require resolution. Use numeric chat ID instead, or implement username resolution. Target: ${rawTarget}`
266
+ );
267
+ }
268
+
269
+ await this.sendMessage(chatId, text);
270
+ }
271
+ }
272
+
273
+ // ============================================================
274
+ // FACTORY FUNCTION
275
+ // ============================================================
276
+
277
+ /**
278
+ * Create a Telegram channel from config
279
+ */
280
+ export function createTelegramChannel(config: TelegramChannelConfig): GLMTelegramChannel {
281
+ return new GLMTelegramChannel(config);
282
+ }
283
+
284
+ /**
285
+ * Create Telegram channel config from environment (Doppler)
286
+ */
287
+ export function createTelegramConfigFromEnv(): TelegramChannelConfig | null {
288
+ const token = process.env.TELEGRAM_BOT_TOKEN;
289
+ if (!token) return null;
290
+
291
+ const allowedUsers = process.env.TELEGRAM_ALLOWED_USERS
292
+ ?.split(",")
293
+ .map((s) => parseInt(s.trim(), 10))
294
+ .filter((n) => !isNaN(n));
295
+
296
+ const allowedChats = process.env.TELEGRAM_ALLOWED_CHATS
297
+ ?.split(",")
298
+ .map((s) => parseInt(s.trim(), 10))
299
+ .filter((n) => !isNaN(n));
300
+
301
+ return {
302
+ platform: "telegram",
303
+ accountId: process.env.TELEGRAM_ACCOUNT_ID || "default",
304
+ instanceId: process.env.TELEGRAM_INSTANCE_ID,
305
+ botToken: token,
306
+ testChatId: process.env.TELEGRAM_TEST_CHAT_ID,
307
+ allowedUsers,
308
+ allowedChats,
309
+ daemonWorkdir: process.env.DAEMON_WORKDIR,
310
+ daemonBaseBranch: process.env.DAEMON_BASE_BRANCH,
311
+ enableDaemonAutoPR: process.env.DAEMON_AUTO_PR === "true",
312
+ enableDaemonAutoCommit: process.env.DAEMON_AUTO_COMMIT === "true",
313
+ butlerStorageDir: process.env.BUTLER_STORAGE_DIR,
314
+ };
315
+ }