Skip to content

Latest commit

 

History

History
303 lines (240 loc) · 24.8 KB

File metadata and controls

303 lines (240 loc) · 24.8 KB

Social Agent Knowledge Base

This file is the fast-start context for LLMs and human maintainers working in this repo.

What This Project Does

social-agent turns selected Reddit posts into platform-specific social posts, queues them into four daily slots, and publishes them to enabled social platforms.

Current content flow:

  1. Load tenant-scoped user_sources from Supabase.
  2. Accept tenant-scoped source records delivered by the separate Reddit Browser Collector or by controlled fallback imports.
  3. Bank each accepted tenant-scoped source record into multiple reusable angles.
  4. Draft only one saved angle at a time into enabled platforms.
  5. Generate an Instagram image only when Instagram is enabled.
  6. Copy generated Instagram images into Cloudinary so queued posts use stable delivery URLs.
  7. Save each transformed item into the automation queue store.
  8. Publish only to enabled platforms.
  9. Save publish IDs and history in the automation history store.

Runtime Layout

  • config.ts: loads .env, exposes runtime config, and supports per-platform toggles.
  • src/agent.ts: cron scheduler and startup checks.
  • src/cli.ts: local operations like fetch, queue, status, and post-now.
  • src/server.ts: dashboard/API server on GUI_PORT.
  • src/automation-service.ts: shared API/CLI/cron automation service with readiness gates and SQLite locks.
  • src/supabase-worker.ts: OneClickPostFactory SaaS worker that polls Supabase agent_jobs, processes jobs by job.user_id, and writes tenant-scoped results back to Supabase.
  • src/supabase-client.ts: small server-side Supabase REST client that requires SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY or aliases SUPABASE_SECRET_KEY/SERVICE_ROLE_KEY, and CREDENTIAL_ENCRYPTION_KEY for the worker path.
  • src/tenant-credentials.ts: decrypts SaaS user_credentials.*_enc values using CREDENTIAL_ENCRYPTION_KEY.
  • src/content-engine.ts: shared source-bank / angle-bank / queue orchestration.
  • src/publish.ts: retired shared publish orchestrator. Threads and Instagram adapters fail closed and cannot reach Meta; canonical publication belongs to the workspace deterministic outbox workers.
  • src/runtime-policy.ts: runtime readiness checks, platform readiness, and automation gating.
  • src/control-plane.ts: single-install users, sessions, MFA/RBAC, billing state, runtime settings/secrets, and audit logs.
  • src/validators.ts: API request validation for auth, settings, and queue mutations.
  • src/cloudinary.ts: uploads generated Instagram image assets to stable Cloudinary delivery URLs.
  • src/http-client.ts: shared HTTP helper with timeout/error handling.
  • src/linkedin.ts: LinkedIn publisher using the UGC Posts API.
  • src/x.ts: X/Twitter publisher using OAuth 1.0a or OAuth 2.0 user-context auth depending on configured credentials.
  • src/threads.ts: read-only Threads credential and verification support; publication fails closed with legacy_meta_publication_disabled.
  • src/instagram.ts: read-only Instagram identity and verification support; publication fails closed with legacy_meta_publication_disabled.
  • src/facebook.ts: Facebook Group publisher using Graph API feed posts.
  • src/test-meta.ts: Meta diagnostics for identity, Page, Instagram linkage, Group access, and Threads account checks.
  • src/test-x.ts: X diagnostics for the configured auth mode and optional live-post smoke tests.
  • src/store.ts: SQLite-backed queue/history/source/angle/platform-state persistence with one-time legacy JSON import from the data/ directory.
  • src/ai.ts: source extraction, angle drafting, lightweight learning memory, GPT Image generation, and Cloudinary persistence handoff.
  • src/reddit.ts: Reddit fetcher for allowed subreddits.
  • content-os/: repo-ready prompt pack that defines source extraction, platform rules, quality checks, and banned phrasing.

Current Architecture Boundary

