Agent-native X (Twitter) posting as one MCP server for Claude Code / Cursor / Codex / Windsurf. Your X token never touches the agent; every post clears a server-side guardrail before it ships.
Dry-run a draft through the casserole guardrail WITHOUT posting — see whether it would pass, be held, or be blocked, and why, so you can fix it before it ships. You draft; capx just lints.
Check a draft against the casserole guardrail **before** posting or scheduling it — a linter, not a send.
`preview` runs the exact same admission + guardrail a real post would, but sends nothing, records nothing, and
never touches the token. Use it to catch a block or a hold early and fix the wording. Draft / intent: "$ARGUMENTS"
Follow these steps:
1. **Get the draft.** Take the text from "$ARGUMENTS", or the post you just wrote with the user. If there are
several (a thread), preview them one at a time.
2. **Preview it.** Call `preview` with `{ text }` (add `aiGenerated` only if the user wants it labelled
AI-assisted — their choice, default off; it doesn't change a text verdict). Read the result back plainly:
- **Would post ✓** — it passes; you can `post_now` or `create_loop` it as-is.
- **Would be held** — casserole would hold it for human review; show the reasons.
- **Would be blocked / rewritten** — it would not go as-is; show exactly why.
3. **Fix the content, not the guard.** If it wouldn't pass, work the *words* with the user — be more specific,
drop vague hype or engagement-bait, cut hashtag stuffing, de-duplicate against a near-identical past post.
Then preview again. Never try to route around the guard: `preview` runs the same rules as the real gate at
send, so a draft that previews clean is exactly the one that ships.
4. **Hand off.** Once it previews clean, offer to post it now (`post_now`) or queue it (`create_loop`), or hand
back to whichever skill asked for the review.
**Hard rules:** `preview` never posts and is never a way to bypass the guard — it just shows the verdict early.
The fix for a block is always the content. capx writes nothing; you draft, casserole decides.Turn a bug you just fixed — the session's debugging or a closed issue — into one specific 'we fixed X' post. You draft from the real diff; capx never writes, it ships it guarded.
Turn a bug the developer just closed into one honest "we fixed X" X post. **You write it** from the real
debugging — the failing test that's now green, the root cause you tracked down, the diff that shipped. capx
never generates content; it only ships (the post passes the casserole guardrail at send). Which fix, if the
user named one: "$ARGUMENTS"
Follow these steps:
1. **Find the real fix.** Default to the debugging you just did *this session* — the bug you closed, the
root cause you found, the test now passing. If "$ARGUMENTS" points at something specific (an issue, PR,
or commit), read it: `git show <sha>`, `git log --oneline`, and `gh issue view` / `gh pr view` if
available. You need three *true* things: the **symptom** (what was broken, as someone actually hit it),
the **cause** (why), and the **fix** (what changed). If you can't ground all three in the real work,
ask — do not invent a bug or a fix.
2. **Check it's worth a post.** A user-facing bug, a nasty crash, data that was silently wrong, a hard root
cause — those are worth telling people about. A typo fix, an internal refactor nobody hit, a lint tweak —
skip it. If the fix isn't interesting to anyone outside the repo, say so and stop.
3. **Draft ONE post — symptom → fix.** The shape that lands is "*here's what was broken → here's what caused
it → it's fixed now*," concrete throughout. Make it:
- **Specific about the symptom** — name what actually broke and who felt it. "Loops silently dropped the
last post when the queue hit exactly one left — fixed" beats "squashed a nasty bug!". (The guardrail
blocks vague hype, engagement-bait, and hashtag stuffing — specificity is what gets through *and* what
people read.)
- **Grounded in the diff** — do NOT overstate. If you don't know how many users hit it or how long it was
live, don't put a number on it. No severity theater, no invented impact.
- **In the user's voice** — if a voice profile is available (`voice-match`), match it; otherwise
mirror their recent posts, or ask.
- **Within X's length** — ≤ 280 weighted chars; if the story genuinely needs the cause spelled out, make
it a short thread and say so.
4. **Show it and get approval.** Present the draft. Let the user edit, sharpen, or kill it. Never post
without showing them first.
5. **Ship it.** Call `post_now` with `{ text, idempotencyKey }` — pass a stable `idempotencyKey` so a retry
can't double-post the same fix. This sends once, now (it's not scheduled — if the user wants a batch or a
queue, point them at `build-in-public`).
**When a post comes back `blocked` or `held`,** show the reason and fix the *content* — get more specific,
drop the bait, cut the duplicate. Never try to route around the guard.
**Hard rules:** you draft, casserole decides. And never fabricate — a "we fixed X" about a bug that wasn't
real, or a fix that didn't actually ship, is the fastest way to burn trust. Post it only when it's true.A loop is running low — refill it from your newest commits. list_loops finds the near-empty one; you draft fresh grounded posts; capx ships them guarded via top_up_loop.
A scheduled loop is draining faster than you've been refilling it. Top it up from the work you've **actually
shipped since it was last filled** — capx never generates content; you write the posts from the real git
history, and each one passes the casserole guardrail at send. This is the back half of `build-in-public`:
together they turn a queue into a self-refilling engine. User's extra intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Find the loop that's low.** Call `list_loops`. Each loop reports how many posts remain and its schedule.
Pick the one running low — the loop named/id'd in "$ARGUMENTS" if given, otherwise the loop with the
fewest posts left (a loop at 0–2 remaining is the one to refill). If nothing is actually low, say so and
stop — don't manufacture posts to pad a healthy queue.
2. **Find where you left off.** You're refilling from *new* work only, not re-posting old news. Establish the
waterline: check what that loop already has queued/sent so you don't repeat it, then read the commits since
then — `git log --oneline -30` (or the range in "$ARGUMENTS"), skimming diffs of the substantive ones.
**Skip the noise** — merges, `wip`, `fix typo`, formatting, dep bumps. You want the shippable, interesting
changes: new features, real fixes, milestones, hard problems solved.
3. **Check there's genuinely something new to say.** If the only commits since the last fill are noise, don't
force it — tell the user the loop is low but there's no fresh, postable work yet, and let them decide
(write something manually, pause the loop, or come back after more commits). A refill with nothing real
behind it is exactly what the guard blocks and what readers tune out.
4. **Draft the top-up posts.** Cluster related new commits into a handful of stories and write ONE post each
that is:
- **Concrete and specific** — name the actual thing you shipped and why it matters. "Fixed X so Y now
works" beats "more progress!". (The guard blocks vague hype, engagement-bait, hashtag-stuffing, and
duplicates — specificity is what gets through *and* what people read.)
- **Grounded in the commits** — don't invent features, numbers, or outcomes that aren't real. Unsure a
claim is true? Leave it out or ask.
- **Not a repeat** — distinct from what this loop already queued or sent in step 2.
- **In the user's voice** — match the `voice-match` profile if available, else mirror the tone of the
posts already in this loop.
- **Within X's length** — one post ≤ 280 weighted chars.
5. **Show the drafts and get approval.** Present the new posts as a numbered list, note how many the loop had
left and how many you're adding. Let the user edit, cut, or reorder before anything is queued. Never top
up without showing them first.
6. **Top it up.** Call `top_up_loop` with `{ id, posts: [...] }` for the loop from step 1. The new posts
append to the existing queue and fire on the loop's current schedule — you're extending the runway, not
changing the cadence.
7. **Confirm.** Call `list_loops` again and show the refilled loop with its new remaining count and schedule,
so the user can see the runway is restored.
**Hard rules:** you draft, casserole decides — if a post comes back `blocked` or `held`, show the reason and
fix the *content* (be more specific, drop the bait); never route around the guard. Never fabricate progress to
fill a gap — an empty loop is better than a false one. And refill from *new* work only, so the timeline stays
honest and non-repetitive.
**Make it automatic:** run this whenever a loop pauses for being low, or on your own cadence (e.g. after a big
push). Paired with `build-in-public` creating the loop, `gap-alert` keeps it alive — your normal commits
become the fuel that never lets the queue hit zero.Rewrite the FIRST line of a draft for stopping power without tipping into bait. You draft 2-3 openers grounded in the real content; casserole still gates at send.
The first line is the only line most people read before they decide to keep going. Sharpen it **without lying
to earn the scroll-stop**. capx never writes this for you — **you** draft the openers from the draft's real
content, and casserole still checks the winner at send. The draft to work on: "$ARGUMENTS"
Follow these steps:
1. **Find the hook and the substance.** Split the draft into its **first line** (the hook you're rewriting)
and the **body** (what the post is actually about). List the concrete, true things the body already
contains — the real number, the specific thing built, the actual before/after, the surprising detail. Your
new openers can only use what's really in there. If the body has no specific hook-worthy fact, say so —
the fix is a better *post*, not a louder first line.
2. **Diagnose the current opener.** Name why it's weak in one line: is it vague ("big update today"),
throat-clearing ("So I've been thinking…"), hype with no substance, or **engagement-bait** ("you won't
believe" / "RT if…" / a manufactured cliffhanger)? That diagnosis tells you what to trade *in*.
3. **Draft 2-3 alternative openers.** Each must be:
- **Grounded** — pulled straight from the body's real facts. Do NOT invent a number, a result, or a stake
to make the line hit harder. A hook that promises something the body doesn't deliver is a bait hook.
- **Specific over loud** — lead with the concrete detail. "Cut cold-start from 4s to 400ms" stops more
scrolls than "HUGE performance win" — and it's what actually gets *through* the guard and read.
- **Load-bearing** — the opener should make a real claim or state a real detail, not just tease one. If a
line only works by withholding, it's bait; rewrite it to *state* the interesting thing.
- **Structurally varied** — give the user genuinely different angles (the number, the tension, the plain
statement of what changed), not three rewordings of the same line.
4. **Show the options and keep the body intact.** Present the openers as a numbered list next to the original
first line so the user can compare. Leave the rest of the post unchanged unless a new hook makes the old
second line redundant — if so, flag that one-line trim. Let the user pick, edit, or blend before anything
ships.
5. **Ship the winner only on request.** If the user picks one and asks to post, send the full post (new hook
+ body) with `post_now { text }`. Otherwise just hand back the rewritten draft — this skill's job is the
opener, not the schedule.
**Hard rules:** you sharpen, **casserole decides** — a baity or hype-only hook comes back `blocked` or
`held`; when it does, show the reason and fix the *content* (make the line more specific, drop the tease),
never reword it just to slip past the guard. And never manufacture a stake or a number the body can't back
up — a hook is only as honest as the post beneath it.Direct the user's OWN image-gen tool to a high-quality, on-brand image for a post: pick the model, engineer the prompt, generate, then upload + attach. capx runs no model — it orchestrates yours and ships the post guarded (caption only).
Get a genuinely good image onto a post by driving the user's **own** connected image tool (higgsfield / fal /
kling / whatever they wired via `media-connect`). **capx generates nothing** — you use their tool to make the
image, then `upload_media` streams the file to the chokepoint and `post_now` attaches it. casserole guards the
**caption**; the image itself is not moderated, so honesty is on you and the user. The brief: "$ARGUMENTS"
Follow these steps:
1. **Check a media tool is connected.** If there's no image-generation tool in this session, stop and point
the user at **`media-connect`** first — capx has nothing to generate with on its own.
2. **Pin the job.** From "$ARGUMENTS" and the post, get concrete: what must the image show, the mood, the
aspect ratio (X favors 16:9 or 1:1 in-feed), any text/logo, and must-not-haves. If it's unclear, ask one
sharp question rather than guessing.
3. **Pick the model.** Use **`model-guide`** to choose the right image model for the job (photoreal vs
illustration vs product shot vs diagram). Different tools/models win at different things — pick deliberately.
4. **Engineer the prompt.** Use **`prompt-engine`** to turn the brief into a precise, model-shaped prompt
(subject, style, lighting, composition, negatives, aspect ratio). Show the user the prompt before spending
a generation.
5. **Generate with THEIR tool.** Call the user's connected image tool with that prompt. If the result misses,
iterate the prompt (not the guard) — adjust and regenerate. Land on one image the user approves.
6. **Disclose if it's AI-made.** These images are AI-generated. Per X policy and the user's honesty, offer to
note it — disclose in the caption and/or set `aiGenerated` on the post per the user's choice (Option C:
their call). capx carries the label; it doesn't decide it for them.
7. **Upload and attach.** Call `upload_media` with the local file path → it returns a media id. Then draft/keep
the caption and call `post_now` with `{ text, mediaIds: [thatId] }` (a distinct `idempotencyKey`). Show the
user the caption first; casserole gates the caption text at send.
8. **Confirm.** Report the posted result (or the guardrail verdict on the caption, and fix the *caption* if
held/blocked — never the guard).
**Hard rules:** capx never makes the image — the user's tool does; capx uploads + ships. The caption is
guarded; the media is the user's own and un-inspected, so keep it honest and disclose AI generation.Turn a README + release into a launch thread: hook, problem, solution, proof, CTA. You draft every part from the repo's reality; capx never writes — it ships each guarded. Heavy approval before live.
Turn a project's README and its first release into a full launch thread — the one-shot public moment that
says *this exists, and here's why it matters.* **You (the agent) write every part** from what the repo
actually is; capx never generates content — it only ships each part, guarded by casserole at send. A launch
amplifies, so the bar for being *true* is higher here than anywhere. User's extra intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Connect and confirm who's launching.** Run `whoami`. If no account is connected, stop and point the
user at the **connect** skill — a launch has to go out under the right handle. Show the handle it will
post from and confirm it's the one they want to launch on.
2. **Read the repo's reality.** Pull the launch facts from the source, not your imagination: the `README` (the
top "what is this / why / how" section), and the first release — `git tag`, `git show` on the tag, release
notes or `CHANGELOG`, and `package.json` name + version + description. If "$ARGUMENTS" names a file, tag, or
angle, use it. **If there's no release yet**, say so and ask whether to launch off the README alone or wait
for the tag.
3. **Extract the load-bearing facts and check them back.** List for the user: the one-line pitch, the concrete
problem it solves, the 2–4 things it *actually* does today, any real proof (a benchmark, a test count, a
working demo — only if it exists in the repo), the canonical link (repo / docs / landing), the real
install-or-try command, and the version. **Flag anything you can't verify from the repo** — unverifiable
claims do not go in the thread.
4. **Draft the whole thread as an arc** — write every part before posting any:
- **1/ Hook** — the single line that earns the scroll-stop: the problem or the promise, stated concretely.
No "excited to announce" / "🚀 big news" — the guardrail eats vague hype, and so do readers.
- **2/ Problem** — why this was worth building; the real pain, named.
- **3/ Solution** — what it is, plus the 2–4 concrete capabilities from step 3.
- **4/ Proof / how** — a real snippet, the actual command, or a true number. Grounded, never invented.
- **5/ CTA** — the canonical link and one clear ask (try it, star it, read the docs).
Each part: ≤ 280 weighted chars, concrete and specific, **no line repeated across parts** (the guard blocks
duplicates), no hashtag stuffing. Match the user's voice — use the **voice-match** skill if available,
else mirror their recent posts. Number the parts `1/n … n/n`.
5. **Heavy approval — a launch is one-shot.** Present the full thread as a numbered list with the part count
and the CTA link visible. Explicitly invite edits to the **hook** and the **CTA** — the two parts that carry
the launch. **Post nothing until the user gives an explicit go**, and if they edit, read the final thread
back once before posting.
6. **Post the parts in order, chained.** For each part *in sequence* call `post_now` with its `text`, a
distinct `idempotencyKey` per part (so a retry can't double-post a part), and `aiGenerated` set to the
user's labelling choice (default off — the user decides). Send `1/n` first, then pass its returned
`platformPostId` as `2/n`'s `inReplyToId`, and so on, so the parts land as a native connected reply-thread —
each part still passes casserole individually at send.
7. **If the guard stops a part, stop the thread.** On `blocked` or `held`, do NOT keep posting the rest on top
of a broken part. Show the reason, fix *that part's content* (be more specific, drop the hype/bait, remove
any duplicate line), get a quick re-approval, then resume posting from where it stopped. Never route around
the guard.
8. **Confirm the launch.** Report each part's posted status and link, in order, and confirm the thread is live
end-to-end. If any part is still blocked, say which one and what it needs.
**Hard rules:** you draft, casserole decides — never route around a block, fix the content. **Never fabricate**
a feature, number, or outcome the repo doesn't back — a launch that overpromises is exactly the one that gets
caught, and the hook is a claim too: it has to be true. **Connect first** via **connect**; a launch only
goes out under a real, connected account.Guide the user to connect their OWN media-generation MCP servers (higgsfield, fal, kling, ...) to their agent, so capx can attach the images/videos those tools produce. capx generates nothing — it orchestrates your tools and ships the result.
Help the user wire up their **own** image/video generation into the agent, so the capx director skills can
drive it and then attach the output to a post. **capx runs no models and integrates no provider** — you use
whatever media tools *you* connect; capx only uploads the finished asset and ships the post (the caption is
guarded by casserole; the media is not). User's steer, if any: "$ARGUMENTS"
Follow these steps:
1. **Explain the split, briefly.** capx = the guarded pipe to X. Media *generation* is a separate tool the
user brings — an MCP server like **higgsfield**, **fal**, or **kling** (or any image/video MCP). Once it's
connected to the same agent, the director skills (`image-director`, `video-director`) can call it, and
`upload_media` attaches the result. Nothing about the user's media tool touches capx's servers.
2. **Point at the connection method for their agent.** Connecting an MCP server is per-agent:
- **Claude Code** — add it to `.mcp.json` (or `claude mcp add`); it appears as tools next to capx.
- **Cursor** — add it under MCP settings / `.cursor/mcp.json`.
- **Codex / Windsurf / others** — add it to that agent's MCP config file.
Have the user follow their provider's install snippet (higgsfield/fal/kling each publish one). Don't paste
the user's API keys yourself — they go in that tool's own config, on the user's machine.
3. **Confirm it's live.** Ask the user to confirm the media tool's tools now show up in this session (e.g. an
image-generate or video-generate tool). If they don't, it's a config/restart issue on the media tool's
side — point them back to that provider's setup, not capx.
4. **Hand off.** Once a media tool is connected, point the user at **`image-director`** or **`video-director`**
to actually make something, and **`model-guide`** to choose which model fits the job.
**Hard rules:** capx never generates media and never sees the user's media-tool keys. This skill only helps
connect *their* tools; the making happens in those tools, and capx attaches + ships the result.Reference: which image/video model to reach for by job (photoreal, illustration, product shot, short clip) and the tradeoffs, so you choose well BEFORE generating. capx integrates no provider — you generate with your own connected tools.
Help pick the **right model for the job** before spending a generation. capx integrates no provider and runs
no models — this is guidance for choosing among the tools the user connected (via `media-connect`). Match the
job to a model class; the exact best model shifts fast, so reason from the job, and defer to what the user's
connected tools actually offer. The job: "$ARGUMENTS"
**How to choose — match the job to what matters:**
| Job | What to optimize for | Reach for |
|---|---|---|
| Photoreal scene / product shot | realism, correct materials, few artifacts | a top photoreal image model; add a clean negative list |
| Illustration / brand graphic | style control, consistency | a model strong at stylized/illustrative output |
| Diagram / text-in-image | legible text, layout | a model that handles typography (most struggle — often better done in a real design tool) |
| Logo / precise graphic | exactness | usually NOT generative — use a vector/design tool |
| Short social clip | motion coherence, a strong first second | a current short-video model; keep it brief |
| Talking / complex motion | temporal consistency, no morphing | the most coherent video model the user has; keep duration low |
**Guidance to give:**
1. **Name the job precisely** — "photoreal 1:1 product shot on a plain background" beats "a nice image." The
sharper the job, the clearer the model choice.
2. **Prefer the user's connected tools.** Recommend from what they actually have wired (higgsfield / fal /
kling / …); if their tool exposes several models, point at the one whose strength matches the row above.
3. **Call out the known-hard cases.** Text-in-image, logos, hands, and long coherent video are where
generative models still fail most — flag these and suggest a non-generative route when that's the honest call.
4. **Then hand off.** Once a model is chosen, go to **`prompt-engine`** for the prompt and **`image-director`**
/ **`video-director`** to generate and attach.
**Hard rules:** this is reference only — capx generates nothing and endorses no single vendor. Choose from the
user's own connected tools, and be honest when a job is better done outside a generative model.Turn a vague creative ask into a precise, model-optimized prompt for image or video generation — the quality engine the director skills call. It produces a PROMPT; capx never generates the media itself.
Turn a fuzzy ask ("make it look cool") into a **specific, model-shaped prompt** that the user's own image or
video tool can actually execute well. This skill **only writes the prompt** — the user's connected media tool
generates from it; capx generates nothing. It's the shared quality engine `image-director` and `video-director`
call. The idea + medium: "$ARGUMENTS"
Follow these steps:
1. **Read the intent and the medium.** From "$ARGUMENTS": what's the subject, what's it *for* (a launch, a
TIL, a product), image or video, and the target model if named. If the medium isn't clear, ask.
2. **Extract the visual spec.** Name the levers explicitly — vague prompts get vague output:
- **Subject** — the concrete thing, and what it's doing.
- **Composition & framing** — shot type, angle, focal point, aspect ratio (X: 16:9 / 1:1 / 9:16).
- **Style** — photoreal / illustration / 3D / diagram; reference an era or medium, not a living artist.
- **Light & mood** — lighting direction, palette, tone.
- **For video also:** the opening beat (first second), camera motion, pacing, and duration.
- **Negatives** — what to exclude (text artifacts, extra fingers, watermarks, clutter).
3. **Write the prompt to the model's shape.** Different models want different phrasing (natural-language vs
tag-weighted vs structured). If the target model is known, format for it; otherwise give a clean
natural-language prompt plus a short negative list. Keep it tight — precise beats long.
4. **Show it and iterate.** Present the prompt to the user (and the director skill) before a generation is
spent. Offer one or two variants if the direction is genuinely ambiguous. After a generation, tune the
prompt from what actually came back — that feedback loop is the whole point.
**Hard rules:** this skill outputs a prompt only — capx never generates the media. Keep prompts honest to what
the user wants to depict, and avoid mimicking a specific living artist's style by name.First-time walkthrough — check the connection, link X if needed, then help the user post their first thing and read the guardrail verdict. Warm, one step at a time.
Walk a brand-new user from zero to their first posted tweet through capx — one small step at a time, warm and
unhurried. **You (the agent) write any post text; capx never generates content** — it only ships, and every send
passes the casserole guardrail. What they told you they're here to do, if anything: "$ARGUMENTS"
Do these in order, and **pause after each step** — don't run ahead. One thing at a time.
1. **Say hi and check the connection.** Greet them in a line, then call `whoami` to see where they stand.
- If it returns their X handle, they're already connected → skip to step 3.
- If it errors or says "not connected," that's expected for a first run → go to step 2. Don't treat it as
something broken.
2. **Connect their X account.** Tell them capx posts from *their own* X account — no shared database, they stay in
control — and that connecting is a quick browser sign-in. Then hand off to the **`connect`** skill to run
the OAuth flow. When it's done, call `whoami` again to confirm the handle before moving on. Don't paste tokens
or ask for their password yourself — the connect flow does it in the browser.
3. **Find out what to post.** Ask what they'd like to say — a shipped feature, something they learned, what they're
building today. If "$ARGUMENTS" already tells you, use it. Keep the ask light; one good post beats a plan.
4. **Draft it with them.** Write ONE post from what they told you:
- **Concrete and specific** — name the real thing. "Shipped dark mode so late-night users stop getting flashed"
beats "excited to share an update!". Specificity is both what reads well *and* what clears the guard (it
blocks vague hype, engagement-bait, hashtag-stuffing, and duplicates).
- **True** — only what they actually did. Don't invent features, numbers, or outcomes. If unsure, ask or leave
it out.
- **Within X's length** — ≤ 280 weighted chars.
Show the draft and let them edit or rewrite. Never send without a clear yes.
5. **Post it.** On their go-ahead, call `post_now` with `{ text }`. Ask if they'd like the post labelled
AI-assisted and set `aiGenerated` to their choice (default off — it's their call). Then explain the verdict in plain terms:
- **Sent** → celebrate briefly and show the result / link.
- **Held or blocked** → this is the guardrail doing its job, not an error. Show the reason it gave, then fix the
**content** together — make it more specific, drop the bait, de-duplicate — and try again. Never route around
the guard or resend the same text hoping it passes.
6. **Point at what's next.** One or two lines: they can post anytime by just asking, or set up a recurring schedule
(a "loop," which needs a verified X account 30+ days old) with the **`build-in-public`** or **`loop`**
skills. Leave them somewhere they can take the next step on their own.
**The one rule to hold:** you draft, casserole decides. If a post comes back held or blocked, the fix is always the
words — never a workaround. And keep it real: capx ships what's true, nothing invented.Reshape a draft you already wrote — long post to a thread, a thread to one tight post, or fit it to X's norms. You reshape; the meaning stays; capx ships it guarded.
Reshape a draft the user already wrote into a better shape for X — a long post into a thread, a thread into
one tight post, or any draft trimmed to X's length and format norms. **You do the reshaping** by moving the
user's own words around; capx never generates content and this skill never rewrites the substance. The one
rule under everything: **meaning in must equal meaning out.** Each post still passes the casserole guardrail
at send. What the user gave you: "$ARGUMENTS"
Follow these steps:
1. **Get the draft and the target shape.** From "$ARGUMENTS", take the draft — the pasted text, or read the
file / link they pointed at. Identify the target shape: long post → thread, thread → single post, or "fit
to X." If they didn't say, infer the obvious move from length (a 600-char block wants to be a thread; a
5-tweet thread they want tightened wants to be one post) and **confirm before reshaping**.
2. **Read what's actually there.** Understand the point, the claims, and the voice. List the **load-bearing
bits you must not lose** — every concrete fact, number, name, and specific. You're rearranging and
trimming, not rewriting; if you can't keep a specific in the new shape, that's a conversation, not a cut
you make silently.
3. **Reshape.** Do the transform the shape calls for:
- **Long → thread:** split at natural seams, one idea per tweet. The lead tweet earns the read with the
sharpest concrete point — *not* "a thread 🧵" bait. Each tweet stands on its own and is ≤ 280 weighted
chars. Number them 1/n.
- **Thread → single:** keep the single strongest idea, cut the throat-clearing and the connective filler
between tweets, land it in ≤ 280 weighted chars.
- **Fit to X:** trim to ≤ 280 weighted, unpack link/hashtag clutter, and strip the vague hype and
engagement-bait — the guard blocks those anyway, so this is what actually gets it through.
Across all three: **do not add a feature, number, or claim the draft didn't make**, and don't sand off the
specifics — specificity is what passes the guard *and* what people read. Keep the user's voice.
4. **Show before / after.** Present the original and the reshaped version clearly labeled (a numbered list for
a thread). Call out exactly what you changed and why — what you split, cut, or tightened — and confirm in
one line that the meaning is unchanged. Never hand back or post a version they haven't seen.
5. **Get approval or edits.** Let the user edit, reorder, cut, or reject. If they want a different shape, go
back to step 3. Nothing ships until they say so.
6. **Hand back or post.** Default: hand the approved text back for them to use.
- **Single post, live now:** call `post_now { text }` — casserole guards it at send.
- **Thread, live as a reply-chain:** hand back the numbered tweets for the user to publish (capx ships
single posts, not reply-chains — don't pretend otherwise). If they'd rather the tweets go out as
standalone posts dripped over days, schedule them with `create_loop { time, daysOfWeek (0=Sun..6=Sat),
posts: [...], timezone }` — note a loop needs a **verified X account at least 30 days old**; if it's
rejected for that, explain the requirement instead of retrying.
- Not connected yet? Run the `connect` skill first, then continue.
7. **If the guard holds or blocks it.** Show the exact reason and **fix the content** — tighten it, drop the
bait, cut the duplicate, restore a specific — then send again. Never route around the guard, and never
re-send the same text to dodge a duplicate flag.
**Hard rules:** you reshape, you don't rewrite — the meaning that goes in is the meaning that comes out, and
you never invent a claim the draft didn't make. casserole decides at send; when it says no, fix the content,
never the guard.Turn a blog post, README, or long doc (a file path or URL) into an X thread. You (the agent) read it and write the posts; capx never generates — it ships them guarded. Post now or queue.
Take one long piece — a blog post, README, changelog, or design doc — and turn it into an X thread. **You
read the source and write every post** from what it actually says; capx never generates content, it only
ships (each post clears the casserole guardrail at send). The thing to repurpose: "$ARGUMENTS"
Follow these steps:
1. **Read the actual source.** "$ARGUMENTS" is a file path or a URL. If it's a path, open the file; if it's a
URL, fetch it with your web tool. Work from the **real text**, not the title or your memory of the topic.
If you can't reach a URL, say so and ask the user to paste it or give a local path — don't guess at the
contents.
2. **Pull the load-bearing points.** Find the **3–6 things that actually carry the piece** — the core claim,
the key decision and why, the concrete result, the counterintuitive bit, the how. Skip the intro throat-
clearing, SEO filler, and anything the source doesn't really commit to. If a point isn't supported by the
text in front of you, it doesn't go in the thread.
3. **Shape the thread — hook + one post per point + close.**
- **Hook (post 1):** lead with the single sharpest, most specific thing the reader gets — the result or
the surprising claim. Not "🧵 a thread on X" and not vague hype; the guardrail blocks bait, and it reads
as filler anyway. Make post 1 earn the scroll.
- **Body (one post per point):** each post carries exactly one point, concrete and self-contained, in the
source's own facts. Number them (`2/`, `3/`, …) so the sequence reads as a thread.
- **Close (last post):** the takeaway, and — if the source is public — link back to the full piece.
- **Grounded:** no invented features, numbers, or outcomes. If the doc says "cut p95 by ~30%," say that;
don't round it into a bigger story. When unsure a claim is true to the source, cut it or ask.
- **Within X's length:** each post ≤ 280 weighted chars.
- **Voice:** if a voice profile exists (the `voice-match` skill), match it; else mirror the user's
recent posts, or ask.
4. **Show the whole thread and get approval.** Present it as a numbered list, in order, so the user sees the
arc. Let them edit, cut, reorder, or retitle the hook before anything ships. Never post without showing it.
5. **Pick the delivery — thread now, or drip queue.** Ask which the user wants (or use what "$ARGUMENTS"
already said):
- **Post the thread now** → call `post_now` once per post, **in order, first post first**. Ask the user
whether to label the thread AI-assisted and pass `aiGenerated` per their choice (default off — it's their
call); pass a distinct `idempotencyKey` per post so a retry never double-ships. **Chain it into a native
thread:** post 1 first, then pass each post's returned `platformPostId` as the next call's `inReplyToId`,
so the posts connect as a real reply-thread (keep the numbering too).
- **Drip it as a queue** → call `create_loop` with `{ time, daysOfWeek (0=Sun..6=Sat), posts: [...],
timezone }` to release the points one-per-fire over a schedule instead of all at once. A loop needs a
**verified X account at least 30 days old** — if create_loop rejects it for that, explain the requirement
rather than retrying. Then `list_loops` to confirm.
6. **When a post comes back `blocked` or `held`, fix the content.** Casserole decides at send. Show the user
the exact reason, then make that post more specific / drop the bait / de-duplicate it and resend — never
try to route around the guard.
7. **Confirm.** Report what shipped: the posted thread (in order), or the created loop with its schedule and
queued posts.
**Hard rules:** you write, casserole ships. Repurposing is compression, not invention — the thread must say
only what the source actually says. If the piece won't yield 3 solid points, tell the user it's better as a
single post than a padded thread.Stand up your OWN capx chokepoint — the self-hostable image that holds the token vault and runs the guard at send. Walks the real needs from the repo; never fabricates commands.
Walk the user through standing up their **own** capx chokepoint — the one self-hostable image that holds the
KMS-encrypted token vault and runs the casserole guard server-side at every send. This is a **guide**: you
read the repo and point at real files. **Do not invent commands or env vars** — everything below traces to a
file in this repo; if something isn't there, say so instead of guessing. User's host/intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Read the real self-host story first.** Open `docs/P1-CHOKEPOINT.md` §8 (self-host, honest) and §1
(architecture in one picture), plus `services/chokepoint/README.md`. These are ground truth. The honest
claim is **"one code path, one mode flag — but N required secrets and a public HTTPS domain,"** *not*
"compose up and you're done." Hosted-capx and any self-hoster run the **identical** image; the only
difference is configuration. Don't promise the user a one-liner.
2. **Confirm the things they must bring.** Self-host = **BYO-only** (they pay X directly; no capx secret, no
MoR, no counter). They need: (a) a host that runs the image (`services/chokepoint/Dockerfile`, or
`node --experimental-strip-types services/chokepoint/src/serve.ts` — `serve.ts` honors `$PORT`, so
Railway/Render/Fly/Heroku route to it with no port config); (b) **one Postgres** — `VAULT_DB_URL` (no Redis
in P1); (c) a **KMS key or local key** — `KMS_KEY_ID`; (d) their **own X app**; (e) a **public HTTPS URL**
for `OAUTH_CALLBACK_URL` (real domain + TLS, or a tunnel). If they cannot provide a public HTTPS callback,
**stop here and say so** — the hosted-callback OAuth flow physically cannot complete without one.
3. **Register their own X app.** At developer.x.com → Project + App → enable **OAuth 2.0**, permissions
**Read and write**, scopes `tweet.read tweet.write users.read offline.access`. The callback/redirect URL
registered there **must equal** the `OAUTH_CALLBACK_URL` they set on the server. For BYO public PKCE they
need only the public **client id** — no confidential secret. (The `X_CLIENT_ID`/`X_CLIENT_SECRET` *pair* in
`serve.ts` is the HOSTED lane-B case; self-host stays BYO, so leave the **secret** unset.)
4. **Set the server env.** These are the required server vars from `packages/config/src/index.ts`
(`serverEnvSchema`) — cite them exactly, don't paraphrase names: `CAPX_DEPLOY_MODE=self-host` (disables lane
B as config, never a code fork), `VAULT_DB_URL`, `KMS_KEY_ID` (secret), `SESSION_SIGNING_KEY` (secret),
`ADMIN_API_KEY` (secret — gates `POST /admin/revoke`), and `OAUTH_CALLBACK_URL` (required, **https-only**: a
non-https URL is rejected at boot). `CHOKEPOINT_PORT` defaults to `4477`. Generate the three key/secret
values as long random strings and **never commit them**. Leave `X_CLIENT_SECRET` and `MOR_WEBHOOK_SECRET`
unset for BYO self-host; optionally set `X_CLIENT_ID` to their own **public** id as the BYO default.
5. **Seed the allowlist.** Admission is a hashed-email allowlist — see the `allowlist(email_hash)` table in
`services/chokepoint/migrations/001_init.sql`. An empty allowlist **denies everyone** by design (fail-closed),
so nothing connects until their own email hash is seeded in. Migrations run automatically on boot (`serve.ts`
calls `runMigrations`); follow the repo's admission seeding path — don't hand-write SQL you can't cite.
6. **Boot it.** From `services/chokepoint`, `docker compose up` brings up worker + Postgres (Compose project
`capx-chokepoint`, host Postgres on **5442** — confirm it's free first). On a PaaS, deploy the `Dockerfile`
and set the env vars in the dashboard. `serve.ts` opens Postgres, runs migrations, wires the real X
endpoints, and logs `capx-chokepoint listening on :<port>`. Verify it's up by hitting `GET /healthz`.
7. **Point the agent client at THEIR instance.** The only client-side change (see `clientEnvSchema` in the same
config file): set **`CAPX_CHOKEPOINT_URL`** to their chokepoint's public base URL — this is what makes the
capx MCP tools talk to their instance instead of hosted capx. Keep `CAPX_LANE=byo`, and set the client
`X_CLIENT_ID` to their own public id if they didn't set a server default. **No secret ever goes in the
client** — it holds only a short-TTL session handle + non-secret pointers.
8. **Connect and verify end-to-end.** Run the `connect` skill against the new instance, then call
`whoami` — it should report `connected` on their own account through their own chokepoint. Have them do one
`post_now` and confirm it publishes. **The guard still runs:** casserole ships inside the image and gates
every send server-side on their instance exactly as on hosted — self-host does not remove the gate. What it
*does* change (be honest, per `docs/P1-CHOKEPOINT.md` §9): a weak local `KMS_KEY_ID` lowers the bar for
**their own** tokens only.
**Hard rules:** reference the repo, never fabricate. If a command or env var isn't in the files you read, tell
the user it's unspecified and point at the doc — don't make one up. Ground every requirement in reality:
self-host is honest work (a public HTTPS domain + real secrets + a seeded allowlist), not a one-liner, and the
casserole guard runs at send whether they host it or capx does.Turn a merged PR or GitHub release into a clear "we shipped X, here's what it means for you" X post or short thread. You draft from the real diff/notes; capx ships it guarded.
Turn a thing you just shipped — a merged PR or a tagged release — into an announcement post that tells people
what changed and why they should care. **You write the post** from the real diff and notes; capx never
generates content, it only ships (each post passes the casserole guardrail at send). What to announce, if
named: "$ARGUMENTS"
If the account isn't connected yet, run the `connect` skill first, then come back.
Follow these steps:
1. **Find the source.** From "$ARGUMENTS", identify the merged PR or release. If nothing's named, default to
the most recent merge on this branch.
- PR: `gh pr view <n-or-url> --json title,body,mergedAt,files,url` — and skim the actual change with
`gh pr diff <n>` for anything non-obvious.
- Release: `gh release view <tag> --json name,body,tagName,url`.
- No `gh` / no URL? Fall back to `git log --oneline -10` and `git show <sha>` for the merge commit.
Confirm it's actually **merged / published** — don't announce something still open or a draft tag.
2. **Extract the user-facing change.** Read past the internal churn (refactors, test scaffolding, bumps) to
the *one thing a user or fellow dev actually gets*: a new capability, a fixed pain, a faster path, a
breaking change they must act on. That — not the commit count — is the story.
3. **Draft the announcement.** Write ONE post (or a short thread if it genuinely needs it) that is:
- **"We shipped X → here's what it means for you"** — lead with the change, then the payoff. "Merged
streaming exports so a 2GB report downloads without timing out" beats "big PR landed today 🚀".
- **Concrete and specific** — name the real feature, the real fix, the real before/after. The guardrail
blocks vague hype, engagement-bait, hashtag-stuffing, and duplicates — specificity is what gets through
*and* what people read.
- **Grounded in the diff/notes** — do NOT invent features, numbers, benchmarks, or outcomes that aren't in
the PR or release. If a claim isn't backed by what you read, cut it or ask.
- **Honest about breaking changes** — if users must migrate or something's deprecated, say so plainly; that
post is more valuable than the feature brag.
- **In the user's voice** — if the `voice-match` skill is available, match it; otherwise mirror their
recent posts, or ask. Link the PR/release only if the user wants it public.
- **Within X's length** — one post ≤ 280 weighted chars; if the change needs setup + payoff + migration,
make it a short thread and say so.
4. **Show the draft and get approval.** Present the post (or thread) as text the user can edit, trim, or
reject. Never ship without showing them first.
5. **Now or scheduled?** Ask — or use what "$ARGUMENTS" already said. A ship-note is usually a **post now**
moment; do that with `post_now`. If they'd rather line it up for a launch window, get local time (HH:MM),
days, and timezone for `create_loop` instead.
6. **Ship it.**
- Post now: `post_now { text }`. For a thread, post the lead first, then the follow-ups in order.
- Scheduled: `create_loop { time, daysOfWeek (0=Sun..6=Sat), posts: [...], timezone }`. Note a loop needs a
**verified X account at least 30 days old** — if create_loop rejects it for that, explain the requirement
instead of retrying.
7. **Confirm.** Report the sent post's result, or call `list_loops` and show the queued announcement with its
schedule.
**Hard rules:** you draft, casserole decides — if the post comes back `blocked` or `held`, show the reason and
fix the *content* (be more specific, drop the bait, dedupe against your last ship-note); never route around the
guard. And never fabricate what shipped — an announcement is only worth posting if it's true and already
merged.Turn raw material into a numbered X thread — a sharp hook, one idea per post, a clean close/CTA. You draft it; capx ships each post in order, guarded.
Turn raw material into a structured X thread: a hook that earns attention, body beats that each carry one
idea, and a close that lands the point. **You write every post** from the material below — capx never
generates content; it only ships (each post passes the casserole guardrail at send). Raw material and intent:
"$ARGUMENTS"
If you're not connected yet, run the `connect` skill first; `whoami` confirms which account you'll post as.
Follow these steps:
1. **Read the raw material.** Take "$ARGUMENTS" as the source — notes, a story, a decision log, a topic with
key points. Everything in the thread must trace back to something real here (or the user's own knowledge).
If the material is thin or you'd have to invent features, numbers, or outcomes to fill it, **ask instead of
padding** — a fabricated thread fails the moment a reader checks.
2. **Find the spine.** A thread is ONE argument told in beats, not a pile of tweets. Name the single takeaway
the reader should leave with. If the material actually holds two unrelated ideas, that's two threads — say
so rather than forcing them together.
3. **Write the hook (post 1).** The first post earns the rest. Lead with a concrete claim, number, or tension
pulled straight from the material — the guard blocks vague hype and engagement-bait ("a 🧵 you NEED to
read"), and readers scroll past it anyway. Substance is the hook. A light "here's how it went" at the end is
fine to pull them down; bait is not.
4. **Break the body into beats — one idea per post.** Each body post makes a single point that stands on its
own and advances the argument. No post should depend on the next one to finish a thought. Ground every claim
in the material; don't invent a metric or a feature to make a beat punchier.
5. **Write the close (last post).** Land the takeaway in one line, then one clear CTA — follow, reply, try the
thing, read the repo. One ask, concrete. No hashtag stuffing (the guard blocks it, and it reads as spam).
6. **Number and length-check every post.** Number them (`1/N`, `2/N`, … or your own convention). Each post
≤ 280 weighted chars — if one runs long, **split the idea into two beats** rather than truncating a
sentence. Keep every post distinct; the guard blocks near-duplicates, so don't restate the same line twice.
7. **Show the full thread and get approval.** Present it as the numbered list, in order. Let the user edit,
cut, reorder, or merge beats before anything is sent. Never post without showing them first.
8. **Ship it in order — sequence or queue.** Ask which the user wants:
- **Post now as an ordered sequence** — call `post_now` for each post in order, 1…N, chained into a native
thread: post 1 first, then pass its returned `platformPostId` as the next post's `inReplyToId`, and so on.
Give each a stable `idempotencyKey` so a retry can't double-send, and set `aiGenerated` to the user's
labelling choice (default off — the user decides). Each post passes casserole at send.
- **Queue it** — call `create_loop` with `{ time, daysOfWeek (0=Sun..6=Sat), posts: [...], timezone }` to
drip one post per fire, in order. A loop needs a **verified X account at least 30 days old** — if
create_loop rejects it for that, explain the requirement instead of retrying.
9. **If a post comes back `blocked` or `held` mid-thread, stop.** Don't keep firing the rest on top of a gap —
an ordered thread with a hole in it is broken. Show the reason, fix *that post's content* (be more specific,
drop the bait, cut the hashtags or the duplicate line), get re-approval, then continue from where it stalled
so the sequence stays coherent. Never route around the guard.
10. **Confirm.** For a live sequence, show what posted, in order. For a queue, call `list_loops` and show the
loop, its schedule, and the queued posts.
**Hard rules:** you draft, casserole decides — fix the *content* on a block, never bypass the guard. One idea
per post, and every post honest — a thread only works if each beat is true and stands on its own.Turn the one non-obvious thing you learned this session into a single sharp TIL post. You (the agent) write it from real session context; capx never writes, it ships it guarded.
Turn the genuinely non-obvious thing learned **this session** into ONE punchy "Today I learned" post for X.
**You write it** from the real work you and the user just did — capx never generates content; it only ships
(the post passes the casserole guardrail at send). User's steer, if any: "$ARGUMENTS"
Follow these steps:
1. **Mine the session for the real lesson.** Look back over what you actually did together this session — the
bug that wasn't where you thought it was, the API that behaved differently than the docs implied, the
one-liner that replaced twenty, the assumption that turned out wrong. If "$ARGUMENTS" names the lesson, use
it. You're hunting for the **non-obvious** thing — the fact someone would screenshot, not "I wrote some
code today."
2. **Apply the TIL bar.** Keep the candidate only if it passes all three:
- **Genuinely useful** — a stranger doing similar work would be glad they read it. If it's obvious to
anyone in the field, it's not a TIL.
- **Not a humblebrag** — the value is the lesson, not "look how hard the thing I shipped was." Lead with
what *you learned*, not what you built.
- **Real** — it actually happened this session. Do NOT invent a lesson, a number, or a gotcha that didn't
occur. If nothing this session clears the bar, say so and stop — don't manufacture one.
3. **Draft ONE post.** Write a single post that:
- **States the concrete lesson** — the specific fact, tool, flag, or behavior. "TIL Postgres `NULL` breaks
`NOT IN` because `x NOT IN (1, NULL)` is never true" beats "TIL SQL has gotchas." Specificity is what
gets read *and* what clears the guard (it blocks vague hype, engagement-bait, hashtag-stuffing,
duplicates).
- **Earns the surprise** — one line of *why* it was non-obvious or what you'd have gotten wrong. That's the
part people keep.
- **Is self-contained** — no "as I mentioned earlier," no missing context. A reader with zero session
history should get the whole insight.
- **In the user's voice** — if the `voice-match` skill or a voice profile is available, match it;
otherwise mirror the user's recent posts, or ask.
- **Fits one post** — ≤ 280 weighted chars. A TIL is a single shot, not a thread; if it needs a thread it's
probably two separate lessons — pick the sharper one.
4. **Show it and get approval.** Present the draft. Let the user tighten, swap the lesson, or kill it. Never
post without showing them first.
5. **Ship it.** On approval, call `post_now` with `{ text }`. Ask whether to label it AI-assisted and set
`aiGenerated` to the user's choice (default off — it's their call). Pass an `idempotencyKey` so a retry
can't double-post.
6. **Handle the guard honestly.** If `post_now` comes back `blocked` or `held`, show the reason and fix the
**content** — make the lesson more concrete, cut the bait, dedupe against a near-identical past post. Never
try to route around the guard. On success, confirm it's live (`whoami` if you need the handle).
**Hard rules:** you draft, casserole decides. A TIL is only worth posting if it's true and non-obvious — never
fabricate the lesson, and never dress up an ordinary task as a revelation. One real insight beats ten
manufactured ones.Direct the user's OWN video-gen tool to a short, X-native clip — hook in the first second, right aspect ratio, length norms — then upload + attach. capx generates nothing; it orchestrates your tool and ships the post guarded (caption only).
Get a short, scroll-stopping video onto a post by driving the user's **own** connected video tool (higgsfield /
kling / fal / whatever they wired via `media-connect`). **capx generates nothing** — you use their tool, then
`upload_media` streams the file and `post_now` attaches it (X processes video before it's ready; the chokepoint
waits for that). casserole guards the **caption**, not the clip. The brief: "$ARGUMENTS"
Follow these steps:
1. **Check a video tool is connected.** No video-generation tool in the session → stop and send the user to
**`media-connect`**. capx has nothing to generate with.
2. **Pin the job for X.** Short wins: aim for a **tight clip (a few to ~30s)**, a **hook in the first second**
(X autoplays muted — the first frame + on-screen motion must earn the stop), and an aspect ratio that fits
feed (**9:16** for vertical, **1:1** or **16:9** otherwise). Get the subject, mood, and any on-screen text
from "$ARGUMENTS" and the post.
3. **Pick the model.** Use **`model-guide`** to choose the right video model for the look and length; they
differ a lot on motion, coherence, and max duration.
4. **Engineer the prompt.** Use **`prompt-engine`** for a video-shaped prompt: subject + action, camera move,
pacing, style, the opening beat, aspect ratio, duration. Show it before generating — video costs more than
images, so get the prompt right first.
5. **Generate with THEIR tool.** Call the user's connected video tool. Review the result against the hook and
length; iterate the prompt if the opening is weak or it runs long. Land on one clip the user approves.
6. **Disclose if it's AI-made.** AI-generated video especially should be disclosed. Offer to note it in the
caption and/or set `aiGenerated` per the user's choice (Option C). capx carries the label; it doesn't decide.
7. **Upload and attach.** Call `upload_media` with the file path — for video the chokepoint uploads in chunks
and waits for X to finish processing, then returns a media id. Then `post_now` with `{ text, mediaIds:
[thatId] }` and a distinct `idempotencyKey`. Show the caption first; casserole gates the caption at send.
8. **Confirm.** Report the posted result, or the caption's guardrail verdict (fix the *caption*, never the guard).
**Hard rules:** capx never makes the video — the user's tool does; capx uploads + ships. Guard the caption,
keep the clip honest, and disclose AI generation.Learn the developer's own X voice from their recent posts — tone, length, emoji/hashtag habits, rhythm — so other capx skills draft in it. A read-only look at their own timeline; capx never writes.
Build a reusable **voice profile** from the developer's *own* recent X posts so other capx skills
(`build-in-public`, `thread-builder`, …) draft in their actual voice. capx never generates a voice — this only
**reads** what the user already wrote and characterizes it. It's a **READ of your own timeline only** — nothing
is posted, scheduled, or edited here. User's extra intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Confirm the connection and get the handle.** Call `whoami`. If `connected` is false, stop and point the
user at the `connect` skill — you can't profile a voice with no account. Otherwise note `username` —
that's whose voice you're profiling. Reading your own recent posts sits **inside the `tweet.read` scope you
already granted at connect**, so there's no new consent screen.
2. **Pull the corpus.** Read the user's recent authored posts — aim for **20–30**, or the count named in
"$ARGUMENTS". These come from your own timeline under that already-granted `tweet.read` scope. Skip pure
retweets and bare one-word replies; you want *their* writing. **If the running capx build doesn't surface a
posts read, do not guess** — ask the user to paste 15–30 recent posts (or point at their `@handle`). A voice
profile built from nothing is a fabrication, and capx doesn't fabricate.
3. **Extract the signal.** From the real posts, characterize each trait and **pin a real example to every one**:
- **Tone & register** — casual / technical / dry / playful; first-person "I" vs "we"; how they frame wins vs
problems.
- **Length & shape** — typical char count; one-liners vs multi-line; singles vs threads; where they break lines.
- **Emoji habits** — which ones, how many, where — or never.
- **Hashtags & mentions** — do they use hashtags at all, how many, or none.
- **Punctuation & casing** — all-lowercase? em dashes? ellipses? Oxford commas?
- **Openers & rhythm** — how a post usually starts (a claim, a number, a question) and its cadence.
- **Vocabulary tics** — recurring phrases, how they name things, jargon level.
Ground every observation in a post you actually read. **Don't invent a trait you can't point to.**
4. **Summarize the profile.** Present it tight and skimmable — one line per trait, each with a real quoted
example — then a 2–3 line **"voice in a nutshell"** the other skills can apply directly. Keep it concrete:
"1–2 lines, lowercase, no emoji, opens with the thing shipped" beats "casual and authentic".
5. **Confirm and keep it for the session.** Show the profile and ask the user to correct anything that feels
off — they know their voice better than any sample. Once they're happy, **hold the profile in this working
session** so `build-in-public` and other draft-skills match it; if they want it to persist, save it to a file
they name. It's a session snapshot — re-run this skill to refresh after their style drifts.
**Hard rules:** This skill is **read-only** — it never posts, schedules, or edits; it reads your own timeline and
describes it. Every trait must trace to a real post — if the sample's too thin to be sure, say so and ask for
more rather than guessing. And matching a voice is **not** a licence to bypass the guard: when another skill
drafts in this profile, casserole still gates at send — sounding like the user never means vague hype, bait, or
hashtag-stuffing gets through.Show everything capx has posted or attempted on your behalf — most recent first, with delivery state and the AI-assist label. A read-only trust view of your own connected handle.
Show the user a straight, honest record of what capx has actually done on their behalf: every post it sent or
attempted, newest first, with the delivery state and whether it was labelled AI-assisted. This is a **trust**
feature — read-only, the user's own connected handle only, and it cannot post anything. User's ask, if any: "$ARGUMENTS"
Follow these steps:
1. **Read the trail.** Call `audit` (pass `limit` if the user asked for a specific count, e.g. 20; default is
the most recent 50). It returns the durable send history for the connected account.
2. **Show it clearly.** Present the entries as a readable list — for each: when, the delivery state
(`SENT` / `SENDING` / `PENDING` / `PUBLISH_FAILED`), a snippet of the text, and an AI-assisted tag if it was
labelled. If there's nothing yet, say so plainly.
3. **Explain what it does and doesn't cover.** Be honest: this is the record of what capx **sent or tried to
send**. A post the guardrail **blocked or held never went out** — those verdicts are shown live at post time
(or via the `draft-review` skill), not stored here. So a short trail means little has been posted, not that
anything was hidden.
4. **Offer the next step.** If the user is checking on a failed send (`PUBLISH_FAILED`), offer to re-post that
text with a fresh `post_now` — capx's idempotency means a genuine retry won't double-post.
**Hard rules:** `audit` is read-only and scoped to the caller's own handle — it can't see anyone else's posts
and can't send anything. It exists so the user can always verify, in their own words, exactly what capx did.Suggest good X post times — a v1 heuristic (audience timezone + known-good windows), honest it's not your data yet. Outputs slots you feed straight to create_loop.
Recommend *when* to post on X and hand back concrete time slots the user can drop into `create_loop`. **Be
honest about what this is:** v1 is a heuristic — general rules of thumb for a tech/dev audience, not a
data-driven answer from the user's own post performance. capx doesn't measure their reach yet, so treat these
windows as a hypothesis to test, not a fact. User's extra intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Get the audience's clock.** Time-of-day only matters relative to where the *readers* are. Ask for the
audience's timezone/region (or use "$ARGUMENTS"); if unknown, default to the connected account's own
timezone via `whoami` and say that's what you assumed. If their followers are split (dev/B2B usually skews
US + Europe), pick the timezone where most followers are — not where the user happens to sit.
2. **Say the honest part up front.** These are general priors, not "your best times." capx does not yet track
the user's own post performance, so nothing here comes from their analytics. It's a sensible starting slot
to try and then adjust from real results.
3. **Known-good windows (audience-local):**
- Weekday mid-morning **~9:00–11:00** — people catching up at the desk / end of commute.
- Around lunch **~12:00–13:00** — a scroll break.
- Early evening **~17:00–18:00** — commute home / wind-down.
Tue–Thu tend to beat Mon and Fri for dev/B2B content.
4. **Dead hours to avoid:**
- Overnight in the audience timezone (**~22:00–06:00**) — the feed is asleep.
- Late Friday afternoon through the weekend for B2B/developer content — reach usually drops. (Weekends are
fine for lighter/personal posts, and later in the morning.)
Don't push a slot into the dead zone just to hit a round number of posts.
5. **Turn windows into loop slots.** `create_loop` fires **once per day** at a single `time` on the
`daysOfWeek` you give it. So a "slot" = one time-of-day + a day set. Pick a *specific minute* inside a good
window (e.g. `09:30`, not "morning") and choose days (weekdays = `[1,2,3,4,5]`, 0=Sun..6=Sat). If they want
two posts a day, that's **two loops** (e.g. one at 09:30, one at 12:30) — say so explicitly.
6. **Space things out.** Call `list_loops` and check what's already scheduled. Don't stack a new slot on top
of an existing one — offset by an hour or more so posts don't fire on top of each other and the same window
isn't overloaded.
7. **Present the slots.** Give a short numbered list, each slot as concrete, ready-to-use values: `time`
(HH:MM), `daysOfWeek`, and `timezone` — the **audience** timezone, since that's what makes the fire time
land where the readers are. Add a one-line reason per slot. Let the user adjust before anything is
scheduled.
8. **Hand off.** If the user wants, call `create_loop` with the chosen slot(s) and their already-approved
posts (`time`, `daysOfWeek`, `posts`, `timezone`) — otherwise just hand the values back for them to use in
another skill (like `build-in-public` or `loop`). Note: a loop needs a **verified X account at least
30 days old**; if `create_loop` rejects it for that, explain the requirement instead of retrying.
**Hard rules:** this is v1 and it's a guess, not gospel — the real best time is whatever the user's *own*
audience shows over a few weeks, so tell them to try a slot, watch what lands, and shift it. Never present
these windows as if they came from the user's analytics, and never cite engagement numbers you don't have.
capx never writes the posts and it doesn't invent a magic best time — you're picking a sensible starting slot
from public rules of thumb, and the content itself still passes the casserole guardrail at send.Turn recent git activity into build-in-public X posts, queued to a schedule. You (the agent) draft from the real commits — capx never writes; it ships them guarded.
Turn what the developer has actually been building into a queue of build-in-public X posts. **You write the
posts** from the real git history — capx never generates content; it only ships (each post passes the
casserole guardrail at send). User's extra intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Read the real work.** Run `git log --oneline -30` (or a range the user named). Skim the diffs of the
substantive commits if needed. **Skip the noise** — merges, `wip`, `fix typo`, formatting, dependency
bumps. You want the *shippable, interesting* changes: new features, fixes users would care about,
milestones, hard problems solved.
2. **Group into themes.** Cluster related commits into a handful of "this is worth a post" stories. A week
is usually 3–5 posts, not one-per-commit.
3. **Draft the posts.** For each theme write ONE post that is:
- **Concrete and specific** — name the actual thing you built and why it matters. "Shipped X so users can
now Y" beats "made great progress today!". (The guardrail actively blocks vague hype, engagement-bait,
hashtag stuffing, and duplicates — specificity is what gets through *and* what people actually read.)
- **Grounded in the commits** — do NOT invent features, numbers, or outcomes that aren't real. If you're
unsure a claim is true, leave it out or ask.
- **In the user's voice** — if a voice profile is available (the `voice-match` skill), match it;
otherwise mirror the tone of their recent posts, or ask.
- **Within X's length** — one post ≤ 280 weighted chars; if a story needs more, make it a short thread and
say so.
4. **Show the drafts and get approval.** Present the posts as a numbered list. Let the user edit, cut, or
reorder before anything is scheduled. Never post without showing them first.
5. **Get the schedule.** Ask for local time (HH:MM), which days (e.g. weekdays), and timezone — or use what
the user gave in "$ARGUMENTS". If they'd rather post one right now instead of scheduling, do that with
`post_now`.
6. **Queue it.** Call `create_loop` with `{ time, daysOfWeek (0=Sun..6=Sat), posts: [...], timezone }`. One
post is sent per fire, in order. Note: a loop needs a **verified X account at least 30 days old** — if
create_loop rejects it for that, explain the requirement instead of retrying.
7. **Confirm.** Call `list_loops` and show the created loop, its schedule, and the queued posts.
**When the queue later runs low** it pauses and asks for more — re-run this skill (or `loop`) to top it
up from the newer commits. Your normal work keeps the queue full.
**Hard rules:** you draft, casserole decides — if a post comes back `blocked` or `held`, show the reason and
fix the *content* (be more specific, drop the bait); never try to route around the guard. And never fabricate
progress — build-in-public only works if it's true.Turn a backlog of drafts or a content goal into a non-robotic posting cadence — how many a week, which days and times — then create the loop(s). You draft; capx only ships.
Take a pile of drafts (or a content goal) and turn it into a **sensible, human-looking posting rhythm** — how
many a week, which days, what times, spread so it never reads as a bot — then create the loop(s). **You do the
drafting** when drafting is needed; capx never generates content, it only ships (every post clears the
casserole guardrail at send). User's material or target, if any: "$ARGUMENTS"
If you're not connected to X yet, run the **connect** skill first — loops can't be created without it.
Follow these steps:
1. **Get the material.** Two cases. (a) The user hands you a **backlog of drafts** — those are theirs; use
them as-is (only polish if asked). (b) They give only a **goal or theme** — then *you* write the posts,
grounded in real things (their actual work, product, or experience). Never fabricate features, numbers, or
outcomes. Count how many posts you have — that number drives everything below.
2. **Pick a sustainable rate.** A healthy build-in-public rhythm is **~3–5 posts/week**. Resist dumping the
whole backlog in one week — more than roughly one a day reads as spam, and the guard blocks near-duplicates
and low-effort filler anyway. Compute the **runway**: posts ÷ per-week = how many weeks the queue lasts.
Name the tradeoff out loud (post more often → run dry sooner).
3. **Spread it so it's not robotic.** Pick days *across* the week rather than clustered, and **vary the
time-of-day** instead of firing the same minute every day. Anchor to when their audience is awake and to
their own timezone. A little irregularity is the point — it's what a real person's feed looks like.
4. **Decide one loop or several.** One `create_loop` = one recurring schedule (fixed days + time) that sends
**one queued post per fire, in order**. Use **multiple loops** when the plan naturally splits — e.g. a
Tue/Thu 09:00 loop for substantive updates plus a Sat 11:00 loop for lighter posts, or different content
buckets on different rhythms. Assign specific drafts to each loop in send order.
5. **Show the plan and get approval.** Present it as a clear map: each loop's days / time / timezone, exactly
which posts land in which loop and in what order, and the runway (weeks until it empties). Let the user move
posts between loops, shift times, or cut before anything is created. Never create a loop without showing the
plan first.
6. **Create the loop(s).** For each, call `create_loop` with `{ time (HH:MM local), daysOfWeek
(0=Sun..6=Sat), posts: [...], timezone }`. Note: a loop needs a **verified X account at least 30 days
old** — if `create_loop` rejects it for that reason, explain the requirement instead of retrying. If a draft
comes back `blocked` or `held` by casserole, show the reason and fix the **content** (make it more specific,
drop the hype or bait) — never route around the guard.
7. **Confirm.** Call `list_loops` and show every loop you created — schedule, timezone, and the queued posts in
order — so the whole cadence is visible in one place.
**When a loop later runs low** it pauses and asks for more — top it up with `top_up_loop {id, posts}`, or
re-run a drafting skill (`build-in-public` / `loop`) to refill it from newer work. If the user would
rather fire a single post now than schedule anything, that's `post_now`.
**Hard rules:** you draft, casserole decides — on `blocked`/`held`, fix the content, never bypass the guard.
Never fabricate to fill a slot; a sparse-but-true cadence beats a dense fake one. And keep it human — a
schedule that's too dense or too mechanical is worse than a lighter, well-spread one.Turn a commit range or version bump into a 'what shipped in vX' X thread — you (the agent) read the range and write each post; capx never writes, it ships them guarded.
Turn a release into a "what shipped in vX" X thread — a hook post plus one post per notable change. **You
read the range and write every post** from the real diff; capx never generates content, it only ships (each
post passes the casserole guardrail at send). User's range / intent, if any: "$ARGUMENTS"
Follow these steps:
1. **Resolve the range.** From "$ARGUMENTS": if it's a range (`v1.2.0..HEAD`, `abc123..def456`), use it as-is;
if it's a version or tag, use `<previous-tag>..<that-tag>` (find the prior tag with
`git describe --tags --abbrev=0 <tag>^`); if it's empty, default to since the last release:
`git log $(git describe --tags --abbrev=0)..HEAD`. Confirm the **version label** the thread will announce —
and only use a real one (an actual tag or bump). Never invent a version number.
2. **Read the real work.** Run `git log --oneline <range>` and skim the diffs of the substantive commits.
**Skip the noise** — merges, `wip`, formatting, dependency bumps, internal refactors with no user-visible
effect. You want what a user or downstream dev would actually notice.
3. **Group by user-facing change.** Cluster the commits into a handful of notable items: new features, fixed
behavior, breaking changes, performance wins, new config/flags. Ten commits behind one feature = one item,
not ten. Internal churn gets no post.
4. **Draft the thread.** Write it as an ordered sequence:
- **Post 1 is the hook** — lead with the single most compelling shipped thing (or the version headline).
Concrete beats "big update is here!". This is the tweet that earns the scroll.
- **One post per notable item** after it. Each is: **concrete and specific** (name the actual change and
what it lets someone do — the guard blocks vague hype, engagement-bait, hashtag-stuffing, and
duplicates); **grounded in the diff** (do not invent features, numbers, or outcomes — if a commit didn't
land it, it isn't in the thread); **flagged if breaking** (say so plainly, with the migration line if the
diff shows one); **in the user's voice** (match the `voice-match` profile if present, else mirror
their recent posts or ask); and **≤ 280 weighted chars** each.
- Optional closing post with a real upgrade/install line (`npm i pkg@1.3.0`, a link) — only if it's true.
5. **Show the thread and get approval.** Present it as a numbered list in reading order. Let the user edit,
cut, reorder, or drop the version label before anything ships. Never post without showing them first.
6. **Ship it — post now or queue.** Ask which the user wants:
- **Thread it now** (default): call `post_now` for each post **in order**, hook first, chained into a native
reply-thread — post the hook, then pass its returned `platformPostId` as the next post's `inReplyToId`, and
so on down the range. Pass a distinct `idempotencyKey` per post so a retry can't double-send, and set
`aiGenerated` to the user's labelling choice (default off — the user decides). Each post still passes
casserole individually at send.
- **Queue it** (drip on a schedule): call `create_loop` with
`{ time, daysOfWeek (0=Sun..6=Sat), posts: [...in order], timezone }` — one post per fire. A loop needs a
**verified X account at least 30 days old**; if create_loop rejects it for that, explain the requirement
instead of retrying.
7. **Confirm.** For a live thread, confirm each post went through and stop if one is `blocked`/`held`. For a
queue, call `list_loops` and show the loop, its schedule, and the queued posts.
**Hard rules:** you draft, casserole decides — if a post comes back `blocked` or `held`, show the reason and
fix the *content* of that post (be more specific, drop the bait, split it), never route around the guard. And
the changelog must match the diff: every claim traces to a real commit, every number is one you can point to,
the version is one that actually exists. A "what shipped" thread only works if it all shipped.Check the capx connection and self-heal it — whoami reports your @handle, lane, and needs-reauth; if it's stale or nothing's connected, it routes you to connect.
Check the capx connection in one call and self-heal it if it's broken. This is a **read-and-route** skill — it
reports state and hands off; it never posts and never writes content. If the user named an account they expect
to be posting as, it's here: "$ARGUMENTS".
Follow these steps:
1. **Call `whoami`.** One call. It returns `{ connected, username, lane, needsReauth }` — that's the whole
ground truth. Don't infer the connection from anything else, and don't fabricate a handle or lane you didn't
get back.
2. **Nothing connected** (`connected: false`) — say so plainly and point them to the **connect** skill to
link their X account. Stop here; there's nothing to heal until they connect.
3. **Connected and healthy** (`needsReauth: false`) — report it in a line: the **@handle** (`username`), the
**lane** (`byo` = your own X developer app / `capx-app` = capx's shared app), and that it's good to post.
Done.
4. **Connected but needs re-auth** (`needsReauth: true`) — the stored token has expired or been revoked, so
posts and loops will fail until it's refreshed. Say that in one line, then walk them through **connect**
to re-authorize the **same** @handle. Don't re-run `whoami` hoping the flag clears — re-auth is the only fix.
5. **Cross-check the handle.** If "$ARGUMENTS" named an expected account and the connected `username` doesn't
match, flag the mismatch (you're connected as @X, not @Y) so nothing goes out from the wrong account — then
reconnect via connect if it's the wrong one.
**Keep it short** — this is a status check, not a report: a line or two per case. Never ask the user for their
X password or tokens; connect handles auth in the browser and the token lives server-side, never on this
machine.