Skip to content

Full rewrite: Go CLI + TUI + Wails GUI + menubar + AI agent#1

Merged
MarJC5 merged 24 commits intomainfrom
go
Apr 20, 2026
Merged

Full rewrite: Go CLI + TUI + Wails GUI + menubar + AI agent#1
MarJC5 merged 24 commits intomainfrom
go

Conversation

@MarJC5
Copy link
Copy Markdown
Owner

@MarJC5 MarJC5 commented Apr 20, 2026

Summary

Complete rewrite from the original bash-based devner to a Go implementation with three interfaces sharing a single backend.

  • CLI (Cobra) — scripting-friendly, all ~30 commands.
  • TUI (Bubble Tea) — 5 scenes, keyboard-first.
  • GUI (Wails v2 + React + Tailwind) — cross-platform native window with light/dark/system theme, EN/FR i18n, frosted-glass surfaces (macOS vibrancy / Windows Mica), 8 screens at CLI parity.
  • Menubar daemon (fyne.io/systray) — macOS top-right status item with live stack N/M running label and Start/Stop/Open entries.
  • AI agent — tool-use loop with destructive-action confirmation, streaming responses, multi-provider (Infomaniak v2 / Anthropic native / Ollama).

All four surfaces go through the same `internal/app.Deps` bundle — no HTTP layer between them, no divergence. Projects are stored in a pure-Go SQLite db (modernc, no CGO required for the CLI).

Highlights

  • Framework scaffolding: WordPress, Laravel, Node, Next.js, Nuxt, Astro, SvelteKit, Vite — with per-framework post-setup (`.env`, `wp-config.php`, `migrate`).
  • Node-like dev servers: internal 3100–3999 port allocation, Caddy reverse_proxy, HMR-friendly env vars, process-group kill.
  • HTTPS via Caddy's internal CA; optional `mkcert -install` (delegated to Terminal.app on macOS because Security framework blocks non-Aqua keychain writes).
  • GoReleaser for the CLI + GitHub Actions matrix for the Wails GUI across macOS (arm64/amd64), Linux, Windows.

Test plan

  • `make build` + `make build-menubar` + `make gui-build` all pass on macOS arm64
  • `go test ./internal/... ./cmd/devner/...` green
  • `pnpm tsc --noEmit` clean in `gui/frontend/`
  • Manual: `devner up`, create WP project, open in browser HTTPS 200
  • Manual: `devner gui` launches the Wails app; theme + language switch persist across relaunch
  • Manual: `devner menubar` places the tray icon; Start/Stop/Open work
  • CI: `v*` tag triggers GoReleaser + Wails matrix — pending first tag push
  • Linux + Windows GUI build — validated only via CI, not locally

🤖 Generated with Claude Code

MarJC5 and others added 24 commits April 20, 2026 10:53
Single cross-platform binary (macOS, Linux, Windows) replacing the bash/
Makefile orchestrator. Same Docker stack (FrankenPHP, MySQL 8.4, Postgres
+PostGIS, Redis, Mailpit, Adminer) with TUI (Bubble Tea) and an AI agent
that calls validated tools.

Highlights:
- 18 CLI subcommands (up/down/new/remove/import/reconcile/agent/tui/...)
- 5 TUI scenes (Projects, Stack, Chat, Logs, Config)
- Tool-use agent loop with destructive-action confirmation
- Multi-provider LLM: Infomaniak AI (OpenAI-compat), Anthropic native, Ollama
- 11 tools exposed to the agent with JSON schemas
- WordPress / Laravel / Node / Next.js / Astro scaffold + detect + DB sniff
- MySQL + Postgres ops with validated identifiers and random passwords
- Docker Compose v2 wrapper + native docker exec/logs
- txeh /etc/hosts + Caddy admin API + mkcert integration
- SQLite (modernc, pure Go) for metadata + tool-call audit history

The old bash v1 stays on main untouched. `devner import --source=...`
migrates existing projects.

Build: cd devner-go && go build -o bin/devner ./cmd/devner
Test:  cd devner-go && go test ./...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… branch

The go branch is now a pure v2 Go repository structure:
- Go code (cmd/, internal/, assets/, migrations/) moved from devner-go/
  subdirectory to the repo root
