[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CZ71BWW0MMMB2G0D0TFE7M":3,"skill-same:01M6CZ71BWW0MMMB2G0D0TFE7M":69},{"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":51,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M6CZ71BWW0MMMB2G0D0TFE7M","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","aeonfun","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F287715280?v=4","01M6CZ23C6MXGMF4N0V1DZ1247","aeon","01M6CZ23GMZP1BR424J8Y8K3DG",791,"MIT","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon\u002Fblob\u002Fmain\u002Fskills\u002Fdigest\u002FSKILL.md",[],[5,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"basics","signal","send","source","input","atom","as","news","web","write","xai","rss","api","feeds","content","generate","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":28,"tags":42,"title":43,"category":19,"requires":44},"",[33,26],"Digest",[45],"XAI_API_KEY?","main","504a21d53c0c9b485f6e90c43db83000825e1d3c","skills\u002Fdigest",308,"https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon","https:\u002F\u002Fwww.aeon.fun\u002F","https:\u002F\u002Fgithub.com\u002Faeonfun","The most autonomous agent framework.","2026-11-13 00:01:44",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add aeonfun\u002Faeon --skill digest",{"tool":61,"command":62},"bunx","bunx skills add aeonfun\u002Faeon --skill digest",{"tool":64,"command":65},"pnpm","pnpm dlx skills add aeonfun\u002Faeon --skill digest","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon\u002Fblob\u002Fmain\u002Fskills\u002Fdigest\u002FSKILL.md",true,[],[70,94,123,147,170,190],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":35},"01M70R8RA3SFXK2D1XP7XTJGAQ","sns-digest","Digest Internet Computer governance - new and open NNS proposals and SNS DAO proposals - so the operator sees what is up for a vote without watching the dashboard. Fully public, no secret; fires only when there is something new to report.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon\u002Fblob\u002Fmain\u002Fskills\u002Fsns-digest\u002FSKILL.md",[],[77,5,78,79,80,81,82,83,84,85,86,87,31,88,89,90,91,92,93,28],"sns","sns digest","proposals","for","up","open","icp","only","new","internet","when","notifications","dao","crypto","dashboard","governance","report",{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":105,"publishedAt":35},"01M70R5BMYW94KZ3XQM3MSWPC0","icp-deploy-check","Pre-deploy safety checklist for an Internet Computer Rust canister - read a local\u002Fcloned canister repo and verify the deploy-time footguns from the canister-security checklist (controller backup, anonymous-principal rejection, no mainnet fetch_root_key, upgrade safety) before it ships. Static, read-only, no network and no mainnet action.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon\u002Fblob\u002Fmain\u002Fskills\u002Ficp-deploy-check\u002FSKILL.md",[100],{"categoryNo":101,"categoryName":102,"taxonomyType":103,"taxonomyTypeDesc":104},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",[83,106,107,108,109,110,111,112,113,114,115,84,86,116,117,118,119,120,121,122],"icp deploy check","deploy check","check","deploy","fetch","anonymous","static","time","rust","local","upgrade","controller","read","Safety","network","action","security",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":137,"publishedAt":35},"01M70R312QZ9T236SZHGX8A8CM","canister-status","Monitor Internet Computer canisters for control-plane changes - a new controller, a module-hash change (upgrade), or a canister going missing from the public index - and alert the moment one happens. Uses only the public IC dashboard API.","https:\u002F\u002Fgithub.com\u002Faeonfun\u002Faeon\u002Fblob\u002Fmain\u002Fskills\u002Fcanister-status\u002FSKILL.md",[129,132],{"categoryNo":130,"categoryName":131,"taxonomyType":103,"taxonomyTypeDesc":104},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[138,139,80,140,141,83,142,84,85,86,116,143,144,31,88,117,90,145,91,146],"canister status","status","missing","monitor","module","alert","plane","change","changes",{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":155,"publishedAt":35},"01M6FVEXRG77BT4VH94P0S1TDT","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\u002Fblob\u002Fmain\u002Fskills\u002Fyou-web-search\u002FSKILL.md",[153,154],{"categoryNo":130,"categoryName":131,"taxonomyType":103,"taxonomyTypeDesc":104},{"categoryNo":133,"categoryName":134,"taxonomyType":135,"taxonomyTypeDesc":136},[156,157,158,159,27,113,84,31,160,118,161,162,19,163,164,165,166,167,168,169],"web search","search","you","you web search","web crawling","read-only","real-time","keyless","with","research","results","quality","high","limits",{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":35},"01M6FVER8Y2B0H7H26G4GTYK6N","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\u002Fblob\u002Fmain\u002Fskills\u002Fwrite-tweet\u002FSKILL.md",[],[28,177,178,179,31,180,29,19,181,182,183,184,33,185,186,187,188,189],"write tweet","social","studio","across","twitter","multi","via","format","standalone","past","drafts","thread","size",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":199,"publishedAt":35},"01M6FVENCJ0HY2TEE6CNZN4F96","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\u002Fblob\u002Fmain\u002Fskills\u002Fweekly-aeoncard\u002FSKILL.md",[196],{"categoryNo":197,"categoryName":198,"taxonomyType":103,"taxonomyTypeDesc":104},"01JBSEEDCAT000000000000016","图像与多媒体",[200,201,25,202,203,204,205,113,206,207,208,209,210,88,211,212,28,213,12,214],"weekly aeoncard","weekly","usage","SVG","memory","card","Meta","image","recap","csv","monitoring","productivity","build","token","top"]