frontmatter · SKILL.md+
name: calendar-schedule
description: >
Turn a content plan — often produced by the social-calendar or social-repurpose skills —
into real scheduled posts on the real calendar: parse the plan, map every row to a
connected channel, preview each row's server-side readiness, propose the full week's
schedule, and book every row with one call each. Covers LinkedIn, X (Twitter),
Instagram, and Facebook today, with Mastodon, Threads, Bluesky, TikTok, and YouTube in
platform review. Use when the user says "schedule my week", "schedule this plan", "put
my calendar on the calendar", or "schedule these posts". Requires the Inklate CLI
(npm i -g inklate) or the Inklate MCP connection. Reads social-context.md when present
for cadence and voice. The receipt is the week as a table of real scheduled times and
post IDs, verified against the queue.
license: MIT
metadata:
version: 0.2.0
topics:
- planning
- publishing
examplePrompt: "Schedule this two-week content plan across my channels"
pairsWith:
- social-calendar
- social-repurpose
mcpTools:
- posts_create
- posts_validate
- posts_list
- posts_get
cliCommands:
- posts create
- posts validate
- posts list
- posts getTake a content plan off the page and onto the real calendar: every row becomes one validated, scheduled post with a confirmed time on a real channel.
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 row that isn'tready— 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-calendar-schedule) and STOP. - If multiple organizations exist, ask which one owns this calendar. All times and channels below are scoped to that organization (CLI:
--org <slug>).
Context
Read social-context.md (project root, then .agents/social-context.md) if present. ## Cadence is the reality check against overstuffed plans, ## Platforms resolves ambiguous channel names in the plan, and ## Voice / ## Never govern any content adjustments you make while resolving errors. Proceed without it if absent.
Workflow
- Parse the plan. Read the plan table or list the user provides. Extract per row: content (or a pointer to a draft), target channel(s), and intended day/time. Flag rows you cannot parse instead of guessing.
- Map rows to channels. Match each row's platform to a connected channel. Rows targeting unconnected platforms get flagged and set aside — never silently dropped, never scheduled to a wrong channel.
- Preview every row before booking anything. Call
posts_validate(CLI:inklate posts validate --content … --channel …) for each mapped row — a dry run, nothing is stored. Read each row'sreadyand per-channelerrors[]/warnings[]. For every fixable error, adjust the row's content directly (autofix already applied where it could when you actually create it, but a dry-run preview surfaces judgment calls early); surface judgment calls to the user in one consolidated batch, not one interruption per row. - Build the proposed schedule. Convert the plan's intended times into concrete datetimes in the organization's timezone, then apply the constraints:
- Spacing: no two posts on the same channel within 3 hours — when the plan violates this, nudge the later post and note the nudge.
- Existing queue: check
posts_list(CLI:inklate posts list --scheduled-from … --scheduled-to …) for already-scheduled posts in the window so the new week doesn't collide with them. - Rows with a day but no time get a sensible default for that platform, marked as your suggestion.
- Past times are an error, not a nudge: flag any row whose intended time has already passed.
- Approval gate. Present the entire proposed week as one table — day, time (org timezone), channel, content summary, readiness from step 3 — before anything is booked. Scheduling posts is a real-world action: get an explicit yes on the whole table, or apply the user's edits and re-present. Never book a partial week on an implicit go-ahead.
- Book it — one call per row. On approval, call
posts_create(CLI:inklate posts create --content … --channel … --scheduled-at <time> --idempotency-key <row-id> --yes) for each row; the same call both writes the post and arms it for that instant, so there is no separate sync, preflight, or schedule-placements step left to run. The--idempotency-key(the plan row's own identity is a good source) makes a retry replay instead of creating a second copy of the same row.- If a row's arming is refused (exit 4 — its content stopped being
readysince the preview, e.g. a channel disconnected in the meantime), park that row: retry the identical call without--scheduled-atto at least save it as a draft, note the reason, and keep going with the rest of the batch.
- If a row's arming is refused (exit 4 — its content stopped being
- Verify. Call
posts_listfor the scheduled window and confirm every booked row appears with the right time and channel. For any row still processing, callposts_get(CLI:inklate posts get <id>) and report the real state — there is no separate status call. Discrepancies between what was booked and what the queue shows are reported immediately, not glossed. - Handle a mid-batch stop honestly. Booking a whole week is many calls, so expect to be interrupted:
- Exit 7 (rate limited) means the budget is spent and the message names the wait. Report which rows are booked, which are not, and the wait — then offer to resume. Never report a half-booked week as done, and never abandon it as failed.
- Exit 6 (conflict) means that post's state forbids the booking (already published, channel disconnected). Park that row with the reason and keep going with the rest.
- Rerunning a row that already succeeded is safe when it carries the same idempotency key from step 6.
- If the plan spans more than one week, book week by week — approve and verify week one before proposing week two, so a mid-plan change of mind never strands half-booked rows.
Quality bar
- Never book a row that previewed as not
ready; parked rows appear in the receipt as unscheduled with the reason. - All times are stated and booked in the organization's timezone; if the plan's times imply a different timezone, resolve the conflict with the user before proposing.
- Spacing rule is hard: no two posts on one channel within 3 hours. Nudges made to satisfy it are listed explicitly.
- The proposed-schedule table shown for approval must match what was actually booked — no drift between preview and booking.
- Verification via
posts_listis not optional; the receipt reflects the queue's reality, not the request you sent. - If the plan exceeds the cadence in social-context.md by more than double, say so once before proposing — then respect the user's call.
- Every row in the receipt carries its post ID so the user can edit or unschedule any single slot later without archaeology.
Receipt
End with the booked week as verified against the queue:
| Day | Time (org tz) | Channel | Post ID | Content | Status |
|---|---|---|---|---|---|
| Mon Jul 20 | 9:00 AM | post_abc123 | "Why we killed our roadmap…" | scheduled | |
| Mon Jul 20 | 1:00 PM | X | post_abc124 | Thread: roadmap lessons (6 tweets) | scheduled |
| Wed Jul 22 | 10:00 AM | post_abc125 | Carousel: 5 lessons | processing (media pending) |
Below the table: total posts booked, any rows parked with reasons, any spacing nudges applied, and the posts_get state for anything not yet confirmed on the calendar.
Every row in this table was verified against posts_list — it is the queue's account of the week, not the request's.