Post Engineer

io.github.Ryan-Carloso由 GitHub 帳號 Ryan-Carloso 發布v1.32.0更新於 Nov 6, 2026

Create AI video personas and schedule videos to YouTube, Instagram, LinkedIn and Bluesky.

概覽

AI 產生的概覽

建立 AI 影片人設,生成短影音並排程自動發佈到 YouTube、Instagram、LinkedIn 與 Bluesky。

可以做什麼
  • 建立與管理人設:設定頭像、聲音、語言與領域,修改人設,並維護每個人設的圖片庫
  • 挑選聲音與面孔:瀏覽內建聲音與面孔,註冊並試聽克隆聲音,刪除克隆
  • 生成與驗證影片:不花 token 地驗證分段腳本,生成人設影片,輪詢進度,取得最終連結,取消任務
  • 排程與編輯貼文:檢視待發與歷史時段,重新命名或刪除待發時段,取消整份排程
  • 連結與管理社群帳號:列出帳號,連結 YouTube、Instagram、LinkedIn 或 Bluesky,設定人設預設發佈帳號,中斷連結
  • 追蹤成本與成效:查詢 token 餘額與流水,讀取單支影片數據,預覽並執行刪除人設
適用情境
適合讓助理端到端跑完短影音流程:撰寫並驗證腳本、生成影片、排入已連結社群帳號的發佈佇列。也適合在同一個工作階段核對 token 花費與已發佈影片的表現。
執行需求
以 npm 套件 post-engineer-mcp 透過 npx 在本機執行,需要 Node.js。需要託管平台產生的 API 金鑰,放在環境變數 POST_ENGINEER_API_KEY;可用 POST_ENGINEER_API_URL 指向自架執行個體。發佈功能需先在平台以 OAuth 連結社群帳號,或提供 Bluesky 應用程式密碼。生成影片會消耗預付 token。
安裝前請注意
生成會消耗預付 token,且每次生成都綁定自動發佈,一次呼叫可能直接公開發佈而無需另外確認。刪除人設無法復原,且待發時段已預付的 token 不會退還。連結 Bluesky 需提供應用程式密碼,OAuth 連結會把發佈權限授予助理。上傳素材會存成暫存引擎資源,僅保留一小時。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Post Engineer,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

Post Engineer

[CI] [License: Apache 2.0] [npm]

🇧🇷🇵🇹 Leia em Português

Create AI video personas that generate and publish short videos on autopilot.

Give a persona a niche and a voice; Post Engineer writes the script, generates the voiceover, assembles the video, and publishes it to your social accounts on a schedule. Manage everything from the web dashboard, drive it programmatically through the API, or let your AI agent run it through the MCP server — all from this one monorepo.

Post Engineer is open source under the Apache License, Version 2.0. Self-host the whole platform on your own VPS following docs/SELF_HOSTING.md, or use the hosted version and buy tokens. Lip sync (LongCat) runs on Modal.com: Modal's Starter plan includes $30/month in compute credits (see modal.com/pricing). We are not Modal: this free-credit offer is theirs and can end at any time. Light self-hosted usage can fit inside the free credits while it lasts — usage beyond that is billed by Modal.

Prefer the hosted version? The same platform runs at post-engineer.com — you can skip the local setup and connect the MCP server straight to your account (see MCP server).

What it does

  • AI personas — persistent characters with a voice, face, language, and niche. Optional lip-synced intro clips for a human-like presenter, plus a persona image library for face consistency across videos.
  • Automatic video generation — topic in, HD short video out: script (LLM), voiceover (TTS), subtitles, stock footage, and background music, assembled into a finished video.
  • Scheduling & publishing — queue posts for YouTube, Instagram, LinkedIn, and Bluesky; track upcoming and past posts with per-account status.
  • Dashboard — personas, posts history, connected social accounts, API keys, and token billing in one Next.js app.
  • Agent control — an MCP server (apps/mcp/) exposes the same capabilities to AI agents (Claude, Cursor, Codex, OpenCode): create personas, generate videos, check status, and schedule posts.

Repository layout

This is a pnpm/Nx monorepo — web, engine, and MCP live together:

text
apps/web/       Next.js 15 dashboard + API routes (TypeScript)apps/engine/    Python/FastAPI video-generation engineapps/mcp/       MCP server for AI agents (npm package: post-engineer-mcp)

The web app owns authentication, personas, scheduling, OAuth, billing, and the API. It calls the engine over HTTP (authenticated with a shared secret, MONEYPRINT_API_SECRET) for video generation, TTS, and publishing. The MCP server talks to the web API with a user API key. Postgres (via Supabase) is the system of record: personas, schedules, OAuth tokens (encrypted at rest), API keys, and token balances.

