server-handbook 0.1.0__py3-none-any.whl
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.
- server_handbook/__init__.py +3 -0
- server_handbook/__main__.py +3 -0
- server_handbook/assets/schemas/service.schema.json +391 -0
- server_handbook/assets/skills/server-handbook/SKILL.md +24 -0
- server_handbook/assets/skills/server-handbook/references/workflow.md +39 -0
- server_handbook/cli.py +157 -0
- server_handbook/config.py +205 -0
- server_handbook/documents.py +217 -0
- server_handbook/errors.py +5 -0
- server_handbook/mcp.py +227 -0
- server_handbook/operations.py +383 -0
- server_handbook/registry.py +450 -0
- server_handbook/runtime.py +133 -0
- server_handbook-0.1.0.dist-info/METADATA +144 -0
- server_handbook-0.1.0.dist-info/RECORD +19 -0
- server_handbook-0.1.0.dist-info/WHEEL +5 -0
- server_handbook-0.1.0.dist-info/entry_points.txt +3 -0
- server_handbook-0.1.0.dist-info/licenses/LICENSE +21 -0
- server_handbook-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:server-handbook:service:1",
|
|
4
|
+
"title": "Server Handbook service declaration v1",
|
|
5
|
+
"description": "Portable structural validation. The runtime also checks native absolute paths, configured roots, symlinks, source placement, file contents and HTTP URL semantics.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schema_version",
|
|
10
|
+
"id",
|
|
11
|
+
"name",
|
|
12
|
+
"kind",
|
|
13
|
+
"state",
|
|
14
|
+
"home",
|
|
15
|
+
"readme"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"schema_version": {
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"const": 1
|
|
21
|
+
},
|
|
22
|
+
"id": {
|
|
23
|
+
"$ref": "#/$defs/serviceId"
|
|
24
|
+
},
|
|
25
|
+
"name": {
|
|
26
|
+
"$ref": "#/$defs/shortText"
|
|
27
|
+
},
|
|
28
|
+
"kind": {
|
|
29
|
+
"enum": [
|
|
30
|
+
"compose",
|
|
31
|
+
"systemd",
|
|
32
|
+
"project",
|
|
33
|
+
"desktop",
|
|
34
|
+
"toolchain"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"state": {
|
|
38
|
+
"enum": [
|
|
39
|
+
"prepared",
|
|
40
|
+
"installed",
|
|
41
|
+
"deployed",
|
|
42
|
+
"retired"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"home": {
|
|
46
|
+
"$ref": "#/$defs/absolutePath"
|
|
47
|
+
},
|
|
48
|
+
"readme": {
|
|
49
|
+
"allOf": [
|
|
50
|
+
{
|
|
51
|
+
"$ref": "#/$defs/absolutePath"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"pattern": "\\.[mM][dD]$"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"version": {
|
|
59
|
+
"$ref": "#/$defs/shortText"
|
|
60
|
+
},
|
|
61
|
+
"systemd_units": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"maxItems": 256,
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"maxLength": 256,
|
|
67
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9@_.:\\\\-]*\\.(service|socket|timer|target|mount|automount|path|slice|scope)$",
|
|
68
|
+
"not": {
|
|
69
|
+
"pattern": "\\s"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"compose": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"required": [
|
|
77
|
+
"project",
|
|
78
|
+
"service",
|
|
79
|
+
"files"
|
|
80
|
+
],
|
|
81
|
+
"properties": {
|
|
82
|
+
"project": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"pattern": "^[a-z0-9][a-z0-9_-]{0,62}$",
|
|
85
|
+
"not": {
|
|
86
|
+
"pattern": "\\s"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"service": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$",
|
|
92
|
+
"not": {
|
|
93
|
+
"pattern": "\\s"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"files": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"minItems": 1,
|
|
99
|
+
"maxItems": 32,
|
|
100
|
+
"items": {
|
|
101
|
+
"$ref": "#/$defs/absolutePath"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"packages": {
|
|
107
|
+
"$ref": "#/$defs/textArray"
|
|
108
|
+
},
|
|
109
|
+
"local_ports": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"maxItems": 256,
|
|
112
|
+
"items": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": false,
|
|
115
|
+
"required": [
|
|
116
|
+
"bind",
|
|
117
|
+
"port",
|
|
118
|
+
"transport",
|
|
119
|
+
"purpose"
|
|
120
|
+
],
|
|
121
|
+
"properties": {
|
|
122
|
+
"bind": {
|
|
123
|
+
"$ref": "#/$defs/text"
|
|
124
|
+
},
|
|
125
|
+
"port": {
|
|
126
|
+
"$ref": "#/$defs/port"
|
|
127
|
+
},
|
|
128
|
+
"transport": {
|
|
129
|
+
"enum": [
|
|
130
|
+
"tcp",
|
|
131
|
+
"udp"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"purpose": {
|
|
135
|
+
"$ref": "#/$defs/text"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"remote_endpoints": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"maxItems": 256,
|
|
143
|
+
"items": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"additionalProperties": false,
|
|
146
|
+
"required": [
|
|
147
|
+
"direction",
|
|
148
|
+
"host",
|
|
149
|
+
"port",
|
|
150
|
+
"transport",
|
|
151
|
+
"purpose"
|
|
152
|
+
],
|
|
153
|
+
"properties": {
|
|
154
|
+
"direction": {
|
|
155
|
+
"enum": [
|
|
156
|
+
"inbound",
|
|
157
|
+
"outbound"
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
"host": {
|
|
161
|
+
"$ref": "#/$defs/text"
|
|
162
|
+
},
|
|
163
|
+
"port": {
|
|
164
|
+
"$ref": "#/$defs/port"
|
|
165
|
+
},
|
|
166
|
+
"transport": {
|
|
167
|
+
"enum": [
|
|
168
|
+
"tcp",
|
|
169
|
+
"udp"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
"purpose": {
|
|
173
|
+
"$ref": "#/$defs/text"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"config_paths": {
|
|
179
|
+
"$ref": "#/$defs/pathArray"
|
|
180
|
+
},
|
|
181
|
+
"data_paths": {
|
|
182
|
+
"$ref": "#/$defs/pathArray"
|
|
183
|
+
},
|
|
184
|
+
"secret_refs": {
|
|
185
|
+
"$ref": "#/$defs/pathArray"
|
|
186
|
+
},
|
|
187
|
+
"depends_on": {
|
|
188
|
+
"type": "array",
|
|
189
|
+
"maxItems": 256,
|
|
190
|
+
"items": {
|
|
191
|
+
"$ref": "#/$defs/serviceId"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"commands": {
|
|
195
|
+
"type": "object",
|
|
196
|
+
"additionalProperties": false,
|
|
197
|
+
"description": "Display-only maintenance notes; Server Handbook never executes these strings.",
|
|
198
|
+
"properties": {
|
|
199
|
+
"status": {
|
|
200
|
+
"$ref": "#/$defs/text"
|
|
201
|
+
},
|
|
202
|
+
"start": {
|
|
203
|
+
"$ref": "#/$defs/text"
|
|
204
|
+
},
|
|
205
|
+
"stop": {
|
|
206
|
+
"$ref": "#/$defs/text"
|
|
207
|
+
},
|
|
208
|
+
"restart": {
|
|
209
|
+
"$ref": "#/$defs/text"
|
|
210
|
+
},
|
|
211
|
+
"logs": {
|
|
212
|
+
"$ref": "#/$defs/text"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"notes": {
|
|
217
|
+
"$ref": "#/$defs/textArray"
|
|
218
|
+
},
|
|
219
|
+
"evidence": {
|
|
220
|
+
"$ref": "#/$defs/pathArray"
|
|
221
|
+
},
|
|
222
|
+
"checks": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"maxItems": 16,
|
|
225
|
+
"items": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"additionalProperties": false,
|
|
228
|
+
"required": [
|
|
229
|
+
"type",
|
|
230
|
+
"url",
|
|
231
|
+
"expected_status",
|
|
232
|
+
"timeout_seconds"
|
|
233
|
+
],
|
|
234
|
+
"properties": {
|
|
235
|
+
"type": {
|
|
236
|
+
"const": "http"
|
|
237
|
+
},
|
|
238
|
+
"url": {
|
|
239
|
+
"allOf": [
|
|
240
|
+
{
|
|
241
|
+
"$ref": "#/$defs/text"
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"maxLength": 2048,
|
|
245
|
+
"format": "uri",
|
|
246
|
+
"pattern": "^[hH][tT][tT][pP][sS]?://",
|
|
247
|
+
"not": {
|
|
248
|
+
"anyOf": [
|
|
249
|
+
{
|
|
250
|
+
"pattern": "\\s"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"pattern": "^[hH][tT][tT][pP][sS]?://[^/?#]*@"
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"expected_status": {
|
|
259
|
+
"type": "integer",
|
|
260
|
+
"minimum": 100,
|
|
261
|
+
"maximum": 599
|
|
262
|
+
},
|
|
263
|
+
"timeout_seconds": {
|
|
264
|
+
"type": "integer",
|
|
265
|
+
"minimum": 1,
|
|
266
|
+
"maximum": 30
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
"allOf": [
|
|
273
|
+
{
|
|
274
|
+
"if": {
|
|
275
|
+
"properties": {
|
|
276
|
+
"kind": {
|
|
277
|
+
"const": "compose"
|
|
278
|
+
},
|
|
279
|
+
"state": {
|
|
280
|
+
"const": "deployed"
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"required": [
|
|
284
|
+
"kind",
|
|
285
|
+
"state"
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
"then": {
|
|
289
|
+
"required": [
|
|
290
|
+
"compose"
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"if": {
|
|
296
|
+
"properties": {
|
|
297
|
+
"kind": {
|
|
298
|
+
"const": "systemd"
|
|
299
|
+
},
|
|
300
|
+
"state": {
|
|
301
|
+
"const": "deployed"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
"required": [
|
|
305
|
+
"kind",
|
|
306
|
+
"state"
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
"then": {
|
|
310
|
+
"required": [
|
|
311
|
+
"systemd_units"
|
|
312
|
+
],
|
|
313
|
+
"properties": {
|
|
314
|
+
"systemd_units": {
|
|
315
|
+
"minItems": 1
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
"$defs": {
|
|
322
|
+
"text": {
|
|
323
|
+
"type": "string",
|
|
324
|
+
"minLength": 1,
|
|
325
|
+
"maxLength": 4096,
|
|
326
|
+
"allOf": [
|
|
327
|
+
{
|
|
328
|
+
"pattern": "\\S"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"not": {
|
|
332
|
+
"pattern": "\u0000"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
},
|
|
337
|
+
"shortText": {
|
|
338
|
+
"allOf": [
|
|
339
|
+
{
|
|
340
|
+
"$ref": "#/$defs/text"
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
"maxLength": 200
|
|
344
|
+
},
|
|
345
|
+
"serviceId": {
|
|
346
|
+
"type": "string",
|
|
347
|
+
"pattern": "^[a-z0-9][a-z0-9_-]{0,63}$",
|
|
348
|
+
"not": {
|
|
349
|
+
"pattern": "\\s"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"absolutePath": {
|
|
353
|
+
"allOf": [
|
|
354
|
+
{
|
|
355
|
+
"$ref": "#/$defs/text"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
"description": "POSIX, Windows drive, or UNC absolute syntax. Native-platform validation and resolved containment are enforced at runtime.",
|
|
359
|
+
"anyOf": [
|
|
360
|
+
{
|
|
361
|
+
"pattern": "^/"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"pattern": "^[A-Za-z]:[\\\\/]"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"pattern": "^\\\\"
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
"textArray": {
|
|
372
|
+
"type": "array",
|
|
373
|
+
"maxItems": 256,
|
|
374
|
+
"items": {
|
|
375
|
+
"$ref": "#/$defs/text"
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
"pathArray": {
|
|
379
|
+
"type": "array",
|
|
380
|
+
"maxItems": 256,
|
|
381
|
+
"items": {
|
|
382
|
+
"$ref": "#/$defs/absolutePath"
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
"port": {
|
|
386
|
+
"type": "integer",
|
|
387
|
+
"minimum": 1,
|
|
388
|
+
"maximum": 65535
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-handbook
|
|
3
|
+
description: Adopt, inspect, deploy, or hand off projects on a server using Server Handbook's registry and managed operation receipts. Use for service maintenance and server context recovery, not unrelated coding tasks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Server Handbook
|
|
7
|
+
|
|
8
|
+
Use the installed CLI or `handbook_*` MCP tools to find the actual service and its maintenance instructions. The user may have chosen custom paths; do not assume `/srv/workspace` or move existing projects.
|
|
9
|
+
|
|
10
|
+
Start with `handoff` / `handbook_handoff`, then inspect the target service and read its registered README. Read existing workspace and project AGENTS instructions. Preserve explicit user choices and authorization; this skill supplies a workflow, not additional permission.
|
|
11
|
+
|
|
12
|
+
Treat these as different evidence:
|
|
13
|
+
|
|
14
|
+
- Declaration: desired metadata, paths and current registry revision.
|
|
15
|
+
- Observation: a fresh query, possibly `unknown` if permissions or tools are unavailable.
|
|
16
|
+
- Receipt: what a specific managed operation actually executed and verified at a recorded time.
|
|
17
|
+
|
|
18
|
+
For deployment, inspect the plan and source drift first. Apply only the requested registered service action, with its current expected revision and a meaningful purpose. Inspect the resulting receipt. Process readiness alone does not establish business correctness; report the configured check scope and any missing checks.
|
|
19
|
+
|
|
20
|
+
For metadata changes, edit the service's declared source file, then register it with the current expected revision. Registration and managed execution update their own README block automatically; retain manual notes. Run doctor and inspect reported drift or unfinished operations before reporting completion.
|
|
21
|
+
|
|
22
|
+
If work was interrupted, inspect its receipt and live state. Recovery only marks abandoned work interrupted; it does not replay commands or roll back external effects. Do not rerun first-time database initialization as routine maintenance.
|
|
23
|
+
|
|
24
|
+
Read [the workflow reference](references/workflow.md) when preparing a new project, adopting an old one, or handling an incomplete operation. Direct shell actions outside this tool are not fully recorded; explain such changes rather than claiming a complete audit trail.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Using the shared server workflow
|
|
2
|
+
|
|
3
|
+
Commands below assume the workspace config is discoverable. Otherwise add `--config /absolute/path/to/.server-handbook/config.json` before the subcommand. Use `--help` to confirm installed options.
|
|
4
|
+
|
|
5
|
+
## Locate and inspect
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
server-handbook handoff
|
|
9
|
+
server-handbook list
|
|
10
|
+
server-handbook show SERVICE_ID
|
|
11
|
+
server-handbook doctor
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Follow returned README paths and the recorded Compose files or systemd units. `overview --runtime` adds current host observations. Missing permissions return unknown, not stopped. Descriptor `commands` values are reference text and are never an execution mechanism.
|
|
15
|
+
|
|
16
|
+
## Prepare or adopt
|
|
17
|
+
|
|
18
|
+
`project new` and `adopt` preview by default. `--apply` writes their reviewed preparation files; neither deploys or registers a service. Adoption retains the existing home and manual documentation. Review discovered candidates before deciding they are current services.
|
|
19
|
+
|
|
20
|
+
Fill `home/deploy/registry/SERVICE_ID.json` with actual metadata. Keep credentials in their existing controlled configuration, and record only paths in `secret_refs`. State `prepared` remains honest until actual deployment is established. Optional profiles guide development without replacing repository-specific conventions.
|
|
21
|
+
|
|
22
|
+
## Register and deploy
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
server-handbook register /absolute/service-home/deploy/registry/SERVICE_ID.json --note 'Actual metadata change and known verification scope'
|
|
26
|
+
server-handbook plan SERVICE_ID --action up
|
|
27
|
+
server-handbook apply SERVICE_ID --action up --expect-revision REVISION --note 'Purpose of this authorized deployment'
|
|
28
|
+
server-handbook operations --service SERVICE_ID
|
|
29
|
+
server-handbook operation OPERATION_ID
|
|
30
|
+
server-handbook doctor
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
On an existing declaration, registration also requires `--expect-revision REVISION`. Resolve conflicts by rereading and comparing; do not overwrite blindly. Compose `up/restart/stop` applies only to the named service; dependencies must already be running. systemd `start/restart/stop/reload` applies to the registered units.
|
|
34
|
+
|
|
35
|
+
Read the receipt even if the process started. It distinguishes execution failure, check failure, process-only verification, configured HTTP verification, and documentation refresh failure. For business acceptance beyond those probes, use the project's existing tests under the user's task and record the additional outcome honestly in maintenance notes.
|
|
36
|
+
|
|
37
|
+
## Incomplete work
|
|
38
|
+
|
|
39
|
+
An unfinished operation blocks another managed action on the same service. Inspect both the receipt and current state. `recover OPERATION_ID --note ...` refuses a live owner and marks abandoned work interrupted. It never performs rollback, retries commands or resets data. Build a new plan only after understanding remaining effects.
|
server_handbook/cli.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""One CLI over the shared validation, registration and execution core."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .errors import HandbookError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parser():
|
|
13
|
+
root = argparse.ArgumentParser(description="Server Handbook: server context, deployment registry, and tracked operations.")
|
|
14
|
+
root.add_argument("--version", action="version", version=f"Server Handbook {__version__}")
|
|
15
|
+
root.add_argument("--config", help="Workspace config; also SERVER_HANDBOOK_CONFIG or nearest .server-handbook/config.json")
|
|
16
|
+
commands = root.add_subparsers(dest="command", required=True)
|
|
17
|
+
init = commands.add_parser("init", help="Preview initialization; --apply creates only tool-owned files and managed blocks")
|
|
18
|
+
init.add_argument("--workspace", required=True)
|
|
19
|
+
init.add_argument("--name", default="Server Handbook")
|
|
20
|
+
init.add_argument("--project-root", action="append")
|
|
21
|
+
init.add_argument("--service-root", action="append")
|
|
22
|
+
init.add_argument("--records-dir")
|
|
23
|
+
init.add_argument("--apply", action="store_true")
|
|
24
|
+
project = commands.add_parser("project", help="Prepare a new project without claiming a deployment")
|
|
25
|
+
new = project.add_subparsers(dest="project_command", required=True).add_parser("new")
|
|
26
|
+
new.add_argument("id")
|
|
27
|
+
new.add_argument("--name", required=True)
|
|
28
|
+
new.add_argument("--kind", choices=("project", "compose", "systemd"), default="project")
|
|
29
|
+
new.add_argument("--home")
|
|
30
|
+
new.add_argument("--profile", choices=("generic", "python", "node", "java"), default="generic")
|
|
31
|
+
new.add_argument("--apply", action="store_true")
|
|
32
|
+
adopt = commands.add_parser("adopt", help="Prepare an existing project; retain its files and manual notes")
|
|
33
|
+
for name in ("home", "id", "name"):
|
|
34
|
+
adopt.add_argument(f"--{name}", required=True)
|
|
35
|
+
adopt.add_argument("--kind", choices=("project", "compose", "systemd", "desktop", "toolchain"), default="project")
|
|
36
|
+
adopt.add_argument("--compose-file", action="append")
|
|
37
|
+
adopt.add_argument("--compose-project")
|
|
38
|
+
adopt.add_argument("--compose-service")
|
|
39
|
+
adopt.add_argument("--unit", action="append")
|
|
40
|
+
adopt.add_argument("--apply", action="store_true")
|
|
41
|
+
register = commands.add_parser("register", help="Register a reviewed descriptor and refresh its managed README block")
|
|
42
|
+
register.add_argument("file")
|
|
43
|
+
register.add_argument("--note", required=True)
|
|
44
|
+
register.add_argument("--expect-revision", type=int)
|
|
45
|
+
listed = commands.add_parser("list", help="List registered metadata, not live health")
|
|
46
|
+
listed.add_argument("--offset", type=int, default=0)
|
|
47
|
+
listed.add_argument("--limit", type=int, default=20)
|
|
48
|
+
for name in ("show", "history"):
|
|
49
|
+
commands.add_parser(name).add_argument("id")
|
|
50
|
+
commands.add_parser("overview").add_argument("--runtime", action="store_true")
|
|
51
|
+
commands.add_parser("discover", help="Find candidates read-only; never auto-register")
|
|
52
|
+
commands.add_parser("doctor", help="Check source drift, registry integrity and unfinished operations").add_argument("--runtime", action="store_true")
|
|
53
|
+
docs = commands.add_parser("docs")
|
|
54
|
+
render = docs.add_subparsers(dest="docs_command", required=True).add_parser("render")
|
|
55
|
+
render.add_argument("id")
|
|
56
|
+
render.add_argument("--apply", action="store_true")
|
|
57
|
+
commands.add_parser("handoff", help="Compact zero-context entry with next steps")
|
|
58
|
+
skill = commands.add_parser("skill", help="Export the bundled portable Agent Skill without changing client settings")
|
|
59
|
+
export = skill.add_subparsers(dest="skill_command", required=True).add_parser("export")
|
|
60
|
+
export.add_argument("destination")
|
|
61
|
+
export.add_argument("--apply", action="store_true")
|
|
62
|
+
for name in ("plan", "apply"):
|
|
63
|
+
operation = commands.add_parser(name, help="Plan a registered operation" if name == "plan" else "Execute and record a registered operation")
|
|
64
|
+
operation.add_argument("id")
|
|
65
|
+
operation.add_argument("--action", required=True, choices=("up", "start", "restart", "stop", "reload"))
|
|
66
|
+
if name == "apply":
|
|
67
|
+
operation.add_argument("--expect-revision", required=True, type=int)
|
|
68
|
+
operation.add_argument("--note", required=True)
|
|
69
|
+
operations = commands.add_parser("operations")
|
|
70
|
+
operations.add_argument("--service")
|
|
71
|
+
operations.add_argument("--limit", type=int, default=20)
|
|
72
|
+
commands.add_parser("operation").add_argument("id")
|
|
73
|
+
recovery = commands.add_parser("recover", help="Mark an abandoned operation interrupted; never replay or roll back commands")
|
|
74
|
+
recovery.add_argument("id")
|
|
75
|
+
recovery.add_argument("--note", required=True)
|
|
76
|
+
commands.add_parser("mcp", help="Serve stdio MCP; readonly unless explicitly enabled").add_argument("--allow-write", action="store_true")
|
|
77
|
+
return root
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def dispatch(args):
|
|
81
|
+
from .config import initialize, load_config
|
|
82
|
+
if args.command == "init":
|
|
83
|
+
return initialize(args.workspace, name=args.name, project_roots=args.project_root,
|
|
84
|
+
service_roots=args.service_root, records_dir=args.records_dir, apply=args.apply)
|
|
85
|
+
if args.command == "skill":
|
|
86
|
+
from .documents import export_skill
|
|
87
|
+
return export_skill(args.destination, apply=args.apply)
|
|
88
|
+
config = load_config(args.config)
|
|
89
|
+
from . import documents, operations, registry, runtime
|
|
90
|
+
if args.command == "project":
|
|
91
|
+
return documents.project_new(config, args.id, args.name, kind=args.kind, home=args.home,
|
|
92
|
+
profile=args.profile, apply=args.apply)
|
|
93
|
+
if args.command == "adopt":
|
|
94
|
+
return documents.adopt(config, args.home, args.id, args.name, kind=args.kind,
|
|
95
|
+
compose_files=args.compose_file, compose_project=args.compose_project,
|
|
96
|
+
compose_service=args.compose_service, systemd_units=args.unit, apply=args.apply)
|
|
97
|
+
if args.command == "register":
|
|
98
|
+
return documents.register_and_render(config, args.file, args.note, expect_revision=args.expect_revision)
|
|
99
|
+
if args.command == "list":
|
|
100
|
+
return registry.list_services(config, offset=args.offset, limit=args.limit)
|
|
101
|
+
if args.command == "show":
|
|
102
|
+
return registry.get_service(config, args.id)
|
|
103
|
+
if args.command == "history":
|
|
104
|
+
return {"history": registry.service_history(config, args.id)}
|
|
105
|
+
if args.command == "overview":
|
|
106
|
+
return runtime.overview(config, include_runtime=args.runtime)
|
|
107
|
+
if args.command == "discover":
|
|
108
|
+
return runtime.discover(config)
|
|
109
|
+
if args.command == "doctor":
|
|
110
|
+
return runtime.doctor(config, include_runtime=args.runtime)
|
|
111
|
+
if args.command == "docs":
|
|
112
|
+
return documents.render_service_docs(config, args.id, apply=args.apply)
|
|
113
|
+
if args.command == "handoff":
|
|
114
|
+
return documents.handoff(config)
|
|
115
|
+
if args.command == "plan":
|
|
116
|
+
return operations.plan_operation(config, args.id, args.action)
|
|
117
|
+
if args.command == "apply":
|
|
118
|
+
return operations.apply_operation(config, args.id, args.action, args.expect_revision, args.note)
|
|
119
|
+
if args.command == "operations":
|
|
120
|
+
return {"operations": operations.list_operations(config, service_id=args.service, limit=args.limit)}
|
|
121
|
+
if args.command == "operation":
|
|
122
|
+
return operations.get_operation(config, args.id)
|
|
123
|
+
if args.command == "recover":
|
|
124
|
+
return operations.recover_operation(config, args.id, args.note)
|
|
125
|
+
raise HandbookError("Unknown command; use --help.")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main(argv=None):
|
|
129
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
130
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
131
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
132
|
+
args = parser().parse_args(argv)
|
|
133
|
+
try:
|
|
134
|
+
if args.command == "mcp":
|
|
135
|
+
from .mcp import main as mcp_main
|
|
136
|
+
values = (["--config", args.config] if args.config else []) + (["--allow-write"] if args.allow_write else [])
|
|
137
|
+
return mcp_main(values)
|
|
138
|
+
result = dispatch(args)
|
|
139
|
+
if result is not None:
|
|
140
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
141
|
+
if args.command == "apply" and result["status"] != "succeeded":
|
|
142
|
+
return 1
|
|
143
|
+
if args.command == "doctor" and (not result["valid"] or result["unfinished_operations"]):
|
|
144
|
+
return 1
|
|
145
|
+
return 0
|
|
146
|
+
except ImportError as error:
|
|
147
|
+
if args.command == "mcp":
|
|
148
|
+
print("MCP extra is unavailable. Install 'server-handbook[mcp]' in this environment.", file=sys.stderr)
|
|
149
|
+
return 1
|
|
150
|
+
raise error
|
|
151
|
+
except (HandbookError, OSError) as error:
|
|
152
|
+
print(f"Error: {error}", file=sys.stderr)
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
raise SystemExit(main())
|