This codebase now has two runtime boundaries:

  1. Local single-install runtime:
  • APP_DATA_DIR points to one global runtime data directory.
  • Queue, history, source memory, angle memory, platform state, runtime settings, runtime secrets, and billing state are global for that installation.
  • Control-plane users are owner/operator/viewer accounts for the same installation, not separate SaaS customer tenants.
  • Billing is represented as one access state for the installation, not per tenant or per workspace.
  1. OneClickPostFactory SaaS worker runtime:
  • src/supabase-worker.ts polls Supabase agent_jobs when SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY or aliases SUPABASE_SECRET_KEY/SERVICE_ROLE_KEY, and CREDENTIAL_ENCRYPTION_KEY are configured.
  • SUPABASE_URL must point to the owner-managed OneClickPostFactory Supabase project that Lovable also uses; do not point the worker at a hidden managed project whose secret/service-role key and credential encryption key are unavailable.
  • Every SaaS job is processed by job.user_id.
  • SaaS reads/writes for profiles, user_credentials, user_sources, user_settings, queue_items, publish_history, source_records, angle_records, and worker_logs are scoped by job.user_id.
  • SaaS source ingestion also enforces source intent. user_sources rows declare provider, acquisition_mode, source_scope, target_author, allowed_subreddits, and allow_unfiltered_rss, but the active product direction is no longer Cloudflare Worker Reddit fetching.
  • There is no active built-in SaaS Reddit scraping path. The active direction is the user-installed OneClick Reddit Connector in /home/oneclickwebsitedesignfactory/reddit-user-connector; the main app still owns source records, OpenAI angle extraction after valid sources, queueing, publishing, logs, billing, source ownership checks, and the internal owner access override.
  • fetch_sources / refresh_queue must not treat Reddit public JSON, RSS, OAuth, Browser Run, Playwright browser login, or Devvit as the normal product path. When no usable source records or banked angles exist, the worker should return reddit_source_ingestion_unavailable without calling Reddit, OpenAI, queue creation, or publishing.
  • Reddit public JSON and Reddit RSS are legacy/quarantined paths. Keep compatibility metadata, fail-closed tests, and historical docs only where needed for old rows or diagnosis. Do not keep active fetch adapters or expose them as normal setup or recovery paths.
  • The old per-source Reddit OAuth credential path is not a product path. Reddit OAuth is not available for this project without new confirmed credentials and product approval; do not add tenant Reddit OAuth prompts or present OAuth as the fix.
  • Devvit is an archived/reference experiment for source collection because outbound domain approval blocked delivery. Do not present Devvit as an active product path unless a new explicit decision changes that.
  • Verified Cloudflare runtime truth for 2026-05-21: the last known good source-fetching flow ran on backend Worker version 3e2a598a-b8bc-461f-9a40-d65b3ad2e156 (likely commit fc53fcb Add OpenAI usage visibility and runaway protection) and used adapter reddit_public_json, not RSS. Public JSON returned HTTP 200, fetched 20 subreddit posts, accepted 17, rejected 3 with rejected_author_mismatch, reached OpenAI angle extraction, created angle records, created queue rows, and scheduled publishing later completed. Source rows had acquisition_mode = oauth, but that label was misleading and has been replaced with public_json for Reddit public JSON rows. The old per-source Reddit OAuth path is removed/quarantined; do not rewrite history by claiming RSS or OAuth was the May 21 success path.
  • RSS evidence is not productive product evidence: HTTP fetches happened, but persisted evidence shows zero accepted posts, source records, angle records, queue rows, publish rows, or OpenAI calls from RSS. Do not make RSS the default or recommend it as a fix.
  • Manual Reddit import exists as an advanced fallback, not the main product direction. Existing metadata-only source_records are not draftable shortcuts; queue fill depends on source records with enough source body becoming angle_records.
  • Before creating any new Reddit connector, service, or runtime, read docs/CODEX_WORKFLOW_GUARDRAILS.md.
  • SaaS credential values are decrypted with CREDENTIAL_ENCRYPTION_KEY.
  • SaaS billing entitlement is checked from Supabase profiles; the local SQLite billing state and local billing bypass do not grant SaaS worker entitlement. profiles.dev_access_until is only a time-limited dev/test override, and it must be presented as dev/test access rather than paid Stripe status. Permanent internal owner access lives in service-role-owned internal_access_overrides, keyed by immutable Supabase Auth user id, not email, and must not be represented as paid Stripe status.
  • Local SQLite remains acceptable for local admin/dev state, but it is not the SaaS tenant source of truth.

