A hierarchical, vision-tagged personal meme/sticker library. Use this skill to add new memes (single image or whole collections) and to retrieve a contextually-appropriate meme to send back. The library uses progressive disclosure — a lightweight root index decides which collection to drill into, then per-collection metadata holds the per-image semantics. Trigger when the user asks to ingest a meme/sticker image, build a new sticker pack, search for "a meme that fits this mood", or maintain t...
Scanned 5/27/2026
Install via CLI
openskills install yr-96/meme-library---
name: meme-library
description: A hierarchical, vision-tagged personal meme/sticker library. Use this skill to add new memes (single image or whole collections) and to retrieve a contextually-appropriate meme to send back. The library uses progressive disclosure — a lightweight root index decides which collection to drill into, then per-collection metadata holds the per-image semantics. Trigger when the user asks to ingest a meme/sticker image, build a new sticker pack, search for "a meme that fits this mood", or maintain the library.
---
# Meme Library
A channel-agnostic library for managing and retrieving memes/stickers. This skill **does not send** anything — it stores, tags, indexes, and selects. Wire it into your own delivery skill (WeChat, Telegram, Slack, Discord, CLI, …) by consuming the path it returns.
## Library Layout
```
$MEME_LIB_PATH/ (default: ~/.meme-library/)
├── index.json # root index — ALWAYS read this first
├── <collection_a>/ # one folder per IP / character / theme
│ ├── meta.json # per-image semantics for this collection
│ ├── img_001.jpg
│ └── ...
├── <collection_b>/
│ ├── meta.json
│ └── ...
```
**Progressive disclosure principle:** `index.json` is small and always loaded. Per-collection `meta.json` is loaded only when that collection is selected. This keeps context small as the library grows.
## index.json schema
```json
{
"version": "1.0",
"collections": {
"<collection_id>": {
"path": "<collection_id>/",
"name": "Human-readable name",
"count": 12,
"vibe": "One-sentence summary of this collection's overall personality",
"best_for": ["situation 1", "situation 2", "..."],
"avoid_when": ["situation that this collection clashes with", "..."]
}
}
}
```
## meta.json schema (per collection)
```json
{
"<filename.ext>": {
"path": "<absolute path>",
"description": "What is in the image, visually",
"tags": ["tag1", "tag2", "..."],
"occasion": "Free-text description of the situation this meme fits"
}
}
```
See `references/schema.md` for the full spec.
---
## Workflows
### Workflow 1 — Add a single meme
Trigger: user shares one image and says "save this to <collection>" / "add to my memes".
1. Run `python scripts/add_meme.py --image <path> --collection <id>` (creates the collection if missing).
2. The script will: vision-analyze the image → propose a semantic filename → copy the file → update `<collection>/meta.json`.
3. Confirm to user: filename, tags, occasion.
4. **After adding ≥3 new memes to a collection, run Workflow 5** to regenerate the collection's vibe/best_for/avoid_when, since the personality may have shifted. (`add_meme.py` deliberately does *not* refresh the fingerprint on every call — it would double the API cost per add.)
### Workflow 2 — Add a whole new collection
Trigger: user shares a folder/zip of images and says "build a new sticker pack called X".
1. Unzip if needed to a temp dir.
2. Run `python scripts/add_collection.py --source <dir> --collection <id> --name "<display name>"`.
3. The script processes images **one at a time** (vision tools must NOT be called in parallel) → writes each into `<collection>/meta.json` → at the end, summarizes all descriptions into the collection's `vibe`/`best_for`/`avoid_when` and writes the entry to `index.json`.
4. Confirm to user: count added, collection vibe.
### Workflow 3 — Retrieve a meme that fits the current context
Trigger: an upstream agent/skill needs a meme for the current conversation.
**Preferred flow (LLM-native, used when this skill is being driven by a language model):**
1. Read `$MEME_LIB_PATH/index.json`.
2. Score each collection against the conversation context using `vibe` + `best_for` + `avoid_when`. Pick the top 1–2 collections (or zero if none fit — see "When to skip").
3. Read `meta.json` for the selected collection(s).
4. Pick the single meme whose `occasion` + `tags` best match the context. Strongly prefer specificity over generic catch-alls.
5. Return the absolute `path` (and metadata) to the caller. **Never invent or guess paths** — only return paths that exist in the loaded JSON.
**Fallback flow (programmatic, for non-LLM consumers):**
```bash
python scripts/search.py --context "用户气死了" --top-k 1
```
Outputs JSON with the matched meme path and metadata, using keyword-overlap scoring across collections.
### Workflow 4 — Maintenance
```bash
python scripts/health_check.py
```
Reports: orphan files (image present but no DB entry), missing files (DB entry but no image), index/meta count drift, and collections whose vibe summary is stale (no fingerprint at all, or count has drifted ≥3 since the last regeneration — see `vibe_count` in the index entry).
### Workflow 5 — Regenerate a collection's vibe fingerprint
Trigger: `health_check` reports `stale_vibe`, the user says "the vibe of X is off", or you've added several new memes to a collection one at a time.
```bash
# Refresh one collection
python scripts/regen_vibe.py --collection <id>
# Refresh every collection that health_check considers stale
python scripts/regen_vibe.py --stale-only
# Nuke and rebuild every fingerprint in the library
python scripts/regen_vibe.py --all
```
The script reads every meme's description/tags/occasion in the target collection, calls the vision provider's `summarise_collection` once, writes the resulting `vibe` / `best_for` / `avoid_when` to `index.json`, and snapshots the current count into `vibe_count` so the next health check has an accurate baseline.
---
## Behavior Rules (for the agent driving this skill)
These are the principles that make a library useful in practice, regardless of how the meme is eventually delivered:
1. **Don't be random.** The library has rich `occasion` text — actually read it. Random selection produces tone-deaf sends.
2. **Specificity over breadth.** A meme whose `occasion` is "对方迟迟不回消息时催促" beats a generic "震惊" meme when the user is being ghosted.
3. **Skip is a valid answer.** If no meme scores a clear match, return nothing. Better to send no meme than a wrong one.
4. **One per turn.** A retrieval call returns at most one meme.
5. **Don't repeat.** If the caller passes a `--exclude` list of recently-sent paths, never return one in that list.
6. **Respect opt-out signals.** If the upstream context mentions the user has asked to stop sending memes, return nothing and surface that to the caller.
## When to skip
Return zero results when:
- Topic is serious / technical / business-critical
- The conversation tone is somber and no comforting collection exists in the library
- All candidate memes score below a clear-match threshold
- The user has opted out
## Vision Provider
Configurable via env var `MEME_VISION_PROVIDER`:
| Value | Requires |
|-------|----------|
| `claude` (default) | `ANTHROPIC_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| `gemini` | `GEMINI_API_KEY` |
To plug in a custom provider, see `scripts/lib/vision.py` — implement the `VisionProvider` protocol.
## Pitfalls
- **Never call vision tools in parallel.** Most providers throttle or fail under burst load. Always loop sequentially.
- **All mutations must go through `scripts/lib/store.py`.** The public helpers (`ensure_collection`, `add_meme`, `remove_meme`, `update_collection_fingerprint`) acquire a coarse-grained `flock` and write atomically via tempfile + `os.replace` — bypassing them risks half-written JSON or lost concurrent updates.
- **Filenames are semantic, not original.** Always rename incoming images to `<role>_<emotion>_<seq>.<ext>` (or similar) on intake. `IMG_2401.JPEG` is useless to future-you. `add_meme.py` does this for you via the vision provider's `filename_suggestion`.
- **Vibe drifts.** A collection's personality changes as it grows. Re-summarize after every ~3 additions or whenever the user manually requests it (see Workflow 5).
- **The `path` field in `meta.json` is recomputed on read.** `load_meta` always rebuilds it from `lib_root + collection + filename`, so the library survives being moved across machines or relocated to a new `$MEME_LIB_PATH`. Don't rely on the on-disk value being current.
- **Use `$MEME_LIB_PATH` for portability.** Don't hard-code paths in skills that consume `meme-library`; read the env var (default `~/.meme-library/`).
No comments yet. Be the first to comment!