[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J40A2NWQ893KDBKXCTY0F3":3,"skill-same:01M6J40A2NWQ893KDBKXCTY0F3":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":35,"linkOnly":36,"markdownContent":37,"markdownBody":38,"frontMatter":39,"refName":46,"commitSha":47,"skillDirectory":48,"forksCount":49,"repositoryHtmlUrl":50,"repositoryHomepageUrl":51,"ownerHtmlUrl":52,"ownerBlogUrl":53,"ownerDescription":54,"updatedAt":55,"install":56,"resources":69},"01M6J40A2NWQ893KDBKXCTY0F3","digest",null,"Generate and send a digest on a configurable topic, optionally pulling RSS\u002FAtom feeds as an input source alongside web + X signal","aeonfun\u002Faeon-agent","aeonfun","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F287715280?v=4","01M6CZ23C6MXGMF4N0V1DZ1247","aeon","01M6J3TN6FHJPWGPAR9ZVJY6FP",12,"MIT","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fdigest\u002FSKILL.md",[],[5,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"generate","signal","send","content","source","input","rss","write","xai","web","atom","news","api","basics","as","feeds","2026-11-10 10:12:30",false,"---\nname: digest\ndescription: Generate and send a digest on a configurable topic, optionally pulling RSS\u002FAtom feeds as an input source alongside web + X signal\nmetadata:\n  title: Digest\n  mode: write\n  category: basics\n  var: \"\"\n  tags:\n    - content\n    - news\n  requires:\n    - XAI_API_KEY?\n---\n\u003C!-- autoresearch: variation B — curatorial discipline (filter → distill → structure → sanity-check) folded with direct-curl xAI + web\u002FRSS inputs and memory-aware dedup; RSS feed-reading + item-selection absorbed from rss-digest as an additional source class -->\n\n> **${var}** — Selects the digest's topic and which source classes feed it. Grammar:\n> - `\"\"` (empty) → **digest's default sources** (WebSearch + xAI\u002FGrok + aggregators), no topic filter — a broad daily digest.\n> - `\"\u003Ctopic>\"` → topic-focused digest on the default web sources, filtered to `\u003Ctopic>` (e.g. `\"solana\"`, `\"AI agents\"`, `\"rust\"`).\n> - `\"rss\"` → **RSS-only**: pull feeds from `memory\u002Ffeeds.yml`, no topic filter.\n> - `\"rss: \u003Ctopic>\"` → RSS-only, filtered to `\u003Ctopic>` (e.g. `\"rss: rust\"`).\n> - `\"\u003Ctopic> +rss\"` → default web sources **and** RSS feeds combined, both filtered to `\u003Ctopic>` (e.g. `\"AI agents +rss\"`).\n\nToday is ${today}. Generate and send a daily **${var}** digest.\n\nThe whole point of a digest is **signal, not volume**. A reader skimming for 60 seconds should walk away with three things they didn't know that morning and one of them should change a decision they'd make this week. Anything that doesn't clear that bar gets cut.\n\n## Preamble — orient and parse the selector\n\n1. Read `memory\u002FMEMORY.md` for high-level context and tracked topics, and scan the last 3 days of `memory\u002Flogs\u002F` so you can dedup against anything already reported.\n2. Parse `${var}` into **`{topic, sources}`**:\n   - Strip a trailing `+rss` → adds RSS to the default web sources. Remainder is the `topic`.\n   - A leading `rss:` (or the bare token `rss`) → **RSS-only** source set; text after the colon is the `topic` (empty = no filter).\n   - Otherwise the whole string is the `topic` and `sources = default web` (empty string = default web, no topic filter).\n   - Resulting `sources` is one of: `web` (default), `rss` (RSS-only), or `web+rss` (both).\n3. The `topic`, when non-empty, is a filter applied to **every** source class — web queries are scoped to it and RSS items must match it (title\u002Fdescription\u002Ftags). Empty topic = keep all relevant items.\n\n## Config (RSS source)\n\nWhen `sources` includes `rss`, this skill reads feed URLs from `memory\u002Ffeeds.yml`. If the file doesn't exist yet, create it (write mode) with the shape below, or — if you have nothing to seed it with — log a one-line note and treat the RSS source as empty for this run.\n\n```yaml\n# memory\u002Ffeeds.yml\nfeeds:\n  - name: Example Feed\n    url: https:\u002F\u002Fexample.com\u002Frss\n  - name: Another Feed\n    url: https:\u002F\u002Fexample.com\u002Fatom.xml\n```\n\n## Phase 1 — Gather (cast a wide net)\n\nPull from the source classes selected by `${var}`. Never rely on a single one — if `sources = web`, use at least two of the web classes below; if `sources = web+rss`, RSS counts as one class and you still want a second.\n\n### Web sources (active when `sources` is `web` or `web+rss`)\n\n1. **WebSearch** (built-in) — run 2 distinct queries:\n   - `\"${topic}\" news ${today}` (broad). If `topic` is empty, run a general query for the day's notable stories in the operator's tracked areas (from `memory\u002FMEMORY.md`).\n   - One narrower query you choose based on `${topic}` (e.g. for \"solana\" → `\"solana\" launches OR funding OR exploit ${today}`; for \"AI agents\" → `\"agent framework\" OR \"agentic\" release ${today}`).\n2. **xAI x_search via Grok** — pulls the X\u002FTwitter signal layer. `XAI_API_KEY` is injected into this skill's environment (declared in `requires:`) and is the **primary** path; see **Fetching the X signal** below for the full contract (attempt the curl before any fallback, set the Bash tool `timeout` ≥180000, record the true failure reason).\n\n   **Path A - X.AI API (primary):** a direct `curl` to the Responses API. First confirm the key as **Fetching the X signal** rule 1 says (the run notes' \"Keys set for this run\" line, no shell test); if `KEY_PRESENT` (it will be), this path is required. When you run the curl, set the Bash tool's `timeout` to at least `180000`.\n\n   **Shell shape.** Each Bash call is one allowed command (pipes are fine). The run refuses a call that sets a variable (`FROM_DATE=$(date ...)`, `PROMPT=\"...\"`, `HTTP=$(.\u002Fsecretcurl ...)`), uses `[ ... ] &&` tests, or joins commands with `;`, `&&` or `||`, and each refusal costs a model turn. So get each date with a bare `date` call, write the printed values and the topic into later commands literally (the `\u003CFROM_DATE>`, `\u003CTO_DATE>`, `\u003CTOPIC>` placeholders), and build the payload with one `jq -n` call piped straight into one `.\u002Fsecretcurl` call. A `>` redirect into a file and a `$name` in a jq filter (`--arg p ... '$p'`) are refused too, so the topic and dates go into the filter as plain JSON strings (a `\"` inside the topic becomes `\\\"`, and reword any `'`).\n\n   Get the window dates first, each `date` as its own Bash call:\n   ```bash\n   date -u -d yesterday +%Y-%m-%d\n   ```\n   ```bash\n   date -u +%Y-%m-%d\n   ```\n   The first prints FROM_DATE, the second TO_DATE. Build the payload with the prompt inline and pipe it into the send in the same call (it prints the HTTP status; `.\u002Fsecretcurl` also prints an `http=... bytes=...` line on stderr); `\u003CTOPIC>` is the parsed `topic`, or `the most notable technology, AI, and crypto stories today` when the topic is empty:\n   ```bash\n   jq -n '{model:\"grok-4.7\", input:[{role:\"user\",content:\"Search X for substantive, recent posts about: \u003CTOPIC>. Date range: \u003CFROM_DATE> to \u003CTO_DATE>. Return up to 10 high-signal posts - prioritize verifiable claims, launches, funding, releases, exploits, or hard data over hot takes. For EACH post return: @handle, the full text, date posted, exact engagement counts (likes, retweets, replies; 0 if unknown), and the direct link https:\u002F\u002Fx.com\u002Fhandle\u002Fstatus\u002FID. Return a numbered list.\"}], tools:[{type:\"x_search\",from_date:\"\u003CFROM_DATE>\",to_date:\"\u003CTO_DATE>\"}]}' \\\n     | .\u002Fsecretcurl -s -o \u002Ftmp\u002Fxai-digest.json -w '%{http_code}' --max-time 150 -X POST \"https:\u002F\u002Fapi.x.ai\u002Fv1\u002Fresponses\" \\\n     -H \"Content-Type: application\u002Fjson\" -H \"Authorization: Bearer {XAI_API_KEY}\" --data-binary @-\n   ```\n   On `HTTP=200` with a non-empty body, parse it with `jq -r '.output[] | select(.type == \"message\") | .content[] | select(.type == \"output_text\") | .text'` and feed each post (handle, text, engagement, permalink) into the web-candidate pool. A slow curl is **not** a missing key — do not treat a timeout as key-unavailable.\n\n   **Path B - WebFetch\u002FWebSearch fallback (last resort, lower quality):** only if the key is `KEY_UNSET`, or Path A returned a non-2xx \u002F empty body \u002F timeout. Attempt a WebFetch to a public X search URL like `https:\u002F\u002Fx.com\u002Fsearch?q=${topic}&f=live`, or a `site:x.com \"\u003Ctopic>\" after:\u003CFROM_DATE>` WebSearch; extract a few top posts and prefer results within the last 48h. Record the **true reason** (`key-unset` | `http-\u003Ccode>` | `empty` | `timeout`) in the log - never \"XAI_API_KEY unavailable\" when the key was set. If this also returns nothing, skip the X source for this run.\n3. **WebFetch on a topic-relevant aggregator** (only if WebSearch returned thin results): e.g. `https:\u002F\u002Fnews.ycombinator.com\u002F`, `https:\u002F\u002Fwww.reddit.com\u002Fr\u002F\u003Ctopic>\u002Ftop\u002F?t=day.json`, or a known feed for the topic.\n\nAim for **~15 raw web candidates** at this stage. More is fine; fewer than 8 is a warning sign — broaden your queries before moving on.\n\n### RSS source (active when `sources` is `rss` or `web+rss`)\n\nRead `memory\u002Ffeeds.yml` for the feed list. For **each feed** in `feeds.yml`:\n\n1. Fetch the RSS\u002FAtom XML: `curl -sL \"FEED_URL\"`. If curl fails, fall back to **WebFetch** on the same URL.\n2. Parse for entries published in the **last 24h** (check `\u003CpubDate>` or `\u003Cupdated>` tags).\n3. Extract **title, link, and description** for each new entry.\n\nDeduplicate against recent logs (see Phase 2). From all new entries, select the **5–7 most interesting** items — prioritize topics tracked in `memory\u002FMEMORY.md`, and apply the `${topic}` filter when set (title\u002Fdescription\u002Ftags must match). For each selected item, if the summary is too thin, use **WebFetch** to pull the full article, then write a 1–2 sentence note on why it matters. These become RSS candidates entering the shared pipeline below.\n\nIf `sources = rss` and there are **no new items across all feeds**, log `RSS_DIGEST_OK` in the run log and end without notifying.\n\n## Phase 2 — Filter (kill the noise)\n\nPool every candidate (web + RSS) and drop any that fails a single check:\n\n- **No source link?** Drop it. Every surviving item must have a clickable URL (article URL, feed entry link, or `https:\u002F\u002Fx.com\u002Fhandle\u002Fstatus\u002FID`).\n- **Older than 36 hours?** Drop it unless it's a still-developing story being re-surfaced for a new reason. (RSS entries are already scoped to the last 24h; this catches stale web results.)\n- **Pure speculation, hot take, or \"X reacts to Y\"?** Drop it. Keep things with a verifiable claim, named entity, number, release, or transaction.\n- **Already covered in the last 3 daily logs?** Check `memory\u002Flogs\u002F` for entries from the last 3 days. If the same story (same headline subject, same primary actor) appears, drop the duplicate unless there's a material new development to report.\n- **Two sources telling the same story?** Keep one — prefer the primary source (announcement post, repo release, official filing) over the recap. A web hit and an RSS entry on the same event count as duplicates; keep the primary.\n\nTarget: ~5–8 survivors after this pass.\n\n## Phase 3 — Distill and structure (force the shape)\n\nPick the **3–5 strongest** items. Lead with the **single most actionable** one — the item where a reader can do something today (subscribe, sell, fork, attend, apply, watch). Then descend by importance.\n\nFormat the digest exactly like this (**unified format** — used for `web`, `rss`, and `web+rss` runs):\n\n```\n*${var} - ${today}*\n\n_TL;DR: \u003Cone sentence covering the day's gravity. Concrete, no adjectives.>_\n\n1. *\u003CHeadline-style title, ≤90 chars>*\n   \u003C1-2 sentence summary. Lead with what happened, not who said it.>\n   Why it matters: \u003Cone short clause - concrete consequence, not vibes>\n   \u003Clink>\n\n2. *\u003CTitle>*\n   ...\n\n3. *\u003CTitle>*\n   ...\n\n(Optional, only if there's genuine secondary signal:)\n*Also worth a glance:* \u003C1-line bullet> · \u003C1-line bullet>\n```\n\n**Format rules:**\n- Markdown only. No emoji. No \"Here's your digest\" preamble.\n- Total length: **≤3000 chars** (the old 4000 was too loose — discipline forces cuts).\n- Every item: title + summary + link. Include a \"Why it matters\" line whenever you can state a concrete consequence (price impact, user-facing change, upstream dependency, deadline, precedent). If you can't write one without hand-waving, **omit the line** — do not replace it with filler like \"this could be significant\" or \"watch this space\".\n- On thin-news days where fewer than 3 items clear the bar: log `DIGEST_FETCH_EMPTY` (or `DIGEST_THIN` if 1–2 items survived) in the run log and **skip the notification** rather than padding.\n\n**Alternate RSS layout (RSS-only runs):** when `sources = rss`, you may instead group items by feed name if that reads better than a single ranked list — this preserves the original RSS-digest presentation:\n\n```\n*RSS Digest - ${today}*\n\n*Feed Name*\n- [Title](url) - summary\n- [Title](url) - summary\n\n*Feed Name*\n- [Title](url) - summary\n```\n\nThe grouped RSS layout stays **≤4000 chars**. Prefer the unified ranked format when the run mixes sources (`web+rss`) so the reader gets one prioritized list.\n\n## Phase 4 — Sanity-check (last pass before sending)\n\nBefore calling `.\u002Fnotify`, walk this checklist mentally:\n\n- [ ] Lead item is the most actionable one I have, not just the most dramatic.\n- [ ] Every link resolves to a real URL (no `[link]` placeholders, no truncated IDs).\n- [ ] No item is paraphrasing a hot take — each has a verifiable underlying fact.\n- [ ] No two items are the same story under different angles (including a web hit + an RSS entry on the same event).\n- [ ] Char count under the limit for the chosen format (3000 unified \u002F 4000 grouped RSS).\n- [ ] No emoji slipped in. No corporate hedging (\"could potentially\", \"it remains to be seen\").\n\nIf the digest fails any check, fix it before sending. If after filtering you have **fewer than 3 strong items**, do not pad — send a shorter \"thin day\" digest with whatever survived and a one-line note acknowledging it was a quiet news day. Do not invent or stretch.\n\n## Phase 5 — Send and log\n\n1. Write the digest body to a file (e.g. `\u002Ftmp\u002Fdigest.md`) with the Write tool (not a shell redirect) and send it with `.\u002Fnotify -f \u002Ftmp\u002Fdigest.md` (a long multi-line argv trips the sandbox; `-f` keeps it short).\n2. Append to `memory\u002Flogs\u002F${today}.md` under **one** `### digest` heading:\n   ```\n   ### digest (${var})\n   - Source mode: \u003Cweb | rss | web+rss>\n   - Sources used: \u003Clist - e.g. WebSearch, xAI API (api|fallback:reason), feeds.yml (Feed A, Feed B)>\n   - Raw candidates: \u003CN> (web \u003CNw> \u002F rss \u003CNr>), after filter: \u003CM>, sent: \u003CK>\n   - Lead item: \u003Ctitle>\n   - Notes: \u003Canything unusual - xAI fetch fallback + true reason (http-\u003Ccode>\u002Fempty\u002Ftimeout\u002Fkey-unset), thin day (DIGEST_THIN\u002FDIGEST_FETCH_EMPTY), RSS_DIGEST_OK, dedup against prior log>\n   ```\n3. Update `memory\u002FMEMORY.md` \"Recent Digests\" table with one row: date, topic (or `${var}`), key topics (3 short keywords).\n\n## Fetching the X signal\n\n`XAI_API_KEY` is **injected into this skill's environment** (declared in `requires:`) and is **present and valid**. The primary way to pull the X\u002FTwitter signal layer is a **direct `curl` to `https:\u002F\u002Fapi.x.ai\u002Fv1\u002Fresponses`** with `Authorization: Bearer {XAI_API_KEY}` (see Phase 1 → Path A). There is **no network sandbox** blocking this — just make the call.\n\nRules:\n\n1. **Check, don't assume.** Read the \"Keys set for this run\" line in your run notes, never a shell test (those are refused and cost a turn): `XAI_API_KEY` on that line = `KEY_PRESENT`, not on it = `KEY_UNSET`. If `KEY_PRESENT` (it will be), Path A is required before any fallback.\n2. **Allow enough time.** Grok's `x_search` typically takes 30–120s (it searches X live). Set the Bash tool's `timeout` to at least **180000 (180s)** for the curl, and keep **`--max-time 150`** on the curl itself so it fails cleanly rather than hanging. A slow curl is **not** a missing key — never treat a timeout as key-unavailable.\n3. **Capture the HTTP status** and parse the body with the standard `jq` extractor (`jq -r '.output[] | select(.type == \"message\") | .content[] | select(.type == \"output_text\") | .text'`). `HTTP=200` + non-empty body → use it.\n4. **Fall back only on a real failure**, recording the **true reason**: `key-unset` (only if step 1 said `KEY_UNSET`), `http-\u003Ccode>` (non-2xx), `empty` (200 but nothing parsed), or `timeout` (curl exceeded `--max-time`). Never log \"XAI_API_KEY unavailable\" when the key was set.\n\nWebFetch \u002F WebSearch are **last-resort fallbacks only** for the X signal (lower quality — WebSearch favours older high-engagement posts). Never reach for them while the key works.\n\n**RSS feeds & public aggregators (no auth):** fetch with `curl -sL`; if that fails intermittently, the built-in **WebFetch** tool is a reliable fallback for any feed URL, aggregator (HN, Reddit JSON, news sites), or article. This is a convenience fallback for unauthenticated URLs, not a sandbox workaround. A digest built from WebSearch + reachable RSS alone is still valid — note it in the log so health checks can spot the pattern.\n\n## Environment Variables Required\n\n- `XAI_API_KEY` — X.AI API key for Grok's `x_search`. Declared in `requires:`, so it is **injected into this skill's environment** and is the primary path for the X signal layer (direct `curl`; see **Fetching the X signal**). Optional overall — digest still works on web + RSS sources alone if it is ever unset.\n- Notification channels configured via repo secrets (see CLAUDE.md).\n\n## Constraints\n\n- Never send a digest with placeholder links or \"TBD\" sections.\n- Never invent items to hit a target count. Fewer good items beats more weak ones.\n- Never repeat a story already in the last 3 days of `memory\u002Flogs\u002F` unless there's a material update — and say so explicitly when you do.\n","\u003C!-- autoresearch: variation B — curatorial discipline (filter → distill → structure → sanity-check) folded with direct-curl xAI + web\u002FRSS inputs and memory-aware dedup; RSS feed-reading + item-selection absorbed from rss-digest as an additional source class -->\n\n> **${var}** — Selects the digest's topic and which source classes feed it. Grammar:\n> - `\"\"` (empty) → **digest's default sources** (WebSearch + xAI\u002FGrok + aggregators), no topic filter — a broad daily digest.\n> - `\"\u003Ctopic>\"` → topic-focused digest on the default web sources, filtered to `\u003Ctopic>` (e.g. `\"solana\"`, `\"AI agents\"`, `\"rust\"`).\n> - `\"rss\"` → **RSS-only**: pull feeds from `memory\u002Ffeeds.yml`, no topic filter.\n> - `\"rss: \u003Ctopic>\"` → RSS-only, filtered to `\u003Ctopic>` (e.g. `\"rss: rust\"`).\n> - `\"\u003Ctopic> +rss\"` → default web sources **and** RSS feeds combined, both filtered to `\u003Ctopic>` (e.g. `\"AI agents +rss\"`).\n\nToday is ${today}. Generate and send a daily **${var}** digest.\n\nThe whole point of a digest is **signal, not volume**. A reader skimming for 60 seconds should walk away with three things they didn't know that morning and one of them should change a decision they'd make this week. Anything that doesn't clear that bar gets cut.\n\n## Preamble — orient and parse the selector\n\n1. Read `memory\u002FMEMORY.md` for high-level context and tracked topics, and scan the last 3 days of `memory\u002Flogs\u002F` so you can dedup against anything already reported.\n2. Parse `${var}` into **`{topic, sources}`**:\n   - Strip a trailing `+rss` → adds RSS to the default web sources. Remainder is the `topic`.\n   - A leading `rss:` (or the bare token `rss`) → **RSS-only** source set; text after the colon is the `topic` (empty = no filter).\n   - Otherwise the whole string is the `topic` and `sources = default web` (empty string = default web, no topic filter).\n   - Resulting `sources` is one of: `web` (default), `rss` (RSS-only), or `web+rss` (both).\n3. The `topic`, when non-empty, is a filter applied to **every** source class — web queries are scoped to it and RSS items must match it (title\u002Fdescription\u002Ftags). Empty topic = keep all relevant items.\n\n## Config (RSS source)\n\nWhen `sources` includes `rss`, this skill reads feed URLs from `memory\u002Ffeeds.yml`. If the file doesn't exist yet, create it (write mode) with the shape below, or — if you have nothing to seed it with — log a one-line note and treat the RSS source as empty for this run.\n\n```yaml\n# memory\u002Ffeeds.yml\nfeeds:\n  - name: Example Feed\n    url: https:\u002F\u002Fexample.com\u002Frss\n  - name: Another Feed\n    url: https:\u002F\u002Fexample.com\u002Fatom.xml\n```\n\n## Phase 1 — Gather (cast a wide net)\n\nPull from the source classes selected by `${var}`. Never rely on a single one — if `sources = web`, use at least two of the web classes below; if `sources = web+rss`, RSS counts as one class and you still want a second.\n\n### Web sources (active when `sources` is `web` or `web+rss`)\n\n1. **WebSearch** (built-in) — run 2 distinct queries:\n   - `\"${topic}\" news ${today}` (broad). If `topic` is empty, run a general query for the day's notable stories in the operator's tracked areas (from `memory\u002FMEMORY.md`).\n   - One narrower query you choose based on `${topic}` (e.g. for \"solana\" → `\"solana\" launches OR funding OR exploit ${today}`; for \"AI agents\" → `\"agent framework\" OR \"agentic\" release ${today}`).\n2. **xAI x_search via Grok** — pulls the X\u002FTwitter signal layer. `XAI_API_KEY` is injected into this skill's environment (declared in `requires:`) and is the **primary** path; see **Fetching the X signal** below for the full contract (attempt the curl before any fallback, set the Bash tool `timeout` ≥180000, record the true failure reason).\n\n   **Path A - X.AI API (primary):** a direct `curl` to the Responses API. First confirm the key as **Fetching the X signal** rule 1 says (the run notes' \"Keys set for this run\" line, no shell test); if `KEY_PRESENT` (it will be), this path is required. When you run the curl, set the Bash tool's `timeout` to at least `180000`.\n\n   **Shell shape.** Each Bash call is one allowed command (pipes are fine). The run refuses a call that sets a variable (`FROM_DATE=$(date ...)`, `PROMPT=\"...\"`, `HTTP=$(.\u002Fsecretcurl ...)`), uses `[ ... ] &&` tests, or joins commands with `;`, `&&` or `||`, and each refusal costs a model turn. So get each date with a bare `date` call, write the printed values and the topic into later commands literally (the `\u003CFROM_DATE>`, `\u003CTO_DATE>`, `\u003CTOPIC>` placeholders), and build the payload with one `jq -n` call piped straight into one `.\u002Fsecretcurl` call. A `>` redirect into a file and a `$name` in a jq filter (`--arg p ... '$p'`) are refused too, so the topic and dates go into the filter as plain JSON strings (a `\"` inside the topic becomes `\\\"`, and reword any `'`).\n\n   Get the window dates first, each `date` as its own Bash call:\n   ```bash\n   date -u -d yesterday +%Y-%m-%d\n   ```\n   ```bash\n   date -u +%Y-%m-%d\n   ```\n   The first prints FROM_DATE, the second TO_DATE. Build the payload with the prompt inline and pipe it into the send in the same call (it prints the HTTP status; `.\u002Fsecretcurl` also prints an `http=... bytes=...` line on stderr); `\u003CTOPIC>` is the parsed `topic`, or `the most notable technology, AI, and crypto stories today` when the topic is empty:\n   ```bash\n   jq -n '{model:\"grok-4.7\", input:[{role:\"user\",content:\"Search X for substantive, recent posts about: \u003CTOPIC>. Date range: \u003CFROM_DATE> to \u003CTO_DATE>. Return up to 10 high-signal posts - prioritize verifiable claims, launches, funding, releases, exploits, or hard data over hot takes. For EACH post return: @handle, the full text, date posted, exact engagement counts (likes, retweets, replies; 0 if unknown), and the direct link https:\u002F\u002Fx.com\u002Fhandle\u002Fstatus\u002FID. Return a numbered list.\"}], tools:[{type:\"x_search\",from_date:\"\u003CFROM_DATE>\",to_date:\"\u003CTO_DATE>\"}]}' \\\n     | .\u002Fsecretcurl -s -o \u002Ftmp\u002Fxai-digest.json -w '%{http_code}' --max-time 150 -X POST \"https:\u002F\u002Fapi.x.ai\u002Fv1\u002Fresponses\" \\\n     -H \"Content-Type: application\u002Fjson\" -H \"Authorization: Bearer {XAI_API_KEY}\" --data-binary @-\n   ```\n   On `HTTP=200` with a non-empty body, parse it with `jq -r '.output[] | select(.type == \"message\") | .content[] | select(.type == \"output_text\") | .text'` and feed each post (handle, text, engagement, permalink) into the web-candidate pool. A slow curl is **not** a missing key — do not treat a timeout as key-unavailable.\n\n   **Path B - WebFetch\u002FWebSearch fallback (last resort, lower quality):** only if the key is `KEY_UNSET`, or Path A returned a non-2xx \u002F empty body \u002F timeout. Attempt a WebFetch to a public X search URL like `https:\u002F\u002Fx.com\u002Fsearch?q=${topic}&f=live`, or a `site:x.com \"\u003Ctopic>\" after:\u003CFROM_DATE>` WebSearch; extract a few top posts and prefer results within the last 48h. Record the **true reason** (`key-unset` | `http-\u003Ccode>` | `empty` | `timeout`) in the log - never \"XAI_API_KEY unavailable\" when the key was set. If this also returns nothing, skip the X source for this run.\n3. **WebFetch on a topic-relevant aggregator** (only if WebSearch returned thin results): e.g. `https:\u002F\u002Fnews.ycombinator.com\u002F`, `https:\u002F\u002Fwww.reddit.com\u002Fr\u002F\u003Ctopic>\u002Ftop\u002F?t=day.json`, or a known feed for the topic.\n\nAim for **~15 raw web candidates** at this stage. More is fine; fewer than 8 is a warning sign — broaden your queries before moving on.\n\n### RSS source (active when `sources` is `rss` or `web+rss`)\n\nRead `memory\u002Ffeeds.yml` for the feed list. For **each feed** in `feeds.yml`:\n\n1. Fetch the RSS\u002FAtom XML: `curl -sL \"FEED_URL\"`. If curl fails, fall back to **WebFetch** on the same URL.\n2. Parse for entries published in the **last 24h** (check `\u003CpubDate>` or `\u003Cupdated>` tags).\n3. Extract **title, link, and description** for each new entry.\n\nDeduplicate against recent logs (see Phase 2). From all new entries, select the **5–7 most interesting** items — prioritize topics tracked in `memory\u002FMEMORY.md`, and apply the `${topic}` filter when set (title\u002Fdescription\u002Ftags must match). For each selected item, if the summary is too thin, use **WebFetch** to pull the full article, then write a 1–2 sentence note on why it matters. These become RSS candidates entering the shared pipeline below.\n\nIf `sources = rss` and there are **no new items across all feeds**, log `RSS_DIGEST_OK` in the run log and end without notifying.\n\n## Phase 2 — Filter (kill the noise)\n\nPool every candidate (web + RSS) and drop any that fails a single check:\n\n- **No source link?** Drop it. Every surviving item must have a clickable URL (article URL, feed entry link, or `https:\u002F\u002Fx.com\u002Fhandle\u002Fstatus\u002FID`).\n- **Older than 36 hours?** Drop it unless it's a still-developing story being re-surfaced for a new reason. (RSS entries are already scoped to the last 24h; this catches stale web results.)\n- **Pure speculation, hot take, or \"X reacts to Y\"?** Drop it. Keep things with a verifiable claim, named entity, number, release, or transaction.\n- **Already covered in the last 3 daily logs?** Check `memory\u002Flogs\u002F` for entries from the last 3 days. If the same story (same headline subject, same primary actor) appears, drop the duplicate unless there's a material new development to report.\n- **Two sources telling the same story?** Keep one — prefer the primary source (announcement post, repo release, official filing) over the recap. A web hit and an RSS entry on the same event count as duplicates; keep the primary.\n\nTarget: ~5–8 survivors after this pass.\n\n## Phase 3 — Distill and structure (force the shape)\n\nPick the **3–5 strongest** items. Lead with the **single most actionable** one — the item where a reader can do something today (subscribe, sell, fork, attend, apply, watch). Then descend by importance.\n\nFormat the digest exactly like this (**unified format** — used for `web`, `rss`, and `web+rss` runs):\n\n```\n*${var} - ${today}*\n\n_TL;DR: \u003Cone sentence covering the day's gravity. Concrete, no adjectives.>_\n\n1. *\u003CHeadline-style title, ≤90 chars>*\n   \u003C1-2 sentence summary. Lead with what happened, not who said it.>\n   Why it matters: \u003Cone short clause - concrete consequence, not vibes>\n   \u003Clink>\n\n2. *\u003CTitle>*\n   ...\n\n3. *\u003CTitle>*\n   ...\n\n(Optional, only if there's genuine secondary signal:)\n*Also worth a glance:* \u003C1-line bullet> · \u003C1-line bullet>\n```\n\n**Format rules:**\n- Markdown only. No emoji. No \"Here's your digest\" preamble.\n- Total length: **≤3000 chars** (the old 4000 was too loose — discipline forces cuts).\n- Every item: title + summary + link. Include a \"Why it matters\" line whenever you can state a concrete consequence (price impact, user-facing change, upstream dependency, deadline, precedent). If you can't write one without hand-waving, **omit the line** — do not replace it with filler like \"this could be significant\" or \"watch this space\".\n- On thin-news days where fewer than 3 items clear the bar: log `DIGEST_FETCH_EMPTY` (or `DIGEST_THIN` if 1–2 items survived) in the run log and **skip the notification** rather than padding.\n\n**Alternate RSS layout (RSS-only runs):** when `sources = rss`, you may instead group items by feed name if that reads better than a single ranked list — this preserves the original RSS-digest presentation:\n\n```\n*RSS Digest - ${today}*\n\n*Feed Name*\n- [Title](url) - summary\n- [Title](url) - summary\n\n*Feed Name*\n- [Title](url) - summary\n```\n\nThe grouped RSS layout stays **≤4000 chars**. Prefer the unified ranked format when the run mixes sources (`web+rss`) so the reader gets one prioritized list.\n\n## Phase 4 — Sanity-check (last pass before sending)\n\nBefore calling `.\u002Fnotify`, walk this checklist mentally:\n\n- [ ] Lead item is the most actionable one I have, not just the most dramatic.\n- [ ] Every link resolves to a real URL (no `[link]` placeholders, no truncated IDs).\n- [ ] No item is paraphrasing a hot take — each has a verifiable underlying fact.\n- [ ] No two items are the same story under different angles (including a web hit + an RSS entry on the same event).\n- [ ] Char count under the limit for the chosen format (3000 unified \u002F 4000 grouped RSS).\n- [ ] No emoji slipped in. No corporate hedging (\"could potentially\", \"it remains to be seen\").\n\nIf the digest fails any check, fix it before sending. If after filtering you have **fewer than 3 strong items**, do not pad — send a shorter \"thin day\" digest with whatever survived and a one-line note acknowledging it was a quiet news day. Do not invent or stretch.\n\n## Phase 5 — Send and log\n\n1. Write the digest body to a file (e.g. `\u002Ftmp\u002Fdigest.md`) with the Write tool (not a shell redirect) and send it with `.\u002Fnotify -f \u002Ftmp\u002Fdigest.md` (a long multi-line argv trips the sandbox; `-f` keeps it short).\n2. Append to `memory\u002Flogs\u002F${today}.md` under **one** `### digest` heading:\n   ```\n   ### digest (${var})\n   - Source mode: \u003Cweb | rss | web+rss>\n   - Sources used: \u003Clist - e.g. WebSearch, xAI API (api|fallback:reason), feeds.yml (Feed A, Feed B)>\n   - Raw candidates: \u003CN> (web \u003CNw> \u002F rss \u003CNr>), after filter: \u003CM>, sent: \u003CK>\n   - Lead item: \u003Ctitle>\n   - Notes: \u003Canything unusual - xAI fetch fallback + true reason (http-\u003Ccode>\u002Fempty\u002Ftimeout\u002Fkey-unset), thin day (DIGEST_THIN\u002FDIGEST_FETCH_EMPTY), RSS_DIGEST_OK, dedup against prior log>\n   ```\n3. Update `memory\u002FMEMORY.md` \"Recent Digests\" table with one row: date, topic (or `${var}`), key topics (3 short keywords).\n\n## Fetching the X signal\n\n`XAI_API_KEY` is **injected into this skill's environment** (declared in `requires:`) and is **present and valid**. The primary way to pull the X\u002FTwitter signal layer is a **direct `curl` to `https:\u002F\u002Fapi.x.ai\u002Fv1\u002Fresponses`** with `Authorization: Bearer {XAI_API_KEY}` (see Phase 1 → Path A). There is **no network sandbox** blocking this — just make the call.\n\nRules:\n\n1. **Check, don't assume.** Read the \"Keys set for this run\" line in your run notes, never a shell test (those are refused and cost a turn): `XAI_API_KEY` on that line = `KEY_PRESENT`, not on it = `KEY_UNSET`. If `KEY_PRESENT` (it will be), Path A is required before any fallback.\n2. **Allow enough time.** Grok's `x_search` typically takes 30–120s (it searches X live). Set the Bash tool's `timeout` to at least **180000 (180s)** for the curl, and keep **`--max-time 150`** on the curl itself so it fails cleanly rather than hanging. A slow curl is **not** a missing key — never treat a timeout as key-unavailable.\n3. **Capture the HTTP status** and parse the body with the standard `jq` extractor (`jq -r '.output[] | select(.type == \"message\") | .content[] | select(.type == \"output_text\") | .text'`). `HTTP=200` + non-empty body → use it.\n4. **Fall back only on a real failure**, recording the **true reason**: `key-unset` (only if step 1 said `KEY_UNSET`), `http-\u003Ccode>` (non-2xx), `empty` (200 but nothing parsed), or `timeout` (curl exceeded `--max-time`). Never log \"XAI_API_KEY unavailable\" when the key was set.\n\nWebFetch \u002F WebSearch are **last-resort fallbacks only** for the X signal (lower quality — WebSearch favours older high-engagement posts). Never reach for them while the key works.\n\n**RSS feeds & public aggregators (no auth):** fetch with `curl -sL`; if that fails intermittently, the built-in **WebFetch** tool is a reliable fallback for any feed URL, aggregator (HN, Reddit JSON, news sites), or article. This is a convenience fallback for unauthenticated URLs, not a sandbox workaround. A digest built from WebSearch + reachable RSS alone is still valid — note it in the log so health checks can spot the pattern.\n\n## Environment Variables Required\n\n- `XAI_API_KEY` — X.AI API key for Grok's `x_search`. Declared in `requires:`, so it is **injected into this skill's environment** and is the primary path for the X signal layer (direct `curl`; see **Fetching the X signal**). Optional overall — digest still works on web + RSS sources alone if it is ever unset.\n- Notification channels configured via repo secrets (see CLAUDE.md).\n\n## Constraints\n\n- Never send a digest with placeholder links or \"TBD\" sections.\n- Never invent items to hit a target count. Fewer good items beats more weak ones.\n- Never repeat a story already in the last 3 days of `memory\u002Flogs\u002F` unless there's a material update — and say so explicitly when you do.\n",{"name":5,"metadata":40,"description":7},{"var":41,"mode":26,"tags":42,"title":43,"category":32,"requires":44},"",[22,30],"Digest",[45],"XAI_API_KEY?","main","ba8ac7e88c8ff955526557369fadc27eca9c1037","skills\u002Fdigest",4,"https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent","https:\u002F\u002Ft.me\u002Faeon_agent","https:\u002F\u002Fgithub.com\u002Faeonfun","https:\u002F\u002Fwww.aeon.fun\u002F","The most autonomous agent framework.","2026-11-14 00:38:03",{"source":8,"skillName":5,"commands":57,"prompt":67,"zipAvailable":68},[58,61,64],{"tool":59,"command":60},"npx","npx skills add aeonfun\u002Faeon-agent --skill digest",{"tool":62,"command":63},"bunx","bunx skills add aeonfun\u002Faeon-agent --skill digest",{"tool":65,"command":66},"pnpm","pnpm dlx skills add aeonfun\u002Faeon-agent --skill digest","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fdigest\u002FSKILL.md",true,[],[71,106,126,152,177,199],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":87,"publishedAt":35},"01M6J4ASEHAQYBZ5TZT4RVAM1Z","you-web-search","Web search using You.com Search API with high-quality, cited results — runs keyless out of the box; YDC_API_KEY unlocks higher limits and real-time web crawling","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fyou-web-search\u002FSKILL.md",[77,82],{"categoryNo":78,"categoryName":79,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":83,"categoryName":84,"taxonomyType":85,"taxonomyTypeDesc":86},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[88,89,90,91,28,92,93,31,94,95,96,97,32,98,99,100,101,102,103,104,105],"web search","search","you","you web search","time","only","web crawling","read","read-only","real-time","keyless","with","research","results","high","quality","box","limits",{"skillNo":107,"name":108,"nameZh":6,"description":109,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":35},"01M6J4AK1ZAR1HS398K0CWWBFX","write-tweet","Multi-format tweet studio - standalone drafts (10 across 5 size tiers), a 5-10 tweet thread, or 10 remixes of past tweets, selected via ${var}","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fwrite-tweet\u002FSKILL.md",[],[26,113,114,115,116,31,117,27,118,119,32,120,121,122,123,124,125,22],"write tweet","standalone","social","studio","across","past","drafts","twitter","thread","multi","size","via","format",{"skillNo":127,"name":128,"nameZh":6,"description":129,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":135,"publishedAt":35},"01M6J4AFYF6ZHHNQV6H7N92S8K","weekly-aeoncard","Build a weekly token-consumption recap image from memory\u002Ftoken-usage.csv - this week + all-time totals, top skills, rendered as a shareable SVG card.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fweekly-aeoncard\u002FSKILL.md",[132],{"categoryNo":133,"categoryName":134,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000016","图像与多媒体",[136,137,33,138,139,140,141,92,142,143,144,145,146,147,148,149,150,26,151,12],"weekly","weekly aeoncard","usage","SVG","memory","card","Meta","image","recap","csv","monitoring","notifications","productivity","top","build","token",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":158,"publishedAt":35},"01M6J4AAZF6D0YQ8NK92NYJSRX","vuln-tracker","One lifecycle poll over everything vuln-scanner produces - PR and advisory status, PVR triage transitions, and pending-disclosure aging, with a stars-secured impact headline and one action queue.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-tracker\u002FSKILL.md",[],[159,160,142,161,162,26,151,163,164,165,166,167,168,169,170,171,172,99,173,174,175,176],"tracker","vuln tracker","impact","scanner","status","action","security","pr","stars","lifecycle","dev","gh","aging","advisory","github","transitions","triage","global",{"skillNo":178,"name":179,"nameZh":6,"description":180,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":181,"categories":182,"keywords":186,"publishedAt":35},"01M6J4A3ZM65VJNT4VEA9ESV39","vuln-scanner","Audit trending repos for real security vulnerabilities and disclose responsibly - scan and route findings (PVR \u002F dependency PR), re-submit queued advisories, and send armed email disclosures","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-scanner\u002FSKILL.md",[183],{"categoryNo":184,"categoryName":185,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000005","邮件与沟通",[162,187,188,189,190,142,191,165,192,193,194,169,173,195,176,196,31,197,198,21],"vuln scanner","resend","re","trending","repos","email","for","scan","vulnerabilities","submit","route","audit",{"skillNo":200,"name":201,"nameZh":6,"description":202,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":203,"categories":204,"keywords":206,"publishedAt":35},"01M6J49YHDRN3JNAN9GJW810P2","video-script","Turn a repo, product page, or update into a recording-ready video script in a receipts-first format - verifies every claim against live sources, then writes timestamped VO + on-screen direction with a plain-language glossary, assets checklist, anti-tells, and a verify-before-recording list","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon-agent\u002Fblob\u002Fmain\u002Fskills\u002Fvideo-script\u002FSKILL.md",[205],{"categoryNo":133,"categoryName":134,"taxonomyType":80,"taxonomyTypeDesc":81},[207,208,209,210,211,212,213,214,215,32,216,217,218,99,219,220,221,222,223,125],"Video","video script","script","recording","sources","ready","direction","page","repo","claim","plain-language","glossary","first","verify","product","screen","update"]