Known SaaS worker limitations:

  • Facebook Group publishing remains paused until the app/token/group permission path is verified.

Important Build Rule

This repo is authored in TypeScript with a split execution model: tsx for source-driven dev/operator commands and compiled dist/ output for the production service runtime.

  • Edit .ts files, not generated build output.
  • npm run dev, npm run fetch, npm run status, and similar operator commands run from source through tsx.
  • npm run build emits compiled output into dist/.
  • npm start and npm run start:pm2 run the compiled service from dist/src/agent.js.
  • The build copies public/ and content-os/ into dist/ for the compiled runtime.

Current Platform State

As of May 13, 2026:

  • Production SaaS publishing uses tenant-encrypted credentials from Supabase, not the old global .env platform tokens that were used by the local worker during early testing.
  • Stored encrypted credential fields only prove that a user saved values. They do not prove the platform token still works. UI and docs must say Stored, not verified until there is a recent read check, successful publish, or explicit verification result.
  • Meta/Threads auth failures such as OAuth code 190 / Failed to decode, LinkedIn INVALID_ACCESS_TOKEN or 401, and X 401 Unauthorized / unauthorized_client mean the tenant must reconnect that platform. Do not rewrite publisher payloads when read-only token checks already fail.
  • Failed publish rows should store a user-facing reconnect/payload message in queue_items.error_message and log safe diagnostics in worker_logs without tokens, full headers, or full response bodies.
  • Threads and Instagram posting are retired from this project. Approved publications must use scripts/threads-outbox-runner.mjs or scripts/instagram-publisher-outbox-runner.mjs in the workspace.
  • LinkedIn code has been merged from linkedin-agent-v4, but SaaS tenants must provide valid encrypted LinkedIn credentials before live-posting.
  • X is implemented as a first-class text-only platform. Production SaaS currently uses tenant-scoped OAuth 2.0 user-context credentials, verifies them with /2/users/me, and logs the safe auth mode as x_oauth2_user_context.
  • X OAuth 2.0 connect, callback, token persistence, refresh-token support, and portal-token import are implemented in src/x.ts, src/server.ts, and src/cli.ts.
  • X live-post smoke test succeeded as @JohnWOE15 on April 29, 2026: https://x.com/i/web/status/2049570569494958455.
  • Threads now uses its own token path through THREADS_ACCESS_TOKEN.
  • Threads uses read-only identity and verification endpoints here. This source tree contains no /me/threads_publish dispatch.
  • Facebook/Instagram Graph defaults were bumped to v25.0.
  • Instagram can now auto-discover the page-linked instagram_business_account and derive a Page access token from FACEBOOK_PAGE_ID + META_ACCESS_TOKEN.
  • Instagram generated images must be persisted to Cloudinary before an Instagram queue row is considered publishable; this avoids expiring OpenAI provider URLs in scheduled slots.
  • Automation readiness requires Cloudinary configuration when Instagram is enabled. The worker fails closed instead of queueing Instagram rows with temporary image URLs.
  • OpenAI image quota or billing hard-limit failures are normalized as openai_image_billing_blocked with stage instagram_image_generation; this is an Instagram media blocker, not a platform credential or publisher payload failure.
  • Reddit fetch success and OpenAI text angle-extraction failure are separate stages. If accepted Reddit posts are fetched but OpenAI text quota/billing/rate/model access blocks extractSourceBank, normalize the failure as openai_text_*, set stage angle_extraction, stop further extraction attempts for systemic failures, and finalize the job.
  • Accepted source records must be preserved before angle extraction. A source_records row without matching angle_records is not "done" and must not block future angle extraction after OpenAI billing/quota is fixed.
  • SaaS jobs must never stay running after terminal OpenAI text extraction failures. agent_jobs.result.summary must say Reddit succeeded, angle extraction failed, queue rows were not created, and what the next action is.
  • Cloudflare scheduled automation has three separate stages: enqueue due source fetches, enqueue banked-angle slot-fill jobs, and enqueue due publishes. Scheduled publish only publishes existing queue_items whose scheduled_for time is due; it must not invent content. Slot filling from existing angles is done by a scheduled refresh_queue job with fill_existing_angles_only=true, so it must not fall through into Reddit fetching when no angles are available.
  • Scheduled slot filling uses existing unused/in-progress, metadata-complete tenant angles and writes normal platform-specific queue_items with scheduled_for. Slot occupancy is user_id + platform + tenant-local date + slot_index; X slot 0 does not block Threads slot 0. Compute slot times in automation_timezone, then posting_timezone, then Europe/London, and store scheduled_for as UTC. If OpenAI drafting fails while filling a slot, the job must finalize and last_automation_result must explain the OpenAI blocker rather than retrying every minute.
  • Instagram image generation uses OPENAI_IMAGE_TIMEOUT_MS, separate from the generic HTTP_TIMEOUT_MS. Keep image timeouts finite, classify aborts as instagram_image_generation, and skip OpenAI image calls when no Instagram slot is open.
  • Queue retry behavior is safe: failed platforms no longer delete queued items.
  • Partial success is supported: one platform can succeed without forcing the whole slot to fail.
  • Source reuse is supported: a Reddit post is only exhausted when no banked angles remain.
  • Draft generation skips disabled platforms to save tokens.
  • Existing queued items can auto-hydrate missing drafts from stored source and angle memory when a platform is enabled later, including X.
  • CLI commands, local API routes, and local cron publishing all go through src/automation-service.ts, which applies the local automation gate and uses SQLite locks.
  • The SaaS worker path goes through src/supabase-worker.ts, uses Supabase agent_jobs, and does not use local SQLite for tenant queue/history/log/source state.
  • For SaaS publish regressions, first verify tenant credential decryption and read-only platform identity checks. If a tenant token is invalid but a global .env token still works, the fix is tenant reconnection and clearer error handling, not changing platform endpoint or payload contracts.

