devlanes 0.2.0 → 0.4.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.
package/user-schema.json CHANGED
@@ -6,49 +6,48 @@
6
6
  "examples": [
7
7
  "5GiB"
8
8
  ],
9
+ "markdownDescription": "A byte count with decimal or binary size units.",
9
10
  "pattern": "^[0-9]+(B|KB|MB|GB|KiB|MiB|GiB)$",
10
11
  "type": "string"
11
12
  },
12
13
  "CaKind": {
13
- "description": "Certificate authority used to issue certificates for local project hostnames.",
14
+ "description": "Certificate authority that signs HTTPS certificates for lane hostnames.",
15
+ "enum": [
16
+ "devlanes",
17
+ "mkcert"
18
+ ],
19
+ "enumDescriptions": [
20
+ "devlanes creates its own CA in `~/.devlanes/ca` on first use, limited to names under `localhost`, `test`, `internal`, `home.arpa` and `hostnames.tld`, so it cannot sign for real sites. Trust it once with `devlanes certs trust`.",
21
+ "Load mkcert's existing root CA (from `mkcert -CAROOT`) instead, so browsers that already trust it need nothing new. HTTPS stays off when `mkcert` is not on `PATH`; the mkcert CA has no name limits, and `certs untrust` leaves it trusted."
22
+ ],
14
23
  "examples": [
15
24
  "devlanes",
16
25
  "mkcert"
17
26
  ],
18
- "oneOf": [
19
- {
20
- "const": "devlanes",
21
- "description": "Use the CA managed in the devlanes home directory.",
22
- "examples": [
23
- "devlanes"
24
- ],
25
- "type": "string"
26
- },
27
- {
28
- "const": "mkcert",
29
- "description": "Reuse the CA in mkcert's CAROOT directory.",
30
- "examples": [
31
- "mkcert"
32
- ],
33
- "type": "string"
34
- }
35
- ]
27
+ "markdownDescription": "Certificate authority that signs HTTPS certificates for lane hostnames.",
28
+ "markdownEnumDescriptions": [
29
+ "devlanes creates its own CA in `~/.devlanes/ca` on first use, limited to names under `localhost`, `test`, `internal`, `home.arpa` and `hostnames.tld`, so it cannot sign for real sites. Trust it once with `devlanes certs trust`.",
30
+ "Load mkcert's existing root CA (from `mkcert -CAROOT`) instead, so browsers that already trust it need nothing new. HTTPS stays off when `mkcert` is not on `PATH`; the mkcert CA has no name limits, and `certs untrust` leaves it trusted."
31
+ ],
32
+ "type": "string"
36
33
  },
37
34
  "CloudflareDns": {
38
35
  "additionalProperties": false,
39
- "description": "Cloudflare DNS credentials for the reserved public resolver.",
36
+ "description": "Reserved Cloudflare DNS credentials for the planned `public` resolver; not supported yet.",
40
37
  "examples": [
41
38
  {
42
39
  "token": "${{ env.CF_API_TOKEN }}"
43
40
  }
44
41
  ],
42
+ "markdownDescription": "Reserved Cloudflare DNS credentials for the planned `public` resolver; not supported yet.",
45
43
  "properties": {
46
44
  "token": {
47
45
  "$ref": "#/$defs/Templated_string",
48
- "description": "API token with permission to edit the domain's DNS records; prefer an environment expression.",
46
+ "description": "Reserved: Cloudflare API token allowed to edit the domain's DNS records. Write an environment expression such as `${{ env.CF_API_TOKEN }}`, not the token itself.",
49
47
  "examples": [
50
48
  "${{ env.CF_API_TOKEN }}"
51
- ]
49
+ ],
50
+ "markdownDescription": "Reserved: Cloudflare API token allowed to edit the domain's DNS records. Write an environment expression such as `${{ env.CF_API_TOKEN }}`, not the token itself."
52
51
  }
53
52
  },
54
53
  "required": [
@@ -57,17 +56,41 @@
57
56
  "type": "object"
58
57
  },
59
58
  "ConfigDuration": {
60
- "description": "A duration written with `ms`, `s`, `m`, `h`, or `d` units.",
59
+ "description": "A duration: a whole number followed by `ms`, `s`, `m`, `h` or `d`, such as `500ms`, `15s` or `10m`. Units cannot be combined.",
61
60
  "examples": [
62
61
  "15s",
63
62
  "10m"
64
63
  ],
64
+ "markdownDescription": "A duration: a whole number followed by `ms`, `s`, `m`, `h` or `d`, such as `500ms`, `15s` or `10m`. Units cannot be combined.",
65
65
  "pattern": "^[0-9]+(ms|s|m|h|d)$",
66
66
  "type": "string"
67
67
  },
68
+ "DaemonConfig": {
69
+ "additionalProperties": false,
70
+ "description": "The daemon's own lifetime.",
71
+ "examples": [
72
+ {
73
+ "exit_when_idle": false
74
+ }
75
+ ],
76
+ "markdownDescription": "The daemon's own lifetime.",
77
+ "properties": {
78
+ "exit_when_idle": {
79
+ "default": true,
80
+ "description": "Stop the daemon about 30 seconds after the last TUI closes when no service, task or start is running; the next devlanes command starts it again. Containers keep running either way. Defaults to true. Only a daemon a command started on demand exits; one installed with `devlanes daemon install` keeps running.",
81
+ "examples": [
82
+ true,
83
+ false
84
+ ],
85
+ "markdownDescription": "Stop the daemon about 30 seconds after the last TUI closes when no service, task or start is running; the next devlanes command starts it again. Containers keep running either way. Defaults to true. Only a daemon a command started on demand exits; one installed with `devlanes daemon install` keeps running.",
86
+ "type": "boolean"
87
+ }
88
+ },
89
+ "type": "object"
90
+ },
68
91
  "DnsProvider": {
69
92
  "additionalProperties": false,
70
- "description": "Reserved DNS-01 provider configuration; not supported yet.",
93
+ "description": "Reserved DNS-01 provider settings for the planned `public` resolver; not supported yet.",
71
94
  "examples": [
72
95
  {
73
96
  "cloudflare": {
@@ -75,15 +98,17 @@
75
98
  }
76
99
  }
77
100
  ],
101
+ "markdownDescription": "Reserved DNS-01 provider settings for the planned `public` resolver; not supported yet.",
78
102
  "properties": {
79
103
  "cloudflare": {
80
104
  "$ref": "#/$defs/CloudflareDns",
81
- "description": "Cloudflare DNS API credentials supplied by an environment expression.",
105
+ "description": "Reserved: Cloudflare credentials for DNS-01 certificates; not supported yet.",
82
106
  "examples": [
83
107
  {
84
108
  "token": "${{ env.CF_API_TOKEN }}"
85
109
  }
86
- ]
110
+ ],
111
+ "markdownDescription": "Reserved: Cloudflare credentials for DNS-01 certificates; not supported yet."
87
112
  }
88
113
  },
89
114
  "required": [
@@ -93,38 +118,41 @@
93
118
  },
94
119
  "HistoryConfig": {
95
120
  "additionalProperties": false,
96
- "description": "Age limits for finished unit/task history and disconnected remote-host records.",
121
+ "description": "How long the daemon's database keeps records of finished runs and of remote boxes. An hourly cleanup deletes what is older.",
97
122
  "examples": [
98
123
  {
99
124
  "remote_hosts_retention": "90d",
100
125
  "retention": "7d"
101
126
  }
102
127
  ],
128
+ "markdownDescription": "How long the daemon's database keeps records of finished runs and of remote boxes. An hourly cleanup deletes what is older.",
103
129
  "properties": {
104
130
  "remote_hosts_retention": {
105
131
  "$ref": "#/$defs/ConfigDuration",
106
132
  "default": "7776000000ms",
107
- "description": "Maximum age since a remote host last connected successfully; defaults to 90d. Records without a timestamp are retained.",
133
+ "description": "How long a remote box stays listed by `devlanes remote ls` after its last successful connection; defaults to `90d` and must be longer than `0s`. A record without a connection time is never deleted by age, and `state.max_size` can delete records sooner.",
108
134
  "examples": [
109
135
  "90d",
110
136
  "180d"
111
- ]
137
+ ],
138
+ "markdownDescription": "How long a remote box stays listed by `devlanes remote ls` after its last successful connection; defaults to `90d` and must be longer than `0s`. A record without a connection time is never deleted by age, and `state.max_size` can delete records sooner."
112
139
  },
113
140
  "retention": {
114
141
  "$ref": "#/$defs/ConfigDuration",
115
142
  "default": "604800000ms",
116
- "description": "Maximum age since a unit or task finished; defaults to 7d. Disk pressure may prune it sooner.",
143
+ "description": "How long records of finished service and task runs (start and end times, exit status) are kept after the run ends; defaults to `7d` and must be longer than `0s`. A run that still has log index entries or a metrics summary in the database stays until those expire, and `state.max_size` can delete runs sooner.",
117
144
  "examples": [
118
145
  "7d",
119
146
  "30d"
120
- ]
147
+ ],
148
+ "markdownDescription": "How long records of finished service and task runs (start and end times, exit status) are kept after the run ends; defaults to `7d` and must be longer than `0s`. A run that still has log index entries or a metrics summary in the database stays until those expire, and `state.max_size` can delete runs sooner."
121
149
  }
122
150
  },
123
151
  "type": "object"
124
152
  },