Prerequisites

  • Node.js 20+ and pnpm 10 (npm install -g pnpm or via corepack)
  • Python 3.11–3.12 and uv (curl -LsSf astral.sh/uv/install.sh | sh)
  • ffmpeg on PATH (the engine shells out to it via moviepy)
  • A Supabase project (hosted at supabase.com, or self-hosted) with a Postgres database
  • API keys for the external services you plan to use (see the table below)

Local setup from scratch

1. Install dependencies

bash
git clone https://github.com/Ryan-Carloso/post-engineer.gitcd post-engineerpnpm install

2. Set up Supabase

  1. Create a project at supabase.com (or point at your self-hosted instance).

  2. In the Supabase SQL editor, apply the database scripts. The persona image library script lives in this repo: supabase/migrations/002_persona-images.sql (manual-apply by design).

    Need help? If you are setting up a fresh project and need the current schema snapshot, reach out: Email: [email protected] · WhatsApp: +351 962 248 268

3. Create the three env files (never commit these)

There are exactly three env files by design — copy each from its example and fill in your own values:

bash
cp apps/web/.env.example apps/web/.envcp apps/engine/.env.example apps/engine/.envcp apps/engine/config.example.toml apps/engine/config.toml
FilePurposeKey variables
apps/web/.envWeb app + API routesNEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY (server-only), TOKEN_ENCRYPTION_KEY (generate: openssl rand -base64 32), MONEYPRINT_API_SECRET, MONEYPRINT_API_URL, OAuth client ids/secrets + redirect URIs, Stripe keys, MCP_OAUTH_PRIVATE_KEY_PEM
apps/engine/.envEngine runtimeMONEYPRINT_API_SECRET (must be identical to the web value), SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY (for the in-process fill scheduler), mandatory R2_ACCOUNT_ID + R2_ACCESS_KEY_ID + R2_SECRET_ACCESS_KEY (final-video archive), optional DISCORD_WEBHOOK_URL, optional POSTHOG_API_KEY (error tracking, analytics funnel, AI observability traces)
apps/engine/config.tomlEngine behavior configLLM/TTS/stock-footage provider keys (OpenAI-compatible, Pexels, Pixabay, …), listen_port (default 8080)

The app fails fast on missing variables (no silent fallbacks) — see apps/web/.env.example for the full documented list.

4. Register OAuth apps (for social publishing)

To connect YouTube, Instagram, or LinkedIn accounts you need OAuth apps whose redirect URIs exactly match the values in apps/web/.env:

  • Google / YouTube — Google Cloud Console: create an OAuth 2.0 Client ID, add the authorized redirect URIs from GOOGLE_REDIRECT_URI / GOOGLE_REDIRECT_URI_LOCAL, and fill in GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET.
  • Instagram — Meta for Developers: a Business app with the Instagram product (instagram_business_basic and instagram_business_content_publish permissions); set INSTAGRAM_CLIENT_ID / INSTAGRAM_CLIENT_SECRET and register INSTAGRAM_REDIRECT_URI.
  • LinkedIn — LinkedIn Developers: create an app, register LINKEDIN_REDIRECT_URI, and set LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET.
  • Threads — (Threads API). The "Access the Threads API" use case of the Meta app has its own App ID and App Secret (THREADS_CLIENT_ID / THREADS_CLIENT_SECRET), different from Instagram's. Add the Threads product (threads_basic, threads_content_publish and threads_manage_insights permissions) and register THREADS_REDIRECT_URI.
  • Bluesky uses an app password entered in the dashboard (encrypted at rest) — no OAuth app registration needed.

5. Stripe setup (token billing)

  1. Create a Stripe account and get the secret + publishable keys (STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY).
  2. Create the token-pack products and put the price IDs in STRIPE_PRICE_PACK_10 / _50 / _100.
  3. Add a webhook endpoint pointing at /api/billing/webhook and store the signing secret in STRIPE_WEBHOOK_SECRET. The webhook handler verifies the signature and is idempotent.

6. Generate the self-managed secrets

These are yours to create — nothing to register:

bash
# Web <-> engine shared secret (identical in both .env files)openssl rand -hex 32
# OAuth token encryption key (AES-256-GCM)openssl rand -base64 32
# MCP OAuth signing key (PEM, \n-escaped into MCP_OAUTH_PRIVATE_KEY_PEM)openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt

Running

bash
pnpm dev:web      # dashboard at http://localhost:3434pnpm dev:engine   # video engine API at http://127.0.0.1:8080 (see /docs there)

