dev-cockpit 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +86 -29
  3. package/bin/dev-cockpit.mjs +26 -4
  4. package/dist/actions/builtin.d.ts +25 -0
  5. package/dist/actions/builtin.d.ts.map +1 -0
  6. package/dist/actions/dispatch.d.ts +21 -0
  7. package/dist/actions/dispatch.d.ts.map +1 -0
  8. package/dist/actions/registry.d.ts +11 -0
  9. package/dist/actions/registry.d.ts.map +1 -0
  10. package/dist/actions/types.d.ts +76 -0
  11. package/dist/actions/types.d.ts.map +1 -0
  12. package/dist/buildCli.d.ts.map +1 -1
  13. package/dist/chunk-6XGHLLYT.js +46 -0
  14. package/dist/chunk-6XGHLLYT.js.map +7 -0
  15. package/dist/chunk-C4GFJDMG.js +79 -0
  16. package/dist/chunk-C4GFJDMG.js.map +7 -0
  17. package/dist/chunk-Q6677JQF.js +32609 -0
  18. package/dist/chunk-Q6677JQF.js.map +7 -0
  19. package/dist/chunk-VN6UILQW.js +1460 -0
  20. package/dist/chunk-VN6UILQW.js.map +7 -0
  21. package/dist/cockpit/Cockpit.d.ts +6 -0
  22. package/dist/cockpit/Cockpit.d.ts.map +1 -1
  23. package/dist/cockpit/Footer.d.ts +6 -4
  24. package/dist/cockpit/Footer.d.ts.map +1 -1
  25. package/dist/cockpit/TabBar.d.ts.map +1 -1
  26. package/dist/cockpit/hooks/useGlobalKeys.d.ts +15 -15
  27. package/dist/cockpit/hooks/useGlobalKeys.d.ts.map +1 -1
  28. package/dist/cockpit/hooks/useTerminalWidth.d.ts +12 -0
  29. package/dist/cockpit/hooks/useTerminalWidth.d.ts.map +1 -0
  30. package/dist/cockpit/panes/CommandModal.d.ts +18 -0
  31. package/dist/cockpit/panes/CommandModal.d.ts.map +1 -0
  32. package/dist/cockpit/panes/Help.d.ts.map +1 -1
  33. package/dist/cockpit/panes/Output.d.ts +7 -0
  34. package/dist/cockpit/panes/Output.d.ts.map +1 -1
  35. package/dist/cockpit/panes/Repos.d.ts.map +1 -1
  36. package/dist/cockpit/state/store.d.ts +14 -11
  37. package/dist/cockpit/state/store.d.ts.map +1 -1
  38. package/dist/cockpit/tab-state.d.ts +12 -0
  39. package/dist/cockpit/tab-state.d.ts.map +1 -1
  40. package/dist/commands/dev.d.ts.map +1 -1
  41. package/dist/commands/doctor.d.ts.map +1 -1
  42. package/dist/commands/init-config-wizard.d.ts +103 -2
  43. package/dist/commands/init-config-wizard.d.ts.map +1 -1
  44. package/dist/commands/init-config.d.ts +2 -0
  45. package/dist/commands/init-config.d.ts.map +1 -1
  46. package/dist/commands/link.d.ts +20 -0
  47. package/dist/commands/link.d.ts.map +1 -0
  48. package/dist/commands/migrate-config.d.ts +18 -0
  49. package/dist/commands/migrate-config.d.ts.map +1 -0
  50. package/dist/commands/mount.d.ts +17 -32
  51. package/dist/commands/mount.d.ts.map +1 -1
  52. package/dist/core/config-discovery.d.ts +39 -0
  53. package/dist/core/config-discovery.d.ts.map +1 -0
  54. package/dist/core/config.d.ts +73 -5
  55. package/dist/core/config.d.ts.map +1 -1
  56. package/dist/core/manifest.d.ts +47 -0
  57. package/dist/core/manifest.d.ts.map +1 -0
  58. package/dist/core/migrations.d.ts +33 -0
  59. package/dist/core/migrations.d.ts.map +1 -0
  60. package/dist/core/subprocess.d.ts +20 -0
  61. package/dist/core/subprocess.d.ts.map +1 -1
  62. package/dist/core/types.d.ts +36 -12
  63. package/dist/core/types.d.ts.map +1 -1
  64. package/dist/devtools-YXMW6JJ6.js +3720 -0
  65. package/dist/devtools-YXMW6JJ6.js.map +7 -0
  66. package/dist/docker/highlights.d.ts +14 -4
  67. package/dist/docker/highlights.d.ts.map +1 -1
  68. package/dist/docker/logs.d.ts +3 -2
  69. package/dist/docker/logs.d.ts.map +1 -1
  70. package/dist/health/builtin.d.ts.map +1 -1
  71. package/dist/index.d.ts +14 -3
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +92944 -53
  74. package/dist/index.js.map +7 -0
  75. package/dist/ink.js +38 -1
  76. package/dist/ink.js.map +7 -0
  77. package/dist/link-HXNII7EU.js +65 -0
  78. package/dist/link-HXNII7EU.js.map +7 -0
  79. package/dist/mount/compose.d.ts +21 -0
  80. package/dist/mount/compose.d.ts.map +1 -0
  81. package/dist/mount/discovery.d.ts +35 -0
  82. package/dist/mount/discovery.d.ts.map +1 -0
  83. package/dist/mount/git-status.d.ts +12 -0
  84. package/dist/mount/git-status.d.ts.map +1 -0
  85. package/dist/mount/manifest.d.ts +16 -0
  86. package/dist/mount/manifest.d.ts.map +1 -0
  87. package/dist/mount/symlinks.d.ts +30 -0
  88. package/dist/mount/symlinks.d.ts.map +1 -0
  89. package/dist/mount/types.d.ts +60 -0
  90. package/dist/mount/types.d.ts.map +1 -0
  91. package/dist/react.js +35 -1
  92. package/dist/react.js.map +7 -0
  93. package/dist/runCockpit.d.ts +3 -0
  94. package/dist/runCockpit.d.ts.map +1 -1
  95. package/docs/commands.md +29 -16
  96. package/docs/config-reference.md +115 -11
  97. package/docs/getting-started.md +9 -6
  98. package/docs/index.md +5 -1
  99. package/docs/init-config.md +34 -8
  100. package/docs/mount.md +198 -25
  101. package/docs/notifications.md +14 -13
  102. package/docs/panes.md +36 -15
  103. package/docs/processes.md +42 -0
  104. package/package.json +93 -90
  105. package/dist/buildCli.js +0 -107
  106. package/dist/cli.js +0 -2
  107. package/dist/cockpit/Cockpit.js +0 -73
  108. package/dist/cockpit/Footer.js +0 -33
  109. package/dist/cockpit/TabBar.js +0 -12
  110. package/dist/cockpit/help/content.js +0 -22
  111. package/dist/cockpit/help/loader.js +0 -118
  112. package/dist/cockpit/help/renderer.js +0 -35
  113. package/dist/cockpit/help/types.js +0 -1
  114. package/dist/cockpit/hooks/useCockpitStore.js +0 -5
  115. package/dist/cockpit/hooks/useGlobalKeys.js +0 -173
  116. package/dist/cockpit/panes/FilterModal.js +0 -22
  117. package/dist/cockpit/panes/Health.js +0 -30
  118. package/dist/cockpit/panes/Help.js +0 -81
  119. package/dist/cockpit/panes/Output.js +0 -108
  120. package/dist/cockpit/panes/Repos.js +0 -48
  121. package/dist/cockpit/panes/SearchModal.js +0 -31
  122. package/dist/cockpit/state/store.js +0 -111
  123. package/dist/cockpit/tab-state.js +0 -7
  124. package/dist/commands/dev.js +0 -158
  125. package/dist/commands/doctor.js +0 -66
  126. package/dist/commands/init-config-wizard.js +0 -818
  127. package/dist/commands/init-config.js +0 -131
  128. package/dist/commands/mount.js +0 -150
  129. package/dist/core/config.js +0 -152
  130. package/dist/core/logger.js +0 -38
  131. package/dist/core/notifier.js +0 -100
  132. package/dist/core/paths.js +0 -18
  133. package/dist/core/subprocess.js +0 -82
  134. package/dist/core/types.js +0 -1
  135. package/dist/docker/highlights.js +0 -79
  136. package/dist/docker/logs.js +0 -172
  137. package/dist/docker/restart.js +0 -45
  138. package/dist/docker/stack-trace.js +0 -44
  139. package/dist/health/builtin.js +0 -144
  140. package/dist/health/context.js +0 -31
  141. package/dist/health/notify-resolver.js +0 -28
  142. package/dist/health/registry.js +0 -64
  143. package/dist/health/remediations.js +0 -41
  144. package/dist/health/runner.js +0 -22
  145. package/dist/health/scheduler.js +0 -107
  146. package/dist/health/types.js +0 -1
  147. package/dist/health/useHealth.js +0 -122
  148. package/dist/lint/reactive.js +0 -131
  149. package/dist/runCockpit.js +0 -75
  150. package/dist/watchers/manager.js +0 -239
  151. package/dist/watchers/path-mapper.js +0 -29
  152. package/dist/watchers/types.js +0 -9
  153. package/docs/watchers.md +0 -27
