
X Ops
- 31 installs
- Updated August 3, 2026
- saltbo/agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
x-ops is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- x-ops
- AI & Agent Building
- AI-coding skill
X Ops by the numbers
- 31 all-time installs (skills.sh)
- Ranked #9,202 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/saltbo/agent-skills --skill x-opsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| Last updated | August 3, 2026 |
| Repository | saltbo/agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
X Ops — Operational Procedures
All operations use x-cli.py (tweepy + official X API v2). Set the alias at the start of every session:
X="uv run .agents/skills/x-ops/x-cli.py"Commands
Read
$X profile <username> # full profile with followers/following/tweet count
$X tweet <tweet_id> # tweet with reply_count, likes, views, author_followers
$X search "<query>" [count] # search recent tweets (min 10, max 100)
$X mentions <user_id> [count] # mentions of a userAll read commands return JSON with full metrics (reply_count, followers, etc).
Write
$X post "<text>" # post a tweet
$X reply <tweet_id> "<text>" # reply to a tweet
$X like <tweet_id> # like a tweet
$X follow <user_id> # follow a user
$X unfollow <user_id> # unfollow a userWrite commands return JSON confirmation with the created tweet ID.
Rate Limits (X API v2)
| Operation | Limit |
|---|---|
| search | 60 / 15min |
| mentions | 10 / 15min |
| profile | 95 / 15min |
| tweet | 300 / 15min |
| post | 100 / day |
| like | 1000 / day |
| follow | 15 / 15min |
Wait 2-3 seconds between write operations.
API Constraint
X API blocks replies/quotes to accounts that haven't mentioned @rdsaltbo. Do NOT attempt replies to strangers — always 403. Only reply to mentions.
Cycle Workflow
Minimize Bash calls. Every $X call is a tool round-trip. Target: 8-12 calls per cycle.
1. $X profile rdsaltbo — get follower count, determine tier (1 call) 2. $X search "<query>" 20 — run 1-2 searches from task description (1-2 calls) 3. $X mentions 726438383401074688 — check mentions (1 call) 4. Reply to actionable mentions only — do NOT reply to strangers (0-2 calls) 5. Like 5-8 posts from search results — pick substantive posts from target developers (5-8 calls, can batch quickly) 6. Follow 3-5 developers from search results — use author_id from search data directly (3-5 calls) 7. Post 1 original tweet based on what you observed in search (1 call) 8. Log actions + create next task (2 calls)
Search Query Rotation
Use 1-2 per cycle. These find both content inspiration and like/follow targets:
claude codeAI coding agentcursor AIORcodexmulti-agentANDcodevibe codingAI developer toolscoding agent workflow"build in public" AI tools"looking to connect" developer AI"indie hacker" AI agent
De-duplication
Do NOT like/follow the same person more than once per cycle. Check the task description's "Follow-up from last cycle" — if you already followed someone in previous cycles, skip them.
Task Standards
Title format
x-ops #<seq>: <focus keyword>
Description template
## Search queries
- <query 1>
- <query 2>
## Tweet plan
- Type: <hot take | build-in-public | tip | comparison | observation>
- Topic: <what to write about>
(or "None — replies + connect only cycle")
## Follow-up from last cycle
- <conversation to continue, or "none">
## Notes from last cycle
- <observations about what worked or didn't>Self-continuation
⚠️ CRITICAL: You MUST include --scheduled-at. Without it, the task runs immediately and creates an infinite loop that spams the account. This has happened before — do NOT skip this parameter.
Calculate the scheduled time with a random offset (avoid exact intervals — looks robotic):
OFFSET=$((110 + RANDOM % 40))m # 110-150 minutes (roughly 2h with jitter)
NEXT=$(date -u -v+${OFFSET} +"%Y-%m-%dT%H:%M:%SZ")
# Check if NEXT falls in quiet hours (UTC 06:00-14:00 = EST 01:00-09:00 = PST 22:00-06:00)
# If so, push to 14:00 UTC (09:00 EST / 06:00 PST)
NEXT_HOUR=$(date -u -v+${OFFSET} +"%H")
if [ "$NEXT_HOUR" -ge 6 ] && [ "$NEXT_HOUR" -lt 14 ]; then
NEXT=$(date -u -v+1d +"%Y-%m-%dT14:%M:%SZ") # next day 14:00 UTC
fi
ak create task \
--board jb21kfv6 \
--assign-to e6f896b845f81e93 \
--scheduled-at "$NEXT" \
--title "x-ops #<next-seq>: <focus>" \
--description "<filled template>" \
--priority medium \
--labels "ops,engagement,connect"Verify the created task has a scheduled_at value in the response. If not, cancel it immediately.
Comment Standards
Post comments using ak task log <task-id> "<message>":
1 — Start
CYCLE START | Followers: <n> | Tier: <phase> | Queries: <q1>, <q2>2 — Likes
LIKES (<count>)
♥ @<user> (<followers>) — "<first 50 chars of their post>" [tweet:<id>]3 — Follows
FOLLOWS (<count>)
+ @<user> (<followers>) — <reason>4 — Mentions
MENTIONS (<count> new)
↩ @<user> — "<reply preview>" [tweet:<id>]
— no actionable mentions5 — Tweet
TWEET [<id>]
"<full tweet text>"
Type: <type>6 — Summary
CYCLE COMPLETE | Followers: <n>
Likes: <n> | Follows: <n> | Mentions replied: <n> | Tweet: <0 or 1>
Next: <task-id> scheduled <time>Error Handling
- 403 on reply → skip target, find another. Do NOT count as success.
- Rate limit error → wait until reset, then retry.
- Auth error → stop and report. Do not retry.
- If
ak create taskfails → retry once after 10 seconds.
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = ["tweepy", "requests"]
# ///
"""Complete X (Twitter) CLI using official API v2 via tweepy."""
import json
import os
import sys
from pathlib import Path
import tweepy
ENV_FILE = Path.home() / ".config" / "x-ops" / ".env"
def _load_env():
if ENV_FILE.exists():
for line in ENV_FILE.read_text().splitlines():
if "=" in line and not line.startswith("#"):
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip())
_load_env()
CLIENT = tweepy.Client(
consumer_key=os.environ["TWITTER_API_KEY"],
consumer_secret=os.environ["TWITTER_API_SECRET"],
access_token=os.environ["TWITTER_ACCESS_TOKEN"],
access_token_secret=os.environ["TWITTER_ACCESS_TOKEN_SECRET"],
bearer_token=os.environ.get("TWITTER_BEARER_TOKEN", ""),
)
TWEET_FIELDS = ["public_metrics", "author_id", "created_at", "conversation_id"]
USER_FIELDS = ["public_metrics", "description", "location", "created_at"]
EXPANSIONS = ["author_id"]
def _fmt_tweet(t, users=None):
m = t.public_metrics or {}
author = None
if users and t.author_id:
author = next((u for u in users if u.id == t.author_id), None)
return {
"id": str(t.id),
"text": t.text,
"author_id": str(t.author_id) if t.author_id else None,
"author": author.username if author else None,
"author_name": author.name if author else None,
"author_followers": (author.public_metrics or {}).get("followers_count") if author else None,
"replies": m.get("reply_count", 0),
"retweets": m.get("retweet_count", 0),
"likes": m.get("like_count", 0),
"views": m.get("impression_count", 0),
"created_at": str(t.created_at) if t.created_at else None,
}
def _fmt_user(u):
m = u.public_metrics or {}
return {
"id": str(u.id),
"name": u.name,
"username": u.username,
"description": u.description,
"followers": m.get("followers_count", 0),
"following": m.get("following_count", 0),
"tweets": m.get("tweet_count", 0),
"location": getattr(u, "location", None),
}
def cmd_profile(args):
"""Usage: profile <username>"""
r = CLIENT.get_user(username=args[0], user_fields=USER_FIELDS, user_auth=False)
print(json.dumps(_fmt_user(r.data), indent=2))
def cmd_tweet(args):
"""Usage: tweet <tweet_id>"""
r = CLIENT.get_tweet(args[0], tweet_fields=TWEET_FIELDS,
expansions=EXPANSIONS, user_fields=USER_FIELDS,
user_auth=False)
users = r.includes.get("users", []) if r.includes else []
print(json.dumps(_fmt_tweet(r.data, users), indent=2))
def cmd_search(args):
"""Usage: search <query> [count]"""
count = int(args[1]) if len(args) > 1 else 10
r = CLIENT.search_recent_tweets(query=args[0], max_results=max(10, min(count, 100)),
tweet_fields=TWEET_FIELDS,
expansions=EXPANSIONS,
user_fields=USER_FIELDS,
user_auth=False)
users = r.includes.get("users", []) if r.includes else []
for t in (r.data or []):
print(json.dumps(_fmt_tweet(t, users)))
def cmd_mentions(args):
"""Usage: mentions <user_id> [count]"""
count = int(args[1]) if len(args) > 1 else 10
r = CLIENT.get_users_mentions(args[0], max_results=min(count, 100),
tweet_fields=TWEET_FIELDS,
expansions=EXPANSIONS,
user_fields=USER_FIELDS)
users = r.includes.get("users", []) if r.includes else []
for t in (r.data or []):
print(json.dumps(_fmt_tweet(t, users)))
def cmd_post(args):
"""Usage: post <text>"""
r = CLIENT.create_tweet(text=args[0])
print(json.dumps({"id": str(r.data["id"]), "text": args[0]}))
def cmd_reply(args):
"""Usage: reply <tweet_id> <text>"""
r = CLIENT.create_tweet(text=args[1], in_reply_to_tweet_id=args[0])
print(json.dumps({"id": str(r.data["id"]), "reply_to": args[0], "text": args[1]}))
def cmd_like(args):
"""Usage: like <tweet_id>"""
CLIENT.like(args[0])
print(json.dumps({"ok": True, "liked": args[0]}))
def cmd_follow(args):
"""Usage: follow <user_id>"""
CLIENT.follow_user(args[0])
print(json.dumps({"ok": True, "followed": args[0]}))
def cmd_unfollow(args):
"""Usage: unfollow <user_id>"""
CLIENT.unfollow_user(args[0])
print(json.dumps({"ok": True, "unfollowed": args[0]}))
def cmd_usage(args):
"""Show API usage for current billing period."""
import requests
bt = os.environ.get("TWITTER_BEARER_TOKEN", "")
r = requests.get("https://api.x.com/2/usage/tweets",
headers={"Authorization": f"Bearer {bt}"})
d = r.json().get("data", {})
print(json.dumps({
"used": int(d.get("project_usage", 0)),
"cap": int(d.get("project_cap", 0)),
"reset_day": d.get("cap_reset_day"),
}, indent=2))
COMMANDS = {
"usage": cmd_usage,
"profile": cmd_profile,
"tweet": cmd_tweet,
"search": cmd_search,
"mentions": cmd_mentions,
"post": cmd_post,
"reply": cmd_reply,
"like": cmd_like,
"follow": cmd_follow,
"unfollow": cmd_unfollow,
}
def main():
if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
cmds = ", ".join(COMMANDS.keys())
print(f"Usage: x-cli.py <command> [args...]\nCommands: {cmds}")
sys.exit(1)
cmd = sys.argv[1]
args = sys.argv[2:]
COMMANDS[cmd](args)
if __name__ == "__main__":
main()