@doriandev/devcc 0.1.2 → 0.2.0

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 (136) hide show
  1. package/README.md +556 -386
  2. package/bin/devcc.mjs +50 -23
  3. package/dist/devcc/agents/agents.d.ts +112 -0
  4. package/dist/devcc/agents/agents.d.ts.map +1 -0
  5. package/dist/devcc/agents/agents.js +179 -0
  6. package/dist/devcc/agents/agents.js.map +1 -0
  7. package/dist/devcc/app/App.d.ts +15 -0
  8. package/dist/devcc/app/App.d.ts.map +1 -1
  9. package/dist/devcc/app/App.js +326 -17
  10. package/dist/devcc/app/App.js.map +1 -1
  11. package/dist/devcc/app/controlPlaneCommands.d.ts +14 -0
  12. package/dist/devcc/app/controlPlaneCommands.d.ts.map +1 -0
  13. package/dist/devcc/app/controlPlaneCommands.js +179 -0
  14. package/dist/devcc/app/controlPlaneCommands.js.map +1 -0
  15. package/dist/devcc/app/keymap.d.ts.map +1 -1
  16. package/dist/devcc/app/keymap.js +24 -3
  17. package/dist/devcc/app/keymap.js.map +1 -1
  18. package/dist/devcc/automation.d.ts +31 -0
  19. package/dist/devcc/automation.d.ts.map +1 -0
  20. package/dist/devcc/automation.js +202 -0
  21. package/dist/devcc/automation.js.map +1 -0
  22. package/dist/devcc/cli.d.ts.map +1 -1
  23. package/dist/devcc/cli.js +46 -3
  24. package/dist/devcc/cli.js.map +1 -1
  25. package/dist/devcc/components/Sidebar.d.ts.map +1 -1
  26. package/dist/devcc/components/Sidebar.js +1 -0
  27. package/dist/devcc/components/Sidebar.js.map +1 -1
  28. package/dist/devcc/components/primitives.d.ts.map +1 -1
  29. package/dist/devcc/components/primitives.js +3 -0
  30. package/dist/devcc/components/primitives.js.map +1 -1
  31. package/dist/devcc/core/commands.d.ts +25 -0
  32. package/dist/devcc/core/commands.d.ts.map +1 -1
  33. package/dist/devcc/core/commands.js.map +1 -1
  34. package/dist/devcc/core/config.d.ts +69 -3
  35. package/dist/devcc/core/config.d.ts.map +1 -1
  36. package/dist/devcc/core/config.js +185 -5
  37. package/dist/devcc/core/config.js.map +1 -1
  38. package/dist/devcc/core/state.d.ts +19 -1
  39. package/dist/devcc/core/state.d.ts.map +1 -1
  40. package/dist/devcc/core/state.js +6 -0
  41. package/dist/devcc/core/state.js.map +1 -1
  42. package/dist/devcc/environment/EnvironmentService.d.ts +58 -0
  43. package/dist/devcc/environment/EnvironmentService.d.ts.map +1 -0
  44. package/dist/devcc/environment/EnvironmentService.js +159 -0
  45. package/dist/devcc/environment/EnvironmentService.js.map +1 -0
  46. package/dist/devcc/environment/integrations.d.ts +52 -0
  47. package/dist/devcc/environment/integrations.d.ts.map +1 -0
  48. package/dist/devcc/environment/integrations.js +94 -0
  49. package/dist/devcc/environment/integrations.js.map +1 -0
  50. package/dist/devcc/environment/tools.d.ts +51 -0
  51. package/dist/devcc/environment/tools.d.ts.map +1 -0
  52. package/dist/devcc/environment/tools.js +99 -0
  53. package/dist/devcc/environment/tools.js.map +1 -0
  54. package/dist/devcc/git/GitService.d.ts +26 -0
  55. package/dist/devcc/git/GitService.d.ts.map +1 -1
  56. package/dist/devcc/git/GitService.js +71 -2
  57. package/dist/devcc/git/GitService.js.map +1 -1
  58. package/dist/devcc/git/worktrees.d.ts +88 -0
  59. package/dist/devcc/git/worktrees.d.ts.map +1 -0
  60. package/dist/devcc/git/worktrees.js +226 -0
  61. package/dist/devcc/git/worktrees.js.map +1 -0
  62. package/dist/devcc/index.d.ts +13 -3
  63. package/dist/devcc/index.d.ts.map +1 -1
  64. package/dist/devcc/index.js +11 -1
  65. package/dist/devcc/index.js.map +1 -1
  66. package/dist/devcc/platform/launch.d.ts +30 -0
  67. package/dist/devcc/platform/launch.d.ts.map +1 -0
  68. package/dist/devcc/platform/launch.js +93 -0
  69. package/dist/devcc/platform/launch.js.map +1 -0
  70. package/dist/devcc/services/ServiceManager.d.ts +10 -2
  71. package/dist/devcc/services/ServiceManager.d.ts.map +1 -1
  72. package/dist/devcc/services/ServiceManager.js +18 -4
  73. package/dist/devcc/services/ServiceManager.js.map +1 -1
  74. package/dist/devcc/services/ollama.d.ts +64 -5
  75. package/dist/devcc/services/ollama.d.ts.map +1 -1
  76. package/dist/devcc/services/ollama.js +118 -17
  77. package/dist/devcc/services/ollama.js.map +1 -1
  78. package/dist/devcc/services/remote.d.ts +90 -0
  79. package/dist/devcc/services/remote.d.ts.map +1 -0
  80. package/dist/devcc/services/remote.js +233 -0
  81. package/dist/devcc/services/remote.js.map +1 -0
  82. package/dist/devcc/services/types.d.ts +8 -1
  83. package/dist/devcc/services/types.d.ts.map +1 -1
  84. package/dist/devcc/services/types.js.map +1 -1
  85. package/dist/devcc/utils/exec.d.ts +6 -0
  86. package/dist/devcc/utils/exec.d.ts.map +1 -1
  87. package/dist/devcc/utils/exec.js +12 -2
  88. package/dist/devcc/utils/exec.js.map +1 -1
  89. package/dist/devcc/verify/verify.d.ts +107 -0
  90. package/dist/devcc/verify/verify.d.ts.map +1 -0
  91. package/dist/devcc/verify/verify.js +203 -0
  92. package/dist/devcc/verify/verify.js.map +1 -0
  93. package/dist/devcc/views/OverviewView.d.ts.map +1 -1
  94. package/dist/devcc/views/OverviewView.js +55 -1
  95. package/dist/devcc/views/OverviewView.js.map +1 -1
  96. package/dist/devcc/views/ServicesView.d.ts +2 -0
  97. package/dist/devcc/views/ServicesView.d.ts.map +1 -1
  98. package/dist/devcc/views/ServicesView.js +15 -0
  99. package/dist/devcc/views/ServicesView.js.map +1 -1
  100. package/dist/devcc/views/SystemView.d.ts +10 -1
  101. package/dist/devcc/views/SystemView.d.ts.map +1 -1
  102. package/dist/devcc/views/SystemView.js +127 -19
  103. package/dist/devcc/views/SystemView.js.map +1 -1
  104. package/dist/devcc/views/WorktreesView.d.ts +48 -0
  105. package/dist/devcc/views/WorktreesView.d.ts.map +1 -0
  106. package/dist/devcc/views/WorktreesView.js +235 -0
  107. package/dist/devcc/views/WorktreesView.js.map +1 -0
  108. package/package.json +1 -1
  109. package/src/devcc/agents/agents.ts +282 -0
  110. package/src/devcc/app/App.ts +376 -20
  111. package/src/devcc/app/controlPlaneCommands.ts +197 -0
  112. package/src/devcc/app/keymap.ts +24 -3
  113. package/src/devcc/automation.ts +242 -0
  114. package/src/devcc/cli.ts +45 -3
  115. package/src/devcc/components/Sidebar.ts +1 -0
  116. package/src/devcc/components/primitives.ts +3 -0
  117. package/src/devcc/core/commands.ts +26 -0
  118. package/src/devcc/core/config.ts +271 -11
  119. package/src/devcc/core/state.ts +25 -0
  120. package/src/devcc/environment/EnvironmentService.ts +215 -0
  121. package/src/devcc/environment/integrations.ts +133 -0
  122. package/src/devcc/environment/tools.ts +151 -0
  123. package/src/devcc/git/GitService.ts +104 -2
  124. package/src/devcc/git/worktrees.ts +256 -0
  125. package/src/devcc/index.ts +57 -2
  126. package/src/devcc/platform/launch.ts +125 -0
  127. package/src/devcc/services/ServiceManager.ts +15 -3
  128. package/src/devcc/services/ollama.ts +162 -19
  129. package/src/devcc/services/remote.ts +306 -0
  130. package/src/devcc/services/types.ts +9 -1
  131. package/src/devcc/utils/exec.ts +26 -2
  132. package/src/devcc/verify/verify.ts +316 -0
  133. package/src/devcc/views/OverviewView.ts +76 -1
  134. package/src/devcc/views/ServicesView.ts +17 -0
  135. package/src/devcc/views/SystemView.ts +144 -22
  136. package/src/devcc/views/WorktreesView.ts +270 -0
