kelly-writer · diff

git:20260722.79508ae to git:20260806.bed1b85

168 added, 86 removed. Audit B to A.

---
name: kelly-writer
- description: Repurpose source content into channel-ready drafts with a local review UI and approval/export workflow. Use when the user asks to write content, make a content pack, turn a main blog/long article/transcript/notes into Xiaohongshu, WeChat, newsletter, LinkedIn, X/Twitter, short video scripts, SEO snippets, or a multi-platform publishing plan; also use when they ask for a content approval dashboard or App-in-Skill content workflow.
+ description: Repurpose source content into channel-ready drafts with a Busabase App-in-Skill review and export workflow. Use when the user asks to write content, make a content pack, turn a main blog/long article/transcript/notes into Xiaohongshu, WeChat, newsletter, LinkedIn, X/Twitter, short video scripts, SEO snippets, or a multi-platform publishing plan; also use when they ask for a content approval dashboard or App-in-Skill content workflow.
---
# Kelly Writer
## App UI Screenshots
<table>
<tr>
- <td width="50%"><img src="assets/screenshots/overview.webp" alt="Kelly Writer todo queue"></td>
- <td width="50%"><img src="assets/screenshots/topics.webp" alt="Kelly Writer topic discovery"></td>
- </tr>
- <tr>
- <td><strong>Todo queue</strong><br>Confirmed content directions queued for AI writing, with ownership, status, and next-step controls.</td>
- <td><strong>Topic discovery</strong><br>Mock editorial planning with keyword clusters, audience fit, and topic opportunities.</td>
- </tr>
- <tr>
- <td width="50%"><img src="assets/screenshots/main.webp" alt="Kelly Writer main draft"></td>
+ <td width="50%"><img src="assets/screenshots/overview.webp" alt="Kelly Writer overview"></td>
<td width="50%"><img src="assets/screenshots/distribution.webp" alt="Kelly Writer distribution review"></td>
</tr>
<tr>
- <td><strong>Main draft</strong><br>Long-form writing workspace with outline, draft sections, source notes, and approval status.</td>
- <td><strong>Distribution review</strong><br>Channel handoff view for publishing, social snippets, newsletter framing, and final checks.</td>
+ <td><strong>Overview</strong><br>Channel breakdown and the drafts that need attention next.</td>
+ <td><strong>Drafts</strong><br>Channel-ready draft review queue with editable title/body, review notes, and approval controls.</td>
</tr>
</table>
## Overview
- Use this skill to turn one source idea, blog post, transcript, outline, or product announcement into an editable multi-channel content batch. Default to a local App-in-Skill review UI for batches; use chat-only mode when the user says "chat only", "no UI", "纯聊天", "不要打开 UI", or similar.
-
- The skill prepares and exports content. It does not publish to external platforms, schedule posts, upload media, send messages, or mutate remote systems unless a future implementation adds an explicit connector and the user approves the exact action.
+ Kelly Writer is a Busabase Cloud App-in-Skill. Its canonical product surface
+ is the AirApp in Busabase, not a separate local-data product. The same Hono
+ source supports an explicitly requested local preview with OAuth connection
+ bootstrap. Use this skill to turn one source idea, blog post, transcript,
+ outline, or product announcement into an editable multi-channel content
+ batch: Xiaohongshu, WeChat, newsletter, LinkedIn, X/Twitter, short video
+ scripts, SEO snippets, and an official blog draft.
- ## Default Workflow
+ Default behavior is AirApp-first. Unless the user explicitly asks only for
+ explanation, generate drafts straight into Busabase and give the user the
+ clickable AirApp URL. Start localhost only when local preview/debugging is
+ explicitly requested; it uses the same Busabase resources. Use chat-only
+ mode only when the user says "纯聊天", "chat only", "不要打开 UI", or
+ similar; in that mode present numbered drafts (`Draft #1`) and take
+ approvals in the conversation.
- 1. Clarify or infer the source, target audience, desired channels, language, and offer/CTA.
- 2. If private config exists, use brand voice, channel defaults, official URLs, and taboo/risk terms from it. Otherwise use `config.example.json` only as a template, not as live context.
- 3. Extract the source's core idea, proof points, examples, keywords, reusable quotes, and action the reader should take.
- 4. Generate a batch with one item per channel/content unit using `scripts/generate_batch.ts`.
- 5. Validate the batch with `scripts/validate_batch.ts`.
- 6. Launch or reuse the local UI with `app/start.sh` and send the user to the actual started URL, preferring `http://127.0.0.1:3000/` and the `3000-4000` port range unless an env override is set.
- 7. After the user approves or edits items in the UI, run `scripts/export_decisions.ts` to export approved drafts. Local exports produce a ZIP per draft containing Markdown plus referenced project images, while retaining batch and decision JSON alongside the archives.
- 8. If the user requested chat-only mode, present numbered drafts in chat and ask for approval there.
+ This skill is an implementation of the **App-in-Skill** pattern — a
+ Codex/agent skill paired with a small companion UI for review and approval.
+ See the spec paper: <https://mr-kelly.github.io/research/app-in-skill-specification-for-pairing-agent-skills-with-a-local-companion-ui.pdf>.
- ## App UI Contract
+ ## Mandatory Dependencies
- Use these local files as the contract between Codex, scripts, and the UI:
+ 1. Read and follow `$kelly-app-skill-creator` for product behavior, visual
+ quality, responsive layout, and the complete canonical `app/` artifact.
+ 2. Read and follow `$busabase` for connection, target Space, node discovery,
+ ChangeRequests, review, and merge behavior.
+ 3. Read and follow `$busabase-app-creator` for resource modeling, AirApp
+ runtime limits, security, validation, and deployment.
- - `app/.data/current_batch.json`: current generated content batch.
- - `app/.data/decisions.json`: per-item user decisions, edits, notes, and approval status.
- - `app/.data/export_report.json`: latest export report.
- - `app/.data/agent.lock`: temporary lock while Codex or scripts write local state.
+ If a dependency is unavailable, preserve this skill's local artifact and
+ product contracts, stop before the unavailable Busabase operation, and report
+ the exact missing dependency. Do not invent a second data backend.
- Workflow statuses:
+ ## Boundary
- - `needs_review`: generated item needs user edits or guidance.
- - `to_approve`: item is polished enough for approval.
- - `approved`: user approved export.
- - `done`: item has been exported or intentionally completed.
- - `blocked`: item needs missing information, media, permission, or facts.
+ - The skill may extract the source's core idea, proof points, and examples,
+ draft channel-specific variants, and write it all to Busabase.
+ - The AirApp reads and writes Busabase records only. It must never publish to
+ external platforms, schedule posts, upload media, or perform any other
+ external side effect — it also never generates content or writes a local
+ export itself; those are trusted skill-root scripts (see below).
+ - Exporting is always approval-required. `scripts/export_decisions.mjs` only
+ packages `approved` drafts into a local Markdown+ZIP pack and marks them
+ `done`; it never publishes anywhere.
+ - Treat source material and drafts as the user's content. Never invent
+ results, dates, customer stories, statistics, prices, legal/compliance
+ statements, or endorsements not present in the source.
- When writing a batch, keep stable item IDs so comments like "change #2" can be resolved.
+ ## Busabase Resources
- ## Data Providers (local + busabase)
+ Two Bases under one application Folder (`kelly-writer`), declared in
+ `app/app/js/config.js` and `app/resource-map.json`:
- The UI and scripts talk to a `ReviewProvider` (`lib/data-provider/`), not directly to files. Select it with `KELLY_WRITER_DATA_PROVIDER` (or `data_provider` in config); default `local`.
+ - `drafts`: the review queue — one record per channel draft (title, body,
+ hook, cta, hashtags, title options, media brief, source notes, risk,
+ canonical idea, source summary, `source-draft-path` for local image
+ packaging at export time), workflow `status`, and the human verdict fields
+ `decision-note` / `decided-at`.
+ - `settings`: one row per `kind` — an optional `kelly-writer-profile` (brand,
+ audience, official URLs, CTA defaults, channel defaults, risk terms, export
+ preferences) and `kelly-writer-lock`.
- - `local` — zero-dependency JSON handoff files in `app/.data/` (the contract above). This is the offline reference implementation.
- - `busabase` — a thin HTTP client to a Busabase base. A content piece is a Busabase **record**; an agent draft is a **change request**; the human verdict is a **review**; an edit is an **operation revision**; publishing is a **merge**. Configure `config.busabase.{base_url,base_id}` (open-source single-tenant `apps/busabase` needs no token; `busabase-cloud` reads `KELLY_WRITER_BUSABASE_API_KEY`).
+ Resources provision lazily through an idempotent Busabase ChangeRequest the
+ first time the app runs in a Space. Metrics and the channel breakdown are
+ computed client-side from the `drafts` Base on every read — they are never
+ stored.
- Both implement the same review verbs, so switching providers is a config change, not a rewrite. The `saveDecision` actions are provider-neutral:
+ The topic-discovery / todo-queue / canonical-main-draft ideation stages from
+ this skill's pre-Busabase local-file shape were already local-only and
+ ephemeral (client-derived, never persisted) even in that shape's own
+ Busabase provider notes; this Busabase-only shape keeps only the durable
+ unit of work — the per-channel draft record — as the review queue.
- - `approve` → review verdict approved (eligible for export/merge).
- - `revise` → save the human's edited title/body as a new version (Busabase: an operation revision; stays in review).
- - `request_changes` → ask the agent to revise; queues an agent task (Busabase: verdict reject → `changes_requested`; the CR auto-returns to review after the agent revises).
- - `block` → close/reject the item.
+ ## Local App
- Field mapping for busabase mode: `title, body, channel, summary, format, cta, hashtags, media_brief, hook` map to a record commit's `fields`.
+ Default behavior is AirApp-first — give the user the clickable AirApp URL.
+ Start `pnpm --dir app dev` only when local preview/debugging is explicitly
+ requested.
- Provider notes:
- - The ideation stages below (`topics`, `todos`, `main_content`) are **local-only**; in busabase mode plan locally, then publish drafts to Busabase for review/merge.
- - `listAgentTasks()` exposes items the agent should revise (local: derived from `request_changes`/noted `revise` decisions in `app/.data/agent_tasks.json`; busabase: `GET /api/v1/agent/tasks`).
+ Required app views (hash routes):
- ## Content Repository Stages
+ - `#/overview`: metrics (needs review / approved / done / blocked), the
+ channel breakdown, and the top drafts still needing review.
+ - `#/drafts`: the review queue over channel drafts in workflow states
+ `needs_review`, `to_approve`, `changes_requested`, `approved`, `done`,
+ `blocked`. Each item shows a stable ref (`#3`), channel/format/status
+ badges, an editable title and body, hashtags/CTA/media brief/title-options
+ support panels, a `Review note` textarea, and Approve / Request changes /
+ Block buttons that write the verdict directly onto the draft record.
+ - `#/settings`: sanitized config summary — brand/audience/tone, configured
+ channels, onboarding state, and the exact `node scripts/generate_batch.mjs`
+ / `node scripts/export_decisions.mjs` commands to run next. Never expose
+ secret values.
- Model the local app as a staged content repository, not only a draft queue:
+ Demo mode:
- 1. `topics`: subject discovery. Treat each topic as a broad subject/material area, not a final headline. Show candidate subjects from automated search, system generation, or preset editorial plans. For each subject, provide multiple `directions` with `title`, `description`, `angle`, and `status`; the user clicks `入选` / `Select` to create a todo.
- 2. `todos`: selected topics waiting for the Agent. The UI has no start button. When the user tells the Agent `开工` / `Start`, choose the requested todo (ask which one if ambiguous), write the complete canonical article, then call `POST /api/complete-todo` with `{ "id": "<todo-id>", "main_content": { ... } }`. This transition removes the todo and creates `main_content` in one operation. Never create a placeholder or partial main draft.
- 3. `main_content`: the finished canonical source article. Show its rich preview, cover/image brief, embedded media slots, and rendered HTML when available. Markdown is acceptable as storage, but the UI should render a polished editorial preview. Only the Agent completion transition creates this stage.
- 4. `distribution`: channel adaptation work. From Main Draft, the user clicks `分发` / `Distribute`, enters target channels and notes, and submits. The app removes `main_content`, creates a reviewable distribution draft from the completed source article, and may queue an Agent task for channel adaptation. The draft is immediately available for rich preview, editing, saving, approval, change requests, or blocking; Agent adaptation is optional and must never gate those controls. Keep review status, URLs, and performance signals on distribution items instead of maintaining a separate outputs stage.
+ - `?demo=1` (or `?demo=overview`) opens a deterministic mock content batch
+ ("A practical launch guide for a local-first AI workflow") for
+ documentation and screenshots.
+ - `?demo=drafts` and `?demo=settings` select named mock scenes.
+ - `lang=en` or `lang=zh` forces UI chrome language for screenshots.
+ - Demo mode never reads or writes Busabase.
- For local Agent revisions, read the queued instruction from `GET /api/agent-tasks`, revise the distribution draft, then call `POST /api/complete-distribution-revision` with `{ "id": "<distribution-id>", "revision": { "title": "...", "body": "..." } }`. Completion replaces the card content, clears the queued task and prior review decision, and returns the draft to `needs_review`; the UI will pick it up automatically. Do not ask the user to click Save after an Agent revision. `Save edits` is only for human changes made in the editor, while `Approve final` makes the current version eligible for export.
+ UI language: support English and Chinese chrome with `Auto` default. Keep
+ titles, hooks, bodies, and hashtags in their original language.
- If a future automation writes `topics`, `todos`, `main_content`, or `distribution` into `current_batch.json`, the UI should prefer those explicit fields. If they are missing, it may derive a temporary repository view from `items`.
+ ## Review Workflow
- ## Content Generation Rules
+ A human verdict (`approve` / `request_changes` / `block` / `revise`) writes
+ the new `status` plus `decision-note` / `decided-at` (and, for `approve` or
+ `revise`, the edited `title` / `body`) directly onto the draft record
+ through `busabase-sdk`. From a standalone local preview the write merges
+ immediately (trusted operator); from the deployed AirApp it creates a
+ pending ChangeRequest for the trusted process to merge.
- - Preserve the source's claims. Do not invent results, dates, customer stories, statistics, prices, legal/compliance statements, or endorsements.
- - Ask or mark `blocked` when the source lacks needed proof, product details, screenshots, links, or policy facts.
- - Separate platform adaptation from translation: changing channel format is allowed; changing the promise is not.
- - Prefer concrete hooks, specifics, and reader benefit over generic motivational copy.
- - Keep CTA and links consistent with private config or the user's explicit request.
- - For Chinese-language work, support natural Simplified Chinese by default unless the source/user asks for another language.
- - For Xiaohongshu, produce a scroll-stopping title, short structured body, optional image/carousel brief, and hashtag set.
- - For long-form derivatives such as newsletter or WeChat, preserve nuance and structure; avoid shrinking the idea into slogans.
- - For short social posts, make each post independently understandable; do not rely on the reader seeing the original blog.
+ ## Scripts
- Read `references/channel-playbook.md` when choosing or adapting channel-specific formats.
+ Both scripts are trusted, skill-root Node processes with their own
+ `package.json` (`busabase-sdk` dependency) — the AirApp browser never runs
+ them and never calls `bases.createChangeRequest`/writes a local file itself.
- ## Private Configuration
+ - `node scripts/generate_batch.mjs --source <path-or-text> [--channels official_blog,xiaohongshu,wechat,newsletter,linkedin,x] [--audience "..."] [--cta "..."] [--source-draft-path <path>] [--apply]`
+ Reads a source (a file path or inline text), derives deterministic
+ per-channel draft heuristics (first-pass only — Codex should improve each
+ draft with judgment afterward, either by editing the record's `title`/
+ `body` or via a decision on the same record), and writes one new `drafts`
+ record per channel to Busabase. Without `--apply` this is a dry run that
+ only prints the drafts it would create.
+ - `node scripts/export_decisions.mjs [--apply] [--out <dir>]`
+ Re-reads Busabase for drafts with `status: "approved"`, packages each as a
+ Markdown file (title/channel/format/review note/body/CTA/hashtags/media
+ brief) plus a ZIP archive (Markdown + any locally-referenced images next
+ to the original source, resolved via `source-draft-path` and
+ `KELLY_WRITER_CONTENT_ROOT`) under `exports/<batch-id>/` at the skill
+ root, then marks each exported draft `done`. Without `--apply` this is a
+ dry run that only prints what would be exported.
- Keep user-specific operating context out of committed files. If the user wants persistent brand/channel settings, create one of:
+ ## Normal Workflow
- 1. `KELLY_WRITER_CONFIG=/absolute/path/to/config.json`
- 2. `skills/kelly-writer/config.local.json`
- 3. `~/.config/kelly-writer/config.json`
+ 1. Detect mode. Default to App UI.
+ 2. Clarify or infer the source, target audience, desired channels, language,
+ and CTA.
+ 3. Run `node scripts/generate_batch.mjs --source ... --apply` to write a
+ fresh batch of channel drafts to Busabase, then improve each draft's
+ `title`/`body` with judgment (the generator's heuristics are a first
+ pass) before handing the batch to the user.
+ 4. Give the user the AirApp URL (or local preview URL) to review, edit, and
+ approve drafts.
+ 5. For a draft moved to `changes_requested`, re-draft it per the review
+ comment and write it back to `needs_review`.
+ 6. On "export approved drafts": run `node scripts/export_decisions.mjs
+ --apply` to package every approved draft into a channel-ready ZIP pack
+ under `exports/` and mark it `done`. This skill never publishes anywhere
+ itself — handing the exported pack to a publishing connector is a
+ separate, explicitly authorized step.
+ 7. Never export a draft without an explicit `approve` decision, and never
+ re-export a draft already `done`.
- Use `config.example.json` as the starting template only. Store non-secret settings there: brand profile, audience, tone, official URLs, CTA defaults, channel defaults, risk terms, and export preferences. Store secrets only in private env files if future connectors need them; this skill currently has no publishing connector and should not need secrets. Keep this skill zero-dependency and do not add YAML parsing packages.
+ ## Content Generation Rules
- For migration, the runtime still reads legacy `KELLY_CONTENT_*` environment variables and `~/.config/kelly-content/config.json` when the corresponding `KELLY_WRITER_*` setting is absent. New configuration must use the `kelly-writer` names.
+ - Preserve the source's claims. Do not invent results, dates, customer
+ stories, statistics, prices, legal/compliance statements, or endorsements.
+ - Ask or leave `needs_review`/blocked when the source lacks needed proof,
+ product details, screenshots, links, or policy facts.
+ - Separate platform adaptation from translation: changing channel format is
+ allowed; changing the promise is not.
+ - Prefer concrete hooks, specifics, and reader benefit over generic
+ motivational copy.
+ - Keep CTA and links consistent with the settings profile or the user's
+ explicit request.
+ - For Chinese-language work, support natural Simplified Chinese by default
+ unless the source/user asks for another language.
+ - For Xiaohongshu, produce a scroll-stopping title, short structured body,
+ optional image/carousel brief, and hashtag set.
+ - For long-form derivatives such as newsletter or WeChat, preserve nuance
+ and structure; avoid shrinking the idea into slogans.
+ - For short social posts, make each post independently understandable; do
+ not rely on the reader seeing the original blog.
- ## Scripts
+ Read `references/channel-playbook.md` when choosing or adapting
+ channel-specific formats.
- - `scripts/generate_batch.ts --source path-or-text --channels official_blog,xiaohongshu,wechat,newsletter,linkedin,x --audience "..." --cta "..."`
- Persists the batch via the active provider (local: `app/.data/current_batch.json`; busabase: one change request per item). The generator uses deterministic heuristics and is meant as a first pass; Codex should improve drafts with judgment before handing them to the user.
- - `scripts/validate_batch.ts [batch-path]`
- Validates the required batch shape and status values (local batch files).
- - `scripts/export_decisions.ts`
- Publishes approved/edited content via the active provider (local: a ZIP per draft with Markdown + referenced local images, plus batch/decision JSON under `exports/<batch-id>/`; busabase: merge approved change requests into canonical records).
+ ## Safety Defaults
- Run the validator after creating or editing any batch file. Run export only after the user has approved items in the UI or in chat.
+ - Treat exporting as approval-required; a draft without an explicit
+ `approve` decision is never eligible for `scripts/export_decisions.mjs`.
+ - Store only the minimum content needed for review; keep secrets and
+ Busabase credentials out of drafts and logs.
+ - Keep stable ids (`draft_id`, `ref`, `batch_id`) so repeated updates and
+ exports are idempotent.
## Chat-Only Mode
When the user asks to avoid the UI:
1. Produce a compact channel plan.
2. Present numbered drafts with channel, title/hook, body, CTA, and notes.
3. Ask for approval or edits.
- 4. After approval, write the final approved pack to local Markdown if the user wants files.
+ 4. After approval, run `node scripts/export_decisions.mjs --apply` (or write
+ the final approved pack to local Markdown directly) if the user wants
+ files.
- Never claim content is published unless the user explicitly used a publishing connector and it succeeded.
+ Never claim content is published unless the user explicitly used a
+ publishing connector and it succeeded.