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