diff --git a/docs/gtm/voice.md b/docs/gtm/voice.md new file mode 100644 index 000000000..08ce12529 --- /dev/null +++ b/docs/gtm/voice.md @@ -0,0 +1,138 @@ +# Voice: Brian Love + +> The canonical voice + tone reference for any post drafted on Brian's behalf — blog, social, threads, replies. Synthesized from his pre-2026 blog corpus (`~/repos/brianflove/src/content/posts/`, 2012–2024, ~141 posts). The 2026 technical voice is intentionally excluded — that register was tuned for the streaming-chat pillar post and is narrower than how Brian actually writes. + +## tl;dr + +- One thought per line. Most paragraphs are 1–3 short sentences. Hard returns inside a paragraph are normal. +- Openings restate the title in a single sentence, then jump into the work. No "Introduction" header, no "in this article we'll explore." +- Contractions are normal ("it's," "don't," "let's"). Brian writes the way a senior engineer talks at a meetup. +- Warm, low-stakes humor — "freakin' cool," "giddy up and get onboard," the occasional emoji (🤔 💚 😆 👍). Not bro-voice. Closer to "fun coworker." +- Reader addressed directly ("Let's look at," "I suggest you check it out"); opinions flagged as opinions ("For me," "In my opinion," "what I think"). +- A small set of recurring values across 12 years: ship the boring thing, do it well, have fun, name the people you're thankful to. + +## Core stance + +Brian writes as a working engineer and small-business owner who has shipped a lot and is generous about what he learned. He is not an evangelist and not a guru. He is the friend at the meetup who already tried it. + +The job is to compress what he learned into something you can use this week. He frames decisions as tradeoffs and personal calls — recommends, but labels the recommendation as his own ("what I think," "for me"). + +He doesn't perform expertise. He's willing to be a beginner in public (*"I have spent one day (so far) learning expressjs"*), to be corrected (*"Please share any other antipatterns that you have learned from!"*), and to be earnest (*"Bootstrapping a startup is *hard*"*). + +## Registers + +Brian writes in three distinct registers. Pick one and stay in it. + +**Technical tutorial** (bulk of corpus, 2012–2020). Walks through a specific task. Numbered Goals, heavy "Let's," H2s as questions, explicit `## Conclusion`. Friendly, practical, code-heavy. For "here is how to do X." + +**Opinion / pattern post** (2017–2020). Antipatterns and recommendations, but softer than the 2026 register. Still contracted, still hedged with "I think," closes with an invitation. For "here is what I learned by getting it wrong." + +**Personal / business essay** (2019, 2024). Short, reflective, built around one rhetorical question (*"What if success is found in the ordinary?"*). One-sentence paragraphs. Italicized intensifiers. Closes on an invitation (*"Join me in ordinary success."*). For how he thinks, lives, or works. + +## Sentence-level patterns + +- **One sentence per line.** From "Year in Review": *"In 2023, I began development of Polaris in earnest. / This was my first step into bootstrapping a startup."* +- **Short, direct, most under 20 words.** *"I like ordinary. / Ordinary is welcoming, accepting, inclusive, diverse, unapologetic, and authentic."* +- **Contractions default.** *"Let's dive into the core concepts,"* *"You're finally getting the opportunity to build something."* +- **Italics for single-word emphasis.** *"Bootstrapping a startup is *hard*,"* *"build for mobile and *then* respond to users."* Rarely bold. +- **Bold reserved for must-do callouts.** *"This is a **must** for smaller screen devices."* +- **Em-dashes sparingly for asides.** He more often reaches for a period. +- **Exclamation marks allowed and warm.** *"Because it's freakin' cool!"* / *"Thank you RxJs!"* One or two per post; never stacked. +- **Rhetorical questions as section headers, answered immediately.** *"## What is it and why bother? / Simply put, LESS is a dynamic stylesheet language…"* +- **Inline parentheticals as asides.** *"(and of course follow me on Twitter – if that's your thing)."* +- **Hedges flagged honestly.** *"In my opinion,"* *"From my experience,"* *"For me, …"* + +## Structural moves + +- **Opening: title restated as the lede.** *"Learn the basics of implementing the Redux pattern in Angular applications."* +- **`## Goals` block, 3–5 bullets, near the top.** The last bullet is sometimes literally *"Have fun!"* +- **Series cross-link block** with the current post marked: *"1. NgRX: The Basics (this post) / 2. NgRX: Getting Started…"* +- **"Let's" as workhorse transition.** *"Let's break each of these down," "Let's look at an example," "Let's quickly review."* 3–10 times in a long tutorial. **Signature move.** +- **H2-as-question, body-as-answer.** *"## What are Operators? / ## How are they composable? / ## How do I import?"* +- **Three-bullet spine, each bullet becoming an H3.** Personal essays: *"- Building a product / - Listening to developers / - Consulting."* +- **"Anything Else?" near the close.** *"Did I miss something? Is there something on your top 5 list that I did not mention?"* +- **Explicit `## Conclusion`.** Restates the takeaway in one paragraph. +- **Closing invitation, not CTA.** *"Please share any other antipatterns that you have learned from!"* / *"Join me in ordinary success."* + +## Vocabulary + +### Reaches for + +- **"Let's"** — workhorse. *"Let's dive in," "Let's look at," "Let's review."* +- **"Simply" / "Simple enough" / "Really easy"** — genuine, not condescending. *"That's it. :)"* +- **"For me" / "In my opinion" / "From my experience"** — opinion flags. +- **"Have fun"** — recurring imperative; often the literal last Goals bullet. +- **"Giddy up and get onboard" / "freakin' cool"** — affectionate intensifiers for genuine excitement. +- **"Posture" / "long distance" / "infinite game"** — framing for sustainable work (2024). +- **"Thankful" / "grateful"** — names people: co-founder Mike, Kimberly in the comments, Jon Rista for help on a talk. +- **"P.S." / "haha"** — parenthetical warmth. +- **"In conclusion" / "In summary"** — he uses these. Don't avoid them. + +### Avoids + +- **Bro-voice and hype**: no "blazing fast," "game-changing," "revolutionary." For intensity he uses italics or "freakin' cool." +- **Marketing CTAs**: no "subscribe," "sign up," "DM me." Warm "let me know in the comments" is allowed. +- **Filler "let's dive in"**: when he writes it, the diving happens immediately. +- **Pretending to know more than he does.** *"I have spent one day (so far) learning…"* is the move. +- **Lecturing.** No "obviously," no "as we all know." + +## Recurring themes + framings + +- **Ship the boring thing well.** Apache configs, https setup, font sizing, presentation contrast — treated with the same care as NgRx. +- **Reader is a peer figuring it out.** Tutorials assume curiosity, not ignorance. +- **Personal disclosure as credibility.** *"I invested approximately $100k into Polaris."* *"When I first started giving presentation to a local meetup in Syracuse NY I really wanted to impress the group."* +- **Community and gratitude.** Named thanks to co-founders, advisors, commenters. +- **Fun as a virtue.** "Have fun!" appears as a literal Goals bullet, the 5th presentation tip, and the spine of the personal essays. +- **Faith, family, fitness, prayer** — plain in 2024 ("physical, mental, and spiritual health," "mindfulness, and prayer"). Part of the persona. +- **Bootstrapping and the infinite game.** Long distance over sprint. + +## Humor + persona + +Warm, slightly goofy, occasionally folksy. Humor lives in the asides: + +- *"Because it's freakin' cool!"* +- *"giddy up and get onboard."* +- *"(why not just use an iPhone? Silly kids.)"* — inline CSS-comment aside. +- *"Did I mention that boostrapping a startup is hard?"* — deadpan self-callback (typo and all; he doesn't over-polish). +- *"These are just two antipatterns that I have observed (haha)."* +- *"As the saying goes: 'Developers, developers, developers.' 😆"* + +**Emoji** appear 2018–2024, sparingly, one or two per post. Small repertoire: 💚 💙 🔥 🤔 🏖 👍 😲 😆. Color a sentence, never substitute for one. Don't stack. Don't use in business-essay register. + +**Relationship to reader**: warm peer. The engineer who saved you a seat at the meetup. + +## Examples — opening lines + +1. *"It's been on my list for awhile, and I was finally able to learn more about the LESS language."* (2012) — Personal lede; earns the right to teach by saying "I just learned this too." +2. *"I have spent one day (so far) learning expressjs -- a node.js framework for a web application server."* (2014) — Disclaimer as credibility. +3. *"Learn the basics of implementing the Redux pattern in Angular applications."* (2018) — Title restated as imperative. Standard tutorial opener. +4. *"Angular 9 provides improved debugging in the console using `window.ng`."* (2019) — Direct factual claim. No hedging. +5. *"Learn the top 5 tips for creating impactful presentations to ensure inspiration and not exhaustion."* (2019) — Promise paired with contrast: the "X and not Y" opinion opener. +6. *"I want to briefly look back in order to focus on looking forward."* (2024) — Essay opener: one sentence of intent. +7. *"Success in western culture is often marked by follower count, headcount, and GitHub stars. / What if success is found in the ordinary?"* (2024) — Cultural premise stated, then questioned. Signature essay move. + +## Examples — closing lines + +1. *"I hope this very simply example of using expressjs shows both the simplicity and the power of using expressjs with node.js."* (2014) — Tutorial closer. +2. *"Did I miss something? Is there something on your top 5 list for 2015 website design that I did not mention?"* (2015) — Hands the conversation back. +3. *"These are just two antipatterns that I have observed (haha). / Please share any other antipatterns that you have learned from!"* (2017) — Invitation, not CTA; "(haha)" softens the authority. +4. *"In the next post in this series we'll implement the Redux pattern using NgRx into an existing project."* (2018) — Series closer: forward link, not sign-off. +5. *"If you're going to put yourself out there and stand up in front of a group of peers, don't do it with fear and trembling. Do it with fun."* (2019) — Essay imperative. +6. *"Personally, I want to challenge myself with physical exercise, mindfulness, and prayer."* (2024) — Closes a "Technically / Economically / Personally" trio with the most personal item. +7. *"Join me in ordinary success."* (2024) — Shortest possible closer. + +## Drafting checklist + +- [ ] Opens with one sentence restating the title or stating intent. No "Introduction" header. +- [ ] Contractions present and natural. If absent, draft is in the wrong (2026) register. +- [ ] Paragraphs 1–3 lines; one sentence per line is fine. +- [ ] Pick a register: `## Goals` (tutorial), three-bullet spine (essay), or H2-as-question (concept). Use its scaffolding. +- [ ] At least one "Let's" transition per major section. +- [ ] Italics for single-word emphasis; bold reserved for one must-do callout. +- [ ] Opinions flagged ("I think," "For me," "From my experience"). +- [ ] At least one moment of warmth: an aside, "(haha)," "freakin' cool," a genuine "Thank you!", or a small emoji. +- [ ] No hype: no "blazing," "revolutionary," "game-changing," "10x." +- [ ] No marketing CTAs. Close is an invitation, forward link, or short imperative. +- [ ] Tutorial posts have `## Conclusion`. Essay posts end on a single imperative or invitation. +- [ ] Every recommendation paired with a cost or tradeoff; at least one personal disclosure earns the right to recommend. +- [ ] Reader is a peer. No "obviously," no "as we all know." diff --git a/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md b/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md new file mode 100644 index 000000000..c7b3918a5 --- /dev/null +++ b/docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md @@ -0,0 +1,1087 @@ +# Marketing Meta Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land the structural deliverables of the marketing-meta spec — `marketing/` directory, migrated Cowork home, four skeleton internal packages, voice doc already committed, and a supersede note in the GTM meta. No implementation logic in any subsystem. + +**Architecture:** `marketing/` is a new top-level directory with five sub-trees: `assets/`, `channels/`, `agent/`, `cowork/` (Claude skills + draft inboxes), `metrics/`. Four of them are `@ngaf/*` Nx workspace packages marked `"private": true` and resolved via `tsconfig.base.json` paths. `cowork/` is migrated wholesale from the repo root with one `git mv` and a README path update. + +**Tech Stack:** Nx workspace packages (`@nx/js:tsc` executor), npm workspaces, TypeScript 5.x. No runtime dependencies introduced — skeletons export empty stubs. + +**Spec reference:** `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md`. Branch: `marketing-meta` (already created in worktree; commits `4ccf38ef`, `187ef94f`, `7218c235` already on it). + +--- + +## File Structure + +**Move (single `git mv`):** + +- `cowork/` → `marketing/cowork/` (preserves history) + +**New:** + +- `marketing/README.md` +- `marketing/.env.example` +- `marketing/cowork/inbox/.gitkeep` +- `marketing/cowork/outbox/.gitkeep` +- `marketing/cowork/archive/.gitkeep` +- `marketing/cowork/marketing/SKILL.md` (stub — body lands in sub-spec 4) +- `marketing/assets/package.json` +- `marketing/assets/project.json` +- `marketing/assets/tsconfig.json` +- `marketing/assets/tsconfig.lib.json` +- `marketing/assets/src/index.ts` +- `marketing/channels/package.json` +- `marketing/channels/project.json` +- `marketing/channels/tsconfig.json` +- `marketing/channels/tsconfig.lib.json` +- `marketing/channels/src/index.ts` +- `marketing/agent/package.json` +- `marketing/agent/project.json` +- `marketing/agent/tsconfig.json` +- `marketing/agent/tsconfig.lib.json` +- `marketing/agent/src/index.ts` +- `marketing/metrics/package.json` +- `marketing/metrics/project.json` +- `marketing/metrics/tsconfig.json` +- `marketing/metrics/tsconfig.lib.json` +- `marketing/metrics/src/index.ts` + +**Modified:** + +- `marketing/cowork/README.md` — install path notes (post-move it references the new location). +- `package.json` (root) — add `marketing/*` to `workspaces`. +- `tsconfig.base.json` — add paths for the four new packages. +- `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` — append supersede note for Spec 6. + +--- + +## Task 1: Move `cowork/` → `marketing/cowork/` + +**Files:** + +- Move: `cowork/` → `marketing/cowork/` + +- [ ] **Step 1: Create the marketing root** + +```bash +cd /Users/blove/repos/angular-agent-framework/.claude/worktrees/gtm+cockpit-instrumentation +mkdir -p marketing +``` + +- [ ] **Step 2: Move with git to preserve history** + +```bash +git mv cowork marketing/cowork +git status --short +``` + +Expected: `R cowork/README.md -> marketing/cowork/README.md` and `R cowork/gtm/SKILL.md -> marketing/cowork/gtm/SKILL.md`. + +- [ ] **Step 3: Verify history is preserved** + +```bash +git log --follow --oneline marketing/cowork/gtm/SKILL.md | head -3 +``` + +Expected: at least one historical commit shown (not just the rename). + +- [ ] **Step 4: Commit the move** + +```bash +git add -A +git commit -m "refactor: move cowork/ to marketing/cowork/ + +Cowork becomes a subsystem of the marketing umbrella. The /gtm skill +content is unchanged; only its path moves. Install instructions in the +README will be updated in the next commit." +``` + +--- + +## Task 2: Update `marketing/cowork/README.md` install paths + +**Files:** + +- Modify: `marketing/cowork/README.md` + +- [ ] **Step 1: Read the current README** + +Run: `cat marketing/cowork/README.md` — note the four install snippets (lines that reference `cowork/gtm/SKILL.md`). + +- [ ] **Step 2: Update path references** + +Edit every occurrence of `cowork/gtm/SKILL.md` → `marketing/cowork/gtm/SKILL.md` in the README. The README has install snippets like: + +```bash +cp cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +``` + +Replace with: + +```bash +cp marketing/cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +``` + +Also update the "What's in this directory" ASCII tree to show the new layout: + +``` +marketing/cowork/ +├── README.md # This file. +├── gtm/ +│ └── SKILL.md # The GTM Cowork skill. +├── marketing/ +│ └── SKILL.md # The marketing pipeline Cowork skill (stub; body in sub-spec 4). +├── inbox/ # Drafts awaiting review. +├── outbox/ # Approved + posted drafts. +└── archive/ # Rejected or expired drafts. +``` + +- [ ] **Step 3: Commit** + +```bash +git add marketing/cowork/README.md +git commit -m "docs(marketing/cowork): update install paths after move" +``` + +--- + +## Task 3: Add draft inbox/outbox/archive directories + +**Files:** + +- Create: `marketing/cowork/inbox/.gitkeep` +- Create: `marketing/cowork/outbox/.gitkeep` +- Create: `marketing/cowork/archive/.gitkeep` + +- [ ] **Step 1: Create the dirs and gitkeeps** + +```bash +mkdir -p marketing/cowork/inbox marketing/cowork/outbox marketing/cowork/archive +touch marketing/cowork/inbox/.gitkeep marketing/cowork/outbox/.gitkeep marketing/cowork/archive/.gitkeep +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/cowork/inbox/.gitkeep marketing/cowork/outbox/.gitkeep marketing/cowork/archive/.gitkeep +git commit -m "feat(marketing/cowork): scaffold inbox/outbox/archive dirs" +``` + +--- + +## Task 4: Add `/marketing` Cowork skill stub + +**Files:** + +- Create: `marketing/cowork/marketing/SKILL.md` + +- [ ] **Step 1: Create the stub skill** + +```bash +mkdir -p marketing/cowork/marketing +``` + +Then write `marketing/cowork/marketing/SKILL.md`: + +```markdown +--- +name: marketing +description: | + Cacheplane marketing pipeline operator. Reads `marketing/cowork/inbox/*.json` + draft bundles, presents them for review in conversation, supports + edit/approve/reject decisions, and dispatches approved drafts to channel + adapters. Invoke when "drafts are waiting" or when the user wants to + produce a thread/post for X, LinkedIn, Dev.to, or Reddit. +status: stub +--- + +# Marketing Cowork skill — STUB + +Implementation lands in the cowork-loop sub-spec +(`docs/superpowers/specs/marketing/-cowork-loop-design.md`). + +This file exists so the directory shape and skill name are reserved. +Do NOT invoke this skill until the cowork-loop sub-spec is merged. + +## Expected file conventions (preview) + +- `marketing/cowork/inbox/.json` — drafts awaiting review (agent writes) +- `marketing/cowork/outbox/.json` — approved + posted (skill writes) +- `marketing/cowork/archive/.json` — rejected or expired (skill writes) + +`` is `YYYY-MM-DD-`. + +## Expected DraftBundle shape (preview) + +See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` §5.3. +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/cowork/marketing/SKILL.md +git commit -m "feat(marketing/cowork): add /marketing skill stub" +``` + +--- + +## Task 5: Add `marketing/*` to npm workspaces + +**Files:** + +- Modify: `package.json` (root) + +- [ ] **Step 1: Edit workspaces array** + +In root `package.json`, change the `workspaces` array from: + +```json + "workspaces": [ + "packages/*", + "apps/*", + "libs/*" + ], +``` + +to: + +```json + "workspaces": [ + "packages/*", + "apps/*", + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" + ], +``` + +(Listing them explicitly rather than `marketing/*` because `marketing/cowork`, `marketing/cowork/inbox`, etc. are NOT packages — they're skill markdown + JSON inbox dirs.) + +- [ ] **Step 2: Commit (install runs in Task 11)** + +```bash +git add package.json +git commit -m "chore: register marketing/* packages in npm workspaces" +``` + +--- + +## Task 6: Scaffold `@ngaf/marketing-assets` skeleton + +**Files:** + +- Create: `marketing/assets/package.json` +- Create: `marketing/assets/project.json` +- Create: `marketing/assets/tsconfig.json` +- Create: `marketing/assets/tsconfig.lib.json` +- Create: `marketing/assets/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/assets" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-assets", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/assets/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/assets"], + "options": { + "outputPath": "dist/marketing/assets", + "main": "marketing/assets/src/index.ts", + "tsConfig": "marketing/assets/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-assets — Brand asset rendering for the marketing pipeline. +// Skeleton only. Implementation lands in the brand-assets sub-spec. + +export interface CardInput { + template: string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(_input: CardInput): Promise { + throw new Error( + '@ngaf/marketing-assets: renderCard() not yet implemented. See brand-assets sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/assets/ +git commit -m "feat(marketing/assets): scaffold @ngaf/marketing-assets skeleton" +``` + +--- + +## Task 7: Scaffold `@ngaf/marketing-channels` skeleton + +**Files:** + +- Create: `marketing/channels/package.json` +- Create: `marketing/channels/project.json` +- Create: `marketing/channels/tsconfig.json` +- Create: `marketing/channels/tsconfig.lib.json` +- Create: `marketing/channels/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/channels" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-channels", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/channels/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/channels"], + "options": { + "outputPath": "dist/marketing/channels", + "main": "marketing/channels/src/index.ts", + "tsConfig": "marketing/channels/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-channels — Channel adapters for X, LinkedIn, Dev.to, Reddit. +// Skeleton only. Implementations land in the channel-adapters sub-spec. + +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(_id: ChannelId): ChannelAdapter { + throw new Error( + '@ngaf/marketing-channels: getAdapter() not yet implemented. See channel-adapters sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/channels/ +git commit -m "feat(marketing/channels): scaffold @ngaf/marketing-channels skeleton" +``` + +--- + +## Task 8: Scaffold `@ngaf/marketing-agent` skeleton + +**Files:** + +- Create: `marketing/agent/package.json` +- Create: `marketing/agent/project.json` +- Create: `marketing/agent/tsconfig.json` +- Create: `marketing/agent/tsconfig.lib.json` +- Create: `marketing/agent/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/agent" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-agent", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/agent/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/agent"], + "options": { + "outputPath": "dist/marketing/agent", + "main": "marketing/agent/src/index.ts", + "tsConfig": "marketing/agent/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-agent — LangGraph drafting agent for the marketing pipeline. +// Skeleton only. Implementation lands in the content-agent sub-spec. + +import type { Draft } from '@ngaf/marketing-channels'; + +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; + trigger: Trigger; + drafts: Draft[]; + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(_trigger: Trigger): Promise { + throw new Error( + '@ngaf/marketing-agent: draft() not yet implemented. See content-agent sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/agent/ +git commit -m "feat(marketing/agent): scaffold @ngaf/marketing-agent skeleton" +``` + +--- + +## Task 9: Scaffold `@ngaf/marketing-metrics` skeleton + +**Files:** + +- Create: `marketing/metrics/package.json` +- Create: `marketing/metrics/project.json` +- Create: `marketing/metrics/tsconfig.json` +- Create: `marketing/metrics/tsconfig.lib.json` +- Create: `marketing/metrics/src/index.ts` + +- [ ] **Step 1: Create package.json** + +```json +{ + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/metrics" + }, + "sideEffects": false, + "private": true +} +``` + +- [ ] **Step 2: Create project.json** + +```json +{ + "name": "marketing-metrics", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/metrics/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/metrics"], + "options": { + "outputPath": "dist/marketing/metrics", + "main": "marketing/metrics/src/index.ts", + "tsConfig": "marketing/metrics/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} +``` + +- [ ] **Step 3: Create tsconfig.json** + +```json +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} +``` + +- [ ] **Step 4: Create tsconfig.lib.json** + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} +``` + +- [ ] **Step 5: Create src/index.ts** + +```typescript +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-metrics — Metrics ingestion for the marketing pipeline. +// Skeleton only. Implementation lands in the metrics-ingest sub-spec. + +export interface RunOptions { + sinceHours?: number; +} + +export interface RunResult { + posts: number; + eventsEmitted: number; +} + +export function run(_opts?: RunOptions): Promise { + throw new Error( + '@ngaf/marketing-metrics: run() not yet implemented. See metrics-ingest sub-spec.', + ); +} +``` + +- [ ] **Step 6: Commit** + +```bash +git add marketing/metrics/ +git commit -m "feat(marketing/metrics): scaffold @ngaf/marketing-metrics skeleton" +``` + +--- + +## Task 10: Add path mappings to `tsconfig.base.json` + +**Files:** + +- Modify: `tsconfig.base.json` + +- [ ] **Step 1: Add four paths** + +Find the `"paths"` object in `tsconfig.base.json` (alphabetical with existing `@ngaf/*` entries). Insert these four entries in alphabetical position: + +```json + "@ngaf/marketing-agent": ["marketing/agent/src/index.ts"], + "@ngaf/marketing-assets": ["marketing/assets/src/index.ts"], + "@ngaf/marketing-channels": ["marketing/channels/src/index.ts"], + "@ngaf/marketing-metrics": ["marketing/metrics/src/index.ts"], +``` + +- [ ] **Step 2: Verify Nx sees the new projects** + +```bash +npx nx show projects | grep marketing +``` + +Expected output (order may vary): +``` +marketing-agent +marketing-assets +marketing-channels +marketing-metrics +``` + +- [ ] **Step 3: Verify the agent skeleton can import from channels (cross-package resolution)** + +```bash +npx tsc --noEmit marketing/agent/src/index.ts +``` + +Expected: clean, no errors. (This validates the `import type { Draft } from '@ngaf/marketing-channels';` resolves via the new path mapping.) + +- [ ] **Step 4: Commit** + +```bash +git add tsconfig.base.json +git commit -m "chore(tsconfig): add path mappings for marketing/* packages" +``` + +--- + +## Task 11: Install workspaces + verify build + +**Files:** none (verification only) + +- [ ] **Step 1: Install (surgical, no full lockfile rewrite)** + +```bash +cd /Users/blove/repos/angular-agent-framework/.claude/worktrees/gtm+cockpit-instrumentation +npm install --no-audit --no-fund +``` + +Expected: npm picks up the new `marketing/*` workspaces. No new dependencies are added (skeletons have no `dependencies` blocks). The lockfile may add `marketing/` workspace entries; that's expected. + +**Per project memory ("Don't regenerate package-lock.json on macOS"):** if the install changes any `@next/swc-*` platform binding entries in package-lock.json, revert those specific changes with `git checkout package-lock.json` and try `npm install --package-lock-only` instead. + +- [ ] **Step 2: Verify Nx graph** + +```bash +npx nx graph --file=/tmp/nx-graph.json +node -e "const g = require('/tmp/nx-graph.json'); console.log(Object.keys(g.graph.nodes).filter(n => n.startsWith('marketing-')).sort())" +``` + +Expected: `[ 'marketing-agent', 'marketing-assets', 'marketing-channels', 'marketing-metrics' ]` + +- [ ] **Step 3: Verify each package builds** + +```bash +npx nx run-many --target=build --projects=marketing-assets,marketing-channels,marketing-agent,marketing-metrics +``` + +Expected: 4/4 builds succeed. Each produces a `dist/marketing//index.js` and `index.d.ts`. + +- [ ] **Step 4: Commit lockfile changes if any** + +```bash +git add package-lock.json +git diff --cached --stat +# Only commit if there are real changes +git commit -m "chore: refresh lockfile for marketing/* workspaces" || echo "no lockfile changes" +``` + +--- + +## Task 12: Write `marketing/README.md` + +**Files:** + +- Create: `marketing/README.md` + +- [ ] **Step 1: Create the README** + +```markdown +# marketing/ + +Agentic marketing pipeline. Five composable subsystems that turn source content (blog posts, releases, prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. + +## Structure + +``` +marketing/ +├── assets/ # @ngaf/marketing-assets — branded image rendering +├── channels/ # @ngaf/marketing-channels — X, LinkedIn, Dev.to, Reddit adapters +├── agent/ # @ngaf/marketing-agent — LangGraph drafting agent +├── cowork/ # Claude skills (/gtm, /marketing) + inbox/outbox/archive +└── metrics/ # @ngaf/marketing-metrics — feedback ingestion → PostHog +``` + +All four packages are internal (`"private": true`). They are NOT published to npm. + +## Specs + +- Meta (this umbrella): `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- Sub-specs (when written): + - `brand-assets` — `docs/superpowers/specs/marketing/-brand-assets-design.md` + - `channel-adapters` — `docs/superpowers/specs/marketing/-channel-adapters-design.md` + - `content-agent` — `docs/superpowers/specs/marketing/-content-agent-design.md` + - `cowork-loop` — `docs/superpowers/specs/marketing/-cowork-loop-design.md` + - `metrics-ingest` — `docs/superpowers/specs/marketing/-metrics-ingest-design.md` + +## Voice + messaging source-of-truth + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases +- `docs/gtm/icp.md` — audience + +All in this repo. No machine-local paths in checked-in code. + +## Status + +This directory was scaffolded by the marketing-meta spec. Subsystems are skeletons. Implementation lands as each sub-spec ships. + +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/README.md +git commit -m "docs(marketing): add directory charter README" +``` + +--- + +## Task 13: Write `marketing/.env.example` + +**Files:** + +- Create: `marketing/.env.example` + +- [ ] **Step 1: Create the file** + +``` +# marketing/.env.example +# +# Placeholder env var names for the marketing pipeline. Real values live in +# .env at the repo root (gitignored). Copy lines you need into .env and fill +# in your credentials. +# +# Sub-specs may add more keys. This file is documentation, not consumed at +# runtime — each adapter reads its own keys via process.env. + +# X / Twitter +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= + +# LinkedIn +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= + +# Dev.to +DEVTO_API_KEY= + +# Reddit +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= + +# Pipeline behavior +# DRY_RUN=1 # Adapters return synthetic PostResults; nothing is posted. +``` + +- [ ] **Step 2: Commit** + +```bash +git add marketing/.env.example +git commit -m "docs(marketing): add .env.example with placeholder channel keys" +``` + +--- + +## Task 14: Append supersede note to GTM meta-spec + +**Files:** + +- Modify: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` + +- [ ] **Step 1: Find the Spec 6 row in the workstream decomposition table** + +Run: `grep -n 'community-launch' docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` + +Expected: at least one line number matching the row `| 6 | community-launch | 3 | ...`. + +- [ ] **Step 2: Add a note immediately under the table** + +Insert this paragraph after the table (immediately before the "Sequencing notes" heading at line ~134): + +```markdown +> **Spec 6 superseded.** The one-shot `community-launch` workstream was replaced by the ongoing marketing pipeline. See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` and its sub-specs. The launch exit criterion (week-1 dashboard snapshot, post-mortem doc) moves into the metrics-ingest sub-spec. +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +git commit -m "docs(gtm): note marketing umbrella supersedes Spec 6" +``` + +--- + +## Task 15: Final verification + PR + +**Files:** none (verification only) + +- [ ] **Step 1: Full build of marketing packages** + +```bash +npx nx run-many --target=build --projects=marketing-assets,marketing-channels,marketing-agent,marketing-metrics +``` + +Expected: 4/4 green. + +- [ ] **Step 2: Verify website + cockpit still build (the move didn't break anything)** + +```bash +npx nx run website:build +``` + +Expected: green. (`marketing/cowork/` is unrelated to website, but verify nothing imports from `cowork/` anywhere.) + +```bash +grep -rE "from ['\"]\.\.\/\.\.\/\.\.\/cowork|from ['\"]cowork" --include="*.ts" --include="*.tsx" apps/ libs/ 2>&1 | head +``` + +Expected: no matches. The cowork dir only contains markdown + JSON; nothing should import it. + +- [ ] **Step 3: Push branch + open PR** + +```bash +git push -u origin marketing-meta +gh pr create --title "feat(marketing): scaffold marketing/ umbrella + cowork migration" --body "$(cat <<'EOF' +## Summary +- Adds `marketing/` directory with 4 internal Nx packages: `@ngaf/marketing-{assets,channels,agent,metrics}` (all `"private": true`, skeletons only) +- Moves `cowork/` → `marketing/cowork/` (single `git mv`, history preserved) +- Adds `marketing/cowork/marketing/SKILL.md` stub (body in cowork-loop sub-spec) +- Adds `marketing/cowork/{inbox,outbox,archive}/.gitkeep` +- Adds `docs/gtm/voice.md` (Brian's pre-2026 voice synthesis) +- Adds `marketing/README.md` + `marketing/.env.example` +- Notes Spec 6 supersede in the GTM meta-spec + +Spec: `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +Plan: `docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md` + +## Test plan +- [ ] `npx nx run-many --target=build --projects=marketing-*` green +- [ ] `npx nx show projects | grep marketing` lists all 4 +- [ ] `npx nx run website:build` green (move didn't break anything) +- [ ] `git log --follow marketing/cowork/gtm/SKILL.md` shows pre-move history + +🤖 Generated with [Claude Code](https://claude.com/claude-code) +EOF +)" +``` + +- [ ] **Step 4: Enable auto-merge on green** + +```bash +gh pr merge --auto --squash +``` + +--- + +## Self-review + +**Spec coverage check** (against §10 deliverables in the meta-spec): + +- ✅ Meta-spec doc — committed as `187ef94f` before plan (out of plan scope) +- ✅ `marketing/README.md` — Task 12 +- ✅ Migrate `cowork/` → `marketing/cowork/` — Task 1 +- ✅ `marketing/.env.example` — Task 13 +- ✅ `docs/gtm/voice.md` — already committed as `7218c235` (out of plan scope, done by subagent) +- ✅ Skeleton `package.json` + `src/index.ts` for 4 packages — Tasks 6, 7, 8, 9 +- ✅ `project.json` for each so `nx graph` sees it — Tasks 6, 7, 8, 9 +- ✅ `tsconfig.base.json` path mappings — Task 10 +- ✅ Workspaces registration — Task 5 +- ✅ Inbox/outbox/archive scaffolding — Task 3 +- ✅ `/marketing` skill stub — Task 4 +- ✅ Cowork README path update — Task 2 +- ✅ GTM meta supersede note — Task 14 +- ✅ Build verification — Tasks 11, 15 + +**Placeholder scan:** All steps have concrete code, exact paths, exact commands. The `--design.md` references in the README and skill stub are intentional (sub-specs haven't been written yet); they're documentation pointers, not code TODOs. + +**Type consistency:** +- `Draft`, `PostResult`, `PostMetrics`, `ChannelAdapter`, `ChannelId` defined in Task 7, consumed in Task 8 via `import type { Draft } from '@ngaf/marketing-channels'`. Cross-package import is validated in Task 10 step 3. +- `Trigger`, `DraftBundle` defined in Task 8, referenced in skill stub (Task 4) as documentation. +- `CardInput`, `RenderedCard` defined in Task 6. No cross-package consumers in this plan. +- `RunOptions`, `RunResult` defined in Task 9. No cross-package consumers. + +All package names are `@ngaf/marketing-` consistently; all Nx project names are `marketing-` consistently. + +--- + +## Execution Handoff + +Plan complete and saved to `docs/superpowers/plans/marketing/2026-05-17-marketing-meta.md`. Two execution options: + +**1. Subagent-Driven (recommended)** — I dispatch a fresh subagent per task, two-stage review between each. + +**2. Inline Execution** — Execute tasks in this session using executing-plans, batch execution with checkpoints. + +Which approach? diff --git a/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md b/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md index e286f7821..f1c7083f3 100644 --- a/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +++ b/docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md @@ -131,6 +131,8 @@ Seven specs follow Spec 0. Dependencies form a DAG: | 6 | community-launch | 3 | 2, 3, 4, 5 | Launch executed, week-1 dashboard snapshot committed, post-mortem doc written. | | 7 | enterprise-pipeline | 4 | 1, 2 | 3 pilots tracked end-to-end in enterprise-funnel dashboard; ≥1 sanitized public artifact produced from a pilot. | +> **Spec 6 superseded.** The one-shot `community-launch` workstream was replaced by the ongoing marketing pipeline. See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` and its sub-specs. The launch exit criterion (week-1 dashboard snapshot, post-mortem doc) moves into the metrics-ingest sub-spec. + **Sequencing notes:** - Specs 5–7 can spin up in parallel as soon as their dependencies clear. The critical path is 0 → 1 → 2 → 3 → 4 (developer track end-to-end). diff --git a/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md b/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md new file mode 100644 index 000000000..689e5d069 --- /dev/null +++ b/docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md @@ -0,0 +1,368 @@ +--- +workstream: marketing-meta +status: approved +owner: brian +phase: 0 +spec: docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md +parent: docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md +supersedes: docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md#spec-6 +--- + +# Marketing Meta (Design) + +> Umbrella spec for the agentic marketing pipeline. Defines the `marketing/` directory, the five composable subsystems inside it, the interfaces between them, the sequencing, and the cross-cutting concerns (secrets, triggers, voice). No implementation lands here — each subsystem gets its own spec. + +## 1. Goal + +Stand up `marketing/` as a composable, agentic pipeline that turns source content (blog posts, releases, manual prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. Replaces ad-hoc launches with a continuous motion. + +This spec **supersedes Spec 6 (`community-launch`)** in the GTM meta. The marketing pipeline subsumes one-shot launch artifacts into the ongoing system. A short note in the GTM meta points here. + +## 2. Context + +- Parent: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md`. The GTM meta defines analytics, ICP, messaging, and the developer/enterprise CTA fork. This spec consumes those decisions; it does not redesign them. +- Cowork is a Claude skill surface (per `cowork/gtm/SKILL.md`), not a SaaS. The `/marketing` skill is a Claude skill that reads/writes structured files in `marketing/cowork/`. +- The PostHog `marketing:*` namespace already exists. This spec extends it with `marketing:social_*` events when the metrics subsystem ships. +- Voice + messaging come from this repo only: `docs/gtm/voice.md`, `docs/gtm/messaging.md`, `docs/gtm/icp.md`. No machine-local paths in any checked-in code. +- Existing repo conventions: + - Scope: `@ngaf/*` for all internal + published packages. Privacy via `"private": true` in `package.json`. + - Specs at `docs/superpowers/specs//YYYY-MM-DD--design.md`. + - Date-prefixed filenames everywhere (blog content, specs, plans). + +## 3. Scope + +**In scope (deliverables of this meta — see §10):** + +- This spec. +- `marketing/README.md` — directory charter. +- Migration: `cowork/` → `marketing/cowork/`. +- `marketing/.env.example` — placeholder env vars for each channel. +- `docs/gtm/voice.md` — Brian's voice + tone reference, synthesized from his blog corpus. +- Skeleton `package.json` for each of the 5 internal packages, marked `"private": true`. Empty `index.ts`. Nx sees the workspace shape. +- A note in `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` pointing at this umbrella as the supersede for Spec 6. + +**Out of scope (each is its own follow-up spec — see §6):** + +- Brand asset rendering implementation. +- Channel adapter implementations (X, LinkedIn, Dev.to, Reddit). +- The drafting agent. +- The `/marketing` Cowork skill body. +- Metrics ingestion code. +- Replacing the existing `marketing:*` analytics events (the existing namespace stays; the metrics spec extends it). + +## 4. Directory layout + +``` +marketing/ +├── README.md # charter + index of sub-specs +├── .env.example # placeholder secrets per channel +├── assets/ # @ngaf/marketing-assets (sub-spec 1) +│ ├── package.json # "private": true +│ └── src/index.ts # empty in this spec +├── channels/ # @ngaf/marketing-channels (sub-spec 2) +│ ├── package.json +│ └── src/index.ts +├── agent/ # @ngaf/marketing-agent (sub-spec 3) +│ ├── package.json +│ └── src/index.ts +├── cowork/ # Claude skills (migrated from /cowork) +│ ├── README.md # manual install instructions (updated path) +│ ├── gtm/ +│ │ └── SKILL.md # existing GTM skill, content unchanged +│ ├── marketing/ +│ │ └── SKILL.md # stub in this spec; body in sub-spec 4 +│ ├── inbox/ # .gitkeep — drafts await review here +│ ├── outbox/ # .gitkeep — approved + posted drafts +│ └── archive/ # .gitkeep — rejected or expired +└── metrics/ # @ngaf/marketing-metrics (sub-spec 5) + ├── package.json + └── src/index.ts +``` + +## 5. Subsystem charters + interfaces + +Each subsystem is independent, composable, and has one clear job. The interfaces below are the contract — implementation can change behind them. + +### 5.1 `@ngaf/marketing-assets` — Brand asset system + +**Charter.** Renders branded social images (1200×630, 1200×1200, 1080×1080) from typed input. Single source of truth for visual tokens, motifs, typography. Built on Next.js `ImageResponse` so it shares runtime with the website's blog OG cards. Each template is a named TSX function under `assets/templates/`. + +**Interface.** + +```ts +import type { Buffer } from 'node:buffer'; + +export interface CardInput { + template: 'x-announcement' | 'blog-og' | 'release' | 'linkedin-square' | string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(input: CardInput): Promise; +``` + +**Reuse.** The website's existing `/blog/[slug]/opengraph-image.tsx` and `/opengraph-image.tsx` migrate to call `renderCard()` once this package ships. Migration is a sub-spec deliverable, not in this meta. + +### 5.2 `@ngaf/marketing-channels` — Channel adapters + +**Charter.** One adapter per channel. Identical interface. Encapsulates auth, post, read metrics. Initial channels: X, LinkedIn, Dev.to, Reddit. Adding channels = adding adapter files. + +**Interface.** + +```ts +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; // channel-native (X = 280, LinkedIn = 3000, etc.) + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; // for X threads — successive tweets + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; // ISO; null = post now +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; // ISO +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(id: ChannelId): ChannelAdapter; +``` + +**Dry-run.** Every adapter accepts `DRY_RUN=1` env var. In dry-run, `post()` returns a synthetic `PostResult` with `postId: 'dry-'` and writes the draft to `marketing/cowork/outbox/` without hitting any API. Used by CI + safe local development. + +### 5.3 `@ngaf/marketing-agent` — Content-drafting agent + +**Charter.** LangGraph (JS SDK) agent that takes a `Trigger` + source context and emits N channel drafts. Reads `docs/gtm/voice.md`, `messaging.md`, `icp.md` for tone + claims. Writes output to `marketing/cowork/inbox/.json`. + +**Interface.** + +```ts +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; // YYYY-MM-DD- + trigger: Trigger; + drafts: Draft[]; // one per target channel + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(trigger: Trigger): Promise; +``` + +**CLI.** `pnpm marketing draft --trigger= [args]`. Same entry point for git-action and cron invocations. + +### 5.4 `marketing/cowork/marketing/SKILL.md` — Human approval surface + +**Charter.** Claude skill that reads `inbox/.json`, presents drafts in the conversation, lets the user edit/approve/reject. On approval, dispatches to channel adapters and moves the bundle to `outbox/.json` with post results. On reject, moves to `archive/.json`. + +**Interface.** Skill markdown + file conventions (`inbox/`, `outbox/`, `archive/`). The `DraftBundle` JSON shape (defined in 5.3) is the single source-of-truth for what flows through. + +### 5.5 `@ngaf/marketing-metrics` — Metrics ingestion + +**Charter.** Reads `marketing/cowork/outbox/*.json`, calls each adapter's `metrics(postId)`, and emits PostHog events in the `marketing:*` namespace. + +**Interface.** + +```ts +export function run(opts?: { sinceHours?: number }): Promise<{ posts: number; eventsEmitted: number }>; +``` + +**CLI.** `pnpm marketing metrics --since-hours=24`. Cron-invoked daily. Stateless — re-reading the same outbox just re-emits the same events (PostHog's distinct-id-based dedup handles idempotency for impression deltas; this spec doesn't try to be smarter). + +**New analytics events** (added in metrics sub-spec, not here): + +- `marketing:social_impression` — props: `channel`, `post_id`, `count` +- `marketing:social_click` — props: `channel`, `post_id`, `destination_url` +- `marketing:draft_approved` — props: `channel`, `post_id`, `latency_seconds` +- `marketing:draft_rejected` — props: `channel`, `reason` + +## 6. Sub-spec sequencing + +``` +[0] marketing-meta (this) + │ + ▼ + [migrate cowork/ → marketing/cowork/] + │ + ▼ +[1] brand-assets ─────────┐ + ▼ +[2] channel-adapters ─▶ [3] content-agent ─▶ [4] cowork-loop ─▶ [5] metrics-ingest +``` + +| # | Sub-spec | Depends on | Exit | +|--:|----------|------------|------| +| 0 | marketing-meta (this) | — | This spec merged; `cowork/` migrated; voice.md committed; skeleton packages exist | +| 1 | brand-assets | 0 | `@ngaf/marketing-assets` builds; `renderCard()` produces PNGs for 2+ templates; website blog-OG route migrated to call it | +| 2 | channel-adapters | 0 | All 4 adapters implement `ChannelAdapter`; secrets loading works; dry-run post round-trip green for each channel | +| 3 | content-agent | 1, 2 | Agent produces channel-shaped drafts from a blog-merge trigger; output lands in `marketing/cowork/inbox/` | +| 4 | cowork-loop | 3 | `/marketing` skill installed; full draft→approve→post round-trip works end-to-end for one channel (X) | +| 5 | metrics-ingest | 4 | One full post's metrics flow back into PostHog `marketing:social_impression` + `social_click` | + +**Critical path:** 0 → 2 → 3 → 4. Assets (1) is parallel to channels (2). Metrics (5) is a tail. + +**v1 channel cut.** Adapter sub-spec can ship with X first and add the other 3 incrementally — each is a small follow-up commit, not a re-spec. X first matches Brian's existing audience and the most concrete launch target. + +## 7. Cross-cutting concerns + +### 7.1 Secrets + +Channel API tokens live in `.env` at repo root (already gitignored). Each adapter reads its own keys via `process.env`: + +``` +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= +DEVTO_API_KEY= +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= +``` + +`marketing/.env.example` lists every required key with a one-line comment. For production cron, secrets come from the host (GitHub Actions secrets — exact configuration is in the cowork-loop or metrics sub-spec, not here). + +### 7.2 Triggers + +Three trigger sources, one entry point (`pnpm marketing draft`): + +1. **Source-content merge** — A new blog post or release tag lands on main. GitHub Action runs `pnpm marketing draft --trigger=blog-merge --slug=`. Action file ships in sub-spec 3. +2. **Manual Cowork prompt** — `/marketing draft ` in Claude. The skill writes a small trigger file and invokes the CLI. +3. **Scheduled cadence** — GitHub Actions cron, weekly. `pnpm marketing draft --trigger=cadence --window=weekly`. + +All converge on the same `draft()` entry point in `@ngaf/marketing-agent`. + +### 7.3 File conventions + +``` +marketing/cowork/ + inbox/.json # drafts awaiting review (agent writes, skill reads) + outbox/.json # approved + posted (skill writes, includes PostResult[]) + archive/.json # rejected or expired (skill writes) +``` + +`` is `YYYY-MM-DD-` — date-prefixed, like blog posts. The JSON shape is `DraftBundle` (defined in 5.3). One JSON file per pipeline run, regardless of how many channels it produced drafts for. + +### 7.4 Voice + messaging source-of-truth + +The agent reads three files **in this repo only**: + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks (NEW in this spec) +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases (existing) +- `docs/gtm/icp.md` — audience (existing) + +Zero references to `~/repos/brianflove/*` or any machine-local path in checked-in code or specs. Reproducible on CI. + +`voice.md` is synthesized once from Brian's blog corpus by a one-shot subagent task and committed to this repo. Subsequent voice tuning happens by editing `voice.md` directly. + +## 8. Architecture + +``` + ┌─────────────┐ trigger ┌─────────────┐ reads ┌──────────────┐ + │ GH Action / │ ───────────▶ │ agent │ ────────▶ │ voice.md │ + │ cron / CLI │ │ (draft()) │ │ messaging.md │ + └─────────────┘ └──────┬──────┘ │ icp.md │ + │ writes └──────────────┘ + ▼ + ┌─────────────┐ + │ inbox/ │ + │ .json │ + └──────┬──────┘ + │ /marketing skill reads + ▼ + ┌─────────────┐ + │ Cowork │ ── user approves / edits / rejects + │ (Claude) │ + └──────┬──────┘ + │ on approve: dispatch adapters + ▼ + ┌───────────────────────┐ + │ channel adapters │ + │ X · LI · Dev · Rdt │ + └──────┬────────────────┘ + │ writes + ▼ + ┌─────────────┐ + │ outbox/ │ + │ .json │ + └──────┬──────┘ + │ metrics CLI reads (cron) + ▼ + ┌─────────────┐ + │ PostHog │ + │ marketing:* │ + └─────────────┘ +``` + +Asset rendering hangs off the agent (drafts include an asset spec; the agent calls `renderCard()` and embeds the PNG bytes in `Draft.media`). + +## 9. Risks + non-goals + +| # | Risk | Mitigation | Owner | +|--:|------|------------|-------| +| 1 | Channel APIs change underneath us (esp. X) | One adapter file per channel; each is small enough to rewrite in a day | Sub-spec 2 | +| 2 | Auto-posting goes off the rails | Every dispatch is gated by Cowork approval; no auto-post path in v1 | Sub-spec 4 | +| 3 | Voice file ages out of date | One-line drafting checklist at bottom of `voice.md`; revisit quarterly | This spec (subagent) | +| 4 | Metrics get noisy or wrong | Single namespace + draft_id correlation makes it easy to filter; v1 doesn't try to be smarter than that | Sub-spec 5 | +| 5 | The agent over-produces channel-specific drafts that all sound the same | Channel-specific prompts per adapter; voice.md provides shape, channel adapter prompts provide format | Sub-spec 3 | + +**Non-goals (v1):** + +- Multi-author voice (only Brian). +- Multi-tenant (single Cacheplane). +- Engagement automation (replies, DMs, comment threads). Out of scope and likely permanently. +- A web UI for approvals — Cowork is the UI. +- Analytics dashboards specific to social. Existing developer-funnel + marketing dashboards consume the new events; no new dashboard JSON in v1. + +## 10. Deliverables (of this meta-spec) + +- ☐ This spec at `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- ☐ `marketing/README.md` — directory charter, links to sub-specs, install/run quickstart +- ☐ Migrate `cowork/` → `marketing/cowork/` (single `git mv` + README path update + `cowork/{inbox,outbox,archive}/.gitkeep`) +- ☐ `marketing/.env.example` — placeholder env var names for each channel +- ☐ `docs/gtm/voice.md` — Brian's voice + tone reference (synthesized by subagent) +- ☐ Skeleton `package.json` + `src/index.ts` for: `@ngaf/marketing-assets`, `@ngaf/marketing-channels`, `@ngaf/marketing-agent`, `@ngaf/marketing-metrics`. All `"private": true`. Each exports an empty interface so Nx + tsconfig paths resolve. +- ☐ Each skeleton package has a `project.json` so `nx graph` sees it. +- ☐ Note appended to `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` pointing at this umbrella as the supersede for Spec 6. + +## 11. References + +- Parent: `docs/superpowers/specs/gtm/2026-05-13-gtm-meta-design.md` +- Existing Cowork skill: `cowork/gtm/SKILL.md` (to be moved) +- Existing analytics: `apps/website/src/lib/analytics/events.ts`, `docs/gtm/taxonomy.md` +- Blog seed post (first content this pipeline will syndicate): `apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx` diff --git a/marketing/.env.example b/marketing/.env.example new file mode 100644 index 000000000..996a379be --- /dev/null +++ b/marketing/.env.example @@ -0,0 +1,30 @@ +# marketing/.env.example +# +# Placeholder env var names for the marketing pipeline. Real values live in +# .env at the repo root (gitignored). Copy lines you need into .env and fill +# in your credentials. +# +# Sub-specs may add more keys. This file is documentation, not consumed at +# runtime — each adapter reads its own keys via process.env. + +# X / Twitter +X_API_KEY= +X_API_SECRET= +X_ACCESS_TOKEN= +X_ACCESS_SECRET= + +# LinkedIn +LINKEDIN_ACCESS_TOKEN= +LINKEDIN_AUTHOR_URN= + +# Dev.to +DEVTO_API_KEY= + +# Reddit +REDDIT_CLIENT_ID= +REDDIT_CLIENT_SECRET= +REDDIT_USERNAME= +REDDIT_PASSWORD= + +# Pipeline behavior +# DRY_RUN=1 # Adapters return synthetic PostResults; nothing is posted. diff --git a/marketing/README.md b/marketing/README.md new file mode 100644 index 000000000..c925a5a61 --- /dev/null +++ b/marketing/README.md @@ -0,0 +1,38 @@ +# marketing/ + +Agentic marketing pipeline. Five composable subsystems that turn source content (blog posts, releases, prompts, schedules) into multi-channel posts (X, LinkedIn, Dev.to, Reddit), with Cowork as the human approval surface and PostHog as the feedback loop. + +## Structure + +``` +marketing/ +├── assets/ # @ngaf/marketing-assets — branded image rendering +├── channels/ # @ngaf/marketing-channels — X, LinkedIn, Dev.to, Reddit adapters +├── agent/ # @ngaf/marketing-agent — LangGraph drafting agent +├── cowork/ # Claude skills (/gtm, /marketing) + inbox/outbox/archive +└── metrics/ # @ngaf/marketing-metrics — feedback ingestion → PostHog +``` + +All four packages are internal (`"private": true`). They are NOT published to npm. + +## Specs + +- Meta (this umbrella): `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` +- Sub-specs (when written): + - `brand-assets` — `docs/superpowers/specs/marketing/-brand-assets-design.md` + - `channel-adapters` — `docs/superpowers/specs/marketing/-channel-adapters-design.md` + - `content-agent` — `docs/superpowers/specs/marketing/-content-agent-design.md` + - `cowork-loop` — `docs/superpowers/specs/marketing/-cowork-loop-design.md` + - `metrics-ingest` — `docs/superpowers/specs/marketing/-metrics-ingest-design.md` + +## Voice + messaging source-of-truth + +- `docs/gtm/voice.md` — Brian's tone, phrasing, structural quirks +- `docs/gtm/messaging.md` — positioning, claims, no-go phrases +- `docs/gtm/icp.md` — audience + +All in this repo. No machine-local paths in checked-in code. + +## Status + +This directory was scaffolded by the marketing-meta spec. Subsystems are skeletons. Implementation lands as each sub-spec ships. diff --git a/marketing/agent/package.json b/marketing/agent/package.json new file mode 100644 index 000000000..30578ecd2 --- /dev/null +++ b/marketing/agent/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/agent" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/agent/project.json b/marketing/agent/project.json new file mode 100644 index 000000000..a412512f2 --- /dev/null +++ b/marketing/agent/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-agent", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/agent/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/agent"], + "options": { + "outputPath": "dist/marketing/agent", + "main": "marketing/agent/src/index.ts", + "tsConfig": "marketing/agent/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/agent/src/index.ts b/marketing/agent/src/index.ts new file mode 100644 index 000000000..4c7bfa73a --- /dev/null +++ b/marketing/agent/src/index.ts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-agent — LangGraph drafting agent for the marketing pipeline. +// Skeleton only. Implementation lands in the content-agent sub-spec. + +import type { Draft } from '@ngaf/marketing-channels'; + +export type Trigger = + | { kind: 'blog-merge'; slug: string } + | { kind: 'release'; tag: string } + | { kind: 'cowork-prompt'; topic: string; freeform?: string } + | { kind: 'cadence'; window: 'weekly' }; + +export interface DraftBundle { + id: string; + trigger: Trigger; + drafts: Draft[]; + source: { url?: string; title?: string; excerpt?: string }; + createdAt: string; +} + +export function draft(_trigger: Trigger): Promise { + throw new Error( + '@ngaf/marketing-agent: draft() not yet implemented. See content-agent sub-spec.', + ); +} diff --git a/marketing/agent/tsconfig.json b/marketing/agent/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/agent/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/agent/tsconfig.lib.json b/marketing/agent/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/agent/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/marketing/assets/package.json b/marketing/assets/package.json new file mode 100644 index 000000000..fcb978a5b --- /dev/null +++ b/marketing/assets/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/assets" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/assets/project.json b/marketing/assets/project.json new file mode 100644 index 000000000..82c9dfe9a --- /dev/null +++ b/marketing/assets/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-assets", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/assets/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/assets"], + "options": { + "outputPath": "dist/marketing/assets", + "main": "marketing/assets/src/index.ts", + "tsConfig": "marketing/assets/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/assets/src/index.ts b/marketing/assets/src/index.ts new file mode 100644 index 000000000..5f04bc5d9 --- /dev/null +++ b/marketing/assets/src/index.ts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-assets — Brand asset rendering for the marketing pipeline. +// Skeleton only. Implementation lands in the brand-assets sub-spec. + +export interface CardInput { + template: string; + title: string; + subtitle?: string; + tag?: string; + author?: { name: string; role?: string }; +} + +export interface RenderedCard { + png: Buffer; + width: number; + height: number; + contentType: 'image/png'; +} + +export function renderCard(_input: CardInput): Promise { + throw new Error( + '@ngaf/marketing-assets: renderCard() not yet implemented. See brand-assets sub-spec.', + ); +} diff --git a/marketing/assets/tsconfig.json b/marketing/assets/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/assets/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/assets/tsconfig.lib.json b/marketing/assets/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/assets/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/marketing/channels/package.json b/marketing/channels/package.json new file mode 100644 index 000000000..94048fc2a --- /dev/null +++ b/marketing/channels/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/channels" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/channels/project.json b/marketing/channels/project.json new file mode 100644 index 000000000..e27933ba3 --- /dev/null +++ b/marketing/channels/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-channels", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/channels/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/channels"], + "options": { + "outputPath": "dist/marketing/channels", + "main": "marketing/channels/src/index.ts", + "tsConfig": "marketing/channels/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/channels/src/index.ts b/marketing/channels/src/index.ts new file mode 100644 index 000000000..e1f138630 --- /dev/null +++ b/marketing/channels/src/index.ts @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-channels — Channel adapters for X, LinkedIn, Dev.to, Reddit. +// Skeleton only. Implementations land in the channel-adapters sub-spec. + +export type ChannelId = 'x' | 'linkedin' | 'devto' | 'reddit'; + +export interface Draft { + channel: ChannelId; + text: string; + media?: { png: Buffer; alt: string }[]; + threadParts?: string[]; + link?: { url: string; previewTitle?: string }; + scheduledAt?: string; +} + +export interface PostResult { + channel: ChannelId; + postId: string; + url: string; + postedAt: string; +} + +export interface PostMetrics { + postId: string; + impressions?: number; + clicks?: number; + replies?: number; + shares?: number; + fetchedAt: string; +} + +export interface ChannelAdapter { + readonly id: ChannelId; + post(draft: Draft): Promise; + metrics(postId: string): Promise; +} + +export function getAdapter(_id: ChannelId): ChannelAdapter { + throw new Error( + '@ngaf/marketing-channels: getAdapter() not yet implemented. See channel-adapters sub-spec.', + ); +} diff --git a/marketing/channels/tsconfig.json b/marketing/channels/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/channels/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/channels/tsconfig.lib.json b/marketing/channels/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/channels/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/cowork/README.md b/marketing/cowork/README.md similarity index 73% rename from cowork/README.md rename to marketing/cowork/README.md index 70bb628c8..f111ad7a3 100644 --- a/cowork/README.md +++ b/marketing/cowork/README.md @@ -7,26 +7,31 @@ The skill is **project-local**. It isn't published to a marketplace. Install ste ## What's in this directory ``` -cowork/ +marketing/cowork/ ├── README.md # This file. -└── gtm/ - └── SKILL.md # The single Cowork skill. Edit here; install copies/symlinks elsewhere. +├── gtm/ +│ └── SKILL.md # The GTM Cowork skill. +├── marketing/ +│ └── SKILL.md # The marketing pipeline Cowork skill (stub; body in sub-spec 4). +├── inbox/ # Drafts awaiting review. +├── outbox/ # Approved + posted drafts. +└── archive/ # Rejected or expired drafts. ``` ## Install (one-time per machine) -The skill ships as `cowork/gtm/SKILL.md`. To make it loadable by Cowork / Claude Code on your machine, register it as a user-level skill: +The skill ships as `marketing/cowork/gtm/SKILL.md`. To make it loadable by Cowork / Claude Code on your machine, register it as a user-level skill: ```bash mkdir -p ~/.claude/skills/gtm -cp cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md +cp marketing/cowork/gtm/SKILL.md ~/.claude/skills/gtm/SKILL.md ``` Or symlink it so edits in this repo flow through immediately: ```bash mkdir -p ~/.claude/skills/gtm -ln -sf "$(pwd)/cowork/gtm/SKILL.md" ~/.claude/skills/gtm/SKILL.md +ln -sf "$(pwd)/marketing/cowork/gtm/SKILL.md" ~/.claude/skills/gtm/SKILL.md ``` Verify: open a Claude Code session in this repo and type `/gtm`. The skill should load and announce its responsibilities. @@ -51,7 +56,7 @@ The routine itself lives at the user/runtime layer; it is not committed to the r ## Update -Edit `cowork/gtm/SKILL.md` in this repo. If you symlinked, the change is live immediately. If you copied, re-run the `cp` command after committing. +Edit `marketing/cowork/gtm/SKILL.md` in this repo. If you symlinked, the change is live immediately. If you copied, re-run the `cp` command after committing. ## Why one skill, not a plugin diff --git a/marketing/cowork/archive/.gitkeep b/marketing/cowork/archive/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cowork/gtm/SKILL.md b/marketing/cowork/gtm/SKILL.md similarity index 100% rename from cowork/gtm/SKILL.md rename to marketing/cowork/gtm/SKILL.md diff --git a/marketing/cowork/inbox/.gitkeep b/marketing/cowork/inbox/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/marketing/cowork/marketing/SKILL.md b/marketing/cowork/marketing/SKILL.md new file mode 100644 index 000000000..9d1765b44 --- /dev/null +++ b/marketing/cowork/marketing/SKILL.md @@ -0,0 +1,30 @@ +--- +name: marketing +description: | + Cacheplane marketing pipeline operator. Reads `marketing/cowork/inbox/*.json` + draft bundles, presents them for review in conversation, supports + edit/approve/reject decisions, and dispatches approved drafts to channel + adapters. Invoke when "drafts are waiting" or when the user wants to + produce a thread/post for X, LinkedIn, Dev.to, or Reddit. +status: stub +--- + +# Marketing Cowork skill — STUB + +Implementation lands in the cowork-loop sub-spec +(`docs/superpowers/specs/marketing/-cowork-loop-design.md`). + +This file exists so the directory shape and skill name are reserved. +Do NOT invoke this skill until the cowork-loop sub-spec is merged. + +## Expected file conventions (preview) + +- `marketing/cowork/inbox/.json` — drafts awaiting review (agent writes) +- `marketing/cowork/outbox/.json` — approved + posted (skill writes) +- `marketing/cowork/archive/.json` — rejected or expired (skill writes) + +`` is `YYYY-MM-DD-`. + +## Expected DraftBundle shape (preview) + +See `docs/superpowers/specs/marketing/2026-05-17-marketing-meta-design.md` §5.3. diff --git a/marketing/cowork/outbox/.gitkeep b/marketing/cowork/outbox/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/marketing/metrics/package.json b/marketing/metrics/package.json new file mode 100644 index 000000000..27a15813c --- /dev/null +++ b/marketing/metrics/package.json @@ -0,0 +1,14 @@ +{ + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT", + "main": "./src/index.ts", + "types": "./src/index.ts", + "repository": { + "type": "git", + "url": "https://github.com/cacheplane/angular-agent-framework.git", + "directory": "marketing/metrics" + }, + "sideEffects": false, + "private": true +} diff --git a/marketing/metrics/project.json b/marketing/metrics/project.json new file mode 100644 index 000000000..5528232b8 --- /dev/null +++ b/marketing/metrics/project.json @@ -0,0 +1,21 @@ +{ + "name": "marketing-metrics", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "marketing/metrics/src", + "projectType": "library", + "tags": ["scope:marketing", "type:lib"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/marketing/metrics"], + "options": { + "outputPath": "dist/marketing/metrics", + "main": "marketing/metrics/src/index.ts", + "tsConfig": "marketing/metrics/tsconfig.lib.json" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/marketing/metrics/src/index.ts b/marketing/metrics/src/index.ts new file mode 100644 index 000000000..454c42e98 --- /dev/null +++ b/marketing/metrics/src/index.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// @ngaf/marketing-metrics — Metrics ingestion for the marketing pipeline. +// Skeleton only. Implementation lands in the metrics-ingest sub-spec. + +export interface RunOptions { + sinceHours?: number; +} + +export interface RunResult { + posts: number; + eventsEmitted: number; +} + +export function run(_opts?: RunOptions): Promise { + throw new Error( + '@ngaf/marketing-metrics: run() not yet implemented. See metrics-ingest sub-spec.', + ); +} diff --git a/marketing/metrics/tsconfig.json b/marketing/metrics/tsconfig.json new file mode 100644 index 000000000..cf0cba0d6 --- /dev/null +++ b/marketing/metrics/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "references": [{ "path": "./tsconfig.lib.json" }] +} diff --git a/marketing/metrics/tsconfig.lib.json b/marketing/metrics/tsconfig.lib.json new file mode 100644 index 000000000..643573425 --- /dev/null +++ b/marketing/metrics/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts"] +} diff --git a/package-lock.json b/package-lock.json index d995c618b..4de0dc5d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,11 @@ "workspaces": [ "packages/*", "apps/*", - "libs/*" + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" ], "dependencies": { "@ag-ui/client": "^0.0.52", @@ -173,12 +177,12 @@ }, "libs/a2ui": { "name": "@ngaf/a2ui", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT" }, "libs/ag-ui": { "name": "@ngaf/ag-ui", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@ag-ui/client": "*", @@ -190,7 +194,7 @@ }, "libs/chat": { "name": "@ngaf/chat", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "dependencies": { "@cacheplane/partial-json": ">=0.1.1 <0.3.0", @@ -318,7 +322,7 @@ }, "libs/langgraph": { "name": "@ngaf/langgraph", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@angular/core": "^20.0.0 || ^21.0.0", @@ -331,7 +335,7 @@ }, "libs/licensing": { "name": "@ngaf/licensing", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@noble/ed25519": "^2.2.3" @@ -339,7 +343,7 @@ }, "libs/render": { "name": "@ngaf/render", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "peerDependencies": { "@angular/common": "^20.0.0 || ^21.0.0", @@ -350,7 +354,7 @@ }, "libs/telemetry": { "name": "@ngaf/telemetry", - "version": "0.0.35", + "version": "0.0.37", "license": "MIT", "bin": { "ngaf-telemetry-postinstall": "node/postinstall.js" @@ -377,6 +381,26 @@ "react-dom": "^19.0.0" } }, + "marketing/agent": { + "name": "@ngaf/marketing-agent", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/assets": { + "name": "@ngaf/marketing-assets", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/channels": { + "name": "@ngaf/marketing-channels", + "version": "0.0.0", + "license": "MIT" + }, + "marketing/metrics": { + "name": "@ngaf/marketing-metrics", + "version": "0.0.0", + "license": "MIT" + }, "node_modules/@ag-ui/client": { "version": "0.0.52", "resolved": "https://registry.npmjs.org/@ag-ui/client/-/client-0.0.52.tgz", @@ -12076,6 +12100,22 @@ "resolved": "libs/licensing", "link": true }, + "node_modules/@ngaf/marketing-agent": { + "resolved": "marketing/agent", + "link": true + }, + "node_modules/@ngaf/marketing-assets": { + "resolved": "marketing/assets", + "link": true + }, + "node_modules/@ngaf/marketing-channels": { + "resolved": "marketing/channels", + "link": true + }, + "node_modules/@ngaf/marketing-metrics": { + "resolved": "marketing/metrics", + "link": true + }, "node_modules/@ngaf/minting-service": { "resolved": "apps/minting-service", "link": true diff --git a/package.json b/package.json index 01700b0f0..81e38c1b7 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,11 @@ "workspaces": [ "packages/*", "apps/*", - "libs/*" + "libs/*", + "marketing/assets", + "marketing/channels", + "marketing/agent", + "marketing/metrics" ], "dependencies": { "@ag-ui/client": "^0.0.52", diff --git a/tsconfig.base.json b/tsconfig.base.json index 453b76c99..696b3c26d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,10 @@ "@ngaf/langgraph": ["libs/langgraph/src/public-api.ts"], "@ngaf/licensing": ["libs/licensing/src/index.ts"], "@ngaf/licensing/testing": ["libs/licensing/src/testing.ts"], + "@ngaf/marketing-agent": ["marketing/agent/src/index.ts"], + "@ngaf/marketing-assets": ["marketing/assets/src/index.ts"], + "@ngaf/marketing-channels": ["marketing/channels/src/index.ts"], + "@ngaf/marketing-metrics": ["marketing/metrics/src/index.ts"], "@ngaf/render": ["libs/render/src/public-api.ts"], "@ngaf/telemetry": ["libs/telemetry/src/index.ts"], "@ngaf/telemetry/browser": ["libs/telemetry/src/browser/public-api.ts"],