daily-card · Social Card Generator
One-Liner
Input Title + Subtitle → Output a PNG: automatically includes today's date, vector stickers related to the text theme, and a unified color scheme. Purely local rendering, 100% control over Chinese text, reproducible results with the same parameters.
This repository is itself a skill directory: The directory containing this SKILL.md is the project root. Code, configuration templates, and documentation are all at the same level. It can be placed into the respective skills paths of WorkBuddy / Claude Code / Codex CLI, or imported by skillhub-like platforms from GitHub.
Installation & Location
Project root = Directory containing this SKILL.md. It can run from any path — the code uses __file__ for location, no absolute paths required.
cd /path/to/daily-card # Replace with actual installation location
python3 -m pip install -r requirements.txt
./install.sh # Idempotent installation: dependencies, private config initialization, self-check
./scripts/selfcheck.sh # Can run self-check independently anytime (also CI entry point)
No internet connection required, no AI interface, no API key needed. Chinese font auto-detection: macOS uses Hiragino Sans GB / PingFang, Linux uses Noto Sans CJK, Windows uses Microsoft YaHei; if none are found, it exits with a prompt to specify a font file using --font / --font-bold.
Agents should prioritize using the local CLI; no need to start any service. The web backend and remote API are optional and described in "Two Optional Backends".
Most Common Use: Command Line Generation (Preferred for Agents)
python3 cardgen.py "Live well today" "Make ordinary days a little brighter"
The command prints the full path after File: ...; present this PNG path to the user using present_files, and explain the theme and dimensions in the response. Default output directory is output/, filename is <Title>-<Date>-<Platform>.png.
Common Parameters
| Parameter | Purpose | Example |
|---|---|---|
-t / -s | Title / Subtitle | -t "Good Morning" -s "Get up first" |
--platform | Dimension preset | xhs(1080×1440) / moments(1080×1080) / wechat(900×500) / story(1080×1920) / wechat2(1080×460) |
--theme | Force a theme; by default auto-detects from keywords | --theme study |
--date | Specify date | --date 2026-01-01 |
--brand | Footer branding | --brand "Claw · Daily Quote" |
--out | Specify output path | --out /tmp/card.png |
--seed | Layout random seed; same seed = same layout | --seed 42 |
--bg-image | Custom background image (auto color extraction) | --bg-image ./bg.jpg |
--bg-scheme | Use the Nth color scheme from the image, or a custom main color | --bg-scheme 2 / --bg-scheme "#C2185B" |
--list-schemes | Output the candidate color scheme JSON for the background image | python3 cardgen.py --bg-image bg.jpg --list-schemes |
--list-themes | List all themes and stickers | |
--config-dir | Use a different config directory | |
--init-config | Rebuild config/themes.json from the factory template (overwrites private themes; back up first) |
Complete Workflow for Background Image Color Extraction
# 1. First, see which color schemes the image can extract (returns JSON: name / base / colors / ratio)
python3 cardgen.py --bg-image ./bg.jpg --list-schemes
# 2. Choose one to generate (0 is main color; 1/2/3 are accent colors; can also directly give #RRGGBB)
python3 cardgen.py "Dusk is the day's reply to the night" "Good work today" \
--bg-image ./bg.jpg --bg-scheme 2 --platform xhs
All colors (background, text, accent, card, stickers) are derived from the selected scheme, so sticker colors are always in the same palette as the image; sticker type is still automatically matched based on the title theme.
Configuration Files (Edit Configs, Not Code)
| File | Content | Version Controlled |
|---|---|---|
config/themes.default.json | Factory template: 11 theme sets, read-only, used for "restore defaults" | Yes |
config/stickers.json | 28 stickers' Chinese names/groups + 6 background pattern registrations (with semantic description desc) | Yes |
config/themes.json | Active theme configuration: label, keywords, 10 colors, motif, stickers, dark | No, private data |
config/settings.json | Default platform, branding, oversampling factor, grain intensity, output directory, default color scheme index | No, private data |
themes.json and settings.json are private user data, not in version control, not released with the public repository. On first run, cardconfig.ensure_config() automatically copies them from the factory template — no manual creation needed. To use a different config directory, use the DAILY_CARD_CONFIG environment variable or --config-dir. See config/README.md for details.
Built-in 11 themes: study morning night coffee sport nature mood work tech festival quote.
To change colors/stickers: Edit config/themes.json directly in the corresponding theme's colors / stickers / keywords, or use the web backend API; the script re-reads the file on every run, so changes take effect immediately. To add a new theme, include all 10 color keys (#RRGGBB) and at least one valid sticker ID. Because it's private data, changes won't appear in git status; back up by copying the file manually.
Two Optional Backends
| Backend | When Needed | How to Enable |
|---|---|---|
| None (default) | Only using CLI to generate cards | Do nothing, just run python3 cardgen.py ... |
| Local Web Backend (standalone mode) | Need GUI, real-time preview, visual theme editing | python3 server.py, serves both the page and API at http://127.0.0.1:8777 |
| Remote Server (pure API mode) | Team sharing, agent needs a stable endpoint | python3 server.py --api-only + nginx subpath reverse proxy, set DAILY_CARD_URL |
In --api-only mode, the backend only responds to /api/* (static files served directly by nginx); /api/reveal returns 403; GET /api/health returns version / mode / serves_static / config_dir for online version verification.
The CLI always runs on the caller's machine, independent of the backend. Do not call /api/* when no service is running — the connection will fail.
Local Web Backend (Visual Configuration + Real-Time Preview)
python3 server.py # http://127.0.0.1:8777 (standalone mode)
python3 server.py --port 9000 --open
python3 server.py --api-only # Cloud deployment: only API, static served by nginx
# macOS: double-click start-server.command
Three panels: Card Design (form + real-time preview + download/save), Theme Configuration (visual color/sticker/keyword editing and writing back to JSON), Global Settings (defaults and rendering parameters). Tabs can be deep-linked via #design / #config / #settings.
The Theme Configuration panel has three columns: theme list / editor / real-time effect preview. When editing colors, background patterns, dark mode toggle, or stickers, the right side immediately shows the unsaved draft effect, including a real card, a color semiotics diagram (annotating what each of the 10 colors is used for and what the current pattern actually looks like), what the stickers look like under that color scheme, and WCAG contrast/brightness consistency diagnostics. The preview is read-only in memory — it does not write config/themes.json — save only when you click "Save Configuration".
Same-Domain Subpath Deployment (Cloud Server)
All frontend URLs are derived at runtime by web/app.js from BASE (document.baseURI), no hardcoded root paths in source; index.html uses relative paths for style.css / app.js. To point the API to a different domain, set window.DAILY_CARD_BASE before loading app.js. nginx strips /daily-card/api/* prefix to /api/* — no backend route changes needed.
| Command | Purpose |
|---|---|
./scripts/publish-web.sh | Publish web/ to a version directory and atomically switch the current symlink |
./scripts/publish-web.sh --list | View published versions and current symlink target |
./scripts/publish-web.sh --rollback [ver] | Instant rollback, no Python restart required |
./scripts/test_subpath.py | In-process simulation of nginx prefix stripping, 10-chain assertions |
Frontend static versions can be released and rolled back independently of the backend. Use DAILY_CARD_WEB_ROOT (default /opt/daily-card-web) and DAILY_CARD_WEB_KEEP (default 5) to override. Trailing slash is mandatory: /daily-card must 301 redirect to /daily-card/, otherwise relative paths resolve to the domain root. Full nginx config, systemd, caching, and authentication details are in docs/DEPLOY.md.
When an agent calls the remote API, DAILY_CARD_URL must be set to the prefix (e.g., https://host/daily-card); the code will construct the full URL as https://host/daily-card/api/save — do not set it to the bare domain.
JSON API (Agents Can Call Directly, No Browser Needed)
| Endpoint | Description |
|---|---|
GET /api/health | Health check: version, running mode, config directory (no frontend required) |
GET /api/bootstrap | Fetch themes_doc, settings, registry, platforms, and paths in one call |
POST /api/render | Generate a preview; returns {png: dataURL, path, theme, size, bytes} |
POST /api/save | Generate and write to output/; returns the saved path |
POST /api/theme-preview | Unsaved theme draft → card + semiotics diagram + sticker sheet + readability diagnostics; does not write config files |
POST /api/schemes | Background image → multiple color schemes. Send {"path":"/abs/bg.jpg"} or {"image":"data:image/png;base64,..."} |
GET /api/schemes?image_id=xxx | Re-fetch schemes for a previously uploaded image |
POST /api/config/themes | {"themes_doc": {...}} save theme configuration (with validation) |
POST /api/config/settings | {"settings": {...}} save global settings |
POST /api/config/reset | Restore factory themes (overwrites private themes; back up first) |
POST /api/reveal | Open the output folder |
GET /api/sticker?id=book&theme=study | Sticker icon PNG |
Request body for /api/render and /api/save:
{
"title": "Live well today",
"subtitle": "Make ordinary days a little brighter",
"platform": "xhs",
"theme": "auto",
"date": null,
"brand": "Claw · Daily Quote",
"seed": null,
"bg_image_id": null,
"bg_path": "./bg.jpg",
"bg_scheme": 0,
"dark_override": null
}
bg_schemecan be an integer index or#RRGGBB;dark_overrideastrue/falseforces light/dark card,nullfollows image detection.bg_image_idis returned by/api/schemes; alternatively,bg_pathcan be a path accessible by the server.- The returned
pngisdata:image/png;base64,...— decode and write to file.
Example (when DAILY_CARD_URL is not set, uses local):
DC_URL="${DAILY_CARD_URL:-http://127.0.0.1:8777}"
curl -s "$DC_URL/api/save" \
-H 'Content-Type: application/json' \
-d '{"title":"Live well today","subtitle":"Make ordinary days a little brighter","platform":"xhs"}' \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["path"])'
Theme Draft Preview / Color Diagnostics (Inspect Before Saving)
To evaluate a color scheme's appearance and text readability before writing the config, call /api/theme-preview. The theme field contains the complete theme draft, which the server only keeps in memory:
{
"theme": {
"label": "MY THEME · New Theme",
"keywords": ["check-in"],
"motif": "dots",
"dark": false,
"colors": {
"bg1": "#F7E9C8", "bg2": "#EED6A4", "card": "#FFFCF6",
"ink": "#2E3A2C", "sub": "#6D7570", "accent": "#E0703C",
"accent2": "#5D8B6A", "soft": "#F1D9A8", "pill": "#2E3A2C",
"pill_text": "#FFFDF5"
},
"stickers": ["book", "pencil", "sun"]
},
"title": "Sample Title",
"subtitle": "Sample Subtitle",
"platform": "moments",
"date": "2026-11-11",
"seed": 7
}
Returned fields (card / legend / stickers / sticker_sheet are all data:image/png;base64,...):
| Field | Meaning |
|---|---|
card | Card rendered from this draft |
legend | Semantic color scheme diagram: real pattern + card layers + what each of the 10 colors controls |
stickers | Selected stickers as they appear under the current color scheme |
sticker_sheet | Full sticker sprite, along with sheet (cols/rows/cell/order) for slicing and display |
diagnostics | [{level: ok/warn/tip, text: Chinese description}] |
contrast | Contrast ratio values (ink_card / sub_card / pill_pair / card_bg / accent_bg / bg_light) |
motif / dark / draft_only | Pattern Chinese name and description, light/dark flag, always true indicating unsaved |
diagnostics checks WCAG contrast: readability of main text/subtext/label text, contrast between card and background, whether accent colors pop, and contradictions like "very dark background but set as light theme". Recommended workflow: First POST a draft to see diagnostics → if there's a warn, adjust colors based on hints and re-run → only call /api/config/themes to save when there are no warnings.
Without the backend, you can call the module directly:
import base64, cardconfig as cfg, cardpreview as cp
draft = cfg.load_doc()["themes"]["study"] # Nested structure, includes colors
draft["colors"]["accent"] = "#C2185B" # Make a change to test
r = cp.build_preview({"theme": draft, "title": "Sample", "subtitle": "Subtitle"})
for it in r["diagnostics"]:
print(it["level"], it["text"])
open("/tmp/draft-card.png", "wb").write(base64.b64decode(r["card"].split(",", 1)[1]))
Updates & Multi-Sync
The single source of truth for versions is the git repository; all other endpoints are derivatives, with one-way flow:
| Endpoint | Update Method | Private Config |
|---|---|---|
| Local machine / Cloud server | git pull && ./install.sh | Unaffected, not overwritten |
| Cloud server frontend static version | ./scripts/publish-web.sh, --rollback for instant rollback | Decoupled from Python service, no restart needed |
| Local skill registration | ./install.sh --link-skill creates symlink to avoid config drift | Unaffected |
| Skillhub platform snapshots | Maintainer tags and re-imports; inherently version-lagged | Platform only gets factory template |
The factory template themes.default.json is updated with versions; the user's private themes.json is never overwritten. To merge new factory themes into your own config, manually copy the relevant entries from themes.default.json, or use the backend's "Export JSON → Merge → Import" workflow. Do not run --init-config / /api/config/reset directly.
Server deployment and CI/CD instructions are in docs/DEPLOY.md. scripts/selfcheck.sh runs 8 steps, including frontend path portability checks and subpath deployment simulation (in-process proxy, no actual nginx required).
Agent Recommended Workflow
- Extract title and subtitle from user input (if no subtitle is given, generate a short, fitting phrase, or leave it empty).
- Determine platform: if unspecified, use
xhs; if Official Account is mentioned, usewechat; for Moments, usemoments. - If the user provides a background image: first run
--list-schemesor call/api/schemesto see candidates, then generate with the chosen scheme; if the user doesn't specify one, use scheme 0. - Execute the CLI (no backend needed); only call
/api/savewhen the user explicitly requests a backend/remote workflow. - Use present_files to show the final image, and explain the theme, dimensions, date, and color scheme in your response.
- If the user wants to change the style: edit
config/themes.jsonor use the backend config panel — do not editcardgen.py.
Notes
- Code division:
cardgen.pyhandles rendering;cardconfig.pyhandles config loading/saving/color extraction;cardpreview.pyhandles theme draft previews and color diagnostics;server.pyis the backend. All read the sameconfig/*.jsonfiles. output/,uploads/,config/themes.json,config/settings.json, and.envare all in.gitignore;examples/contains demo images (~6.7 MB, not required for runtime, can be deleted at any time).- Theme IDs only allow alphanumeric characters,
-, and_; colors must be#RRGGBB; sticker IDs must exist inconfig/stickers.json. - Larger image size and higher oversampling will slow down generation; default 2x oversampling is sufficient for clarity.
server.pyhas zero authentication and by default listens only on127.0.0.1. Before public deployment, be sure to add reverse proxy authentication or IP whitelisting; for cloud deployment, use--api-onlyto expose only the API. Seedocs/DEPLOY.mdfor details.- Source code must not contain absolute paths like
src="/..."/href="/..."/"/api/...";selfcheck.shwill block such regressions. All requests should useurl().
Known Limitations / Next Version Plans
- Custom sticker upload is not supported (current version): Stickers can only be chosen from the 28 registered vector stickers in
config/stickers.json. There is no separate sticker library management module, and uploading PNG/SVG as stickers is not supported. To add new shapes, developers currently need to add ans_xxxdrawing function incardgen.pyand register it instickers.json. - Next version plans: Custom sticker upload (PNG/SVG, with automatic color extraction to match themes), sticker library management panel (add/delete/edit/group/preview/reuse), and decoupling stickers from themes. This has been explicitly deferred to the next version by the user and is not included in this release.
- Theme draft preview uses 1x oversampling for faster responses; the final 2x output may have slight differences in very fine details, but layout and color scheme are consistent.