125
153
  "HostnamesConfig": {
126
154
  "additionalProperties": false,
127
- "description": "Hostname resolution and optional system hosts-file entries.",
155
+ "description": "How lane hostnames resolve to this machine, and whether they are also written to `/etc/hosts` for programs that cannot resolve them otherwise.",
128
156
  "examples": [
129
157
  {
130
158
  "hosts_file": "auto",
@@ -135,13 +163,15 @@
135
163
  "tld": "test"
136
164
  }
137
165
  ],
166
+ "markdownDescription": "How lane hostnames resolve to this machine, and whether they are also written to `/etc/hosts` for programs that cannot resolve them otherwise.",
138
167
  "properties": {
139
168
  "base_domain": {
140
169
  "default": null,
141
- "description": "Reserved public-resolver base domain; not supported yet.",
170
+ "description": "Reserved for the planned `public` resolver; any value is rejected for now.",
142
171
  "examples": [
143
172
  "dev.example.invalid"
144
173
  ],
174
+ "markdownDescription": "Reserved for the planned `public` resolver; any value is rejected for now.",
145
175
  "type": [
146
176
  "string",
147
177
  "null"
@@ -150,56 +180,66 @@
150
180
  "dns_provider": {
151
181
  "anyOf": [
152
182
  {
153
- "$ref": "#/$defs/DnsProvider"
183
+ "$ref": "#/$defs/DnsProvider",
184
+ "description": "",
185
+ "examples": [],
186
+ "markdownDescription": ""
154
187
  },
155
188
  {
156
189
  "type": "null"
157
190
  }
158
191
  ],
159
192
  "default": null,
160
- "description": "Reserved DNS-01 provider settings; not supported yet.",
193
+ "description": "Reserved for the planned `public` resolver's DNS-01 certificates; any value is rejected for now.",
161
194
  "examples": [
162
195
  {
163
196
  "cloudflare": {
164
197
  "token": "${{ env.CF_API_TOKEN }}"
165
198
  }
166
199
  }
167
- ]
200
+ ],
201
+ "markdownDescription": "Reserved for the planned `public` resolver's DNS-01 certificates; any value is rejected for now."
168
202
  },
169
203
  "hosts_file": {
170
204
  "anyOf": [
171
205
  {
172
- "$ref": "#/$defs/HostsFile"
206
+ "$ref": "#/$defs/HostsFile",
207
+ "description": "",
208
+ "examples": [],
209
+ "markdownDescription": ""
173
210
  },
174
211
  {
175
212
  "type": "null"
176
213
  }
177
214
  ],
178
215
  "default": null,
179
- "description": "Whether exact hostnames should also be written to the system hosts file.",
216
+ "description": "Whether lane hostnames are also written to `/etc/hosts`, for programs that do not resolve `*.localhost` themselves (workerd, musl or pure-Go binaries); defaults to `auto`, which writes them only when a probe at daemon start shows the system resolver fails `*.localhost`. `never` here wins over every project; otherwise a project's `lanes.hosts_file` replaces this value for that project. Writing needs the helper `devlanes certs trust --hosts` installs, only exact names are written (no wildcards), and `resolver: hosts_file` ignores this key.",
180
217
  "examples": [
181
218
  "auto",
182
219
  "never",
183
220
  "always"
184
- ]
221
+ ],
222
+ "markdownDescription": "Whether lane hostnames are also written to `/etc/hosts`, for programs that do not resolve `*.localhost` themselves (workerd, musl or pure-Go binaries); defaults to `auto`, which writes them only when a probe at daemon start shows the system resolver fails `*.localhost`. `never` here wins over every project; otherwise a project's `lanes.hosts_file` replaces this value for that project. Writing needs the helper `devlanes certs trust --hosts` installs, only exact names are written (no wildcards), and `resolver: hosts_file` ignores this key."
185
223
  },
186
224
  "resolver": {
187
225
  "$ref": "#/$defs/Resolver",
188
226
  "default": "localhost",
189
- "description": "localhost or resolver in v1; public parses but is not supported yet.",
227
+ "description": "Which domain lanes get and what makes their hostnames resolve; defaults to `localhost`. `resolver` also needs `tld` and a one-time `devlanes certs trust --resolver`; `public` is not supported yet.",
190
228
  "examples": [
191
229
  "localhost",
192
230
  "hosts_file",
193
231
  "resolver",
194
232
  "public"
195
- ]
233
+ ],
234
+ "markdownDescription": "Which domain lanes get and what makes their hostnames resolve; defaults to `localhost`. `resolver` also needs `tld` and a one-time `devlanes certs trust --resolver`; `public` is not supported yet."
196
235
  },
197
236
  "tld": {
198
237
  "default": null,
199
- "description": "Custom top-level domain for the embedded DNS strategy.",
238
+ "description": "Top-level domain for the `resolver` strategy, such as `test`; required there. It must be `test`, `internal`, `home.arpa` or a domain under one of them (or under `localhost`), made of valid DNS labels, because any other name could belong to a real site; the file is rejected otherwise, whatever the strategy. A new devlanes CA may sign names under the `tld` set when it is created, whatever the strategy, so switching to another `tld` later needs a new CA.",
200
239
  "examples": [
201
240
  "test"
202
241
  ],
242
+ "markdownDescription": "Top-level domain for the `resolver` strategy, such as `test`; required there. It must be `test`, `internal`, `home.arpa` or a domain under one of them (or under `localhost`), made of valid DNS labels, because any other name could belong to a real site; the file is rejected otherwise, whatever the strategy. A new devlanes CA may sign names under the `tld` set when it is created, whatever the strategy, so switching to another `tld` later needs a new CA.",
203
243
  "type": [
204
244
  "string",
205
245
  "null"
@@ -209,73 +249,67 @@
209
249
  "type": "object"
210
250
  },
211
251
  "HostsFile": {
212
- "description": "Whether project hostnames are also written to the system hosts file.",
252
+ "description": "Whether routed hostnames are also written to `/etc/hosts`.",
253
+ "enum": [
254
+ "auto",
255
+ "never",
256
+ "always"
257
+ ],
258
+ "enumDescriptions": [
259
+ "Write the exact hostnames only when the daemon's startup check finds that the system resolver does not answer `*.localhost` with a loopback address.",
260
+ "Never write hostnames to `/etc/hosts`. Set in the user config, it applies to every project whatever the project sets.",
261
+ "Always write the exact hostnames, even when the system resolver handles `*.localhost`, for programs that send `*.localhost` to DNS themselves."
262
+ ],
213
263
  "examples": [
214
264
  "auto",
215
265
  "never",
216
266
  "always"
217
267
  ],
218
- "oneOf": [
219
- {
220
- "const": "auto",
221
- "description": "Write entries when the system cannot resolve the configured hostnames.",
222
- "examples": [
223
- "auto"
224
- ],
225
- "type": "string"
226
- },
227
- {
228
- "const": "never",
229
- "description": "Do not write hosts entries.",
230
- "examples": [
231
- "never"
232
- ],
233
- "type": "string"
234
- },
235
- {
236
- "const": "always",
237
- "description": "Always write exact hostnames, including for runtimes without localhost subdomain support.",
238
- "examples": [
239
- "always"
240
- ],
241
- "type": "string"
242
- }
243
- ]
268
+ "markdownDescription": "Whether routed hostnames are also written to `/etc/hosts`.",
269
+ "markdownEnumDescriptions": [
270
+ "Write the exact hostnames only when the daemon's startup check finds that the system resolver does not answer `*.localhost` with a loopback address.",
271
+ "Never write hostnames to `/etc/hosts`. Set in the user config, it applies to every project whatever the project sets.",
272
+ "Always write the exact hostnames, even when the system resolver handles `*.localhost`, for programs that send `*.localhost` to DNS themselves."
273
+ ],
274
+ "type": "string"
244
275
  },
245
276
  "LogRetention": {
246
277
  "additionalProperties": false,
247
- "description": "Disk retention bounded by both age and total log size.",
278
+ "description": "Limits for stored service output; reaching either one deletes the oldest output first.",
248
279
  "examples": [
249
280
  {
250
281
  "days": 7,
251
282
  "max_size": "5GiB"
252
283
  }
253
284
  ],
285
+ "markdownDescription": "Limits for stored service output; reaching either one deletes the oldest output first.",
254
286
  "properties": {
255
287
  "days": {
256
288
  "default": 7,
257
- "description": "Number of days to retain logs and database log-segment metadata; defaults to 7.",
289
+ "description": "Delete output older than this many days, along with its index entries in the database; defaults to 7 and must be at least 1. The newest log file of each stopped service is kept.",
258
290
  "examples": [
259
291
  7
260
292
  ],
261
293
  "format": "uint32",
262
- "minimum": 0,
294
+ "markdownDescription": "Delete output older than this many days, along with its index entries in the database; defaults to 7 and must be at least 1. The newest log file of each stopped service is kept.",
295
+ "minimum": 1,
263
296
  "type": "integer"
264
297
  },
265
298
  "max_size": {
266
299
  "$ref": "#/$defs/ByteSize",
267
300
  "default": "5368709120B",
268
- "description": "Maximum total size of retained logs; defaults to 5GiB.",
301
+ "description": "Total size of stored output across all projects and lanes; defaults to `5GiB` and must be above zero. Over it, the oldest output goes first wherever it is, but each stopped service's newest file and up to 1 MiB of a running service's current file are kept.",
269
302
  "examples": [
270
303
  "5GiB"
271
- ]
304
+ ],
305
+ "markdownDescription": "Total size of stored output across all projects and lanes; defaults to `5GiB` and must be above zero. Over it, the oldest output goes first wherever it is, but each stopped service's newest file and up to 1 MiB of a running service's current file are kept."
272
306
  }
273
307
  },
274
308
  "type": "object"
275
309
  },
276
310
  "LogsConfig": {
277
311
  "additionalProperties": false,
278
- "description": "Retention settings for stored service/task output and database log metadata.",
312
+ "description": "Retention of the service output stored under `~/.devlanes/logs`.",
279
313
  "examples": [
280
314
  {
281
315
  "retention": {
@@ -284,6 +318,7 @@
284
318
  }
285
319
  }
286
320
  ],
321
+ "markdownDescription": "Retention of the service output stored under `~/.devlanes/logs`.",
287
322
  "properties": {
288
323
  "retention": {
289
324
  "$ref": "#/$defs/LogRetention",
@@ -291,34 +326,37 @@
291
326
  "days": 7,
292
327
  "max_size": "5368709120B"
293
328
  },
294
- "description": "Age and total-size bounds for log files; the age limit also bounds database log-segment metadata.",
329
+ "description": "Age and total-size limits for stored service output; a cleanup every 10 minutes deletes the oldest output past either one. Defaults to 7 days and `5GiB`.",
295
330
  "examples": [
296
331
  {
297
332
  "days": 7,
298
333
  "max_size": "5GiB"
299
334
  }
300
- ]
335
+ ],
336
+ "markdownDescription": "Age and total-size limits for stored service output; a cleanup every 10 minutes deletes the oldest output past either one. Defaults to 7 days and `5GiB`."
301
337
  }
302
338
  },
303
339
  "type": "object"
304
340
  },
305
341
  "MetricsConfig": {
306
342
  "additionalProperties": false,
307
- "description": "Retention for monitor history used by graphs and service baselines, including database aggregates.",
343
+ "description": "Retention of the CPU and memory history in the daemon's database.",
308
344
  "examples": [
309
345
  {
310
346
  "retention": "7d"
311
347
  }
312
348
  ],
349
+ "markdownDescription": "Retention of the CPU and memory history in the daemon's database.",
313
350
  "properties": {
314
351
  "retention": {
315
352
  "$ref": "#/$defs/ConfigDuration",
316
353
  "default": "604800000ms",
317
- "description": "Maximum age of persisted monitor samples and database run summaries; defaults to 7d.",
354
+ "description": "How long per-minute CPU and memory samples, per-run summaries and startup memory peaks are kept; defaults to `7d` and must be longer than `0s`. Shorter keeps less history for graphs and for the memory a start is expected to need; `state.max_size` can delete samples sooner.",
318
355
  "examples": [
319
356
  "7d",
320
357
  "30d"
321
- ]
358
+ ],
359
+ "markdownDescription": "How long per-minute CPU and memory samples, per-run summaries and startup memory peaks are kept; defaults to `7d` and must be longer than `0s`. Shorter keeps less history for graphs and for the memory a start is expected to need; `state.max_size` can delete samples sooner."
322
360
  }
323
361
  },
324
362
  "type": "object"
@@ -329,24 +367,27 @@
329
367
  "75%",
330
368
  "12.5%"
331
369
  ],
370
+ "markdownDescription": "A percentage with a `%` suffix, such as `75%` or `12.5%`.",
332
371
  "pattern": "^\\s*([0-9]+\\.?[0-9]*|\\.[0-9]+)\\s*%\\s*$",
333
372
  "type": "string"
334
373
  },
335
374
  "PortsConfig": {
336
375
  "additionalProperties": false,
337
- "description": "Machine-wide port pool shared by all projects and lanes.",
376
+ "description": "Machine-wide pool every lane's block of ports comes from.",
338
377
  "examples": [
339
378
  {
340
379
  "range": "20000-32767"
341
380
  }
342
381
  ],
382
+ "markdownDescription": "Machine-wide pool every lane's block of ports comes from.",
343
383
  "properties": {
344
384
  "range": {
345
385
  "default": "20000-32767",
346
- "description": "Inclusive port range, written start-end; defaults to 20000-32767.",
386
+ "description": "Ports devlanes may hand out, written `start-end` (both included, start above 0); defaults to `20000-32767`, below Linux's usual ephemeral range so the OS does not pick them for outgoing connections. Each lane of each project gets its own block of consecutive ports from it, so a smaller range fits fewer lanes.",
347
387
  "examples": [
348
388
  "20000-32767"
349
389
  ],
390
+ "markdownDescription": "Ports devlanes may hand out, written `start-end` (both included, start above 0); defaults to `20000-32767`, below Linux's usual ephemeral range so the OS does not pick them for outgoing connections. Each lane of each project gets its own block of consecutive ports from it, so a smaller range fits fewer lanes.",
350
391
  "pattern": "^[0-9]+-[0-9]+$",
351
392
  "type": "string"
352
393
  }
@@ -355,7 +396,7 @@
355
396
  },
356
397
  "ProxyConfig": {
357
398
  "additionalProperties": false,
358
- "description": "Ports and network access policy for the local HTTP/HTTPS proxy.",
399
+ "description": "Ports and network exposure of the daemon's proxy, which routes each lane hostname to its service.",
359
400
  "examples": [
360
401
  {
361
402
  "http_port": 80,
@@ -363,89 +404,79 @@
363
404
  "lan": false
364
405
  }
365
406
  ],
407
+ "markdownDescription": "Ports and network exposure of the daemon's proxy, which routes each lane hostname to its service.",
366
408
  "properties": {
367
409
  "http_port": {
368
410
  "default": 80,
369
- "description": "HTTP listening port; defaults to 80 and redirects to HTTPS when TLS is enabled.",
411
+ "description": "Port for plain HTTP; defaults to 80, falling back to 8080 like `https_port`, and must differ from it. While HTTPS runs it redirects known hostnames to HTTPS; without a CA it serves them over plain HTTP.",
370
412
  "examples": [
371
413
  80,
372
414
  8080
373
415
  ],
374
416
  "format": "uint16",
417
+ "markdownDescription": "Port for plain HTTP; defaults to 80, falling back to 8080 like `https_port`, and must differ from it. While HTTPS runs it redirects known hostnames to HTTPS; without a CA it serves them over plain HTTP.",
375
418
  "maximum": 65535,
376
419
  "minimum": 0,
377
420
  "type": "integer"
378
421
  },
379
422
  "https_port": {
380
423
  "default": 443,
381
- "description": "HTTPS listening port; defaults to 443.",
424
+ "description": "Port the HTTPS proxy listens on; defaults to 443. When the port is taken or not allowed (low ports on Linux need extra permission), the daemon falls back to 8443 and service URLs carry the port.",
382
425
  "examples": [
383
426
  443,
384
427
  8443
385
428
  ],
386
429
  "format": "uint16",
430
+ "markdownDescription": "Port the HTTPS proxy listens on; defaults to 443. When the port is taken or not allowed (low ports on Linux need extra permission), the daemon falls back to 8443 and service URLs carry the port.",
387
431
  "maximum": 65535,
388
432
  "minimum": 0,
389
433
  "type": "integer"
390
434
  },
391
435
  "lan": {
392
436
  "default": false,
393
- "description": "Accept connections from other machines; defaults to loopback-only access.",
437
+ "description": "Accept connections from other devices on the network; defaults to false, which refuses every peer that is not this machine. Those devices must still resolve the hostnames to this machine and trust the CA.",
394
438
  "examples": [
395
439
  false,
396
440
  true
397
441
  ],
442
+ "markdownDescription": "Accept connections from other devices on the network; defaults to false, which refuses every peer that is not this machine. Those devices must still resolve the hostnames to this machine and trust the CA.",
398
443
  "type": "boolean"
399
444
  }
400
445
  },
401
446
  "type": "object"
402
447
  },
403
448
  "Resolver": {
404
- "description": "Strategy for resolving local project hostnames; defaults to localhost.",
449
+ "description": "Strategy that makes lane hostnames resolve to this machine.",
450
+ "enum": [
451
+ "localhost",
452
+ "hosts_file",
453
+ "resolver",
454
+ "public"
455
+ ],
456
+ "enumDescriptions": [
457
+ "Lane domains end in `.localhost` (`acme.localhost`, `acme-2.localhost`), which browsers and many system resolvers send to loopback by themselves. `hosts_file` covers programs that do not.",
458
+ "Keep `.localhost` names and always write exact hostnames to `/etc/hosts`, as `hosts_file: always` does, whatever `hosts_file` says; a project's `lanes.hosts_file` still takes precedence. Needs the helper from `devlanes certs trust --hosts`.",
459
+ "Lane domains become `<project>.<tld>` and `<project>-<N>.<tld>`, answered by a DNS server the daemon runs on `127.0.0.1:15353`, so wildcard hostnames resolve too for programs that use the system resolver. Needs `tld` and a one-time `devlanes certs trust --resolver`; projects that set `lanes.vars.domain` or `lanes.matrix` keep their own domains.",
460
+ "Reserved for public DNS names with DNS-01 certificates; not supported yet, so the config is rejected."
461
+ ],
405
462
  "examples": [
406
463
  "localhost",
407
464
  "hosts_file",
408
465
  "resolver",
409
466
  "public"
410
467
  ],
411
- "oneOf": [
412
- {
413
- "const": "localhost",
414
- "description": "Use names under .localhost, resolved by browsers without a custom DNS service.",
415
- "examples": [
416
- "localhost"
417
- ],
418
- "type": "string"
419
- },
420
- {
421
- "const": "hosts_file",
422
- "description": "Resolve exact project names through system hosts-file entries.",
423
- "examples": [
424
- "hosts_file"
425
- ],
426
- "type": "string"
427
- },
428
- {
429
- "const": "resolver",
430
- "description": "Use the daemon's DNS resolver for the configured custom top-level domain.",
431
- "examples": [
432
- "resolver"
433
- ],
434
- "type": "string"
435
- },
436
- {
437
- "const": "public",
438
- "description": "Reserved public DNS and DNS-01 strategy; not supported yet.",
439
- "examples": [
440
- "public"
441
- ],
442
- "type": "string"
443
- }
444
- ]
468
+ "markdownDescription": "Strategy that makes lane hostnames resolve to this machine.",
469
+ "markdownEnumDescriptions": [
470
+ "Lane domains end in `.localhost` (`acme.localhost`, `acme-2.localhost`), which browsers and many system resolvers send to loopback by themselves. `hosts_file` covers programs that do not.",
471
+ "Keep `.localhost` names and always write exact hostnames to `/etc/hosts`, as `hosts_file: always` does, whatever `hosts_file` says; a project's `lanes.hosts_file` still takes precedence. Needs the helper from `devlanes certs trust --hosts`.",
472
+ "Lane domains become `<project>.<tld>` and `<project>-<N>.<tld>`, answered by a DNS server the daemon runs on `127.0.0.1:15353`, so wildcard hostnames resolve too for programs that use the system resolver. Needs `tld` and a one-time `devlanes certs trust --resolver`; projects that set `lanes.vars.domain` or `lanes.matrix` keep their own domains.",
473
+ "Reserved for public DNS names with DNS-01 certificates; not supported yet, so the config is rejected."
474
+ ],
475
+ "type": "string"
445
476
  },
446
477
  "StartupConfig": {
447
478
  "additionalProperties": false,
448
- "description": "Startup pacing. Machine settings, so they live in user config only.",
479
+ "description": "Pacing of service starts, shared by every lane and project on this machine. Starts queue first come, first served; only spawning waits, running processes are never limited.",
449
480
  "examples": [
450
481
  {
451
482
  "max": 4,
@@ -455,19 +486,22 @@
455
486
  "initial": 2,
456
487
  "max": 8,
457
488
  "mode": "adaptive",
489
+ "reserve_memory": "2GiB",
458
490
  "settle_cpu": "10%",
459
491
  "slot_timeout": "20s",
460
492
  "target_cpu": "75%"
461
493
  }
462
494
  ],
495
+ "markdownDescription": "Pacing of service starts, shared by every lane and project on this machine. Starts queue first come, first served; only spawning waits, running processes are never limited.",
463
496
  "properties": {
464
497
  "initial": {
465
498
  "default": null,
466
- "description": "Adaptive starting window; default CPU cores / 4, at least 2, at most `max`.",
499
+ "description": "Adaptive starting window; defaults to `max`, so starts run at full speed until the machine is under pressure. Set it lower to ramp up from a few slots instead; never above `max`.",
467
500
  "examples": [
468
501
  2
469
502
  ],
470
503
  "format": "uint32",
504
+ "markdownDescription": "Adaptive starting window; defaults to `max`, so starts run at full speed until the machine is under pressure. Set it lower to ramp up from a few slots instead; never above `max`.",
471
505
  "minimum": 1,
472
506
  "type": [
473
507
  "integer",
@@ -476,11 +510,12 @@
476
510
  },
477
511
  "max": {
478
512
  "default": null,
479
- "description": "Largest window (the fixed window in `fixed` mode); default CPU cores.",
513
+ "description": "Most services starting at once: the constant window in `fixed` mode and the ceiling in `adaptive` mode. Defaults to the number of CPU cores; `all` ignores it.",
480
514
  "examples": [
481
515
  8
482
516
  ],
483
517
  "format": "uint32",
518
+ "markdownDescription": "Most services starting at once: the constant window in `fixed` mode and the ceiling in `adaptive` mode. Defaults to the number of CPU cores; `all` ignores it.",
484
519
  "minimum": 1,
485
520
  "type": [
486
521
  "integer",
@@ -489,95 +524,112 @@
489
524
  },
490
525
  "mode": {
491
526
  "$ref": "#/$defs/StartupMode",
492
- "default": "fixed",
493
- "description": "`adaptive`, `fixed` or `all`; default `fixed`.",
527
+ "default": "adaptive",
528
+ "description": "`adaptive`, `fixed` or `all`; default `adaptive`. A starting service holds its slot until its ready check (`tcp`, `http`, `log` or `cmd`) passes, it fails, or `slot_timeout` passes; one without a check (`ready: started`) holds it until it settles (`settle_cpu`).",
494
529
  "examples": [
495
530
  "adaptive",
496
531
  "fixed",
497
532
  "all"
498
- ]
533
+ ],
534
+ "markdownDescription": "`adaptive`, `fixed` or `all`; default `adaptive`. A starting service holds its slot until its ready check (`tcp`, `http`, `log` or `cmd`) passes, it fails, or `slot_timeout` passes; one without a check (`ready: started`) holds it until it settles (`settle_cpu`)."
535
+ },
536
+ "reserve_memory": {
537
+ "anyOf": [
538
+ {
539
+ "$ref": "#/$defs/ByteSize",
540
+ "description": "",
541
+ "examples": [],
542
+ "markdownDescription": ""
543
+ },
544
+ {
545
+ "type": "null"
546
+ }
547
+ ],
548
+ "default": null,
549
+ "description": "Free memory every new start must leave, on top of the memory the services already starting are expected to still need (each one's past startup peak, or 512 MiB without history). Defaults to 10% of RAM, at most `2GiB`; `0B` turns it off. A start with nothing else starting never waits for it. `all` ignores it.",
550
+ "examples": [
551
+ "2GiB",
552
+ "0B"
553
+ ],
554
+ "markdownDescription": "Free memory every new start must leave, on top of the memory the services already starting are expected to still need (each one's past startup peak, or 512 MiB without history). Defaults to 10% of RAM, at most `2GiB`; `0B` turns it off. A start with nothing else starting never waits for it. `all` ignores it."
499
555
  },
500
556
  "settle_cpu": {
501
557
  "$ref": "#/$defs/Percent",
502
558
  "default": "10%",
503
- "description": "A `ready: started` service has finished starting once its process tree\nstays below this share of one core for 2 s; default 10%.",
559
+ "description": "Only for services without a ready check (`ready: started`, the default for a service without `hosts`): such a service gives its slot back once its process tree uses less than this share of one core for 2 s, meaning it has finished starting up. Services with a `tcp`, `http`, `log` or `cmd` check keep their slot until the check passes instead. Defaults to `10%`.",
504
560
  "examples": [
505
561
  "10%",
506
562
  "12.5%"
507
- ]
563
+ ],
564
+ "markdownDescription": "Only for services without a ready check (`ready: started`, the default for a service without `hosts`): such a service gives its slot back once its process tree uses less than this share of one core for 2 s, meaning it has finished starting up. Services with a `tcp`, `http`, `log` or `cmd` check keep their slot until the check passes instead. Defaults to `10%`."
508
565
  },
509
566
  "slot_timeout": {
510
567
  "$ref": "#/$defs/ConfigDuration",
511
568
  "default": "20000ms",
512
- "description": "A start slot is given back after this even if the service is not ready,\nso services waiting on each other cannot block the queue; default 20s.",
569
+ "description": "Longest a starting service holds its slot; after it, the next queued service may start even if this one is not ready, so services waiting on each other cannot block the queue. Defaults to `20s` and must be above zero; the service itself keeps starting.",
513
570
  "examples": [
514
571
  "15s",
515
572
  "10m"
516
- ]
573
+ ],
574
+ "markdownDescription": "Longest a starting service holds its slot; after it, the next queued service may start even if this one is not ready, so services waiting on each other cannot block the queue. Defaults to `20s` and must be above zero; the service itself keeps starting."
517
575
  },
518
576
  "target_cpu": {
519
577
  "$ref": "#/$defs/Percent",
520
578
  "default": "75%",
521
- "description": "Adaptive mode grows the window while system CPU stays below this; default 75%.",
579
+ "description": "System CPU use below which `adaptive` mode adds slots; defaults to `75%`, and must be above 0% and at most 100%. Lower it to keep the machine responsive during large starts; other modes ignore it.",
522
580
  "examples": [
523
581
  "75%",
524
582
  "80%"
525
- ]
583
+ ],
584
+ "markdownDescription": "System CPU use below which `adaptive` mode adds slots; defaults to `75%`, and must be above 0% and at most 100%. Lower it to keep the machine responsive during large starts; other modes ignore it."
526
585
  }
527
586
  },