Current tracked operational mode in .env.example:

  • ENABLE_THREADS=true
  • ENABLE_INSTAGRAM=true
  • ENABLE_LINKEDIN=false
  • ENABLE_X=false
  • ENABLE_FACEBOOK=false

Important: config.ts has fallback defaults that enable Threads, Instagram, and Facebook if no env/runtime setting is present. The tracked .env.example pins Facebook off. Keep platform toggles explicit in real deployments.

This was done because:

  • Threads and Instagram are both confirmed working.
  • LinkedIn has been merged but not yet verified from this repo.
  • X remains off in the tracked example config, but live posting is confirmed when valid OAuth 2.0 user-context credentials are configured.
  • The Facebook Group still returns (#3) Missing Permission.

Meta Config Notes

Do not mix these IDs up:

  • FACEBOOK_USER_ID: the Facebook user account ID returned by /me
  • FACEBOOK_PAGE_ID: the Facebook Page ID returned by /me/accounts
  • INSTAGRAM_ACCOUNT_ID: must be the Page-linked instagram_business_account ID, not a Facebook user ID
  • FACEBOOK_PAGE_ACCESS_TOKEN: optional override for Instagram publishing; otherwise the app derives it from the configured Page
  • THREADS_USER_ID: the Threads account ID returned by graph.threads.net/me
  • FACEBOOK_GROUP_ID: the Group object ID used for /feed

Do not commit real platform IDs, tenant IDs, account IDs, email addresses, or secrets from .env.

X Config Notes

  • Production SaaS X publishing uses OAuth 2.0 user-context tenant credentials: x_client_id_enc, x_client_secret_enc, x_oauth2_access_token_enc, and x_oauth2_refresh_token_enc.
  • The OAuth2 Client ID must be used exactly as shown in X Developer Console. It may naturally look encoded or decode to a string containing :1:ci; do not decode it, transform it, or reject it for that reason. Do not use the API Key / Consumer Key in its place.
  • The production OAuth callback is https://oneclickpostfactory.woeinvests.workers.dev/auth/x/callback; /auth/x/start must create state + PKCE and /auth/x/callback must exchange the code immediately.
  • The authorize URL uses the exact decrypted client_id through URLSearchParams; the token and refresh endpoints are the only place base64 is required: Authorization: Basic base64(client_id + ":" + client_secret), with form bodies that do not include client_secret.
  • Supabase stores credential ciphertext, which can look base64-like. Never classify the raw database column as the OAuth client ID, and never infer correctness by base64-decoding the decrypted Client ID.
  • Do not add OAuth 1.0a tenant support unless it becomes an explicit product decision. A working old local .env token does not prove a SaaS tenant credential is valid.
  • X_CLIENT_ID, X_CLIENT_SECRET, and X_REDIRECT_URI support the OAuth 2.0 user-context connect flow. X_OAUTH2_CLIENT_ID and X_OAUTH2_CLIENT_SECRET are accepted aliases for the labels used in the X developer portal.
  • X_OAUTH2_ACCESS_TOKEN and X_OAUTH2_REFRESH_TOKEN are supported for v2 posting after OAuth connect.
  • Do not use app-only bearer tokens for posting.
  • X publishing is text-only.
  • Auth mode priority in src/x.ts is OAuth 2.0 refresh-token config, then a static OAuth 2.0 access token, then OAuth 1.0a credentials for local/global runtime compatibility only.
  • The OAuth 2.0 path authenticates with /2/users/me and posts through /2/tweets.
  • SaaS publish jobs must fail cleanly with needs_reconnect if /2/users/me returns 401 Unauthorized or refresh returns unauthorized_client; do not leave X jobs in running.
  • The dashboard owner route /auth/x/start begins the OAuth 2.0 flow, and /auth/x/callback persists the returned access and refresh tokens into runtime secrets.
  • npm run import-x-oauth2 imports user-context OAuth 2.0 access/refresh tokens generated directly in the X developer portal, saves them into encrypted runtime secrets, validates /2/users/me, and only then clears X draft-only mode.
  • Queued publishing in src/publish.ts only attempts X when OAuth 1.0a credentials or X_OAUTH2_ACCESS_TOKEN are present. After OAuth 2.0 connect succeeds, the persisted access token satisfies this requirement.
  • If X returns a credits/access-tier publish entitlement error, the runtime switches X into temporary draft-only mode.

Platform Toggles

These env vars control which platforms are active:

  • ENABLE_THREADS
  • ENABLE_INSTAGRAM
  • ENABLE_LINKEDIN
  • ENABLE_X
  • ENABLE_FACEBOOK

Behavior:

  • Disabled platforms are skipped by src/publish.ts.
  • A slot is considered complete if every enabled platform succeeds.
  • Disabled platforms do not keep a queue item stuck in retry status.
  • The automation gate in src/runtime-policy.ts also requires an owner account, active billing access, core credentials, and readiness for each enabled platform.

Commands That Matter

In this workspace, run Node/npm commands inside the Linux shell and load NVM first:

source ~/.nvm/nvm.sh && npm run build

package.json is strict JSON, so script commentary belongs here and in the README command table rather than as inline comments inside the scripts object.

  • npm run build: compile TypeScript to dist/
  • npm run typecheck: TypeScript validation without emitting JS
  • npm run ci: typecheck, build, compiled smoke test, and local security regression suite
  • npm run dev: start agent (cron + dashboard) from TypeScript through tsx
  • npm run worker:supabase: start only the Supabase SaaS worker loop
  • npm run test-meta: diagnose Meta setup
  • npm run test-x: validate the configured X auth mode and optionally live-post a test update
  • npm run test: run the local security hardening regression suite
  • npm run fetch: fill empty queue slots from Reddit
  • npm run queue: inspect queued content and publish IDs/errors
  • npm run status: show which slots are filled
  • npm run memory: inspect source/angle memory counts
  • npm run history: inspect recent publish history
  • npm run post-now: immediately post every queued slot to enabled platforms
  • npm run import-x-oauth2: import X OAuth 2.0 user-context tokens generated in the X developer portal
  • npm run backup: snapshot APP_DATA_DIR into backups/
  • npm run restore -- --from <backup-dir>: restore a backup into APP_DATA_DIR
  • npm run smoke:dist: verify the compiled CLI/runtime wiring
  • npm start: start compiled agent (cron + dashboard) from dist/

Content OS

The repo includes a prompt pack in content-os/:

  • SYSTEM.md
  • PLATFORM_RULES.md
  • QUALITY_CHECKS.md
  • BANNED_PHRASES.json

src/ai.ts uses this OS in practice by:

  • extracting a source summary plus multiple reusable angles first
  • drafting natively per platform from one selected angle instead of rewriting line by line
  • using lightweight learning notes from recent history to avoid repetition
  • checking banned phrases
  • scoring specificity, human tone, and platform fit before finalizing

For product direction around drafting quality, strategy profile, and future learning boundaries, read docs/CONTENT_STRATEGY_ROADMAP.md before changing angle extraction or drafting prompts.

Data Files

  • data/automation.sqlite: queue, history, sources, angles, platform-state, and automation locks
  • data/control-plane.sqlite: users, sessions, billing, runtime config/secrets, and audit logs
  • data/control-plane.key: generated local encryption key when APP_ENCRYPTION_KEY is not provided; production should use APP_ENCRYPTION_KEY
  • data/queue.json, data/used_ids.json, data/history.json, data/sources.json, data/angles.json, data/platform-state.json: legacy import sources retained as runtime artifacts/backups if present
  • data/agent.log: dashboard log feed
  • backups/: default backup output directory for npm run backup

These files are local runtime state, not source code, and not the source of truth for SaaS tenant data.

Known Risks

  • Instagram posting depends on the currently accessible Page continuing to expose the linked instagram_business_account.
  • LinkedIn posting still needs a live validation run from this repo even though the publish slice was ported from a working standalone project.
  • X posting is confirmed for the current OAuth 2.0 user-context app, but future failures can still happen if credentials expire, the X app permissions change, or credits/access tier are removed.
  • Facebook Group posting still depends on app/token/group permissions that are not fixed in code.
  • OpenAI image outputs can be temporary or base64-only depending on model/API path. Instagram queue rows must store Cloudinary URLs, not provider URLs.
  • Cloudinary must be configured for Instagram queues so generated image assets are copied to a stable delivery URL immediately after generation.
  • Compiled output can go stale if npm run build is skipped before npm start or npm run start:pm2.

Good First Checks When Something Breaks

  1. Run npm run build.
  2. Run npm run test-meta.
  3. Run npm run test-x if X is enabled.
  4. Run npm run status to see whether X is in draft-only mode.
  5. Run npm run queue.
  6. Inspect data/automation.sqlite, data/control-plane.sqlite, and data/agent.log.
  7. Confirm .env platform toggles match intended behavior.
  8. For SaaS worker issues, inspect Supabase agent_jobs and worker_logs for the affected user_id.

If You Need To Change Publishing Behavior

Start here:

  • src/publish.ts for platform orchestration
  • src/agent.ts for cron and startup rules
  • src/cli.ts and src/server.ts for manual and API posting behavior

For Meta diagnostics:

  • src/test-meta.ts

For Threads-specific work:

  • src/threads.ts

For X-specific work:

  • src/x.ts