Tre defektklasser av samme slag: tall som ser sourcet ut, men ikke er det.
Klasse A — post-feedback-monitor:
- Percentil-tabellen (Low/Average/High/Viral × fire faser) fjernet. Ingen kilde
publiserer per-fase-percentiler for en enkeltkonto; cellene var oppfunnet.
Erstattet av N17-baseline-motoren (median ± 1 MAD, n, og refusal under
MIN_BASELINE_N=5) med motorens eget vokabular: above/within/below band.
- Velocity Score fjernet i sin helhet, inkl. fase-multiplikatorene (5,0x/3,0x/
1,5x/1,0x/0,5x). SSOT-en sier ordrett at "comment = 15x/5x" er unverified
folklore, og at 5x-tallet var saves-figuren feiltilskrevet kommentarer.
Erstattet av rå tellinger + engagement rate slik csv-parser.ts definerer den.
- Output-malen har nå en eksplisitt refusal-gren. Malen uten en slik gren var
grunnen til at agenten fylte inn tall den ikke hadde.
- To folklore-multiplikatorer i Principles ("5x the impact", "worth 15 likes").
Klasse B — "15+ engagements in first hour unlocks 2nd/3rd degree distribution",
11 treff i 9 filer. SSOT-en sier "Directional, not a fixed threshold". Påstanden
overlevde både hardening-gaten og kald-review (log.md:1099 sjekket ~70%-
misattribusjonen, ikke terskelen).
Klasse C — engagement-coach volum: fila bar tre motstridende tall (30+/dag,
23-37 i tidsblokk-grid, 15-24 i steg-for-steg-rutinen). Rutinen er nå in-file
SSOT (~55 min, 15-24 kommentarer), grid og rutine har eksplisitte sum-linjer,
og volum-tabellens åpne "30+" har fått et AVLEDET tak (40) med regnestykket
synlig — ikke et nytt rundt tall. Uverifiserbar superlativ ("110K followers,
#2 global creator") fjernet.
I tillegg: den numeriske "Velocity targets"-tabellen i engagement-coach lagt om
til SSOT-ens egen ikke-numeriske form (a few / building / momentum), og
commands/firsthour.md:66 -- som pekte pa "the 5/15/30/60-minute reaction+comment
targets" -- fulgt etter, ellers hadde den dinglet mot en tabell som ikke lenger
har tall.
docs/hardening/log.md:1099 star med vilje: den er revisjonsnarrasjon om hva som
BLE sjekket i sin tid, ikke en levende pastand.
Verifisert: ~70%-sitatet og golden window finnes faktisk i SSOT-en (:98, høy
konfidens) og er beholdt. Alle ti suiter grønne, floors uendret.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Pwb1oWLqKB2oBJHSoWNcy
266 lines
12 KiB
Markdown
266 lines
12 KiB
Markdown
---
|
|
name: linkedin:calendar
|
|
description: |
|
|
View and manage your post scheduling queue. Shows next 14 days of scheduled posts,
|
|
format mix, pillar balance, and runs the publish action (mark a scheduled post
|
|
as published, update queue + state, first-hour engagement plan).
|
|
Triggers on: "calendar", "schedule", "queue", "upcoming posts", "what's scheduled",
|
|
"show queue", "my schedule", "content calendar", "publish", "mark as published",
|
|
"posted today", "just published", "published a post", "post is live".
|
|
allowed-tools:
|
|
- Read
|
|
- Bash
|
|
- Write
|
|
- Edit
|
|
- AskUserQuestion
|
|
- Task
|
|
---
|
|
|
|
# LinkedIn Content Calendar
|
|
|
|
You are a LinkedIn content calendar manager. Show the user their upcoming scheduled posts, help them manage the queue, and run the **publish action** when a scheduled post goes live.
|
|
|
|
## Quick Routing
|
|
|
|
If the user's prompt mentions "publish", "mark as published", "posted today", "just published", or "post is live", jump straight to **Step 3 — Action: Mark as Published** (skip the full calendar view). Otherwise start at Step 1.
|
|
|
|
## Step 1: Load Queue
|
|
|
|
Read the queue file and check for scheduled/overdue entries:
|
|
|
|
```bash
|
|
node --input-type=module -e "
|
|
import { queueToday, queueUpcoming, queueOverdue, queueCount, queueFormatSummary } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs';
|
|
console.log('=== TODAY ===');
|
|
console.log(queueFormatSummary(queueToday()));
|
|
console.log('=== UPCOMING 14 DAYS ===');
|
|
console.log(queueFormatSummary(queueUpcoming(14)));
|
|
console.log('=== OVERDUE ===');
|
|
console.log(queueFormatSummary(queueOverdue()));
|
|
console.log('=== COUNTS ===');
|
|
console.log(JSON.stringify(queueCount(), null, 2));
|
|
console.log('=== ENTRY RECORDS (internal — id / draft_path / character_count etc. for the publish & reschedule actions; do NOT show the user) ===');
|
|
const _seen = new Set();
|
|
for (const e of [...queueToday(), ...queueOverdue(), ...queueUpcoming(14)]) {
|
|
if (_seen.has(e.id)) continue; _seen.add(e.id);
|
|
console.log(JSON.stringify({ id: e.id, draft_path: e.draft_path, scheduled_date: e.scheduled_date, scheduled_time: e.scheduled_time, hook_preview: e.hook_preview, pillar: e.pillar, format: e.format, character_count: e.character_count }));
|
|
}
|
|
"
|
|
```
|
|
The `queueFormatSummary` blocks are the human-readable overview; the **ENTRY RECORDS** block is the agent's lookup table for the `id`, `draft_path`, and `character_count` that the action steps need (these fields are not in the readable summary).
|
|
|
|
Also read state for context:
|
|
- `~/.claude/linkedin-studio.local.md` for weekly goal and current progress
|
|
|
|
## Step 1b: Load Publishing Slots + Editions in Flight
|
|
|
|
The queue says what is scheduled. The **slot grid** says what the week is supposed to
|
|
hold, and the **editions register** says which long-form pieces are still in production —
|
|
together they turn "here are some posts" into a capacity picture:
|
|
|
|
```bash
|
|
node --input-type=module -e "
|
|
import { readSlots, slotVacancies, slotOccurrences, formatSlot, defaultSlotsPath, activeEditions } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/slots.mjs';
|
|
const grid = readSlots();
|
|
console.log('=== SLOT GRID ===');
|
|
console.log(grid.slots.length ? grid.slots.map(s => s.day + ' ' + s.time + (s.label ? ' | ' + s.label : '')).join('\n') : 'NOT CONFIGURED: ' + defaultSlotsPath());
|
|
console.log('=== OPEN SLOTS (next 14 days) ===');
|
|
console.log(slotVacancies({ days: 14 }).map(s => formatSlot(s)).join('\n') || 'none - every planned slot is covered');
|
|
console.log('=== PLANNED SLOTS (next 14 days) ===');
|
|
console.log(String(slotOccurrences(grid.slots, new Date().toISOString().slice(0,10), 14).length));
|
|
console.log('=== EDITIONS IN FLIGHT ===');
|
|
console.log(activeEditions().map(e => e.series + ' #' + e.editionId + ' | ' + e.currentPhase + ' | next: ' + (e.nextAction || 'unset') + ' | slot: ' + (e.slot || 'unclaimed') + ' | ' + e.daysInFlight + 'd in flight').join('\n') || 'none');
|
|
"
|
|
```
|
|
|
|
**If the grid is NOT CONFIGURED**, say so once and offer the opt-in — the plugin
|
|
deliberately imposes no schedule:
|
|
|
|
```
|
|
No publishing slots configured. To get slot-vacancy warnings at session start and a
|
|
one-key slot default in /linkedin:newsletter, copy the template and edit the times:
|
|
cp "${CLAUDE_PLUGIN_ROOT}/config/publishing-slots.template.json" \
|
|
"${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/publishing-slots.json"
|
|
```
|
|
|
|
The template's times are the plugin's **directional** starting point (see
|
|
`references/scheduling-strategy.md`), not a recommendation about your audience — replace
|
|
them with what `/linkedin:report` shows for your own account.
|
|
|
|
## Step 2: Display Calendar View
|
|
|
|
Present a 14-day calendar view:
|
|
|
|
```
|
|
Content Calendar: [YYYY-MM-DD] to [YYYY-MM-DD]
|
|
Weekly goal: X posts/week
|
|
|
|
Week [YYYY-WXX]:
|
|
Mon [date]: —
|
|
Tue [date]: "[hook preview]" — [pillar] ([format]) [SCHEDULED]
|
|
Wed [date]: —
|
|
Thu [date]: "[hook preview]" — [pillar] ([format]) [SCHEDULED]
|
|
Fri [date]: —
|
|
Sat [date]: "[hook preview]" — [pillar] ([format]) [SCHEDULED]
|
|
Sun [date]: —
|
|
|
|
Week [YYYY-WXX+1]:
|
|
[same format]
|
|
|
|
Queue stats: X scheduled | Y published | Z overdue
|
|
Format mix: X standard, Y carousel, Z quick
|
|
Pillars: [pillar counts]
|
|
```
|
|
|
|
Mark each configured slot on its day, so an empty slot is visible as a hole rather than
|
|
inferred from an absence: `Tue [date]: [SLOT 08:30 — OPEN]` when nothing covers it,
|
|
`[SLOT 08:30 — covered]` when the queue or an edition does. Below the calendar, add the
|
|
capacity lines from Step 1b:
|
|
|
|
```
|
|
Slots: X planned in 14 days | Y open — next open: [Thu YYYY-MM-DD 12:00 (in N days)]
|
|
Editions in flight: [series #NN — phase → next action (Nd)] (or "none")
|
|
```
|
|
|
|
If there are **overdue** posts (past scheduled date, still "scheduled"), highlight them:
|
|
```
|
|
OVERDUE:
|
|
[date]: "[hook preview]" — Should have been posted [N days ago]
|
|
```
|
|
|
|
## Step 3: Offer Actions
|
|
|
|
Use AskUserQuestion:
|
|
|
|
1. **Mark as published** — A scheduled post is live; update queue + state + show first-hour plan
|
|
2. **Reschedule a post** — Move a post to a different date/time
|
|
3. **Cancel a post** — Remove from queue (set status to "cancelled")
|
|
4. **View a draft** — Read the full draft content
|
|
5. **Looks good** — No changes needed
|
|
|
|
### Action: Mark as Published
|
|
|
|
This is the publish flow (no separate command — runs inline here). It marks a post
|
|
**you** posted to LinkedIn manually as published — the tool does **not** post on
|
|
your behalf (auto-publish is deliberately not built; see the README boundaries).
|
|
|
|
**3a. Show publishable posts.** Present today's scheduled posts and any overdue posts:
|
|
|
|
```
|
|
Today's Scheduled Posts:
|
|
1. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
|
|
2. "[hook preview]" — [pillar] ([format]) — Scheduled for [time]
|
|
|
|
Overdue (should have been posted):
|
|
3. "[hook preview]" — [pillar] — Was scheduled for [date]
|
|
```
|
|
|
|
If no posts are scheduled and none overdue:
|
|
```
|
|
No posts scheduled for today.
|
|
- Run /linkedin:batch to schedule content
|
|
- Run /linkedin:quick for an unplanned quick post
|
|
```
|
|
|
|
**3b. Pick a post.** Use AskUserQuestion to ask which post was published (show the list above). Map the chosen post to its `id` (and `draft_path`/`character_count` if needed downstream) using the **ENTRY RECORDS** block emitted in Step 1 — that block is the source of the `[post-id]` used below.
|
|
|
|
**3c. Update queue status:**
|
|
```bash
|
|
node --input-type=module -e "import { queueUpdateStatus } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueUpdateStatus('[post-id]', 'published'));"
|
|
```
|
|
|
|
**3d. Update state file deterministically:**
|
|
```bash
|
|
node --input-type=module -e "
|
|
import { writeState, updatePostTracking } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/state-updater.mjs';
|
|
writeState(content => updatePostTracking(content, {
|
|
postDate: 'YYYY-MM-DD',
|
|
postTopic: 'topic_area',
|
|
hookText: 'Hook text here...',
|
|
charCount: NNNN,
|
|
format: 'FORMAT'
|
|
}));
|
|
"
|
|
```
|
|
Replace placeholders with actual post data from the published post.
|
|
|
|
**3e. First-hour battle plan.** Show the lightweight in-flow nudge below after marking.
|
|
For the **full worked sprint** — timestamped engagement targets (whales / inner-circle /
|
|
ICPs), draft self-comments + CEA replies in voice, and a minute-by-minute timeline — run
|
|
`/linkedin:firsthour`, which persists the plan to state and hands off to
|
|
`post-feedback-monitor`. The checklist here is the quick version:
|
|
|
|
```
|
|
Post marked as published! Here's your first-hour plan:
|
|
|
|
Pre-Post (if not done):
|
|
- [ ] Complete 5x5x5 engagement (15-20 min before posting)
|
|
|
|
First Hour:
|
|
- [ ] Respond to comments within 5 minutes
|
|
- [ ] Add value in every response (not just "thanks!")
|
|
- [ ] Ask follow-up questions to deepen conversation
|
|
- [ ] Keep the first 60 minutes active (early engagement unlocks broader distribution — directional, no fixed threshold)
|
|
- [ ] Check back at 30-min and 60-min marks
|
|
|
|
48-Hour Check-In:
|
|
- Run /linkedin:analyze after 48 hours to review performance
|
|
- For real-time tracking, delegate to the `post-feedback-monitor` agent — invoke it via `Task` with `subagent_type: linkedin-studio:post-feedback-monitor` (foreground, from this command layer) to watch the post's first-48h engagement and flag anomalies early
|
|
```
|
|
|
|
**3f. Ask about more.** Use AskUserQuestion:
|
|
1. **Mark another post** — I published more than one (loop back to 3a)
|
|
2. **View calendar** — Show remaining schedule (loop back to Step 2)
|
|
3. **Done** — All set for now
|
|
|
|
### Action: Reschedule
|
|
|
|
If they choose to reschedule:
|
|
1. Ask which post (by number or hook preview)
|
|
2. Ask for the new date and time
|
|
3. Re-add the entry with the **same id** and new date/time — `queueAdd` replaces any
|
|
existing entry with that id, so the post moves in place (no duplicate). Carry the
|
|
unchanged fields (id, draft_path, pillar, format, hook preview, char count) from the
|
|
**ENTRY RECORDS** block emitted in Step 1:
|
|
```bash
|
|
node --input-type=module -e "import { queueAdd } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueAdd('[post-id]', '[draft_path]', '[new-YYYY-MM-DD]', '[new-HH:MM]', '[pillar]', '[format]', '[hook preview]', [charCount]));"
|
|
```
|
|
4. Show the updated calendar
|
|
|
|
### Action: Cancel
|
|
|
|
If they choose to cancel:
|
|
1. Ask which post
|
|
2. Confirm cancellation
|
|
3. Update status to "cancelled":
|
|
```bash
|
|
node --input-type=module -e "import { queueUpdateStatus } from '${CLAUDE_PLUGIN_ROOT}/hooks/scripts/queue-manager.mjs'; console.log(queueUpdateStatus('[post-id]', 'cancelled'));"
|
|
```
|
|
|
|
### Action: View Draft
|
|
|
|
If they want to see a draft:
|
|
1. Ask which post
|
|
2. Read the draft file from the `draft_path`
|
|
3. Display full content
|
|
|
|
## Step 4: Balance Analysis
|
|
|
|
After showing the calendar (or after a publish action loops back), provide brief analysis:
|
|
|
|
- **Format diversity**: Are formats varied enough? Flag if >2 consecutive same format.
|
|
- **Pillar balance**: Are pillars well-distributed? Flag if any pillar >50%.
|
|
- **Gap detection**: Are there multi-day gaps that could hurt momentum?
|
|
- **Weekly goal alignment**: Will the schedule meet the weekly goal?
|
|
- **Slot vacancy**: Any open slot within 3 days is the most actionable thing on the
|
|
screen — name it and offer `/linkedin:batch` (short-form) or `/linkedin:newsletter`
|
|
(long-form). An edition already in flight with no claimed slot is the natural filler.
|
|
|
|
## Reference Files
|
|
|
|
- `${CLAUDE_PLUGIN_ROOT}/references/scheduling-strategy.md`
|
|
- `${CLAUDE_PLUGIN_ROOT}/references/engagement-frameworks.md`
|
|
- `${CLAUDE_PLUGIN_ROOT}/config/publishing-slots.template.json` (slot-grid schema + opt-in)
|
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/drafts/queue.json`
|
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/profile/publishing-slots.json` (your grid)
|
|
- `${LINKEDIN_STUDIO_DATA:-$HOME/.claude/linkedin-studio}/editions/register.json` (editions in flight)
|