528
587
  "type": "object"
529
588
  },
530
589
  "StartupMode": {
531
- "description": "How the start gate sizes its window.",
590
+ "description": "How the number of services allowed to start at once is chosen.",
591
+ "enum": [
592
+ "adaptive",
593
+ "fixed",
594
+ "all"
595
+ ],
596
+ "enumDescriptions": [
597
+ "Begin each burst of starts at `initial` (by default `max`, full speed). Halve the slots after 3 s above 90% CPU or when the system reports memory pressure, start nothing while that pressure is critical, and add a slot every 2 s while system CPU stays below `target_cpu`, up to `max`.",
598
+ "Allow `max` starts at a time, never more or fewer. A start also waits while free memory cannot fit what it needs.",
599
+ "Start every service immediately, with no limit and no memory check."
600
+ ],
532
601
  "examples": [
533
602
  "adaptive",
534
603
  "fixed",
535
604
  "all"
536
605
  ],
537
- "oneOf": [
538
- {
539
- "const": "adaptive",
540
- "description": "Grow the window while CPU and memory allow, shrink it under pressure.",
541
- "examples": [
542
- "adaptive"
543
- ],
544
- "type": "string"
545
- },
546
- {
547
- "const": "fixed",
548
- "description": "Always `max` starts at a time.",
549
- "examples": [
550
- "fixed"
551
- ],
552
- "type": "string"
553
- },
554
- {
555
- "const": "all",
556
- "description": "No pacing: every service starts at once.",
557
- "examples": [
558
- "all"
559
- ],
560
- "type": "string"
561
- }
562
- ]
606
+ "markdownDescription": "How the number of services allowed to start at once is chosen.",
607
+ "markdownEnumDescriptions": [
608
+ "Begin each burst of starts at `initial` (by default `max`, full speed). Halve the slots after 3 s above 90% CPU or when the system reports memory pressure, start nothing while that pressure is critical, and add a slot every 2 s while system CPU stays below `target_cpu`, up to `max`.",
609
+ "Allow `max` starts at a time, never more or fewer. A start also waits while free memory cannot fit what it needs.",
610
+ "Start every service immediately, with no limit and no memory check."
611
+ ],
612
+ "type": "string"
563
613
  },
564
614
  "StateConfig": {
565
615
  "additionalProperties": false,
566
- "description": "Soft disk budget for the database; live registry and completion records are preserved.",
616
+ "description": "Size budget for the daemon's SQLite database, enforced by its hourly cleanup.",
567
617
  "examples": [
568
618
  {
569
619
  "max_size": "512MiB"
570
620
  }
571
621
  ],
622
+ "markdownDescription": "Size budget for the daemon's SQLite database, enforced by its hourly cleanup.",
572
623
  "properties": {
573
624
  "max_size": {
574
625
  "$ref": "#/$defs/ByteSize",
575
626
  "default": "536870912B",
576
- "description": "Budget for the database plus WAL and shared-memory files; defaults to 512MiB and must be nonzero. Old metrics and history are pruned first; protected live state may exceed it.",
627
+ "description": "Size the database (`state.db` with its WAL and shared-memory files) should stay under; defaults to `512MiB` and must be above zero. When it is larger, the cleanup deletes the oldest metric samples first, then the oldest history (finished runs, log index entries, remote boxes), even before their retention ends. Lanes, ports and running units are never deleted, so the file can stay above the budget.",
577
628
  "examples": [
578
629
  "512MiB",
579
630
  "1GiB"
580
- ]
631
+ ],
632
+ "markdownDescription": "Size the database (`state.db` with its WAL and shared-memory files) should stay under; defaults to `512MiB` and must be above zero. When it is larger, the cleanup deletes the oldest metric samples first, then the oldest history (finished runs, log index entries, remote boxes), even before their retention ends. Lanes, ports and running units are never deleted, so the file can stay above the budget."
581
633
  }
582
634
  },
583
635
  "type": "object"
@@ -585,110 +637,159 @@
585
637
  "Templated_string": {
586
638
  "anyOf": [
587
639
  {
588
- "description": "Literal value used without expression evaluation.",
589
640
  "type": "string"
590
641
  },
591
642
  {
592
- "description": "Text containing `${{ ... }}`, evaluated for each lane; write `${{ '${{' }}` for a literal `${{`.",
593
- "examples": [
594
- "${{ lane.index }}"
595
- ],
596
643
  "pattern": "\\$\\{\\{",
597
644
  "type": "string"
598
645
  }
599
646
  ],
600
- "description": "A literal value or a `${{ ... }}` expression, checked for the required type after evaluation.\nWrite `${{ '${{' }}` for a literal `${{`.",
647
+ "description": "A plain value or text with `${{ ... }}` expressions, evaluated for each lane with names such as `lane`, `self`, `services`, `infra` and `env`. A value that is one whole expression keeps its type, which must fit the setting. Write `${{ '${{' }}` for a literal `${{`.",
601
648
  "examples": [
602
649
  "${{ lane.index }}"
603
- ]
650
+ ],
651
+ "markdownDescription": "A plain value or text with `${{ ... }}` expressions, evaluated for each lane with names such as `lane`, `self`, `services`, `infra` and `env`. A value that is one whole expression keeps its type, which must fit the setting. Write `${{ '${{' }}` for a literal `${{`."
604
652
  },
605
653
  "TerminalConfig": {
606
654
  "anyOf": [
607
655
  {
608
- "description": "A terminal app name from when devlanes opened units in a new terminal\nwindow; that feature is gone, so the name is ignored with a warning.",
609
- "examples": [
610
- "ghostty"
611
- ],
612
656
  "type": "string"
613
657
  },
614
658
  {
615
659
  "$ref": "#/$defs/TerminalSettings",
616
- "description": "How devlanes uses the terminal's own progress bar.",
617
- "examples": [
618
- {
619
- "progress": "never"
620
- }
621
- ]
660
+ "description": "",
661
+ "examples": [],
662
+ "markdownDescription": ""
622
663
  }
623
664
  ],
624
- "description": "How devlanes uses the terminal, or (from older configs) a terminal app\nname, which is ignored.",
665
+ "description": "Terminal integration of the CLI, or a terminal app name from older versions, which is ignored.",
625
666
  "examples": [
626
667
  {
627
668
  "progress": "auto"
628
669
  }
629
- ]
670
+ ],
671
+ "markdownDescription": "Terminal integration of the CLI, or a terminal app name from older versions, which is ignored."
672
+ },
673
+ "TerminalFeature": {
674
+ "description": "When devlanes uses a terminal feature (title, links, notifications, marks).",
675
+ "enum": [
676
+ "auto",
677
+ "always",
678
+ "never"
679
+ ],
680
+ "enumDescriptions": [
681
+ "Only in terminals known to support it, on human output to a terminal; never inside tmux, screen, CI or a dumb terminal.",
682
+ "In every terminal, tmux included; a terminal without support may ignore it, or keep a title devlanes cannot put back.",
683
+ "Never use it."
684
+ ],
685
+ "examples": [
686
+ "auto",
687
+ "always",
688
+ "never"
689
+ ],
690
+ "markdownDescription": "When devlanes uses a terminal feature (title, links, notifications, marks).",
691
+ "markdownEnumDescriptions": [
692
+ "Only in terminals known to support it, on human output to a terminal; never inside tmux, screen, CI or a dumb terminal.",
693
+ "In every terminal, tmux included; a terminal without support may ignore it, or keep a title devlanes cannot put back.",
694
+ "Never use it."
695
+ ],
696
+ "type": "string"
630
697
  },
631
698
  "TerminalProgress": {
632
- "description": "When start progress goes to the terminal tab's progress bar. Only a terminal\nsession in human output ever gets it; `DEVLANES_TERM_PROGRESS=0` or `1` overrides this.",
699
+ "description": "When start progress goes to the terminal tab's progress bar.",
700
+ "enum": [
701
+ "auto",
702
+ "always",
703
+ "never"
704
+ ],
705
+ "enumDescriptions": [
706
+ "Only in terminals known to draw it (Ghostty, WezTerm, iTerm2 3.6.6+, Windows Terminal, ConEmu, Ptyxis 48+, Konsole 26.04+); never inside tmux, screen, CI or a dumb terminal.",
707
+ "In every terminal, for ones `auto` does not recognize such as kitty; a terminal without support may ignore it or show it as text or a notification. Inside tmux it is passed through, which needs `allow-passthrough on`.",
708
+ "Never draw the progress bar."
709
+ ],
633
710
  "examples": [
634
711
  "auto",
635
712
  "always",
636
713
  "never"
637
714
  ],
638
- "oneOf": [
639
- {
640
- "const": "auto",
641
- "description": "Only in terminals known to draw it (Ghostty, WezTerm, recent iTerm2, Windows Terminal, ConEmu, Ptyxis, Konsole), never inside tmux or screen.",
642
- "examples": [
643
- "auto"
644
- ],
645
- "type": "string"
646
- },
647
- {
648
- "const": "always",
649
- "description": "In any terminal, also kitty 0.47 or later and inside tmux (needs `allow-passthrough on`).",
650
- "examples": [
651
- "always"
652
- ],
653
- "type": "string"
654
- },
655
- {
656
- "const": "never",
657
- "description": "Never.",
658
- "examples": [
659
- "never"
660
- ],
661
- "type": "string"
662
- }
663
- ]
715
+ "markdownDescription": "When start progress goes to the terminal tab's progress bar.",
716
+ "markdownEnumDescriptions": [
717
+ "Only in terminals known to draw it (Ghostty, WezTerm, iTerm2 3.6.6+, Windows Terminal, ConEmu, Ptyxis 48+, Konsole 26.04+); never inside tmux, screen, CI or a dumb terminal.",
718
+ "In every terminal, for ones `auto` does not recognize such as kitty; a terminal without support may ignore it or show it as text or a notification. Inside tmux it is passed through, which needs `allow-passthrough on`.",
719
+ "Never draw the progress bar."
720
+ ],
721
+ "type": "string"
664
722
  },
665
723
  "TerminalSettings": {
666
724
  "additionalProperties": false,
667
- "description": "How devlanes uses the terminal's own progress bar.",
725
+ "description": "How devlanes uses the terminal: its progress bar, tab title, links, notifications and marks.",
668
726
  "examples": [
669
727
  {
670
728
  "progress": "auto"
671
729
  },
672
730
  {
673
- "progress": "never"
731
+ "progress": "never",
732
+ "title": "never"
674
733
  }
675
734
  ],
735
+ "markdownDescription": "How devlanes uses the terminal: its progress bar, tab title, links, notifications and marks.",
676
736
  "properties": {
737
+ "links": {
738
+ "$ref": "#/$defs/TerminalFeature",
739
+ "default": "auto",
740
+ "description": "Whether service URLs, config error locations and log or bundle paths are clickable links (the OSC 8 sequence); defaults to `auto`, which writes them in iTerm2, kitty, WezTerm, Ghostty, Windows Terminal, VTE terminals, Konsole, Alacritty and foot. Elsewhere the same text prints without a link. `DEVLANES_TERM_LINKS=0` or `1` overrides this setting.",
741
+ "examples": [
742
+ "auto",
743
+ "never"
744
+ ],
745
+ "markdownDescription": "Whether service URLs, config error locations and log or bundle paths are clickable links (the OSC 8 sequence); defaults to `auto`, which writes them in iTerm2, kitty, WezTerm, Ghostty, Windows Terminal, VTE terminals, Konsole, Alacritty and foot. Elsewhere the same text prints without a link. `DEVLANES_TERM_LINKS=0` or `1` overrides this setting."
746
+ },
747
+ "marks": {
748
+ "$ref": "#/$defs/TerminalFeature",
749
+ "default": "auto",
750
+ "description": "Whether long command output (logs of several services, failed starts) marks where each section starts, so the terminal's jump-to-mark or jump-to-prompt keys move between them; defaults to `auto`: marks in iTerm2, and prompt marks (OSC 133) in WezTerm, Ghostty, kitty, foot and Windows Terminal. `DEVLANES_TERM_MARKS=0` or `1` overrides this setting.",
751
+ "examples": [
752
+ "auto",
753
+ "never"
754
+ ],
755
+ "markdownDescription": "Whether long command output (logs of several services, failed starts) marks where each section starts, so the terminal's jump-to-mark or jump-to-prompt keys move between them; defaults to `auto`: marks in iTerm2, and prompt marks (OSC 133) in WezTerm, Ghostty, kitty, foot and Windows Terminal. `DEVLANES_TERM_MARKS=0` or `1` overrides this setting."
756
+ },
757
+ "notifications": {
758
+ "$ref": "#/$defs/TerminalFeature",
759
+ "default": "auto",
760
+ "description": "Whether a start that took a while finishing or failing, and a running service crashing, show a desktop notification while the terminal is not focused; defaults to `auto`. The TUI knows focus from the terminal; a command such as `devlanes start` does not, so with `auto` it notifies only in kitty, which itself skips a focused window. Terminals without notifications get the bell. At most one notification per 10 seconds. `DEVLANES_TERM_NOTIFICATIONS=0` or `1` overrides this setting.",
761
+ "examples": [
762
+ "auto",
763
+ "never"
764
+ ],
765
+ "markdownDescription": "Whether a start that took a while finishing or failing, and a running service crashing, show a desktop notification while the terminal is not focused; defaults to `auto`. The TUI knows focus from the terminal; a command such as `devlanes start` does not, so with `auto` it notifies only in kitty, which itself skips a focused window. Terminals without notifications get the bell. At most one notification per 10 seconds. `DEVLANES_TERM_NOTIFICATIONS=0` or `1` overrides this setting."
766
+ },
677
767
  "progress": {
678
768
  "$ref": "#/$defs/TerminalProgress",
679
769
  "default": "auto",
680
- "description": "Show start progress in the terminal tab's progress bar (OSC 9;4); defaults to auto.",
770
+ "description": "Whether starts show their progress in the terminal tab's own progress bar (the OSC 9;4 sequence); defaults to `auto`. Only human output on a terminal gets it, and `DEVLANES_TERM_PROGRESS=0` or `1` overrides this setting.",
681
771
  "examples": [
682
772
  "auto",
683
773
  "never"
684
- ]
774
+ ],
775
+ "markdownDescription": "Whether starts show their progress in the terminal tab's own progress bar (the OSC 9;4 sequence); defaults to `auto`. Only human output on a terminal gets it, and `DEVLANES_TERM_PROGRESS=0` or `1` overrides this setting."
776
+ },
777
+ "title": {
778
+ "$ref": "#/$defs/TerminalFeature",
779
+ "default": "auto",
780
+ "description": "Whether the terminal tab's title says what devlanes is doing (`devlanes · acme@0 · starting 3/8` in the TUI, `devlanes start acme@0 · 3/8` or `devlanes logs -f api` for long commands); defaults to `auto`. The previous title is saved on the terminal's title stack and put back on exit, so `auto` sets it only where that stack exists: xterm, iTerm2, kitty, Ghostty and VTE terminals such as GNOME Terminal. Never inside tmux or screen, which manage titles themselves. `DEVLANES_TERM_TITLE=0` or `1` overrides this setting.",
781
+ "examples": [
782
+ "auto",
783
+ "never"
784
+ ],
785
+ "markdownDescription": "Whether the terminal tab's title says what devlanes is doing (`devlanes · acme@0 · starting 3/8` in the TUI, `devlanes start acme@0 · 3/8` or `devlanes logs -f api` for long commands); defaults to `auto`. The previous title is saved on the terminal's title stack and put back on exit, so `auto` sets it only where that stack exists: xterm, iTerm2, kitty, Ghostty and VTE terminals such as GNOME Terminal. Never inside tmux or screen, which manage titles themselves. `DEVLANES_TERM_TITLE=0` or `1` overrides this setting."
685
786
  }
686
787
  },
687
788
  "type": "object"
688
789
  },
689
790
  "TlsConfig": {
690
791
  "additionalProperties": false,
691
- "description": "Certificate authority selection and requested certificate trust targets.",
792
+ "description": "Certificate authority behind the proxy's HTTPS certificates, and the stores `devlanes certs trust` installs it into.",
692
793
  "examples": [
693
794
  {
694
795
  "ca": "devlanes",
@@ -698,19 +799,21 @@
698
799
  ]
699
800
  }
700
801
  ],