@@ -1,20 +1,23 @@
1
1
  # Getting started
2
2
 
3
- Run `dev-cockpit dev` from a directory that contains a `cockpit.yaml`. The TUI boots into the **Repos** tab and starts any watchers + health checks declared in your config.
3
+ Run `dev-cockpit dev` from a directory that contains a `cockpit.yaml`. The TUI boots into the **Targets** tab (or whatever `config.defaultPane` points at) and starts any processes + health checks declared in your config.
4
+
5
+ Don't have a `cockpit.yaml` yet? `dev-cockpit init-config -i` walks you through it in eight steps with auto-detection from `package.json` / `compose.yaml`.
4
6
 
5
7
  ## At a glance
6
8
 
7
- - **Cycle tabs** — `←` / `→` (or `Tab` / `Shift-Tab`)
9
+ - **Cycle tabs** — `←` / `→` (or `Tab` / `Shift-Tab`). Empty tabs (Targets with no rows, Health with no checks) auto-hide.
8
10
  - **Quit** — `q`
9
11
  - **Toggle notifications for this session** — `n`
12
+ - **Open the `:` command palette** — `:` (any tab). Type to filter actions, ↑↓ to navigate, Enter to fire.
10
13
 
11
- Every tab fills the full terminal. Modals (filter, search) render in place of the active pane.
14
+ Every tab fills the full terminal. Modals (filter, search, `:` palette) render in place of the active pane.
12
15
 
