claude code skill
/jba-post
Drafts an update from your latest commit (lowercase, honest tone), shows it for approval, then ships via the bearer-token API. Attach screenshots to the chat and they ship as a carousel.
source
---
name: jba-post
description: Post an update to JustBuildApps from the current repo. Drafts from recent git activity in a lowercase, honest tone, shows for approval, then ships via bearer-token API. Use when the user says /jba-post, "post to jba", "ship an update to justbuildapps", or after finishing a notable change worth surfacing on a creation's feed.
---
# jba-post — ship an update to JustBuildApps
You're posting an update to a "creation" on JustBuildApps (the creator feed at `/u/<handle>`). Updates have a kind, title, body — they're short, honest, lowercase.
## Inputs
The user invokes via `/jba-post` (manual) or asks you to "post an update". They may pass:
- A `kind` hint: `new` | `iteration` | `commentary` (optional — infer if missing)
- A creation slug override (optional — usually inferred from cwd)
- Free-form context like "about the dark mode work"
## Config
Three files in `~/.config/jba/`:
- `handle` — the user's JustBuildApps handle (single line, no `@`). Used to look up their creator profile.
- `token` — bearer write token. Either a bare value or an env-style line like `JBA_WRITE_TOKEN=xxx` (the skill strips the prefix). Must match the server's `JBA_WRITE_TOKEN`.
- `repos.json` — `{ "<absolute-repo-path>": "<creation-slug>", ... }`
If `handle` is missing, ask the user for their handle and offer to write it.
API base: defaults to `https://justbuildapps.com/api/v1`. Override via `JBA_API_BASE` env. For local dev: `JBA_API_BASE=http://localhost:8000/api/v1`.
Token + handle extraction one-liners (use these in bash):
```bash
JBA_TOKEN=$(awk -F= '{ if ($1=="JBA_WRITE_TOKEN") print $2; else print $0 }' ~/.config/jba/token | tr -d '\n\r ')
JBA_HANDLE=$(tr -d '\n\r @' < ~/.config/jba/handle)
```
## Steps
1. **Locate the repo.** `git rev-parse --show-toplevel`. If not in a git repo, ask the user which creation slug to post to.
2. **Pick the creation slug.** Read `~/.config/jba/repos.json` and look up the absolute repo path. If there's no match, list known creations from `GET /api/v1/creators/$JBA_HANDLE` and ask which one to post to (and offer to add the mapping to `repos.json`).
3. **Read git context.**
```bash
git log -5 --oneline
git log -1 --pretty=format:'%s%n%n%b'
git diff HEAD~1 --stat
```
Use the most recent meaningful commit (skip merges, "wip", tag-only). If the user gave free-form context, weight it heavily.
4. **Resolve creation_id.** `GET /api/v1/creators/$JBA_HANDLE` (no auth needed), find the creation matching the slug, grab its `id`. Cache in memory for this turn.
4a. **Check for media.** If the user attached one or more images, gifs, or videos to the chat, each appears as a file path on disk (typically `/tmp/...` or under `~/Pictures/...`, often referenced with an `image_path` attribute or surfaced in a `<system-reminder>`). Treat them as media attachments for this post — order matters (first attachment is the cover, the rest become a carousel).
In the draft block, list them on a single `media:` line with the cover first, separated by `, `:
- One file: `media: cover.png`
- Multiple: `media: cover.png, second.png, third.png`
If no media is attached, skip this step and omit the `media:` line.
Allowed types: `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `video/mp4`, `video/webm`, `video/quicktime`. Cap is 20MB per file. Max 8 files per post.
5. **Draft the update.** Output exactly this block to the user, nothing else around it:
```
┌─ jba update draft ──────────────────────────
creation: <name> (slug: <slug>, id: <id>)
kind: <new|iteration|commentary>
media: <filename(s) or omit if none>
title: <≤100 chars, lowercase, no trailing period>
<body — 1-3 short paragraphs, ≤2000 chars>
└─────────────────────────────────────────────
```
Then ask: **"ship it? (y / edit / cancel)"**
6. **Loop on edits.** If user says "edit X" or rewrites text, redraft and re-show. Only proceed on explicit `y` / `yes` / `ship`.
7. **Upload media (if attached).** Only after approval. The upload endpoint is bearer-authed and returns one URL per file. Upload each file in order, collecting URLs into a JSON array.
```bash
JBA_TOKEN=$(awk -F= '{ if ($1=="JBA_WRITE_TOKEN") print $2; else print $0 }' ~/.config/jba/token | tr -d '\n\r ')
JBA_API_BASE=${JBA_API_BASE:-https://justbuildapps.com/api/v1}
# Repeat per file, in attachment order:
URL_1=$(curl -sS -X POST "$JBA_API_BASE/media/upload" \
-H "Authorization: Bearer $JBA_TOKEN" \
-F "file=@<path-to-file-1>" \
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("url",""))')
# URL_2=... # repeat for each additional file
# Build the JSON array for the post body:
MEDIA_URLS_JSON=$(python3 -c 'import json,sys; print(json.dumps([u for u in sys.argv[1:] if u]))' "$URL_1" "$URL_2")
```
Each URL looks like `/api/v1/media/<uuid>.<ext>`. If any upload fails, show the error and ask whether to ship without media (or with the partial set).
8. **POST it.** Include `media_urls` (array) in the body if step 7 ran successfully. The server keeps `media_url` in sync with `media_urls[0]` so old clients still work.
```bash
curl -sS -X POST "$JBA_API_BASE/creations/$CID/updates" \
-H "Authorization: Bearer $JBA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"kind\":\"<kind>\",\"title\":\"<title>\",\"body\":\"<body>\",\"media_urls\":$MEDIA_URLS_JSON}"
```
For zero attachments, omit `media_urls` entirely. For exactly one, you can use either `"media_urls":["<URL>"]` or the legacy `"media_url":"<URL>"` — both are accepted.
On success, show the response (id, published_at) and the live URL: `https://justbuildapps.com/u/$JBA_HANDLE` for prod, `http://localhost:3000/u/$JBA_HANDLE` for local.
## Tone
JustBuildApps posts read like indie shipping logs. Match this style by default; the user can override.
- **lowercase everywhere**, including titles. No caps unless quoting a name (Android, iOS, GitHub).
- short sentences. one idea per line.
- direct and honest. acknowledge what doesn't work. no marketing speak.
- no emojis. no hashtags. no exclamation points except when natural.
- words to avoid: delve, holistic, robust, leverage, journey, thrilled, excited to announce.
**Good titles:** "fixed the accelerometer on android", "stripped like/skip from /explore", "high scores save now", "switched the llm to gemini 2.5 flash"
**Good bodies:** Set the change in one line. Add why or what was surprising. End with the truth (what's left, what still doesn't work). Like: *"localstorage. not even sql. you score, it sticks. that's the whole feature."*
## Kind inference
- `new` — the creation itself just launched / first time it's live. Rare.
- `iteration` — a feature, fix, refactor that ships. Default for most commits.
- `commentary` — a thought, retrospective, decision-not-yet-built. No code change required.
If the diff is meaningful code, it's `iteration`. If the message is reflective with no code, it's `commentary`.
## Errors
- **401** → token missing/wrong. Tell the user: `set the token at ~/.config/jba/token (same value as server's JBA_WRITE_TOKEN env)`
- **403 "Not your creation"** → slug resolved to a creation owned by someone else. Re-check `repos.json` and `handle`.
- **404 "Creation not found"** → bad creation_id, re-fetch creator.
- **5xx** → show the response, ask user whether to retry.
## Don't
- Don't auto-post without explicit `y` from the user. HITL is the whole point.
- Don't fabricate detail not present in the diff or user-provided context.
- Don't write more than 3 paragraphs of body unless the user asks.
- Don't add a trailing summary like "this is a great update!" — the post IS the output.
~JustBuildApps