# Ops Social Planner

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

- Skill: `lifecycle-innovations-limited/ops-social-planner` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add lifecycle-innovations-limited/ops-social-planner`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lifecycle-innovations-limited/ops-social-planner/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: Lifecycle-Innovations-Limited (https://skillmd.com/u/lifecycle-innovations-limited)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/lifecycle-innovations-limited/ops-social-planner

---


# /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

```bash
"${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.