802
+ "markdownDescription": "Certificate authority behind the proxy's HTTPS certificates, and the stores `devlanes certs trust` installs it into.",
701
803
  "properties": {
702
804
  "ca": {
703
805
  "$ref": "#/$defs/CaKind",
704
806
  "default": "devlanes",
705
- "description": "Use devlanes' own CA by default, or reuse mkcert's CA.",
807
+ "description": "Which CA signs the certificate for each lane hostname; defaults to `devlanes`. Choose `mkcert` when your browsers already trust mkcert's CA.",
706
808
  "examples": [
707
809
  "devlanes",
708
810
  "mkcert"
709
- ]
811
+ ],
812
+ "markdownDescription": "Which CA signs the certificate for each lane hostname; defaults to `devlanes`. Choose `mkcert` when your browsers already trust mkcert's CA."
710
813
  },
711
814
  "trust": {
712
815
  "default": [],
713
- "description": "Trust stores that should accept the local CA; mobile targets are ignored with a warning for now.",
816
+ "description": "Stores `devlanes certs trust` installs the CA into and `certs untrust` removes it from; an empty list means `[system]`. On Linux, browsers do not read the system store, so add `chrome` and `firefox` there; inside WSL add `windows` for browsers on Windows. `devlanes doctor` shows each store's state.",
714
817
  "examples": [
715
818
  [
716
819
  "system",
@@ -718,79 +821,71 @@
718
821
  ],
719
822
  [
720
823
  "chrome"
824
+ ],
825
+ [
826
+ "system",
827
+ "windows"
721
828
  ]
722
829
  ],
723
830
  "items": {
724
831
  "$ref": "#/$defs/TrustTarget",
725
- "description": "Store in which the local CA should be trusted; mobile stores are not supported yet.",
832
+ "description": "Certificate store that `devlanes certs trust` installs the CA into.",
726
833
  "examples": [
727
834
  "system",
728
835
  "firefox",
729
836
  "chrome",
837
+ "windows",
730
838
  "ios-simulator",
731
839
  "android-emulator"
732
- ]
840
+ ],
841
+ "markdownDescription": "Certificate store that `devlanes certs trust` installs the CA into."
733
842
  },
843
+ "markdownDescription": "Stores `devlanes certs trust` installs the CA into and `certs untrust` removes it from; an empty list means `[system]`. On Linux, browsers do not read the system store, so add `chrome` and `firefox` there; inside WSL add `windows` for browsers on Windows. `devlanes doctor` shows each store's state.",
734
844
  "type": "array"
735
845
  }
736
846
  },
737
847
  "type": "object"
738
848
  },
739
849
  "TrustTarget": {
740
- "description": "Store in which the local CA should be trusted; mobile stores are not supported yet.",
850
+ "description": "Certificate store that `devlanes certs trust` installs the CA into.",
851
+ "enum": [
852
+ "system",
853
+ "firefox",
854
+ "chrome",
855
+ "windows",
856
+ "ios-simulator",
857
+ "android-emulator"
858
+ ],
859
+ "enumDescriptions": [
860
+ "The OS store: the login keychain on macOS, read by Safari, Chrome, Edge and `/usr/bin/curl`; on Linux, the distribution's CA anchors, updated through sudo.",
861
+ "Every Firefox profile's certificate database, through `certutil` from the NSS tools (`brew install nss`, or `libnss3-tools` / `nss-tools` on Linux).",
862
+ "The store Chrome and Chromium read: `~/.pki/nssdb` through `certutil` on Linux, the same login keychain as `system` on macOS.",
863
+ "The Windows user root store, read by Edge, Chrome and Firefox on Windows. For devlanes inside WSL, whose browser usually runs on Windows: it runs Windows' `certutil.exe` through interop, and Windows asks you to confirm each change. On Windows it is the same store as `system`; on macOS and Linux outside WSL `certs trust` reports it as unavailable.",
864
+ "Reserved for the iOS simulator; not supported yet, so it is dropped with a warning.",
865
+ "Reserved for the Android emulator; not supported yet, so it is dropped with a warning."
866
+ ],
741
867
  "examples": [
742
868
  "system",
743
869
  "firefox",
744
870
  "chrome",
871
+ "windows",
745
872
  "ios-simulator",
746
873
  "android-emulator"
747
874
  ],
748
- "oneOf": [
749
- {
750
- "const": "system",
751
- "description": "Trust the local CA in the operating system's certificate store.",
752
- "examples": [
753
- "system"
754
- ],
755
- "type": "string"
756
- },
757
- {
758
- "const": "firefox",
759
- "description": "Trust the local CA in Firefox's certificate database.",
760
- "examples": [
761
- "firefox"
762
- ],
763
- "type": "string"
764
- },
765
- {
766
- "const": "chrome",
767
- "description": "Trust the local CA in Chrome's certificate database where required.",
768
- "examples": [
769
- "chrome"
770
- ],
771
- "type": "string"
772
- },
773
- {
774
- "const": "ios-simulator",
775
- "description": "Reserved iOS simulator trust target; not supported yet, so it is ignored with a warning.",
776
- "examples": [
777
- "ios-simulator"
778
- ],
779
- "type": "string"
780
- },
781
- {
782
- "const": "android-emulator",
783
- "description": "Reserved Android emulator trust target; not supported yet, so it is ignored with a warning.",
784
- "examples": [
785
- "android-emulator"
786
- ],
787
- "type": "string"
788
- }
789
- ]
875
+ "markdownDescription": "Certificate store that `devlanes certs trust` installs the CA into.",
876
+ "markdownEnumDescriptions": [
877
+ "The OS store: the login keychain on macOS, read by Safari, Chrome, Edge and `/usr/bin/curl`; on Linux, the distribution's CA anchors, updated through sudo.",
878
+ "Every Firefox profile's certificate database, through `certutil` from the NSS tools (`brew install nss`, or `libnss3-tools` / `nss-tools` on Linux).",
879
+ "The store Chrome and Chromium read: `~/.pki/nssdb` through `certutil` on Linux, the same login keychain as `system` on macOS.",
880
+ "The Windows user root store, read by Edge, Chrome and Firefox on Windows. For devlanes inside WSL, whose browser usually runs on Windows: it runs Windows' `certutil.exe` through interop, and Windows asks you to confirm each change. On Windows it is the same store as `system`; on macOS and Linux outside WSL `certs trust` reports it as unavailable.",
881
+ "Reserved for the iOS simulator; not supported yet, so it is dropped with a warning.",
882
+ "Reserved for the Android emulator; not supported yet, so it is dropped with a warning."
883
+ ],
884
+ "type": "string"
790
885
  },
791
886
  "TuiConfig": {
792
887
  "additionalProperties": false,
793
- "description": "Terminal UI behaviour of the CLI.",
888
+ "description": "Terminal UI settings: when it opens, its colors and symbols, and what quitting does with running lanes. Changes apply to the next command.",
794
889
  "examples": [
795
890
  {
796
891
  "open_on_start": false
@@ -803,187 +898,188 @@
803
898
  "on_quit": "keep"
804
899
  }
805
900
  ],
901
+ "markdownDescription": "Terminal UI settings: when it opens, its colors and symbols, and what quitting does with running lanes. Changes apply to the next command.",
806
902
  "properties": {
807
903
  "glyphs": {
808
904
  "$ref": "#/$defs/TuiGlyphs",
809
905
  "default": "auto",
810
- "description": "Symbols the TUI draws states and markers with; defaults to auto.",
906
+ "description": "Characters the TUI draws states, markers and the spinner with; defaults to `auto`. Set `ascii` when your font shows boxes or misaligned symbols.",
811
907
  "examples": [
812
908
  "auto",
813
909
  "unicode",
814
910
  "ascii"
815
- ]
911
+ ],
912
+ "markdownDescription": "Characters the TUI draws states, markers and the spinner with; defaults to `auto`. Set `ascii` when your font shows boxes or misaligned symbols."
816
913
  },
817
914
  "on_quit": {
818
915
  "$ref": "#/$defs/TuiOnQuit",
819
916
  "default": "ask",
820
- "description": "What quitting the TUI does while lanes of the project on screen run; defaults to ask. The quit dialog's \"remember\" box writes it.",
917
+ "description": "What `q` or Ctrl+C does while lanes of the project on screen are running; defaults to `ask`. Ticking \"remember\" in the quit dialog writes the choice here.",
821
918
  "examples": [
822
919
  "ask",
823
920
  "keep",
824
921
  "stop"
825
- ]
922
+ ],
923
+ "markdownDescription": "What `q` or Ctrl+C does while lanes of the project on screen are running; defaults to `ask`. Ticking \"remember\" in the quit dialog writes the choice here."
826
924
  },
827
925
  "open_on_start": {
828
926
  "default": true,
829
- "description": "Run `start`, `restart`, `lane resume` and `lane new --start` inside the TUI when typed at a terminal; defaults to true. false prints them as text, like `--no-ui` or DEVLANES_NO_UI=1.",
927
+ "description": "Open the TUI for `start`, `restart`, `lane resume` and `lane new --start` when they run at a terminal; defaults to true. Set false to have them print text instead, as `--no-ui` or `DEVLANES_NO_UI=1` do.",
830
928
  "examples": [
831
929
  true,
832
930
  false
833
931
  ],
932
+ "markdownDescription": "Open the TUI for `start`, `restart`, `lane resume` and `lane new --start` when they run at a terminal; defaults to true. Set false to have them print text instead, as `--no-ui` or `DEVLANES_NO_UI=1` do.",
834
933
  "type": "boolean"
835
934
  },
836
935
  "theme": {
837
936
  "$ref": "#/$defs/TuiTheme",
838
937
  "default": "auto",
839
- "description": "Colors of the TUI; defaults to auto. NO_COLOR always turns colors off.",
938
+ "description": "Color palette of the TUI; defaults to `auto`, which picks one from what the terminal reports. Choose `dark` or `light` when auto guesses wrong; `NO_COLOR` in the environment turns colors off whatever this says.",
840
939
  "examples": [
841
940
  "auto",
842
941
  "dark",
843
942
  "light",
844
943
  "ansi"
845
- ]
944
+ ],
945
+ "markdownDescription": "Color palette of the TUI; defaults to `auto`, which picks one from what the terminal reports. Choose `dark` or `light` when auto guesses wrong; `NO_COLOR` in the environment turns colors off whatever this says."
846
946
  }
847
947
  },
848
948
  "type": "object"
849
949
  },
