prompt-lookup
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use…
Manage Ghost CMS blogs through the Content and Admin APIs. Use for reading, creating, editing, publishing, or scheduling posts and pages, uploading images, and managing tags, members, and newsletters.
Claude Code
$ npx -y skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog --agent claude-code
Codex
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a codex -y
Cursor
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a cursor -y
Gemini CLI
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a gemini-cli -y
GitHub Copilot
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a github-copilot -y
OpenCode
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a opencode -y
Windsurf
$ npx skills add georgeguimaraes/ghost-agent-tools --skill ghost-blog -a windsurf -y
name: ghost-blog description: "Manage Ghost CMS blogs through the Content and Admin APIs. Use for reading, creating, editing, publishing, or scheduling posts and pages, uploading images, and managing tags, members, and newsletters."
Interact with the user's Ghost blog using the Content API (read-only, public data) and Admin API (full read/write access).
| Variable | Purpose | |----------|---------| | `GHOST_API_URL` | Base URL of the Ghost instance (e.g. `https://myblog.ghost.io`) | | `GHOST_CONTENT_API_KEY` | Content API key for read-only public access | | `GHOST_ADMIN_API_KEY` | Admin API key in `{id}:{secret}` format for full access |
These are created in Ghost Admin under Settings > Integrations > Custom Integration.
All operations use the `Ghost` class from `ghost_api.py` (stdlib only, no dependencies). It handles JWT auth, JSON serialization, and HTTP requests. The module lives in the same directory as this skill file.
Requires [uv](https://docs.astral.sh/uv/). Resolve `GHOST_SKILL_DIR` to the absolute directory containing this `SKILL.md`, wherever the skill is installed. Both Python helpers live beside it. Use that path rather than the working directory:
GHOST_SKILL_DIR="/absolute/path/to/ghost-blog" PYTHONPATH="$GHOST_SKILL_DIR" uv run --no-project python - << 'PY' from ghost_api import Ghost g = Ghost() # ... operations here ... PY
| Method | Signature | Description | |--------|-----------|-------------| | `g.get(path, **params)` | Returns dict | GET request, params become query string | | `g.post(path, data, **params)` | Returns dict | POST with JSON body | | `g.put(path, data, **params)` | Returns dict | PUT with JSON body | | `g.delete(path)` | Returns None | DELETE request | | `g.upload(file_path, ref=None)` | Returns URL string | Multipart image upload | | `g.unsplash_search(query, orientation="landscape", per_page=10)` | Returns list of dicts | Search Unsplash photos | | `g.unsplash_caption(photo_id=None, user_name=None, user_username=None)` | Returns HTML string | Build Unsplash attribution caption | | `g.set_unsplash_feature_image(post_id, photo_id)` | Returns post dict | Set feature image from Unsplash photo ID |
**Path convention**: paths start with `content/` or `admin/` (e.g. `content/posts`, `admin/posts/abc123`). Auth is handled automatically based on prefix.
# Public posts
posts = g.get("content/posts", include="tags,authors", limit=15)
# All posts including drafts
posts = g.get("admin/posts", include="tags,authors", formats="html", limit=15)
for p in posts["posts"]:
date = (p.get("published_at") or "(draft)")[:10]
tags = ", ".join(t["name"] for t in p.get("tags", []))
print(f" {date} {p['title']} {tags}")
print(f"Total: {posts['meta']['pagination']['total']}")Pass `filter` as a query param using NQL syntax:
# By tag
posts = g.get("content/posts", filter="tag:my-tag", include="tags")
# Drafts only (Admin API)
drafts = g.get("admin/posts", filter="status:draft", formats="html")
# Last 7 days
recent = g.get("admin/posts", filter="published_at:>now-7d", formats="html")
# Combined: published + specific tag (+ is AND, comma is OR)
posts = g.get("admin/posts", filter="status:published+tag:news", formats="html")**NQL operators**: `:` (equals), `-` (not), `>` `>=` `<` `<=` (comparison), `~` (contains), `[a,b]` (in), `+` (AND), `,` (OR), `()` (grouping). Wrap dates/special chars in single quotes.
# By ID
post = g.get("admin/posts/POST_ID", formats="html", include="tags,authors")["posts"][0]
# By slug (Content API)
post = g.get("content/posts/slug/my-post-slug", include="tags,authors")["posts"][0]Use `g.create_post()` which automatically adds `source=html` when HTML content is present (Ghost v5+ requires this to convert HTML to its internal Lexical format; without it, content will be empty):
post = g.create_post({
"title": "My New Post",
"html": "<p>Post content in HTML.</p>",
"status": "draft",
"tags": [{"name": "Tag Name"}],
"custom_excerpt": "A short excerpt.",
})
print(f"Created: {post['title']} (ID: {post['id']})")**Status options**: `draft` (default), `published`, `scheduled` (requires `published_at`).
Tags that don't exist are created automatically. To preserve raw HTML blocks, wrap in `<!--kg-card-begin: html-->` and `<!--kg-card-end: html-->`.
Use `g.update_post()` which fetches `updated_at` automatically and adds `source=html` when HTML content is present. Tags and authors are **replaced entirely** on update, so send the complete desired list.
post = g.update_post("POST_ID", {
"title": "Updated Title",
"html": "<p>Updated content.</p>",
})If you already have `updated_at` from a previous GET, pass it to skip the extra request:
post = g.update_post("POST_ID", {
"html": "<p>Updated content.</p>",
}, updated_at=existing_post["updated_at"])post = g.get("admin/posts/POST_ID")["posts"][0]
g.put("admin/posts/POST_ID",
{"posts": [{"status": "published", "updated_at": post["updated_at"]}]})post = g.get("admin/posts/POST_ID")["posts"][0]
g.put("admin/posts/POST_ID",
{"posts": [{
"status": "scheduled",
"published_at": "2026-03-15T11:00:00.000Z",
"updated_at": post["updated_at"],
}]})**Always confirm with the user before deleting.**
g.delete("admin/posts/POST_ID")url = g.upload("/path/to/image.jpg")
print(url) # https://myblog.ghost.io/content/images/2026/02/image.jpgSupported formats: JPEG, PNG, GIF, WEBP, SVG.
After uploading, use this HTML to embed
A skill for managing Ghost blogs from Codex, Claude Code, and other agents that support Agent Skills.
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use…
AI-driven presentation workflow for generating editable PPTX decks and slides, reconstructing page visuals, creating reusable…
Use this skill when the user wants a .pptx with smooth cross-slide animation — PowerPoint Morph transitions, Keynote-style…
Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to…