import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { Type } from "@sinclair/typebox"; const BIRD_BIN = "/opt/homebrew/bin/bird"; const TweetParams = Type.Object({ url: Type.String({ minLength: 1, description: "Tweet URL or numeric ID" }), mode: Type.Optional( Type.Union([ Type.Literal("read"), Type.Literal("thread"), Type.Literal("replies"), ]), ), }); export default function (pi: ExtensionAPI) { pi.registerTool({ name: "read_tweet", label: "Read Tweet", description: "Read an X/Twitter tweet or thread using the local bird CLI. " + "Returns tweet author, text, media, metrics, and timestamps. " + "Use this instead of webfetch for x.com/twitter.com URLs.", promptSnippet: "Read X/Twitter tweets with bird CLI", promptGuidelines: [ "Use read_tweet when the user shares an x.com or twitter.com URL and asks what it says", "Use mode='thread' for full conversation threads", "Use mode='replies' to fetch replies to a tweet", "If fetch fails with auth errors, ask the user to sign in to x.com in their browser and retry", ], parameters: TweetParams, async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { const { url, mode = "read" } = params; try { const { execSync } = await import("child_process"); const cmd = `${BIRD_BIN} ${mode} ${JSON.stringify(url)} --json`; const stdout = execSync(cmd, { encoding: "utf-8", timeout: 15000 }); const parsed = JSON.parse(stdout); const author = parsed.author?.name || parsed.author?.screen_name || "unknown"; const text = parsed.text || parsed.content || ""; const createdAt = parsed.created_at || ""; const retweetCount = parsed.metrics?.retweet_count ?? parsed.metrics?.retweets ?? 0; const likeCount = parsed.metrics?.like_count ?? parsed.metrics?.likes ?? 0; const replyCount = parsed.metrics?.reply_count ?? parsed.metrics?.replies ?? 0; const mediaCount = parsed.media?.length ?? 0; const threadCount = parsed.thread?.tweets?.length ?? 0; const repliesCount = parsed.replies?.length ?? 0; const lines: string[] = []; lines.push(`Author: ${author}`); if (createdAt) lines.push(`Posted: ${createdAt}`); lines.push(""); lines.push(text); if (retweetCount || likeCount || replyCount) { lines.push(""); lines.push(`Retweets: ${retweetCount} Likes: ${likeCount} Replies: ${replyCount}`); } if (mediaCount) lines.push(`Media: ${mediaCount} attachment(s)`); if (threadCount) lines.push(`Thread: ${threadCount} tweets total`); if (repliesCount) lines.push(`Replies fetched: ${repliesCount}`); return { content: [ { type: "text", text: lines.join("\n") }, { type: "text", text: `\n--- raw ---\n${JSON.stringify(parsed, null, 2)}` }, ], details: { author, text: text.slice(0, 500), url, mode, tweetCount: threadCount || repliesCount || 1, }, }; } catch (e: any) { const errMsg = e.stderr || e.message || String(e); const hint = errMsg.includes("auth") ? " Sign in to x.com in your browser and retry." : ""; return { content: [{ type: "text", text: `Failed to fetch tweet: ${errMsg}${hint}` }], details: { error: errMsg, url, mode }, }; } }, }); }