Skip to content

feat(marketing/channels): X adapter (OAuth 2.0, threads, media) + shared infra#425

Merged
blove merged 20 commits into
mainfrom
marketing-channel-adapters
May 18, 2026
Merged

feat(marketing/channels): X adapter (OAuth 2.0, threads, media) + shared infra#425
blove merged 20 commits into
mainfrom
marketing-channel-adapters

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 18, 2026

Summary

Sub-spec 2 of the marketing umbrella. Replaces the @ngaf/marketing-channels skeleton with a real X adapter plus shared infra.

Shared infra (reused by future LinkedIn/Dev.to/Reddit adapters):

  • `types.ts` — Draft, PostResult, PostMetrics, ChannelAdapter, ChannelId
  • `validation.ts` — channel-aware validateDraft() with hard-reject behavior
  • `http.ts` — fetch wrapper with timeout, retry on 5xx, 401-refresh hook
  • `dry-run.ts` — DRY_RUN=1 writes to marketing/cowork/outbox/dry-runs/
  • `registry.ts` — getAdapter(id) resolves from a static map

X adapter:

  • OAuth 2.0 User Context with PKCE (corrected from initial OAuth 1.0a assumption — verified May 2026)
  • v2 /2/media/upload endpoint (not v1.1 chunked)
  • One-time bootstrapper CLI: `pnpm marketing:channels:x:auth`
  • Auto-refresh on 401; new refresh token printed to stderr for .env update
  • Single tweet + threads + image media (PNG ≤ 5MB, alt text required)
  • metrics() stubbed (X tier has no read endpoints; comment points at upgrade path)

Tests: 33 tests across 5 spec files (validation: 14, http: 7, dry-run: 2, auth: 5, post: 5). All msw-mocked.

Plan deviations (all caught + handled):

  1. http.ts had a `retryRes` scope bug in the plan — caught by the test it ships with, fixed minimally
  2. tsconfig.lib.json needed `dom` lib for Blob/FormData/BodyInit — added matching libs/telemetry's pattern
  3. media.ts needed `new Uint8Array(png)` wrap to satisfy strict DOM BlobPart type — runtime behavior identical

Manual smoke required before merge (per spec): see marketing/channels/MANUAL-SMOKE.md

Spec: `docs/superpowers/specs/marketing/2026-05-17-channel-adapters-design.md`
Plan: `docs/superpowers/plans/marketing/2026-05-17-channel-adapters.md`

Test plan

  • `npx nx run marketing-channels:build` green
  • `npx nx run marketing-channels:test` green (33 tests)
  • `npx nx run website:build` green
  • Brian runs manual smoke (single tweet + media + thread); pastes results below
  • Auto-merge enabled only after manual smoke confirms

🤖 Generated with Claude Code

blove and others added 19 commits May 18, 2026 10:37
Replaces @ngaf/marketing-channels skeleton with real X adapter.
OAuth 2.0 with PKCE (corrected from initial OAuth 1.0a assumption),
v2 /2/media/upload endpoint, hard validation, dry-run, 401-refresh
path, one-time bootstrapper CLI. Stub metrics() until X tier upgrade.
Shared validation/http/registry/dry-run infra for future LinkedIn,
Dev.to, Reddit adapters. Dev.to lands next after X.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 tasks across 5 phases: shared infra (types, validation, http,
dry-run, registry), X auth (state machine + OAuth 2.0 PKCE
bootstrapper CLI), X media upload (v2 endpoint), X post (single +
thread + media), docs + smoke script, build/test verification, PR.
Full TDD; msw/node mocks for all HTTP paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 18, 2026 7:21pm

Request Review

tsx (and npx tsx) do not auto-load .env files; the X adapter reads
required env vars directly via process.env. Without --env-file, the
bootstrapper and smoke script throw "X adapter missing env vars"
even when .env is correctly populated.

- npm script `marketing:channels:x:auth` now passes --env-file=.env to tsx
- New npm script `marketing:channels:x:smoke` for the smoke runs
- MANUAL-SMOKE.md updated to use the npm scripts (so the env-file flag is
  centralized, not duplicated in user-typed commands)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove enabled auto-merge (squash) May 18, 2026 19:09
@blove blove merged commit f3cff32 into main May 18, 2026
18 checks passed
blove added a commit that referenced this pull request May 18, 2026
* docs(marketing/channels): spec for Dev.to adapter

Follow-up to channel-adapters. Extends Draft with optional
DraftArticle (title, tags, canonicalUrl, description) — usable by
Dev.to v1 and LinkedIn long-form later. Single API key auth, single
POST endpoint, real metrics() (Dev.to read API is free). Direct
syndication of blog posts with canonical_url set to cacheplane.ai/blog.
Publishes immediately (Cowork is the human gate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(marketing/channels): plan for Dev.to adapter

10 tasks across 5 phases: extend Draft with DraftArticle, add Dev.to
validation rules (TDD), implement post() and metrics() with msw mocks,
DevToAdapter class, wire registry, extend smoke script, README +
MANUAL-SMOKE updates, build/test verification, PR (stacked on #425).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(marketing/channels): add DraftArticle sub-type to Draft

* feat(marketing/channels): add Dev.to validation rules

* feat(marketing/channels): Dev.to post()

* feat(marketing/channels): Dev.to metrics() — real read API

* feat(marketing/channels): add DevToAdapter class

* feat(marketing/channels): wire DevToAdapter into registry

* feat(marketing/channels): smoke script supports --channel=devto

* docs(marketing/channels): document Dev.to adapter

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant