frontmatter · SKILL.md+
name: posts-update
description: >
Change or call off a post that already exists in Inklate: find it, show what it says
now, edit the content, channels, media, labels, campaign, or the schedule in one call,
and let the server re-validate it on the spot — or take it off the calendar and delete
it. Covers LinkedIn, X (Twitter), Instagram, and Facebook today, with Mastodon, Threads,
Bluesky, TikTok, and YouTube in platform review. Use when the user says "edit that
scheduled post", "change the time", "unschedule that", "cancel tomorrow's post", or
"delete that draft". Requires the Inklate CLI (npm i -g inklate) or the Inklate MCP
connection. Reads social-context.md when present so edits stay on-voice. Published
posts are immutable and this skill says so rather than pretending; every edit's
response carries fresh per-channel validation, and every cancellation is confirmed
first.
license: MIT
metadata:
version: 0.2.0
topics:
- editing
- publishing
examplePrompt: "Change the time on tomorrow's LinkedIn post and fix the typo in the first line"
pairsWith:
- social-post
mcpTools:
- posts_list
- posts_get
- posts_update
- posts_delete
- search
cliCommands:
- posts list
- posts get
- posts update
- posts delete
- searchEdit, reschedule, or cancel a post that already exists — the half of the loop posts-create and posts-publish leave to the dashboard.
Transports
- CLI (preferred):
inklate(npm i -g inklate) — structured YAML on stdout (--jsonfor JSON), progress on stderr, and typed exit codes:3no credentials,4a deliberate refusal — arming a not-ready post with--scheduled-at— with its machine-readable payload on stdout,5not found,6the resource changed underneath you,7rate limited but safe to re-run later. Pass--no-inputwhen running unattended so a missing flag fails loudly instead of prompting, and--yesto carry an approval the user has already given in chat. - 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
- Run
inklate auth check(MCP:organizations_list) first. - On exit 3 (no credentials): interactively run
inklate auth login; headless, the key comes fromINKLATE_API_KEY=inklate_…in the environment orinklate auth set --token inklate_…. If neither transport is available at all, tell the user to install withnpm i -g inklate(or connect athttps://inklate.com/skills/connect?ref=skills-posts-update) and STOP. - If multiple organizations exist, ask which one owns the post (CLI:
--org <slug>).
What can change, and what cannot
Read this before touching anything — it decides whether the request is even possible:
| Post state | Editable? | How |
|---|---|---|
| draft | yes | posts_update — content/channels/media/labels/campaign, or add --scheduled-at to arm it |
| scheduled | yes | posts_update — --scheduled-at reschedules, --clear-schedule returns it to draft |
| failed | yes | posts_update, then hand off to posts-publish (inklate posts publish <id>) |
| publishing (in flight) | no — wait | poll posts_get; it is mid-flight, not stuck — there is no separate status call |
| published | never | immutable; say so and offer the real alternatives |
Published is immutable. No flag changes that. When the user asks to edit something already live, say it plainly and offer what actually exists: delete it on the platform and publish a corrected post, or leave it and post a follow-up. Never imply an edit went through.
Craft boundary
This skill owns find → show → change → re-validate → confirm. The WRITING belongs to the paired craft skill: when social-post is installed, hand it the new copy and use what comes back. Without it, make the edit the user asked for and nothing more — an edit request is not an invitation to restyle the post.
Context
Read social-context.md (project root, then .agents/social-context.md) if present. ## Voice and ## Never constrain any rewriting you do while editing; ## Platforms resolves a vague reference ("the LinkedIn one") to a channel. Proceed without it if absent.
Workflow
- Find the post, don't guess it. A vague reference ("tomorrow's post", "the roadmap one") resolves through
posts_list(CLI:inklate posts list --status scheduled --scheduled-from … --scheduled-to …) orsearch(CLI:inklate search "roadmap") — never through memory of an earlier session. If more than one candidate matches, list them with IDs and times and ask which one. Acting on the wrong post is unrecoverable once it publishes. - Read it before you change it. Call
posts_get(CLI:inklate posts get <id>) and show the user what the post says right now — per-channel content (content.markdown— the copy exactly as you would write it back — andcontent.text, the same copy as the platform will show it), media, scheduled time, and status. This is also where you capture the post'supdated_atfor step 4's concurrency guard, and where you check the state table above. - State the change in one line and get a yes. "Replacing the first line and moving Tuesday 9:00 → Wednesday 14:00" — then wait. Editing a scheduled post is a real-world action: it changes what goes out and when.
- Apply the edit in one call.
inklate posts update <id> --content … --channel … --media … --scheduled-at <time> --expected-updated-at <from step 2>(MCP:posts_update) handles content, targets, media, labels, campaign, and the schedule together — there is nothing left to sync or re-materialize separately.--expected-updated-atis not optional. It is the optimistic-concurrency guard: pass theupdated_atyou read in step 2 and the edit refuses (exit 6) if anyone touched the post in between. Skip it and you silently overwrite a teammate's dashboard edit.- On exit 6, do not retry with a fresh timestamp on autopilot. Re-read the post, show the user what changed underneath them, and let them decide.
--scheduled-atand--clear-scheduleare mutually exclusive — pick one.- A channel's copy is only ever overwritten on purpose.
--per-channel '{"x": {"content": "…"}}'edits one channel without touching the others;--per-channel '{"x": null}'is the explicit reset back to the shared content — the only way a channel's own copy (hand-edited or auto-fixed earlier) gets discarded. A plain--contentedit never silently overwrites a channel that already has its own copy. - Adding a channel with
--channel(repeatable) gives the new channel a freshly adapted copy of the shared content; existing channels' copies are untouched.
- Mind the approval pipeline. If the organization gates publishing on approval, any content change after approval sends the post back through it. Say this at the moment of editing, not after: a user who thinks they fixed a typo on an approved post needs to know it now needs re-approval.
- Re-validation is automatic. The
posts_updateresponse is the same Post View asposts_create/posts_get: top-levelready, and per channelerrors[],warnings[], andfixes_applied[]from server-side autofix. There is no separate re-check call — read the response you already got back. An edit that was fine on LinkedIn can overflow X; if it did, the response says so. Auto-fixable violations are applied and reported the same way as inposts-create; judgment calls go back to the user. - Rescheduling is
--scheduled-atonposts update(CLI:inklate posts update <id> --scheduled-at <time> --yes— ISO-8601 with offset, or30m/2h/3d/1w). Restate the new time unambiguously in the organization's timezone before booking it. Arming refuses (exit 4) if the post isn'treadyfor its current channels — nothing about the post changes when that happens; fix the errors and retry. - Taking it off the calendar is
--clear-scheduleonposts update(CLI:inklate posts update <id> --clear-schedule --yes) — the post returns to draft, content intact, nothing published. This is the reversible cancellation and the right default when the user says "cancel that". - Deleting is
posts_delete(CLI:inklate posts delete <id> --yes, add--takedownto also best-effort remove the provider-side copies) and it is not reversible. Confirm the specific post by ID and summary, name what is being destroyed, and get an explicit yes. When the user's intent might be "just don't post it", offer--clear-schedulefirst — most "cancel" requests mean unschedule, not delete. - Verify from the platform, not from your own request. Re-read with
posts_get(orposts_listfor the window) and report the state that came back. There is no separate status call —posts_getalso carries live per-channel publish results (url,external_post_id, or a sanitizederror) once something has shipped.
Quality bar
- Every post acted on was resolved this session by ID from
posts_list,search, orposts_get— never from a remembered ID or an assumed "the one we just made". --expected-updated-ataccompanies everyposts_updatethat changes an existing post. An edit without it is a bug, even when it succeeds.- Published posts are reported as immutable, with the two real alternatives — never worked around, never described as edited.
- Unschedule (
--clear-schedule) is offered before delete whenever the user's words are ambiguous; delete requires the post named back to them and an explicit yes. - Every content or target change is followed by reading the fresh
ready/errors[]theposts_updateresponse already carries — no second call needed to see it. - A channel's own copy (hand-edited or auto-fixed) is only ever discarded by the explicit
--per-channel '{"<channel>": null}'reset, never as a side effect of a shared-content edit. - When approval gating is in play, re-entry into the approval pipeline is stated at edit time.
- Exit 6 is a fact about the world, not a retry signal: re-read, show, ask.
- This skill changes and cancels; it never publishes. Shipping is
posts-publish's job.
Receipt
End with what actually changed, one row per action:
| Post ID | Action | Before | After | Status |
|---|---|---|---|---|
| post_abc123 | edited | "Our roadmap is dead…" | "We killed our roadmap…" | scheduled, ready |
| post_abc123 | rescheduled | Tue Jul 21, 9:00 AM | Wed Jul 22, 2:00 PM | scheduled |
| post_def456 | unscheduled | scheduled Wed Jul 22 | draft | draft, content intact |
Below the table: the post IDs touched, any fixes applied on the way, anything refused with the reason (published, exit 6 conflict, approval re-entry), and the single next action — publish via posts-publish, or nothing if the user wanted it parked.