@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Yeego Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/QUICKSTART.md ADDED
@@ -0,0 +1,82 @@
1
+ # Quick Start Guide
2
+
3
+ ## 🚀 Get Started in 3 Steps
4
+
5
+ ### Step 1: Get Your Configuration
6
+
7
+ 1. Open Yeego mobile app
8
+ 2. Go to your profile → Settings
9
+ 3. Tap "Connect to OpenClaw" 🦞
10
+ 4. Tap "Copy Configuration"
11
+
12
+ You'll get something like:
13
+
14
+ ```
15
+ # Yeego OpenClaw Plugin Configuration
16
+ YEEGO_TOKEN=eyJhbG...
17
+ YEEGO_PROFILE_ID=abc123...
18
+ YEEGO_BASE_URL=http://localhost:8091
19
+ YEEGO_PLUGIN_URL=http://localhost:8092/openclaw-plugin
20
+ ```
21
+
22
+ ### Step 2: Save Configuration
23
+
24
+ ```bash
25
+ cd openclaw-plugin
26
+
27
+ # Paste your config and save
28
+ bun run setup "YEEGO_TOKEN=eyJhbG...
29
+ YEEGO_PROFILE_ID=abc123...
30
+ YEEGO_BASE_URL=http://localhost:8091"
31
+ ```
32
+
33
+ ✅ Configuration saved to `~/.yeego/config.json`
34
+
35
+ ### Step 3: Start Polling
36
+
37
+ ```bash
38
+ bun run start
39
+ ```
40
+
41
+ That's it! The plugin will:
42
+ - ✅ Connect to your Yeego profile
43
+ - ✅ Poll for new messages every 5 seconds
44
+ - ✅ Process them through OpenClaw
45
+ - ✅ Send AI responses back to Yeego
46
+
47
+ ## 📱 Test It
48
+
49
+ 1. Open Yeego app
50
+ 2. Send a message to your AI persona
51
+ 3. Watch the poller process it
52
+ 4. See the AI response appear in the app
53
+
54
+ ## 🔧 Troubleshooting
55
+
56
+ **No messages being processed?**
57
+ - Make sure you sent a message in the Yeego app
58
+ - Check the profile_id matches your actual profile
59
+
60
+ **Connection errors?**
61
+ - Verify backend is running (`http://localhost:8091`)
62
+ - Check your token hasn't expired (30 days validity)
63
+
64
+ **Want to reset?**
65
+ ```bash
66
+ rm -rf ~/.yeego
67
+ # Then run setup again
68
+ ```
69
+
70
+ ## 📦 Production Deployment
71
+
72
+ When ready to deploy:
73
+
74
+ ```bash
75
+ # Build standalone executable
76
+ bun run build
77
+
78
+ # Run it anywhere
79
+ ./yeego-poller start
80
+ ```
81
+
82
+ No dependencies needed - it's a single binary! 🎉
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # Yeego OpenClaw Plugin
2
+
3
+ OpenClaw channel plugin for integrating Yeego with OpenClaw AI agent.
4
+
5
+ ## Overview
6
+
7
+ This plugin enables bidirectional communication between Yeego and OpenClaw:
8
+
9
+ - **Inbound**: Listens to Yeego user messages via PocketBase realtime subscriptions
10
+ - **Outbound**: Sends OpenClaw AI responses back to Yeego conversations
11
+ - **Proactive**: Allows OpenClaw to initiate messages through cron jobs and tools
12
+
13
+ ## Architecture
14
+
15
+ ### Components
16
+
17
+ 1. **Channel Plugin** (`src/channel.ts`)
18
+ - Registers Yeego as an OpenClaw channel
19
+ - Handles outbound message sending via PocketBase
20
+ - Manages poller subprocess lifecycle
21
+ - Implements target resolution for conversation IDs
22
+
23
+ 2. **Realtime Listener** (`poller.ts`)
24
+ - Subscribes to PocketBase message events
25
+ - Processes incoming user messages
26
+ - Calls OpenClaw agent CLI
27
+ - Stores AI responses back to PocketBase
28
+ - Sets messageChannel metadata (workaround for CLI limitation)
29
+
30
+ 3. **Session Mapping** (`src/sessionMapping.ts`)
31
+ - Maintains persistent session ↔ conversation mapping
32
+ - File-based storage for cross-process sharing
33
+ - Enables proactive message targeting
34
+
35
+ 4. **Tools** (`src/tools/`)
36
+ - Profile management tools (create, list, get profiles)
37
+ - Target ID lookup for Yeego conversations
38
+
39
+ ## Key Implementation Details
40
+
41
+ ### Message Flow
42
+
43
+ #### Inbound (User → OpenClaw)
44
+ ```
45
+ User Message (Yeego App)
46
+ ↓
47
+ PocketBase Realtime Event
48
+ ↓
49
+ Poller (poller.ts)
50
+ ↓
51
+ OpenClaw Agent CLI (--reply-channel yeego)
52
+ ↓
53
+ AI Response → PocketBase
54
+ ↓
55
+ Yeego App displays response
56
+ ```
57
+
58
+ #### Outbound (OpenClaw → User)
59
+ ```
60
+ OpenClaw wants to send message
61
+ ↓
62
+ Resolves target via messaging.targetResolver.looksLikeId
63
+ ↓
64
+ Calls outbound.sendText
65
+ ↓
66
+ Creates message in PocketBase
67
+ ↓
68
+ Yeego App displays message
69
+ ```
70
+
71
+ ### Critical Workarounds
72
+
73
+ #### 1. messageChannel Metadata
74
+ **Problem**: OpenClaw CLI's `--reply-channel` flag doesn't set the session's `messageChannel` property.
75
+
76
+ **Solution**: `setSessionMessageChannel()` in `poller.ts` directly modifies the session JSONL file after creation to inject `messageChannel: "yeego"`.
77
+
78
+ ```typescript
79
+ // Wait for session file creation, then patch it
80
+ setTimeout(() => {
81
+ this.setSessionMessageChannel(conversation.id, 'yeego');
82
+ }, 1000);
83
+ ```
84
+
85
+ #### 2. Target Recognition
86
+ **Problem**: OpenClaw's target resolver tries to look up conversation IDs in a directory, causing "Unknown target" errors.
87
+
88
+ **Solution**: Implement `messaging.targetResolver.looksLikeId` to tell OpenClaw that our alphanumeric conversation IDs are valid IDs:
89
+
90
+ ```typescript
91
+ messaging: {
92
+ targetResolver: {
93
+ looksLikeId: (_raw, normalized) => /^[a-z0-9]+$/i.test(normalized),
94
+ },
95
+ }
96
+ ```
97
+
98
+ This bypasses directory lookup entirely.
99
+
100
+ #### 3. Cron Job Delivery
101
+
102
+ **Important**: When using cron jobs or other isolated sessions to send messages to Yeego, you must specify both `channel` and `to` parameters in the payload:
103
+
104
+ ```javascript
105
+ {
106
+ "deliver": true,
107
+ "channel": "yeego",
108
+ "to": "conversation_id_here"
109
+ }
110
+ ```
111
+
112
+ Unlike normal conversations where OpenClaw knows the origin context, cron jobs run in isolated sessions. The `channel` and `to` parameters tell OpenClaw where to deliver the message.
113
+
114
+ **Example cron job configuration**:
115
+ ```json
116
+ {
117
+ "id": "daily_reminder",
118
+ "enabled": true,
119
+ "schedule": "0 9 * * *",
120
+ "payload": {
121
+ "deliver": true,
122
+ "channel": "yeego",
123
+ "to": "lq45hgra9wpado5",
124
+ "message": "Good morning! Don't forget your daily tasks."
125
+ }
126
+ }
127
+ ```
128
+
129
+ Without these parameters, you'll get an error: "Cron delivery requires a recipient (--to)".
130
+
131
+ ## Installation
132
+
133
+ ### Quick Start (Recommended)
134
+
135
+ 1. Install the plugin:
136
+ ```bash
137
+ openclaw plugins install @yeego/openclaw-plugin
138
+ ```
139
+
140
+ 2. Get your configuration from Yeego app:
141
+ - Open Yeego mobile app
142
+ - Go to Profile → Settings
143
+ - Tap "Connect to OpenClaw" 🦞
144
+ - Copy the configuration
145
+
146
+ 3. Add configuration to `~/.openclaw/openclaw.json`:
147
+ ```json
148
+ {
149
+ "channels": {
150
+ "yeego": {
151
+ "config": {
152
+ "token": "your-token-here",
153
+ "profileId": "your-profile-id",
154
+ "baseUrl": "https://yeego.app",
155
+ "sidecarUrl": "https://yeego.app/_sidecar",
156
+ "connectUrl": "https://yeego.app/_sidecar/public/openclaw/connect"
157
+ }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ 4. Restart OpenClaw gateway:
164
+ ```bash
165
+ openclaw gateway restart
166
+ ```
167
+
168
+ The plugin will automatically:
169
+ - ✅ Enable the Yeego channel
170
+ - ✅ Start the message poller process
171
+ - ✅ Connect to your Yeego profile
172
+
173
+ ### Manual Installation (Development)
174
+
175
+ For local development, contact the Yeego team for access to the plugin source code.
176
+
177
+ ### Configuration Files
178
+
179
+ The plugin creates and manages these files automatically:
180
+ - `~/.yeego/config.json` - Yeego credentials (auto-generated from OpenClaw config)
181
+ - `~/.openclaw/yeego-sessions/session-mappings.json` - Session mappings
182
+ - `~/.yeego/processed.json` - Processed message IDs (deduplication)
183
+
184
+ ## Development
185
+
186
+ ### File Structure
187
+
188
+ ```
189
+ openclaw-plugin/
190
+ ├── index.ts # Plugin entry point
191
+ ├── poller.ts # Realtime listener (subprocess)
192
+ ├── src/
193
+ │ ├── channel.ts # Channel plugin implementation
194
+ │ ├── sessionMapping.ts # Session storage
195
+ │ └── tools/
196
+ │ ├── index.ts # Tool factory
197
+ │ ├── profileTools.ts # Profile management
198
+ │ └── pocketbase.ts # PocketBase client wrapper
199
+ ├── package.json
200
+ └── README.md
201
+ ```
202
+
203
+ ### Type Safety
204
+
205
+ All code uses TypeScript with strict typing:
206
+ - PocketBase record types
207
+ - OpenClaw plugin SDK types
208
+ - Explicit return types for all functions
209
+ - No `any` types (except for globalThis polyfill)
210
+
211
+ ### Code Style
212
+
213
+ - Clear section separators with `// ===` comments
214
+ - Descriptive function and variable names
215
+ - JSDoc comments for public APIs
216
+ - Grouped related functions
217
+ - Constants at top of file
218
+
219
+ ## Testing
220
+
221
+ Send a message in Yeego app to test the integration. Check logs:
222
+
223
+ ```bash
224
+ tail -f ~/.openclaw/logs/gateway.log | grep Yeego
225
+ ```
226
+
227
+ Expected flow:
228
+ 1. `[Yeego] New message detected`
229
+ 2. `[Yeego] Processing message`
230
+ 3. `[Yeego] ✓ Set messageChannel="yeego"`
231
+ 4. `[Yeego] Message delivered`
232
+
233
+ ## Troubleshooting
234
+
235
+ ### "Unknown target" errors
236
+ - Check that `messaging.targetResolver.looksLikeId` is implemented
237
+ - Verify conversation ID format is alphanumeric
238
+
239
+ ### "Failed to connect to PocketBase"
240
+ - Use `127.0.0.1` instead of `localhost` in config
241
+ - Verify PocketBase is running: `curl http://127.0.0.1:8091/api/health`
242
+
243
+ ### Messages go to wrong channel
244
+ - Check `~/.openclaw/agents/main/sessions/{sessionId}.jsonl`
245
+ - First line should contain `"messageChannel": "yeego"`
246
+ - If missing, poller's `setSessionMessageChannel()` may have failed
247
+
248
+ ### Poller not starting
249
+ - Check gateway logs: `tail ~/.openclaw/logs/gateway.log`
250
+ - Verify tsx is available: `which tsx`
251
+ - Check poller config: `cat ~/.yeego/config.json`
252
+
253
+ ### "Cron delivery requires a recipient (--to)"
254
+ This error occurs when a cron job or isolated session tries to send messages without specifying the delivery target.
255
+
256
+ **Solution**: Add `channel` and `to` parameters to your cron job payload:
257
+ ```json
258
+ {
259
+ "payload": {
260
+ "deliver": true,
261
+ "channel": "yeego",
262
+ "to": "conversation_id"
263
+ }
264
+ }
265
+ ```
266
+
267
+ See the "Cron Job Delivery" section under "Key Implementation Details" for more information.
268
+
269
+ ## License
270
+
271
+ Part of the Yeego application.
package/index.ts ADDED
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Yeego OpenClaw Plugin Entry Point
3
+ *
4
+ * This plugin integrates Yeego with OpenClaw by:
5
+ * - Registering the Yeego channel for message sending/receiving
6
+ * - Providing tools for Yeego-specific operations (profile management, target lookup)
7
+ * - Auto-starting the yeego-poller process for message polling
8
+ */
9
+
10
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
11
+ import { yeegoPlugin } from "./src/channel.js";
12
+ import { createYeegoTools } from "./src/tools/index.js";
13
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
14
+ import { join, dirname } from 'path';
15
+ import { homedir } from 'os';
16
+ import { spawn, ChildProcess } from 'child_process';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ let pollerProcess: ChildProcess | null = null;
20
+ let pollerRestartCount = 0;
21
+ const MAX_RESTART_ATTEMPTS = 5;
22
+
23
+ function ensureChannelConfig() {
24
+ try {
25
+ const configPath = join(homedir(), '.openclaw', 'openclaw.json');
26
+ if (!existsSync(configPath)) {
27
+ console.error(`[Yeego] Config file not found: ${configPath}`);
28
+ return;
29
+ }
30
+
31
+ const fullConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
32
+
33
+ // Check if channels.yeego.config exists
34
+ if (!fullConfig?.channels?.yeego?.config) {
35
+ console.error(`[Yeego] No channel config found, skipping auto-enable`);
36
+ return;
37
+ }
38
+
39
+ let modified = false;
40
+
41
+ // Auto-enable the channel if not explicitly set
42
+ if (fullConfig.channels.yeego.enabled === undefined) {
43
+ fullConfig.channels.yeego.enabled = true;
44
+ modified = true;
45
+ console.error(`[Yeego] Auto-enabled channel`);
46
+ }
47
+
48
+ // Save if modified
49
+ if (modified) {
50
+ writeFileSync(configPath, JSON.stringify(fullConfig, null, 2), 'utf-8');
51
+ console.error(`[Yeego] Updated OpenClaw config with required settings`);
52
+ }
53
+ } catch (error) {
54
+ console.error(`[Yeego] Error ensuring channel config:`, error);
55
+ }
56
+ }
57
+
58
+ const plugin = {
59
+ id: "yeego",
60
+ name: "Yeego",
61
+ description: "Yeego chat channel integration for OpenClaw",
62
+
63
+ configSchema: {
64
+ type: "object" as const,
65
+ additionalProperties: false,
66
+ properties: {
67
+ token: {
68
+ type: "string" as const,
69
+ description: "Yeego authentication token",
70
+ },
71
+ profileId: {
72
+ type: "string" as const,
73
+ description: "Yeego profile ID",
74
+ },
75
+ baseUrl: {
76
+ type: "string" as const,
77
+ description: "Yeego base URL",
78
+ },
79
+ sidecarUrl: {
80
+ type: "string" as const,
81
+ description: "Yeego sidecar URL",
82
+ },
83
+ connectUrl: {
84
+ type: "string" as const,
85
+ description: "Yeego connect endpoint URL",
86
+ },
87
+ },
88
+ required: ["token", "profileId", "baseUrl", "sidecarUrl", "connectUrl"],
89
+ },
90
+
91
+ async register(api: OpenClawPluginApi) {
92
+ try {
93
+ // Ensure channel configuration is complete
94
+ ensureChannelConfig();
95
+
96
+ // Register channel plugin
97
+ api.registerChannel({ plugin: yeegoPlugin });
98
+ console.error(`[Yeego] Channel registered`);
99
+
100
+ // Register tool factory
101
+ api.registerTool((ctx) => {
102
+ console.error(`[Yeego] Creating tools for session: ${ctx.sessionKey}`);
103
+ const tools = createYeegoTools(ctx);
104
+ console.error(`[Yeego] Registered ${tools.length} tools: ${tools.map(t => t.name).join(", ")}`);
105
+ return tools;
106
+ });
107
+
108
+ console.error(`[Yeego] Plugin registration complete`);
109
+
110
+ // Call connect endpoint on startup
111
+ await callConnectEndpoint(api);
112
+
113
+ // Start the poller process
114
+ startPoller();
115
+ } catch (error) {
116
+ console.error(`[Yeego] Registration error:`, error);
117
+ throw error;
118
+ }
119
+ },
120
+ };
121
+
122
+ function getPluginDir(): string {
123
+ // Get the directory where this plugin is installed
124
+ // Try multiple methods to find the plugin directory
125
+ try {
126
+ // Method 1: Use import.meta.url (ESM)
127
+ const __filename = fileURLToPath(import.meta.url);
128
+ return dirname(__filename);
129
+ } catch {
130
+ // Method 2: Check common installation paths
131
+ const possiblePaths = [
132
+ join(homedir(), '.openclaw', 'plugins', '@yeego', 'yeego'),
133
+ join(homedir(), '.openclaw', 'plugins', '@yeego', 'openclaw-plugin'),
134
+ ];
135
+
136
+ for (const p of possiblePaths) {
137
+ if (existsSync(join(p, 'yeego-poller'))) {
138
+ return p;
139
+ }
140
+ }
141
+
142
+ // Fallback
143
+ return join(homedir(), '.openclaw', 'plugins', '@yeego', 'yeego');
144
+ }
145
+ }
146
+
147
+ function startPoller() {
148
+ try {
149
+ const pluginDir = getPluginDir();
150
+ const pollerPath = join(pluginDir, 'yeego-poller');
151
+
152
+ if (!existsSync(pollerPath)) {
153
+ console.error(`[Yeego] Poller binary not found at: ${pollerPath}`);
154
+ return;
155
+ }
156
+
157
+ // Check if poller is already running
158
+ if (pollerProcess && !pollerProcess.killed) {
159
+ console.error(`[Yeego] Poller is already running (pid: ${pollerProcess.pid})`);
160
+ return;
161
+ }
162
+
163
+ console.error(`[Yeego] Starting poller from: ${pollerPath}`);
164
+
165
+ pollerProcess = spawn(pollerPath, ['start'], {
166
+ detached: false,
167
+ stdio: ['ignore', 'pipe', 'pipe'],
168
+ });
169
+
170
+ pollerProcess.stdout?.on('data', (data) => {
171
+ console.error(`[Yeego Poller] ${data.toString().trim()}`);
172
+ });
173
+
174
+ pollerProcess.stderr?.on('data', (data) => {
175
+ console.error(`[Yeego Poller] ${data.toString().trim()}`);
176
+ });
177
+
178
+ pollerProcess.on('error', (err) => {
179
+ console.error(`[Yeego] Failed to start poller:`, err);
180
+ pollerProcess = null;
181
+ });
182
+
183
+ pollerProcess.on('exit', (code, signal) => {
184
+ console.error(`[Yeego] Poller exited with code ${code}, signal ${signal}`);
185
+ pollerProcess = null;
186
+
187
+ // Auto-restart after 5 seconds if it crashed (with limit)
188
+ if (code !== 0 && code !== null) {
189
+ pollerRestartCount++;
190
+ if (pollerRestartCount <= MAX_RESTART_ATTEMPTS) {
191
+ const delay = Math.min(5000 * pollerRestartCount, 30000); // Exponential backoff, max 30s
192
+ console.error(`[Yeego] Poller crashed, restarting in ${delay}ms (attempt ${pollerRestartCount}/${MAX_RESTART_ATTEMPTS})...`);
193
+ setTimeout(() => startPoller(), delay);
194
+ } else {
195
+ console.error(`[Yeego] Poller crashed ${pollerRestartCount} times, giving up. Please check configuration.`);
196
+ }
197
+ } else {
198
+ // Normal exit, reset counter
199
+ pollerRestartCount = 0;
200
+ }
201
+ });
202
+
203
+ console.error(`[Yeego] Poller started (pid: ${pollerProcess.pid})`);
204
+
205
+ // Reset restart counter on successful start
206
+ setTimeout(() => {
207
+ if (pollerProcess && !pollerProcess.killed) {
208
+ pollerRestartCount = 0;
209
+ console.error(`[Yeego] Poller running stable, reset restart counter`);
210
+ }
211
+ }, 10000); // Consider stable if running for 10 seconds
212
+
213
+ // Cleanup on process exit
214
+ process.on('exit', () => {
215
+ if (pollerProcess && !pollerProcess.killed) {
216
+ console.error(`[Yeego] Stopping poller...`);
217
+ pollerProcess.kill('SIGTERM');
218
+ }
219
+ });
220
+
221
+ process.on('SIGINT', () => {
222
+ if (pollerProcess && !pollerProcess.killed) {
223
+ pollerProcess.kill('SIGTERM');
224
+ }
225
+ process.exit(0);
226
+ });
227
+
228
+ process.on('SIGTERM', () => {
229
+ if (pollerProcess && !pollerProcess.killed) {
230
+ pollerProcess.kill('SIGTERM');
231
+ }
232
+ process.exit(0);
233
+ });
234
+
235
+ } catch (error) {
236
+ console.error(`[Yeego] Error starting poller:`, error);
237
+ }
238
+ }
239
+
240
+ function validateYeegoUrl(url: string, baseUrl: string): boolean {
241
+ try {
242
+ const parsed = new URL(url);
243
+ const baseParsed = new URL(baseUrl);
244
+
245
+ // Ensure URL and baseUrl have the same hostname
246
+ if (parsed.hostname !== baseParsed.hostname) {
247
+ console.error(`[Yeego] URL hostname mismatch: ${parsed.hostname} !== ${baseParsed.hostname}`);
248
+ return false;
249
+ }
250
+
251
+ // Ensure using https (except localhost)
252
+ if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost' && !parsed.hostname.endsWith('.localhost')) {
253
+ console.error(`[Yeego] Invalid protocol: ${parsed.protocol}, must use https`);
254
+ return false;
255
+ }
256
+
257
+ return true;
258
+ } catch (error) {
259
+ console.error(`[Yeego] Invalid URL:`, error);
260
+ return false;
261
+ }
262
+ }
263
+
264
+ async function callConnectEndpoint(api: OpenClawPluginApi) {
265
+ try {
266
+ // Read config directly from file instead of using api.getChannelConfig
267
+ // This is more reliable during the registration phase
268
+ const configPath = join(homedir(), '.openclaw', 'openclaw.json');
269
+ if (!existsSync(configPath)) {
270
+ console.error(`[Yeego] Config file not found: ${configPath}`);
271
+ return;
272
+ }
273
+
274
+ const fullConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
275
+ const config = fullConfig?.channels?.yeego?.config;
276
+
277
+ if (!config || !config.connectUrl || !config.token || !config.profileId || !config.baseUrl) {
278
+ console.error(`[Yeego] Missing config for connect endpoint:`, {
279
+ hasConnectUrl: !!config?.connectUrl,
280
+ hasToken: !!config?.token,
281
+ hasProfileId: !!config?.profileId,
282
+ hasBaseUrl: !!config?.baseUrl,
283
+ configPath,
284
+ });
285
+ return;
286
+ }
287
+
288
+ // Validate connectUrl to prevent SSRF
289
+ if (!validateYeegoUrl(config.connectUrl, config.baseUrl)) {
290
+ console.error(`[Yeego] Invalid connectUrl: must match baseUrl domain`);
291
+ return;
292
+ }
293
+
294
+ console.error(`[Yeego] Calling connect endpoint`);
295
+
296
+ const response = await fetch(config.connectUrl, {
297
+ method: "POST",
298
+ headers: {
299
+ "Authorization": `Bearer ${config.token}`,
300
+ "Content-Type": "application/json",
301
+ },
302
+ body: JSON.stringify({ profile_id: config.profileId }),
303
+ });
304
+
305
+ if (!response.ok) {
306
+ const errorText = await response.text();
307
+ console.error(`[Yeego] Connect endpoint failed: ${response.status} ${errorText}`);
308
+ return;
309
+ }
310
+
311
+ const result = await response.json();
312
+ console.error(`[Yeego] Successfully connected:`, result);
313
+
314
+ // Write config to ~/.yeego/config.json for poller (using underscore format)
315
+ const yeegoConfigDir = join(homedir(), '.yeego');
316
+ const yeegoConfigFile = join(yeegoConfigDir, 'config.json');
317
+
318
+ mkdirSync(yeegoConfigDir, { recursive: true });
319
+
320
+ const pollerConfig = {
321
+ token: config.token,
322
+ profile_id: config.profileId,
323
+ base_url: config.baseUrl,
324
+ sidecar_url: config.sidecarUrl,
325
+ };
326
+
327
+ writeFileSync(yeegoConfigFile, JSON.stringify(pollerConfig, null, 2), 'utf-8');
328
+ console.error(`[Yeego] Saved poller config to ${yeegoConfigFile}`);
329
+ } catch (error) {
330
+ console.error(`[Yeego] Error calling connect endpoint:`, error);
331
+ }
332
+ }
333
+
334
+ export default plugin;