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 分