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.