Ask XActions
Ask how to do anything with XActions in plain language and get a sourced answer plus the exact thing to run. On the web at xactions.app/ask, in your terminal as
xactions ask, and to an AI agent as thex_askMCP tool. Free, no account, no API key.
Ask XActions is a conversational assistant for the toolkit. It searches the documentation, the 50 agent skills, the browser scripts, the marketing pages, and the GitHub repository, then streams an answer that cites its sources with clickable [1] [2] markers.
An explanation is only half of what someone asking "how do I unfollow all users" wants. The other half is the thing they run, so every answer ends with the runnable actions behind it: the browser script to paste (copied straight from source, with the x.com page to paste it on), the terminal command to type, and the MCP tool an agent should call. Those come from a catalog built out of the repository itself, so it cannot drift from what the code actually offers.
Three ways to ask
| Surface | Use it when |
|---|---|
| xactions.app/ask | You are reading the docs and want an answer with buttons |
xactions ask "..." |
You are in a terminal and want the command without leaving it |
x_ask MCP tool |
An AI agent needs to know how the toolkit works before acting |
Using it
Open xactions.app/ask, type a question, press Enter. Or deep-link a question:
https://xactions.app/ask?q=how+do+i+unfollow+everyone
- History (top right) lists past conversations. They live in your browser's
localStorage; nothing is stored on the server, which is what the Private badge means. - Auto / model picker (in the composer) chooses who answers.
Autowalks the free lanes below. Pick a provider and paste your own key to make it answer first; the key stays in your browser and is sent only with your question. - Every answer has Copy and Retry, code blocks have a copy button, and source chips open the doc, script page, or GitHub file that backed the answer.
- Keyboard:
Entersends,Shift+Enteradds a line,/focuses the box,Escstops a streaming answer,Ctrl/Cmd+Shift+Ostarts a new conversation.
In the terminal
xactions ask "how do I unfollow everyone?"
xactions ask "scrape followers" --json # machine-readable: answer, sources, actions
echo "how do I download a video" | xactions ask # reads a piped question
xactions ask "post a thread" --provider groq --key gsk_... # answer with your own key
The answer streams as it is written, wrapped to your terminal, and ends with a Run it block and the sources. Retrieval is local to the installed package, so it still answers with no network once the lanes are unreachable. Flags: --json, --quiet, --no-sources, --provider, --key, --model.
As an MCP tool
x_ask lets an agent read the manual instead of guessing at it. Point Claude Desktop, Cursor, or any MCP client at the XActions server (MCP setup) and the agent can ask before it acts:
{ "name": "x_ask", "arguments": { "question": "how do I unfollow everyone?" } }
{
"question": "how do I unfollow everyone?",
"answer": "Paste the Unfollow Everyone script into the DevTools console on ...",
"lane": "llm7",
"actions": [
{ "kind": "script", "id": "unfollow-everyone", "run": "Open x.com/YOUR_USERNAME/following, press F12, paste into the Console tab", "raw": "https://raw.githubusercontent.com/..." },
{ "kind": "mcp", "id": "x_unfollow_all", "run": "Call x_unfollow_all with confirm" }
],
"sources": [{ "n": 1, "title": "Unfollow Everyone", "url": "https://..." }]
}
Pass actionsOnly: true to skip the written answer and get just the matching scripts, commands and tools. That path never calls a model, so it returns in milliseconds: use it when the agent only needs to know which tool does the job.
x_ask is a read tool. It touches no X account, needs no session, and works with no API key.
How it works
question ──► BM25 search over dashboard/data/ask-index.json ─┐
└─► GitHub search (issues, PRs; code with a token) ──┴─► numbered sources
│
system prompt + sources + question ──► free LLM chain ──► streamed answer
- Index.
npm run ask:index(scripts/build-ask-index.mjs) strips YAML frontmatter and SEO keyword lists (both matched queries while teaching the reader nothing), then chunksdocs/**/*.md,skills/*/SKILL.md,tutorials/**/*.md, the top-level README/CHANGELOG, the header comments of every browser script insrc/andscripts/, and the text of the dashboard pages (FAQ, pricing, features, ...). Each chunk carries the live URL it came from: the rendered docs page when one exists (viadashboard/docs/_pages-manifest.json), the/scripts/<slug>page for a script, or the GitHub blob URL. The result isdashboard/data/ask-index.json, served at/data/ask-index.json.npm run docs:checkfails when the committed index is stale. - Retrieval.
src/ask/engine.jsbuilds a BM25 searcher over the chunks at startup (about 20 ms per query), with a small synonym map bridging how people ask ("all", "twitter", "retweet") to how the docs are written ("everyone", "X", "repost"). Titles are boosted, results are capped per document, and a live GitHub issues/PR search is merged in so recent bug reports show up too. - Actions.
src/ask/actions.jsranks the catalog indashboard/data/ask-actions.jsonagainst the question and the passages retrieval just found. A passage fromdocs/examples/unfollow-everyone.mdis direct evidence thatsrc/unfollowEveryone.jsis the answer, which survives phrasing ("clean out my following", "start fresh") that no keyword match would. Terms carried by a large share of the catalog are ignored, so the product's own name cannot make "is XActions safe?" look like a request to run something, and a conceptual question correctly returns nothing runnable. - Answer. The sources, the question, and the matched actions go to the first lane that accepts the request in
src/ask/lanes.js; the reply streams back as Server-Sent Events. The model is given the actions so its prose agrees with the buttons beside it instead of inventing a script name.
The action catalog
scripts/build-ask-actions.mjs reads all three executable surfaces from source, so nothing is hand-maintained:
| Surface | Read from | Carries |
|---|---|---|
| Browser scripts | src/*.js, scripts/*.js headers |
title, description, the x.com page to paste it on, raw source URL, /scripts/<slug> page, whether core.js must be pasted first |
| CLI commands | the .command()/.description() calls in src/cli/** |
full invocation including subcommand prefixes |
| MCP tools | the tool definitions in src/mcp/server.js |
name, description, required arguments |
61 scripts exist in both src/ and scripts/; the catalog keeps whichever copy the generated page was built from, so the source quoted beside a link always matches the page it links to.
Free LLM lanes
Every lane is an OpenAI-compatible chat endpoint reached with fetch, so the same file runs in Node, in the Cloudflare Worker, and in the browser. Lanes are tried in order; a 402, 429, 5xx, or network error moves on to the next one.
| Order | Lane | Needs | Model |
|---|---|---|---|
| 1 | Your key (BYOK, from the model picker) | user-supplied | provider default |
| 2 | Groq | GROQ_API_KEY |
llama-3.3-70b-versatile |
| 3 | Cerebras | CEREBRAS_API_KEY |
llama-3.3-70b |
| 4 | OpenRouter free tier | OPENROUTER_API_KEY |
first live :free model (read from /models, never hardcoded) |
| 5 | xAI | XAI_API_KEY |
grok-4.1-fast |
| 6 | Google AI Studio | GEMINI_API_KEY |
gemini-2.5-flash-lite |
| 7 | Mistral | MISTRAL_API_KEY |
mistral-small-latest |
| 8 | Cloudflare Workers AI | CLOUDFLARE_AI_API_TOKEN + CLOUDFLARE_ACCOUNT_ID |
llama-3.3-70b-instruct-fp8-fast |
| 9 | LLM7 | nothing | gemini-3.1-flash-lite |
| 10 | Pollinations | nothing | openai-fast |
| 11 | OVH AI Endpoints (anonymous) | nothing | Meta-Llama-3.3-70B |
Lanes 9 to 11 need no key, so a deployment with an empty environment still answers. Add any of the keyed free tiers to get a stronger model and a separate quota pool. GITHUB_TOKEN is optional and only raises the GitHub search rate limit and enables code search.
Two facts about the keyless tier worth knowing before you rely on it:
- Its quota is per IP and it is shared with everyone else on that address. All three keyless lanes can be rate limited at once. Configure one keyed free tier (Groq is the easiest) if you want a lane that is reliably yours.
- LLM7 refuses browser calls. It answers
401to any request carrying anOriginheader, so it is server-side only and the in-browser fallback skips it (browserSafeinbuildLaneChain). Pollinations and OVH send permissive CORS headers and do work from the page, as does any provider you supply your own key for.
When every lane is busy
Rather than showing an error, the engine falls back to a documentation digest: the passages that matched the question, quoted from the index, with their links. It is labelled "Answered via the documentation index, no model lane was free" so nobody mistakes it for a written answer, and it is real retrieved text, never generated. This runs on every surface, so a question is never answered with nothing when the index already found the material.
API
POST /api/ask
{
"question": "how do I unfollow everyone?",
"history": [{ "role": "user", "content": "..." }, { "role": "assistant", "content": "..." }],
"byok": { "provider": "groq", "apiKey": "gsk_..." }
}
history (optional) is the prior turns of the conversation, last six are used. byok (optional) makes that provider lead the chain; providers: groq, openrouter, xai, openai, gemini, mistral, cerebras.
The response is text/event-stream. Each data: line is one JSON event:
data: {"type":"sources","sources":[{"n":1,"title":"Unfollow Everyone","url":"https://...","kind":"doc","path":"docs/examples/unfollow-everyone.md"}]}
data: {"type":"actions","actions":[{"kind":"script","id":"unfollow-everyone","title":"Unfollow Everyone","run":"Open x.com/YOUR_USERNAME/following, press F12, paste into the Console tab","raw":"https://raw.githubusercontent.com/...","page":"/scripts/unfollow-everyone","why":"named by a cited source"}]}
data: {"type":"lane","lane":"llm7"}
data: {"type":"delta","text":"To unfollow everyone"}
data: {"type":"delta","text":" on X, paste"}
data: {"type":"done","lane":"llm7","model":"gemini-3.1-flash-lite","partial":false,"sources":[...]}
done carries partial: true when a lane died mid-answer and what had already streamed was kept, and digest: true with lane: "docs" when every lane was busy and the answer is the documentation digest described above.
An error event carries message when every lane failed. From a terminal:
curl -N https://xactions.app/api/ask \
-H 'content-type: application/json' \
-d '{"question":"how do I unfollow everyone?"}'
The actions event arrives before the first token, so a client can show what to run while the prose is still streaming.
GET /api/ask/health
Index size and digest, action-catalog size, the lanes configured on this deployment, and the suggested questions.
Where it runs
- Cloudflare Worker (
worker/index.js):/api/askis answered at the edge. The index is read from the static assets bundle and the lanes are called withfetch, so no origin server is needed. - Express (
api/routes/ask.js): mounted at/api/askin bothapi/server.jsand the Vercelapi/serverless.js. - Browser fallback (
dashboard/js/ask.js): ifPOST /api/askdoes not answer with an event stream (for example a static-only deploy, which is what xactions.app serves until the Worker is deployed), the page downloads/data/ask-index.json(about 650 KB gzipped, fetched only on this path and then cached), runs the same engine from/js/ask/engine.js, and calls the keyless lanes directly. A toast says so and the answer is labelled "in-browser".
Development
npm run ask:index # rebuild the index AND the action catalog, and mirror src/ask/ into dashboard/js/ask/
npm run ask:index:check # exit 1 if either is stale (part of npm run docs:check)
npx vitest run tests/ask # retrieval, action matching, lane failover, and the CLI and MCP surfaces end to end
Rebuild whenever you change docs, skills, tutorials, a script header, a dashboard page, a CLI command, or an MCP tool. The action catalog is derived from source, so adding a script or a command is enough to make it answerable: no list to update by hand.
Related: MCP setup, CLI reference, Browser scripts, REST API.