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
{
"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)
{
"<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".
- Run
python scripts/add_meme.py --image <path> --collection <id>(creates the collection if missing). - The script will: vision-analyze the image → propose a semantic filename → copy the file → update
<collection>/meta.json. - Confirm to user: filename, tags, occasion.
- 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.pydeliberately 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".
- Unzip if needed to a temp dir.
- Run
python scripts/add_collection.py --source <dir> --collection <id> --name "<display name>". - 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'svibe/best_for/avoid_whenand writes the entry toindex.json. - 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):
- Read
$MEME_LIB_PATH/index.json. - 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"). - Read
meta.jsonfor the selected collection(s). - Pick the single meme whose
occasion+tagsbest match the context. Strongly prefer specificity over generic catch-alls. - 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):
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
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.
# 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:
- Don't be random. The library has rich
occasiontext — actually read it. Random selection produces tone-deaf sends. - Specificity over breadth. A meme whose
occasionis "对方迟迟不回消息时催促" beats a generic "震惊" meme when the user is being ghosted. - Skip is a valid answer. If no meme scores a clear match, return nothing. Better to send no meme than a wrong one.
- One per turn. A retrieval call returns at most one meme.
- Don't repeat. If the caller passes a
--excludelist of recently-sent paths, never return one in that list. - 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-grainedflockand 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.JPEGis useless to future-you.add_meme.pydoes this for you via the vision provider'sfilename_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
pathfield inmeta.jsonis recomputed on read.load_metaalways rebuilds it fromlib_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_PATHfor portability. Don't hard-code paths in skills that consumememe-library; read the env var (default~/.meme-library/).