/ops-social-planner — engine-agnostic planned-content viewer skill

OPS on-demand: This skill should be used when the user asks to "content calendar", "what is scheduled"…

by Lifecycle-Innovations-Limited·MIT license·★ 543 Stars on the repo·GitHub ↗

Use now

Inside this skill

SKILL.md · the author's own words
# /ops-social-planner — engine-agnostic planned-content viewer

Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Work…

Read-only dashboard of **every** scheduled post and ad, grouped **identity/project → channel → time**,
**regardless of posting engine**. Personal/founder identity and project brands stay strictly separated
(see `/ops-socials` identity rules). See `SPEC.md` for the full design.

## What it does

1. `bin/ops-social-planner collect` reads `$PREFS_PATH/preferences.json`
   (`marketing.social_identities.personal.*` + `marketing.projects.*.social`), dispatches a
   **per-engine fetcher** keyed on `social.engine.primary`, normalizes to one schema, and writes
   `$OPS_DATA_DIR/social-planner/state.json` (**owner data — never committed**, Rule 0).
2. It then serves the static UI (`ui/`) on localhost and opens the browser.

Word-for-word excerpt, not a result. Read the full SKILL.md ↓

Files of /ops-social-planner — engine-agnostic planned-content viewer

Lifecycle-Innovations-Limited/main1 file shown
SKILL.md
Show the full text72 lines

/ops-social-planner — engine-agnostic planned-content viewer

Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).

Read-only dashboard of every scheduled post and ad, grouped identity/project → channel → time, regardless of posting engine. Personal/founder identity and project brands stay strictly separated (see /ops-socials identity rules). See SPEC.md for the full design.

What it does

  1. bin/ops-social-planner collect reads $PREFS_PATH/preferences.json (marketing.social_identities.personal.* + marketing.projects.*.social), dispatches a per-engine fetcher keyed on social.engine.primary, normalizes to one schema, and writes $OPS_DATA_DIR/social-planner/state.json (owner data — never committed, Rule 0).
  2. It then serves the static UI (ui/) on localhost and opens the browser.

Engines

engine.primary Source Status
typefully GET /v2/social-sets/{id}/drafts (Bearer; key from ~/.config/typefully/config.json) ✅ wired
upload-post GET /api/uploadposts/schedule (Apikey; key from engine.upload_post.api_key_ref) ✅ wired
meta-graph / meta-ads Graph Ads API per project BM/token 🔌 hook (UI shows "pending")
google-ads GAQL per project customer id 🔌 hook
null / unprovisioned — shown fail-closed (0 items)

Adding an engine = adding one fetch<Engine>() in bin/ops-social-planner + a dispatch branch. No UI change.

Run it

"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner"                 # collect → serve → open
"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner" collect          # regenerate state.json only
"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner" serve --port 7937  # serve existing state

The UI renders ui/state.sample.json (synthetic, PII-free) when no live state exists, so it works with zero credentials — useful for the PR preview.

Agent enrichment path (optional, when invoked as a skill)

The headless collector covers posts on wired engines. When richer output is wanted, the agent can:

  • Pull ads via MCP (mcp__meta__*, Google Ads) per project and merge kind:"ad" items into state.json.
  • Replace the heuristic rationale with an LLM-written one (slot + sequence + channel intent).
  • Re-run serve to refresh. Do not publish or edit from here — routing/mutation belongs to /ops-socials + /ops-marketing.

Guarantees

  • Read-only. No secrets in the client. Server binds 127.0.0.1 only.
  • Identity separation honored: personal vs project never merged; unprovisioned projects render fail-closed.
  • No owner data committed — only ui/ + bin/ + SPEC.md + the synthetic fixture.
1---
2name: ops-social-planner
3description: "OPS on-demand: This skill should be used when the user asks to \"content calendar\", \"what is scheduled\"…"
4argument-hint: '[--project <id>] [--collect-only] [--port <n>] [--no-open]'
5allowed-tools:
6 - Bash
7 - Read
8 - Grep
9 - Glob
10 - mcp__typefully__typefully_list_drafts
11 - mcp__typefully__typefully_get_draft
12 - mcp__typefully__typefully_get_queue
13 - mcp__upload-post__list_scheduled
14 - mcp__upload-post__list_profiles
15effort: medium
16maxTurns: 25
17---
18 
19# /ops-social-planner — engine-agnostic planned-content viewer
20 
21Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Workflow` are missing, follow Rule 10 in `ops-rules` (Hermes: numbered options / two-turn Telegram card; `delegate_task`).
22 
23Read-only dashboard of **every** scheduled post and ad, grouped **identity/project → channel → time**,
24**regardless of posting engine**. Personal/founder identity and project brands stay strictly separated
25(see `/ops-socials` identity rules). See `SPEC.md` for the full design.
26 
27## What it does
28 
291. `bin/ops-social-planner collect` reads `$PREFS_PATH/preferences.json`
30 (`marketing.social_identities.personal.*` + `marketing.projects.*.social`), dispatches a
31 **per-engine fetcher** keyed on `social.engine.primary`, normalizes to one schema, and writes
32 `$OPS_DATA_DIR/social-planner/state.json` (**owner data — never committed**, Rule 0).
332. It then serves the static UI (`ui/`) on localhost and opens the browser.
34 
35## Engines
36 
37| `engine.primary` | Source | Status |
38| ------------------------- | -------------------------------------------------------------------------------------- | ---------------------------- |
39| `typefully` | `GET /v2/social-sets/{id}/drafts` (Bearer; key from `~/.config/typefully/config.json`) | ✅ wired |
40| `upload-post` | `GET /api/uploadposts/schedule` (Apikey; key from `engine.upload_post.api_key_ref`) | ✅ wired |
41| `meta-graph` / `meta-ads` | Graph Ads API per project BM/token | 🔌 hook (UI shows "pending") |
42| `google-ads` | GAQL per project customer id | 🔌 hook |
43| `null` / unprovisioned | — | shown fail-closed (0 items) |
44 
45Adding an engine = adding one `fetch<Engine>()` in `bin/ops-social-planner` + a dispatch branch. No UI change.
46 
47## Run it
48 
49```bash
50"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner" # collect → serve → open
51"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner" collect # regenerate state.json only
52"${CLAUDE_PLUGIN_ROOT}/bin/ops-social-planner" serve --port 7937 # serve existing state
53```
54 
55The UI renders `ui/state.sample.json` (synthetic, PII-free) when no live state exists, so it works
56with zero credentials — useful for the PR preview.
57 
58## Agent enrichment path (optional, when invoked as a skill)
59 
60The headless collector covers posts on wired engines. When richer output is wanted, the agent can:
61 
62- Pull **ads** via MCP (`mcp__meta__*`, Google Ads) per project and merge `kind:"ad"` items into `state.json`.
63- Replace the heuristic `rationale` with an LLM-written one (slot + sequence + channel intent).
64- Re-run `serve` to refresh.
65 Do **not** publish or edit from here — routing/mutation belongs to `/ops-socials` + `/ops-marketing`.
66 
67## Guarantees
68 
69- Read-only. No secrets in the client. Server binds `127.0.0.1` only.
70- Identity separation honored: personal vs project never merged; unprovisioned projects render fail-closed.
71- No owner data committed — only `ui/` + `bin/` + `SPEC.md` + the synthetic fixture.
72 

Discussion