850
950
  "TuiGlyphs": {
851
- "description": "Symbols the TUI draws with.",
951
+ "description": "Character set the TUI draws its symbols with.",
952
+ "enum": [
953
+ "auto",
954
+ "unicode",
955
+ "ascii"
956
+ ],
957
+ "enumDescriptions": [
958
+ "Unicode, or ASCII when the locale (the first of `LC_ALL`, `LC_CTYPE` and `LANG` that is set) is not UTF-8.",
959
+ "Always Unicode symbols, such as ● ✕ ▾ and a braille spinner.",
960
+ "Plain ASCII only, for fonts or terminals that draw Unicode symbols badly."
961
+ ],
852
962
  "examples": [
853
963
  "auto",
854
964
  "unicode",
855
965
  "ascii"
856
966
  ],
857
- "oneOf": [
858
- {
859
- "const": "auto",
860
- "description": "Unicode when the locale is UTF-8, else ASCII.",
861
- "examples": [
862
- "auto"
863
- ],
864
- "type": "string"
865
- },
866
- {
867
- "const": "unicode",
868
- "description": "Unicode symbols such as ● ✕ ▾.",
869
- "examples": [
870
- "unicode"
871
- ],
872
- "type": "string"
873
- },
874
- {
875
- "const": "ascii",
876
- "description": "Plain ASCII, for fonts or terminals that draw Unicode symbols badly.",
877
- "examples": [
878
- "ascii"
879
- ],
880
- "type": "string"
881
- }
882
- ]
967
+ "markdownDescription": "Character set the TUI draws its symbols with.",
968
+ "markdownEnumDescriptions": [
969
+ "Unicode, or ASCII when the locale (the first of `LC_ALL`, `LC_CTYPE` and `LANG` that is set) is not UTF-8.",
970
+ "Always Unicode symbols, such as ● ✕ ▾ and a braille spinner.",
971
+ "Plain ASCII only, for fonts or terminals that draw Unicode symbols badly."
972
+ ],
973
+ "type": "string"
883
974
  },
884
975
  "TuiOnQuit": {
885
- "description": "What quitting the TUI does with running lanes.",
976
+ "description": "What quitting the TUI does with the running lanes of the project on screen.",
977
+ "enum": [
978
+ "ask",
979
+ "keep",
980
+ "stop"
981
+ ],
982
+ "enumDescriptions": [
983
+ "Show a dialog each time to choose between keeping the lanes running and stopping them.",
984
+ "Close the TUI at once; every lane keeps running in the daemon.",
985
+ "Stop the running lanes of the project on screen, then close the TUI. Lanes of other projects keep running."
986
+ ],
886
987
  "examples": [
887
988
  "ask",
888
989
  "keep",
889
990
  "stop"
890
991
  ],
891
- "oneOf": [
892
- {
893
- "const": "ask",
894
- "description": "Ask each time whether to keep the lanes running or stop them.",
895
- "examples": [
896
- "ask"
897
- ],
898
- "type": "string"
899
- },
900
- {
901
- "const": "keep",
902
- "description": "Close the TUI and leave every lane running in the daemon.",
903
- "examples": [
904
- "keep"
905
- ],
906
- "type": "string"
907
- },
908
- {
909
- "const": "stop",
910
- "description": "Stop the running lanes of the project on screen, then close the TUI.",
911
- "examples": [
912
- "stop"
913
- ],
914
- "type": "string"
915
- }
916
- ]
992
+ "markdownDescription": "What quitting the TUI does with the running lanes of the project on screen.",
993
+ "markdownEnumDescriptions": [
994
+ "Show a dialog each time to choose between keeping the lanes running and stopping them.",
995
+ "Close the TUI at once; every lane keeps running in the daemon.",
996
+ "Stop the running lanes of the project on screen, then close the TUI. Lanes of other projects keep running."
997
+ ],
998
+ "type": "string"
917
999
  },
918
1000
  "TuiTheme": {
919
- "description": "Colors of the TUI.",
1001
+ "description": "Color palette of the TUI.",
1002
+ "enum": [
1003
+ "auto",
1004
+ "dark",
1005
+ "light",
1006
+ "ansi"
1007
+ ],
1008
+ "enumDescriptions": [
1009
+ "Use the true-color dark or light palette when the terminal reports true color (`COLORTERM`) and its background (`COLORFGBG`); otherwise the terminal's 16 colors.",
1010
+ "Always use the true-color palette made for a dark background.",
1011
+ "Always use the true-color palette made for a light background.",
1012
+ "Use only the terminal's 16 named colors, so the TUI follows the terminal's color scheme."
1013
+ ],
920
1014
  "examples": [
921
1015
  "auto",
922
1016
  "dark",
923
1017
  "light",
924
1018
  "ansi"
925
1019
  ],
926
- "oneOf": [
927
- {
928
- "const": "auto",
929
- "description": "The terminal's 16 colors, or the dark or light palette when the terminal reports true color and its background.",
930
- "examples": [
931
- "auto"
932
- ],
933
- "type": "string"
934
- },
935
- {
936
- "const": "dark",
937
- "description": "The true-color palette for a dark background.",
938
- "examples": [
939
- "dark"
940
- ],
941
- "type": "string"
942
- },
943
- {
944
- "const": "light",
945
- "description": "The true-color palette for a light background.",
946
- "examples": [
947
- "light"
948
- ],
949
- "type": "string"
950
- },
951
- {
952
- "const": "ansi",
953
- "description": "The terminal's own 16 colors, which follow its theme.",
954
- "examples": [
955
- "ansi"
956
- ],
957
- "type": "string"
958
- }
959
- ]
1020
+ "markdownDescription": "Color palette of the TUI.",
1021
+ "markdownEnumDescriptions": [
1022
+ "Use the true-color dark or light palette when the terminal reports true color (`COLORTERM`) and its background (`COLORFGBG`); otherwise the terminal's 16 colors.",
1023
+ "Always use the true-color palette made for a dark background.",
1024
+ "Always use the true-color palette made for a light background.",
1025
+ "Use only the terminal's 16 named colors, so the TUI follows the terminal's color scheme."
1026
+ ],
1027
+ "type": "string"
960
1028
  },
961
1029
  "UpdatesConfig": {
962
1030
  "additionalProperties": false,
963
- "description": "Release checks: the daemon reads the latest release's version once a day and\ncommands print a one-line notice. Nothing is downloaded or installed.",
1031
+ "description": "Release checks: the daemon reads the latest release's version once a day and commands print a one-line notice. Nothing is downloaded or installed.",
964
1032
  "examples": [
965
1033
  {
966
1034
  "check": false
967
1035
  }
968
1036
  ],
1037
+ "markdownDescription": "Release checks: the daemon reads the latest release's version once a day and commands print a one-line notice. Nothing is downloaded or installed.",
969
1038
  "properties": {
970
1039
  "check": {
971
1040
  "default": true,
972
- "description": "Check GitHub once a day for a newer devlanes release and show the upgrade command; defaults to true.",
1041
+ "description": "Have the daemon read the latest release's version from GitHub once a day; when it is newer, commands print the upgrade command on one line, at most once a day. Defaults to true; `DEVLANES_NO_UPDATE_CHECK=1` or a CI environment also turns it off.",
973
1042
  "examples": [
974
1043
  true,
975
1044
  false
976
1045
  ],
1046
+ "markdownDescription": "Have the daemon read the latest release's version from GitHub once a day; when it is newer, commands print the upgrade command on one line, at most once a day. Defaults to true; `DEVLANES_NO_UPDATE_CHECK=1` or a CI environment also turns it off.",
977
1047
  "type": "boolean"
978
1048
  }
979
1049
  },
980
1050
  "type": "object"
1051
+ },
1052
+ "UserLanesConfig": {
1053
+ "additionalProperties": false,
1054
+ "description": "Machine settings for lanes. Each lane gets its own data folder at `<data_dir>/<project>/<lane id>`, available as `${{ lane.data_path }}` and `DEVLANES_LANE_DATA`. devlanes creates it when the lane is created and before each start, and `devlanes lane clean` deletes it.",
1055
+ "examples": [
1056
+ {
1057
+ "data_dir": "~/.devlanes/data"
1058
+ }
1059
+ ],
1060
+ "markdownDescription": "Machine settings for lanes. Each lane gets its own data folder at `<data_dir>/<project>/<lane id>`, available as `${{ lane.data_path }}` and `DEVLANES_LANE_DATA`. devlanes creates it when the lane is created and before each start, and `devlanes lane clean` deletes it.",
1061
+ "properties": {
1062
+ "data_dir": {
1063
+ "default": null,
1064
+ "description": "Folder that holds every lane's data folder, one subfolder per project; defaults to `data` in the devlanes home (`~/.devlanes/data`). Must be an absolute path or start with `~/`. The daemon reads it when it starts. After a change and a daemon restart, lanes use new, empty folders under the new root; move the old folders there by hand to keep their data.",
1065
+ "examples": [
1066
+ "~/.devlanes/data",
1067
+ "/Volumes/fast/devlanes"
1068
+ ],
1069
+ "markdownDescription": "Folder that holds every lane's data folder, one subfolder per project; defaults to `data` in the devlanes home (`~/.devlanes/data`). Must be an absolute path or start with `~/`. The daemon reads it when it starts. After a change and a daemon restart, lanes use new, empty folders under the new root; move the old folders there by hand to keep their data.",
1070
+ "type": [
1071
+ "string",
1072
+ "null"
1073
+ ]
1074
+ }
1075
+ },
1076
+ "type": "object"
981
1077
  }
982
1078
  },
983
1079
  "$id": "https://devlanes.run/v1/user-schema.json",
984
1080
  "$schema": "https://json-schema.org/draft/2020-12/schema",
985
1081
  "additionalProperties": false,
986
- "description": "Per-user machine settings, separate from project configuration.",
1082
+ "description": "devlanes v0.4.0 user config: per-user settings in ~/.devlanes/config.yaml. Run a dev environment per git worktree, side by side. https://devlanes.run",
987
1083
  "examples": [
988
1084
  {
989
1085
  "hostnames": {
@@ -997,20 +1093,35 @@
997
1093
  }
998
1094
  }
999
1095
  ],
1096
+ "markdownDescription": "devlanes v0.4.0 user config: per-user settings in ~/.devlanes/config.yaml. Run a dev environment per git worktree, side by side. https://devlanes.run",
1000
1097
  "properties": {
1098
+ "daemon": {
1099
+ "$ref": "#/$defs/DaemonConfig",
1100
+ "default": {
1101
+ "exit_when_idle": true
1102
+ },
1103
+ "description": "The daemon's lifetime: whether it stops on its own once the last TUI closes and nothing runs.",
1104
+ "examples": [
1105
+ {
1106
+ "exit_when_idle": false
1107
+ }
1108
+ ],
1109
+ "markdownDescription": "The daemon's lifetime: whether it stops on its own once the last TUI closes and nothing runs."
1110
+ },
1001
1111
  "history": {
1002
1112
  "$ref": "#/$defs/HistoryConfig",
1003
1113
  "default": {
1004
1114
  "remote_hosts_retention": "7776000000ms",
1005
1115
  "retention": "604800000ms"
1006
1116
  },
1007
- "description": "Retention for finished unit/task history and disconnected remote hosts in the database.",
1117
+ "description": "How long the database keeps records of finished service and task runs, and of remote boxes that no longer connect.",
1008
1118
  "examples": [
1009
1119
  {
1010
1120
  "remote_hosts_retention": "90d",
1011
1121
  "retention": "7d"
1012
1122
  }
1013
- ]
1123
+ ],
1124
+ "markdownDescription": "How long the database keeps records of finished service and task runs, and of remote boxes that no longer connect."
1014
1125
  },
