add-ecosystem
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.
$ npx -y skills add civitai/civitai --skill dev-server --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/dev-serverContext preview
The summary Claude sees to decide when to auto-load this skill.
Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.
name: dev-server description: Manage Next.js dev servers across worktrees. Start, stop, and read logs from dev servers. Agents can access logs from any running session, regardless of who started it.
Centralized management of Next.js dev servers across multiple git worktrees. The daemon handles port allocation, environment variable injection, and log aggregation so that any agent can access dev server logs regardless of who started the server.
# Check what's running node .claude/skills/dev-server/cli.mjs status # Start a dev server for current worktree node .claude/skills/dev-server/cli.mjs start # Start for a specific worktree node .claude/skills/dev-server/cli.mjs start /path/to/worktree # Start with a service on production (see Env modes) node .claude/skills/dev-server/cli.mjs start /path/to/worktree --prod buzz # View logs node .claude/skills/dev-server/cli.mjs logs <session-id> # Stop a session node .claude/skills/dev-server/cli.mjs stop <session-id>
**Checking if server is ready:** After starting, poll the session status to check `ready: true`. The daemon marks sessions ready either via configured health check endpoint or by detecting "Ready" patterns in logs.
The daemon is spawned with `process.execPath` (`startDaemon` in `cli.mjs` and `console.mjs`), i.e. **whatever node ran the CLI verb that first started it**. It then passes its own environment down to every `next dev` it supervises. So the node you happened to have on `PATH` the first time you typed any command above is the node the whole tree runs on, until someone shuts the daemon down — and nothing records which one that was.
That is not academic. Measured on a dev box: the daemon was running on ambient node **26.7.0**, while `.nvmrc` pins **24.19.0**, `package.json` declares `engines.node: ">=24.0.0 <25"`, and production is built on `node:24.19.0-alpine3.24`. It had been started from a shell outside the dev shell, and that shell also had no `pnpm` at all — which silently disables the daemon's own auto-install path (it shells out to `pnpm install` / `pnpm run db:generate` when it sees the lockfile or the schema move).
**The fix, whatever your setup: start it from a shell whose `node --version` matches `.nvmrc` and which has `pnpm` on `PATH`.** `nvm use` at the repo root gives you both.
**On NixOS (optional)** the flake wrapper does that for you — it pins node to the same version `.nvmrc` names, puts pnpm on `PATH`, and exports the Prisma engine paths NixOS needs:
nix run .#dev-server -- status nix run .#dev-server -- start nix run .#dev-server -- logs <session-id>
Every `node .claude/skills/dev-server/cli.mjs …` invocation elsewhere in this document takes the same subcommands — the wrapper only decides which node runs them, so nothing here depends on having Nix.
Either way, **check what you have got** before trusting a session:
# the daemon's real interpreter, not the one you assume. # the pid file belongs to the skill dir the daemon RUNS FROM, which is the primary checkout — a # relative path here reads the wrong tree's file, or none, when you are standing in a worktree. readlink -f /proc/$(cat <primary-checkout>/.claude/skills/dev-server/daemon.pid)/exe
Changing node means restarting the daemon — `cli.mjs shutdown`, then start it again from the right shell. A running daemon will not pick up a new `PATH`.
The daemon lives on `127.0.0.1:9444`. Set `DEV_DAEMON_PORT` to stand another one beside it:
DEV_DAEMON_PORT=9555 node .claude/skills/dev-server/cli.mjs status
The CLI, the console, `scripts/test-unit-run.mjs` and the daemon itself all resolve the port through `scripts/daemon-port.mjs`, and a daemon a client spawns inherits that client's environment — so no two of them can disagree about where it is. Until 2026-08-19 the daemon did not read the variable at all, and setting it pointed the client at a port nothing was serving.
The variable is only a default for the daemon; an explicit `node scripts/daemon.mjs --port <port>` still wins. Each daemon owns the shared `daemon.pid`, so the last one started is the one that file names.
node .claude/skills/dev-server/cli.mjs probe /home
A dev server that has stopped serving properly does not refuse connections. It accepts and answers slowly, or accepts and never answers — so an unbounded `curl` sits there until the 300s tool timeout and returns nothing you can act on. Chaining a few in one shell call is how ten minutes disappear. A `PreToolUse` hook blocks unbounded requests at dev ports for this reason; `--max-time` is the escape hatch if you really want curl.
`probe` requests the route twice with a hard budget, reads the session's own log for those two requests, and returns a verdict with the matching remedy. It always terminates.
UPSTREAM-SLOW http://localhost:3000/home UPSTREAM-SLOW — the framework is fine; time is spent in application code (database, tunnel, cache). first : 200 in 8.10s [next.js 30ms | application-code 8.10s] repeat: 200 in 8.10s [next.js 31ms | application-code 8.10s] -> Not a cache problem — do NOT purge, it will not help. [...]
Exit code is 0 for `ok`/`cold` and 1 for everything else, so it substitutes for a curl in a check.
Two verdicts worth knowing before you meet them. **`stopped-answering`** means the first request was served and the second was not — the process is up and something inside it has parked, so starting another session is the wrong move and the remedy says so. And a probe may add `note: more than one request to this route in the window`: `probe` tags its own request with a `?__probe=` nonce so it can find its own log line, but a route that already has a query string — and every `/api/trpc/*` route, whose handlers parse their query — falls back to matching on
Repo: civitai/civitai
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Wire an existing ecosystem into the generation system. Adds generation support to…
Author a prompt-enhancement system prompt for a new ecosystem and register/update it on the…
Add a new trainable base model to BOTH trainers end-to-end — the in-app trainer (main Next.js…
Wire an existing ecosystem into the LoRA training system so it appears as a trainable base…
Deterministically drive a first-party Civitai App Block in the operator's browser and produce…