package/README.md CHANGED
@@ -1,50 +1,75 @@
1
1
  # Dev Command Center
2
2
 
3
- A single keyboard-driven screen that replaces the pile of terminal tabs you keep
4
- open while working on a project. Built with [OpenTUI](https://opentui.com).
3
+ [![npm](https://img.shields.io/npm/v/@doriandev/devcc)](https://www.npmjs.com/package/@doriandev/devcc)
4
+ [![license](https://img.shields.io/npm/l/@doriandev/devcc)](LICENSE)
5
5
 
6
- ## What it is for
6
+ A single keyboard-driven screen that replaces the pile of terminal tabs you keep open while working on a
7
+ project. Built with [OpenTUI](https://opentui.com).
7
8
 
8
- Working on a real project normally means one tab running `next dev`, another for
9
- `git status`, another for `pnpm test`, another where you `docker ps` or check
10
- whether Postgres is up, and a fifth for actually typing commands. State ends up
11
- scattered: you cannot see at a glance whether the dev server is up, whether your
12
- tree is dirty, or which of those tabs has the error in it.
9
+ ## Quick start
13
10
 
14
- Dev Command Center puts that in one place.
15
-
16
- - **Watches** - git branch and changes, which services are actually listening
17
- (Docker, Postgres, Redis, Ollama, a framework dev server), host CPU / memory /
18
- disk, and outdated dependencies.
19
- - **Controls** - starts, stops and restarts the dev processes you declare in
20
- `.devcc.ts`; runs your `package.json` scripts; stages, commits, pulls and
21
- pushes.
22
- - **Collects** - everything it launches streams into one log viewer, per source
23
- or merged and searchable, so the error you are chasing is not in a tab you
24
- have scrolled away from.
25
-
26
- ### More than a launcher
27
-
28
- Three things it does that running the commands by hand does not:
29
-
30
- - **Brings a stack up in order.** `dependsOn` plus a readiness check means `web`
31
- waits until `api` is genuinely accepting connections, not merely spawned.
32
- - **Recovers from crashes.** Opt-in automatic restarts with exponential backoff,
33
- and it gives up rather than thrashing.
34
- - **Holds several projects open at once.** Each keeps its processes alive in the
35
- background while you flip between them with `ctrl+o`.
36
-
37
- ### Where the line is
38
-
39
- It is not a terminal replacement - there is no shell in it, and it will not run
40
- interactive commands. It is not a deploy tool or a container manager; it observes
41
- Docker rather than replacing compose. And it deliberately will not touch
42
- processes it did not start: anything you launched in another tab shows up as
43
- detected and read-only.
11
+ ```bash
12
+ npm i -g @doriandev/devcc # or: pnpm add -g @doriandev/devcc
13
+ cd ~/code/example.com
14
+ devcc
15
+ ```
44
16
 
45
- It earns its place on projects with more than one long-running process, or when
46
- you bounce between repositories. For a single `next dev` you would barely notice
47
- the difference - the payoff scales with how many moving parts your setup has.
17
+ You need [Bun](https://bun.sh) 1.3+ installed (or Node 26.4+, see [Requirements](#requirements)). No
18
+ configuration is needed to get a useful screen. Press `?` inside the app for the manual.
19
+
20
+ In scripts, CI and AI coding agents, `devcc verify` is the project's quality gate: it runs your checks and
21
+ exits 0 only when all of them pass (see [Verification](#verification)).
22
+
23
+ ## Contents
24
+
25
+ - [Why devcc](#why-devcc)
26
+ - [At a glance](#at-a-glance)
27
+ - [Requirements](#requirements)
28
+ - [Usage](#usage)
29
+ - [Worktrees and AI agents](#worktrees-and-ai-agents)
30
+ - [Verification](#verification)
31
+ - [Environment](#environment)
32
+ - [Remote machines](#remote-machines)
33
+ - [Configuration](#configuration)
34
+ - [Keyboard shortcuts](#keyboard-shortcuts)
35
+ - [Known limitations](#known-limitations)
36
+ - [Contributing](#contributing)
37
+
38
+ ## Why devcc
39
+
40
+ Working on a real project normally means one tab running `next dev`, another for `git status`, another
41
+ for `pnpm test`, another where you `docker ps` or check whether Postgres is up, and a fifth for actually
42
+ typing commands. You cannot see at a glance whether the dev server is up, whether your tree is dirty, or
43
+ which tab has the error in it.
44
+
45
+ devcc puts that in one place:
46
+
47
+ - **Watches** git branch and changes, which services are actually listening (Docker, Postgres, Redis,
48
+ Ollama, a framework dev server), host CPU / memory / disk, and outdated dependencies.
49
+ - **Controls** the dev processes you declare in `.devcc.ts` (start, stop, restart), runs your
50
+ `package.json` scripts, and stages, commits, pulls and pushes.
51
+ - **Collects** everything it launches into one log viewer, per source or merged and searchable.
52
+
53
+ It also does things that running the commands by hand does not:
54
+
55
+ - **Brings a stack up in order.** `dependsOn` plus a readiness check means `web` waits until `api` is
56
+ genuinely accepting connections, not merely spawned.
57
+ - **Recovers from crashes.** Opt-in automatic restarts with exponential backoff, and it gives up rather
58
+ than thrashing.
59
+ - **Holds several projects open at once.** Each keeps its processes alive in the background while you flip
60
+ between them with `ctrl+o`.
61
+ - **Runs AI agents side by side.** Claude Code and Codex, each in its own git worktree, with their output
62
+ in the log viewer.
63
+ - **Gates the work.** `devcc verify` runs the same checks for you, for CI and for an agent, with an exit
64
+ code that means something.
65
+
66
+ **What it is not.** It is not a terminal replacement: there is no shell in it and it will not run
67
+ interactive commands. It is not a deploy tool or a container manager; it observes Docker rather than
68
+ replacing compose. It orchestrates git, mise, direnv, Claude Code, Codex, Tailscale, SSH and Ollama; it
69
+ replaces none of them. It will never touch a process it did not start.
70
+
71
+ It pays off on projects with more than one long-running process, or when you bounce between repositories.
72
+ For a single `next dev` you would barely notice the difference.
48
73
 
49
74
  ## At a glance
50
75
 
@@ -71,263 +96,244 @@ the difference - the payoff scales with how many moving parts your setup has.
71
96
 
72
97
  ## Requirements
73
98
 
74
- OpenTUI renders through a native Zig core over FFI, so the dashboard needs one
75
- of:
99
+ OpenTUI renders through a native Zig core over FFI, so the dashboard needs one of:
76
100
 
77
101
  - **Bun 1.3.0 or newer** (recommended), or
78
102
  - **Node.js 26.4.0 or newer**, started with `--experimental-ffi`
79
103
 
80
- The `devcc` launcher detects this for you: run it with any Node ≥ 20 and it will
81
- re-exec into Bun (or into Node with the right flag) automatically. Dependency
82
- installation, linting, type checking and the unit tests all run on plain Node.
104
+ You do not have to pick. The `devcc` launcher runs on any Node ≥ 20 and re-execs into Bun, or into Node
105
+ with the right flag, automatically. If neither is available it says so and stops.
83
106
 
84
- ## Install and run
85
-
86
- ```bash
87
- npm i -g @doriandev/devcc # or: pnpm add -g @doriandev/devcc
88
- cd ~/code/example.com
89
- devcc
90
- ```
107
+ ## Usage
91
108
 
92
- `devcc` runs against the project you are standing in. To point it somewhere else
93
- without changing directory:
109
+ `devcc` runs against the project you are standing in. To point it somewhere else:
94
110
 
95
111
  ```bash
96
112
  devcc -C ~/code/example.com
97
113
  ```
98
114
 
99
- ### CLI options
115
+ | Command | What it does |
116
+ | ------------------------------ | ------------------------------------------------------------- |
117
+ | `devcc` | Open the dashboard |
118
+ | `devcc verify [--json]` | Run the project's verification steps; exit 0 only if all pass |
119
+ | `devcc worktree list [--json]` | List git worktrees |
120
+ | `devcc env [--json]` | Report installed tools, mise runtimes and direnv status |
100
121
 
101
- | Option | Description |
102
- | ------------------- | ------------------------------------------------- |
103
- | `-C`, `--cwd <dir>` | Project directory (default: nearest project root) |
104
- | `-h`, `--help` | Show usage |
105
- | `-v`, `--version` | Show the version |
122
+ | Option | Description |
123
+ | ------------------- | ------------------------------------------------------ |
124
+ | `-C`, `--cwd <dir>` | Project directory (default: nearest project root) |
125
+ | `--json` | One JSON document on stdout, no colour or escape codes |
126
+ | `-h`, `--help` | Show usage |
127
+ | `-v`, `--version` | Show the version |
106
128
 
107
- `DEVCC_LOG_LEVEL` (`debug` \| `info` \| `warn` \| `error` \| `silent`) controls
108
- the internal diagnostics log written to `.devcc/devcc.log`.
109
-
110
- ### Running from source
129
+ `verify`, `worktree list` and `env` do not load the dashboard, so they run on any Node ≥ 20 without Bun,
130
+ which is what you want in CI or inside an agent's sandbox.
111
131
 
112
132
  ```bash
113
- git clone https://github.com/dordevgit/devcc.git
114
- cd devcc
115
- pnpm install
116
- pnpm dev:center # launch against this repo
117
- node bin/devcc.mjs -C ~/code/example.com # or against another project
118
- pnpm build # compile to dist/
133
+ devcc verify # human-readable, failing output shown inline
134
+ devcc verify --json | jq .summary # "4/5 passed, 1 failed (Tests)"
135
+ devcc worktree list --json | jq -r '.worktrees[].path'
136
+ devcc env # what is installed on this machine
119
137
  ```
120
138
 
121
- ## How much setup is needed
139
+ Set `DEVCC_LOG_LEVEL` (`debug` \| `info` \| `warn` \| `error` \| `silent`) to control the internal
140
+ diagnostics log written to `.devcc/devcc.log`.
141
+
142
+ ## Worktrees and AI agents
122
143
 
123
- Two prerequisites, then nothing else is required to get a useful screen:
144
+ Press `w` (or `0`) for the Worktrees view: every git worktree of the repository, and below them the AI
145
+ agents devcc has started. The worktree devcc is running in is marked `●`.
124
146
 
125
- 1. **Install it** - `npm i -g @doriandev/devcc`.
126
- 2. **A runtime with FFI** - Bun ≥ 1.3, or Node ≥ 26.4 run with
127
- `--experimental-ffi` (see [Requirements](#requirements)). The launcher
128
- handles this for you if Bun is installed; if neither is available it says so
129
- and stops.
147
+ ```
148
+ WORKTREES
149
+ main ~/code/devcc main
150
+ ○ feature/ai-agents ~/code/devcc-feature-ai-agents 1 agent
151
+ fix/process-restart ~/code/devcc-fix-process-restart
152
+
153
+ AI AGENTS
154
+ ● running Claude Code feature/ai-agents Add retry to the process manager 2m 10s
155
+ ○ stopped Codex fix/process-restart Find why restart loses the pid exit 0
156
+ ```
130
157
 
131
- ### What works with no configuration
158
+ **Worktrees.** `n` asks for a branch (created if it does not exist), a base (default: your current branch)
159
+ and a directory (default: `../<repo>-<branch>`), then runs `git worktree add`. `d` removes the selected
160
+ worktree after a confirmation, with plain `git worktree remove`: devcc never passes `--force`, so git
161
+ refuses when there are uncommitted or untracked changes, and devcc shows that refusal. It also refuses
162
+ to remove the main worktree, the one it is running in, or one where an agent is still working. `o` opens a
163
+ worktree as another project in devcc, `t` opens a terminal there, `e` opens your editor.
132
164
 
133
- Run `devcc` in `~/code/example.com` having written nothing, and it works these
134
- out for itself:
165
+ **AI agents** (optional: needs the `claude` or `codex` CLI). With a worktree selected, `c` launches Claude
166
+ Code and `x` launches Codex. devcc asks for the task and starts the agent with that worktree as its working
167
+ directory. Agents run non-interactively (`claude -p <task>`, `codex exec <task>`) as managed processes:
168
+ their output streams into the Logs view (`enter` on a session), `s` stops one, and quitting devcc stops
169
+ them all. For an interactive session, open a terminal in the worktree with `t` and run the agent there.
135
170
 
136
- | It figures out | How |
137
- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
- | Project root | Walks up from the working directory looking for `package.json` or `.git` |
139
- | Package manager | `packageManager` field → lockfile → the manager that launched it → npm |
140
- | Your scripts | Reads `scripts` from `package.json` |
141
- | Git state | Runs `git status --porcelain=v2` directly - branch, staged/modified/untracked, ahead/behind, recent commits |
142
- | Local services | TCP-probes Postgres, Redis and Ollama; queries the Docker daemon and any compose file; infers dev-server ports from your dependencies (`next` → 3000, `vite` → 5173, `typesense` → 8108) |
143
- | Host metrics | Node's `os` and `fs` APIs - CPU, memory, disk, load, uptime |
144
- | Tool versions | `git`, `docker` and your package manager, read once in the background |
171
+ devcc passes no permission flags of its own, so each agent runs with its own defaults, and headless Claude
172
+ Code cannot edit files by default. Grant what you are comfortable with in config:
145
173
 
146
- That is real detection rather than defaults, so Overview, Scripts, Git, Services
147
- and System are all populated on first launch.
174
+ ```ts
175
+ agents: {
176
+ claude: { args: ["--permission-mode", "acceptEdits"] },
177
+ codex: { args: ["--sandbox", "workspace-write"] },
178
+ }
179
+ ```
148
180
 
149
- ### What does require configuration
181
+ ## Verification
150
182
 
151
- **Managed processes.** devcc cannot guess that `pnpm dev` is the thing you want
152
- it to own, that it should wait for port 3000 before calling it ready, or that it
153
- is safe to restart. Nothing is launched or controlled until you declare it in
154
- `.devcc.ts`:
183
+ Declare the checks that decide whether the project can ship:
155
184
 
156
185
  ```ts
157
- // ~/code/example.com/.devcc.ts
158
- export default {
159
- processes: [
160
- {
161
- id: "web",
162
- name: "Next.js",
163
- command: "pnpm",
164
- args: ["dev"],
165
- url: "localhost:3000",
166
- readiness: { port: 3000 }, // "running" means actually serving
167
- restartOnCrash: true, // comes back after a crash, with backoff
168
- },
169
- ],
170
- commands: [
171
- {
172
- id: "migrate",
173
- title: "Run migrations",
174
- category: "Database",
175
- command: "pnpm",
176
- args: ["db:migrate"],
177
- confirm: true, // asks first: it mutates the database
178
- },
179
- ],
180
- }
186
+ // command ids from `commands`, package.json script names, or inline steps
187
+ verify: ["format:check", "lint", "typecheck", "test", "build"],
181
188
  ```
182
189
 
183
- Restart devcc and the Processes pane is live: `s` starts or stops, `r` restarts,
184
- `enter` jumps to that process's logs, and quitting stops everything devcc
185
- started. The declared command appears in the palette (`/`) and asks before it
186
- runs.
190
+ A name is looked up in `commands` first, then in `package.json` scripts, so you rarely need to repeat
191
+ anything. An inline step is `{ id, name?, command, args?, cwd? }`.
187
192
 
188
- This is deliberate. The alternative is a tool that spawns processes you did not
189
- ask for, and devcc will never touch a process it did not start - see
190
- [Configuration](#configuration) for every available field.
193
+ ```
194
+ $ devcc verify
195
+ devcc verify · ~/code/shop · 3 step(s)
196
+
197
+ → lint: pnpm run lint
198
+ ✓ lint (2.7s)
199
+ → test: pnpm run test
200
+ ✗ test (1.7s · exit 1)
201
+ FAIL tests/cart.test.ts > applies the discount
202
+ AssertionError: expected 90 to be 81
203
+ → build: pnpm run build
204
+ ✓ build (6.0s)
205
+
206
+ ✗ 2/3 passed, 1 failed (test) in 10.4s
207
+ ```
191
208
 
192
- ## Keyboard shortcuts
209
+ Steps run one at a time, in order, and a failure does not stop the run, so one pass shows every problem.
210
+ Colour is switched off for the steps and escape codes are stripped from captured output.
193
211
 
194
- Press `?`, `F1` or `ctrl+k` in the app for the manual, which also explains what each section is for and the main config fields.
212
+ | Exit code | Meaning |
213
+ | --------- | ------------------------------------------------------------------------- |
214
+ | `0` | Every step passed |
215
+ | `1` | At least one step failed (or was cancelled) |
216
+ | `2` | Nothing was verified: no steps configured, or a step name matches nothing |
195
217
 
196
- ### Global
218
+ A missing configuration is an error on purpose: a gate that passes because it checked nothing is worse
219
+ than no gate. That makes a line like this safe to put in an agent's instructions:
197
220
 
198
- | Key | Action |
199
- | --------------------- | --------------------------------------------------------- |
200
- | `↑` / `k`, `↓` / `j` | Previous / next item |
201
- | `←` / `h`, `→` | Previous / next section |
202
- | `tab`, `shift+tab` | Cycle sections |
203
- | `1`…`9` | Jump straight to a section |
204
- | `p`, `g`, `l`, `d` | Processes, Git, Logs, Dependencies |
205
- | `enter` | Open / activate the selection |
206
- | `esc` | Close an overlay |
207
- | `/` | Command palette |
208
- | `ctrl+p` | Open another project (others keep running) |
209
- | `ctrl+o` | Cycle through open projects |
210
- | `?` / `F1` / `ctrl+k` | The in-app manual (also "Open the manual" in the palette) |
211
- | `shift+R` | Refresh everything |
212
- | `q` or `ctrl+c` | Quit (stops the processes devcc started) |
221
+ ```text
222
+ Do not finish until `devcc verify` succeeds.
223
+ ```
213
224
 
214
- ### Processes and Services
225
+ In the dashboard, `shift+V` (or "Project: verify" in the palette) runs the same steps as managed tasks:
226
+ progress shows on the Overview, and each step's output has its own log source.
215
227
 
216
- | Key | Action |
217
- | ------- | --------------------------------- |
218
- | `s` | Start or stop the selection |
219
- | `r` | Restart the selection |
220
- | `i` | Inspect the selection |
221
- | `enter` | Jump to that process's logs |
222
- | `d` | `docker compose down` (confirmed) |
228
+ ## Environment
223
229
 
224
- ### Scripts
230
+ The System view (`8`) starts with the environment, detected in the background when a project opens and
231
+ re-checked with `r`. `devcc env` prints the same report.
225
232
 
226
- | Key | Action |
227
- | --------- | ---------------------------------- |
228
- | `enter` | Run the selected script |
229
- | `x` | Stop a running script |
230
- | `f` / `c` | Filter the list / clear the filter |
233
+ - **Tools**: Node, pnpm, npm, Bun, Git, mise, direnv, Docker, Rust/Cargo, Claude Code, Codex, Tailscale and
234
+ Ollama, each with its version, or "not installed". Missing tools are normal. Every probe has a 3-second
235
+ timeout, so a hung executable shows as a warning instead of freezing anything.
236
+ - **mise** (optional). When mise is installed, the runtimes pinned for this project, read from
237
+ `mise ls --current --json`. An untrusted `mise.toml` is reported, never trusted automatically.
238
+ - **direnv** (optional). Whether the project has an `.envrc`, whether it is allowed, and whether devcc
239
+ itself was started with it loaded. "Environment: direnv allow" in the palette runs `direnv allow` after
240
+ a confirmation; devcc never approves an `.envrc` on its own.
231
241
 
232
- ### Git
242
+ Anything that needs attention is summarised in one line on the Overview.
233
243
 
234
- | Key | Action |
235
- | --------------- | --------------------------------- |
236
- | `s` / `u` | Stage / unstage the selected file |
237
- | `d` | Discard file changes (confirmed) |
238
- | `c` | Commit staged changes |
239
- | `p` / `shift+P` | Pull / push (push is confirmed) |
240
- | `f` | Fetch |
241
- | `r` | Refresh status |
244
+ ## Remote machines
242
245
 
243
- ### Dependencies
246
+ Declare the machines you develop on. `host` can be an alias from `~/.ssh/config`: devcc resolves it with
247
+ `ssh -G`, so your existing SSH settings apply, and it never stores users, keys or addresses of its own.
244
248
 
245
- | Key | Action |
246
- | --- | --------------------------------------- |
247
- | `r` | Check the registry for updates |
248
- | `u` | Update the selected package (confirmed) |
249
+ ```ts
250
+ remoteMachines: [
251
+ { id: "gpu", name: "GPU Server", host: "gpu-server", transport: "ssh", services: ["gpu-ollama"] },
252
+ ],
253
+ ```
249
254
 
250
- ### Logs
255
+ Each machine appears under REMOTE in the Services view: whether its SSH port answers and how quickly,
256
+ whether it is online in your tailnet, and the status of its associated services. `o` on a machine opens an
257
+ SSH session in a terminal. Reachability is a TCP connection to the SSH port, made at most once a minute
258
+ while the project is open, plus whenever you refresh.
251
259
 
252
- | Key | Action |
253
- | ---------------- | -------------------------------------------------- |
254
- | `j` / `k` | Scroll |
255
- | `PgUp` / `PgDn` | Page |
256
- | `home` / `end` | Jump to start / end |
257
- | `f` | Toggle follow |
258
- | `s` | Search - plain text, or a regex if it parses |
259
- | `x` | Clear the search |
260
- | `e` | Toggle errors only (stderr) |
261
- | `a` | Merged stream of every source, colour-keyed |
262
- | `tab`, `←` / `→` | Change log source |
263
- | `c` | Clear the current buffer (all, in the merged view) |
260
+ **Tailscale** (optional). When the `tailscale` CLI is available (including the macOS app's bundled one),
261
+ devcc reads `tailscale status --json` every 30 seconds: connected or not, this node's name, and which of
262
+ your remote machines are online.
263
+
264
+ **Remote Ollama** (optional). Point a service entry at any Ollama server, whether on localhost, a LAN
265
+ hostname or a Tailscale name:
266
+
267
+ ```ts
268
+ services: [{ id: "gpu-ollama", type: "ollama", baseUrl: "http://gpu-server:11434" }],
269
+ ```
270
+
271
+ The entry lists the server's models with parameter size, quantisation and disk size, marks the ones
272
+ currently loaded (with VRAM use) from `/api/ps`, and says "unreachable" when the server is down. Remote
273
+ endpoints are checked at most once a minute. The local Ollama on port 11434 (or `OLLAMA_HOST`) is still
274
+ detected automatically.
264
275
 
265
276
  ## Configuration
266
277
 
267
- Configuration is **optional** - without it the dashboard still detects your
268
- package manager, reads `package.json` scripts, shows git status, probes for
269
- local services, and monitors the host. A config file adds processes devcc can
270
- control and project-specific commands.
278
+ ### Without a config file
271
279
 
272
- devcc looks for the first of `.devcc.ts`, `devcc.config.ts`, `.devcc.mts`,
273
- `devcc.config.mts`, `.devcc.js`, `devcc.config.js`, `.devcc.mjs`,
274
- `devcc.config.mjs`, `.devcc.json` in the project root. TypeScript config files
275
- are transpiled on the fly, so no build step is needed.
280
+ Run `devcc` in a project with no configuration and it detects all of this, so Overview, Scripts, Git,
281
+ Services and System are populated on first launch:
276
282
 
277
- A real example (this repository's own `.devcc.ts`):
283
+ | Detects | How |
284
+ | --------------- | -------------------------------------------------------------------------------------------- |
285
+ | Project root | Walks up from the working directory looking for `package.json` or `.git` |
286
+ | Package manager | `packageManager` field → lockfile → the manager that launched it → npm |
287
+ | Scripts | `scripts` in `package.json` |
288
+ | Git state | `git status --porcelain=v2`: branch, staged/modified/untracked, ahead/behind, recent commits |
289
+ | Local services | TCP probes for Postgres, Redis and Ollama; the Docker daemon and any compose file |
290
+ | Dev servers | Ports inferred from your dependencies (`next` → 3000, `vite` → 5173, `typesense` → 8108) |
291
+ | Host metrics | Node's `os` and `fs` APIs: CPU, memory, disk, load, uptime |
292
+ | Tool versions | `git`, `docker` and your package manager, read once in the background |
278
293
 
279
- ```ts
280
- import { defineConfig } from "./src/devcc/index.js"
294
+ ### Adding processes and commands
281
295
 
282
- export default defineConfig({
283
- name: "dev command center",
296
+ devcc cannot guess that `pnpm dev` is the thing you want it to own, that it should wait for port 3000
297
+ before calling it ready, or that it is safe to restart. Nothing is launched or controlled until you declare
298
+ it in `.devcc.ts` in the project root:
284
299
 
300
+ ```ts
301
+ // ~/code/example.com/.devcc.ts
302
+ export default {
285
303
  processes: [
286
304
  {
287
- id: "typecheck",
288
- name: "tsc --watch",
289
- command: "pnpm",
290
- args: ["exec", "tsc", "-p", "tsconfig.json", "--noEmit", "--watch", "--pretty", "false"],
291
- kind: "service",
292
- },
293
- {
294
- id: "vitest",
295
- name: "vitest --watch",
305
+ id: "web",
306
+ name: "Next.js",
296
307
  command: "pnpm",
297
- args: ["exec", "vitest", "--watch"],
298
- kind: "service",
308
+ args: ["dev"],
309
+ url: "localhost:3000",
310
+ readiness: { port: 3000 }, // "running" means actually serving
311
+ restartOnCrash: true, // comes back after a crash, with backoff
299
312
  },
300
313
  ],
301
-
302
314
  commands: [
303
315
  {
304
- id: "verify",
305
- title: "Verify (lint, typecheck, test, build)",
306
- category: "Project",
316
+ id: "migrate",
317
+ title: "Run migrations",
318
+ category: "Database",
307
319
  command: "pnpm",
308
- args: ["run", "verify"],
309
- keywords: ["ci", "check", "all"],
310
- },
311
- {
312
- id: "clean",
313
- title: "Remove dist/",
314
- category: "Project",
315
- command: "rm",
316
- args: ["-rf", "dist"],
317
- confirm: true,
320
+ args: ["db:migrate"],
321
+ confirm: true, // asks first: it mutates the database
318
322
  },
319
323
  ],
320
-
321
- refresh: { system: 1_500, services: 5_000, git: 8_000 },
322
- })
324
+ }
323
325
  ```
324
326
 
325
- A monorepo example. In a project that has devcc installed, import `defineConfig`
326
- from the package name (`command-center`); in a project that does not, export a
327
- plain object instead - the shape is identical and it is validated either way:
327
+ Restart devcc and the Processes pane is live: `s` starts or stops, `r` restarts, `enter` jumps to that
328
+ process's logs, and quitting stops everything devcc started. The declared command appears in the palette
329
+ (`/`) and asks before it runs.
330
+
331
+ **Type-checked config.** If the project has `@doriandev/devcc` installed as a dependency, wrap the config
332
+ in `defineConfig` for editor autocompletion. The shape is the same and it is validated either way. A
333
+ monorepo example:
328
334
 
329
335
  ```ts
330
- import { defineConfig } from "command-center"
336
+ import { defineConfig } from "@doriandev/devcc"
331
337
 
332
338
  export default defineConfig({
333
339
  processes: [
@@ -362,44 +368,43 @@ export default defineConfig({
362
368
  })
363
369
  ```
364
370
 
365
- Without the package installed, the same config works as a plain object:
371
+ **File names.** devcc uses the first of `.devcc.ts`, `devcc.config.ts`, `.devcc.mts`, `devcc.config.mts`,
372
+ `.devcc.js`, `devcc.config.js`, `.devcc.mjs`, `devcc.config.mjs` or `.devcc.json` in the project root.
373
+ TypeScript is transpiled on the fly, so no build step is needed.
366
374
 
367
- ```ts
368
- // .devcc.ts in any project - no import, no dependency
369
- export default {
370
- processes: [{ id: "web", name: "Web", command: "pnpm", args: ["dev"], port: 3000 }],
371
- }
372
- ```
375
+ **Mistakes are safe.** Invalid entries are reported in **Settings → Config issues** and skipped; a typo
376
+ never stops the dashboard from starting.
373
377
 
374
- ### Process definitions
378
+ ### Process fields
375
379
 
376
380
  | Field | Type | Description |
377
381
  | ------------------- | ----------------------- | ------------------------------------------------------------------- |
378
- | `id` | `string` | Unique id. Also the log source id. **Required** |
379
- | `name` | `string` | Display name (defaults to `id`) |
380
- | `command` | `string` | Executable. **Required** - never a shell string |
382
+ | `id` | `string` | **Required.** Unique id, also used as the log source id |
383
+ | `command` | `string` | **Required.** Executable, never a shell string |
381
384
  | `args` | `string[]` | Arguments passed as an array, so quoting is never an issue |
385
+ | `name` | `string` | Display name (defaults to `id`) |
382
386
  | `cwd` | `string` | Absolute, or relative to the project root |
383
387
  | `env` | `Record<string,string>` | Extra environment variables |
384
388
  | `kind` | `"service" \| "task"` | Long-running vs. expected to exit (default `service`) |
385
389
  | `autoStart` | `boolean` | Start with the dashboard (default `false`) |
386
390
  | `url` | `string` | Informational address shown next to the process |
387
391
  | `port` | `number` | Port to associate with the process |
392
+ | `dependsOn` | `string[]` | Ids that must be _ready_ before this one starts |
393
+ | `readiness` | `object` | How devcc decides the process is usable, see below |
388
394
  | `killGraceMs` | `number` | SIGTERM → SIGKILL grace period (default 5000) |
389
395
  | `restartOnCrash` | `boolean` | Bring it back automatically after a non-zero exit (default `false`) |
390
396
  | `maxCrashRestarts` | `number` | Give up after this many consecutive crashes (default 5) |
391
397
  | `crashBackoffMs` | `number` | First backoff delay, doubling per crash (default 1000) |
392
398
  | `maxCrashBackoffMs` | `number` | Backoff ceiling (default 30000) |
393
399
  | `healthyAfterMs` | `number` | A run lasting this long resets the crash budget (default 30000) |
394
- | `dependsOn` | `string[]` | Ids that must be _ready_ before this one starts |
395
- | `readiness` | `object` | How devcc decides the process is usable - see below |
400
+
401
+ A manual stop cancels any pending crash restart and resets the budget.
396
402
 
397
403
  ### Readiness checks
398
404
 
399
- Without a readiness block a process is `running` the moment it has spawned.
400
- With one, it stays `starting` until the check passes, and `dependsOn` waits for
401
- that - so "start all" brings a stack up in the right order and only once each
402
- layer is actually usable.
405
+ Without a readiness block a process is `running` the moment it has spawned. With one, it stays `starting`
406
+ until the check passes, and `dependsOn` waits for that. So "start all" brings a stack up in the right
407
+ order, and only once each layer is actually usable.
403
408
 
404
409
  ```ts
405
410
  {
@@ -423,25 +428,86 @@ layer is actually usable.
423
428
  | `timeoutMs` | Give up waiting and report `running` anyway, with a warning (default 30000) |
424
429
  | `intervalMs` | Port probe interval (default 250) |
425
430
 
426
- Either `port` or `logPattern` is required; both may be given. A dependency
427
- cycle is reported and those processes are left alone rather than started in an
428
- arbitrary order.
431
+ Either `port` or `logPattern` is required; both may be given. A dependency cycle is reported and those
432
+ processes are left alone rather than started in an arbitrary order.
433
+
434
+ ### Command fields
435
+
436
+ | Field | Type | Description |
437
+ | ------------------------ | ------------------------------- | ------------------------------------------- |
438
+ | `id` | `string` | **Required.** Unique id |
439
+ | `command`, `args`, `cwd` | | As for processes. **`command` is required** |
440
+ | `title` | `string` | Palette entry text |
441
+ | `category` | `string` | Palette grouping (default `Project`) |
442
+ | `keywords` | `string[]` | Extra fuzzy-search terms |
443
+ | `confirm` | `boolean \| { title, message }` | Ask before running |
444
+
445
+ ### Service fields
446
+
447
+ Extra ports to watch: `{ id, name, port, host?, healthPath?, category? }`. `healthPath` upgrades the check
448
+ from a TCP probe to an HTTP request. An Ollama server is `{ id, name?, type: "ollama", baseUrl }`; see
449
+ [Remote machines](#remote-machines).
429
450
 
430
- ### Command definitions
451
+ ### Control-plane fields
431
452
 
432
- | Field | Type | Description |
433
- | ------------------------ | ------------------------------- | ---------------------------------------- |
434
- | `id` | `string` | Unique id. **Required** |
435
- | `title` | `string` | Palette entry text |
436
- | `category` | `string` | Palette grouping (default `Project`) |
437
- | `command`, `args`, `cwd` | | As for processes. **`command` required** |
438
- | `keywords` | `string[]` | Extra fuzzy-search terms |
439
- | `confirm` | `boolean \| { title, message }` | Ask before running |
453
+ All optional. Nothing here is required for devcc to work.
440
454
 
441
- ### Service definitions
455
+ | Field | Description |
456
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
457
+ | `verify` | Steps for `devcc verify`: command ids, script names, or `{ id, name?, command, args?, cwd? }` |
458
+ | `agents` | Per agent id: `{ command?, args?, name?, enabled? }`. `claude` and `codex` are built in; any other key adds an agent whose task is the last argument |
459
+ | `worktrees.directory` | Where new worktrees go, relative to the main worktree (default `..`) |
460
+ | `editor` | `{ command: "zed" }` (also `code`, `cursor`, `webstorm`, with optional `args`) or, on macOS, `{ application: "WebStorm" }` |
461
+ | `terminal` | macOS default: Terminal. `{ application: "iTerm" }`, or `{ command, args, execArgs }` where `{path}` in `args` is the directory and `execArgs` makes it run a program (used for SSH) |
462
+ | `remoteMachines` | `{ id, name?, host, transport: "ssh", port?, user?, services? }` |
463
+
464
+ ### A full example
465
+
466
+ A Next.js app on pnpm, with Claude Code and Codex working in worktrees, models served from a GPU box, and a
467
+ verification gate:
468
+
469
+ ```ts
470
+ // ~/code/shop/.devcc.ts
471
+ import { defineConfig } from "@doriandev/devcc"
472
+
473
+ export default defineConfig({
474
+ name: "shop",
475
+ editor: { command: "zed" },
476
+
477
+ processes: [
478
+ {
479
+ id: "web",
480
+ name: "Next.js",
481
+ command: "pnpm",
482
+ args: ["dev"],
483
+ url: "localhost:3000",
484
+ readiness: { port: 3000 },
485
+ restartOnCrash: true,
486
+ },
487
+ ],
488
+
489
+ agents: {
490
+ claude: { args: ["--permission-mode", "acceptEdits"] },
491
+ codex: { args: ["--sandbox", "workspace-write"] },
492
+ },
493
+ worktrees: { directory: "../shop-worktrees" },
494
+
495
+ verify: [
496
+ "lint",
497
+ "typecheck",
498
+ "test",
499
+ { id: "build", name: "Next.js build", command: "pnpm", args: ["build"] },
500
+ ],
501
+
502
+ services: [{ id: "gpu-ollama", type: "ollama", baseUrl: "http://gpu-server:11434" }],
503
+ remoteMachines: [
504
+ { id: "gpu", name: "RTX 3090 Server", host: "gpu-server", transport: "ssh", services: ["gpu-ollama"] },
505
+ ],
506
+ })
507
+ ```
442
508
 
443
- Extra ports to watch: `{ id, name, port, host?, healthPath?, category? }`.
444
- `healthPath` upgrades the check from a TCP probe to an HTTP request.
509
+ Open it with `devcc`, press `w`, then `n` to create `feature/checkout` and `c` to give Claude a task in it.
510
+ Press `shift+V` before you merge; the agent can run `devcc verify` itself.
445
511
 
446
512
  ### Other options
447
513
 
@@ -454,19 +520,167 @@ Extra ports to watch: `{ id, name, port, host?, healthPath?, category? }`.
454
520
  | `projects` | Extra project paths offered in the switcher (absolute or relative) |
455
521
  | `projectSearchDirs` | Extra directories to scan for sibling projects |
456
522
 
457
- Invalid entries are reported in **Settings → Config issues** and skipped; a typo
458
- never stops the dashboard from starting.
523
+ ## Keyboard shortcuts
524
+
525
+ You do not need to memorise these: press `?`, `F1` or `ctrl+k` in the app for the manual, or `/` to search
526
+ every action in the command palette. The footer shows key hints for the current view.
527
+
528
+ ### Global
529
+
530
+ | Key | Action |
531
+ | ----------------------- | --------------------------------------------- |
532
+ | `↑` / `k`, `↓` / `j` | Previous / next item |
533
+ | `←` / `h`, `→` | Previous / next section |
534
+ | `tab`, `shift+tab` | Cycle sections |
535
+ | `1`…`9`, `0` | Jump straight to a section (`0` is Worktrees) |
536
+ | `p`, `g`, `w`, `l`, `d` | Processes, Git, Worktrees, Logs, Dependencies |
537
+ | `enter` | Open / activate the selection |
538
+ | `esc` | Close an overlay |
539
+ | `/` | Command palette |
540
+ | `ctrl+p` | Open another project (others keep running) |
541
+ | `ctrl+o` | Cycle through open projects |
542
+ | `?` / `F1` / `ctrl+k` | The in-app manual |
543
+ | `shift+R` | Refresh everything |
544
+ | `shift+V` | Verify the project |
545
+ | `q` or `ctrl+c` | Quit (stops the processes devcc started) |
546
+
547
+ ### Processes and Services
548
+
549
+ | Key | Action |
550
+ | ------- | ---------------------------------- |
551
+ | `s` | Start or stop the selection |
552
+ | `r` | Restart the selection |
553
+ | `i` | Inspect the selection |
554
+ | `enter` | Jump to that process's logs |
555
+ | `d` | `docker compose down` (confirmed) |
556
+ | `o` | SSH to the selected remote machine |
557
+
558
+ ### Worktrees
559
+
560
+ | Key | Action |
561
+ | ----------- | ------------------------------------------------------ |
562
+ | `n` | New worktree (branch, base, directory) |
563
+ | `d` | Remove the worktree (confirmed, never forced) |
564
+ | `o` / enter | Open the worktree in devcc; on an agent, its logs |
565
+ | `t` / `e` | Open a terminal / your editor in the worktree |
566
+ | `c` / `x` | Launch Claude Code / Codex with a task in the worktree |
567
+ | `s` | Stop the selected agent |
568
+ | `r` | Refresh the list |
569
+
570
+ ### Scripts
571
+
572
+ | Key | Action |
573
+ | --------- | ---------------------------------- |
574
+ | `enter` | Run the selected script |
575
+ | `x` | Stop a running script |
576
+ | `f` / `c` | Filter the list / clear the filter |
577
+
578
+ ### Git
579
+
580
+ | Key | Action |
581
+ | --------------- | --------------------------------- |
582
+ | `s` / `u` | Stage / unstage the selected file |
583
+ | `c` | Commit staged changes |
584
+ | `f` | Fetch |
585
+ | `p` / `shift+P` | Pull / push (push is confirmed) |
586
+ | `d` | Discard file changes (confirmed) |
587
+ | `r` | Refresh status |
588
+
589
+ ### Logs
590
+
591
+ | Key | Action |
592
+ | ---------------- | -------------------------------------------------- |
593
+ | `j` / `k` | Scroll |
594
+ | `PgUp` / `PgDn` | Page |
595
+ | `home` / `end` | Jump to start / end |
596
+ | `f` | Toggle follow |
597
+ | `s` | Search: plain text, or a regex if it parses |
598
+ | `x` | Clear the search |
599
+ | `e` | Toggle errors only (stderr) |
600
+ | `a` | Merged stream of every source, colour-keyed |
601
+ | `tab`, `←` / `→` | Change log source |
602
+ | `c` | Clear the current buffer (all, in the merged view) |
603
+
604
+ ### Dependencies
459
605
 
460
- ## Architecture
606
+ | Key | Action |
607
+ | --- | --------------------------------------- |
608
+ | `r` | Check the registry for updates |
609
+ | `u` | Update the selected package (confirmed) |
610
+
611
+ ## Known limitations
612
+
613
+ - **Runtime.** The dashboard needs Bun, or Node ≥ 26.4 with `--experimental-ffi`. This is an OpenTUI/FFI
614
+ constraint, not a design choice.
615
+ - **Services.** Adapters ship for Docker Compose, PostgreSQL, Redis, Ollama (local or remote), Tailscale,
616
+ SSH machines and HTTP/port services. Others (Kubernetes, Vercel, Azure, …) are designed for but not
617
+ implemented.
618
+ - **Agents run headless.** Claude Code and Codex are started in their non-interactive modes with a task;
619
+ devcc cannot host their interactive interfaces. Use `t` for a terminal in the worktree instead.
620
+ - **Opening things outside devcc.** On macOS, terminals, editors and SSH use `open`. Elsewhere, set
621
+ `editor.command` and `terminal.command` (with `execArgs` for SSH).
622
+ - **direnv "loaded"** describes devcc's own environment (whether you started it from a direnv-enabled
623
+ shell), not your other terminals.
624
+ - **Remote reachability** is a TCP connection to the SSH port. It does not log in, so a machine that
625
+ accepts connections but rejects your key still shows as reachable.
626
+ - **Git.** The panel covers status, staging, discard, commit, fetch, pull and push. Branch switching,
627
+ rebasing, merge-conflict resolution and diff viewing are not implemented.
628
+ - **Detected services are read-only.** devcc will not start or stop something it did not launch, apart
629
+ from Docker Compose. `docker compose down --volumes` is deliberately not bound to a key.
630
+ - **Mouse.** Whatever OpenTUI provides by default; the interface is designed and tested for the keyboard.
631
+ - **Small terminals.** Below roughly 60 columns the sidebar hides and columns are trimmed. The layout
632
+ degrades rather than breaking, but it is cramped.
633
+
634
+ ## Contributing
635
+
636
+ ### Running from source
637
+
638
+ ```bash
639
+ git clone https://github.com/dordevgit/devcc.git
640
+ cd devcc
641
+ pnpm install
642
+ pnpm dev:center # launch against this repo
643
+ node bin/devcc.mjs -C ~/code/example.com # or against another project
644
+ ```
645
+
646
+ This repository's own [`.devcc.ts`](.devcc.ts) is a working config example.
647
+
648
+ ### Scripts
649
+
650
+ ```bash
651
+ pnpm lint # eslint (flat config, type-checked rules)
652
+ pnpm typecheck # tsc --noEmit, strict
653
+ pnpm test # vitest: core logic, no terminal required
654
+ pnpm test:tui # end-to-end smoke test against OpenTUI's headless renderer (Bun only)
655
+ pnpm build # tsc -> dist/
656
+ pnpm verify # lint + typecheck + test + build
657
+ pnpm format # prettier
658
+ ```
659
+
660
+ `pnpm test` covers the non-UI core: package-manager detection, config parsing and validation, the command
661
+ registry, the log ring buffer, process state transitions and real process lifecycles, service detection
662
+ and adapter failure handling, git porcelain and worktree parsing (plus create/remove against a real
663
+ repository), agent launching, tool/mise/direnv detection, verification and its CLI exit codes, Tailscale
664
+ and remote Ollama parsing, the observable store, and the formatting helpers. No optional tool (Claude,
665
+ Codex, Ollama, Tailscale, mise, direnv) needs to be installed.
666
+
667
+ `pnpm test:tui` boots the real application against OpenTUI's test renderer and drives it with synthetic
668
+ keystrokes: navigation, palette, help, starting/restarting/stopping a managed process, running a package
669
+ script, log capture, creating and removing worktrees, launching an agent in a worktree, verification, and
670
+ clean shutdown. It asserts on behaviour rather than on frame snapshots.
671
+
672
+ ### Architecture
461
673
 
462
674
  ```
463
675
  src/devcc/
464
- cli.ts argv parsing, TTY check, process exit code
676
+ cli.ts argv parsing, subcommands, TTY check, exit code
677
+ automation.ts devcc verify / worktree list / env (no TUI, JSON output)
465
678
  index.ts public API (defineConfig, App, all reusable pieces)
466
679
 
467
680
  app/
468
681
  App.ts composition root: renderer, services, state, input
469
682
  builtinCommands.ts the built-in command set
683
+ controlPlaneCommands.ts worktree, agent, verify, environment and remote commands
470
684
  keymap.ts global bindings + the help cheat sheet
471
685
  theme.ts palette and status glyphs
472
686
 
@@ -478,25 +692,26 @@ src/devcc/
478
692
 
479
693
  components/ Header, Footer, Sidebar, RowList, StatusBadge, Overlay,
480
694
  ConfirmDialog, CommandPalette, ProjectPicker, HelpOverlay
481
- views/ Overview, Processes, Services, Scripts, Git, Logs,
482
- Dependencies, System, Settings (all extend View.ts)
695
+ views/ Overview, Processes, Services, Scripts, Git, Worktrees,
696
+ Logs, Dependencies, System, Settings (all extend View.ts)
483
697
 
484
- processes/ ManagedProcess types + ProcessManager (incl. crash policy)
698
+ processes/ ManagedProcess types, ProcessManager (incl. crash policy),
699
+ graph.ts: dependsOn planner (waves + cycle detection)
485
700
  services/ ServiceAdapter interface, ServiceManager, and the
486
- docker / node / postgres / redis / ollama adapters
701
+ docker / node / postgres / redis / ollama adapters,
702
+ remote.ts: SSH machines + Tailscale
703
+ git/ GitService, pure porcelain-v2 parsers, worktrees.ts
704
+ agents/ AgentAdapter (Claude Code, Codex, custom) + AgentManager
705
+ environment/ tool detection, mise/direnv parsers, EnvironmentService
706
+ verify/ step resolution, the runner loop, the spawn executor
707
+ platform/ editor / terminal / SSH launch commands
708
+ logs/ LogBuffer ring buffer, LogStore, merge.ts (merged stream, search)
487
709
  deps/ DependencyService + per-manager `outdated` parsers
488
710
  projects/ ProjectRegistry: discovery, recents, switcher list
489
- processes/graph.ts dependsOn planner (waves + cycle detection)
490
- logs/merge.ts merged stream, search filter
491
- git/ GitService + pure porcelain-v2 parsers
492
711
  system/ SystemMonitor (Node APIs only)
493
- logs/ LogBuffer ring buffer + LogStore
494
- utils/ exec, packageManager, paths, format, fuzzy, logger,
495
- errors
712
+ utils/ exec, packageManager, paths, format, fuzzy, logger, errors
496
713
  ```
497
714
 
498
- ### How data flows
499
-
500
715
  ```
501
716
  ProcessManager ─┐
502
717
  ServiceManager ─┤ typed events ┌─────────┐ watched slices ┌───────┐
@@ -505,65 +720,61 @@ SystemMonitor ─┤ └─────────┘
505
720
  LogStore ─┘
506
721
  ```
507
722
 
508
- Backends never touch renderables. They emit typed events; `App` folds those into
509
- a single `AppState`; views subscribe to the slices they care about via
510
- `store.watch(selector, listener)`, so a 1 Hz CPU sample does not repaint the git
511
- panel. List rendering recycles `TextRenderable`s and only materialises the rows
723
+ Backends never touch renderables. They emit typed events; `App` folds those into a single `AppState`;
724
+ views subscribe to the slices they care about via `store.watch(selector, listener)`, so a 1 Hz CPU sample
725
+ does not repaint the git panel. List rendering recycles `TextRenderable`s and only materialises the rows
512
726
  that fit the viewport.
513
727
 
514
- ### Design decisions worth knowing
515
-
516
- - **Nothing runs through a shell.** Every child process is
517
- `spawn(executable, args[])`, so arguments containing spaces or quotes cannot
518
- become injection.
519
- - **Process trees die properly.** Children are spawned detached (their own
520
- process group) and stopped with `kill(-pgid, SIGTERM)`, escalating to
521
- `SIGKILL` after the grace period, so dev servers do not leave orphaned
522
- workers behind.
523
- - **Detected ≠ owned.** A process devcc launched is `ownership: "managed"`.
524
- Anything merely observed on the host is `"detected"` and can never be
525
- signalled by devcc.
526
- - **Destructive actions are always confirmed**: `git restore`, `git push`,
527
- `docker compose down`, stop-all, clear-all-logs, and any config command marked
528
- `confirm`. Discarding untracked files is refused outright.
529
- - **Memory is bounded by construction.** Each log source is a fixed-capacity
530
- ring buffer (5000 lines by default); nothing else retains log lines.
531
- - **Startup never blocks.** The first frame renders before git, Docker, service
532
- detection or tool versions have answered; each populates asynchronously with
533
- its own timeout.
534
- - **Several projects can be open at once.** `ctrl+p` opens another project
535
- without closing the current one; each keeps its processes and log capture
536
- alive in the background, only the visible project polls git/services/system,
537
- and the sidebar grows a PROJECTS block with per-project running counts.
538
- Closing a project (or quitting) stops what devcc started there, with a
539
- confirmation.
540
- - **Starts are ordered.** `dependsOn` and `readiness` turn "start all" into a
541
- wave-by-wave bring-up: each wave waits for the previous one to be ready. The
728
+ ### Design decisions
729
+
730
+ How the worktree, agent, environment, verification and remote features fit onto the existing abstractions
731
+ is written up in [docs/control-plane.md](docs/control-plane.md).
732
+
733
+ **Safety**
734
+
735
+ - **Nothing runs through a shell.** Every child process is `spawn(executable, args[])`, so arguments
736
+ containing spaces or quotes cannot become injection.
737
+ - **Detected ≠ owned.** A process devcc launched is `ownership: "managed"`. Anything merely observed on the
738
+ host is `"detected"` and can never be signalled by devcc.
739
+ - **Destructive actions are always confirmed:** `git restore`, `git push`, `docker compose down`, stop-all,
740
+ clear-all-logs, and any config command marked `confirm`. Discarding untracked files is refused outright.
741
+ - **Process trees die properly.** Children are spawned detached (their own process group) and stopped with
742
+ `kill(-pgid, SIGTERM)`, escalating to `SIGKILL` after the grace period, so dev servers do not leave
743
+ orphaned workers behind.
744
+
745
+ **Performance**
746
+
747
+ - **Startup never blocks.** The first frame renders before git, Docker, service detection or tool versions
748
+ have answered; each populates asynchronously with its own timeout.
749
+ - **Memory is bounded.** Each log source is a fixed-capacity ring buffer (5000 lines by default). The
750
+ merged view materialises at most 2,000 recent lines on demand. Search is plain substring, or a regex when
751
+ the query parses as one, so typing `(` never breaks the view.
752
+ - **Dependency checks never poll.** They reach the network, so they run only when you ask, and updating a
753
+ package is confirmed because it rewrites the lockfile.
754
+ - **Remote checks are rate-limited.** An adapter can set `refreshIntervalMs`; the poller skips it until that
755
+ has passed, so an open dashboard does not keep hitting a remote machine. Explicit refresh ignores it.
756
+ - **Agents are processes.** An agent adapter only describes a command line; the session is a managed task
757
+ in the same `ProcessManager`, so status, logs, stop and shutdown work exactly as for anything else.
758
+
759
+ **Structure**
760
+
761
+ - **Starts are ordered.** `dependsOn` and `readiness` turn "start all" into a wave-by-wave bring-up. The
542
762
  ordering is a pure function (`planStartOrder`) with its own tests.
543
- - **The merged log stream is bounded.** Per-source views window straight into
544
- that source's ring buffer; the merged view materialises at most 2,000 recent
545
- lines on demand. Searching is plain substring, or a regex when the query
546
- parses as one, so typing `(` never breaks the view.
547
- - **Crash recovery is opt-in.** A process only comes back automatically if its
548
- definition says `restartOnCrash`, with exponential backoff and a hard attempt
549
- limit; a manual stop cancels any pending restart and clears the budget.
550
- - **Switching project rebuilds the session, not the UI.** The renderer, views and
551
- store survive; the process manager, service adapters, git service, logger and
552
- command registry are torn down and rebuilt for the new directory, and anything
553
- devcc had started is stopped first (with a confirmation).
554
- - **Dependency checks never poll.** They reach the network, so they run only when
555
- you ask, and updating a package is confirmed because it rewrites the lockfile.
556
- - **Errors are never swallowed.** They are normalised into `DevccError`, written
557
- to `.devcc/devcc.log`, shown in the footer, and kept for inspection in
558
- **Settings → Diagnostics**.
559
-
560
- ## Extension points
561
-
562
- **Add a command** - register it once and it appears in the palette, with an
563
- optional confirmation gate:
763
+ - **Several projects stay open.** Each open project keeps its processes and log capture alive in the
764
+ background; only the visible one polls git, services and system. Closing a project (or quitting) stops
765
+ what devcc started there, with a confirmation.
766
+ - **Switching project rebuilds the session, not the UI.** The renderer, views and store survive; the
767
+ process manager, service adapters, git service, logger and command registry are rebuilt for the new
768
+ directory.
769
+ - **Errors are never swallowed.** They are normalised into `DevccError`, written to `.devcc/devcc.log`,
770
+ shown in the footer, and kept for inspection in **Settings → Diagnostics**.
771
+
772
+ ### Extending devcc
773
+
774
+ **Add a command.** Register it once and it appears in the palette, with an optional confirmation gate:
564
775
 
565
776
  ```ts
566
- import { type CommandDefinition } from "command-center"
777
+ import { type CommandDefinition } from "@doriandev/devcc"
567
778
 
568
779
  const command: CommandDefinition = {
569
780
  id: "deploy.preview",
@@ -578,15 +789,15 @@ const command: CommandDefinition = {
578
789
  }
579
790
  ```
580
791
 
581
- `CommandContext` exposes the store, the process/service/git/log/system services,
582
- and `navigate`, `notify`, `confirm`, `prompt`, `runScript` and `quit`.
792
+ `CommandContext` exposes the store, the process/service/git/log/system services, and `navigate`,
793
+ `notify`, `confirm`, `prompt`, `runScript` and `quit`.
583
794
 
584
- **Add a service adapter** - implement `ServiceAdapter` and register it with the
585
- `ServiceManager`. `detect()` decides whether the service is relevant at all, so
586
- adapters cost nothing in projects that do not use them:
795
+ **Add a service adapter.** Implement `ServiceAdapter` and register it with the `ServiceManager`.
796
+ `detect()` decides whether the service is relevant at all, so adapters cost nothing in projects that do not
797
+ use them:
587
798
 
588
799
  ```ts
589
- import { type ServiceAdapter } from "command-center"
800
+ import { type ServiceAdapter } from "@doriandev/devcc"
590
801
 
591
802
  export class TailscaleService implements ServiceAdapter {
592
803
  readonly id = "tailscale"
@@ -608,52 +819,11 @@ export class TailscaleService implements ServiceAdapter {
608
819
  }
609
820
  ```
610
821
 
611
- The same shape is how Azure, Vercel, GitHub, Kubernetes, remote SSH hosts or CI
612
- pipelines would be added later - each is a `detect()` plus a `getStatus()`, with
613
- optional `start`/`stop`/`restart`.
614
-
615
- **Add a view** - extend `views/View.ts`, register it in `App`, and add an entry
616
- to `SIDEBAR_ITEMS`.
617
-
618
- ## Development
619
-
620
- ```bash
621
- pnpm lint # eslint (flat config, type-checked rules)
622
- pnpm typecheck # tsc --noEmit, strict
623
- pnpm test # vitest - core logic, no terminal required
624
- pnpm test:tui # end-to-end smoke test against OpenTUI's headless renderer
625
- pnpm build # tsc -> dist/
626
- pnpm verify # lint + typecheck + test + build
627
- pnpm format # prettier
628
- ```
629
-
630
- `pnpm test` covers the non-UI core: package-manager detection, config parsing
631
- and validation, the command registry, the log ring buffer, process state
632
- transitions and real process lifecycles, service detection and adapter failure
633
- handling, git porcelain parsing, the observable store, and the formatting
634
- helpers.
822
+ Azure, Vercel, GitHub, Kubernetes, remote SSH hosts or CI pipelines would be added the same way: a
823
+ `detect()` plus a `getStatus()`, with optional `start`/`stop`/`restart`.
635
824
 
636
- `pnpm test:tui` boots the actual application against OpenTUI's supported test
637
- renderer (Bun only) and drives it with synthetic keystrokes: navigation,
638
- palette, help, starting/restarting/stopping a managed process, running a
639
- package script, log capture, and clean shutdown. It asserts on behaviour rather
640
- than on frame snapshots.
825
+ **Add a view.** Extend `views/View.ts`, register it in `App`, and add an entry to `SIDEBAR_ITEMS`.
641
826
 
642
- ## Known limitations
827
+ ## License
643
828
 
644
- - The dashboard itself requires Bun (or Node ≥ 26.4 with `--experimental-ffi`);
645
- this is an OpenTUI/FFI constraint, not a design choice.
646
- - Service adapters ship for Docker Compose, PostgreSQL, Redis, Ollama and
647
- HTTP/port services. Others (Kubernetes, Vercel, Azure, Tailscale, …) are
648
- designed for but not implemented.
649
- - The Git panel covers status, staging, discard, commit, fetch, pull and push.
650
- Branch switching, rebasing, merge-conflict resolution and diff viewing in the
651
- UI are not implemented (`GitService.diff()` exists but has no view yet).
652
- - Detected services are read-only: devcc will not start or stop something it did
653
- not launch, apart from Docker Compose.
654
- - `docker compose down --volumes` is available through `DockerService.down()`
655
- but is deliberately not bound to a key.
656
- - Mouse support is whatever OpenTUI provides by default; the interface is
657
- designed and tested for the keyboard.
658
- - Below roughly 60 columns the sidebar hides and columns are trimmed; the layout
659
- degrades rather than breaking, but very small terminals are cramped.
829
+ [ISC](LICENSE)