Local dev environment orchestrator for WordPress, Laravel, Node, Next.js and Astro projects.
- Single cross-platform Go binary (macOS, Linux, Windows) — no bash, no Makefile for users
- Three interfaces sharing the same backend: CLI (Cobra) · TUI (Bubble Tea) · native GUI (Wails + React + Tailwind)
- macOS menubar daemon with Start / Stop / Open GUI and a live
stack N/M runningindicator - Built-in AI agent: ask in natural language, it calls tools (create projects, databases, hosts, logs, exec)
- Multi-provider LLM: Infomaniak AI Tools (open-source models), Anthropic, local Ollama
- Docker stack: FrankenPHP (PHP+Caddy+Node), MySQL 8.4, PostgreSQL + PostGIS, Redis, Mailpit, Adminer
macOS / Linux
# From release binary (when published)
curl -L https://github.com/MarJC5/devner/releases/latest/download/devner_darwin_arm64.tar.gz | tar -xz
sudo mv devner /usr/local/bin/
# Or build from source
git clone -b go https://github.com/MarJC5/devner.git
cd devner
make install # builds and installs to /usr/local/binWindows (PowerShell)
# Download the .zip from Releases and extract devner.exe into PATH.
iwr -useb https://github.com/MarJC5/devner/releases/latest/download/devner_windows_amd64.zip -o devner.zip
Expand-Archive devner.zipRequirements
- Docker Desktop (macOS, Windows) or Docker Engine + Docker Compose v2 (Linux)
- Go 1.22+ (to build from source)
- (optional)
mkcertfor system-wide HTTPS trust outside browsers
# 1. Start the shared stack (pulls images the first time)
devner up
# 2. Create a Laravel project with MySQL
devner new laravel myapp --db=mysql
# → scaffolded, DB created, Caddy configured
# → https://myapp.localhost
# 3. Interactive TUI
devner tui
# 4. Ask the agent (Infomaniak by default)
# First-run: edit ~/Library/Application Support/devner/config.toml,
# set product_id and api_key under [llm.providers.infomaniak].
devner models # sanity check auth
devner agent "create a Next.js project called web and show me logs"devner import --source=~/path/to/old/projects --dry-run
# review what would be imported
devner import --source=~/path/to/old/projectsDetection (priority order — first match wins for Type):
wp-config.php/wp-settings.php→ WordPress (sniffs DB_NAME, DB_USER from wp-config.php)artisan→ Laravel (sniffs DB_DATABASE from.env)index.php/public/index.php/composer.json/.htaccesswith PHP rewrite → PHP (generic, import-only)astro.config.*→ Astronext.config.*→ Next.jsnuxt.config.*→ Nuxtsvelte.config.*→ SvelteKitvite.config.*→ Vitepackage.json(fallback) → Node
Dev mode is detected in a second pass by reading package.json scripts:
- server — Node-app types with a framework dev script (
vite,next dev,astro dev,nuxi dev,nodemon, …). Gets a reverse-proxy port allocated in [3100, 3999]. - watch — PHP-typed projects (WordPress, Laravel, generic PHP) with a
watchordevscript. Runs alongside FrankenPHP; no port, no Caddy rerouting. - none — libraries (
package.jsonwithmain/module/exports/filesand no dev script), plain PHP, static sites.devner dev startrefuses with a clear message.
devner up # start stack
devner down # stop stack
devner ps # container status
devner logs <svc> # tail container logs
devner restart [svc] # restart stack / service
devner rebuild # force rebuild images
devner delete --force # destroy stack + volumes (DESTRUCTIVE)
devner new <type> <name> [--db=mysql|postgres] [--template=...]
# types: wordpress | laravel | node | nextjs | nuxt | astro | sveltekit | vite
# --template covers Vue / Svelte / Solid / Qwik / Preact / Lit / vanilla via Vite
devner remove <name>
devner list
devner dev start <name> [--command "..."] # launch dev server (Node apps) or asset watcher (PHP+Vite/Mix)
devner dev stop <name>
devner dev status [name] # shows mode=server|watch + running/stopped
devner dev logs <name> [--tail N]
devner db create <mysql|postgres> <name>
devner db drop <mysql|postgres> <name>
devner hosts add <domain> [target] # no-op for *.localhost
devner hosts remove <domain>
devner import --source=<path> # migrate projects from a directory
devner reconcile [--apply] # fix drift: store ↔ FS ↔ Caddy
devner certs status
devner certs install # mkcert -install
devner models # list models of the active LLM provider
devner agent "<prompt>" # one-shot agent call
devner tui # interactive TUI
devner gui # launch the desktop app (Wails)
devner menubar # launch the macOS menubar daemon
devner menubar stop # kill it
devner menubar status # check whether it's running
The config file is created automatically on first run at the OS-standard location:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/devner/config.toml |
| Linux | ~/.config/devner/config.toml |
| Windows | %AppData%\devner\config.toml |
Template (generated on first run, safe to edit):
[stack]
data_dir = "~/.devner"
projects_dir = "~/devner/projects"
[llm]
active_provider = "infomaniak"
# Infomaniak AI Tools — OpenAI-compatible (v2 endpoint).
# Get your product_id from https://manager.infomaniak.com (AI Tools)
# or GET /1/ai with your token.
[llm.providers.infomaniak]
base_url = "https://api.infomaniak.com/2/ai/{product_id}/openai/v1"
# Use either api_key (token in the file) OR api_key_env (env var name).
# api_key wins if both are set.
api_key = ""
api_key_env = "INFOMANIAK_API_KEY"
product_id = ""
model = "qwen3"
kind = "openai_compat"
[llm.providers.anthropic]
api_key_env = "ANTHROPIC_API_KEY"
model = "claude-opus-4-7"
kind = "anthropic"
# Local Ollama — no API key needed.
[llm.providers.ollama]
base_url = "http://localhost:11434/v1"
model = "qwen2.5-coder:14b"
kind = "openai_compat"- Get your product ID:
curl -H "Authorization: Bearer $TOKEN" https://api.infomaniak.com/1/ai - Put the product ID and the token in
config.tomlunder[llm.providers.infomaniak]. - Verify connectivity and list available models:
devner models
- Run the agent:
devner agent "list my projects"
Infomaniak v2 endpoint: POST /2/ai/{product_id}/openai/v1/chat/completions — see docs.
Framework coverage:
| Type | Scaffold | Dev command | Notes |
|---|---|---|---|
nextjs |
pnpm create next-app |
pnpm dev (PORT env) |
App Router + TS + Tailwind by default |
nuxt |
pnpm dlx nuxi init |
pnpm dev --port N |
|
astro |
pnpm create astro |
pnpm dev --port N |
--template minimal|basics|blog|portfolio|starlight |
sveltekit |
pnpm create svelte |
pnpm dev --port N |
--template skeleton|minimal|demo |
vite |
pnpm create vite |
pnpm dev --port N |
--template react-ts|vue-ts|svelte-ts|solid-ts|preact-ts|qwik-ts|lit-ts|vanilla-ts |
node |
npm init -y |
npm run start |
User controls the server code |
For Vue / Svelte / Solid / Qwik / Preact / Lit / vanilla-JS, use devner new vite myapp --template <tpl>-ts — no framework-specific devner type needed.
PHP projects (WordPress, Laravel, generic PHP) serve directly through FrankenPHP's PHP handler. Node-app projects use a different path:
- At creation time, devner allocates an internal port in the range 3100–3999 and persists it on the project row (
projects.dev_portin SQLite). - The rendered Caddyfile emits
reverse_proxy localhost:<port>for that site instead ofphp_server + file_server. - Nothing listens on the port until you run
devner dev start. Caddy returns 502 meanwhile — that's the "up but dev not running" signal. devner dev start <name>spawnspnpm dev(ornpm run startfor plain Node) viadocker exec -dinside the frankenphp container, under a new session sodevner dev stopcan terminate the whole process group (pnpm → vite/next/astro → …). A PID file lives at/tmp/devner/dev-<name>.pid; logs at/tmp/devner/dev-<name>.log.- The URL is the same as PHP projects:
https://<name>.localhost. HTTPS, HMR via WebSocket, everything transparent.
Vite and Astro don't read PORT from env by default — devner invokes them as pnpm dev --port <N>. Next.js reads PORT from env so devner sets it directly. Plain Node projects run npm run start and rely on the user's package.json honouring the PORT env var (conventional). Pass --command "..." to devner dev start to override.
Bind-mount file events don't propagate reliably from the host into the container on macOS. Devner sets CHOKIDAR_USEPOLLING=1, CHOKIDAR_INTERVAL=300, and WATCHPACK_POLLING=1 when spawning dev servers so chokidar/webpack-style watchers fall back to polling. Costs ~1–3 % of one core for instant HMR reliability. Enable Docker Desktop's VirtioFS file-sharing (Settings > General) if you prefer native fsevents without the polling overhead — the env vars become redundant but harmless.
Dev servers die when the frankenphp container restarts (which happens whenever devner applies a new Caddy config — i.e. every devner new / devner remove). This is intentional: devner does not auto-restart them. Run devner dev start <name> again when you want it back.
devner new vite myapp # allocates port, scaffolds pnpm create vite react-ts
devner dev start myapp # launches pnpm dev --port <allocated>
open https://myapp.localhost # HTTP 200, Vite dev server with HMR
devner dev logs myapp # tail the dev server output
devner dev stop myapp # kill the process group, Caddy → 502Many PHP projects (custom CMSes, WordPress themes, Laravel apps) carry a package.json for asset compilation (Vite, Tailwind, Laravel Mix). Devner classifies these as dev_mode=watch at import time and devner dev start launches the asset watcher alongside FrankenPHP — the PHP backend keeps serving https://<name>.localhost, no reverse-proxy switcheroo.
Detection rule: PHP type + a watch script in package.json → mode=watch, command=pnpm watch. If only dev is present, that becomes the command (Vite's dev script on a PHP project is an asset watcher in disguise).
devner import --source=~/path/to/projects # classifies each folder
devner dev status # shows mode=watch / server / none
devner dev start my-cms # runs `pnpm watch` inside frankenphp
devner dev logs my-cms # tails vite build --watch outputLibraries (pure JS packages with main/exports/files and no dev script) get dev_mode=none — devner dev start refuses with a clear message instead of launching a non-existent server.
The LLM sees these tools. Destructive ones (marked ⚠) require confirmation in the TUI or --yes on the CLI.
| Tool | Purpose |
|---|---|
list_projects |
list managed projects |
project_status |
details for one project |
create_project |
scaffold wordpress / laravel / node / nextjs / astro / vite |
delete_project ⚠ |
remove files + DB + Caddy entry |
create_database |
mysql or postgres DB + user |
start_dev_server ⚠ |
launch pnpm dev / npm run start for a Node-like project |
stop_dev_server ⚠ |
stop the dev server |
dev_server_status |
running/stopped + PID + port |
drop_database ⚠ |
drop DB + user |
start_stack / stop_stack |
lifecycle |
rebuild_stack ⚠ |
rebuild images + recreate |
tail_logs |
container logs |
composer ⚠ |
run composer install / require / update in the project |
npm ⚠ |
run npm / pnpm / yarn install / build / add in the project |
wp_cli ⚠ |
run WP-CLI: plugin install, option get, user create, etc. |
artisan ⚠ |
run php artisan on a Laravel project |
exec_in_project ⚠ |
arbitrary shell escape hatch (prefer the typed tools above) |
Every tool call is audited in ~/.devner/store.db (history table).
cmd/devner/ # main (Cobra + Bubble Tea entrypoint)
internal/
cli/ # Cobra subcommands
tui/ # Bubble Tea scenes (projects, stack, chat, logs, config)
agent/ # tool-use loop (max 10 iterations, destructive confirm)
llm/ # Provider interface + OpenAI-compat + Anthropic native
tools/ # Registry + JSON schemas
project/ # WP/Laravel/Node/Next/Astro detect + scaffold
database/ # MySQL + Postgres ops (validated identifiers, random passwords)
runtime/ # docker compose wrapper + docker exec/logs
network/ # Caddyfile rendering + docker restart reload
store/ # SQLite metadata (modernc — pure Go, no CGO)
app/ # Deps bundle (shared CLI + TUI wiring)
config/ # Viper TOML
platform/ # OS detection, hosts path, elevation check
assets/ # embed.FS: compose.yaml, Dockerfile, php.ini, Caddyfile
migrations/ # SQLite schema (.sql + embed.FS)
Cross-platform desktop app built with Wails v2 (Go backend + React + Tailwind frontend + native OS WebView). Shares internal/app.Deps with the CLI / TUI so every action goes through the same code path — no HTTP layer, no divergence.
- Dark + light themes (Sun / Moon / System — follows macOS preference)
- EN / FR UI (persisted in
localStorage, selectable in Settings) - Frosted-glass panels on macOS (vibrancy) and Windows (Mica); flat clean look on Linux
- Keyboard shortcuts:
⌘1..7jump between Projects / Stack / Chat / Logs / Databases / Hosts / Settings
make gui-dev # hot-reload Vite + auto Go rebuild on save
make gui-build # produces gui/build/bin/devner-gui(.app|.exe|binary)
make gui-cleanRequirements: Go 1.22, Node 20, pnpm 9, and the Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@latestLaunched from the CLI with devner gui — the CLI searches /Applications/Devner.app, gui/build/bin/Devner.app, devner-gui on $PATH, or the $DEVNER_GUI env var.
A tiny standalone process (cmd/devner-menubar) owns a system-tray icon with Start stack / Stop stack / Open GUI entries and a live stack N/M running indicator. It runs as a separate binary because Wails and fyne.io/systray both want ownership of the main run loop.
devner menubar # launch (backgrounded)
devner menubar stop
devner menubar statusmake build # ./bin/devner
make build-menubar # ./bin/devner-menubar
make build-all-bins # CLI + menubar
make test # go test ./...
make install # copies devner to /usr/local/bin (may need sudo)
make clean # removes ./bin and ./dist
# Without Make:
go build -o bin/devner ./cmd/devner
go test ./...Cross-compile the CLI:
make build-all # bin/devner-{darwin,linux,windows}-{amd64,arm64}Release (local dry-run):
make release-snapshot # goreleaser --snapshot --cleanRelease CI (GitHub Actions) attaches GUI archives — Devner_darwin_{arm64,amd64}.zip, Devner_linux_amd64.tar.gz, Devner_windows_amd64.zip — alongside the GoReleaser-produced CLI archives on every v* tag push.