⏳ This skill is pending AI review.
Scores will appear once the review pipeline completes.
hitpost-pro
Run HitPost (lihaila.si) from your AI agent. It writes X (Twitter) posts, threads, long posts and X Articles from live research (Grok reads what X is saying this week, Brave checks the web and news), polishes drafts with a blind multi-model judge, and posts them to the user's own X account. Uses the user's HitPost account and credits through an API key. Use it whenever the user has a HitPost account or key, mentions HitPost Pro, or wants live X research, an X Article, or a post published to X from the agent, even if they don't say "HitPost".
Choose how to use this skill
You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.
1. Native installer
This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.
Do not guess an installer command or replace an existing version without reviewing the diff.
2. Complete package recommended
Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.
No complete ProSkills package is published for this listing yet.3. Prompt-only
Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.
Need only the instruction file?
Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.
No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.
// RATINGS
Not yet listed on ClawHub or SkillsMP
// README
HitPost skills for AI agents
Write X (Twitter) posts that earn attention and sound human, right inside your AI agent: Claude Code, Codex, Cursor, Gemini CLI or any agent that supports SKILL.md skills.
Two skills, from the makers of HitPost:
| hitpost | hitpost-pro | |
|---|---|---|
| What writes the posts | Your agent, following HitPost's writing rules | HitPost's own pipeline, on your account |
| Live X research (what X is saying this week) | – (web search only) | ✓ Grok reads X live |
| Polish with a blind multi-model judge | Your agent scores its own rewrites | ✓ DeepSeek + Qwen rewrite, Grok judges |
| X Articles | – | ✓ |
| Posting | Share links | ✓ Posts to your X account after you confirm |
| Cost | Free | Free to install; uses HitPost credits (free credits on sign-up, then top up) |
| Needs | Nothing | A blue-check X account and a HitPost API key |
hitpost (free)
Give it an idea, a topic, a link or a rough note. You get back three ready-to-post drafts, each with a different angle and a one-click Post on X link. It writes single posts, threads and long Premium posts, in any tone and in English, Chinese, Japanese, Korean and more. It uses the same writing rules HitPost runs in production.
- Hooks first. Every draft leads with a reader need (useful, money, insider, a surprising number, humor) and ends on a takeaway, not a restated headline.
- Sounds human. It keeps out the phrases that mark a post as AI-written ("game-changer", "Let's dive in", 🚨, "Excited to share", 赋能, 重磅, 家人们…).
- Doesn't make things up. Numbers, quotes and stories come only from your idea or real research. Research happens only when the post needs current facts, so opinions and tips don't turn into news recaps.
- Counts length the way X does. A bundled checker counts CJK characters and emoji as 2, so a single post shows in full without "Show more", and every post in a thread lands in range.
- Polishes your drafts. Paste your own tweet to get two rewrites, scored 1–10 on hook, clarity, value, shareability and how human it sounds.
hitpost-pro (HitPost credits)
Runs the real HitPost on your account from inside your agent: live research, three drafts (or a full X Article), polish, then posting after you confirm the exact text.
- Sign in with X at lihaila.si. HitPost is for blue-check accounts.
- Open the account menu → Use in AI agents → Create a key.
- Install the skill (below) and paste the key when your agent asks.
Each craft uses 1 credit; polishing and formatting are free within a daily allowance. New accounts get free credits once, then you top up on the site with Stripe. Credits are shared between the site and the skill, and a craft that fails is refunded. It needs an agent with network access; the claude.ai sandbox may block calls to outside servers.
Install
Claude Code
/plugin marketplace add Lihaila/hitpost-skill
/plugin install hitpost@hitpost
This installs both skills. Or copy one in by hand:
git clone https://github.com/Lihaila/hitpost-skill
cp -r hitpost-skill/skills/hitpost ~/.claude/skills/ # free
cp -r hitpost-skill/skills/hitpost-pro ~/.claude/skills/ # HitPost credits
Updating in Claude Code: plugins don't update on their own. To get the latest version:
/plugin marketplace update hitpost
/plugin update hitpost@hitpost
Then restart Claude Code.
Any other agent that supports SKILL.md skills (Codex, Cursor, Gemini CLI and others): copy the skill folder from skills/ into that agent's skills directory (see its docs for the location). The scripts are plain Node with no dependencies, so nothing else is needed. We built and tested these with Claude Code.
claude.ai
Download hitpost.zip (or hitpost-pro.zip) from the latest release. Then open Settings → Capabilities, turn on code execution, and upload it under Skills.
Both skills need Node 18 or later for their scripts; they use the Node on your machine.
Try it
write a tweet about this: cloudflare now blocks AI crawlers by default for new domains
帮我写一个推文串:独立开发者最大的问题不是技术,是不敢发布。语气犀利一点
make this tweet better: "Excited to share that I've been using Claude Code for 3 months…"
gm tweet pls, it's friday
write a thread on what X is saying about the new Grok model, then post it # hitpost-pro
License
MIT
// HOW IT'S BUILT
// FILES
3 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.
skills/hitpost-pro/SKILL.md5.2 KB738e473dcc77upstream ↗
--- name: hitpost-pro description: Run HitPost (lihaila.si) from your AI agent. It writes X (Twitter) posts, threads, long posts and X Articles from live research (Grok reads what X is saying this week, Brave checks the web and news), polishes drafts with a blind multi-model judge, and posts them to the user's own X account. Uses the user's HitPost account and credits through an API key. Use it whenever the user has a HitPost account or key, mentions HitPost Pro, or wants live X research, an X Article, or a post published to X from the agent, even if they don't say "HitPost". --- # HitPost Pro This skill runs the real HitPost pipeline on the user's HitPost account. You don't write the posts yourself here: HitPost researches live and writes them, and you help the user choose, polish and post. You're working for the user, so the posts go to **their own followers**. Everything goes through one script, `scripts/hitpost.mjs` (Node 18+, no dependencies). It prints JSON and sends progress to stderr. Run it as `node <skill-dir>/scripts/hitpost.mjs <command>`. ## Accounts, credits and keys - HitPost is for **blue-check X accounts**. The user signs in with X at lihaila.si, then creates a key under **account menu → Use in AI agents**. - New accounts get free credits once. After that, they **top up with Stripe on the website** (account menu → Top up). Credits are shared between the site and this skill. - **Costs:** a craft (live research plus three drafts, or one X Article) uses 1 credit. Optimize and format are free within a daily allowance. Posting is free, except that a post containing a link uses 1 credit. - A craft that fails on HitPost's side is refunded automatically. ## First use Run `me`. If it exits with code 2, the JSON's `action` says what the user has to do. Tell them in plain words and stop until they've done it: | action | Tell the user | |---|---| | `create_key` | Sign in with X at lihaila.si, open the account menu → **Use in AI agents** → **Create a key**, and paste the key here. Then run `login <key>`. The key is saved to `~/.config/hitpost/key`, readable only by them. | | `blue_check` | HitPost needs a blue-check X account (X Premium; Basic doesn't include a check mark). | | `top_up` | They're out of credits: top up at lihaila.si → account menu → **Top up**, then try again. | | `reconnect_x` | X access expired: sign in with X again at lihaila.si. | Never ask for, or handle, their X password or card details. Sign-in and payment happen only on the website. ## Writing posts: craft ```bash node hitpost.mjs craft --idea "<the user's idea, in their words>" [--format single|thread|long|article] [--tone auto|insight|story|bold|funny|pro] [--lang auto|en|zh|…] ``` - **Format:** `single` is the default. Use `thread` for "thread" or 🧵, `long` for a long Premium post, and `article` for an X Article. Tone and language default to `auto`. - **Pass the idea as the user wrote it.** HitPost decides on its own whether the idea needs research. - **Tell the user before running it** that a craft takes about 1–2 minutes and uses 1 credit. Show the balance from `me` if you have it. - **The result:** `craftId`, `research` (what X is saying, angles, top posts and sources), and `variants`. Each variant has `angle`, `why` and `posts`. An article variant has `titles` and a Markdown `body` instead. - **Show the user** each variant's angle and full text, plus a short "What HitPost found" from `research` with the source links, and the remaining `credits`. - **Keep the draft text exactly as HitPost returned it**, unless the user asks you to change it. ## Polishing: optimize (free, daily allowance) Write the posts to a JSON file (an array of strings, one per post), then run: ```bash node hitpost.mjs optimize --posts posts.json --craft <craftId> ``` DeepSeek and Qwen each rewrite the draft, and Grok scores all versions blind on hook, clarity, value, shareability and how human they sound. Show the winner with its scores and notes, and the other versions in case the user prefers one. Pass `--craft` whenever the draft came from a craft, so the rewrites use its research. `format --posts posts.json --mode emojis|bullets` adds a few emojis or turns the posts into bullets. The wording stays the same. ## Posting: post ```bash node hitpost.mjs post --posts posts.json [--craft <craftId>] [--variant <id>] ``` Posting publishes to the user's X account. It's public and hard to take back, so: 1. **Show the exact text** that will be posted, every post of the thread, and **ask for a clear yes**. Approval to post one draft doesn't carry over to the next. 2. If any post contains a link, say that it uses 1 credit per post with a link. Offer to remove the link and keep the source name instead. 3. After posting, give the user the link to each post from `posted`. The limit is 50 posts a day, counting every post of a thread. Images, replies and likes aren't available through the skill; those stay on the website. ## When something fails - Exit code 2 means the user has to act: see the table above. - Exit code 1 is an error. Show the `error` message. A failed craft refunds its credit, so suggest trying again in a minute. - Don't retry a craft automatically, because each run uses a credit.skills/hitpost-pro/scripts/hitpost.mjs8.3 KB5d7f8b2e8486upstream ↗
#!/usr/bin/env node // HitPost API client for the HitPost Pro skill. No dependencies; Node 18+. // // node hitpost.mjs login <key> save the API key (from lihaila.si → account menu → Use in AI agents) // node hitpost.mjs me account, credits and today's optimizations left // node hitpost.mjs craft --idea "..." [--format single|thread|long|article] [--tone auto|insight|story|bold|funny|pro] [--lang auto|en|zh|ja|ko|es|fr|de|pt] [--handle you] // node hitpost.mjs optimize --posts posts.json [--craft <craftId>] // node hitpost.mjs format --posts posts.json --mode emojis|bullets // node hitpost.mjs post --posts posts.json [--craft <craftId>] [--variant <id>] // // posts.json is a JSON array of post strings (one per post of a thread). // Output is JSON on stdout; progress goes to stderr. Exit codes: 0 ok, 1 error, 2 needs action // (no key, out of credits, not a blue-check account): the JSON's "action" says what to do. import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const BASE = (process.env.HITPOST_URL || 'https://lihaila.si').replace(/\/+$/, ''); const KEY_FILE = path.join(os.homedir(), '.config', 'hitpost', 'key'); const TOPUP = `${BASE}/ (account menu → Top up)`; const KEYS = `${BASE}/ (sign in with X → account menu → Use in AI agents → Create a key)`; const out = (obj, code = 0) => { process.stdout.write(JSON.stringify(obj, null, 2) + '\n'); process.exit(code); }; const needs = (action, message) => out({ ok: false, action, message }, 2); function args(list) { const o = { _: [] }; for (let i = 0; i < list.length; i++) { if (list[i].startsWith('--')) o[list[i].slice(2)] = list[i + 1]?.startsWith('--') || list[i + 1] === undefined ? true : list[++i]; else o._.push(list[i]); } return o; } function readKey() { if (process.env.HITPOST_API_KEY) return process.env.HITPOST_API_KEY.trim(); try { return fs.readFileSync(KEY_FILE, 'utf8').trim(); } catch { return ''; } } function readPosts(file) { if (!file || file === true) out({ ok: false, error: 'Pass --posts <file.json> (a JSON array of post strings).' }, 1); let posts; try { posts = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { out({ ok: false, error: `Couldn't read ${file}: ${e.message}` }, 1); } if (!Array.isArray(posts) || !posts.length || posts.some((p) => typeof p !== 'string')) out({ ok: false, error: `${file} must be a JSON array of post strings.` }, 1); return posts; } /** Turn a refused request into what the user has to do. */ function refused(status, body) { const msg = body?.error || `HTTP ${status}`; if (body?.code === 'badkey' || status === 401) needs('create_key', `${msg} Create one at ${KEYS}, then run: node hitpost.mjs login <key>`); if (body?.code === 'verified' || body?.code === 'signin') needs('blue_check', msg); if (body?.code === 'quota' || body?.code === 'linkcredits' || status === 402) needs('top_up', `${msg} Top up at ${TOPUP}; credits are shared with the website.`); out({ ok: false, status, error: msg }, 1); } async function call(p, { method = 'GET', body } = {}) { const key = readKey(); if (!key) needs('create_key', `No HitPost API key yet. Create one at ${KEYS}, then run: node hitpost.mjs login <key>`); let res; try { res = await fetch(BASE + p, { method, headers: { authorization: `Bearer ${key}`, ...(body ? { 'content-type': 'application/json' } : {}) }, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(400_000), }); } catch (e) { out({ ok: false, error: `Couldn't reach ${BASE}: ${e.message}` }, 1); } return res; } /** Long jobs stream newline-delimited JSON events; returns them all, logging steps to stderr. */ async function streamed(res) { const events = []; const dec = new TextDecoder(); let buf = ''; for await (const chunk of res.body) { buf += dec.decode(chunk, { stream: true }); let i; while ((i = buf.indexOf('\n')) >= 0) { const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1); if (!line) continue; const ev = JSON.parse(line); if (ev.type === 'step') process.stderr.write(`[hitpost] ${ev.step}…\n`); events.push(ev); } } return events; } const credits = (q) => (q ? { credits: q.credits, optimizesLeftToday: q.optimizes } : undefined); const [cmd, ...rest] = process.argv.slice(2); const a = args(rest); switch (cmd) { case 'login': { const key = String(a._[0] || '').trim(); if (!/^hp_[A-Za-z0-9_-]{20,}$/.test(key)) out({ ok: false, error: 'That doesn\'t look like a HitPost key (it starts with hp_).' }, 1); fs.mkdirSync(path.dirname(KEY_FILE), { recursive: true }); fs.writeFileSync(KEY_FILE, key + '\n', { mode: 0o600 }); const res = await call('/api/me'); const body = await res.json().catch(() => ({})); if (!res.ok) refused(res.status, body); out({ ok: true, saved: KEY_FILE, handle: body.user?.handle, ...credits(body.quota) }); } case 'me': { const res = await call('/api/me'); const body = await res.json().catch(() => ({})); if (!res.ok) refused(res.status, body); out({ ok: true, handle: body.user?.handle, blueCheck: body.user?.verified, xConnected: body.user?.connected, ...credits(body.quota), topUp: TOPUP }); } case 'craft': { if (!a.idea || a.idea === true) out({ ok: false, error: 'Pass --idea "what the post is about".' }, 1); const res = await call('/api/craft', { method: 'POST', body: { idea: a.idea, format: a.format, tone: a.tone, lang: a.lang, handle: a.handle } }); if (!res.ok) refused(res.status, await res.json().catch(() => ({}))); const events = await streamed(res); const start = events.find((e) => e.type === 'start'); const research = events.find((e) => e.type === 'research')?.research; const done = events.find((e) => e.type === 'variants'); const err = events.find((e) => e.type === 'error'); if (!done) out({ ok: false, error: err?.error || 'The craft did not finish. Your credit was refunded.', ...credits(err?.quota) }, 1); out({ ok: true, craftId: start?.id, // What the research found, for citing and for the user's judgment. research: research && !research.light ? { pulse: research.x?.pulse || '', angles: research.x?.angles || [], hotPosts: (research.x?.hot_posts || []).map((p) => ({ author: p.author, gist: p.gist, url: p.url })), sources: (research.web || []).map((w) => ({ source: w.source, title: w.title, url: w.url })), } : { kind: research?.kind || 'light' }, variants: done.variants, ...credits(done.quota), }); } case 'optimize': { const res = await call('/api/optimize', { method: 'POST', body: { posts: readPosts(a.posts), craftId: a.craft } }); if (!res.ok) refused(res.status, await res.json().catch(() => ({}))); const events = await streamed(res); const r = events.find((e) => e.type === 'result'); if (!r) out({ ok: false, error: events.find((e) => e.type === 'error')?.error || 'Optimization failed.' }, 1); out({ ok: true, winner: r.winner, candidates: r.candidates.map((c) => ({ id: c.id, posts: c.posts, scores: c.scores, total: c.total, note: c.note, changes: c.changes, warning: !!c.warning })), ...credits(r.quota) }); } case 'format': { if (!['emojis', 'bullets'].includes(a.mode)) out({ ok: false, error: 'Pass --mode emojis or --mode bullets.' }, 1); const res = await call('/api/format', { method: 'POST', body: { posts: readPosts(a.posts), mode: a.mode } }); const body = await res.json().catch(() => ({})); if (!res.ok) refused(res.status, body); out({ ok: true, posts: body.posts }); } case 'post': { const res = await call('/api/post', { method: 'POST', body: { posts: readPosts(a.posts), craftId: a.craft, variantId: a.variant } }); const body = await res.json().catch(() => ({})); if (!res.ok) { if (body.code === 'reconnect' || res.status === 403) needs('reconnect_x', `${body.error || 'X access expired.'} Sign in with X again at ${BASE}/ and retry.`); if (body.posted?.length) out({ ok: false, error: body.error, postedBeforeError: body.posted }, 1); refused(res.status, body); } out({ ok: true, posted: body.posted }); } default: out({ ok: false, error: 'Commands: login <key> | me | craft --idea "…" | optimize --posts f.json | format --posts f.json --mode emojis|bullets | post --posts f.json' }, 1); }README.mdupstream ↗
Contents are not inlined in the catalog. Open the upstream file.
Plain-text copy, no login: /skill-bundles/lihaila-hitpost-skill-skills-hitpost-pro.md