- v1 bash artifacts removed from this branch only (devner.sh, cli/,
  Makefile, docker-compose.yml, services/) — main branch keeps them intact
- projects/ untracked on this branch and added to .gitignore
  (client WordPress sites — must never be committed)

main branch is unchanged; v1 users keep running from there. Switch to
`go` to work with the Go version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The admin API + POST /load / PATCH /config approach fails reliably on
FrankenPHP: every reload triggers an internal admin listener restart that
times out ("stopping admin server: 10s timeout"), and the 'caddy reload'
subcommand goes through the same path. The 3-sec downtime of a container
restart is acceptable for a local dev tool and 100% reliable.

Also fix two related bugs surfaced while testing on 11 imported WP sites:

1. assets/dockerfiles/Caddyfile declared `worker /var/www/html/index.php`
   which doesn't exist on a fresh stack, making frankenphp crash-loop.
   Removed — workers aren't needed; FrankenPHP serves each site lazily.

2. Caddy JSON config placed `try_files` and `split_path` on the `php`
   handler (unknown fields), causing Caddy to reject the config. These
   belong to the `file` matcher — now correctly scoped.

Tested end-to-end: 11 imported WordPress projects respond HTTP 200 via
https://*.localhost after `devner reconcile --apply`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop "v2" references from the README; the repo's `go` branch is
  the current version, there is no coexisting "v1" meant for end users.
- Fix install instructions: correct GitHub org/repo, flat repo layout
  (no more devner-go/ subdir), document `make install`.
- Correct config file path per OS (macOS uses ~/Library/Application
  Support, not ~/.config).
- Add Makefile with build / test / vet / tidy / install / clean /
  build-all (cross-compile) / release-snapshot / release targets.
  Binary is tagged with git describe output and commit hash via ldflags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously Load() used Viper defaults silently when no config file
existed, leaving users with no discoverable place to set their
projects_dir or LLM API keys.

Now on first load, if the config file is missing, write a documented
TOML template to the OS-standard location with comments explaining each
section. Users can immediately see where to put their Infomaniak /
Anthropic keys and where projects get mounted.

Subsequent loads read the user-edited file as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infomaniak AI Tools requires a product_id in the URL path
(/2/ai/{product_id}/openai/v1/...). Previously we hardcoded the v1
endpoint without product_id, which would return 404 against any real
account.

Changes:
- ProviderConfig: add ProductID field (string; empty by default)
- llm/factory.go: substitute {product_id} placeholder in base_url;
  fail with a clear error if the placeholder is present but product_id
  is empty
- config.go: default Infomaniak base_url switched to v2 endpoint with
  {product_id} placeholder; auto-generated config.toml documents the
  required field
- cli: new `devner models` command — hits the provider's /models
  endpoint and lists available models; useful smoke test after
  credentials setup

Tested against a real Infomaniak product once the user sets
product_id + INFOMANIAK_API_KEY.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Users testing against Infomaniak kept putting their token in the
api_key_env field (which expects an env var NAME, not its value),
getting confusing auth errors. Add a direct api_key field so config
can hold the token itself without an env indirection.

Resolution order in resolveAPIKey():
  1. pc.APIKey   — literal value from config
  2. os.Getenv(pc.APIKeyEnv) — env var named by config

api_key wins if both are set. Both remain optional so env-only and
file-only setups both work.

Tested end-to-end with a real Infomaniak product + qwen3 model:
`devner agent "liste mes projets"` successfully calls list_projects,
receives the 11-project store payload, and renders a markdown summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Config block now matches what `devner` auto-generates (v2 endpoint
  with {product_id} placeholder, both api_key and api_key_env shown,
  model=qwen3 instead of the removed mixtral).
- New "Infomaniak setup" section with the 4-step flow and a pointer to
  the `devner models` smoke-test command.
- Quickstart note added: set product_id + api_key in config.toml
  before running `devner agent` the first time.
- Commands block: add `devner models`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the agent had one generic exec_in_project for package
managers and CLI frameworks. Models picked it eventually but the tool
choice was noisier and the descriptions were vague.

