Set this up for me
Give an agent the copy-paste prompt. The steps it must follow are below. They are SETUP.md from the tweet-factory repo, included verbatim at build time (headings shifted down one level so this page keeps a single title).
Human required. Three moments need a person: creating the X app, clicking Authorize in the browser, and pasting any API keys. The agent does the rest.
Set up tweet-factory on this machine for me.
Read these first, in order, and follow them:
1. https://docs.tweetfactory.sh/llms.txt
2. https://docs.tweetfactory.sh/agent.md
3. https://docs.tweetfactory.sh/install.md
4. https://docs.tweetfactory.sh/x-app.md
agent.md is the same runbook as SETUP.md in https://github.com/abhirupghosh/tweet-factory (plus this prompt). When a step and the repo disagree, trust the repo source and say so.
Rules:
- Install tf with the command in https://docs.tweetfactory.sh/install.md. Do not clone the repo unless the user already has a checkout or asked to develop from source.
- If a checkout is already on this machine, stay in it and use ./setup_x.sh. Do not delete it.
- Never invent credentials, tokens, or API keys. Never print secret values back to me.
- Write secrets only into .env. After writing, run chmod 600 .env. .tokens.json is created by tf auth and is already mode 0600.
- Do every step you can do yourself. Stop and ask me when a step says "Human required".
- After each step, run that step's verification check before continuing.
- tweet-factory writes drafts to files. It does not post to X. Do not post tweets for me.
- Run tf fetch on this machine only. The X refresh token rotates on every use; a second machine invalidates it.
Steps that need me (a human), which you cannot finish yourself:
- Creating an X developer app and copying the OAuth 2.0 Client ID and Client Secret.
- Clicking "Authorize app" in a browser during tf auth.
- Pasting any optional key I choose to provide (Tavily, Notion, Slack, or a direct LLM API key).
Start by installing tf (see the install page) and running `tf --help`. If you are already inside a checkout, run ./setup_x.sh --check instead and tell me what is missing.Runbook
Included from
SETUP.mdwhen this site is built. Edit that file, not a copy.
Setting up tweet-factory for a new user — agent runbook
Audience: a coding agent (Claude Code, Cursor, etc.) setting this up for a user on their machine. Follow these steps in order. Ask the user whenever you need a key, secret, or decision — never guess or invent credentials. Keep the user in the loop; the mechanical work is a few commands plus one browser click from them.
The mechanical work is
install.shfor a new machine, or./setup_x.shwhen you are already inside a checkout (Bun/TypeScript — no Python). Your job is to collect the inputs, run the install that matches the machine, help the user fill their persona, and hand them the loop prompt.
Who does what
| Step | Who |
|---|---|
Install tf (script, or ./setup_x.sh when already in a checkout), write .env, chmod 600, fetch, persona file, enrich | Agent |
| Create the X developer app and copy the OAuth 2.0 Client ID and Client Secret | Human |
Click Authorize app in the browser during tf auth | Human |
| Paste optional keys (Tavily, Notion, Slack, direct LLM API) if they want them | Human |
A copy-paste prompt that points an agent at this runbook lives at docs.tweetfactory.sh/agent-prompt.md.
The GitHub repository is private. Release binaries are still public at https://tweetfactory.sh/download/. Do not treat a binary as a substitute for keeping the source private.
Step 0 — Install tf
Prefer the release binary. It does not clone the repo:
curl -fsSL https://tweetfactory.sh/install.sh | sh
mkdir -p ~/tweet-factory && cd ~/tweet-factory
tf --helpThe site build serves install.sh. The script reads https://tweetfactory.sh/download/latest/VERSION, then downloads tf and SHA256SUMS for that tag from https://tweetfactory.sh/download/vX.Y.Z/. TF_VERSION=v0.3.0 pins a tag.
Verify.
tf --helpexits 0 and printstweet-factory (tf).command -v tfis onPATH.
If this machine already has a checkout (a directory with setup_x.sh and package.json), stay there. Do not reinstall over it and do not delete it. Run a read-only check first — it reports missing tools and keys without changing anything:
./setup_x.sh --checkVerify. The command exits 0 and prints a tool/key report. It must not create or edit
.env.
A binary install has no setup_x.sh. Skip the check and continue with Step 1 in the data directory. tf auth writes .env.example there when it is missing.
Step 1 — Get the user's X API credentials (REQUIRED)
The only hard requirement is an X app with OAuth 2.0 (the sole auth X allows for reading bookmarks). Walk the user through it, then collect the keys by asking them. Do not invent values.
Human required. The user does this in a browser at https://developer.x.com/en/portal/dashboard → their app → User authentication settings:
- App permissions: Read
- Type of App: Web App, Automated App or Bot (confidential client → gives a refresh token)
- Callback URI:
http://127.0.0.1:8675/callback(must be exact)- Save, then open the Keys and tokens tab and copy the OAuth 2.0 Client ID and Client Secret.
tf auth requests these scopes: tweet.read, users.read, like.read, bookmark.read, offline.access.
Ask the user for:
TWITTER_OAUTH2_CLIENT_ID(required)TWITTER_OAUTH2_CLIENT_SECRET(required)
Write them into .env (copy .env.example first if .env doesn't exist). On a binary install, tf auth writes .env.example when it is missing. Never echo secret values back. Then lock the file down:
chmod 600 .envVerify. Both keys are non-empty, and the mode is
600. This prints onlyset/missing, never the values:bashstat -c '%a' .env # macOS: stat -f '%OLp' .env awk -F= '$1=="TWITTER_OAUTH2_CLIENT_ID"{id=$2} $1=="TWITTER_OAUTH2_CLIENT_SECRET"{sec=$2} END{if(length(id)&&length(sec)) print "oauth keys: set"; else {print "oauth keys: missing"; exit 1}}' .env
Step 2 — Optional integrations (ask, offer to skip)
Ask, with a clear skip option. All of these can stay blank:
TF_LLM_PROVIDER+ a key — only if the user wants direct-API generation instead of the default Claude Code CLI backend. Defaulttf generatespawnsclaude -pand needs no key. Providers:anthropic(ANTHROPIC_API_KEY),openrouter(OPENROUTER_API_KEY),openai(OPENAI_API_KEY). OptionalTF_LLM_MODELoverrides the model.TAVILY_API_KEY— free at https://tavily.com. Enablestf search,tf enrich, andtf generate --timely. Recommended.NOTION_API_KEY— internal integration at https://www.notion.so/my-integrations. The user must share pages with the integration beforetf notioncan see them.SLACK_WEBHOOK_URL— https://api.slack.com/messaging/webhooks. If set, drafts can be pushed to Slack.
Human required. Pasting any of these keys. Skipping is a complete setup.
Verify.
.envis still mode600. Do not print the values.
Step 3 — Authorize and back-fill
Once .env has at least the two OAuth 2.0 values:
Checkout ( ./setup_x.sh is in this directory ):
NONINTERACTIVE=1 ./setup_x.shA human at the keyboard can run ./setup_x.sh instead and let it ask for anything still blank. It installs Bun, builds tf, then runs the OAuth flow and the first fetch.
Binary install (no setup_x.sh):
tf auth
tf fetch
tf flush || trueHuman required. A browser opens (or a URL is printed). The user logs in as the account they want to analyze and clicks Authorize app. On a headless machine, pass
--no-browserviatf auth --no-browserand have them open the printed URL. If the callback must be reachable from another machine, setOAUTH_REDIRECT_URIandOAUTH_BIND_HOSTand register that exact callback in the X app. The default bind is127.0.0.1.
The first flush marks everything already in the database as processed, so flush/pending.md starts at 0 new items. Later runs of tf refresh show only what's new.
Verify.
bashif [ -x ./tf ]; then TF=./tf; else TF=tf; fi test -s .tokens.json && echo "tokens: present" test -s tweets.db && echo "db: present" "$TF" analyze
tf analyzeshould print a handle and non-zero counts for the sources the account actually has.Has refresh token: trueshould have appeared in the auth output (the app needs a confidential client).
Step 4 — Fill the persona
setup_x.sh does not write persona/PERSONA.md. A binary install writes persona/PERSONA.template.md on tf auth or tf onboard when the file is missing. Create the persona one of these ways:
- Copy the template and edit it:
cp persona/PERSONA.template.md persona/PERSONA.md - Or run
tf onboardand pressfon the forge screen. That writespersona/PERSONA.mdfrom the four interview answers (what they want to be known for, a hot take, what they're building, three voice words), the account tags, and measured stats.
Make it real — this is what makes drafts sound like the user:
- Ask: their handle, the lane they want to be known for, voice rules (casing, length, tone), and any banned phrases.
- Or infer from data:
tf analyzesummarizes measured voice signals (lowercase-start rate, median length, top liked authors). Confirm the draft with the user before treating it as final.
tf generate still runs with no persona file: it infers from those measured signals. A filled-in persona/PERSONA.md is the voice source of truth.
Verify.
persona/PERSONA.mdexists, and it is not still the untouched template (@YOUR_HANDLEshould be gone)../tf analyzeexits 0.
Step 5 — (Optional) enrich article bookmarks
If TAVILY_API_KEY is set and you want full article text behind their bookmarks:
./tf enrichWithout that key the command errors. Skip the step when the key is absent.
Verify. The command exits 0, or you skipped it because
TAVILY_API_KEYis unset.
Step 6 — Hand off the loop prompt
Tell the user how to generate drafts from here. For a one-off batch: ./tf generate. In Claude Code, /tweet-factory runs the bundled skill, which calls tf for data and then drafts.
For a repeating Claude Code loop, pick based on Slack:
With Slack:
/loop 6h Spawn a subagent via the Agent tool with model "opus" to run /tweet-factory end-to-end, then post that run's drafts to Slack via ./tf notify drafts/$(date +%F).md "🧵 fresh tweet ideas". Reply with only a one-line summary. Keep going on this interval.Terminal only (no Slack):
/loop 6h Spawn a subagent via the Agent tool with model "opus" to run /tweet-factory end-to-end and show me the drafts. Keep going on this interval.Verify. The user has a command they can run (
./tf generate) and, if they wanted Slack,SLACK_WEBHOOK_URLis set. Do not run an infinite loop yourself unless they asked for one.
Reminders
- Single-host rule:
tf fetch(and any cron loop) must run on ONE machine only — the X refresh token rotates on every use; running it on two hosts invalidates the token. - Secrets never get committed:
.env,.tokens.json,tweets.db,persona/PERSONA.md,flush/*,drafts/*, andonboarding.jsonare gitignored. - Re-auth: if a run logs a 401, run
./tf authonce to mint a new token. - Scheduling:
tf refreshis the non-LLM refresh (safe to cron).tf cronalso runs enrich, so it needsTAVILY_API_KEYor it errors after the fetch.tf updatereplaces a release binary and is not the database refresh. See deploy and the Deploy section inREADME.md.
Scheduling on a server is the deploy and cron page.