Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
__pycache__/
*.pyc
*.egg-info/
dist/
build/
18 changes: 9 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# devmux — Agent Reference
# devlauncher — Agent Reference

Structured reference for AI agents and coding assistants. No prose.

---

## What it is

- **Package**: devmux
- **Package**: devlauncher
- **Type**: Python CLI tool
- **Install**: `pip install devmux`
- **Install**: `pip install devlauncher`
- **Version**: 0.1.0
- **Python**: 3.9+
- **Purpose**: Starts multiple local dev services (e.g. API + frontend) from one terminal
- **Entry point**: `devmux.cli:main`
- **Entry point**: `devlauncher.cli:main`
- **Config file**: `dev.toml` (TOML format, auto-written on first run if absent)
- **External deps**: none on Python 3.11+; `tomli` required on 3.9–3.10

Expand All @@ -21,9 +21,9 @@ Structured reference for AI agents and coding assistants. No prose.
## CLI Usage

```
devmux # reads ./dev.toml, or runs auto-discovery if not present
devmux path/to/dev.toml # explicit config path
python -m devmux # equivalent to devmux
devlauncher # reads ./dev.toml, or runs auto-discovery if not present
devlauncher path/to/dev.toml # explicit config path
python -m devlauncher # equivalent to devlauncher
```

No flags or subcommands. Single optional positional argument: path to a TOML config file.
Expand Down Expand Up @@ -120,7 +120,7 @@ Runs sequentially before services start.

- Ctrl+C sends SIGTERM to all service subprocesses
- If a process does not exit within 5 seconds, SIGKILL is sent
- All processes are waited on before devmux exits
- All processes are waited on before devlauncher exits
- Windows: uses `process.terminate()` (no SIGTERM/SIGKILL distinction)
- Exit codes: 0 on clean shutdown, 1 on config error

Expand All @@ -131,5 +131,5 @@ Runs sequentially before services start.
- Auto-discovery detects at most one frontend and one backend service per project
- Monorepo layouts (`packages/`, `apps/` with 2+ subdirs) produce a warning; manual `dev.toml` recommended
- UNCERTAIN confidence services (score 1–2) are skipped automatically
- `devmux init` is **not implemented** — do not suggest it or reference it
- `devlauncher init` is **not implemented** — do not suggest it or reference it
- `tomli` must be installed manually on Python 3.9–3.10 if not declared in the consuming project's own dependencies
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# devmux
# devlauncher

Start all your dev services with one command.

## The Problem

Every multi-service project means opening multiple terminals, remembering startup commands, and
dealing with silent port conflicts. devmux runs everything in one place.
dealing with silent port conflicts. devlauncher runs everything in one place.

## Install

```
pip install devmux
pip install devlauncher
```

Requires Python 3.9+.
Expand All @@ -21,10 +21,10 @@ Requires Python 3.9+.

```
cd my-project
devmux
devlauncher
```

devmux scans your project, detects services (Vite, FastAPI, Django, etc.), shows you what it found,
devlauncher scans your project, detects services (Vite, FastAPI, Django, etc.), shows you what it found,
and asks once to confirm. On confirmation it writes a `dev.toml` and starts everything. It never
asks again.

Expand All @@ -46,7 +46,7 @@ env = { VITE_API_URL = "http://localhost:{api.port}" }
Then run:

```
devmux
devlauncher
```

## How It Works
Expand Down
30 changes: 15 additions & 15 deletions docs/auto-discovery.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Auto-Discovery in devmux
# Auto-Discovery in devlauncher

## What it is

Auto-discovery is devmux's ability to start your services without a `dev.toml`.
Auto-discovery is devlauncher's ability to start your services without a `dev.toml`.
It scans the project directory, collects signals, and infers which services exist,
what commands to run, and which ports to use.

Expand All @@ -20,9 +20,9 @@ common case and honest when it isn't sure.
| Non-standard structure or rare frameworks | ~40–60% |
| Monorepos with 3+ services | Low — use `dev.toml` |

**When accuracy is low, devmux tells you.** It will not silently start the wrong
**When accuracy is low, devlauncher tells you.** It will not silently start the wrong
thing. Low-confidence results surface a warning and a suggestion to run
`devmux init` to generate a `dev.toml` you can review.
`devlauncher init` to generate a `dev.toml` you can review.

---

Expand All @@ -34,7 +34,7 @@ accumulated evidence. More signals = higher confidence.

### Step 1: Scan candidate directories

devmux looks for directories that could be services. It scans:
devlauncher looks for directories that could be services. It scans:
- All immediate subdirectories of the project root
- The project root itself