Add four typed tools with explicit schemas:

  - composer(project, args[])        — install / require / update / …
  - npm(project, manager, args[])    — manager: npm | pnpm | yarn
  - wp_cli(project, args[])          — runs `wp --allow-root <args>`
  - artisan(project, args[])         — runs `php artisan <args>`

Plus refactor: shared runInProject() helper (cd into /var/www/html/<p>,
capture output, tail-truncate at 4 KB). ExecInProject stays as the
escape hatch for anything outside these four, and its description
explicitly says "prefer the typed tools when they fit".

All tools stay Destructive=true (LLM-invoked shell requires
confirmation in TUI; --yes bypasses in CLI mode).

Verified end-to-end with Infomaniak qwen3:
  devner agent --yes "montre-moi le siteurl du projet cardis"
  → agent selects wp_cli (not exec_in_project), receives
    "https://cardis.localhost", renders a short answer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Raw markdown dumped to stdout was hard to read — bold, headings, lists
and tables all showed as literal **, ###, |. Now:

- Assistant text is buffered and rendered through charmbracelet/glamour
  (markdown-to-ANSI) so tables, bold, code fences and headings look
  right in a real terminal.
- Tool calls and results are styled with lipgloss (yellow bullet for
  calls, green/red tick for results, faint color for arg payloads).
- <think>...</think> reasoning blocks emitted by qwen3 / gpt-oss /
  kimi are stripped before rendering — they aren't meant for users
  and break table layout.

Behaviour:
- TTY stdout: full styled rendering.
- Non-TTY (piped into grep/jq/tee/file): plain text fallback so
  downstream tools see clean markdown source.
- --raw flag forces the plain-text path even from a TTY.

Uses charmbracelet/glamour already pulled in via go.sum.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-command setup for zsh, bash, and fish. Detects the shell from $SHELL
(or explicit --shell) and emits a snippet that:
  - puts the devner binary on PATH (resolved via os.Executable so the
    absolute path stays correct even if the repo moves),
  - sources Cobra's shell-completion so tab-completion works on
    subcommands, flags and enum values.

Without --install, the snippet is printed for manual sourcing or eval.
With --install, it's appended to the detected rc file:
  - zsh   → ~/.zshrc
  - bash  → ~/.bashrc (macOS prefers ~/.bash_profile if present)
  - fish  → ~/.config/fish/config.fish

The block is wrapped in marker comments
  # >>> devner shell integration >>>
  ...
  # <<< devner shell integration <<<

so the install is idempotent (re-running detects the existing block) and
reversible via `devner shell uninstall`, which strips exactly the marked
block and nothing else.

Tested lifecycle on a temp rc: install → idempotent re-install → clean
uninstall that leaves the pre-existing content untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ojects

Observed behaviour: asking "what PHP version in gotham" made the LLM
reply "I don't know any project named gotham" despite gotham being
right there in the store. Root cause: the system prompt said "prefer
tools" which the model treated as a suggestion rather than a rule.

Rewrite the system message with numbered BEHAVIOUR RULES:
1. Call tools first, ask questions later — never claim a project
   doesn't exist without calling list_projects or project_status
2. When user mentions a project name, call project_status(name) first
3. Prefer typed tools (composer / npm / wp_cli / artisan) over the
   generic exec_in_project escape hatch
4. Call destructive tools directly — the UI already confirms; don't
   double-ask with "are you sure?"
5. Keep answers short; never dump >15 lines of raw stdout
6. Use read-only tools liberally to ground answers in current state

Verified the fix: "dans gotham quel est la version php?" now goes
project_status → exec_in_project("php -v") → "PHP 8.5.5 (ZTS)".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New commands:
  devner open <project> [--editor=code|cursor|zed|subl|webstorm|phpstorm|idea]
  devner code <project>    # alias
  devner cursor <project>  # alias
  devner zed <project>     # alias

Without --editor, picks the first editor found on PATH, falling back to
$VISUAL then $EDITOR.

Also exposes open_in_editor to the agent as a non-destructive tool
(just launches a GUI, no state change), so prompts like
  "ouvre cardis dans VS Code"
resolve to open_in_editor({"project":"cardis","editor":"code"}) instead
of a shell escape hatch.

Implementation lives in internal/project/open.go:
  - DefaultEditor() enumerates SupportedEditors against PATH
  - Open() exec.Start + Process.Release so the editor survives this
    process exiting

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…igrate)

Previously `devner new laravel myapp --db=mysql` left the user with a
scaffolded project that still pointed at sqlite — .env's DB_* lines
stayed commented, migrations had been run against a throwaway sqlite
file, and any HTTP request hit a 500.

Now `devner new` does the finishing work so the project is immediately
reachable at https://<name>.localhost.

Laravel:
  - cp .env.example .env if missing
  - set DB_CONNECTION / DB_HOST / DB_PORT / DB_DATABASE / DB_USERNAME /
    DB_PASSWORD from the freshly created DB credentials (handles both
    commented "# DB_HOST=..." and live lines via a regex-aware set_env
    bash helper)
  - guard composer install (skip if vendor/ exists)
  - php artisan key:generate
  - php artisan migrate --force so the real backend has users/sessions/
    cache/jobs tables (create-project ran migrate on sqlite)
  - php artisan config:clear + cache:clear to drop cached sqlite config

WordPress:
  - wp config create --dbname/--dbuser/--dbpass/--dbhost (--force,
    --allow-root)
  - optional wp core install when --wp-install is passed, with flags
    --wp-title / --wp-admin / --wp-password / --wp-email

Also:
  - New internal/app/provision.go: Deps.CreateProject orchestrates
    scaffold → DB → post-setup → store → Caddy as one atomic method so
    the CLI `new` command and the agent `create_project` tool share one
    path. ApplyCaddy moved here too.
  - tool create_project gains wp_install / wp_title / wp_admin /
    wp_password / wp_email args so the agent can create a fully
    installed WordPress in a single call.
  - Fix mysql.go: CREATE USER IDENTIFIED BY ? was failing because
    MySQL 8.x DDL doesn't accept placeholders on that statement.
    Interpolate the password (random 32-hex, so injection is not a
    concern) after escaping single quotes / backslashes.

Tested end-to-end:
  devner new laravel testapp --db=mysql  → HTTP 200, 9 tables in mysql
  devner new wordpress testwp --db=mysql --wp-install --wp-title=...
     → HTTP 200, WordPress ready at https://testwp.localhost with
       admin/admin credentials.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New command: `devner prompt`

Launches a single-scene, full-screen chat UI (input + transcript,
Esc to close). Wraps the existing chatScene so the agent-loop
streaming, tool-call rendering and destructive-action confirmations
all work the same way. No tabs, no sidebar — designed to pop over
whatever you're doing when a global hotkey fires.

Because global hotkeys are strictly OS-level (Go can't register them
cross-platform without CGO), the binding is delegated to the OS and we
ship helper recipes under scripts/hotkey/:

  - macos-shortcut.sh   AppleScript probe + printed instructions for
                        Raycast / Shortcuts.app / Hammerspoon
  - hammerspoon.lua     Cmd+D → iTerm2 (or Terminal.app) running
                        devner prompt
  - windows.ahk         AutoHotkey v2: Win+D → wt.exe devner prompt
  - linux-gnome.sh      gsettings one-liner to bind Super+D in GNOME

`devner prompt --help` also lists the shortest path per OS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pressing Cmd+D anywhere on macOS now drops a floating terminal running
`devner prompt` over the current app, in true Raycast/Alfred style, with
zero third-party dependencies if iTerm2 is installed.

New command:
  devner hotkey install     — writes the profile
  devner hotkey uninstall   — removes it

What the install does:
  Writes ~/Library/Application Support/iTerm2/DynamicProfiles/devner-prompt.json
  with:
    - Command: <absolute devner binary> prompt  (resolved via PATH, or
      falls back to os.Executable if devner isn't installed yet)
    - Window Type 4 (top-of-screen hotkey window, slides in)
    - Blur, transparency, 24×100 defaults for the popup look
    - Has Hotkey = true so iTerm2 exposes it in the hotkey-profile
      selector

The user still makes a one-time 30-sec toggle in iTerm2 Settings >
Keys > Hotkey to bind Cmd+D to this profile (iTerm2's global hotkey
binding lives in the app plist and is fragile to write from outside —
safer to let the user click through).

Why iTerm2: registering a true global macOS hotkey requires
Accessibility permissions + Carbon/AppKit hooks that would force CGO on
us and shred cross-compilation. iTerm2 already has a mature, ux-polished
hotkey-window feature — ~15M users — so we piggy-back on it instead of
shipping a mini SwiftUI app.

For Raycast / Alfred / Hammerspoon users: they wire `devner prompt` in
their launcher and don't need this command.
For non-macOS: `devner prompt --help` points to AutoHotkey (Windows)
and gsettings (GNOME).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gent

A proper native popup lives in the repo now, complementing the iTerm2 +
Raycast paths. Press Cmd+D anywhere on macOS, a NSPanel slides in over
the current app, type a prompt, Return runs `devner agent --yes --raw`,
output streams into the panel. Esc hides it. No dock icon, no menu bar,
no Accessibility permission needed.

Structure:
  macos/DevnerPrompt/
    main.swift        — ~300 lines, AppKit + Carbon hotkey API
    build.sh          — swiftc one-shot → DevnerPrompt.app (≈120 KB)
    README.md         — install + config notes

Implementation highlights:
  - Carbon RegisterEventHotKey: no Accessibility permission, app-scoped
    registration that fires system-wide because our process is an
    LSUIElement accessory.
  - NSPanel with .nonactivatingPanel + .floating level, centered on the
    screen under the cursor, transparent/blurred background.
  - Runs `devner agent --yes --raw <prompt>` via Process + Pipe, reads
    stdout/stderr on background queues, appends to an NSTextView on the
    main thread so streaming is live.
  - DEVNER_BIN env var overrides the path; defaults scan
    /usr/local/bin then /opt/homebrew/bin.
  - LSUIElement=YES so the app is invisible outside the popup.

Build system:
  - make mac-app          → compiles to macos/DevnerPrompt/build/
  - make mac-app-install  → also copies to /Applications/
  - make clean            → wipes the build dir
  - build artifacts gitignored via macos/*/build/

Why this and not Raycast/iTerm2:
  - Native look closer to a "Dynamic Island"-style overlay.
  - Zero third-party deps. Ships inside the repo, compiles in one
    swiftc call with Xcode Command Line Tools (no Xcode.app needed).
  - The other paths (iTerm2 hotkey window, Raycast script command) are
    still documented — this is just the option for users who want the
    popup to feel native rather than "a terminal window".

Tested: builds cleanly on macOS 26 / Swift 6.2.4 / arm64.
Runtime verification requires GUI; user launches `open
/Applications/DevnerPrompt.app` and presses Cmd+D.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Swift popup added a maintenance burden (Xcode CLT dep, a third
build system, a resident macOS process to babysit, Login Items
setup) for what ultimately overlaps with `devner prompt` + iTerm2
hotkey window or Raycast.

Users who want a floating popup still have two zero-code paths
documented in `devner prompt --help`:
  - iTerm2 hotkey window (Settings > Keys > Hotkey, point at the
    "Devner Prompt" dynamic profile created by `devner hotkey install`)
  - Raycast / Alfred Script Command → `devner prompt`

Removes:
  - macos/DevnerPrompt/*  (main.swift, build.sh, README.md)
  - Makefile targets mac-app and mac-app-install
  - .gitignore rule for macos/*/build/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Going back to just `devner prompt` + whatever the user binds in their
OS / launcher. The iTerm2 dynamic-profile installer and the per-OS
shell helpers were overhead nobody needed — the command itself is
enough, and prompt --help no longer advertises a particular recipe.

Removes:
  - internal/cli/hotkey.go (devner hotkey install / uninstall)
  - its registration in commands.go
  - google/uuid dependency (only used by the iTerm2 profile GUID)
  - scripts/hotkey/ (AppleScript / Hammerspoon / AutoHotkey / GNOME)
  - the hotkey-binding prose from `devner prompt --help`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reported symptom: after `create_project wordpress horde --db=mysql` via
the prompt TUI, a stray line leaked at the bottom of the screen:
  Success: Generated 'wp-config.php' file./cache/core/wordpress-6.9.4-en_US.tar.gz'...

Root cause: scaffold and post-setup shell out through Runtime.Exec
which writes subprocess stdout/stderr to Runtime.Stdout. Default was
os.Stdout, which the Go runtime happily prints over Bubble Tea's
alt-screen — Bubble Tea doesn't (and can't cheaply) intercept all
writes to os.Stdout from libraries we don't control.

