Skip to content
v0.2.4MIT

X (Twitter) connector — mentions, timelines, search, threads, and posting via the official X API v2

What this package declares

The file a client reads when it loads this plugin, exactly as this revision carries it.

plugin.json
{
	"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
	"name": "x",
	"version": "0.2.4",
	"description": "X (Twitter) connector — mentions, timelines, search, threads, and posting via the official X API v2",
	"author": {
		"name": "Dimension"
	},
	"license": "MIT",
	"keywords": [
		"x",
		"twitter",
		"social",
		"posts",
		"mentions",
		"connector"
	],
	"extensions": {
		"ai.insodimension.dimension": {
			"contractVersion": 1,
			"title": "X",
			"icon": "assets/icon.svg",
			"category": "connector",
			"defaultEnabled": false,
			"examples": [
				"Check my X mentions from the last day and draft a reply to each one worth answering",
				"Search X for what people are saying about local-first AI agents this week and summarize the themes",
				"Draft a thread about what we shipped this week, show it to me, and post it once I approve"
			],
			"connect": {
				"kind": [
					"oauth"
				],
				"setup": [
					{
						"title": "Reading is FREE — just stay logged into x.com in your browser",
						"detail": "Research (search, threads, your own posts) runs through x.com's own web endpoint using the cookies you already have from being logged in, at zero cost. Nothing to configure: log into x.com in Safari, Chrome, or Firefox. Only if that isn't possible, set X_AUTH_TOKEN and X_CT0 instead. Run x_usage any time to see which lane is live. Everything below is for POSTING."
					},
					{
						"title": "Create an app in the X developer console — needed only to WRITE",
						"detail": "Posting, replying, threads, deleting, and DMs require the official API with your own developer app; there is no safe free path for writes. If you only want research, you can skip the rest and leave this plugin unconnected. Sign in as the account the agent will act as, create (or open) a Project, and add an App inside it. The App — not the Project — issues the Client ID.",
						"url": "https://console.x.com/",
						"urlLabel": "Open the X developer console"
					},
					{
						"title": "Set up User authentication → type \"Native App\" (public client)",
						"detail": "In the App's \"User authentication settings\", enable OAuth 2.0 and choose App type \"Native App\". This makes it a PUBLIC client: PKCE-only, no client secret. A \"Web App\"/\"Confidential client\" will NOT connect here — X requires confidential clients to send HTTP Basic auth on the token endpoint, which this generic OAuth executor does not do."
					},
					{
						"title": "Register the callback URL exactly: http://localhost:53682/callback",
						"detail": "Paste it verbatim into \"Callback URI / Redirect URL\". X validates this string EXACTLY — a different port or a trailing slash fails the connect. Set \"Website URL\" to anything valid (e.g. https://x.com). Port 53682 must be free when you connect."
					},
					{
						"title": "Set App permissions to \"Read and write\" (add \"Direct message\" only if you want x_dm)",
						"detail": "App permissions are a hard ceiling above the OAuth scopes — a Read-only app cannot post no matter which scope option you pick below. If you change permissions AFTER connecting, you must reconnect for the new ceiling to take effect."
					},
					{
						"title": "Buy API credits — writes are pay-per-use since Feb 2026",
						"detail": "A post costs $0.015, or $0.20 when it contains a URL; a DM $0.015. Without credits every write returns 403. Reads only reach this meter when the free lane is unavailable (then: $0.001 for your own mentions/posts/bookmarks, $0.005 per post for search and timelines) — which is exactly why the free lane exists.",
						"url": "https://docs.x.com/x-api/getting-started/pricing",
						"urlLabel": "Read the pricing page"
					},
					{
						"title": "Copy the OAuth 2.0 Client ID (and the Secret, if X gave you one)",
						"detail": "Keys and tokens → \"OAuth 2.0 Client ID and Client Secret\". A Native App is a public client and issues only a Client ID — leave the secret field blank. A \"Web App, Automated App or Bot\" is a confidential client and issues BOTH; paste the secret too. Either works. Both stay on this machine."
					}
				],
				"form": [
					{
						"id": "clientId",
						"label": "OAuth 2.0 Client ID",
						"help": "From your X App's \"Keys and tokens\" tab, under \"OAuth 2.0 Client ID and Client Secret\". The app must have the callback http://localhost:53682/callback registered exactly.",
						"helpUrl": "https://console.x.com/",
						"helpUrlLabel": "Open the X developer console"
					},
					{
						"id": "clientSecret",
						"label": "OAuth 2.0 Client Secret (blank for a Native App)",
						"secret": true,
						"help": "Leave BLANK if your app is a Native App — that is a public client, PKCE only, and X issues no secret. Fill it in if X showed you one: that means the app is a confidential client, and the secret is sent as HTTP Basic on the token endpoint, which is what X requires and what the engine now does."
					}
				],
				"oauth": {
					"provider": "x",
					"authUrl": "https://x.com/i/oauth2/authorize",
					"tokenUrl": "https://api.x.com/2/oauth2/token",
					"scopes": [
						"tweet.read",
						"tweet.write",
						"users.read",
						"offline.access"
					],
					"usePkce": true,
					"callbackPort": 53682,
					"allowPortFallback": false,
					"tokenAuth": "body",
					"scopeOptions": [
						{
							"id": "read",
							"label": "Read only",
							"detail": "Mentions, your posts, home timeline, search, threads, profiles. No posting, no DMs — the write tools will fail with a scope error.",
							"scopes": [
								"tweet.read",
								"users.read",
								"offline.access"
							]
						},
						{
							"id": "readwrite",
							"label": "Read and post",
							"detail": "Everything above plus posting, threads, deleting your own posts, and bookmarks. No DMs.",
							"scopes": [
								"tweet.read",
								"tweet.write",
								"users.read",
								"bookmark.read",
								"bookmark.write",
								"offline.access"
							],
							"default": true
						},
						{
							"id": "full",
							"label": "Read, post, and DMs",
							"detail": "Adds direct messages. Requires the app's permissions to include \"Direct message\" — otherwise X refuses the scope at consent.",
							"scopes": [
								"tweet.read",
								"tweet.write",
								"users.read",
								"bookmark.read",
								"bookmark.write",
								"dm.read",
								"dm.write",
								"offline.access"
							]
						}
					]
				},
				"configTarget": "~/.config/dimension-x/token.json",
				"verify": {
					"http": "https://api.x.com/2/users/me",
					"failures": [
						{
							"match": "not permitted|client-not-enrolled|453|Access Denied",
							"title": "This X app has no API access",
							"detail": "X accepted the sign-in but the app itself isn't entitled to call the API — usually no purchased credits, or a product tier that doesn't include this endpoint. Buy credits for the app in the developer console, then reconnect.",
							"url": "https://console.x.com/",
							"urlLabel": "Open the X developer console"
						},
						{
							"match": "Unauthorized|invalid_token|invalid_request|401",
							"title": "Reconnect your X account",
							"detail": "The stored token is no longer valid — run Connect again to re-authorize. If it fails immediately, the app is probably a confidential \"Web App\" rather than a public \"Native App\"."
						},
						{
							"match": "Too Many Requests|429|rate limit",
							"title": "X rate limit hit",
							"detail": "Rate limits are separate from billing and reset on a 15-minute window. Wait a few minutes and try again."
						}
					]
				}
			},
			"toolRenderer": [
				{
					"match": "x_me",
					"use": "summary",
					"icon": "assets/tools/x.svg",
					"iconSize": 30,
					"title": "Account"
				},
				{
					"match": "x_mentions",
					"use": "summary",
					"icon": "assets/tools/mentions.svg",
					"iconSize": 30,
					"title": "Mentions"
				},
				{
					"match": "x_my_posts",
					"use": "summary",
					"icon": "assets/tools/posts.svg",
					"iconSize": 30,
					"title": "My posts"
				},
				{
					"match": "x_home_timeline",
					"use": "summary",
					"icon": "assets/tools/timeline.svg",
					"iconSize": 30,
					"title": "Timeline"
				},
				{
					"match": "x_search",
					"use": "summary",
					"icon": "assets/tools/search.svg",
					"iconSize": 30,
					"title": "Search"
				},
				{
					"match": "x_get_post",
					"use": "summary",
					"icon": "assets/tools/posts.svg",
					"iconSize": 30,
					"title": "Post"
				},
				{
					"match": "x_read_thread",
					"use": "summary",
					"icon": "assets/tools/thread.svg",
					"iconSize": 30,
					"title": "Thread"
				},
				{
					"match": "x_user_lookup",
					"use": "summary",
					"icon": "assets/tools/profile.svg",
					"iconSize": 30,
					"title": "Profile"
				},
				{
					"match": "x_bookmarks",
					"use": "summary",
					"icon": "assets/tools/bookmark.svg",
					"iconSize": 30,
					"title": "Bookmarks"
				},
				{
					"match": "x_usage",
					"use": "summary",
					"icon": "assets/tools/usage.svg",
					"iconSize": 30,
					"title": "Usage"
				},
				{
					"match": "x_post",
					"use": "summary",
					"icon": "assets/tools/compose.svg",
					"iconSize": 30,
					"title": "Post"
				},
				{
					"match": "x_thread",
					"use": "summary",
					"icon": "assets/tools/compose.svg",
					"iconSize": 30,
					"title": "Post thread"
				},
				{
					"match": "x_delete",
					"use": "summary",
					"icon": "assets/tools/delete.svg",
					"iconSize": 30,
					"title": "Delete post"
				},
				{
					"match": "x_bookmark",
					"use": "summary",
					"icon": "assets/tools/bookmark.svg",
					"iconSize": 30,
					"title": "Bookmark"
				},
				{
					"match": "x_dm",
					"use": "summary",
					"icon": "assets/tools/dm.svg",
					"iconSize": 30,
					"title": "Send DM"
				}
			],
			"skillCard": {
				"skill": "x",
				"use": "summary",
				"title": "X"
			},
			"tools": [
				{
					"name": "x_me",
					"description": "The connected X account — username, name, id, and follower/following/post counts. Confirms which account the agent is acting as."
				},
				{
					"name": "x_mentions",
					"description": "Posts mentioning the connected account, newest first. The monitoring feed — the official owned read ($0.001 each) because it is authoritative, with a free @handle search as fallback."
				},
				{
					"name": "x_my_posts",
					"description": "The connected account's own recent posts with their engagement metrics. Free when x.com cookies are available."
				},
				{
					"name": "x_home_timeline",
					"description": "The connected account's home timeline (reverse-chronological). Official only — billed at $0.005/post."
				},
				{
					"name": "x_search",
					"description": "Search public posts from the last 7 days with X's query syntax. The research tool — FREE when x.com cookies are available, otherwise $0.005 per post returned."
				},
				{
					"name": "x_get_post",
					"description": "Read one post by id or URL — full text, author, timestamp, and engagement metrics. Official only ($0.005 for the one post)."
				},
				{
					"name": "x_read_thread",
					"description": "Read a whole conversation thread from any post in it, in reply order. Free when x.com cookies are available. Only reaches posts from the last 7 days."
				},
				{
					"name": "x_user_lookup",
					"description": "Look up a public profile by @username — bio, counts, verified status, and account age."
				},
				{
					"name": "x_bookmarks",
					"description": "The connected account's bookmarked posts, newest first. A cheap owned read ($0.001 each)."
				},
				{
					"name": "x_usage",
					"description": "Which read lane is live (free x.com cookies vs billed official API) plus this app's post-read consumption against its monthly cap. The 'am I being charged for reads?' check."
				},
				{
					"name": "x_post",
					"description": "Publish a post, or a reply when replyTo is set. PUBLIC and immediate under the user's own account — confirm the exact text first. Costs $0.015, or $0.20 if the text contains a URL."
				},
				{
					"name": "x_thread",
					"description": "Publish several posts as one self-replying thread. PUBLIC and immediate; a mid-thread failure leaves the earlier posts up. Confirm every post first."
				},
				{
					"name": "x_delete",
					"description": "Delete one of the connected account's own posts by id. Irreversible — confirm the exact post first."
				},
				{
					"name": "x_bookmark",
					"description": "Add or remove a bookmark on a post for the connected account. Private to the account."
				},
				{
					"name": "x_dm",
					"description": "Send a direct message to a user. PRIVATE but immediate and unretractable — confirm the recipient and full text first."
				}
			]
		}
	}
}

Client extensions

Data this package carries for particular clients. The directory lists the clients named and never reads what is addressed to them.

  • ai.insodimension.dimension