Expand All @@ -44,7 +44,7 @@ Directories that are clearly not services are skipped:

### Step 2: Collect signals per directory

For each candidate directory, devmux looks for known files and content:
For each candidate directory, devlauncher looks for known files and content:

**Frontend signals**

Expand Down Expand Up @@ -73,7 +73,7 @@ For each candidate directory, devmux looks for known files and content:

### Step 3: Score and assign roles

Each directory gets a score for each role (frontend / backend). devmux assigns
Each directory gets a score for each role (frontend / backend). devlauncher assigns
the **highest scoring directory per role**.

Confidence thresholds:
Expand Down Expand Up @@ -103,7 +103,7 @@ Default ports per framework:
| Rust (axum, actix) | 8080 |
| Go | 8080 |

If devmux cannot determine the framework, it defaults to `3000` (backend) or
If devlauncher cannot determine the framework, it defaults to `3000` (backend) or
`5173` (frontend) and tells you.

### Step 5: Infer commands
Expand All @@ -125,11 +125,11 @@ Commands are inferred from framework detection:

## Failure modes

| Situation | What devmux does |
| Situation | What devlauncher does |
|---|---|
| Only one service found | Starts it, warns that the other role was not detected |
| Two directories both look like frontends | Picks the one with higher confidence, warns about the other |
| No services detected at all | Exits with a helpful message: "no services detected — run `devmux init`" |
| No services detected at all | Exits with a helpful message: "no services detected — run `devlauncher init`" |
| Framework detected but command uncertain | Uses best-guess command, prints it so the user can verify |
| Monorepo with 3+ services | Detects multiple, warns that `dev.toml` is recommended |

Expand All @@ -146,18 +146,18 @@ Auto-discovery is designed for the **common case**. Use a `dev.toml` when:
- You need `{service.port}` cross-references between services
- Auto-discovery picks the wrong thing

Run `devmux init` to generate a `dev.toml` pre-filled with what auto-discovery
Run `devlauncher init` to generate a `dev.toml` pre-filled with what auto-discovery
found. Edit it from there.

---

## Scope: what devmux will NOT auto-discover
## Scope: what devlauncher will NOT auto-discover

These are out of scope by design:

- **Docker Compose stacks** — use `docker compose up`
- **Kubernetes / Tilt / Skaffold** — wrong abstraction level
- **Services on remote hosts** — devmux is for local dev only
- **Services on remote hosts** — devlauncher is for local dev only
- **Database processes** (Postgres, Redis, etc.) — include in `dev.toml` manually
- **Arbitrary scripts** with no recognised framework signal
- **Monorepos with packages/** structure (pnpm workspaces, turborepo)
Expand All @@ -168,7 +168,7 @@ These are out of scope by design:
## Summary

Auto-discovery is not a permutation engine. It is a signal collector with a
confidence model. It is most useful for developers who want to run `devmux`
confidence model. It is most useful for developers who want to run `devlauncher`
in a new project without writing any config. For anything beyond a
two-service stack with a conventional layout, `dev.toml` is the right tool —
and `devmux init` makes writing it trivial.
and `devlauncher init` makes writing it trivial.
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[project]
name = "devmux"
name = "devlauncher"
version = "0.1.0"
description = "Start all your dev services with one command"
readme = "README.md"
Expand Down Expand Up @@ -33,12 +33,12 @@ dependencies = [
]

[project.urls]
Homepage = "https://github.com/the-non-expert/devmux"
Repository = "https://github.com/the-non-expert/devmux"
"Bug Tracker" = "https://github.com/the-non-expert/devmux/issues"
Homepage = "https://github.com/the-non-expert/devlauncher"
Repository = "https://github.com/the-non-expert/devlauncher"
"Bug Tracker" = "https://github.com/the-non-expert/devlauncher/issues"

[project.scripts]
devmux = "devmux.cli:main"
devlauncher = "devlauncher.cli:main"

[tool.setuptools.packages.find]
where = ["src"]
File renamed without changes.
12 changes: 6 additions & 6 deletions src/devmux/cli.py → src/devlauncher/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""devmux CLI entry point.
"""devlauncher CLI entry point.

Usage:
devmux # reads dev.toml, or auto-discovers services
devmux path/to/dev.toml
python -m devmux
devlauncher # reads dev.toml, or auto-discovers services
devlauncher path/to/dev.toml
python -m devlauncher
"""

import sys
Expand Down Expand Up @@ -135,7 +135,7 @@ def main() -> None:
sys.exit(0)

if answer in ("n", "no"):
print("\nRun 'devmux init' to configure services manually.")
print("\nRun 'devlauncher init' to configure services manually.")
sys.exit(0)

# ── Write dev.toml ─────────────────────────────────────────────────────
Expand All @@ -152,7 +152,7 @@ def main() -> None:
services = _resolve_services(services)

# Print startup header
print(f"\n{BOLD}devmux{RESET}")
print(f"\n{BOLD}devlauncher{RESET}")
for i, svc in enumerate(services):
color = _PALETTE[i % len(_PALETTE)]
print(f" {color}{BOLD}[{svc.name.upper()}]{RESET} http://localhost:{svc.port}")
Expand Down
File renamed without changes.
18 changes: 9 additions & 9 deletions src/devmux/discovery.py → src/devlauncher/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
match per role as a list of Service objects ready for the runner.

Accuracy is intentional: confident detections start silently, uncertain ones
warn the user, and anything too ambiguous recommends `devmux init` instead.
warn the user, and anything too ambiguous recommends `devlauncher init` instead.
"""

import json
Expand Down Expand Up @@ -370,7 +370,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
if _is_monorepo(root_path):
warnings.append(
"Monorepo structure detected (packages/ or apps/ with multiple subdirs). "
"Auto-discovery may be inaccurate. Run 'devmux init' to create a dev.toml."
"Auto-discovery may be inaccurate. Run 'devlauncher init' to create a dev.toml."
)

# Scan candidate directories (immediate subdirs + root itself)
Expand Down Expand Up @@ -409,7 +409,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
warnings.append(
f"Multiple frontend candidates found: '{top.path.name}' (score {top.frontend_score}) "
f"and '{second.path.name}' (score {second.frontend_score}). "
f"Using '{top.path.name}'. Run 'devmux init' if wrong."
f"Using '{top.path.name}'. Run 'devlauncher init' if wrong."
)

if len(backend_candidates) >= 2:
Expand All @@ -418,7 +418,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
warnings.append(
f"Multiple backend candidates found: '{top.path.name}' (score {top.backend_score}) "
f"and '{second.path.name}' (score {second.backend_score}). "
f"Using '{top.path.name}'. Run 'devmux init' if wrong."
f"Using '{top.path.name}'. Run 'devlauncher init' if wrong."
)

services: List[Service] = []
Expand All @@ -434,7 +434,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
if confidence == UNCERTAIN:
svc_warnings.append(
f"Low confidence for frontend in '{fs.path.name}' (score {fs.frontend_score}). "
"Run 'devmux init' to verify."
"Run 'devlauncher init' to verify."
)
services.append(Service(
name="web",
Expand All @@ -459,7 +459,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
if confidence == UNCERTAIN:
svc_warnings.append(
f"Low confidence for backend in '{bs.path.name}' (score {bs.backend_score}). "
"Run 'devmux init' to verify."
"Run 'devlauncher init' to verify."
)
services.append(Service(
name="api",
Expand All @@ -478,7 +478,7 @@ def discover_services(root: Optional[str] = None) -> Tuple[List[Service], List[s
if total >= 4:
warnings.append(
f"{total} potential services found. "
"For complex projects, 'devmux init' is strongly recommended."
"For complex projects, 'devlauncher init' is strongly recommended."
)

return services, warnings
Expand All @@ -492,8 +492,8 @@ def services_to_toml(services: List[Service]) -> str:
runs skip discovery entirely and use this file directly.
"""
lines: List[str] = [
"# Generated by devmux auto-discovery.",
"# Edit freely - devmux will use this file on all future runs.",
"# Generated by devlauncher auto-discovery.",
"# Edit freely - devlauncher will use this file on all future runs.",
"",
]
for svc in services:
Expand Down
2 changes: 1 addition & 1 deletion src/devmux/installer.py → src/devlauncher/installer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dependency install detection and execution.

Before starting services, devmux checks whether dependencies are present
Before starting services, devlauncher checks whether dependencies are present
and runs the appropriate install command if not.

Detection heuristics:
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Tests for Service.install_cmd parsing and serialization."""
import textwrap
import pytest
from devmux.config import Service, load_config
from devmux.discovery import services_to_toml
from devlauncher.config import Service, load_config
from devlauncher.discovery import services_to_toml


# ── Service dataclass ──────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion tests/test_discovery_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from devmux.discovery import discover_services, services_to_toml
from devlauncher.discovery import discover_services, services_to_toml


def _write_pkg_json(directory: Path, scripts=None, deps=None) -> None:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import pytest

from devmux.config import Service
from devmux.installer import needs_install, run_install
from devlauncher.config import Service
from devlauncher.installer import needs_install, run_install


# ── Helpers ────────────────────────────────────────────────────────────────────
Expand Down
Loading