Fix: both tui.Run and tui.RunPrompt now wrap their tea.Program run with
a defer-restored swap of d.Runtime.Stdout / Stderr to io.Discard.
Tools that capture output on purpose (exec_in_project, tail_logs,
composer, npm, wp_cli, artisan, project/post_setup runBash) still
swap to their own captureWriter before running and restore
afterwards, so the agent loop keeps showing tool results. Only the
scaffold/post-setup paths that used to leak raw subprocess noise are
affected — they are now silent.

CLI `devner new` is unchanged: it doesn't call tui.Run, so
Runtime.Stdout stays os.Stdout and the user still sees composer /
wp / pnpm progress streaming as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an end-to-end path for Node-like projects: scaffold, allocate an
internal port, spawn the framework dev server in background inside
frankenphp, and reverse_proxy <name>.localhost to it via Caddy. PHP
projects are untouched.

Highlights:

- migrations/002_dev_port.sql: `projects.dev_port INTEGER NOT NULL
  DEFAULT 0`. Store migrate() now tracks applied files in
  schema_migrations so ALTER TABLE is idempotent across runs.
- store: AllocateDevPort() picks the smallest free port in [3100,3999],
  DevPort round-trips through Upsert/Get/List.
- app/provision.go: CreateProject allocates a dev port for Node /
  NextJS / Astro / Vite before UpsertProject. ApplyCaddy propagates
  DevPort into network.ProjectSite.
- network/caddy.go: renderCaddyfile emits `reverse_proxy localhost:N`
  for sites with DevPort>0, falls back to `php_server + file_server`
  otherwise. WebSocket upgrade handled automatically by Caddy.
- project/devserver.go (NEW): Start/Stop/Status/Logs. Uses
  `setsid bash -lc …` so the PID file's entry is a PGID, enabling
  `kill -TERM -$pgid` to take out the whole process tree (bash →
  pnpm → vite / next / astro) on stop. Defaults inject
  CHOKIDAR_USEPOLLING / WATCHPACK_POLLING / CHOKIDAR_INTERVAL so HMR
  is reliable on Docker Desktop bind mounts.
- project/project.go: new Type "vite" scaffolded via `pnpm create
  vite@latest --template react-ts`. All scaffolders switched to
  `bash -lc` so NVM-installed pnpm/node are on PATH.
- project.DefaultCommand(type, port) returns the right per-framework
  command: `pnpm dev --port N` for Vite/Astro, `pnpm dev` for Next
  (PORT env), `npm run start` for Node.
- cli/dev.go (NEW): `devner dev start/stop/status/logs` mirroring the
  agent tools. `devner dev status` with no arg lists every Node-like
  project with its port and running/stopped state.
- tools/dev.go (NEW): start_dev_server / stop_dev_server /
  dev_server_status registered in the default registry. create_project
  schema enum extended with "vite".
- agent/loop.go: system prompt rule 3 mentions the new dev tools so
  the LLM prefers them over exec_in_project.
- runtime/runtime.go: expose GetStdoutErr / SetStdoutErr so
  DevServer.Status/Logs can capture output without a hard dependency
  on the concrete Runtime type.
- README: new "Node / Next / Astro / Vite projects" section
  documenting the port range, per-framework quirks, HMR polling, and
  the stateless lifecycle decision.

Tested end-to-end:
  devner new vite webapp
  devner dev start webapp
  curl --resolve webapp.localhost:443:127.0.0.1 https://webapp.localhost/
  → HTTP 200 (Vite welcome)
  devner dev status webapp → running pid=N port=3100
  devner dev stop webapp
  curl … → HTTP 502 (proves full process tree died)
  devner remove webapp → clean

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/Qwik/Preact/Lit)

Adds two new project types and a scaffold template selector:

