# dev-cockpit

A reusable, domain-neutral terminal UI dev cockpit. One window for the long-running processes you babysit during development: code, logs, health, and the keystrokes to fix things when they break.

```
┌── Targets ── Output ── Health ── Help ────────────────────────────────────┐
│ Targets                         │ Actions for `web` (docker)             │
│   ▸ web      [docker]          │   [r] Restart web      (default)       │
│     ticker   [process]         │   [L] Tail logs                        │
│     api                        │                                        │
│                                │                                        │
│                                │                                        │
└────────────────────────────────┴────────────────────────────────────────┘
                                              [↑↓] select  [r] restart  [L] tail
```

## What's in the box

- **Four-pane TUI** — Targets / Output / Health / Help, tab-cycle navigation, filter + search modals, narrow-terminal layouts.
- **Processes** — long-running commands spawned once on boot; stdout/stderr streamed into Output, tagged by id, with shell-out execution so pipes/redirects/quoted args just work.
- **Docker log tailing** — stream `docker compose logs -f <services>` into Output with regex-driven highlights (configurable severity per pattern).
- **Health framework** — five built-in check types (`container-running`, `port-open`, `http-ok`, `file-exists`, `exec-zero`) plus profile-contributed ones; each gets a row + a single-keystroke remediation.
- **Mount overlay manager** — `dev-cockpit mount` writes a docker-compose overlay + manifest, with an interactive picker, branch / dirty / broken-symlink status table, and profile hooks for restart + dependency-restore on clear.
- **Actions library** — `config.actions[]` + `Profile.actions` (declarative shell-out OR programmatic `invoke` callbacks). Reach via the vim-style `:` palette or single-keystroke binding scoped to a Targets row.
- **Profile interface** — drop-in adapter so a domain-specific tool plugs in its discoverer / repos / health / actions / commands / cockpit handlers without forking the shell.
- **Live markdown Help** — pages render inside the cockpit; profiles can layer their own docs over the generic ones.
- **Native OS notifications** — fire on state transitions only (no spam). Detached helper, never blocks process exit.
- **A11y** — `NO_COLOR` strips all styles; semantic glyphs (`✓` `⚠` `✗` `●` `○`) carry severity / status info without color.

## Install

```sh
npm install -g dev-cockpit
```

Or `npm link` from a checkout for development.

Requires Node ≥ 20.12 (chalk + inquirer use `node:util.styleText`). The bin guards this at startup with a friendly error message.

## Quick start

```sh
cd your-project
dev-cockpit init-config -i    # interactive wizard, 8 steps with auto-detection
dev-cockpit doctor             # validate + print initial health
dev-cockpit dev                # boot the cockpit
```

The wizard sniffs your project: docker compose services from `compose.yaml`, long-running processes from `package.json` (`dev`, `watch`), one-shot actions from the rest of your scripts (`test`, `build`, `lint`, `format`), workspaces as repo entries. Most prompts just need an enter.

## Configuration

Single file, `cockpit.yaml` at your project root. Schema enforced by zod (`version: 2`). Full reference: [`docs/config-reference.md`](./docs/config-reference.md). Minimal example:

```yaml
version: 2
appName: my-app

processes:
  - id: vite
    command: npm run dev

actions:
  - id: test
    label: Run tests
    command: npm test
    key: t                 # `:` palette + single-keystroke binding
  - id: lint
    label: Lint
    command: npm run lint
    key: l

docker:
  composeFile: compose.yaml
  services:
    - { name: db }
    - { name: cache }

health:
  - id: app-up
    label: app responsive
    type: http-ok
    url: http://localhost:3000/health
    severity: error
    remediation:
      key: R
      label: restart app
      command: docker compose restart app

notifications:
  enabled: true
```

Wrappers upgrading from v1 (pre-rename) configs run `dev-cockpit migrate-config` to persist the v1 → v2 migration (auto-applied in memory on load with a stderr warning).

## Config discovery

dev-cockpit resolves cockpit.yaml in this order:

1. `--config <path>` CLI flag
2. `DEV_COCKPIT_CONFIG` env var
3. `<cwd>/cockpit.yaml`
4. `$XDG_DATA_HOME/dev-cockpit/manifest.json` lookup keyed by canonical `cwd`

All paths inside cockpit.yaml resolve against the **workspace** (your cwd, or whatever a profile's `discoverer`
returns) — never against the config file's directory. Lets one config serve multiple wrappers without rewriting
paths.

To use a config that lives outside your project:

	cd ~/code/my-wrapper
	dev-cockpit link ~/configs/my-wrapper.cockpit.yaml
	dev-cockpit dev            # picks up the mapping

	dev-cockpit link list      # show all mappings
	dev-cockpit link remove    # unregister the current cwd

## Profiles

A profile is a domain-specific bundle that plugs into dev-cockpit's lifecycle. It contributes any combination of:

- `discoverer`        — workspace root resolution
- `reposProvider`     — Targets-pane entries (kind: `repo`)
- `healthChecks`      — pre-built `HealthCheck[]` (programmatic, beyond the five built-ins)
- `setupCli`          — extra CLI commands (the profile's own `select`, `release`, etc.)
- `helpSources`       — markdown docs that layer over the generic Help
- `defaultHelpPage`   — landing page slug for the Help tab
- `defaultPane`       — which tab the cockpit lands on (overrides `config.defaultPane`)
- `configSchemaExt`   — zod schema validating the profile's `profile.<appName>` namespace
- `actions`           — static action defaults merged into the registry
- `wizardSteps`       — extra `init-config -i` steps; output lands under `profile.<appName>:`
- `mountCandidatesProvider` — auto-detected bind-mount candidates
- `mountSymlinks`     — IDE-facing symlink strategy for mount apply / clear
- `onMountApply` / `onMountClear` — post-mount lifecycle hooks (restart docker, run installer, etc.)
- `mountStatusEnricher` — extra columns in `mount status`
- `boot`              — async lifecycle hook returning Cockpit pane handlers + dynamic `actions` (per-row invoke callbacks) + `subscribeFsEvents` + `cleanup`

A profile is its own npm package — its bin entry composes `buildCli({ profile })` and that's it.

```ts
import type { Profile } from 'dev-cockpit';

export const myProfile: Profile = {
  appName: 'my-app',
  healthChecks: [...],
  boot: ({ config, workspaceRoot }) => {
    const manager = new WatcherManager(...);
    manager.start();
    return {
      // Per-repo programmatic actions: closure over the live WatcherManager.
      actions: managedRepos.map((repo) => ({
        id: `repo-rebuild:${repo.name}`,
        label: `Rebuild ${repo.name}`,
        scope: `repos:${repo.name}`,
        key: 'r',
        invoke: () => manager.rebuild(repo.name),
      })),
      subscribeFsEvents: (listener) => manager.subscribeFsEvents(listener),
      cleanup: () => manager.stop(),
    };
  },
};

// Bin entry — that's it.
import { buildCli } from 'dev-cockpit';
buildCli({ profile: myProfile }).parse(process.argv);
```

## Documentation

| Topic | Link |
|---|---|
| Getting started | [`docs/getting-started.md`](./docs/getting-started.md) |
| Commands | [`docs/commands.md`](./docs/commands.md) |
| `init-config` wizard | [`docs/init-config.md`](./docs/init-config.md) |
| Panes | [`docs/panes.md`](./docs/panes.md) |
| Processes | [`docs/processes.md`](./docs/processes.md) |
| Health | [`docs/health.md`](./docs/health.md) |
| Mounts | [`docs/mount.md`](./docs/mount.md) |
| Notifications | [`docs/notifications.md`](./docs/notifications.md) |
| Config reference | [`docs/config-reference.md`](./docs/config-reference.md) |
| ADRs | [`adr/`](./adr/) |
| Changelog | [`CHANGELOG.md`](./CHANGELOG.md) |
| License | MIT |

## Status

Pre-1.0. The public surface (`Profile` interface, `buildCli`, the four core commands plus `migrate-config`, `cockpit.yaml` schema with `version: 2`) is stable enough for first consumers to depend on; expect minor churn until the rest of the post-1.0 roadmap items land. See `CHANGELOG.md` for what's shipped.