Useful commands (all verified against package.json / project.json):

bash
pnpm test        # all tests (web + engine + MCP, via Nx)pnpm lint        # ESLint (web + MCP) + ruff (engine)pnpm typecheck   # tsc --noEmit (web + MCP) + engine checkspnpm build       # production build of the web apppnpm graph       # Nx project graph

Per-app details: apps/web/README.md, apps/engine/README.md, apps/mcp/README.md.

MCP server

The MCP server (apps/mcp/, npm package post-engineer-mcp) lets AI agents create personas, generate videos, check generation status, and schedule posts on your account. You don't need to clone this repo to use it against the hosted platform:

json
{  "mcpServers": {    "post-engineer": {      "type": "local",      "command": ["npx", "-y", "post-engineer-mcp@latest"],      "environment": {        "POST_ENGINEER_API_KEY": "<MY_API_KEY>"      }    }  }}

Generate <MY_API_KEY> at post-engineer.com/recipes. The @latest tag matters: npx reuses its cached copy without checking for updates. To point the server at your own self-hosted instance instead, set POST_ENGINEER_API_URL (see apps/mcp/README.md for the full tool list, OpenCode timeout tips, and local development).

External services you must key yourself

ServiceRequired?What forWhere to get it
SupabaseYesPostgres, auth, storagesupabase.com or self-hosted
Google OAuth appFor YouTube publishingOAuth client id/secretGoogle Cloud Console
Instagram (Meta) appFor Instagram publishingOAuth client id/secretdevelopers.facebook.com
LinkedIn appFor LinkedIn publishingOAuth client id/secretdeveloper.linkedin.com
StripeFor token billingSecret/publishable keys, price IDs, webhook secretstripe.com
LLM provider key or OllamaFor script generationOpenAI-compatible key, or run Ollama locallyYour provider / ollama.com
Pexels / Pixabay / Coverr / TwelveLabsFor stock footageAPI keysRespective developer portals
MONEYPRINT_API_SECRETYesWeb ↔ engine authGenerate yourself (openssl rand -hex 32)
TOKEN_ENCRYPTION_KEYYesEncrypts OAuth tokens at restGenerate yourself (openssl rand -base64 32)
MCP_OAUTH_PRIVATE_KEY_PEMFor MCP server OAuthSigns MCP tokensGenerate yourself (EC key, see above)
PostHogOptionalError tracking + product analyticsNEXT_PUBLIC_POSTHOG_KEY (browser), POSTHOG_API_KEY (server), NEXT_PUBLIC_POSTHOG_HOST — see apps/web/.env.example
Discord webhookOptionalFill-schedule notificationsDiscord channel settings
ZAI_API_KEYCI onlyAI code-review workflowNot a runtime dependency

Support & Contact

Questions, get in touch:

License

Licensed under the Apache License, Version 2.0 — see CONTRIBUTING.md and SECURITY.md.

Credits

Video engine derived from MoneyPrinterTurbo (MIT © 2024 Harry), extended with persona lip-sync, background-music catalog, batch queue, scheduling, and publishing.

來源:README.md,提交 7beca2b

工具

41

create_persona

Create a new AI persona with avatar, voice, language, and niche prompt. avatarUrl is REQUIRED: every persona has a face. A video without a face is a per-video choice — set options.faceless on generate_persona_video.

輸入參數name必填nicheimagesvoiceId必填languageavatarUrl必填faceQualityvideoAspectvideoLayoutscriptPromptparagraphNumberimagePrimaryIndex

list_personas

List all existing personas for the authenticated user.

無需輸入參數。

list_voices

List persona voices (voiceId options): 65 English house voices, each with a text description of how it sounds plus gender/accent/kind, and your ready clones. Filter with gender, origin and search (e.g. "deep calm").

輸入參數genderoriginsearch

list_cloned_voices

List per-user Chatterbox voice clones (status, voice_id when ready).

無需輸入參數。

create_cloned_voice

Register a voice clone from a short HTTPS audio URL (requires explicit consent).

輸入參數consent必填denoise必填audioUrl必填language必填

get_cloned_voice

Get one voice clone by id: its status (poll after create_cloned_voice until ready or failed) and voice_id once ready.

輸入參數cloneId必填

delete_cloned_voice

Delete one of your voice clones by id.

輸入參數cloneId必填

preview_cloned_voice

Synthesize a short MP3 preview for a ready voice clone, optionally speaking your own text (returns base64 audio; 10 previews per day).

輸入參數textcloneId必填language

list_faces

