Skip to content

alon-z/herdr-devup

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Devup — per-project dev layouts for Herdr

A Herdr plugin. Drop a .herdr/dev.toml into a project, or put project TOML files in the plugin config dir, press a key, and Herdr spawns the tabs and panes for that project's dev stack — tunnels, servers, simulators — then pulls the tunnel URL and rewrites the env files that bake it in.

What it does

  • up — read project TOML config, create the tabs and panes it declares, and run each pane's command.
  • sync — re-pull the tunnel URL (ngrok agent API, or a regex over a pane's output), rewrite the configured env vars, and restart the panes that bake them in. Run this whenever the tunnel rotates.
  • down — close the tabs this project's up created.

Security

Devup TOML is executable config: its cmd/command strings run in real shells and its [[sync.apply]] blocks rewrite files. Treat a project's TOML like the project's own code. Before running devup up in a repo you do not fully trust (a clone, a PR branch, someone else's project), read its config first — pressing the keybinding runs whatever commands it declares, as your user.

The plugin itself never runs anything not in that file, only writes the env keys a [[sync.apply]] block names, and only closes the tabs its own up created.

Install

Requires bun on PATH. ngrok features use the local ngrok agent API on :4040.

# local development
git clone <this repo> herdr-devup && cd herdr-devup
bun install
herdr plugin link "$PWD"

# or from GitHub once published
herdr plugin install <owner>/<repo>

Verify:

herdr plugin action list --plugin alonz.devup

Bind keys

The plugin manifest cannot declare keys; add them to your Herdr config.toml:

[[keys.command]]
key = "prefix+u"
type = "plugin_action"
command = "alonz.devup.up"
description = "devup: start project layout"

[[keys.command]]
key = "prefix+y"
type = "plugin_action"
command = "alonz.devup.sync"
description = "devup: sync tunnel URL"

[[keys.command]]
key = "prefix+shift+u"
type = "plugin_action"
command = "alonz.devup.down"
description = "devup: close project layout"

Then herdr server reload-config. Pressing the key in a workspace whose focused pane sits inside a project with .herdr/dev.toml, or inside a working_dir/repo matched by plugin config TOML, runs that project's layout.

Config: TOML project layout

Project-local config still works at .herdr/dev.toml. You can also put TOML files in herdr plugin config-dir alonz.devup (or its projects/ subdir); those match the focused pane by working_dir or by git repo name.

See examples/acme/.herdr/dev.toml for a full sync example. Simple shape:

name = "Options Cafe"
description = "The main options.cafe monorepo"
working_dir = "~/Development/options-cafe/options.cafe"   # ~ and $VARS expand
# repo = "options.cafe"                                   # alternative matcher

[[tabs]]
name = "claude"
command = "claude --dangerously-skip-permissions --chrome"

[[tabs]]
name = "terminal"   # no command: empty shell

[[tabs]]
name = "server"
  [[tabs.panes]]
  command = "php artisan serve"

  [[tabs.panes]]
  command = "npm run dev"
  split = "down"

Full shape with sync:

[project]
name = "myapp"

[[tabs]]                      # one tab
label = "dev"                # `name` also works
  [[tabs.panes]]              # first pane = tab root
  name = "web"
  cmd = "bun run dev"        # `command` also works
  [[tabs.panes]]              # splits from the previous pane
  name = "api"
  cwd = "services/api"       # relative to project root / working_dir
  cmd = "bun run dev"
  split = "right"            # right | down  (default right)
  ratio = 0.5                # optional split ratio
  defer = true               # don't run cmd at `up`; a sync starts it

[[sync]]                     # optional; repeatable
name = "tunnel-url"
source = "ngrok"            # ngrok | pane
addr = 3000                 # ngrok: prefer the tunnel for this local port
timeout_ms = 30000
restart = ["web", "api"]   # panes to (re)start after env is written
  [[sync.apply]]
  file = ".env"
  set = { PUBLIC_URL = "{url}" }

Sync sources

  • source = "ngrok" — polls http://127.0.0.1:4040/api/tunnels, picks the https tunnel (preferring addr). Value is available as {url}.
  • source = "pane" — needs pane = "<name>" and pattern = "<regex>"; scrapes that pane's recent output. {url}/{value} is capture group 1 (or the whole match); {1}, {2}, … are individual groups; {match} is the whole match.

Env writing

Each [[sync.apply]] sets KEY=value in file (created if missing): an existing KEY= line (optionally export -prefixed) has its value replaced in place; otherwise the line is appended. Placeholders in values are substituted from the resolved sync value.

Why defer / restart

NEXT_PUBLIC_* and similar vars are baked at server startup, so the server must start after the env is written. Panes in a sync's restart list are deferred at up and started once the URL is known. restart also re-runs them on a later sync (Ctrl-C, then the command again) when the tunnel rotates.

State

Per-project state (created tab and pane ids, pane commands) lives under HERDR_PLUGIN_STATE_DIR, keyed by project path, so sync and down can find the right panes. The plugin never edits files outside what [[sync.apply]] declares.

About

Herdr plugin: per-project dev layouts from .herdr/dev.toml with tunnel-URL env sync

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors