Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - arichyx/devhub: A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs. · GitHub
Skip to content

Latest commit

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

devhub

A CLI tool for managing local development projects. Start, stop, and monitor your projects from a single config file, with automatic Caddy reverse proxy so you get clean URLs like http://worth.localhost instead of memorizing localhost:3000.

Prerequisites

  • Caddy — required for the reverse proxy feature. If Caddy is not installed, devhub will still start your projects, but the proxy setup will be skipped with a warning.
  • Rust 1.85+ — only needed if building from source.

Installation

cargo install devhub

Or build from source:

git clone https://github.com/arichyx/devhub
cd devhub
cargo install --path .

Configuration

Create ~/.devhub/proj.json:

{
"worth": {
"path": "~/Projects/worth_meter",
"cmd": "PORT=13001 pnpm dev",
"port": 13001,
"startup_timeout_ms": 120000,
"ready_cmd": "curl -fsS http://127.0.0.1:13001/healthz"
},
"blog": {
"path": "~/Code/blogs",
"cmd": "PORT=13002 pnpm dev",
"port": 13002
},
"tauri-demo": {
"path": "~/playground/tauri-demo",
"cmd": "pnpm tauri dev"
}
}

Each project supports these fields:

FieldRequiredDescription
pathyesProject directory. Supports ~ for home dir.
cmdyesShell command to start the project.
portnoIf set, devhub sets up a Caddy reverse proxy at <name>.localhost and, unless ready_cmd is set, uses a TCP readiness probe against 127.0.0.1:<port>. Must be unique across projects.
startup_timeout_msnoHow long devhub start waits for readiness before failing. Defaults to 60000.
ready_cmdnoCustom readiness probe command. If set, devhub start runs this command until it exits 0, the project exits first, or the timeout is reached.

Port conventions

Ports are user-assigned, and cmd can carry the assignment to the app (PORT=13001 pnpm dev, pnpm dev --port 13001), so most projects need no code changes. Pick a dedicated block such as 13001, 13002, … and stay out of the trouble zones: ports below 1024, popular dev defaults (3000, 5173, 8080), and the ephemeral ranges the OS hands to outgoing connections (32768+ on Linux, 49152+ on macOS). Ports must be unique across projects — a config that repeats one is rejected at load time.

Usage

$ devhub list
PROJECT PATH PORT COMMAND
worth ~/Projects/worth_meter 13001 PORT=13001 pnpm dev
blog ~/Code/blogs 13002 PORT=13002 pnpm dev
tauri-demo ~/playground/tauri-demo - pnpm tauri dev
$ devhub start worth
Starting 'worth'...
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93128
url: http://worth.localhost (→ localhost:13001)
Started.
$ devhub status
PROJECT STATUS PID URL
worth running 93128 http://worth.localhost (→ :13001)
blog stopped - -
tauri-demo stopped - -
$ devhub stop worth
Stopping 'worth'...
Stopped.
$ devhub restart worth
Restarting 'worth'...
stopped previous run (pid 93128)
log: /Users/you/.devhub/logs/worth.log
readiness: exec `curl -fsS http://127.0.0.1:13001/healthz`
pid: 93131
url: http://worth.localhost (→ localhost:13001)
Restarted.

Restarting a project that is not running just starts it, so restart also works as "pull my crashed project back up". If the previous run ended on its own, the tail of its log is printed before the fresh start replaces it.

How it works

  • Config & state live in ~/.devhub/:

    ~/.devhub/
    proj.json # your project config
    state.json # runtime state (PIDs, auto-managed)
    Caddyfile # generated reverse proxy config (auto-managed)
    logs/ # per-project stdout/stderr logs
    
  • Processes are spawned in a new process group (setpgid), fully detached from the devhub CLI. They survive after devhub exits.

  • devhub start waits for readiness instead of treating spawn() as success. If ready_cmd is set, it is polled until it exits 0; otherwise, a project with port uses a TCP readiness probe against 127.0.0.1:<port>.

  • The TCP probe verifies the listener on port belongs to the project's own process group. If an unrelated process holds the port (a stale server, a manually started dev server), the start fails immediately and names the offending PID instead of letting that process answer the probe and receive the project's proxy route. Ports must also be unique across projects — a config that declares the same port twice is rejected at load time.

  • During startup and runtime, project stdout/stderr are written to ~/.devhub/logs/<name>.log.

  • On stop, the entire process group receives SIGTERM (then SIGKILL if it doesn't exit within 100ms); the stop returns only after the group is confirmed gone. Stopping a project that is not running is an error, but a group that dies mid-stop is still removed from state first.

  • restart preflights the config before stopping anything, then stops the project (if running) and starts it again through the same readiness-checked path. The proxy route is kept across the restart — the URL briefly 502s instead of disappearing — and if the new start fails, the route is dropped and the error states whether the project is stopped or may still be running.

  • Stale entries (PIDs that are no longer alive) are automatically pruned on every command.

  • Failed starts print the tail of the startup log and then delete that log. Successful project logs are deleted on stop, and inactive logs older than one day are cleaned up automatically on later commands.

  • Projects with a port field get a Caddy reverse proxy entry at http://<name>.localhost → localhost:<port>. The gateway listens on the standard HTTP port 80 (macOS allows unprivileged binding), so project URLs carry no port. Caddy is reloaded (or started) automatically; if port 80 is already held by another process, the error names the offending PID.

For a detailed explanation of process groups, status/stop semantics, Caddy reconciliation, and a worked worth example, see docs/process-management.md.

Case Studies

Development

cargo build
cargo nextest run # or: cargo test

About

A CLI to manage local dev projects — start, stop, and auto-configure Caddy reverse proxy with clean *.localhost URLs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages