Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { XMCPServer } from './mcp/server.js';
async function main() {
// Redirect console.log to stderr so it doesn't conflict with
// MCP StdioServerTransport which uses stdout for protocol messages
console.log = (...args: any[]) => console.error(...args);
console.log = (...args: unknown[]) => console.error(...args);

console.log('═══════════════════════════════════════════════════');
console.log(' MyXstack - Autonomous AI Agent on X (Twitter)');
Expand Down
11 changes: 8 additions & 3 deletions src/services/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ export class AutonomousAgent {

console.log(`\n📬 [${new Date().toLocaleTimeString()}] Found ${newMentions.length} new mention(s)!\n`);

// Process each mention
for (const mention of newMentions) {
// Process mentions oldest-first (API returns newest-first) so Set
// insertion order is chronological and oldest entries are pruned first
for (const mention of [...newMentions].reverse()) {
await this.processMention(mention);
this.processedMentions.add(mention.post.id);
}
Expand All @@ -102,7 +103,11 @@ export class AutonomousAgent {
const excess = this.processedMentions.size - AutonomousAgent.MAX_PROCESSED_MENTIONS;
const iter = this.processedMentions.values();
for (let i = 0; i < excess; i++) {
this.processedMentions.delete(iter.next().value as string);
const { value, done } = iter.next();
if (done) {
break;
}
this.processedMentions.delete(value);
}
}
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/services/grok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class GrokService {
throw new Error(`Grok API error: ${response.status}`);
}

const data: any = await response.json();
const data = await response.json() as { choices: { message?: { content?: string } }[] };
const analysisText = data.choices[0]?.message?.content || '';

// Use the mention post ID so replies target the specific post where the agent was mentioned
Expand Down
14 changes: 12 additions & 2 deletions src/services/xapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ export class XAPIClient {
throw new Error('Failed to get user ID from response');
}

let mentionsUrl = `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`;
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) {
mentionsUrl += `&since_id=${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');

Expand Down Expand Up @@ -91,6 +96,11 @@ export class XAPIClient {
return null;
}

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);
Expand Down