List default/stock persona faces (avatar options). Each face includes id, url, name, gender, age (single number, not a range), ethnicity, hair, and description in English so you can pick without seeing the photo. Pass a face url as avatarUrl when calling create_persona.

無需輸入參數。

update_persona

Update an existing AI persona (only the provided fields change).

輸入參數namenichevoiceIdlanguageavatarUrlpersonaId必填videoAspectvideoLayoutscriptPromptparagraphNumber

list_social_accounts

List connected social accounts (YouTube, Instagram, LinkedIn, Bluesky) with the account IDs needed for posting.

無需輸入參數。

get_persona_publishing_accounts

List a persona's default publishing accounts per provider. generate_persona_video falls back to these when no account IDs are passed; explicit IDs always win.

輸入參數personaId必填

set_persona_publishing_accounts

Replace a persona's default publishing accounts (per provider). Account IDs come from list_social_accounts. Providers mapped to empty arrays keep no defaults.

輸入參數accounts必填personaId必填

connect_account

Connect a social account. For youtube/instagram/linkedin: returns an authorization URL — the user must open it in a browser and authorize, then the account connects automatically (verify with list_social_accounts). For bluesky: connects directly with handle + appPassword (app password, not the main account password).

輸入參數handleprovider必填appPassword

list_schedules

List all automation schedules for the authenticated user.

無需輸入參數。

list_posts

