[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J4HMXACYN289MDJ3YKYC2R":3,"skill-same:01M6J4HMXACYN289MDJ3YKYC2R":64},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":43,"repositoryHtmlUrl":44,"repositoryHomepageUrl":45,"ownerHtmlUrl":46,"ownerBlogUrl":47,"ownerDescription":48,"updatedAt":49,"install":50,"resources":63},"01M6J4HMXACYN289MDJ3YKYC2R","price-alert",null,"Fire when the tracked token does something - new ATH, sharp 1h move, or operator-set target crossed. Silent on normal days.","MiroShark\u002Fmiroshark-aeon","MiroShark","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F304594232?v=4","01M6J466B8NNAZ4H6NNK3N3TT5","01M6J466FXNNWH622S3B9PC496",20,"MIT","https:\u002F\u002Fgithub.com\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fprice-alert\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30],"price alert","alert","price","SET","move","basics","crypto","sharp","token","operator","normal","new","when","2026-11-10 10:12:30",false,"---\nname: price-alert\ndescription: Fire when the tracked token does something - new ATH, sharp 1h move, or operator-set target crossed. Silent on normal days.\nmetadata:\n  title: Price Alert\n  category: basics\n  var: \"\"\n  tags:\n    - crypto\n---\n> **${var}** — Optional. Pass one or more `target_price` levels (comma-separated USD numbers, scientific notation allowed) to fire a one-time alert when the price crosses any of them. Empty = only ATH and sharp-move gates run. Pass `dry-run` to skip notify (state still updates). Pass `set-target:\u003Cprice>` — the shape the Telegram force-reply sends (step 7) — to register a target and get a one-line confirmation.\n\nToday is ${today}. The scheduled digests (`digest`, `token-movers`, ...) run at fixed hours. None of them tell the operator \"the price just hit a new high\" or \"the token moved 28% in the last hour\" — both are events that warrant attention the moment they happen, not 14 hours later in a daily digest. This skill closes that window.\n\n## Why this exists\n\nScheduled daily digests are calm summaries. Real moves need real-time signal. Three classes of move are worth a same-day ping:\n- **New all-time high.** The single most narrative-shaping price event a token can have. Worth marking on the timeline regardless of size.\n- **Sharp 1h moves (±20%).** Either a buyer wave or a liquidation cascade — both change what the operator does next (post about it, watch for follow-through, check the chart).\n- **Operator-set target crossings.** The operator may want to know when price clears $X (often a personal stretch goal or a level tied to a tweet\u002Flaunch). One alert per target per direction.\n\nEverything else is noise not worth a same-day ping.\n\n## Config\n\nReads:\n- `memory\u002FMEMORY.md` \"Tracked Token\" section — the tracked token's contract\u002Fchain. If absent, exit silently.\n- `memory\u002Ftopics\u002Fprice-alert-state.json` — last-known ATH, last-alert timestamps per event type, target-crossing history. Created with defaults on first run.\n\nWrites:\n- `memory\u002Ftopics\u002Fprice-alert-state.json` — updated state every run.\n- `memory\u002Flogs\u002F${today}.md` — one log block per run, even on `OK`.\n- Notification via `.\u002Fnotify` — only when a gate fires.\n\nNo new secrets. Uses keyless DexScreener; falls back to WebFetch when a public `curl` GET is flaky (there is no network sandbox).\n\n## State schema\n\n```json\n{\n  \"contract\": \"0xbf8e8f0e8866a7052f948c16508644347c57aba3\",\n  \"chain\": \"base\",\n  \"ath\": {\n    \"price_usd\": 0.000003757,\n    \"observed_at\": \"2026-05-10T19:00:00Z\",\n    \"announced_at\": \"2026-05-10T19:30:00Z\"\n  },\n  \"last_alerts\": {\n    \"ath\": \"2026-05-10T19:30:00Z\",\n    \"sharp_move\": null,\n    \"target_hit\": null\n  },\n  \"targets\": {\n    \"0.000005\": {\n      \"side\": \"above\",\n      \"first_seen_below_at\": \"2026-05-11T08:00:00Z\",\n      \"hit_at\": null,\n      \"announced_at\": null\n    }\n  }\n}\n```\n\nKey invariants:\n- `ath.price_usd` only ever monotonically increases. If a run sees a lower price than the stored ATH, leave the ATH alone.\n- `last_alerts.*` powers the 4h dedup window. Each gate has its own clock.\n- `targets.${price}.side` is `above` if `current_price \u003C target` when the target was first observed (operator is waiting for the price to climb to it), and `below` otherwise. Set once, never flipped.\n- `targets.${price}.hit_at` is set the run the cross happens. `announced_at` is set the run the notification fires. They differ only if the run lands inside a dedup window — but target alerts never re-fire, so `hit_at == announced_at` in practice.\n\n## Steps\n\n### 1. Parse var\n\n- If `${var}` starts with `set-target:` → set `FROM_REPLY=1`, `MODE=execute`. Strip the `set-target:` prefix (`${var#set-target:}`); the remainder is the target(s) list, parsed exactly like the comma-split below. This is the shape `scripts\u002Ftelegram-route.sh` sends when the operator replies to the step-7 force-reply prompt. The run proceeds normally (register the target, don't fire on first observation) and closes the loop with a confirmation in step 7.\n- Else if `${var}` matches `^dry-run` → `MODE=dry-run`. Strip the prefix; remainder (if any) is treated as targets.\n- Otherwise `MODE=execute`.\n- Split the remainder on `,` (commas) and strip whitespace.\n- For each token: if it parses as a positive float (scientific notation OK, e.g. `5e-6`), include it. Reject zero \u002F negative \u002F non-numeric tokens and log `PRICE_ALERT_BAD_TARGET: ${token}` — continue with the surviving targets.\n- If after filtering the remainder was non-empty but yielded zero valid targets → log `PRICE_ALERT_BAD_VAR: ${var}` and exit. Normally no notify — **but if `FROM_REPLY=1`**, first close the loop with `.\u002Fnotify \"Couldn't read \\\"${var#set-target:}\\\" as a price. Reply with a number like 0.000005.\"` (a force-reply always deserves an acknowledgement).\n- If the remainder was empty, `TARGETS=()` is fine — ATH and sharp-move gates still run.\n\n### 2. Resolve tracked token\n\nEach step below is one Bash call (see **Shell shape** in the Network note):\n\n```bash\nmkdir -p memory\u002Ftopics\n```\n\nCheck for `memory\u002Ftopics\u002Fprice-alert-state.json` with Glob. If it does not exist, create it with Write:\n\n```json\n{\"contract\":null,\"chain\":null,\"ath\":null,\"last_alerts\":{\"ath\":null,\"sharp_move\":null,\"target_hit\":null},\"targets\":{}}\n```\n\nParse the \"Tracked Token\" table in `memory\u002FMEMORY.md`. Pull `CONTRACT` (column 2 of the first data row) and `CHAIN` (column 3, lowercased). If the section is missing, the table is empty, or the contract field doesn't match `^0x[0-9a-fA-F]{40}$` → log `PRICE_ALERT_NO_TOKEN` and exit (no notify, no state write).\n\nIf the state file's `contract` is set and differs from the resolved contract → log `PRICE_ALERT_TOKEN_CHANGED` and reset the state file to defaults with the new contract\u002Fchain. ATH starts over with the new token.\n\n### 3. Fetch current price (DexScreener primary)\n\nOne call, with the contract written in literally:\n\n```bash\ncurl -fsS -o \u002Ftmp\u002Fprice-alert-dex.json \"https:\u002F\u002Fapi.dexscreener.com\u002Flatest\u002Fdex\u002Ftokens\u002F\u003CCONTRACT>\"\n```\n\nThen parse it with `jq` as its own call (e.g. `jq '.pairs | length' \u002Ftmp\u002Fprice-alert-dex.json`). If curl fails, or the response is empty, falsy, or `jq` can't parse it → fall back to **WebFetch** on the same URL with prompt `\"Return the raw JSON body verbatim.\"` and try again. If both paths fail → log `PRICE_ALERT_FETCH_FAIL` and exit with status `ERROR` (no notify, no state mutation beyond touching `last_run_at`).\n\nFrom the parsed JSON:\n- Filter `.pairs[]` to entries where `.chainId == \"${CHAIN}\"`.\n- Of those, pick the entry with the **highest `.liquidity.usd`** — call this the deepest pool. If no pair matches the chain, fall back to the highest-liquidity pair across all chains and mark `chain=fallback` in the log.\n- Extract: `CURRENT_PRICE=.priceUsd` (float), `H1_CHANGE_PCT=.priceChange.h1` (float, may be missing → treat as 0), `H24_CHANGE_PCT=.priceChange.h24` (float, may be missing → treat as 0), `POOL_URL=.url`.\n\nIf `CURRENT_PRICE` is missing, zero, or non-numeric → log `PRICE_ALERT_BAD_PRICE` and exit `ERROR`.\n\n### 4. Evaluate ATH gate\n\n```\nprior_ath = state.ath.price_usd  (null on first run)\nnew_ath   = (prior_ath is null) OR (CURRENT_PRICE > prior_ath)\n```\n\nIf `new_ath`:\n- Always update `state.ath.price_usd = CURRENT_PRICE`, `state.ath.observed_at = NOW`.\n- Notify only if the last `ath` alert was >4h ago (or never). On the first run after token initialisation, suppress the notification (we don't know if this is genuinely an ATH or just the starting baseline) — set `state.ath.announced_at = NOW` so subsequent strict-higher prices alert correctly.\n- If suppressed because `prior_ath is null` (first run), record verdict `ATH_BASELINE` for the log; no notify.\n- Otherwise verdict for this gate is `ATH`.\n\n### 5. Evaluate sharp-move gate\n\n```\nsharp = abs(H1_CHANGE_PCT) >= 20.0\n```\n\nIf `sharp`:\n- If `state.last_alerts.sharp_move` is within the last 4h → verdict `SHARP_MOVE_DEDUPED`, no notify.\n- Otherwise verdict `SHARP_MOVE`, notify, set `state.last_alerts.sharp_move = NOW`.\n\nSign of `H1_CHANGE_PCT` decides the direction word in the message (`up` for ≥0, `down` for \u003C0).\n\n### 6. Evaluate target-crossing gate\n\nFor each target in `TARGETS`:\n- If the target isn't yet in `state.targets`: this is the first time the operator has set it. Record `side` (`above` if `CURRENT_PRICE \u003C target`, else `below`), `first_seen_below_at=NOW`, `hit_at=null`, `announced_at=null`. **Do not notify on first observation.** This avoids \"the price was already there when you set the target\" noise.\n- If the target is in `state.targets` and `hit_at` is already set → skip; target-hit alerts are one-shot per side.\n- Otherwise check the cross:\n  - `side=above` & `CURRENT_PRICE >= target` → crossed.\n  - `side=below` & `CURRENT_PRICE \u003C= target` → crossed.\n- On cross: set `hit_at=NOW`, `announced_at=NOW`, verdict `TARGET_HIT` for this target, notify (subject to 4h dedup on `last_alerts.target_hit`).\n\nTargets that crossed in prior runs stay in the state file with `hit_at` set so they don't refire if the price wobbles back.\n\n### 7. Combine verdicts and notify\n\nRun-level verdict precedence (used for the log status line and the notification ordering):\n1. `ATH` — strict new high after baseline.\n2. `TARGET_HIT` — operator-set level crossed.\n3. `SHARP_MOVE` — ±20% in 1h.\n4. `OK` — no gate fired.\n\nIf multiple gates fired in the same run, send one notification per gate. Each gate independently respects its own dedup clock — an ATH and a TARGET_HIT in the same run produce two messages; an ATH and a SHARP_MOVE_DEDUPED produce one.\n\n#### Notification templates\n\n**ATH:**\n\n```\n*$TOKEN - New ATH - ${today}*\n\n$TOKEN just printed a new all-time high at $X.XXXXe-N.\nPrevious ATH: $Y.YYYYe-N (set ${prior_ath_age} ago).\n24h move: ±Z.Z% · 1h move: ±W.W%\n\nChart: ${POOL_URL}\n```\n\n**SHARP_MOVE:**\n\n```\n*$TOKEN - Sharp 1h Move - ${today}*\n\n$TOKEN ${up|down} ${abs(h1):.1f}% in the last hour - now $X.XXXXe-N.\n24h: ±Z.Z%\n${one of: \"Buyer wave - watch for follow-through.\" | \"Selling pressure - watch the next hour for stabilisation.\" }\n\nChart: ${POOL_URL}\n```\n\n(Direction phrase is hard-coded by sign: positive = buyer wave; negative = selling pressure. No freelance commentary.)\n\n**TARGET_HIT:**\n\n```\n*$TOKEN - Target Hit - ${today}*\n\n$TOKEN just crossed $${target} (now $X.XXXXe-N).\nDirection: ${above|below}\n24h: ±Z.Z% · 1h: ±W.W%\n\nChart: ${POOL_URL}\n```\n\nIf `MODE == dry-run`: build the messages, log the planned notifications, but skip `.\u002Fnotify`. State still updates so dedup clocks advance correctly.\n\nCap each message at ~2500 chars; price-alert messages are short by nature and shouldn't approach this.\n\n#### Send the alert\n\nBuild each firing gate's message to a file and send it with `.\u002Fnotify -f alert.md` (one send per\ngate). State still advances even on a deduped run, so the dedup clocks stay correct.\n\n#### Set-a-target follow-up (force-reply)\n\nButtons and force_reply can't share one message, so this is a **separate** send. After a genuine\n**ATH** alert (post-baseline, not a deduped repeat), and only when no un-hit operator target\ncurrently sits above `CURRENT_PRICE` (don't nag operators who already queued one), offer to capture\nthe next level:\n\n```bash\n.\u002Fnotify \"New high - want an alert when \u003CSYMBOL> clears a level above this? Reply with a price.\" \\\n  --force-reply --placeholder \"e.g. 0.000005\" \\\n  --context \"price-alert::set-target\"\n```\n\nThe reply routes back as `var=set-target:\u003Cprice>` (handled in step 1). On that run, once the target\nis registered (step 6, first-observation — no cross alert), close the loop with a one-line\nconfirmation: `.\u002Fnotify \"Target set: \\$\u003Cprice> for \u003CSYMBOL> - I'll alert you when it crosses.\"` (symbol written in literally)\n(Send it only when a *new* target was actually registered this run.)\n\n### 8. Persist state\n\nRewrite `memory\u002Ftopics\u002Fprice-alert-state.json` as one whole-file write. Get the timestamp as its own call:\n\n```bash\ndate -u +%FT%TZ\n```\n\nIt prints NOW. First save the current file's contents to `memory\u002Ftopics\u002Fprice-alert-state.json.bak` with Write (the one rolling `.bak`). Then Write the full new state: the old object with `last_run_at` = NOW (written in literally) and `contract`, `chain`, `ath`, `last_alerts` and `targets` set to this run's values.\n\nValidate with `jq empty memory\u002Ftopics\u002Fprice-alert-state.json` after writing; if it fails, restore from the `.bak` copy (Write its contents back) and log `PRICE_ALERT_STATE_CORRUPT`.\n\nCap `state.targets` to 20 most-recent entries (LRU by `first_seen_below_at`) so a long-running fork doesn't accumulate stale operator targets.\n\n### 9. Log\n\nAppend to `memory\u002Flogs\u002F${today}.md` under a `### price-alert` heading:\n\n```\n### price-alert\n- **Skill**: price-alert\n- **Token**: ${SYMBOL} (${CONTRACT})\n- **Current**: $X.XXXXe-N | 1h: ±W.W% | 24h: ±Z.Z%\n- **ATH**: $Y.YYYYe-N (set ${YYYY-MM-DD HH:MM} UTC) [${UNCHANGED|NEW}]\n- **Sharp-move gate**: ${FIRED|QUIET|DEDUPED}\n- **Targets evaluated**: ${comma-list of target prices} → ${comma-list of per-target verdicts}\n- **Verdicts fired**: ${comma-list, or NONE}\n- **Notifications sent**: ${N}\n- **Status**: ${PRICE_ALERT_OK | PRICE_ALERT_ATH | PRICE_ALERT_SHARP_MOVE | PRICE_ALERT_TARGET_HIT | PRICE_ALERT_DRY_RUN | PRICE_ALERT_NO_TOKEN | PRICE_ALERT_TOKEN_CHANGED | PRICE_ALERT_FETCH_FAIL | PRICE_ALERT_BAD_PRICE | PRICE_ALERT_BAD_VAR | PRICE_ALERT_STATE_CORRUPT}\n```\n\nThe status field carries the *highest-priority* gate fired this run, or the most relevant error. `OK` means the run completed cleanly and no gate fired.\n\n## Exit taxonomy\n\n| Status | Meaning | Notify? |\n|--------|---------|---------|\n| `PRICE_ALERT_OK` | Run completed, no gate fired | No |\n| `PRICE_ALERT_ATH` | New strict all-time high (post-baseline) | Yes |\n| `PRICE_ALERT_SHARP_MOVE` | ±20% in 1h, outside dedup window | Yes |\n| `PRICE_ALERT_TARGET_HIT` | Operator target crossed for the first time | Yes |\n| `PRICE_ALERT_DRY_RUN` | `var=dry-run` mode | No (state still updates) |\n| `PRICE_ALERT_NO_TOKEN` | No tracked token configured in MEMORY.md | No |\n| `PRICE_ALERT_TOKEN_CHANGED` | Tracked contract changed since last run; state reset | No |\n| `PRICE_ALERT_FETCH_FAIL` | Both curl and WebFetch failed | No |\n| `PRICE_ALERT_BAD_PRICE` | API returned malformed\u002Fzero price | No |\n| `PRICE_ALERT_BAD_VAR` | `${var}` had non-empty, non-`dry-run` text but yielded zero valid targets | No |\n| `PRICE_ALERT_STATE_CORRUPT` | jq validation failed after write; restored from `.bak` | No |\n\n## Network note\n\nDexScreener is keyless and public — `curl` works; there is no network sandbox. If a public `curl` GET is flaky, the **WebFetch fallback** kicks in (built-in Claude tool, prompt: `\"Return the raw JSON body verbatim.\"`). There's no auth header, and the URL doesn't change between runs. Notify goes through `.\u002Fnotify`, which stages to `.pending-notify\u002F`; the workflow re-delivers any messages that failed to send after the run — no extra script needed.\n\n**Shell shape.** Each Bash call is one allowed command (pipes are fine). The run refuses a call that sets a variable (`X=$(...)`), uses `if` \u002F `[ -f ... ] ||` tests, `mktemp` or `mv`, or joins commands with `;`, `&&` or `||`, and each refusal costs a model turn. So the state-file check is a Glob, the DexScreener fetch is one `curl` to a file with the contract written in literally, timestamps come from a bare `date` call, and the state file is written with Write (never a shell redirect).\n\n## Constraints\n\n- **One alert per event per 4h window.** The state file's `last_alerts.*` map is the only authority on whether to suppress.\n- **Target alerts are one-shot per side.** Once `hit_at` is set, the target never re-fires — even if the price wobbles back through the level. Operators add new targets if they want continued signal.\n- **No baseline alert.** The first run after a fresh state file or token change must NOT send an ATH notification — the stored price is the baseline, not a \"new high.\" Subsequent strict-higher prices alert.\n- **No freelance interpretation.** The sharp-move direction phrase is hard-coded by sign. Do not embellish with TA opinions (\"looks like distribution\", \"could test support\"). The verdict tells the operator *what* happened; the operator decides *what it means*.\n- **Liquid-pool selection only.** The deepest-liquidity pair on the configured chain wins. Don't compute prices from blended pool averages — the deepest pool's `priceUsd` is the canonical mark.\n- **State writes are whole-file + validated.** Every state write is one full Write followed by a `jq empty` validation step. Corrupt writes restore from `.bak`.\n- **Read-only across `memory\u002Flogs\u002F`.** This skill never modifies past log files. It only appends to today's.\n- **Targets are absolute USD, not percentages.** This avoids ambiguity (\"20% from where?\"). If operators want move-from-now alerts they have the sharp-move gate.\n- **Idempotent under same-minute reruns.** Same-minute reruns with identical price input produce identical state and zero new notifications (every gate dedup-suppressed).\n","> **${var}** — Optional. Pass one or more `target_price` levels (comma-separated USD numbers, scientific notation allowed) to fire a one-time alert when the price crosses any of them. Empty = only ATH and sharp-move gates run. Pass `dry-run` to skip notify (state still updates). Pass `set-target:\u003Cprice>` — the shape the Telegram force-reply sends (step 7) — to register a target and get a one-line confirmation.\n\nToday is ${today}. The scheduled digests (`digest`, `token-movers`, ...) run at fixed hours. None of them tell the operator \"the price just hit a new high\" or \"the token moved 28% in the last hour\" — both are events that warrant attention the moment they happen, not 14 hours later in a daily digest. This skill closes that window.\n\n## Why this exists\n\nScheduled daily digests are calm summaries. Real moves need real-time signal. Three classes of move are worth a same-day ping:\n- **New all-time high.** The single most narrative-shaping price event a token can have. Worth marking on the timeline regardless of size.\n- **Sharp 1h moves (±20%).** Either a buyer wave or a liquidation cascade — both change what the operator does next (post about it, watch for follow-through, check the chart).\n- **Operator-set target crossings.** The operator may want to know when price clears $X (often a personal stretch goal or a level tied to a tweet\u002Flaunch). One alert per target per direction.\n\nEverything else is noise not worth a same-day ping.\n\n## Config\n\nReads:\n- `memory\u002FMEMORY.md` \"Tracked Token\" section — the tracked token's contract\u002Fchain. If absent, exit silently.\n- `memory\u002Ftopics\u002Fprice-alert-state.json` — last-known ATH, last-alert timestamps per event type, target-crossing history. Created with defaults on first run.\n\nWrites:\n- `memory\u002Ftopics\u002Fprice-alert-state.json` — updated state every run.\n- `memory\u002Flogs\u002F${today}.md` — one log block per run, even on `OK`.\n- Notification via `.\u002Fnotify` — only when a gate fires.\n\nNo new secrets. Uses keyless DexScreener; falls back to WebFetch when a public `curl` GET is flaky (there is no network sandbox).\n\n## State schema\n\n```json\n{\n  \"contract\": \"0xbf8e8f0e8866a7052f948c16508644347c57aba3\",\n  \"chain\": \"base\",\n  \"ath\": {\n    \"price_usd\": 0.000003757,\n    \"observed_at\": \"2026-05-10T19:00:00Z\",\n    \"announced_at\": \"2026-05-10T19:30:00Z\"\n  },\n  \"last_alerts\": {\n    \"ath\": \"2026-05-10T19:30:00Z\",\n    \"sharp_move\": null,\n    \"target_hit\": null\n  },\n  \"targets\": {\n    \"0.000005\": {\n      \"side\": \"above\",\n      \"first_seen_below_at\": \"2026-05-11T08:00:00Z\",\n      \"hit_at\": null,\n      \"announced_at\": null\n    }\n  }\n}\n```\n\nKey invariants:\n- `ath.price_usd` only ever monotonically increases. If a run sees a lower price than the stored ATH, leave the ATH alone.\n- `last_alerts.*` powers the 4h dedup window. Each gate has its own clock.\n- `targets.${price}.side` is `above` if `current_price \u003C target` when the target was first observed (operator is waiting for the price to climb to it), and `below` otherwise. Set once, never flipped.\n- `targets.${price}.hit_at` is set the run the cross happens. `announced_at` is set the run the notification fires. They differ only if the run lands inside a dedup window — but target alerts never re-fire, so `hit_at == announced_at` in practice.\n\n## Steps\n\n### 1. Parse var\n\n- If `${var}` starts with `set-target:` → set `FROM_REPLY=1`, `MODE=execute`. Strip the `set-target:` prefix (`${var#set-target:}`); the remainder is the target(s) list, parsed exactly like the comma-split below. This is the shape `scripts\u002Ftelegram-route.sh` sends when the operator replies to the step-7 force-reply prompt. The run proceeds normally (register the target, don't fire on first observation) and closes the loop with a confirmation in step 7.\n- Else if `${var}` matches `^dry-run` → `MODE=dry-run`. Strip the prefix; remainder (if any) is treated as targets.\n- Otherwise `MODE=execute`.\n- Split the remainder on `,` (commas) and strip whitespace.\n- For each token: if it parses as a positive float (scientific notation OK, e.g. `5e-6`), include it. Reject zero \u002F negative \u002F non-numeric tokens and log `PRICE_ALERT_BAD_TARGET: ${token}` — continue with the surviving targets.\n- If after filtering the remainder was non-empty but yielded zero valid targets → log `PRICE_ALERT_BAD_VAR: ${var}` and exit. Normally no notify — **but if `FROM_REPLY=1`**, first close the loop with `.\u002Fnotify \"Couldn't read \\\"${var#set-target:}\\\" as a price. Reply with a number like 0.000005.\"` (a force-reply always deserves an acknowledgement).\n- If the remainder was empty, `TARGETS=()` is fine — ATH and sharp-move gates still run.\n\n### 2. Resolve tracked token\n\nEach step below is one Bash call (see **Shell shape** in the Network note):\n\n```bash\nmkdir -p memory\u002Ftopics\n```\n\nCheck for `memory\u002Ftopics\u002Fprice-alert-state.json` with Glob. If it does not exist, create it with Write:\n\n```json\n{\"contract\":null,\"chain\":null,\"ath\":null,\"last_alerts\":{\"ath\":null,\"sharp_move\":null,\"target_hit\":null},\"targets\":{}}\n```\n\nParse the \"Tracked Token\" table in `memory\u002FMEMORY.md`. Pull `CONTRACT` (column 2 of the first data row) and `CHAIN` (column 3, lowercased). If the section is missing, the table is empty, or the contract field doesn't match `^0x[0-9a-fA-F]{40}$` → log `PRICE_ALERT_NO_TOKEN` and exit (no notify, no state write).\n\nIf the state file's `contract` is set and differs from the resolved contract → log `PRICE_ALERT_TOKEN_CHANGED` and reset the state file to defaults with the new contract\u002Fchain. ATH starts over with the new token.\n\n### 3. Fetch current price (DexScreener primary)\n\nOne call, with the contract written in literally:\n\n```bash\ncurl -fsS -o \u002Ftmp\u002Fprice-alert-dex.json \"https:\u002F\u002Fapi.dexscreener.com\u002Flatest\u002Fdex\u002Ftokens\u002F\u003CCONTRACT>\"\n```\n\nThen parse it with `jq` as its own call (e.g. `jq '.pairs | length' \u002Ftmp\u002Fprice-alert-dex.json`). If curl fails, or the response is empty, falsy, or `jq` can't parse it → fall back to **WebFetch** on the same URL with prompt `\"Return the raw JSON body verbatim.\"` and try again. If both paths fail → log `PRICE_ALERT_FETCH_FAIL` and exit with status `ERROR` (no notify, no state mutation beyond touching `last_run_at`).\n\nFrom the parsed JSON:\n- Filter `.pairs[]` to entries where `.chainId == \"${CHAIN}\"`.\n- Of those, pick the entry with the **highest `.liquidity.usd`** — call this the deepest pool. If no pair matches the chain, fall back to the highest-liquidity pair across all chains and mark `chain=fallback` in the log.\n- Extract: `CURRENT_PRICE=.priceUsd` (float), `H1_CHANGE_PCT=.priceChange.h1` (float, may be missing → treat as 0), `H24_CHANGE_PCT=.priceChange.h24` (float, may be missing → treat as 0), `POOL_URL=.url`.\n\nIf `CURRENT_PRICE` is missing, zero, or non-numeric → log `PRICE_ALERT_BAD_PRICE` and exit `ERROR`.\n\n### 4. Evaluate ATH gate\n\n```\nprior_ath = state.ath.price_usd  (null on first run)\nnew_ath   = (prior_ath is null) OR (CURRENT_PRICE > prior_ath)\n```\n\nIf `new_ath`:\n- Always update `state.ath.price_usd = CURRENT_PRICE`, `state.ath.observed_at = NOW`.\n- Notify only if the last `ath` alert was >4h ago (or never). On the first run after token initialisation, suppress the notification (we don't know if this is genuinely an ATH or just the starting baseline) — set `state.ath.announced_at = NOW` so subsequent strict-higher prices alert correctly.\n- If suppressed because `prior_ath is null` (first run), record verdict `ATH_BASELINE` for the log; no notify.\n- Otherwise verdict for this gate is `ATH`.\n\n### 5. Evaluate sharp-move gate\n\n```\nsharp = abs(H1_CHANGE_PCT) >= 20.0\n```\n\nIf `sharp`:\n- If `state.last_alerts.sharp_move` is within the last 4h → verdict `SHARP_MOVE_DEDUPED`, no notify.\n- Otherwise verdict `SHARP_MOVE`, notify, set `state.last_alerts.sharp_move = NOW`.\n\nSign of `H1_CHANGE_PCT` decides the direction word in the message (`up` for ≥0, `down` for \u003C0).\n\n### 6. Evaluate target-crossing gate\n\nFor each target in `TARGETS`:\n- If the target isn't yet in `state.targets`: this is the first time the operator has set it. Record `side` (`above` if `CURRENT_PRICE \u003C target`, else `below`), `first_seen_below_at=NOW`, `hit_at=null`, `announced_at=null`. **Do not notify on first observation.** This avoids \"the price was already there when you set the target\" noise.\n- If the target is in `state.targets` and `hit_at` is already set → skip; target-hit alerts are one-shot per side.\n- Otherwise check the cross:\n  - `side=above` & `CURRENT_PRICE >= target` → crossed.\n  - `side=below` & `CURRENT_PRICE \u003C= target` → crossed.\n- On cross: set `hit_at=NOW`, `announced_at=NOW`, verdict `TARGET_HIT` for this target, notify (subject to 4h dedup on `last_alerts.target_hit`).\n\nTargets that crossed in prior runs stay in the state file with `hit_at` set so they don't refire if the price wobbles back.\n\n### 7. Combine verdicts and notify\n\nRun-level verdict precedence (used for the log status line and the notification ordering):\n1. `ATH` — strict new high after baseline.\n2. `TARGET_HIT` — operator-set level crossed.\n3. `SHARP_MOVE` — ±20% in 1h.\n4. `OK` — no gate fired.\n\nIf multiple gates fired in the same run, send one notification per gate. Each gate independently respects its own dedup clock — an ATH and a TARGET_HIT in the same run produce two messages; an ATH and a SHARP_MOVE_DEDUPED produce one.\n\n#### Notification templates\n\n**ATH:**\n\n```\n*$TOKEN - New ATH - ${today}*\n\n$TOKEN just printed a new all-time high at $X.XXXXe-N.\nPrevious ATH: $Y.YYYYe-N (set ${prior_ath_age} ago).\n24h move: ±Z.Z% · 1h move: ±W.W%\n\nChart: ${POOL_URL}\n```\n\n**SHARP_MOVE:**\n\n```\n*$TOKEN - Sharp 1h Move - ${today}*\n\n$TOKEN ${up|down} ${abs(h1):.1f}% in the last hour - now $X.XXXXe-N.\n24h: ±Z.Z%\n${one of: \"Buyer wave - watch for follow-through.\" | \"Selling pressure - watch the next hour for stabilisation.\" }\n\nChart: ${POOL_URL}\n```\n\n(Direction phrase is hard-coded by sign: positive = buyer wave; negative = selling pressure. No freelance commentary.)\n\n**TARGET_HIT:**\n\n```\n*$TOKEN - Target Hit - ${today}*\n\n$TOKEN just crossed $${target} (now $X.XXXXe-N).\nDirection: ${above|below}\n24h: ±Z.Z% · 1h: ±W.W%\n\nChart: ${POOL_URL}\n```\n\nIf `MODE == dry-run`: build the messages, log the planned notifications, but skip `.\u002Fnotify`. State still updates so dedup clocks advance correctly.\n\nCap each message at ~2500 chars; price-alert messages are short by nature and shouldn't approach this.\n\n#### Send the alert\n\nBuild each firing gate's message to a file and send it with `.\u002Fnotify -f alert.md` (one send per\ngate). State still advances even on a deduped run, so the dedup clocks stay correct.\n\n#### Set-a-target follow-up (force-reply)\n\nButtons and force_reply can't share one message, so this is a **separate** send. After a genuine\n**ATH** alert (post-baseline, not a deduped repeat), and only when no un-hit operator target\ncurrently sits above `CURRENT_PRICE` (don't nag operators who already queued one), offer to capture\nthe next level:\n\n```bash\n.\u002Fnotify \"New high - want an alert when \u003CSYMBOL> clears a level above this? Reply with a price.\" \\\n  --force-reply --placeholder \"e.g. 0.000005\" \\\n  --context \"price-alert::set-target\"\n```\n\nThe reply routes back as `var=set-target:\u003Cprice>` (handled in step 1). On that run, once the target\nis registered (step 6, first-observation — no cross alert), close the loop with a one-line\nconfirmation: `.\u002Fnotify \"Target set: \\$\u003Cprice> for \u003CSYMBOL> - I'll alert you when it crosses.\"` (symbol written in literally)\n(Send it only when a *new* target was actually registered this run.)\n\n### 8. Persist state\n\nRewrite `memory\u002Ftopics\u002Fprice-alert-state.json` as one whole-file write. Get the timestamp as its own call:\n\n```bash\ndate -u +%FT%TZ\n```\n\nIt prints NOW. First save the current file's contents to `memory\u002Ftopics\u002Fprice-alert-state.json.bak` with Write (the one rolling `.bak`). Then Write the full new state: the old object with `last_run_at` = NOW (written in literally) and `contract`, `chain`, `ath`, `last_alerts` and `targets` set to this run's values.\n\nValidate with `jq empty memory\u002Ftopics\u002Fprice-alert-state.json` after writing; if it fails, restore from the `.bak` copy (Write its contents back) and log `PRICE_ALERT_STATE_CORRUPT`.\n\nCap `state.targets` to 20 most-recent entries (LRU by `first_seen_below_at`) so a long-running fork doesn't accumulate stale operator targets.\n\n### 9. Log\n\nAppend to `memory\u002Flogs\u002F${today}.md` under a `### price-alert` heading:\n\n```\n### price-alert\n- **Skill**: price-alert\n- **Token**: ${SYMBOL} (${CONTRACT})\n- **Current**: $X.XXXXe-N | 1h: ±W.W% | 24h: ±Z.Z%\n- **ATH**: $Y.YYYYe-N (set ${YYYY-MM-DD HH:MM} UTC) [${UNCHANGED|NEW}]\n- **Sharp-move gate**: ${FIRED|QUIET|DEDUPED}\n- **Targets evaluated**: ${comma-list of target prices} → ${comma-list of per-target verdicts}\n- **Verdicts fired**: ${comma-list, or NONE}\n- **Notifications sent**: ${N}\n- **Status**: ${PRICE_ALERT_OK | PRICE_ALERT_ATH | PRICE_ALERT_SHARP_MOVE | PRICE_ALERT_TARGET_HIT | PRICE_ALERT_DRY_RUN | PRICE_ALERT_NO_TOKEN | PRICE_ALERT_TOKEN_CHANGED | PRICE_ALERT_FETCH_FAIL | PRICE_ALERT_BAD_PRICE | PRICE_ALERT_BAD_VAR | PRICE_ALERT_STATE_CORRUPT}\n```\n\nThe status field carries the *highest-priority* gate fired this run, or the most relevant error. `OK` means the run completed cleanly and no gate fired.\n\n## Exit taxonomy\n\n| Status | Meaning | Notify? |\n|--------|---------|---------|\n| `PRICE_ALERT_OK` | Run completed, no gate fired | No |\n| `PRICE_ALERT_ATH` | New strict all-time high (post-baseline) | Yes |\n| `PRICE_ALERT_SHARP_MOVE` | ±20% in 1h, outside dedup window | Yes |\n| `PRICE_ALERT_TARGET_HIT` | Operator target crossed for the first time | Yes |\n| `PRICE_ALERT_DRY_RUN` | `var=dry-run` mode | No (state still updates) |\n| `PRICE_ALERT_NO_TOKEN` | No tracked token configured in MEMORY.md | No |\n| `PRICE_ALERT_TOKEN_CHANGED` | Tracked contract changed since last run; state reset | No |\n| `PRICE_ALERT_FETCH_FAIL` | Both curl and WebFetch failed | No |\n| `PRICE_ALERT_BAD_PRICE` | API returned malformed\u002Fzero price | No |\n| `PRICE_ALERT_BAD_VAR` | `${var}` had non-empty, non-`dry-run` text but yielded zero valid targets | No |\n| `PRICE_ALERT_STATE_CORRUPT` | jq validation failed after write; restored from `.bak` | No |\n\n## Network note\n\nDexScreener is keyless and public — `curl` works; there is no network sandbox. If a public `curl` GET is flaky, the **WebFetch fallback** kicks in (built-in Claude tool, prompt: `\"Return the raw JSON body verbatim.\"`). There's no auth header, and the URL doesn't change between runs. Notify goes through `.\u002Fnotify`, which stages to `.pending-notify\u002F`; the workflow re-delivers any messages that failed to send after the run — no extra script needed.\n\n**Shell shape.** Each Bash call is one allowed command (pipes are fine). The run refuses a call that sets a variable (`X=$(...)`), uses `if` \u002F `[ -f ... ] ||` tests, `mktemp` or `mv`, or joins commands with `;`, `&&` or `||`, and each refusal costs a model turn. So the state-file check is a Glob, the DexScreener fetch is one `curl` to a file with the contract written in literally, timestamps come from a bare `date` call, and the state file is written with Write (never a shell redirect).\n\n## Constraints\n\n- **One alert per event per 4h window.** The state file's `last_alerts.*` map is the only authority on whether to suppress.\n- **Target alerts are one-shot per side.** Once `hit_at` is set, the target never re-fires — even if the price wobbles back through the level. Operators add new targets if they want continued signal.\n- **No baseline alert.** The first run after a fresh state file or token change must NOT send an ATH notification — the stored price is the baseline, not a \"new high.\" Subsequent strict-higher prices alert.\n- **No freelance interpretation.** The sharp-move direction phrase is hard-coded by sign. Do not embellish with TA opinions (\"looks like distribution\", \"could test support\"). The verdict tells the operator *what* happened; the operator decides *what it means*.\n- **Liquid-pool selection only.** The deepest-liquidity pair on the configured chain wins. Don't compute prices from blended pool averages — the deepest pool's `priceUsd` is the canonical mark.\n- **State writes are whole-file + validated.** Every state write is one full Write followed by a `jq empty` validation step. Corrupt writes restore from `.bak`.\n- **Read-only across `memory\u002Flogs\u002F`.** This skill never modifies past log files. It only appends to today's.\n- **Targets are absolute USD, not percentages.** This avoids ambiguity (\"20% from where?\"). If operators want move-from-now alerts they have the sharp-move gate.\n- **Idempotent under same-minute reruns.** Same-minute reruns with identical price input produce identical state and zero new notifications (every gate dedup-suppressed).\n",{"name":5,"metadata":36,"description":7},{"var":37,"tags":38,"title":39,"category":23},"",[24],"Price Alert","main","9c042aa1b6d79fd49679637374e88c5874cc3aae","skills\u002Fprice-alert",2,"https:\u002F\u002Fgithub.com\u002FMiroShark\u002Fmiroshark-aeon","https:\u002F\u002Ft.me\u002Fmiroshark_aeon","https:\u002F\u002Fgithub.com\u002FMiroShark","https:\u002F\u002Fwww.miroshark.xyz\u002F","Simulate anything, for $1 & less than 10 min.","2026-11-14 00:33:09",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add MiroShark\u002Fmiroshark-aeon --skill price-alert",{"tool":56,"command":57},"bunx","bunx skills add MiroShark\u002Fmiroshark-aeon --skill price-alert",{"tool":59,"command":60},"pnpm","pnpm dlx skills add MiroShark\u002Fmiroshark-aeon --skill price-alert","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fprice-alert\u002FSKILL.md",true,[],[65,102,125,152,177,200],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":81,"publishedAt":31},"01M6J4P8XKB8RJ7PCYMZPZ0F6S","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fyou-web-search\u002FSKILL.md",[71,76],{"categoryNo":72,"categoryName":73,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":77,"categoryName":78,"taxonomyType":79,"taxonomyTypeDesc":80},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[82,83,84,85,86,87,88,89,90,91,92,93,23,94,95,96,97,98,99,100,101],"web search","search","you","you web search","web","time","only","api","web crawling","read","read-only","real-time","keyless","with","research","results","high","quality","box","limits",{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":31},"01M6J4P4H35WQG6RHGTTACSMVW","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fwrite-tweet\u002FSKILL.md",[],[109,110,111,112,113,89,114,115,116,117,23,118,119,120,121,122,123,124],"write","write tweet","standalone","social","studio","across","xai","past","drafts","twitter","thread","multi","size","via","format","content",{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":134,"publishedAt":31},"01M6J4P11K7ZJ2PJDHDTTFZ1HY","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fweekly-aeoncard\u002FSKILL.md",[131],{"categoryNo":132,"categoryName":133,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000016","图像与多媒体",[135,136,137,138,139,140,141,87,142,143,144,145,146,147,148,149,150,109,26,151],"weekly","weekly aeoncard","as","usage","SVG","memory","card","Meta","image","recap","csv","monitoring","notifications","productivity","top","build","aeon",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":158,"publishedAt":31},"01M6J4NVJYQP5DNKSGXAXQW06B","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-tracker\u002FSKILL.md",[],[159,160,142,161,162,109,26,163,164,165,166,167,168,169,170,171,172,95,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":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":181,"categories":182,"keywords":186,"publishedAt":31},"01M6J4NNQCHTKNGENAPJQ8KYHR","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fvuln-scanner\u002FSKILL.md",[183],{"categoryNo":184,"categoryName":185,"taxonomyType":74,"taxonomyTypeDesc":75},"01JBSEEDCAT000000000000005","邮件与沟通",[162,187,188,189,190,142,191,165,192,193,194,169,173,195,176,196,89,197,198,199],"vuln scanner","resend","re","trending","repos","email","for","scan","vulnerabilities","submit","route","audit","send",{"skillNo":201,"name":202,"nameZh":6,"description":203,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":204,"categories":205,"keywords":207,"publishedAt":31},"01M6J4NFBXTGG0R4M484T0KNYW","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\u002FMiroShark\u002Fmiroshark-aeon\u002Fblob\u002Fmain\u002Fskills\u002Fvideo-script\u002FSKILL.md",[206],{"categoryNo":132,"categoryName":133,"taxonomyType":74,"taxonomyTypeDesc":75},[208,209,210,211,212,213,214,215,216,23,217,218,219,95,220,221,222,223,224,123,225,226,227],"Video","video script","script","recording","sources","ready","direction","page","repo","claim","plain-language","glossary","first","verify","product","screen","update","list","live","receipts"]