- `nuxt`      → pnpm dlx nuxi@latest init, then pnpm install
- `sveltekit` → pnpm create svelte@latest, TS + skeleton by default

Extends Vite scaffolding with --template so Vue / Svelte / Solid /
Qwik / Preact / Lit / vanilla-ts are reachable without adding a
dedicated type per framework:

    devner new vite myapp --template vue-ts
    devner new vite myapp --template svelte-ts
    devner new vite myapp --template solid-ts
    ...

Astro and SvelteKit also accept --template (minimal|basics|blog|…
for Astro; skeleton|minimal|demo for SvelteKit).

Wiring:
- project.ScaffoldOptions{Template} — carried through
  CreateProjectRequest.Template.
- app.isNodeLike extended to Nuxt / SvelteKit (they get a dev_port).
- DevServer.DefaultCommand returns `pnpm dev --port N` for Nuxt /
  SvelteKit too.
- tool create_project schema: enum + template field exposed to the
  agent. The LLM can now pick "vite" + "svelte-ts" as an atomic call.
- CLI: devner new --template flag + updated help + README table
  listing per-type scaffold/dev commands.

Tested end-to-end with Svelte-via-Vite:
    devner new vite sveldemo --template svelte-ts
    devner dev start sveldemo
    curl https://sveldemo.localhost → HTTP 200
    devner dev stop sveldemo
    devner remove sveldemo

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nubar daemon

Adds a full cross-platform GUI and a menubar daemon while keeping the
CLI + TUI untouched. All three surfaces share internal/app.Deps — no
HTTP layer, no divergence.

- gui/: Wails v2 app with 8 screens (Projects, Stack, Chat, Logs,
  Databases, Hosts, Settings, plus Create Project modal). Tailwind +
  shadcn-style components, Phosphor icons, macOS vibrancy / Windows
  Mica, light/dark/system theme with localStorage persistence,
  EN/FR i18n, Cmd+1..7 shortcuts, marked.js for chat rendering.
- cmd/devner-menubar: standalone tray daemon (fyne.io/systray) that
  lives next to the CLI. Live "stack N/M running" label, Start /
  Stop / Open Devner entries. Shells out to `devner` rather than
  embedding the full runtime.
- cmd/mkicon: SVG → PNG rasteriser (oksvg + rasterx) that produces
  the template icon for the menubar with proper alpha inversion.
- internal/cli/{gui,menubar}.go: `devner gui` launches the bundle /
  binary; `devner menubar [start|stop|status]` manages the tray.

CI / packaging:
- .github/workflows/ci.yml: OS matrix build + test (menubar excluded
  on Linux — needs libappindicator), new frontend tsc job.
- .github/workflows/release.yml: GUI build matrix (macOS arm64/amd64,
  Linux, Windows) via Wails CLI; menubar built per-OS (CGO required
  for Cocoa/WinAPI); artifacts uploaded alongside GoReleaser CLI
  archives.
- .goreleaser.yaml: owner fixed, menubar removed (not cross-compilable
  from Ubuntu).
- Makefile: build-menubar / build-all-bins targets.
- README: new Desktop app + Menubar sections, devner gui/menubar
  commands documented.

Ops fixes bundled in:
- assets/compose/compose.yaml: removed 5 unused FrankenPHP port
  mappings (3000/4321/5173/8081/2019) — dev servers run on internal
  3100-3999 reached through Caddy; the admin API stays container-
  local. Dropped vestigial extra_hosts on mailpit/adminer.
- internal/runtime/runtime.go: EnsureMaterialized no longer
  overwrites the per-project Caddyfile on every CLI command (it was
  wiping project sites between reloads); it's now written only on
  first run.
- internal/network/certs.go: mkcert -install now delegates to
  Terminal.app on macOS — Security framework refuses non-Aqua
  keychain writes from a background process.
- internal/tui/styles.go + prompt.go: palette aligned with the GUI
  (primary #4A6CF7) so CLI/TUI/GUI feel like one app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MarJC5 MarJC5 merged commit 8969e79 into main Apr 20, 2026
10 checks passed
@MarJC5 MarJC5 deleted the go branch April 20, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant