dev-cockpit
v0.4.2
Published
A reusable, domain-neutral terminal UI dev cockpit — tabbed pane shell, watcher streaming, optional Docker log tail with highlights, health framework with one-keystroke remediations, transition-only OS notifications, live-markdown Help. Profiles extend it
Downloads
2,309
Maintainers
Readme
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] tailWhat'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 mountwrites 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 programmaticinvokecallbacks). 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_COLORstrips all styles; semantic glyphs (✓⚠✗●○) carry severity / status info without color.
Install
npm install -g dev-cockpitOr 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
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 cockpitThe 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. Minimal example:
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: trueWrappers 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:
--config <path>CLI flagDEV_COCKPIT_CONFIGenv var<cwd>/cockpit.yaml$XDG_DATA_HOME/dev-cockpit/manifest.jsonlookup keyed by canonicalcwd
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 cwdProfiles
A profile is a domain-specific bundle that plugs into dev-cockpit's lifecycle. It contributes any combination of:
discoverer— workspace root resolutionreposProvider— Targets-pane entries (kind:repo)healthChecks— pre-builtHealthCheck[](programmatic, beyond the five built-ins)setupCli— extra CLI commands (the profile's ownselect,release, etc.)helpSources— markdown docs that layer over the generic HelpdefaultHelpPage— landing page slug for the Help tabdefaultPane— which tab the cockpit lands on (overridesconfig.defaultPane)configSchemaExt— zod schema validating the profile'sprofile.<appName>namespaceactions— static action defaults merged into the registrywizardSteps— extrainit-config -isteps; output lands underprofile.<appName>:mountCandidatesProvider— auto-detected bind-mount candidatesmountSymlinks— IDE-facing symlink strategy for mount apply / clearonMountApply/onMountClear— post-mount lifecycle hooks (restart docker, run installer, etc.)mountStatusEnricher— extra columns inmount statusboot— async lifecycle hook returning Cockpit pane handlers + dynamicactions(per-row invoke callbacks) +subscribeFsEvents+cleanup
A profile is its own npm package — its bin entry composes buildCli({ profile }) and that's it.
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 |
| Commands | docs/commands.md |
| init-config wizard | docs/init-config.md |
| Panes | docs/panes.md |
| Processes | docs/processes.md |
| Health | docs/health.md |
| Mounts | docs/mount.md |
| Notifications | docs/notifications.md |
| Config reference | docs/config-reference.md |
| ADRs | adr/ |
| Changelog | 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.
