No description
  • JavaScript 77.9%
  • CSS 13.2%
  • HTML 7.4%
  • Batchfile 1.4%
  • Dockerfile 0.1%
Find a file
2026-07-24 16:29:59 +02:00
build Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
electron Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
public Add env variable for com or red + add direct delete option from app to site 2026-07-22 20:55:08 +02:00
src Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
.dockerignore Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
.env.example Add env variable for com or red + add direct delete option from app to site 2026-07-22 20:55:08 +02:00
.gitignore Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
build-win.bat Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
CLAUDE.md Update .md files 2026-07-24 16:29:59 +02:00
Dockerfile Initial commit 2026-07-20 19:49:34 +02:00
package-lock.json Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
package.json Add windows install builder (Electron frontend) 2026-07-23 20:33:28 +02:00
README.md Update .md files 2026-07-24 16:29:59 +02:00

civitai-scheduler

A personal, self-hosted scheduler for Civitai (civitai.com / civitai.red). Queue up image/video posts with a title, tags, and a publish time, and a background job auto-publishes them through your own account when the time comes — no browser tab needs to stay open.

It works by driving Civitai's own MCP server (https://mcp.civitai.com/mcp), which is the officially supported way to post programmatically — the plain REST API (civitai.com/api/v1/...) is read-only.

How it works

 you (browser) ──POST /api/posts──▶ Express server ──▶ SQLite queue (data/scheduler.db)
                                                              │
                                                   node-cron polls every minute
                                                              │
                                                              ▼
                                          uploads images, then calls create_post
                                          on the Civitai MCP server (your API key)
                                                              │
                                                              ▼
                                                  post goes live on civitai.red

Everything — the API key, the queue, the scheduler — lives on the server side. The web UI (a little "departures board") is just a thin client for adding posts and watching their status.

Setup

  1. Get a Civitai API key. civitai.com (or civitai.red) → Account Settings → API Keys. A key with the MediaWrite scope is enough to create posts; a "Full" key is simplest if you don't want to think about scopes.

  2. Install dependencies:

    npm install
    
  3. Configure:

    cp .env.example .env
    # then edit .env and paste your key into CIVITAI_API_KEY
    
  4. Verify the connection and check tool schemas before trusting it with a real post:

    npm run inspect-tools
    

    This prints the live input schema for create_post / upload_image / publish_post / whoami straight from Civitai's server, plus your account status. Run this again any time scheduling starts failing with an argument-validation error — Civitai can evolve these schemas, and src/mcpClient.js is only as correct as the last time someone checked it against them. (It's already been caught once: upload_image expects data/contentType for base64 uploads, not base64/mimeType as Civitai's public docs imply — fixed in src/mcpClient.js.)

    Note: whoami's account-status lookup (user.getSelfStatus) currently errors on Civitai's end (Invalid input) even with a perfectly valid key — this is a bug on their server, not something wrong with your setup. Because of that, the app's own connection check (below) doesn't rely on whoami.

  5. Run it:

    npm start
    

    Then open http://localhost:4173.

    Or run it as a Windows desktop app instead — see Windows desktop app below.

The connection status pill

The dashboard header shows whether your API key is working. Since whoami is unreliable server-side (see above), the check instead uploads a throwaway 1×1 pixel image through the same upload_image tool real posts use — a green pill means the key can actually authenticate and write, not just that the server responded.

That also means it's a real (tiny, orphaned) write against your Civitai account, so it's not polled automatically — it only runs once when the dashboard loads, and whenever you click "recheck". A red pill means the key is likely invalid or expired.

Keeping it running unattended

This is the part that can't live in a browser tab or a static page — it needs a process that's alive at the scheduled time. Options, roughly easiest first:

  • A cheap always-on VPS or a Raspberry Pi / spare machine at home: install Node, npm start, keep it alive with pm2 (pm2 start src/server.js) or a systemd unit.
  • Docker: docker build -t civitai-scheduler . then docker run -d -p 4173:4173 --env-file .env -v $(pwd)/data:/app/data civitai-scheduler.
  • A small hosting platform (Railway, Fly.io, Render, etc.) — deploy the Dockerfile, attach a persistent volume for /app/data, set the env vars from .env.example in their dashboard.
  • The Windows desktop app (see below) — installs a tray-resident app that can auto-launch at Windows login, so the scheduler survives reboots without a terminal or Docker container to babysit.

