diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml index 2f934b1..bef9d5e 100644 --- a/.github/workflows/auto-label.yml +++ b/.github/workflows/auto-label.yml @@ -19,5 +19,3 @@ jobs: uses: actions/labeler@v5 with: sync-labels: true - with: - sync-labels: true diff --git a/README.md b/README.md index fb39de2..8b858b4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MyXstack -This repository hosts a lightweight, step-by-step guide for setting up an autonomous X (Twitter) agent system that acts based on thread context & reasoning, through Grok via the xMCP server. +This repository hosts a lightweight, step-by-step guide for setting up an autonomous X (Twitter) agent system that acts based on thread context and reasoning, through Grok via the xMCP server. ## Phase 1: Gather prerequisites & accounts (1–2 hours) @@ -20,8 +20,8 @@ This repository hosts a lightweight, step-by-step guide for setting up an autono 3. Note the bot user ID (you can fetch this via the API later). ### xAI / Grok API key -. Visit , open the API keys section, and create a key that starts with `xai-`. -. Store the key securely. +1. Visit , open the API keys section, and create a key that starts with `xai-`. +2. Store the key securely. ### Local tooling - Install Python 3.9+ (3.10–3.13 recommended). diff --git a/src/services/grok.ts b/src/services/grok.ts index 0fd6b2c..b72f1e8 100644 --- a/src/services/grok.ts +++ b/src/services/grok.ts @@ -58,8 +58,21 @@ export class GrokService { throw new Error(`Grok API error: ${response.status}`); } - const data: any = await response.json(); - const analysisText = data.choices[0]?.message?.content || ''; + const data: unknown = await response.json(); + + // Validate and narrow response structure + if ( + typeof data !== 'object' || + data === null || + !('choices' in data) || + !Array.isArray(data.choices) || + data.choices.length === 0 + ) { + throw new Error('Invalid response structure from Grok API: missing or empty choices array'); + } + + const typedData = data as { choices: Array<{ message?: { content?: string } }> }; + const analysisText = typedData.choices[0]?.message?.content || ''; // Use the root post ID from the thread, not the mention text return this.parseGrokResponse(analysisText, thread.root_post.id); diff --git a/src/services/xapi.ts b/src/services/xapi.ts index 1f62056..7809559 100644 --- a/src/services/xapi.ts +++ b/src/services/xapi.ts @@ -44,10 +44,16 @@ export class XAPIClient { throw new Error('Failed to get user ID from response'); } - const mentionsResponse = await this.makeXAPIRequest( - `https://api.twitter.com/2/users/${userId}/mentions?max_results=10&expansions=author_id&tweet.fields=created_at,conversation_id,in_reply_to_user_id,referenced_tweets`, - 'GET' - ); + const params = new URLSearchParams({ + max_results: '10', + expansions: 'author_id', + 'tweet.fields': 'created_at,conversation_id,in_reply_to_user_id,referenced_tweets', + }); + if (this.lastMentionId) { + params.set('since_id', this.lastMentionId); + } + const mentionsUrl = `https://api.twitter.com/2/users/${userId}/mentions?${params.toString()}`; + const mentionsResponse = await this.makeXAPIRequest(mentionsUrl, 'GET'); if (!mentionsResponse || !Array.isArray(mentionsResponse.data)) { console.warn('Invalid response from X API (mentions)'); @@ -77,7 +83,11 @@ export class XAPIClient { 'GET' ); - return this.parseThread(response.data || []); + if (!Array.isArray(response.data)) { + console.warn('Unexpected response shape from X API (thread): data is not an array'); + return null; + } + return this.parseThread(response.data); } catch (error) { console.error('Error fetching thread:', error); return null; @@ -181,7 +191,7 @@ export class XAPIClient { }; } - private parseThread(tweets: any[]): XThread | null { + private parseThread(tweets: { created_at: string; [key: string]: unknown }[]): XThread | null { if (tweets.length === 0) return null; const sorted = tweets.sort((a, b) =>