--- name: posts-publish description: > Take approved drafts — often written with the social-crosspost, social-post, or social-thread skills — and ship them for real through Inklate in as few calls as the redesigned API needs: one create call with a schedule time for brand-new content, or a dry-run validate followed by publish for a draft that already exists. Covers LinkedIn, X (Twitter), Instagram, and Facebook today, with Mastodon, Threads, Bluesky, TikTok, and YouTube in platform review. Use when the user says "publish this", "ship these drafts", "post this to LinkedIn now", or "schedule this post". Requires the Inklate CLI (npm i -g inklate) or the Inklate MCP connection. Reads social-context.md when present to keep per-channel copy on-voice. Nothing goes out without the server's own `ready` verdict and an explicit yes from the user — arming a not-ready post is refused before anything ships, atomically, so a failed schedule or publish never leaves a partial write behind. license: MIT metadata: version: 0.2.0 topics: - publishing - cross-posting examplePrompt: "Publish these two drafts to LinkedIn and X" pairsWith: - social-crosspost - social-post - social-thread mcpTools: - posts_create - posts_update - posts_validate - posts_publish - posts_get cliCommands: - posts create - posts update - posts validate - posts publish - posts get --- Turn approved drafts into real published or scheduled posts, with the server's own readiness verdict standing between the draft and the send button. ## Transports - **CLI (preferred):** `inklate` (`npm i -g inklate`) — structured YAML on stdout (`--json` for JSON), progress on stderr, and typed exit codes: `3` no credentials, `4` a deliberate refusal — arming a not-ready post, or `posts validate` finding it not ready — with its machine-readable payload on stdout, `5` not found, `6` the resource changed underneath you, `7` rate limited but safe to re-run later. Pass `--no-input` when running unattended so a missing flag fails loudly instead of prompting, and `--yes` to carry an approval the user has already given in chat. `inklate posts publish --wait` polls to a terminal state — there is no separate status command. - **MCP (fallback):** the Inklate MCP tools when the CLI is not installed. Steps below name the MCP operation with the CLI form in parentheses; the fields are the same on both. ## Connection check 1. Run `inklate auth check` (MCP: `organizations_list`) first, before touching any draft. 2. On exit 3 (no credentials): interactively run `inklate auth login`; headless, the key comes from `INKLATE_API_KEY=inklate_…` in the environment or `inklate auth set --token inklate_…`. If neither transport is available at all, tell the user to install with `npm i -g inklate` (or connect at `https://inklate.com/skills/connect?ref=skills-posts-publish`) and STOP. 3. If multiple organizations are returned, ask which one to publish under before doing anything else (CLI: `--org `). ## Craft boundary This skill owns pull → validate → ship → verify. The WRITING belongs to the paired craft skills: when `social-post`, `social-thread`, or `social-crosspost` is installed, delegate drafting and per-platform adaptation judgment to them and ship their output. Without them, make only the minimal edits the server's response demands — never restyle content wholesale. ## Context Read `social-context.md` (project root, then `.agents/social-context.md`) if present: the `## Voice` and `## Never` sections govern how you adjust per-channel copy, and `## Platforms` tells you the primary channel when the user is vague about targets. Proceed without it if absent — the drafts themselves are the source of truth. ## Workflow 1. Gather the drafts. For each one, confirm with the user which channels it targets. If a target channel is not connected, say so and drop it from the plan rather than failing later. 2. **Brand-new content with a known ship time takes one call.** `inklate posts create --content … --channel … --scheduled-at ` (MCP: `posts_create` with `scheduled_at` set) both writes the post and arms it in the same transaction — `--scheduled-at now` publishes immediately; a future instant books it. Arming refuses atomically (exit 4) when the post isn't `ready`: nothing is saved, so retry the identical call without `--scheduled-at` to keep the content as a draft and read its `errors`. When this succeeds, skip to step 6 (verify). 3. **An existing draft** (from `posts-create`, the dashboard, or an earlier session) gets a fresh dry-run first: `inklate posts validate --id ` (MCP: `posts_validate`) — exits 4 with the same `ready` / `errors[]` / `warnings[]` shape if not ready, and stores nothing either way. This replaces the old sync-then-preflight loop; the create/update response already carried this validation, so `validate` here is just a last look before an irreversible action. 4. For every error `validate` returns, apply the response's own machine `fix` where offered, or make the judgment call yourself — present 2–3 concrete options when a fix would change meaning or trips `## Never`. Apply a resolved fix with `inklate posts update --per-channel '{"": {"content": "…"}}'` (MCP: `posts_update`), which re-validates and returns the same shape — no separate re-check call. **The platform never silently overwrites your fixes.** A channel's copy that was auto-fixed or hand-edited stays exactly as it is through every later content edit; the only way to discard it is the explicit reset `--per-channel '{"": null}'`, which re-adapts that one channel from the shared content. There is no sync step left to accidentally re-run over it. 5. Re-run `inklate posts validate --id ` (or trust the `posts update` response, which returns the identical shape) until every targeted channel is `ready`. A channel that cannot go green (e.g. Instagram with no media) gets flagged — offer to exclude it from the send. 6. **Approval gate.** Show the user the final per-channel copy verbatim — each channel entry's `content.text`, the exact words that platform will receive — plus any media attached. Ask one direct question: publish now, schedule for a time, or hold? Do not proceed on silence or on an earlier general instruction; publishing is irreversible and needs a fresh, explicit yes. 7. On "publish now": for an existing draft, call `posts_publish` (CLI: `inklate posts publish --wait --yes`). For content that doesn't exist yet, fold it into step 2's single `posts create --scheduled-at now --yes` instead of creating first. `--yes` carries the approval you just received in step 6 — it is never a substitute for the gate. 8. On "schedule": confirm the time in the organization's timezone, restate it unambiguously ("Tue Jul 21, 9:00 AM Europe/Berlin"), then call `inklate posts update --scheduled-at