List upcoming (scheduled) and past (published/failed) posts across all connected accounts. Returns two lists: upcoming slots and recent results, each with id, slot_at, status (awaiting|generating|ready|publishing|published|failed), title, schedule_id, task_id, progress (0-100: 0 for awaiting, 100 for ready/publishing/published, live engine progress for generating, last known for failed), stage (engine pipeline stage like "lipsync" while generating, "done" when complete, null otherwise), queuePosition/queueTotal (1-based position among the schedule's awaiting+generating slots; awaiting slots only, null otherwise). Failed slots also carry error and retryable (boolean: true when the failure looks transient). Use when the user asks about their posts — what is coming next, what already went out, how far a video has generated, where it sits in the queue, or why a post failed. Combine with list_schedules or list_social_accounts when persona/account names are needed.

輸入參數limit

cancel_schedule

Cancel (delete) an automation schedule by its schedule ID. Unpublished slots (pending, generating, or ready) are removed so they cannot publish, and their prepaid tokens are refunded. A post already publishing or already published is not recalled. Use list_schedules to find the ID.

輸入參數scheduleId必填

get_slot

Get one scheduled post's full detail by slot ID: status, title, scheduled time, generation progress, its schedule (providers, publish mode) and persona. Use the slotId from list_posts.

輸入參數slotId必填

update_slot_title

Edit the title text of one scheduled post. Only a slot that has not started generating (status pending/awaiting) can be edited — published or mid-flight slots are rejected.

輸入參數title必填slotId必填

delete_slot

Delete one scheduled post slot. Only pending (awaiting) or failed slots can be deleted; published or mid-flight slots are rejected. The schedule's last slot cannot be deleted — use cancel_schedule to delete the whole schedule instead.

輸入參數slotId必填

list_video_generations

List the video generation history (newest first): one row per generated video with status, error code, token refund flag, and timestamps. Use get_video_generation for one row's detail.

輸入參數limit

get_video_generation

Get one video generation's detail by generation ID (the generationId field from list_video_generations).

輸入參數generationId必填

cancel_video_task

Cancel/remove one of your video tasks on the generation engine by task id (from get_video_status, get_video_task_progress, get_post or list_posts). Only the caller's own task can be cancelled; a persona-scoped API key can only cancel tasks belonging to its personas. Unknown or already-finished tasks return an error. No token refund is issued by this call.

輸入參數taskId必填

get_post

Get one post's full detail by id, whichever kind it is: a scheduled slot (kind "slot") or a direct video generation (kind "generation"). The server resolves the id, so there is no need to guess between get_slot and get_video_generation. Both kinds carry live generation progress while the video is still generating.

輸入參數id必填

get_post_metrics

Get per-video performance snapshots (views, likes, comments, saves) of published posts for one window (24h or 7d), newest first, optionally filtered by persona, account, provider (network) and script angle. Returns rows plus truncated (true when more rows exist than the server cap).

輸入參數anglewindow必填accountpersonaprovider

list_token_transactions

List the token ledger: every token credit and debit (purchases, generation spends, refunds), newest first, with amounts, reasons, and the related generation when there is one. Use to audit where tokens went.

輸入參數limitoffset

get_persona_delete_preview

Preview what deleting a persona would remove: counts of schedules, upcoming/published/failed slots, generated videos and library images, plus per-video download links (read-only, never mutates). Call before delete_persona.

輸入參數personaId必填

delete_persona

DESTRUCTIVE and irreversible: delete a persona with all its schedules, slots, generated videos, and image library. No token refunds — prepaid tokens for pending slots are forfeited. Call get_persona_delete_preview first.

輸入參數personaId必填

disconnect_account

Disconnect a social account by provider and account ID (the IDs from list_social_accounts). The account stops being available for publishing.

輸入參數provider必填providerAccountId必填

get_token_balance

Get the prepaid token wallet balance. Check before triggering video generation, which costs tokens.

無需輸入參數。

generate_persona_video

Generate and schedule ONE persona video from a complete script. Product rule: generation is always tied to publishing — one call generates the video AND schedules its automatic publish; there is no orphan generation. Send the full script as segments (first hook, then body, last CTA; 60-200 words total, body at least 30, CTA 5-20, hook 15-40 words ending with terminal punctuation) — no LLM expands it. title is the slot label for this video. Pass providers plus per-provider account IDs (see list_social_accounts), startAt (ISO datetime, wall-clock in timezone when naive), times (HH:MM, 24h), and timezone. Set mode to 'asap' to publish the video the moment its generation finishes instead of scheduling it — startAt and times must NOT be set then. The slot must be 3h-30d ahead. idempotencyKey is generated automatically; reuse it to retry without generating twice (replayed: true). Poll the slot's taskId with get_video_task_progress.

輸入參數modetimestitle必填optionsstartAtlanguagesegments必填timezonepersonaIdproviders必填idempotencyKeyblueskyAccountIds另有 3 個

validate_script

Validate a segments script mechanically, without spending tokens: hook first, CTA last, exactly one of each, no placeholders/links/markdown/emoji/stage directions, hook terminal punctuation, word limits (total 60-200, body at least 30, CTA 5-20, hook 15-40 words before TTS). Returns the word counts and the hook window (8-12 seconds spoken). Measured hook duration is pending a hook-only TTS endpoint and is reported as pending, never invented.

輸入參數segments必填

upload_media

Upload a local image for a video segment and get a mediaId to use in a segment's media field (generate_persona_video). The file is stored as a temporary engine asset (1 hour TTL, same flow as debug uploads): upload shortly before generating, since a scheduled slot far in the future may outlive the asset and the segment then renders without it. JPG/JPEG, PNG, or WebP only, max 8MB (the engine photo limit). The real content type is validated, not the extension. Returns only the mediaId — never a local path or asset URL.

輸入參數path必填

check_media

Mechanically check one public image URL for a segment, without an LLM and without judging relevance (the agent, who sees the image, decides relevance). Returns exactly one code: ok, blocked_403 (the host blocked the SSRF-safe download — retry with upload_media bytes fetched on your own machine), not_image, too_small, too_large, timeout, or face_detected. No fake User-Agent or Referer is sent to bypass hotlink blocks, and private addresses are never fetched. The URL never appears in the output.

輸入參數url必填

get_video_status

Check the generation status and fetch final video URLs for a taskId.

輸入參數taskId必填

get_video_task_progress

Poll one video task for its machine-readable progress: returns {task_id, state, progress, stage, error}. error is the engine failure reason when state is -1 (failed), null otherwise. Use for the video after generate_persona_video; prefer this over get_video_status when only progress matters.

輸入參數taskId必填

list_persona_images

List the image library of a persona (up to 10 tagged images). Each entry has id, tag, description, and is_primary. Use the ids with generate_persona_video options.imageId to force a specific image for the video.

輸入參數personaId必填

add_persona_image

Add an image to a persona image library from a local file path (JPG/JPEG, PNG, or WebP, max 10MB). Optional tag and description drive the deterministic per-video image selection. The server rejects full libraries (10 max).

輸入參數tagpath必填isPrimarypersonaId必填description

update_persona_image

Update a persona library image tag, description, or primary flag.

輸入參數id必填tagisPrimarydescription

remove_persona_image

Remove an image from a persona image library.

輸入參數id必填

publish_video_direct

Publish a ready-made video file directly to social accounts — no generation, no schedule. Reads a local video (.mp4 or .mov, max 2GB) and posts it immediately via POST /api/upload-content. YouTube requires title, description, tags, and privacyStatus; Instagram/Bluesky/LinkedIn require caption.

輸入參數tagstitlecaptionprovider必填videoPath必填accountIds必填descriptionprivacyStatus

版本歷史

1
  1. v1.32.0最新Oct 30, 2026

評分

已滿足適用檢查項的 90%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分
  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分