13
16
  ## What the panes do
14
17
 
15
- - **Repos** — list of repos and (optionally) Docker services with watcher / lint status; per-row actions live here.
16
- - **Output** — streaming logs from watchers and Docker services, with filter and search modals.
17
- - **Health** — workspace health checks; each check has a one-keystroke remediation.
18
+ - **Targets** — repos, docker services, and processes as "runnable units". Split layout (≥200 cols) shows the row list on the left and the actions registered for the selected row on the right; under narrow terminals the panels stack vertically. Built-in `[r]` restarts docker containers and processes; declare more via `config.actions[]` (or a profile's `actions`).
19
+ - **Output** — streaming logs from processes and Docker services. Filter + search modals (`f` / `/`); open the most recent error in `$EDITOR` with `e`. Severity glyphs (`⚠`, `✗`) keep lines scannable without color.
20
+ - **Health** — workspace health checks; each row has a one-keystroke remediation.
18
21
  - **Help** — this guide. `j` / `k` to flip pages, `↑` / `↓` to scroll.
19
22
 
20
23
  ## Try it on a fixture
package/docs/index.md CHANGED
@@ -4,10 +4,14 @@ This is the in-cockpit Help tab. The pages below explain what each pane does, ho
4
4
 
5
5
  - [Getting started](./getting-started.md) — first run and what to expect
6
6
  - [Panes](./panes.md) — Repos, Output, Health, Help
7
- - [Watchers](./watchers.md) — long-running commands streamed into Output
7
+ - [Processes](./processes.md) — long-running commands streamed into Output
8
8
  - [Health](./health.md) — checks and one-keystroke remediations
9
9
  - [Commands](./commands.md) — the four core CLI commands + profile `setupCli`
10
10
  - [init-config](./init-config.md) — scaffold a starter `cockpit.yaml`
11
11
  - [Mount](./mount.md) — host ↔ container bind-mount overlay
12
12
  - [Notifications](./notifications.md) — transition-only OS notifications
13
13
  - [Config reference](./config-reference.md) — `cockpit.yaml` schema
14
+
15
+ ## Accessibility
16
+
17
+ The cockpit honours the [`NO_COLOR`](https://no-color.org) environment variable — set `NO_COLOR=1` (or any non-empty value) and all ANSI colour escapes are stripped. Semantic glyphs (`✓` `⚠` `✗` `●` `○` `▸`) carry the same information in the absence of colour, so panes remain scannable without a terminal palette or for screen-reader users.
@@ -17,29 +17,55 @@ dev-cockpit init-config -i [-f|--force] # interactive wizard
17
17
 
18
18
  ## Static template
19
19
 
20
- `version: 1` and `appName` are required and uncommented. Everything else (watchers, repos, docker, highlights, health, help, notifications, mounts) ships as a commented example. You uncomment what you need. Best when you already know what you want — fastest path from scratch to "edit me".
20
+ `version: 2` and `appName` are required and uncommented. Everything else (processes, repos, docker, highlights, health, actions, help, notifications, mounts) ships as a commented example. Best when you already know what you want — fastest path from scratch to "edit me".
21
21
 
22
22
  ## Interactive wizard
23
23
 
24
- The recommended path the first time you set the cockpit up on a project. It walks through seven steps, with project sniffing pre-populating sensible defaults so you usually just press enter:
24
+ The recommended path the first time you set the cockpit up on a project. Eight steps, with project sniffing pre-populating sensible defaults so you usually just press enter:
25
25
 
26
26
  | Step | Section | What it asks + auto-detects |
27
27
  |------|---------|------------------------------|
28
28
  | 1 | **App name** | Defaults to `package.json` `name`, then the cwd basename. |
29
29
  | 2 | **Docker** | Detects `compose.yaml` / `docker-compose.yml` and parses its services. Offers them as a multi-select. Skip entirely for non-compose projects. |
30
- | 3 | **Watchers** | Surfaces `package.json` scripts (`dev`, `watch`, `start`, `serve`) as pre-checked candidates. Anything that runs forever and writes to stdout — `vite`, `tsc --watch`, `php artisan serve`, `tail -f storage/logs/laravel.log` — fits here. |
31
- | 4 | **Repos** | Detects npm/yarn/pnpm workspaces; falls back to a single `.` entry. Each entry becomes a row in the Repos pane (with `r/w/l` keystroke actions when a profile bundle wires them). |
32
- | 5 | **Highlights** | Regex patterns that re-colour matching Output lines. `error`-severity matches additionally feed the Recent Errors strip + OS notifications. |
33
- | 6 | **Health** | Reframed as test-and-fix pairs. Pick one of the five built-in types (`container-running`, `port-open`, `http-ok`, `file-exists`, `exec-zero`), supply the type-specific arg(s), then bind a single-keystroke remediation. Add as many checks as you like — same type repeats freely (e.g. `port-open` for 5432, 6379, 8108). `container-running` is hidden when no docker block was declared. |
34
- | 7 | **Notifications** | Toggle native OS notifications on state transitions. |
30
+ | 3 | **Processes** | Surfaces `package.json` scripts (`dev`, `watch`, `start`, `serve`) as pre-checked candidates. Anything that runs forever and writes to stdout — `vite`, `tsc --watch`, `php artisan serve`, `tail -f storage/logs/laravel.log` — fits here. |
31
+ | 4 | **Targets** | Detects npm/yarn/pnpm workspaces; falls back to a single `.` entry. Each becomes a row in the Targets pane. The way to attach keystrokes to a repo is the Actions step (step 7), with `scope: repos:<id>` + `key:`. |
32
+ | 5 | **Highlights** | Regex patterns that re-colour matching Output lines. `error`-severity matches additionally feed the Recent Errors strip + OS notifications. Patterns now apply to process stdout/stderr too — not just docker logs. |
33
+ | 6 | **Health** | Test-and-fix pairs. Pick one of the five built-in types (`container-running`, `port-open`, `http-ok`, `file-exists`, `exec-zero`), supply the type-specific arg(s), then bind a single-keystroke remediation. Add as many checks as you like — same type repeats freely. `container-running` is hidden when no docker block was declared. |
34
+ | 7 | **Actions** | One-shot shell commands surfaced through the `:` palette and/or bound to a single keypress. Auto-seeds from `package.json` scripts (`test`, `build`, `lint`, `format`, `typecheck`) with sensible default keys (`t`, `b`, `l`, `f`). Custom actions can be added inline. Distinct from processes (long-running) and health (state-checked). |
35
+ | 8 | **Notifications** | Toggle native OS notifications on state transitions. |
35
36
 
36
37
  After writing the file, the wizard offers to run `dev-cockpit doctor` immediately so you get instant feedback that the config parses + each declared health check has a baseline state.
37
38
 
38
39
  The wizard scaffolds a *working* config. Anything more advanced (custom triggers, `expectStatus`, exec args, mounts, profile namespaces) is a hand-edit on the generated file.
39
40
 
41
+ ## Profile-contributed steps
42
+
43
+ Profiles can append their own wizard steps via `Profile.wizardSteps?: WizardStep[]`. Each step runs after step 8 (Notifications) and before the final summary. Step output (YAML lines, returned from `run(ctx)`) lands under `profile.<appName>:` in the generated cockpit.yaml — the namespace defined by [ADR 0005](../adr/0005-strict-profile-namespace.md).
44
+
45
+ ```ts
46
+ import type { Profile, WizardStep } from 'dev-cockpit';
47
+
48
+ const gitlabStep: WizardStep = {
49
+ id: 'gitlab-config',
50
+ title: 'GitLab integration',
51
+ description: 'Where does this project live in your gitlab?',
52
+ async run(ctx) {
53
+ const host = await ctx.prompts.input({ message: 'GitLab host', default: 'gitlab.com' });
54
+ return ['gitlab:', ` host: ${host}`];
55
+ },
56
+ };
57
+
58
+ export const myProfile: Profile = {
59
+ appName: 'myapp',
60
+ wizardSteps: [gitlabStep],
61
+ };
62
+ ```
63
+
64
+ Step context (`ctx`) exposes the lazy-loaded `prompts` (inquirer subset), `hints` (sniffed project metadata), `workspaceRoot`, and a read-only snapshot of the core-step answers via `result`.
65
+
40
66
  ## Profile-driven defaults
41
67
 
42
- When a profile is active (e.g. `<profile-bin> init-config -i`), the wizard defaults `appName` to the profile's. Profile bundles can also register their own `init-config` via `setupCli(program)` if they want a domain-specific template — the generic command is skipped when the profile claims the slot.
68
+ When a profile is active (e.g. `<profile-bin> init-config -i`), the wizard defaults `appName` to the profile's `appName`. Profile bundles can also register their own `init-config` via `setupCli(program)` if they want a domain-specific template — the generic command is skipped when the profile claims the slot.
43
69
 
44
70
  ## Schema reference
45
71
 
package/docs/mount.md CHANGED
@@ -1,55 +1,228 @@
1
1
  # Mount
2
2
 
3
- `dev-cockpit mount` generates a Docker compose overlay describing host container bind mounts so you can swap a containerised dependency for a local checkout.
3
+ `dev-cockpit mount` generates a Docker compose overlay that bind-mounts host directories into container paths, so changes in a local checkout flow live into the running container without rebuilds.
4
4
 
5
- ## When to use it
5
+ The subsystem is fully generic — it works for any docker-compose project — but profiles can plug in domain logic (package discovery, post-apply restart hooks, IDE-facing symlinks, custom status enrichment) without touching dev-cockpit core.
6
6
 
7
- When you're developing a containerised app and want changes in a local checkout to flow live into the running container — e.g. mount your local `./packages/api` into `/srv/api` inside the `web` service.
7
+ ## Quick start
8
+
9
+ ```yaml
10
+ # cockpit.yaml
11
+ version: 2
12
+ appName: my-app
13
+ docker:
14
+ composeFile: docker-compose.yml
15
+ services:
16
+ - name: web
17
+
18
+ mounts:
19
+ - hostPath: ./packages/api
20
+ containerPath: /srv/api
21
+ ```
22
+
23
+ ```sh
24
+ dev-cockpit mount # interactive picker (or all when --quiet)
25
+ dev-cockpit mount status # active mounts with branch / dirty / broken-symlink
26
+ dev-cockpit mount clear # remove overlay + manifest + symlinks
27
+ ```
28
+
29
+ Apply the generated overlay alongside your existing compose file:
30
+
31
+ ```sh
32
+ docker compose -f docker-compose.yml -f docker-compose.dev-cockpit.yml up -d
33
+ ```
34
+
35
+ ## What gets written
36
+
37
+ | File | Default path | Configurable via |
38
+ |---|---|---|
39
+ | Compose overlay | `<workspace>/docker-compose.dev-cockpit.yml` | `config.mount.overlayPath` |
40
+ | Manifest | `<stateDir>/mount.manifest.json` | `config.mount.manifestFile` |
41
+
42
+ `<stateDir>` lives under `$XDG_STATE_HOME/<appName>/<workspace-hash>/`. The manifest tracks what was applied and when, and is the source of truth for `status` and `clear`.
43
+
44
+ Override the paths when the wrapper has its own conventions:
45
+
46
+ ```yaml
47
+ mount:
48
+ overlayPath: docker/compose/web/docker-compose.dev-link.yml
49
+ manifestFile: dev-link.manifest.json
50
+ ```
8
51
 
9
52
  ## Mount sources
10
53
 
11
54
  Mounts come from two places, merged on `containerPath` (config wins on collision):
12
55
 
13
- 1. **`config.mounts[]`** — explicit, declared in `cockpit.yaml`:
56
+ 1. **`config.mounts[]`** — explicit, declared in cockpit.yaml:
14
57
  ```yaml
15
58
  mounts:
16
59
  - hostPath: ./packages/api
17
60
  containerPath: /srv/api
61
+ meta: # optional opaque metadata (read by profile hooks + status)
62
+ name: api
63
+ type: package
18
64
  ```
19
- 2. **`profile.mountCandidatesProvider?.()`** — discovered programmatically by the active profile (e.g. walking a manifest file to find local checkouts of declared dependencies).
65
+ 2. **`profile.mountCandidatesProvider?.()`** — discovered programmatically (walk a manifest file, scan a parent directory, etc.).
20
66
 
21
- ## Output
67
+ The `meta` field is opaque to dev-cockpit; profile hooks (status enricher, symlink strategy) read it.
22
68
 
23
- - `<workspace>/docker-compose.dev-cockpit.yml` — the overlay file. Apply it alongside your existing compose file:
24
- ```
25
- docker compose -f docker-compose.yml -f docker-compose.dev-cockpit.yml up -d
26
- ```
27
- - `<state-dir>/mount.manifest.json` — records what was applied + when. Used by `mount status` and `mount clear`.
69
+ ## Subcommands
28
70
 
29
- The state dir lives under `XDG_STATE_HOME/<appName>/<workspace-hash>/`.
71
+ ```
72
+ dev-cockpit mount [options] # write overlay + manifest
73
+ -c, --config <path> # default: ./cockpit.yaml
74
+ -s, --service <name> # default: first in docker.services
75
+ -q, --quiet # skip interactive picker; apply all candidates
76
+ -e, --exclude <name> # exclude a candidate by meta.name (repeatable)
30
77
 
31
- ## Subcommands
78
+ dev-cockpit mount status [options] # branch + dirty + broken-symlink table
79
+ dev-cockpit mount clear [options] # remove overlay + manifest + managed symlinks
80
+ ```
81
+
82
+ ### Interactive picker
83
+
84
+ When `mount` runs with discoverable candidates (from `profile.mountCandidatesProvider`) and no `--quiet`, it opens a checkbox picker. Bare-config consumers (only `config.mounts[]`, no provider) bypass the picker — config entries are taken as-is.
85
+
86
+ ### Status
87
+
88
+ `mount status` prints a table per active mount with auto-enriched columns:
32
89
 
33
90
  ```
34
- dev-cockpit mount [-s|--service <name>] # write/refresh the overlay
35
- dev-cockpit mount status # print active overlay manifest
36
- dev-cockpit mount clear # delete overlay + manifest
91
+ NAME BRANCH DIRTY
92
+ ---- ------ -----
93
+ api feat/redis-cache yes
94
+ worker main no [BROKEN SYMLINK]
37
95
  ```
38
96
 
39
- If `--service` is omitted, the first entry in `config.docker.services` is used. Pass `--service` explicitly when your compose file has multiple services and you want mounts attached to a non-default one.
97
+ - **branch / dirty**: auto-detected via `git -C <hostPath> rev-parse --abbrev-ref HEAD` + `git status --porcelain`. Non-git host paths show `?`.
98
+ - **broken-symlink**: only when the profile supplies a `mountSymlinks` strategy (see below). Otherwise the column is omitted.
99
+ - Extra columns: profile-supplied `mountStatusEnricher` can return arbitrary `extra: { columnName: value }` per row.
40
100
 
41
- ## Profile-side discovery
101
+ ### Clear
42
102
 
43
- A profile that scans a manifest (e.g. `composer.json`, `package.json` workspaces) for local checkouts implements `mountCandidatesProvider` and returns `Mount[]`. Discovered candidates blend with `config.mounts[]` automatically:
103
+ `mount clear` removes the overlay file, the manifest, and any managed symlinks (if a `mountSymlinks` strategy is defined). Then it calls `profile.onMountClear(ctx)` with the previous mount set — that's where consumers run things like `composer install` or `npm install` to restore whatever the mounts had shadowed.
104
+
105
+ ## Profile hooks
106
+
107
+ Profiles plug in via five optional fields on the `Profile` interface:
44
108
 
45
109
  ```ts
110
+ import type { Profile } from 'dev-cockpit';
111
+
46
112
  export const myProfile: Profile = {
47
113
  appName: 'my-app',
48
- mountCandidatesProvider(): Mount[] {
49
- return findLocalCheckouts().map((c) => ({
50
- hostPath: c.hostPath,
51
- containerPath: c.containerPath,
52
- }));
53
- },
114
+
115
+ /** Discover mount candidates programmatically. */
116
+ mountCandidatesProvider() { ... },
117
+
118
+ /** Run after overlay + manifest are written. Typical: `docker compose restart <svc>`. */
119
+ async onMountApply(ctx) { ... },
120
+
121
+ /** Run after overlay + manifest + symlinks are removed. Typical: restore deps. */
122
+ async onMountClear(ctx) { ... },
123
+
124
+ /** Optional IDE-facing symlinks applied + removed alongside the overlay. */
125
+ mountSymlinks: { linkPath: (mount, workspaceRoot) => '...' },
126
+
127
+ /** Per-row column enrichment for `mount status`. */
128
+ async mountStatusEnricher(mount, ctx) { ... },
54
129
  };
55
130
  ```
131
+
132
+ ### `mountCandidatesProvider() → Mount[] | Promise<Mount[]>`
133
+
134
+ Discover candidates from whatever convention the consumer uses (composer.json, package.json workspaces, etc.). Return `Mount[]` with `hostPath` + `containerPath` set; populate `meta` for downstream hooks:
135
+
136
+ ```ts
137
+ mountCandidatesProvider() {
138
+ return findLocalCheckouts().map((c) => ({
139
+ hostPath: c.hostPath,
140
+ containerPath: `/srv/${c.name}`,
141
+ meta: { name: c.name, type: c.type },
142
+ }));
143
+ }
144
+ ```
145
+
146
+ ### `onMountApply(ctx) → Promise<void>`
147
+
148
+ Called after the overlay and manifest are written. Common move: restart the docker service so the new mounts take effect.
149
+
150
+ ```ts
151
+ async onMountApply(ctx) {
152
+ await execa('docker', ['compose', 'restart', 'web'], { cwd: ctx.workspaceRoot });
153
+ }
154
+ ```
155
+
156
+ `ctx` carries `workspaceRoot`, the freshly-applied `mounts[]`, and `log` / `errLog` callbacks that route to the cockpit's Output pane.
157
+
158
+ ### `onMountClear(ctx) → Promise<void>`
159
+
160
+ Called after the overlay + manifest + symlinks are removed. `ctx.previous` holds the mount set that was just cleared. Typical: re-install whatever the mounts had shadowed.
161
+
162
+ ```ts
163
+ async onMountClear(ctx) {
164
+ if (ctx.previous.length === 0) return;
165
+ await execa('npm', ['install'], { cwd: ctx.workspaceRoot });
166
+ }
167
+ ```
168
+
169
+ ### `mountSymlinks: MountSymlinkStrategy`
170
+
171
+ Some package managers expect the on-disk path to exist even when a bind-mount shadows it (composer installer-paths, npm install-from-disk, etc.). The symlink strategy creates host-side symlinks alongside the overlay so IDE tooling (LSPs, file watchers, debuggers) can follow into the live clone:
172
+
173
+ ```ts
174
+ mountSymlinks: {
175
+ linkPath(mount, workspaceRoot) {
176
+ const name = mount.meta?.name;
177
+ if (typeof name !== 'string') return null; // skip mounts without metadata
178
+ return path.join(workspaceRoot, 'vendor', name);
179
+ },
180
+ }
181
+ ```
182
+
183
+ Return `null` for mounts that shouldn't get a symlink. Symlinks are removed by `mount clear`; `mount status` detects broken ones (target moved or deleted) and surfaces them in the table.
184
+
185
+ ### `mountStatusEnricher(mount, ctx) → MountStatusInfo`
186
+
187
+ Add custom columns or override the auto-detected `branch` / `dirty` / `brokenSymlink` flags:
188
+
189
+ ```ts
190
+ async mountStatusEnricher(mount, { workspaceRoot }) {
191
+ return {
192
+ extra: {
193
+ tests: await runTestsForMount(mount) ? 'pass' : 'fail',
194
+ },
195
+ };
196
+ }
197
+ ```
198
+
199
+ ## Manifest schema
200
+
201
+ ```json
202
+ {
203
+ "version": 1,
204
+ "appName": "my-app",
205
+ "workspaceRoot": "/abs/path",
206
+ "service": "web",
207
+ "overlayPath": "/abs/path/docker-compose.dev-cockpit.yml",
208
+ "mounts": [
209
+ {
210
+ "hostPath": "/abs/host/api",
211
+ "containerPath": "/srv/api",
212
+ "meta": { "name": "api", "type": "package" }
213
+ }
214
+ ],
215
+ "appliedAt": "2026-05-11T00:00:00.000Z"
216
+ }
217
+ ```
218
+
219
+ Re-running `mount` overwrites the manifest with the new applied set. `mount clear` deletes it.
220
+
221
+ ## Notes for wrappers upgrading from older manifest filenames
222
+
223
+ If your wrapper previously used a different manifest filename (e.g. a profile that pre-dates the mount lift into dev-cockpit core wrote `dev-link.manifest.json`), the new default is `mount.manifest.json`. Two options:
224
+
225
+ 1. **Adopt the new default**: run `mount` once. The new file replaces the legacy one (which is left orphaned in the state dir — safe to delete).
226
+ 2. **Keep the legacy filename**: set `config.mount.manifestFile: dev-link.manifest.json` in cockpit.yaml.
227
+
228
+ Same applies to the overlay path — set `config.mount.overlayPath` if your wrapper expects a specific location for its compose stack.
@@ -14,26 +14,27 @@ OS notifications fire on **state transitions only** — never on every check, ne
14
14
  - recovered
15
15
  ```
16
16
 
17
- 2. **Per-item override** — a `notify:` field on any watcher or health check:
17
+ 2. **Per-item override** — a `notify:` field on any process or health check:
18
18
 
19
19
  ```yaml
20
- - id: lint
21
- command: npx eslint . --watch
22
- notify: false # mute this item
23
-
24
- - id: api-up
25
- type: http-ok
26
- url: http://localhost:3000/health
27
- notify:
28
- onTransitionTo: [error] # only on going-down, not on recovery
20
+ processes:
21
+ - id: lint
22
+ command: npx eslint . --watch
23
+ notify: false # mute this item
24
+
25
+ health:
26
+ - id: api-up
27
+ type: http-ok
28
+ url: http://localhost:3000/health
29
+ notify:
30
+ onTransitionTo: [error] # only on going-down, not on recovery
29
31
  ```
30
32
 
31
33
  3. **Session toggle** — press `n` in the cockpit to mute *all* notifications for the current session (overrides config). The Health pane shows the current session state at the bottom.
32
34
 
33
35
  ## Events
34
36
 
35
- - `health-failed` / `health-recovered` — emitted by the health framework
36
- - `build-failed` / `build-recovered` emitted by watchers
37
- - Profiles can emit their own event names; they flow through the same pipeline.
37
+ - `health-failed` / `health-recovered` — emitted by the health framework on state transitions.
38
+ - Profiles can emit their own event names (`build-failed`, `db-disconnect`, anything domain-specific) via the exported `emitEvent` helper; they flow through the same `enabled` + `onTransitionTo` + `exclude` policy.
38
39
 
39
40
  The `appName` configured in `cockpit.yaml` is used as the title prefix on every notification (e.g. `my-app — API responds`).
package/docs/panes.md CHANGED
@@ -1,28 +1,47 @@
1
1
  # Panes
2
2
 
3
- ## Repos
3
+ Four panes cycle via `←` / `→` or `Tab` / `Shift-Tab`. Empty panes hide automatically — Targets hides when no rows are seeded, Health hides when no checks are registered.
4
4
 
5
- Glyphs:
5
+ ## Targets
6
+
7
+ Two-column layout when the terminal is at least 200 cols wide: row list on the left, actions for the selected row on the right. Narrower terminals stack vertically (list on top, actions below).
8
+
9
+ ```
10
+ ┌── Targets ──────────────────┬── Actions for `ticker` (process) ────┐
11
+ │ ▸ ○ web [docker] │ [r] Restart ticker (default) │
12
+ │ ○ db [docker] │ [t] Run tests │
13
+ │ ○ ticker [process] │ │
14
+ │ ● api │ │
15
+ └───────────────────────────┴───────────────────────────────────────┘
16
+ ```
17
+
18
+ ### Row kinds
19
+
20
+ | Kind | Source | Built-in `r` |
21
+ |---|---|---|
22
+ | `repo` | `config.repos[]` or `profile.reposProvider()` | none — declare an action |
23
+ | `docker` | `config.docker.services[]` | `docker compose restart <service>` |
24
+ | `process` | `config.processes[]` | kill + respawn (tagged spawn lookup) |
25
+
26
+ ### Status glyphs
6
27
 
7
28
  - ● running (green)
8
- - ○ idle (gray/dim)
29
+ - ○ idle (gray)
9
30
  - ✗ failing (red)
10
31
 
11
- Lint sub-glyph (after the name): ✓ pass · ! fail · (none) unknown.
12
-
13
- Selected row is marked with a leading `>` and the pane border is highlighted when this pane has focus.
32
+ Lint sub-glyph (after the name): ✓ pass · ! fail · (none) unknown. The leading `▸` marks the selected row; the pane border highlights when this pane has focus.
14
33
 
15
- Actions (when a row is selected):
34
+ ### Keystrokes
16
35
 
17
- - `r` primary action (rebuild for repo entries, restart for Docker entries)
18
- - `w` toggle watcher (repo entries only)
19
- - `l`run lint (repo entries only)
36
+ - `↑` / `↓` move selection
37
+ - `<key>` (any single character) — fire the first matching action for the selected row from the action registry. Match rule: action whose `scope` is `repos:<rowId>` or `repos` AND whose `key` equals the keystroke. Declared actions win over built-ins on (scope, key) collision so you can shadow the default `r`.
38
+ - `:`open the `:` command palette (any tab; not Targets-specific)
20
39
 
21
- In-flight actions show a banner at the top of the pane plus an inline `(running…)` next to the active row.
40
+ If no action matches, the keystroke is a silent no-op — the right-side panel lists exactly what's bound, including built-ins tagged `(default)`.
22
41
 
23
42
  ## Output
24
43
 
25
- Streaming source-tagged logs.
44
+ Streaming source-tagged logs. Severity is shown by a leading glyph (`⚠` warn, `✗` error; info has none) so lines remain scannable without color.
26
45
 
27
46
  - `↑` / `↓` — scroll one line
28
47
  - `PgUp` / `PgDn` — scroll one page
@@ -36,10 +55,12 @@ A "Recent errors" strip appears at the top when one or more error lines have bee
36
55
 
37
56
  ## Health
38
57
 
39
- Each row shows: severity glyph, label, remediation key (if any). `Enter` toggles the diagnostic detail for the selected row. Press the remediation letter to fix.
58
+ Each row shows: severity glyph (`✓` ok, `⚠` warn, `✗` error), label, remediation key (if any). `Enter` toggles the diagnostic detail for the selected row. Press the remediation letter (case-insensitive — declared `R` matches both `r` and `R`) to fire the fix.
40
59
 
41
- While a remediation runs, a banner appears at the top and the running row shows `… (running…)`.
60
+ While a remediation runs, a banner appears at the top and the running row shows `(running…)`.
42
61
 
43
62
  ## Help
44
63
 
45
- This pane. `j` / `k` (or `Shift-Tab` / `Tab`) cycle pages. `↑` / `↓` scroll. `g` / `G` jump to top / bottom. `←` / `→` always exit Help to the next tab.
64
+ Live-rendered markdown. `j` / `k` (or `Shift-Tab` / `Tab`) cycle pages. `↑` / `↓` scroll. `g` / `G` jump to top / bottom. `←` / `→` always exit Help to the next tab.
65
+
66
+ Under narrow terminals (< 200 cols), the page-title strip hides and the legend collapses to `[j/k] page i/N` to save space.
@@ -0,0 +1,42 @@
1
+ # Processes
2
+
3
+ A process is a long-running command — `tsc --watch`, `vitest --watch`, `npm run dev`, `php artisan serve`, `tail -f some.log` — declared in `cockpit.yaml` under `processes:`. The cockpit spawns each process once at boot and streams its stdout / stderr into the Output pane, tagged by source.
4
+
5
+ Distinct from:
6
+ - **Actions** (one-shot shell commands surfaced via the `:` palette and per-row keys)
7
+ - **Health checks** (state predicates with one-keystroke remediation)
8
+ - **Docker tailing** (`config.docker` — already managed by docker compose)
9
+
10
+ ## Config shape
11
+
12
+ ```yaml
13
+ processes:
14
+ - id: typecheck # required, unique; used as the Output source tag and Targets row id
15
+ label: tsc --watch # optional; shown in Repos + Output
16
+ command: npx tsc --watch --noEmit # shell command; runs via sh -c (cmd /c on Windows)
17
+ cwd: ./apps/web # optional; relative to workspaceRoot
18
+ env: # optional; merged on top of process.env
19
+ DEBUG: '1'
20
+ ```
21
+
22
+ `command` runs through the shell so pipes, redirects, env-prefixes, and properly-quoted arguments all work as expected.
23
+
24
+ ## Built-in restart
25
+
26
+ Every process gets a Targets row of `kind: process` with a built-in action `[r] Restart <id>` (key: `r`, scope: `repos:<id>`). The implementation: SIGTERM the tagged child (via `killSpawnedByTag`), wait for it to exit, then re-spawn it with the same command + cwd + env.
27
+
28
+ You can shadow `r` with a custom action of the same scope + key — declared actions outrank built-ins in the dispatch precedence.
29
+
30
+ ## Output + highlights
31
+
32
+ Each line from a process's stdout / stderr gets:
33
+ - The source label (or `id`) as a tag.
34
+ - Default severity: `info` for stdout, `warn` for stderr.
35
+ - Highlight matching against `config.highlights[]`. A matched pattern's `severity:` wins over the default — so a `pattern: 'boom', severity: error` highlight upgrades any line containing "boom" to `error`, which shows as `✗`-prefixed in Output and feeds the Recent Errors strip.
36
+
37
+ ## Lifecycle
38
+
39
+ - Spawned on `dev-cockpit dev` boot.
40
+ - Tagged with the process's `id` so the per-row restart action can find the right child.
41
+ - Reaped on cockpit shutdown (`q` or SIGTERM) via `killAllSpawned` — SIGTERM with a 1.5s SIGKILL fallback.
42
+ - No fsevent-driven respawn — the process command itself (`tsc --watch`, etc.) is expected to handle re-runs internally.