content-publish
$
npx mdskill add joelhooks/joelclaw/content-publishPublishes content to joelclaw.com via a Convex-first pipeline covering draft to verification.
- Manages the full content lifecycle from drafting to publishing and verification.
- Integrates with Convex for data storage and secret leasing.
- Uses content types and tag conventions to categorize articles, tutorials, notes, and essays.
- Delivers results by executing commands like 'publish article <slug>' or 'push to convex'.
SKILL.md
.github/skills/content-publishView on GitHub ↗
---
name: content-publish
displayName: Content Publish
description: "Publish content to joelclaw.com via the Convex-first pipeline. Covers the full lifecycle: draft → review → publish → revalidate → verify. Handles secret leasing, tag conventions, content types (article, tutorial, note, essay), and verification gates. Use when: 'write article about X', 'publish article <slug>', 'draft a tutorial', 'publish this', 'push to convex', or any content publishing task."
version: 0.1.0
author: joel
tags:
- content
- convex
- publishing
- web
---
# Content Publish
Publish content to joelclaw.com through the Convex-first pipeline. Every article, tutorial, note, and essay flows through this skill.
## Content Types
| Type | `fields.type` | When to use |
|------|--------------|-------------|
| article | `article` | Standard blog post, opinion, narrative |
| tutorial | `tutorial` | Implementable spec — agent or human can build from it |
| note | `note` | Short observation, link commentary, video note |
| essay | `essay` | Long-form, thesis-driven |
**Tags must include the content type.** A tutorial gets `tags: [..., "tutorial"]`. An essay gets `tags: [..., "essay"]`. This enables filtered views and search facets.
## Lifecycle
### 1. Draft
Upsert to Convex with `draft: true`:
```bash
npx convex run contentResources:upsert '<JSON>'
```
Required fields:
```json
{
"resourceId": "article:<slug>",
"type": "article",
"fields": {
"title": "The Title",
"slug": "the-slug",
"description": "One-liner for cards and meta",
"date": "2026-03-02T10:14:00.000Z",
"tags": ["topic1", "topic2", "tutorial"],
"draft": true,
"content": "Full MDX body (frontmatter stripped)"
}
}
```
**Slug rules**: lowercase, hyphenated, no special chars. Derived from title. Check for collisions first:
```bash
npx convex run contentResources:getByResourceId '{"resourceId": "article:<slug>"}'
```
**Date**: Full ISO datetime, not bare date. Determines sort order.
**Content**: Strip any frontmatter from MDX before setting `fields.content`. The frontmatter fields are stored as separate Convex fields, not inline.
### 2. Content preparation
For large content, prepare the JSON payload with Node to handle escaping:
```bash
cd ~/Code/joelhooks/joelclaw/apps/web
node -e "
const fs = require('fs');
const content = fs.readFileSync('<path-to-mdx>', 'utf-8')
.replace(/^---[\\\\s\\\\S]*?---\\\\n/, '').trim();
const args = {
resourceId: 'article:<slug>',
type: 'article',
fields: {
title: '<title>',
slug: '<slug>',
description: '<description>',
date: '<ISO datetime>',
tags: [<tags>],
draft: true,
content: content,
},
};
fs.writeFileSync('/tmp/convex-args.json', JSON.stringify(args));
"
npx convex run contentResources:upsert \"\$(cat /tmp/convex-args.json)\"
```
**Always run `npx convex` from `apps/web/`** — that's where the Convex config and generated API types live.
### 3. Review
Drafts are visible in dev only (`NODE_ENV === "development"`). Preview at `localhost:3000/<slug>`.
Drafts return 404 in production — this is correct. Do not publish without review confirmation from Joel unless the content was explicitly pre-approved.
### 4. Publish
Update the document with `draft: false` and set `updated` timestamp:
```bash
# Re-run the upsert with draft: false
# Add fields.updated = current ISO datetime
```
### 5. Revalidate
Lease the revalidation secret and hit the API:
```bash
# Lease secret (TTL auto-managed)
SECRET=$(joelclaw secrets lease revalidation_secret)
curl -s -X POST "https://joelclaw.com/api/revalidate" \
-H "Content-Type: application/json" \
-d "{
\"secret\": \"$SECRET\",
\"tags\": [\"post:<slug>\", \"article:<slug>\", \"articles\"],
\"paths\": [\"/\", \"/<slug>\", \"/<slug>.md\", \"/<slug>/md\", \"/feed.xml\", \"/sitemap.md\"]
}"
```
Expected response: `{"revalidated": true, ...}`
**Tag convention**:
- `post:<slug>` — individual post cache
- `article:<slug>` — content resource cache
- `articles` — list page cache
- Always include all three tags + the markdown/feed/sitemap paths
### 6. Verify
```bash
# Must return 200
curl -s -o /dev/null -w "%{http_code}" "https://joelclaw.com/<slug>"
# Markdown twin must return 200 and current content
curl -s -o /dev/null -w "%{http_code}" "https://joelclaw.com/<slug>.md"
# Must appear on homepage
curl -s "https://joelclaw.com" | grep -c "<slug>"
# Feed should include it
curl -s "https://joelclaw.com/feed.xml" | grep -c "<slug>"
```
All four checks must pass. If the slug page returns 404 after revalidation, the Convex document is likely still `draft: true` or content is missing.
## Updating existing content
Same upsert flow. Set `fields.updated` to bump sort position. Always revalidate after update.
## Gotchas
- **Convex CLI must run from `apps/web/`** — it needs the project config
- **Content must have frontmatter stripped** — Convex fields ARE the metadata; don't duplicate in content body
- **ISO datetimes, not bare dates** — `2026-03-02T10:14:00.000Z` not `2026-03-02`
- **Secret is ephemeral** — lease from `agent-secrets`, never hardcode or cache
- **Large content needs JSON escaping** — use Node script to build payload, not manual string interpolation
- **Tags must include content type** — tutorials get `"tutorial"` tag, essays get `"essay"` tag
- **The filesystem `content/` directory is gitignored seed material** — Convex is the source of truth at runtime
More from joelhooks/joelclaw
- add-skillCreate new joelclaw skills with the idiomatic process — repo-canonical, symlinked, git-tracked, slogged. Triggers on 'add a skill', 'create skill', 'new skill', 'canonical skill', 'make a skill for', or any request to formalize a process or domain into a reusable skill.
- adr-skillCreate and maintain Architecture Decision Records (ADRs) optimized for agentic coding workflows. Use when you need to propose, write, update, accept/reject, deprecate, or supersede an ADR; bootstrap an adr folder and index; consult existing ADRs before implementing changes; or enforce ADR conventions. This skill uses Socratic questioning to capture intent before drafting, and validates output against an agent-readiness checklist.
- agent-discovery"Optimize websites, docs, and product surfaces for agent discoverability and operator UX. Use when working on agent SEO/AEO/GEO, crawl policy, markdown or JSON projections, llms.txt, sitemap.md, AGENTS.md guidance, content negotiation, accessibility for browser agents, or any request to make a site easier for pi, OpenCode, Claude Code, ChatGPT, Perplexity, or other agent harnesses to find and use."
- agent-loopStart, monitor, and cancel durable multi-agent coding loops via Inngest. Use when the user wants to run autonomous coding workloads, execute a PRD with multiple stories, kick off an AFK coding session, have agents implement features from a plan, or manage running loops. Triggers on "start a coding loop", "run this PRD", "implement these stories", "go AFK and code this", "check loop status", "cancel the loop", "joelclaw loop", or any request for autonomous multi-story code execution.
- agent-mail>-
- agent-workloads"Compatibility alias for the canonical `workflow-rig` front door. Use when older prompts mention `agent-workloads` or when you need the legacy workload-planning guidance; for new work, load `workflow-rig` first."
- clawmail>-
- cli-design"Design and build agent-first CLIs with HATEOAS JSON responses, context-protecting output, and self-documenting command trees. Use when creating new CLI tools, adding commands to existing CLIs (joelclaw, slog), or reviewing CLI design for agent-friendliness. Triggers on 'build a CLI', 'add a command', 'CLI design', 'agent-friendly output', or any task involving command-line tool creation."
- codex-prompting"Use this skill for any request to trigger, coordinate, or craft prompts for Codex. Use when user says 'send to codex', 'use codex', 'prompt codex', 'ask codex', 'delegate to codex', 'run in codex', or asks for a Codex-first execution handoff."
- contributing-to-pi"Contribute fixes, bug reports, and upstream discussions to badlogic/pi-mono without wasting maintainer time. Use when filing pi issues, preparing pi PRs, debugging whether a bug belongs upstream, or responding to maintainer pushback. Triggers on: 'contribute to pi', 'pi-mono issue', 'upstream pi fix', 'open a pi issue', 'why did Mario reject this', or any work in ~/Code/badlogic/pi-mono meant for upstream."