Files
automaton/plugins/pi-read-tweet/index.ts
T

92 lines
3.5 KiB
TypeScript
Raw Normal View History

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 },
};
}
},
});
}