Skip to content
Back to skills

Refresh X

ASecurity

Fetch a tracked X/Twitter account's latest tweets, cluster them, and save a decision-ready gist to memory

  • 6 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentsbashreactapi

Works with

  • api

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add anajuliabit/aeon --skill refresh-x --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Refresh X?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Refresh X
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anajuliabit-refresh-x/badge)](https://www.skillsdirectory.com/skills/anajuliabit-refresh-x)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Refresh X
description: Fetch a tracked X/Twitter account's latest tweets, cluster them, and save a decision-ready gist to memory
var: ""
tags: [social]
---
<!-- autoresearch: variation B — sharper output via verdict + clustering + thread detection + insight lines + signal-score gating -->

> **${var}** — The @handle to check (e.g. "@elonmusk", "vitalikbuterin", "https://x.com/pmarca"). **Required** — set this in aeon.yml or pass when triggering.

If `${var}` is empty or only whitespace after normalization, send `./notify "refresh-x: REFRESH_X_NO_VAR — set var to an X handle"` and exit 0.

Read `memory/MEMORY.md` for context. Read the last 2 days of `memory/logs/` — extract every `https://x.com/` URL under a prior `## Refresh X` section for the same handle into a `SEEN_URLS` set (used for dedup in step 4).

## Steps

### 1. Normalize var

Strip leading `@`, `https://x.com/`, `https://twitter.com/`, `https://nitter.net/`, and any trailing slash or `/status/...`. Lowercase. Reject if the remainder is empty, contains whitespace, or is longer than 15 chars (X handle limit). On reject → `REFRESH_X_NO_VAR` and exit.

Store the cleaned handle as `ACCOUNT`.

### 2. Load tweets

**Path A — prefetched cache (preferred)**: read `.xai-cache/refresh-x.json`. If present and non-empty, parse with:
```bash
jq -r '.output[] | select(.type == "message") | .content[] | select(.type == "output_text") | .text' .xai-cache/refresh-x.json
```
Record `source=xai-cache`.

**Path B — WebFetch fallback**: if the cache is missing, empty, or the parsed text contains zero x.com status URLs, use WebFetch against `https://x.com/${ACCOUNT}` with the prompt: *"List every tweet, reply, and quote tweet visible on this profile with its full text, timestamp, engagement counts (likes/retweets/replies) if shown, and the permalink in the form https://x.com/handle/status/ID. Return a chronological list."* Record `source=webfetch`.

**Path C — degraded**: if both paths fail or `XAI_API_KEY` is unset and WebFetch returns nothing parseable, skip to step 8 with status `REFRESH_X_NO_API_KEY` (if key missing) or `REFRESH_X_ERROR` (if key set but both paths failed).

### 3. Parse into structured tweets

For each tweet extract: `url`, `text`, `timestamp`, `type` (original / reply / quote), `reply_to` (handle, if reply), `quoted_text` (if quote), `likes`, `retweets`, `replies`. Drop retweets of others (not originals from this account). If engagement counts are missing, treat them as 0 — do not fabricate.

Compute `signal_score = likes + 2*retweets + replies − (3 if type=reply else 0)`.

### 4. Dedup and gate

Drop any tweet whose `url` is in `SEEN_URLS` — count these as `deduped_count`.

If fewer than 3 tweets survive dedup AND no thread is detectable (see step 5), skip to step 8 with status `REFRESH_X_NO_NEW` (if everything was deduped) or `REFRESH_X_EMPTY` (if the account simply posted nothing).

### 5. Detect threads

A thread = 2+ tweets by `ACCOUNT`, posted within 30 minutes of each other, where later tweets reply to earlier ones OR share ≥2 meaningful keywords with the opener. Thread tweets are preserved as atomic units regardless of individual signal score. Record each thread as `{opener_url, tweet_count, combined_signal}`.

### 6. Cluster and extract insights

Group surviving tweets (threads count as one unit) into **2–4 sub-narratives** by topic overlap — named entities, project names, recurring keywords. If fewer than 2 narratives emerge, use a single cluster.

For each cluster write:
- **Title** — a 3-8 word topic label.
- **Top tweet(s)** — 1-3 excerpts (≤200 chars each) with permalink and engagement.
- **Insight** — one sentence: what this cluster reveals about the author's stance, claim, or shift today. Not a paraphrase — a claim about what they seem to be arguing or announcing. If you can't write an insight beyond paraphrase, drop the cluster.

For each detected thread, write a 1–2 sentence summary of where the thread lands, plus the opener URL.

### 7. Write the verdict

Pick exactly one verdict based on the clusters:

| Verdict | When |
|---------|------|
| `ANNOUNCEMENT` | Cluster contains a launch, hire, policy, or product drop |
| `ARGUMENT` | Majority of signal comes from contrarian takes or fights |
| `BUILDING` | Ships/code/tech-progress clusters dominate |
| `SHITPOST` | Jokes, memes, low-stakes banter dominate |
| `CONTEXT` | Mostly reacting to a news cycle, not driving one |
| `QUIET` | <3 originals and no thread |

Pair it with a ≤20-word lede describing the day's shape.

### 8. Save gist to memory/logs/${today}.md

Append:

```
## Refresh X — @ACCOUNT
**Verdict:** VERDICT — [lede]
**Counts:** N tweets (X original / Y reply / Z quote), T threads, deduped K

### Clusters
1. **[title]** — signal S
   > "[excerpt]" ([likes]❤ [rt]🔁) [permalink]
   **Insight:** [one-sentence claim]
2. ...

### Threads
- **[topic]** (N tweets, combined signal S): [1-2 sentence landing] — [opener permalink]

### Vibe
[2-3 sentence tone read — not a stat restatement. What's the day *feel* like from this account?]

**Status:** STATUS | source=[xai-cache|webfetch] | count=N | deduped=K
```

If status is `REFRESH_X_EMPTY`, `REFRESH_X_NO_NEW`, `REFRESH_X_NO_API_KEY`, `REFRESH_X_ERROR`, or `REFRESH_X_NO_VAR` — write only the `## Refresh X — @ACCOUNT` header and the `**Status:**` footer, skip the cluster sections.

### 9. Update MEMORY.md (conditional)

Only if a cluster carries an announcement, a specific claim, a named project, or a stance shift the operator will want to reference later: add a one-line bullet under a `## Tracked X Accounts` section (create the section if missing). Format: `- @ACCOUNT YYYY-MM-DD: [one-sentence claim] — [permalink]`.

Do **not** append paraphrases, memes, or generic opinions to MEMORY.md.

### 10. Notify via `./notify`

On `REFRESH_X_OK`:
```
x refresh — @ACCOUNT ([VERDICT])
[lede]
top cluster: [title] — "[≤80 char excerpt]" ([likes]❤)
[N tweets, T threads, K deduped]
```

On `REFRESH_X_EMPTY` / `REFRESH_X_NO_NEW`: **skip notify** — no signal is no signal. Still write the log entry so skill-health can observe the run.

On `REFRESH_X_NO_API_KEY`, `REFRESH_X_ERROR`, `REFRESH_X_NO_VAR`: notify with the status code and a one-line hint (e.g. `"refresh-x: REFRESH_X_NO_API_KEY — set XAI_API_KEY in workflow secrets"`).

## Sandbox note

The sandbox blocks direct curl to api.x.ai because the auth header can't expand `$XAI_API_KEY`. Primary path is the **prefetch cache** (`scripts/prefetch-xai.sh` runs before Claude starts and writes `.xai-cache/refresh-x.json`). Fallback is **WebFetch** against `https://x.com/${ACCOUNT}` — public profile page, no auth needed, bypasses the sandbox. Never curl api.x.ai from the skill body.

## Environment Variables Required

- `XAI_API_KEY` — drives the prefetch cache (primary path). If unset, WebFetch fallback still works for public accounts but returns less structured data.

## Constraints

- Never fabricate engagement counts. Missing → 0, not a guess.
- Never include a tweet URL already in `SEEN_URLS` in the output.
- An insight line that only paraphrases the tweet is not an insight — drop the cluster.
- Keep MEMORY.md updates to one line per noteworthy item. No paragraphs.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…