If you only run this on your laptop and close it, scheduled posts simply won't fire until you reopen it — node-cron also does a catch-up check on startup, so anything overdue publishes right away rather than being skipped.

Windows desktop app (Electron)

The same app also ships as a Windows desktop app — a tray-resident Electron wrapper around the exact same Express server, SQLite queue, and scheduler described above. No separate setup, no browser tab, no terminal window.

Run it in dev mode:

npm run electron:rebuild   # one-time: rebuild better-sqlite3 for Electron's ABI
npm run electron:dev

Build a Windows installer:

build-win.bat

Produces an NSIS installer under release/ (gitignored — it's a build artifact, not something to commit) and automatically restores better-sqlite3 to the plain-Node ABI afterward, so npm start/ npm run dev keep working without any manual fixup (see the gotcha below for why that's needed). Run it from a real cmd.exe or by double-clicking it in Explorer — not from a Git-Bash/MSYS shell, which mangles the /c flag it needs internally.

Under the hood this just runs npm run dist:win plus the ABI-restore steps; you can still run npm run dist:win directly if you're going to rebuild for Electron again right after anyway (e.g. repeated electron:dev testing).

What's different from the plain npm start flow:

  • Configuration lives in a Settings window, not a hand-edited .env file. On first launch (or whenever no API key is set), a Settings window opens automatically — fill in your Civitai API key and the rest, hit Save, and confirm the restart it asks for. Everything is written to %APPDATA%\civitai-scheduler\.env, separate from any repo-root .env you use for npm start.
  • Close-to-tray vs. quit, and launch at Windows startup, are both toggles in that same Settings window (also under "App behavior") — nothing is silently registered without your say-so.
  • Data lives under %APPDATA%\civitai-scheduler\data (SQLite DB + uploaded images) instead of the repo's data/ folder, so a packaged install doesn't need write access to its own installation directory.
  • Changing any Civitai-related setting (API key, MCP/tRPC URL, dashboard password, poll cron, auto-upload window, port) requires restarting the app to take effect — the Settings window will offer to do this for you.

Gotcha: better-sqlite3 is a compiled native module, and plain Node vs. Electron need it built against different ABIs — but they share the same node_modules/better-sqlite3 on disk. After electron:dev/dist:win rebuilds it for Electron, npm start/npm run dev will error until it's rebuilt back for plain Node — build-win.bat does this for you automatically. Plain npm rebuild better-sqlite3 isn't reliable enough to recommend doing by hand: it can get shadowed into rebuilding for Electron again, or (depending on your installed Node version) misdetect the compiler toolset entirely. Docker is unaffected — it always does a fresh npm ci inside the container.

Security notes

  • Your Civitai API key never reaches the browser — it's only used server-side.
  • If you deploy this anywhere reachable from the internet (not just localhost), set DASHBOARD_PASSWORD in .env. Without it, anyone who finds the URL can post to your account. The app refuses to stay silent about this — it logs a warning on startup if it's unset.
  • Uploaded images are stored under data/images/<post-id>/ until published; nothing is deleted automatically after publishing (delete a post's row in the UI to clean it up).

Status

Both upload_image (URL and base64 paths) and create_post have now been exercised for real against a live account — a real scheduled post has published successfully end-to-end. The field names in src/mcpClient.js match Civitai's live MCP schema. If scheduling ever starts failing with an argument-validation error in the future (Civitai's schema can still evolve), paste the last_error text back and check it against what npm run inspect-tools prints.

Retries: a failed post is retried automatically on the next poll (up to 3 attempts) before being marked failed for good; you can also hit "retry" in the UI to reset the attempt counter.

Project layout

src/server.js       Express app + API routes + optional basic auth
src/db.js           SQLite queue (better-sqlite3)
src/scheduler.js     node-cron polling loop; uploads images, creates the post
src/mcpClient.js     Thin wrapper around the Civitai MCP server (upload, create_post, connection check)
src/inspectTools.js  Standalone: dumps live tool schemas + account status
public/              The departures-board web UI (static, no build step)
electron/            Windows desktop app wrapper (Electron) — main process, tray, Settings window
build-win.bat        Builds the Windows installer + restores better-sqlite3's plain-Node ABI