1015
1126
  "hostnames": {
1016
1127
  "$ref": "#/$defs/HostnamesConfig",
@@ -1021,7 +1132,7 @@
1021
1132
  "resolver": "localhost",
1022
1133
  "tld": null
1023
1134
  },
1024
- "description": "Hostname resolution strategy and optional hosts-file policy.",
1135
+ "description": "How lane hostnames such as `api.acme-2.localhost` resolve to this machine: the strategy (`resolver`), its `tld`, and whether names also go into `/etc/hosts` (`hosts_file`). The defaults need no setup for browsers that resolve `*.localhost`.",
1025
1136
  "examples": [
1026
1137
  {
1027
1138
  "hosts_file": "auto",
@@ -1031,7 +1142,21 @@
1031
1142
  "resolver": "resolver",
1032
1143
  "tld": "test"
1033
1144
  }
1034
- ]
1145
+ ],
1146
+ "markdownDescription": "How lane hostnames such as `api.acme-2.localhost` resolve to this machine: the strategy (`resolver`), its `tld`, and whether names also go into `/etc/hosts` (`hosts_file`). The defaults need no setup for browsers that resolve `*.localhost`."
1147
+ },
1148
+ "lanes": {
1149
+ "$ref": "#/$defs/UserLanesConfig",
1150
+ "default": {
1151
+ "data_dir": null
1152
+ },
1153
+ "description": "Where lane data folders (`lane.data_path`) live on this machine.",
1154
+ "examples": [
1155
+ {
1156
+ "data_dir": "~/.devlanes/data"
1157
+ }
1158
+ ],
1159
+ "markdownDescription": "Where lane data folders (`lane.data_path`) live on this machine."
1035
1160
  },
1036
1161
  "logs": {
1037
1162
  "$ref": "#/$defs/LogsConfig",
@@ -1041,7 +1166,7 @@
1041
1166
  "max_size": "5368709120B"
1042
1167
  }
1043
1168
  },
1044
- "description": "Retention for service output files and their database metadata.",
1169
+ "description": "How much service output devlanes keeps under `~/.devlanes/logs`, by age and total size.",
1045
1170
  "examples": [
1046
1171
  {
1047
1172
  "retention": {
@@ -1049,31 +1174,34 @@
1049
1174
  "max_size": "5GiB"
1050
1175
  }
1051
1176
  }
1052
- ]
1177
+ ],
1178
+ "markdownDescription": "How much service output devlanes keeps under `~/.devlanes/logs`, by age and total size."
1053
1179
  },
1054
1180
  "metrics": {
1055
1181
  "$ref": "#/$defs/MetricsConfig",
1056
1182
  "default": {
1057
1183
  "retention": "604800000ms"
1058
1184
  },
1059
- "description": "Retention for monitor history, including aggregates stored in the database.",
1185
+ "description": "How long CPU and memory history stays in the daemon's database.",
1060
1186
  "examples": [
1061
1187
  {
1062
1188
  "retention": "7d"
1063
1189
  }
1064
- ]
1190
+ ],
1191
+ "markdownDescription": "How long CPU and memory history stays in the daemon's database."
1065
1192
  },
1066
1193
  "ports": {
1067
1194
  "$ref": "#/$defs/PortsConfig",
1068
1195
  "default": {
1069
1196
  "range": "20000-32767"
1070
1197
  },
1071
- "description": "Machine-wide pool shared by all project lane blocks.",
1198
+ "description": "Port range that every lane of every project takes its block of ports from, so lanes never collide. Defaults to `20000-32767`.",
1072
1199
  "examples": [
1073
1200
  {
1074
1201
  "range": "20000-32767"
1075
1202
  }
1076
- ]
1203
+ ],
1204
+ "markdownDescription": "Port range that every lane of every project takes its block of ports from, so lanes never collide. Defaults to `20000-32767`."
1077
1205
  },
1078
1206
  "proxy": {
1079
1207
  "$ref": "#/$defs/ProxyConfig",
@@ -1082,26 +1210,28 @@
1082
1210
  "https_port": 443,
1083
1211
  "lan": false
1084
1212
  },
1085
- "description": "HTTP/HTTPS listening ports and LAN access policy.",
1213
+ "description": "Ports of the daemon's HTTP/HTTPS proxy, which serves every `hosts` entry, and whether other devices on the network may connect to it.",
1086
1214
  "examples": [
1087
1215
  {
1088
1216
  "http_port": 80,
1089
1217
  "https_port": 443,
1090
1218
  "lan": false
1091
1219
  }
1092
- ]
1220
+ ],
1221
+ "markdownDescription": "Ports of the daemon's HTTP/HTTPS proxy, which serves every `hosts` entry, and whether other devices on the network may connect to it."
1093
1222
  },
1094
1223
  "startup": {
1095
1224
  "$ref": "#/$defs/StartupConfig",
1096
1225
  "default": {
1097
1226
  "initial": null,
1098
1227
  "max": null,
1099
- "mode": "fixed",
1228
+ "mode": "adaptive",
1229
+ "reserve_memory": null,
1100
1230
  "settle_cpu": "10%",
1101
1231
  "slot_timeout": "20000ms",
1102
1232
  "target_cpu": "75%"
1103
1233
  },
1104
- "description": "How many services start at once, across all lanes and projects.",
1234
+ "description": "How many services may be starting at the same time across all lanes and projects, so a large start does not overload the machine. Defaults to one start per CPU core.",
1105
1235
  "examples": [
1106
1236
  {
1107
1237
  "max": 4,
@@ -1111,40 +1241,47 @@
1111
1241
  "initial": 2,
1112
1242
  "max": 8,
1113
1243
  "mode": "adaptive",
1244
+ "reserve_memory": "2GiB",
1114
1245
  "settle_cpu": "10%",
1115
1246
  "slot_timeout": "20s",
1116
1247
  "target_cpu": "75%"
1117
1248
  }
1118
- ]
1249
+ ],
1250
+ "markdownDescription": "How many services may be starting at the same time across all lanes and projects, so a large start does not overload the machine. Defaults to one start per CPU core."
1119
1251
  },
1120
1252
  "state": {
1121
1253
  "$ref": "#/$defs/StateConfig",
1122
1254
  "default": {
1123
1255
  "max_size": "536870912B"
1124
1256
  },
1125
- "description": "Soft disk budget for the state database and its SQLite sidecar files.",
1257
+ "description": "Size budget for the daemon's database; past it, the oldest metrics and history are deleted before their retention ends.",
1126
1258
  "examples": [
1127
1259
  {
1128
1260
  "max_size": "512MiB"
1129
1261
  }
1130
- ]
1262
+ ],
1263
+ "markdownDescription": "Size budget for the daemon's database; past it, the oldest metrics and history are deleted before their retention ends."
1131
1264
  },
1132
1265
  "terminal": {
1133
1266
  "anyOf": [
1134
1267
  {
1135
- "$ref": "#/$defs/TerminalConfig"
1268
+ "$ref": "#/$defs/TerminalConfig",
1269
+ "description": "",
1270
+ "examples": [],
1271
+ "markdownDescription": ""
1136
1272
  },
1137
1273
  {
1138
1274
  "type": "null"
1139
1275
  }
1140
1276
  ],
1141
1277
  "default": null,
1142
- "description": "How devlanes uses the terminal tab's progress bar during starts.",
1278
+ "description": "Terminal integration of the CLI, written as a mapping with `progress` (the tab's progress bar during starts). A plain string, the terminal app name older versions used, is still accepted but ignored with a warning.",
1143
1279
  "examples": [
1144
1280
  {
1145
1281
  "progress": "auto"
1146
1282
  }
1147
- ]
1283
+ ],
1284
+ "markdownDescription": "Terminal integration of the CLI, written as a mapping with `progress` (the tab's progress bar during starts). A plain string, the terminal app name older versions used, is still accepted but ignored with a warning."
1148
1285
  },
1149
1286
  "tls": {
1150
1287
  "$ref": "#/$defs/TlsConfig",
@@ -1152,7 +1289,7 @@
1152
1289
  "ca": "devlanes",
1153
1290
  "trust": []
1154
1291
  },
1155
- "description": "Certificate authority and explicitly requested trust targets.",
1292
+ "description": "Which certificate authority signs the proxy's HTTPS certificates (`ca`), and which stores `devlanes certs trust` installs it into (`trust`).",
1156
1293
  "examples": [
1157
1294
  {
1158
1295
  "ca": "devlanes",
@@ -1161,7 +1298,8 @@
1161
1298
  "firefox"
1162
1299
  ]
1163
1300
  }
1164
- ]
1301
+ ],
1302
+ "markdownDescription": "Which certificate authority signs the proxy's HTTPS certificates (`ca`), and which stores `devlanes certs trust` installs it into (`trust`)."
1165
1303
  },
1166
1304
  "tui": {
1167
1305
  "$ref": "#/$defs/TuiConfig",
@@ -1171,7 +1309,7 @@
1171
1309
  "open_on_start": true,
1172
1310
  "theme": "auto"
1173
1311
  },
1174
- "description": "How commands use the terminal UI.",
1312
+ "description": "Whether start commands open the terminal UI, how it looks, and what quitting it does with running lanes.",
1175
1313
  "examples": [
1176
1314
  {
1177
1315
  "open_on_start": false
@@ -1183,21 +1321,22 @@
1183
1321
  {
1184
1322
  "on_quit": "keep"
1185
1323
  }
1186
- ]
1324
+ ],
1325
+ "markdownDescription": "Whether start commands open the terminal UI, how it looks, and what quitting it does with running lanes."
1187
1326
  },
1188
1327
  "updates": {
1189
1328
  "$ref": "#/$defs/UpdatesConfig",
1190
1329
  "default": {
1191
1330
  "check": true
1192
1331
  },
1193
- "description": "Whether the daemon looks for new devlanes releases.",
1332
+ "description": "Daily check for a newer devlanes release; nothing is downloaded or installed.",
1194
1333
  "examples": [
1195
1334
  {
1196
1335
  "check": false
1197
1336
  }
1198
- ]
1337
+ ],
1338
+ "markdownDescription": "Daily check for a newer devlanes release; nothing is downloaded or installed."
1199
1339
  }
1200
1340
  },
1201
- "title": "devlanes user config",
1202
1341
  "type": "object"
1203
1342
  }