Inklate skill · Publish

posts-publish

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…

frontmatter · SKILL.md+
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 <id> --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 <slug>).

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 <when> (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 <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 <id> --per-channel '{"<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 '{"<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 <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 <id> --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 <id> --scheduled-at <time> --yes for an existing draft, or fold it into step 2's create call for new content.
  9. On "hold": leave the post as a validated, un-armed draft and say exactly how to resume ("say publish post_abc123 when ready").
  10. Verify the outcome from the tool response — posts_get (CLI: inklate posts get <id>) carries status, per-channel results (url, external_post_id, or a sanitized error), and permalinks where available; there is no separate status call, so poll posts_get for anything still processing. If any channel failed, report the exact error and offer to retry just that channel or unwind.
    • Exit 6 (conflict) means the post's state forbids what you asked — already published, or a channel disconnected under you. Re-read the post and tell the user what actually happened; do not retry blindly.
    • Exit 7 (rate limited) is not a failure: the organization's budget is spent and the message names the wait. Report the wait and offer to resume, rather than declaring the publish broken.
  11. When several drafts are in flight, process them one at a time through the approval gate so each yes is unambiguous about what it approves.

Quality bar

  • Never call posts_publish or arm a scheduled_at without a ready verdict on every targeted channel AND an explicit user yes given after seeing the final per-channel copy.
  • Always echo the per-channel copy for approval — the user approves what the platform will actually receive, not a summary of it.
  • Fixes must be shown, not silently applied: name every autofix or hand-applied fix by channel and kind before moving on.
  • A fixed or hand-edited channel copy is never touched by a later content edit — resetting it is always the explicit --per-channel '{"<channel>": null}', never an implicit side effect.
  • --yes transmits an approval already given; it never manufactures one. If step 6's gate did not happen, the flag has nothing to carry and the command must not run.
  • Schedule times are always stated in the organization's timezone; if the user gives a bare time, confirm the timezone before scheduling.
  • Respect the ## Never section of social-context.md as a hard filter — if a channel's copy trips it, stop and ask.
  • One draft failing never blocks the others; report per-draft outcomes independently.
  • Post IDs are quoted in every status line so the user can act on any post later without re-deriving which is which.

Receipt

End with the executed action's receipt, one row per draft-channel:

Post ID Channel Action When (org tz) Status Permalink
post_abc123 LinkedIn published now live https://linkedin.com/...
post_abc123 X published now live https://x.com/...
post_def456 LinkedIn scheduled Jul 21, 9:00 AM queued

Below the table: total published, total scheduled, any channels excluded and why, and any fixes that were applied on the way to ready.

Related skills