@yeego/yeego-openclaw 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.
- package/LICENSE +21 -0
- package/QUICKSTART.md +82 -0
- package/README.md +271 -0
- package/index.ts +334 -0
- package/openclaw.plugin.json +31 -0
- package/package.json +45 -0
- package/poller.ts +516 -0
- package/src/channel.ts +500 -0
- package/src/sessionMapping.ts +80 -0
- package/src/tools/index.ts +25 -0
- package/src/tools/pocketbase.ts +56 -0
- package/src/tools/profileTools.ts +214 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "yeego-openclaw",
|
|
3
|
+
"channels": ["yeego"],
|
|
4
|
+
"configSchema": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"token": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Yeego authentication token"
|
|
11
|
+
},
|
|
12
|
+
"profileId": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Yeego profile ID"
|
|
15
|
+
},
|
|
16
|
+
"baseUrl": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Yeego base URL"
|
|
19
|
+
},
|
|
20
|
+
"sidecarUrl": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Yeego sidecar URL"
|
|
23
|
+
},
|
|
24
|
+
"connectUrl": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Yeego connect endpoint URL"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": ["token", "profileId", "baseUrl", "sidecarUrl", "connectUrl"]
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yeego/yeego-openclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Yeego plugin for OpenClaw - Connect your AI personas",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"yeego-poller": "./poller.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"setup": "bun run poller.ts setup",
|
|
12
|
+
"start": "bun run poller.ts start",
|
|
13
|
+
"build": "bun build poller.ts --compile --outfile yeego-poller"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"yeego",
|
|
17
|
+
"openclaw",
|
|
18
|
+
"ai",
|
|
19
|
+
"chatbot",
|
|
20
|
+
"plugin"
|
|
21
|
+
],
|
|
22
|
+
"author": "Yeego Team",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"homepage": "https://yeego.app",
|
|
25
|
+
"openclaw": {
|
|
26
|
+
"extensions": ["./index.ts"]
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.ts",
|
|
30
|
+
"poller.ts",
|
|
31
|
+
"src/",
|
|
32
|
+
"openclaw.plugin.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"QUICKSTART.md"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@sinclair/typebox": "^0.34.18",
|
|
38
|
+
"eventsource": "^4.1.0",
|
|
39
|
+
"pocketbase": "^0.26.8",
|
|
40
|
+
"tsx": "^4.21.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/poller.ts
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Yeego OpenClaw Plugin - Message Poller
|
|
4
|
+
*
|
|
5
|
+
* Polls PocketBase for new user messages and processes them through OpenClaw agent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import PocketBase from 'pocketbase';
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
13
|
+
import { setSessionConversation } from './src/sessionMapping.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
interface YeegoConfig {
|
|
20
|
+
token: string;
|
|
21
|
+
profile_id: string;
|
|
22
|
+
base_url: string;
|
|
23
|
+
sidecar_url?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PocketBaseMessage {
|
|
27
|
+
id: string;
|
|
28
|
+
conversation: string;
|
|
29
|
+
message: string;
|
|
30
|
+
by: 'user' | 'persona';
|
|
31
|
+
is_ai_generated: boolean;
|
|
32
|
+
has_finished_generating?: boolean;
|
|
33
|
+
created: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PocketBaseConversation {
|
|
37
|
+
id: string;
|
|
38
|
+
profile: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface OpenClawResponse {
|
|
42
|
+
result?: {
|
|
43
|
+
payloads?: Array<{ text?: string }>;
|
|
44
|
+
};
|
|
45
|
+
text?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Constants
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
const CONFIG_DIR = join(homedir(), '.yeego');
|
|
53
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
54
|
+
const PROCESSED_FILE = join(CONFIG_DIR, 'processed.json');
|
|
55
|
+
const SESSION_METADATA_DELAY_MS = 1000;
|
|
56
|
+
const POLL_INTERVAL_MS = 5000; // Poll every 5 seconds (avoid rate limiting)
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Main Poller Class
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
class YeegoPoller {
|
|
63
|
+
private readonly config: YeegoConfig;
|
|
64
|
+
private readonly pb: PocketBase;
|
|
65
|
+
private readonly processedMessages: Set<string>;
|
|
66
|
+
private isRunning = false;
|
|
67
|
+
private pollInterval?: NodeJS.Timeout;
|
|
68
|
+
|
|
69
|
+
constructor(config: YeegoConfig) {
|
|
70
|
+
this.config = config;
|
|
71
|
+
this.pb = new PocketBase(config.base_url);
|
|
72
|
+
this.pb.autoCancellation(false); // Disable auto-cancellation to allow updates during realtime subscription
|
|
73
|
+
this.pb.authStore.save(config.token);
|
|
74
|
+
this.processedMessages = this.loadProcessedMessages();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --------------------------------------------------------------------------
|
|
78
|
+
// Persistence
|
|
79
|
+
// --------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
private loadProcessedMessages(): Set<string> {
|
|
82
|
+
try {
|
|
83
|
+
if (existsSync(PROCESSED_FILE)) {
|
|
84
|
+
const data = JSON.parse(readFileSync(PROCESSED_FILE, 'utf-8'));
|
|
85
|
+
return new Set(data);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('[Yeego] Failed to load processed messages:', error);
|
|
89
|
+
}
|
|
90
|
+
return new Set();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private saveProcessedMessages(): void {
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(
|
|
96
|
+
PROCESSED_FILE,
|
|
97
|
+
JSON.stringify(Array.from(this.processedMessages)),
|
|
98
|
+
'utf-8'
|
|
99
|
+
);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('[Yeego] Failed to save processed messages:', error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --------------------------------------------------------------------------
|
|
106
|
+
// Connection
|
|
107
|
+
// --------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
async connect(): Promise<void> {
|
|
110
|
+
try {
|
|
111
|
+
console.log('[Yeego] Connecting to Yeego API...');
|
|
112
|
+
|
|
113
|
+
const sidecarUrl = this.config.sidecar_url || this.config.base_url;
|
|
114
|
+
await fetch(`${sidecarUrl}/public/openclaw/connect`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
profile_id: this.config.profile_id,
|
|
118
|
+
}),
|
|
119
|
+
headers: {
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
'Authorization': `Bearer ${this.config.token}`,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
console.log('[Yeego] Connected successfully');
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('[Yeego] Connection failed:', error);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --------------------------------------------------------------------------
|
|
133
|
+
// Message Processing
|
|
134
|
+
// --------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
async checkAndProcessLastMessage(conversationId: string): Promise<void> {
|
|
137
|
+
try {
|
|
138
|
+
const conversation = await this.pb
|
|
139
|
+
.collection('profile_chat_conversations')
|
|
140
|
+
.getOne<PocketBaseConversation>(conversationId);
|
|
141
|
+
|
|
142
|
+
if (conversation.profile !== this.config.profile_id) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const lastMessages = await this.pb
|
|
147
|
+
.collection('profile_chat_messages')
|
|
148
|
+
.getList<PocketBaseMessage>(1, 1, {
|
|
149
|
+
filter: `conversation="${conversationId}"`,
|
|
150
|
+
sort: '-created',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (lastMessages.items.length === 0) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const lastMessage = lastMessages.items[0];
|
|
158
|
+
|
|
159
|
+
if (lastMessage.by === 'user' && !lastMessage.is_ai_generated) {
|
|
160
|
+
if (!this.processedMessages.has(lastMessage.id)) {
|
|
161
|
+
await this.processMessage(lastMessage, conversation);
|
|
162
|
+
this.processedMessages.add(lastMessage.id);
|
|
163
|
+
this.saveProcessedMessages();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('[Yeego] Error checking last message:', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async processMessage(
|
|
172
|
+
message: PocketBaseMessage,
|
|
173
|
+
conversation: PocketBaseConversation
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
let placeholderId: string | null = null;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
console.log(`[Yeego] Processing message: ${message.id}`);
|
|
179
|
+
|
|
180
|
+
// Create placeholder message to show loading state
|
|
181
|
+
const placeholder = await this.pb.collection('profile_chat_messages').create({
|
|
182
|
+
conversation: conversation.id,
|
|
183
|
+
message: '',
|
|
184
|
+
by: 'persona',
|
|
185
|
+
is_ai_generated: true,
|
|
186
|
+
has_finished_generating: false,
|
|
187
|
+
source: 'openclaw',
|
|
188
|
+
});
|
|
189
|
+
placeholderId = placeholder.id;
|
|
190
|
+
console.log(`[Yeego] Created placeholder: ${placeholderId}`);
|
|
191
|
+
|
|
192
|
+
this.saveSessionMapping(conversation.id);
|
|
193
|
+
|
|
194
|
+
const aiText = await this.callOpenClawAgent(message, conversation);
|
|
195
|
+
console.log(`[Yeego] Got AI response, updating placeholder...`);
|
|
196
|
+
|
|
197
|
+
// Update placeholder with AI response
|
|
198
|
+
await this.pb.collection('profile_chat_messages').update(placeholderId, {
|
|
199
|
+
conversation: conversation.id,
|
|
200
|
+
message: aiText,
|
|
201
|
+
has_finished_generating: true,
|
|
202
|
+
});
|
|
203
|
+
console.log(`[Yeego] Update completed!`);
|
|
204
|
+
|
|
205
|
+
// Workaround: Set messageChannel in session file after agent creates it
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
this.setSessionMessageChannel(conversation.id, 'yeego');
|
|
208
|
+
}, SESSION_METADATA_DELAY_MS);
|
|
209
|
+
|
|
210
|
+
console.log(`[Yeego] ✓ Processed message ${message.id}`);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(`[Yeego] Error processing message:`, error);
|
|
213
|
+
|
|
214
|
+
// Clean up placeholder on error
|
|
215
|
+
if (placeholderId) {
|
|
216
|
+
try {
|
|
217
|
+
await this.pb.collection('profile_chat_messages').delete(placeholderId);
|
|
218
|
+
} catch (deleteError) {
|
|
219
|
+
console.error(`[Yeego] Failed to delete placeholder:`, deleteError);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private saveSessionMapping(conversationId: string): void {
|
|
226
|
+
console.log(`[Yeego] Saving session mappings for conversation: ${conversationId}`);
|
|
227
|
+
setSessionConversation(conversationId, conversationId);
|
|
228
|
+
setSessionConversation(`agent:main:${conversationId}`, conversationId);
|
|
229
|
+
setSessionConversation(`yeego:${conversationId}`, conversationId);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private async callOpenClawAgent(
|
|
233
|
+
message: PocketBaseMessage,
|
|
234
|
+
conversation: PocketBaseConversation
|
|
235
|
+
): Promise<string> {
|
|
236
|
+
const proc = spawn('openclaw', [
|
|
237
|
+
'agent',
|
|
238
|
+
'--session-id',
|
|
239
|
+
conversation.id,
|
|
240
|
+
'--reply-channel',
|
|
241
|
+
'yeego',
|
|
242
|
+
'--reply-to',
|
|
243
|
+
conversation.id,
|
|
244
|
+
'--message',
|
|
245
|
+
message.message,
|
|
246
|
+
'--json',
|
|
247
|
+
], {
|
|
248
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const { stdout, stderr, exitCode } = await this.captureProcessOutput(proc);
|
|
252
|
+
|
|
253
|
+
if (exitCode !== 0) {
|
|
254
|
+
console.error(`[Yeego] OpenClaw error:`, stderr);
|
|
255
|
+
return 'Error processing message';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return this.parseOpenClawResponse(stdout);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private captureProcessOutput(proc: ChildProcess): Promise<{
|
|
262
|
+
stdout: string;
|
|
263
|
+
stderr: string;
|
|
264
|
+
exitCode: number;
|
|
265
|
+
}> {
|
|
266
|
+
return new Promise((resolve) => {
|
|
267
|
+
let stdout = '';
|
|
268
|
+
let stderr = '';
|
|
269
|
+
|
|
270
|
+
proc.stdout?.on('data', (data) => {
|
|
271
|
+
stdout += data.toString();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
proc.stderr?.on('data', (data) => {
|
|
275
|
+
stderr += data.toString();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
proc.on('close', (code) => {
|
|
279
|
+
resolve({ stdout, stderr, exitCode: code || 0 });
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private parseOpenClawResponse(output: string): string {
|
|
285
|
+
try {
|
|
286
|
+
const jsonMatch = output.match(/\{[\s\S]*\}/);
|
|
287
|
+
if (jsonMatch) {
|
|
288
|
+
const result: any = JSON.parse(jsonMatch[0]);
|
|
289
|
+
|
|
290
|
+
// Try to find payloads in different locations
|
|
291
|
+
let payloads = result.result?.payloads || result.payloads;
|
|
292
|
+
|
|
293
|
+
if (payloads && payloads.length > 0) {
|
|
294
|
+
const texts = payloads
|
|
295
|
+
.map((p: any) => p.text)
|
|
296
|
+
.filter((t: string) => t && t.trim())
|
|
297
|
+
.join('\n\n');
|
|
298
|
+
return texts || 'No response';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return result.text || 'No response';
|
|
302
|
+
}
|
|
303
|
+
return output.trim() || 'No response';
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(`[Yeego] Failed to parse OpenClaw response:`, error);
|
|
306
|
+
return output.trim() || 'No response';
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private async createAIResponse(conversationId: string, text: string): Promise<void> {
|
|
311
|
+
await this.pb.collection('profile_chat_messages').create({
|
|
312
|
+
conversation: conversationId,
|
|
313
|
+
message: text,
|
|
314
|
+
by: 'persona',
|
|
315
|
+
is_ai_generated: true,
|
|
316
|
+
has_finished_generating: true,
|
|
317
|
+
source: 'openclaw',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// --------------------------------------------------------------------------
|
|
322
|
+
// Session Metadata Workaround
|
|
323
|
+
// --------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Sets messageChannel in OpenClaw session metadata file.
|
|
327
|
+
* This is a workaround because --reply-channel CLI flag doesn't set the session's messageChannel property.
|
|
328
|
+
*/
|
|
329
|
+
private setSessionMessageChannel(sessionId: string, channel: string): boolean {
|
|
330
|
+
try {
|
|
331
|
+
const sessionDir = join(homedir(), '.openclaw', 'agents', 'main', 'sessions');
|
|
332
|
+
const sessionFile = join(sessionDir, `${sessionId}.jsonl`);
|
|
333
|
+
|
|
334
|
+
if (!existsSync(sessionFile)) {
|
|
335
|
+
console.log(`[Yeego] Session file not found yet: ${sessionFile}`);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const lines = readFileSync(sessionFile, 'utf-8')
|
|
340
|
+
.split('\n')
|
|
341
|
+
.filter(l => l.trim());
|
|
342
|
+
|
|
343
|
+
if (lines.length === 0) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const firstLine = JSON.parse(lines[0]);
|
|
348
|
+
if (firstLine.type === 'session') {
|
|
349
|
+
firstLine.messageChannel = channel;
|
|
350
|
+
lines[0] = JSON.stringify(firstLine);
|
|
351
|
+
writeFileSync(sessionFile, lines.join('\n') + '\n', 'utf-8');
|
|
352
|
+
console.log(`[Yeego] ✓ Set messageChannel="${channel}" for session ${sessionId}`);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return false;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(`[Yeego] Failed to set messageChannel:`, error);
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// --------------------------------------------------------------------------
|
|
364
|
+
// Polling
|
|
365
|
+
// --------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
private async pollForNewMessages(): Promise<void> {
|
|
368
|
+
try {
|
|
369
|
+
// Get recent messages from the last 10 seconds
|
|
370
|
+
const tenSecondsAgo = new Date(Date.now() - 10000).toISOString().replace('T', ' ').substring(0, 19);
|
|
371
|
+
const messages = await this.pb.collection('profile_chat_messages').getList<PocketBaseMessage>(1, 50, {
|
|
372
|
+
filter: `created >= "${tenSecondsAgo}" && by = "user" && is_ai_generated = false`,
|
|
373
|
+
sort: '-created',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
for (const message of messages.items) {
|
|
377
|
+
if (!this.processedMessages.has(message.id) && message.conversation) {
|
|
378
|
+
console.log(`[Yeego] New message detected: ${message.id}`);
|
|
379
|
+
await this.checkAndProcessLastMessage(message.conversation);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('[Yeego] Error polling messages:', error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// --------------------------------------------------------------------------
|
|
388
|
+
// Lifecycle
|
|
389
|
+
// --------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
async start(): Promise<void> {
|
|
392
|
+
if (this.isRunning) {
|
|
393
|
+
console.log('[Yeego] Already running');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.isRunning = true;
|
|
398
|
+
console.log('[Yeego] Starting poller...');
|
|
399
|
+
|
|
400
|
+
await this.connect();
|
|
401
|
+
|
|
402
|
+
console.log('[Yeego] Starting to poll for new messages...');
|
|
403
|
+
|
|
404
|
+
// Poll for new messages at regular intervals
|
|
405
|
+
this.pollInterval = setInterval(() => {
|
|
406
|
+
if (this.isRunning) {
|
|
407
|
+
this.pollForNewMessages();
|
|
408
|
+
}
|
|
409
|
+
}, POLL_INTERVAL_MS);
|
|
410
|
+
|
|
411
|
+
console.log('[Yeego] Polling for messages...');
|
|
412
|
+
|
|
413
|
+
return new Promise<void>((resolve) => {
|
|
414
|
+
const checkInterval = setInterval(() => {
|
|
415
|
+
if (!this.isRunning) {
|
|
416
|
+
clearInterval(checkInterval);
|
|
417
|
+
resolve();
|
|
418
|
+
}
|
|
419
|
+
}, 1000);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
stop(): void {
|
|
424
|
+
this.isRunning = false;
|
|
425
|
+
if (this.pollInterval) {
|
|
426
|
+
clearInterval(this.pollInterval);
|
|
427
|
+
this.pollInterval = undefined;
|
|
428
|
+
}
|
|
429
|
+
console.log('[Yeego] Stopped');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// CLI Commands
|
|
435
|
+
// ============================================================================
|
|
436
|
+
|
|
437
|
+
async function setup(configText: string): Promise<void> {
|
|
438
|
+
try {
|
|
439
|
+
console.log('[Yeego] Setting up configuration...');
|
|
440
|
+
|
|
441
|
+
const config: Partial<YeegoConfig> = {};
|
|
442
|
+
configText.split('\n').forEach(line => {
|
|
443
|
+
line = line.trim();
|
|
444
|
+
if (line && !line.startsWith('#')) {
|
|
445
|
+
const match = line.match(/^(\w+)=(.+)$/);
|
|
446
|
+
if (match) {
|
|
447
|
+
const [, key, value] = match;
|
|
448
|
+
if (key === 'YEEGO_TOKEN') config.token = value;
|
|
449
|
+
else if (key === 'YEEGO_PROFILE_ID') config.profile_id = value;
|
|
450
|
+
else if (key === 'YEEGO_BASE_URL') config.base_url = value;
|
|
451
|
+
else if (key === 'YEEGO_SIDECAR_URL') config.sidecar_url = value;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (!config.token || !config.profile_id || !config.base_url) {
|
|
457
|
+
throw new Error('Missing required configuration: token, profile_id, or base_url');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
461
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
465
|
+
console.log('[Yeego] Configuration saved to', CONFIG_FILE);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error('[Yeego] Setup failed:', error);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function start(): Promise<void> {
|
|
473
|
+
try {
|
|
474
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
475
|
+
console.error('[Yeego] Configuration not found. Run setup first.');
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const config: YeegoConfig = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
480
|
+
const poller = new YeegoPoller(config);
|
|
481
|
+
|
|
482
|
+
const shutdown = () => {
|
|
483
|
+
console.log('\n[Yeego] Shutting down...');
|
|
484
|
+
poller.stop();
|
|
485
|
+
process.exit(0);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
process.on('SIGINT', shutdown);
|
|
489
|
+
process.on('SIGTERM', shutdown);
|
|
490
|
+
|
|
491
|
+
await poller.start();
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error('[Yeego] Fatal error:', error);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// Main Entry Point
|
|
500
|
+
// ============================================================================
|
|
501
|
+
|
|
502
|
+
const command = process.argv[2];
|
|
503
|
+
const arg = process.argv[3];
|
|
504
|
+
|
|
505
|
+
if (command === 'setup') {
|
|
506
|
+
if (!arg) {
|
|
507
|
+
console.error('Usage: poller.ts setup <config-text>');
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
setup(arg);
|
|
511
|
+
} else if (command === 'start') {
|
|
512
|
+
start();
|
|
513
|
+
} else {
|
|
514
|
+
console.error('Usage: poller.ts {setup|start}');
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|