# Set this up for me

Give an agent the [copy-paste prompt](agent-prompt.md). 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.

```prompt
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.md`](https://github.com/abhirupghosh/tweet-factory/blob/main/SETUP.md) when 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.sh` for a new machine, or `./setup_x.sh` when 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](https://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:

```bash
curl -fsSL https://tweetfactory.sh/install.sh | sh
mkdir -p ~/tweet-factory && cd ~/tweet-factory
tf --help
```

The 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 --help` exits 0 and prints `tweet-factory (tf)`. `command -v tf` is on `PATH`.

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:

```bash
./setup_x.sh --check
```

> **Verify.** 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:

```bash
chmod 600 .env
```

> **Verify.** Both keys are non-empty, and the mode is `600`. This prints only `set` / `missing`, never the values:
>
> ```bash
> stat -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. Default `tf generate` spawns `claude -p` and needs no key. Providers: `anthropic` (`ANTHROPIC_API_KEY`), `openrouter` (`OPENROUTER_API_KEY`), `openai` (`OPENAI_API_KEY`). Optional `TF_LLM_MODEL` overrides the model.
- `TAVILY_API_KEY` — free at https://tavily.com. Enables `tf search`, `tf enrich`, and `tf generate --timely`. Recommended.
- `NOTION_API_KEY` — internal integration at https://www.notion.so/my-integrations. The user must share pages with the integration before `tf notion` can 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.** `.env` is still mode `600`. 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 ):

```bash
NONINTERACTIVE=1 ./setup_x.sh
```

A 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`):

```bash
tf auth
tf fetch
tf flush || true
```

> **Human 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-browser` via `tf auth --no-browser` and have them open the printed URL. If the callback must be reachable from another machine, set `OAUTH_REDIRECT_URI` and `OAUTH_BIND_HOST` and register that exact callback in the X app. The default bind is `127.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.**
>
> ```bash
> if [ -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 analyze` should print a handle and non-zero counts for the sources the account actually has. `Has refresh token: true` should 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 onboard` and press `f` on the forge screen. That writes `persona/PERSONA.md` from 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 analyze` summarizes 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.md` exists, and it is not still the untouched template (`@YOUR_HANDLE` should be gone). `./tf analyze` exits 0.

### Step 5 — (Optional) enrich article bookmarks

If `TAVILY_API_KEY` is set and you want full article text behind their bookmarks:

```bash
./tf enrich
```

Without 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_KEY` is 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_URL` is 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/*`, and `onboarding.json` are gitignored.
- **Re-auth:** if a run logs a 401, run `./tf auth` once to mint a new token.
- **Scheduling:** `tf refresh` is the non-LLM refresh (safe to cron). `tf cron` also runs enrich, so it needs `TAVILY_API_KEY` or it errors after the fetch. `tf update` replaces a release binary and is not the database refresh. See [deploy](https://docs.tweetfactory.sh/deploy/) and the Deploy section in [`README.md`](https://github.com/abhirupghosh/tweet-factory/blob/main/README.md).
Scheduling on a server is the [deploy and cron](deploy.md) page.
