@crvouga/personal 0.1.2 → 0.1.4
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/README.md +25 -3
- package/cli.mjs +12174 -1883
- package/commands.json +224 -28
- package/package.json +3 -3
- package/probe.mjs +86 -11
- package/releasing.md +12 -2
- package/schema.json +19 -3
package/commands.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"commands": [
|
|
4
|
+
{
|
|
5
|
+
"id": "fleet-sync",
|
|
6
|
+
"title": "Synchronize the enrolled Ubuntu fleet",
|
|
7
|
+
"description": "Controller push to all enrolled Ubuntu nodes at one approved revision; interactive confirmation or --apply --yes. Readiness is verified separately.",
|
|
8
|
+
"group": "Fleet",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"ubuntu",
|
|
11
|
+
"enrollment",
|
|
12
|
+
"sync"
|
|
13
|
+
],
|
|
14
|
+
"args": [
|
|
15
|
+
"fleet-sync"
|
|
16
|
+
],
|
|
17
|
+
"examples": [
|
|
18
|
+
"personal fleet-sync --dry-run --json"
|
|
19
|
+
],
|
|
20
|
+
"mutation": true,
|
|
21
|
+
"help": "personal fleet-sync --help",
|
|
22
|
+
"execution": {
|
|
23
|
+
"default": "interactive-confirmation-or-action-required",
|
|
24
|
+
"authorization": "interactive-confirmation or --apply --yes",
|
|
25
|
+
"preview": "personal fleet-sync --dry-run --json",
|
|
26
|
+
"scope": "enrolled Ubuntu fleet over verified SSH; controller is not an implicit target",
|
|
27
|
+
"note": "JSON/noninteractive never prompt. Legacy fleet sync stays plan-only. Full Ubuntu acceptance is not yet implemented."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "fleet converge",
|
|
32
|
+
"title": "Synchronize the enrolled Ubuntu fleet",
|
|
33
|
+
"description": "Controller push to all enrolled Ubuntu nodes at one approved revision; interactive confirmation or --apply --yes. Readiness is verified separately.",
|
|
34
|
+
"group": "Fleet",
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ubuntu",
|
|
37
|
+
"enrollment",
|
|
38
|
+
"sync"
|
|
39
|
+
],
|
|
40
|
+
"args": [
|
|
41
|
+
"fleet",
|
|
42
|
+
"converge"
|
|
43
|
+
],
|
|
44
|
+
"examples": [
|
|
45
|
+
"personal fleet converge --dry-run --json"
|
|
46
|
+
],
|
|
47
|
+
"mutation": true,
|
|
48
|
+
"help": "personal fleet converge --help",
|
|
49
|
+
"execution": {
|
|
50
|
+
"default": "interactive-confirmation-or-action-required",
|
|
51
|
+
"authorization": "interactive-confirmation or --apply --yes",
|
|
52
|
+
"preview": "personal fleet converge --dry-run --json",
|
|
53
|
+
"scope": "enrolled Ubuntu fleet over verified SSH; controller is not an implicit target",
|
|
54
|
+
"note": "JSON/noninteractive never prompt. Legacy fleet sync stays plan-only. Full Ubuntu acceptance is not yet implemented."
|
|
55
|
+
}
|
|
56
|
+
},
|
|
4
57
|
{
|
|
5
58
|
"id": "t3-plan",
|
|
6
59
|
"title": "Preview creates, updates and owned removals",
|
|
@@ -18,7 +71,12 @@
|
|
|
18
71
|
"examples": [
|
|
19
72
|
"personal t3-plan --json"
|
|
20
73
|
],
|
|
21
|
-
"mutation": false
|
|
74
|
+
"mutation": false,
|
|
75
|
+
"help": "personal t3-plan --help",
|
|
76
|
+
"execution": {
|
|
77
|
+
"default": "read-only",
|
|
78
|
+
"authorization": "none"
|
|
79
|
+
}
|
|
22
80
|
},
|
|
23
81
|
{
|
|
24
82
|
"id": "t3-apply",
|
|
@@ -37,7 +95,15 @@
|
|
|
37
95
|
"examples": [
|
|
38
96
|
"personal t3-apply --json"
|
|
39
97
|
],
|
|
40
|
-
"mutation": true
|
|
98
|
+
"mutation": true,
|
|
99
|
+
"help": "personal t3-apply --help",
|
|
100
|
+
"execution": {
|
|
101
|
+
"default": "apply",
|
|
102
|
+
"authorization": "command-invocation",
|
|
103
|
+
"preview": "personal t3-apply --dry-run --json",
|
|
104
|
+
"requiredOptions": [],
|
|
105
|
+
"note": "JSON selects output format; it does not prevent writes."
|
|
106
|
+
}
|
|
41
107
|
},
|
|
42
108
|
{
|
|
43
109
|
"id": "t3-status",
|
|
@@ -56,7 +122,12 @@
|
|
|
56
122
|
"examples": [
|
|
57
123
|
"personal t3-status --json"
|
|
58
124
|
],
|
|
59
|
-
"mutation": false
|
|
125
|
+
"mutation": false,
|
|
126
|
+
"help": "personal t3-status --help",
|
|
127
|
+
"execution": {
|
|
128
|
+
"default": "read-only",
|
|
129
|
+
"authorization": "none"
|
|
130
|
+
}
|
|
60
131
|
},
|
|
61
132
|
{
|
|
62
133
|
"id": "t3-cleanup",
|
|
@@ -75,7 +146,15 @@
|
|
|
75
146
|
"examples": [
|
|
76
147
|
"personal t3-cleanup --json"
|
|
77
148
|
],
|
|
78
|
-
"mutation": true
|
|
149
|
+
"mutation": true,
|
|
150
|
+
"help": "personal t3-cleanup --help",
|
|
151
|
+
"execution": {
|
|
152
|
+
"default": "apply",
|
|
153
|
+
"authorization": "command-invocation",
|
|
154
|
+
"preview": "personal t3-cleanup --dry-run --json",
|
|
155
|
+
"requiredOptions": [],
|
|
156
|
+
"note": "JSON selects output format; it does not prevent writes."
|
|
157
|
+
}
|
|
79
158
|
},
|
|
80
159
|
{
|
|
81
160
|
"id": "t3-import",
|
|
@@ -92,9 +171,19 @@
|
|
|
92
171
|
"t3-import"
|
|
93
172
|
],
|
|
94
173
|
"examples": [
|
|
95
|
-
"personal t3-import --json"
|
|
96
|
-
],
|
|
97
|
-
"mutation": true
|
|
174
|
+
"personal t3-import --instance INSTANCE_ID --json"
|
|
175
|
+
],
|
|
176
|
+
"mutation": true,
|
|
177
|
+
"help": "personal t3-import --help",
|
|
178
|
+
"execution": {
|
|
179
|
+
"default": "apply",
|
|
180
|
+
"authorization": "command-invocation",
|
|
181
|
+
"preview": "personal t3-plan --json",
|
|
182
|
+
"requiredOptions": [
|
|
183
|
+
"--instance ID"
|
|
184
|
+
],
|
|
185
|
+
"note": "JSON selects output format; it does not prevent writes."
|
|
186
|
+
}
|
|
98
187
|
},
|
|
99
188
|
{
|
|
100
189
|
"id": "workstation",
|
|
@@ -113,12 +202,21 @@
|
|
|
113
202
|
"examples": [
|
|
114
203
|
"personal workstation --help"
|
|
115
204
|
],
|
|
116
|
-
"mutation": false
|
|
205
|
+
"mutation": false,
|
|
206
|
+
"help": "personal workstation --help",
|
|
207
|
+
"execution": {
|
|
208
|
+
"default": "help",
|
|
209
|
+
"mutation": "depends-on-subcommand",
|
|
210
|
+
"requirements": [
|
|
211
|
+
"Personal checkout and Bun for workstation operations"
|
|
212
|
+
],
|
|
213
|
+
"note": "Inspect leaf --help; use --json --non-interactive for diagnostics. Native agent launches stream native output."
|
|
214
|
+
}
|
|
117
215
|
},
|
|
118
216
|
{
|
|
119
217
|
"id": "fleet-node-sync",
|
|
120
218
|
"title": "Sync this node",
|
|
121
|
-
"description": "
|
|
219
|
+
"description": "Reconcile locked tools, the personal launcher, Codex proxy profile and owned T3 instances on this machine. Applies by default; --dry-run previews.",
|
|
122
220
|
"group": "Fleet",
|
|
123
221
|
"keywords": [
|
|
124
222
|
"node",
|
|
@@ -132,7 +230,15 @@
|
|
|
132
230
|
"examples": [
|
|
133
231
|
"personal fleet-node-sync --json"
|
|
134
232
|
],
|
|
135
|
-
"mutation": true
|
|
233
|
+
"mutation": true,
|
|
234
|
+
"help": "personal fleet-node-sync --help",
|
|
235
|
+
"execution": {
|
|
236
|
+
"default": "apply",
|
|
237
|
+
"authorization": "command-invocation",
|
|
238
|
+
"preview": "personal fleet-node-sync --dry-run --json",
|
|
239
|
+
"requiredOptions": [],
|
|
240
|
+
"note": "JSON selects output format; it does not prevent writes."
|
|
241
|
+
}
|
|
136
242
|
},
|
|
137
243
|
{
|
|
138
244
|
"id": "fleet-status",
|
|
@@ -151,7 +257,12 @@
|
|
|
151
257
|
"examples": [
|
|
152
258
|
"personal fleet-status --json"
|
|
153
259
|
],
|
|
154
|
-
"mutation": false
|
|
260
|
+
"mutation": false,
|
|
261
|
+
"help": "personal fleet-status --help",
|
|
262
|
+
"execution": {
|
|
263
|
+
"default": "read-only",
|
|
264
|
+
"authorization": "none"
|
|
265
|
+
}
|
|
155
266
|
},
|
|
156
267
|
{
|
|
157
268
|
"id": "fleet-discover",
|
|
@@ -170,7 +281,12 @@
|
|
|
170
281
|
"examples": [
|
|
171
282
|
"personal fleet-discover --json"
|
|
172
283
|
],
|
|
173
|
-
"mutation": false
|
|
284
|
+
"mutation": false,
|
|
285
|
+
"help": "personal fleet-discover --help",
|
|
286
|
+
"execution": {
|
|
287
|
+
"default": "read-only",
|
|
288
|
+
"authorization": "none"
|
|
289
|
+
}
|
|
174
290
|
},
|
|
175
291
|
{
|
|
176
292
|
"id": "fleet repairs",
|
|
@@ -193,7 +309,12 @@
|
|
|
193
309
|
"examples": [
|
|
194
310
|
"personal fleet repairs --json"
|
|
195
311
|
],
|
|
196
|
-
"mutation": false
|
|
312
|
+
"mutation": false,
|
|
313
|
+
"help": "personal fleet repairs --help",
|
|
314
|
+
"execution": {
|
|
315
|
+
"default": "read-only",
|
|
316
|
+
"authorization": "none"
|
|
317
|
+
}
|
|
197
318
|
},
|
|
198
319
|
{
|
|
199
320
|
"id": "fleet repair",
|
|
@@ -217,7 +338,13 @@
|
|
|
217
338
|
"examples": [
|
|
218
339
|
"personal fleet repair --json"
|
|
219
340
|
],
|
|
220
|
-
"mutation": true
|
|
341
|
+
"mutation": true,
|
|
342
|
+
"help": "personal fleet repair --help",
|
|
343
|
+
"execution": {
|
|
344
|
+
"default": "preview",
|
|
345
|
+
"authorization": "--apply --yes",
|
|
346
|
+
"note": "Supply required options shown by command --help. A preview does not apply changes."
|
|
347
|
+
}
|
|
221
348
|
},
|
|
222
349
|
{
|
|
223
350
|
"id": "fleet health",
|
|
@@ -242,7 +369,12 @@
|
|
|
242
369
|
"examples": [
|
|
243
370
|
"personal fleet health --json"
|
|
244
371
|
],
|
|
245
|
-
"mutation": false
|
|
372
|
+
"mutation": false,
|
|
373
|
+
"help": "personal fleet health --help",
|
|
374
|
+
"execution": {
|
|
375
|
+
"default": "read-only",
|
|
376
|
+
"authorization": "none"
|
|
377
|
+
}
|
|
246
378
|
},
|
|
247
379
|
{
|
|
248
380
|
"id": "fleet discover",
|
|
@@ -263,7 +395,12 @@
|
|
|
263
395
|
"examples": [
|
|
264
396
|
"personal fleet discover --json"
|
|
265
397
|
],
|
|
266
|
-
"mutation": false
|
|
398
|
+
"mutation": false,
|
|
399
|
+
"help": "personal fleet discover --help",
|
|
400
|
+
"execution": {
|
|
401
|
+
"default": "read-only",
|
|
402
|
+
"authorization": "none"
|
|
403
|
+
}
|
|
267
404
|
},
|
|
268
405
|
{
|
|
269
406
|
"id": "fleet doctor",
|
|
@@ -287,7 +424,12 @@
|
|
|
287
424
|
"examples": [
|
|
288
425
|
"personal fleet doctor --json"
|
|
289
426
|
],
|
|
290
|
-
"mutation": false
|
|
427
|
+
"mutation": false,
|
|
428
|
+
"help": "personal fleet doctor --help",
|
|
429
|
+
"execution": {
|
|
430
|
+
"default": "read-only",
|
|
431
|
+
"authorization": "none"
|
|
432
|
+
}
|
|
291
433
|
},
|
|
292
434
|
{
|
|
293
435
|
"id": "fleet doctor local",
|
|
@@ -307,7 +449,12 @@
|
|
|
307
449
|
"examples": [
|
|
308
450
|
"personal fleet doctor --local --json"
|
|
309
451
|
],
|
|
310
|
-
"mutation": false
|
|
452
|
+
"mutation": false,
|
|
453
|
+
"help": "personal fleet doctor --local --help",
|
|
454
|
+
"execution": {
|
|
455
|
+
"default": "read-only",
|
|
456
|
+
"authorization": "none"
|
|
457
|
+
}
|
|
311
458
|
},
|
|
312
459
|
{
|
|
313
460
|
"id": "fleet nodes",
|
|
@@ -327,7 +474,12 @@
|
|
|
327
474
|
"examples": [
|
|
328
475
|
"personal fleet nodes --json"
|
|
329
476
|
],
|
|
330
|
-
"mutation": false
|
|
477
|
+
"mutation": false,
|
|
478
|
+
"help": "personal fleet nodes --help",
|
|
479
|
+
"execution": {
|
|
480
|
+
"default": "read-only",
|
|
481
|
+
"authorization": "none"
|
|
482
|
+
}
|
|
331
483
|
},
|
|
332
484
|
{
|
|
333
485
|
"id": "fleet add",
|
|
@@ -347,7 +499,13 @@
|
|
|
347
499
|
"examples": [
|
|
348
500
|
"personal fleet add --json"
|
|
349
501
|
],
|
|
350
|
-
"mutation": true
|
|
502
|
+
"mutation": true,
|
|
503
|
+
"help": "personal fleet add --help",
|
|
504
|
+
"execution": {
|
|
505
|
+
"default": "preview",
|
|
506
|
+
"authorization": "--apply --yes",
|
|
507
|
+
"note": "Supply required options shown by command --help. A preview does not apply changes."
|
|
508
|
+
}
|
|
351
509
|
},
|
|
352
510
|
{
|
|
353
511
|
"id": "fleet validate",
|
|
@@ -366,7 +524,12 @@
|
|
|
366
524
|
"examples": [
|
|
367
525
|
"personal fleet validate --json"
|
|
368
526
|
],
|
|
369
|
-
"mutation": false
|
|
527
|
+
"mutation": false,
|
|
528
|
+
"help": "personal fleet validate --help",
|
|
529
|
+
"execution": {
|
|
530
|
+
"default": "read-only",
|
|
531
|
+
"authorization": "none"
|
|
532
|
+
}
|
|
370
533
|
},
|
|
371
534
|
{
|
|
372
535
|
"id": "fleet onboard",
|
|
@@ -387,7 +550,12 @@
|
|
|
387
550
|
"examples": [
|
|
388
551
|
"personal fleet onboard --json"
|
|
389
552
|
],
|
|
390
|
-
"mutation": false
|
|
553
|
+
"mutation": false,
|
|
554
|
+
"help": "personal fleet onboard --help",
|
|
555
|
+
"execution": {
|
|
556
|
+
"default": "read-only",
|
|
557
|
+
"authorization": "none"
|
|
558
|
+
}
|
|
391
559
|
},
|
|
392
560
|
{
|
|
393
561
|
"id": "fleet sync",
|
|
@@ -408,7 +576,13 @@
|
|
|
408
576
|
"examples": [
|
|
409
577
|
"personal fleet sync --json"
|
|
410
578
|
],
|
|
411
|
-
"mutation": true
|
|
579
|
+
"mutation": true,
|
|
580
|
+
"help": "personal fleet sync --help",
|
|
581
|
+
"execution": {
|
|
582
|
+
"default": "preview",
|
|
583
|
+
"authorization": "--apply --yes",
|
|
584
|
+
"note": "Supply required options shown by command --help. A preview does not apply changes."
|
|
585
|
+
}
|
|
412
586
|
},
|
|
413
587
|
{
|
|
414
588
|
"id": "fleet init",
|
|
@@ -427,7 +601,13 @@
|
|
|
427
601
|
"examples": [
|
|
428
602
|
"personal fleet init --json"
|
|
429
603
|
],
|
|
430
|
-
"mutation": true
|
|
604
|
+
"mutation": true,
|
|
605
|
+
"help": "personal fleet init --help",
|
|
606
|
+
"execution": {
|
|
607
|
+
"default": "preview",
|
|
608
|
+
"authorization": "--apply --yes",
|
|
609
|
+
"note": "Supply required options shown by command --help. A preview does not apply changes."
|
|
610
|
+
}
|
|
431
611
|
},
|
|
432
612
|
{
|
|
433
613
|
"id": "fleet schema",
|
|
@@ -446,12 +626,17 @@
|
|
|
446
626
|
"examples": [
|
|
447
627
|
"personal fleet schema --json"
|
|
448
628
|
],
|
|
449
|
-
"mutation": false
|
|
629
|
+
"mutation": false,
|
|
630
|
+
"help": "personal fleet schema --help",
|
|
631
|
+
"execution": {
|
|
632
|
+
"default": "read-only",
|
|
633
|
+
"authorization": "none"
|
|
634
|
+
}
|
|
450
635
|
},
|
|
451
636
|
{
|
|
452
637
|
"id": "commands",
|
|
453
638
|
"title": "Explore the command catalog",
|
|
454
|
-
"description": "Search command
|
|
639
|
+
"description": "Search the command index (also available as personal index); emit help links and mutation semantics.",
|
|
455
640
|
"group": "Reference",
|
|
456
641
|
"keywords": [
|
|
457
642
|
"search",
|
|
@@ -464,9 +649,15 @@
|
|
|
464
649
|
"commands"
|
|
465
650
|
],
|
|
466
651
|
"examples": [
|
|
652
|
+
"personal index --json",
|
|
467
653
|
"personal commands --json --search ssh"
|
|
468
654
|
],
|
|
469
|
-
"mutation": false
|
|
655
|
+
"mutation": false,
|
|
656
|
+
"help": "personal commands --help",
|
|
657
|
+
"execution": {
|
|
658
|
+
"default": "read-only",
|
|
659
|
+
"authorization": "none"
|
|
660
|
+
}
|
|
470
661
|
},
|
|
471
662
|
{
|
|
472
663
|
"id": "agent",
|
|
@@ -485,7 +676,12 @@
|
|
|
485
676
|
"examples": [
|
|
486
677
|
"personal agent --json"
|
|
487
678
|
],
|
|
488
|
-
"mutation": false
|
|
679
|
+
"mutation": false,
|
|
680
|
+
"help": "personal agent --help",
|
|
681
|
+
"execution": {
|
|
682
|
+
"default": "read-only",
|
|
683
|
+
"authorization": "none"
|
|
684
|
+
}
|
|
489
685
|
}
|
|
490
686
|
]
|
|
491
687
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvouga/personal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Interactive, agent-friendly developer fleet command center",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"registry": "https://registry.npmjs.org/"
|
|
38
38
|
},
|
|
39
39
|
"personalCli": {
|
|
40
|
-
"contentHash": "
|
|
41
|
-
"sourceCommit": "
|
|
40
|
+
"contentHash": "8554fd297a91a36218ea3e0736bd25fbd8043a68ceab25e8b32b452a8353c755",
|
|
41
|
+
"sourceCommit": "30085cc27cc2fd79da4b7b21ae1df5dffd30c525",
|
|
42
42
|
"dirty": false
|
|
43
43
|
}
|
|
44
44
|
}
|
package/probe.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages/fleet/src/configure.ts
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { mkdir, writeFile, rename } from "node:fs/promises";
|
|
3
|
+
import { mkdir, writeFile, rename, readFile } from "node:fs/promises";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
|
|
6
6
|
// packages/fleet/src/errors.ts
|
|
@@ -33,7 +33,15 @@ function absolutePath(value) {
|
|
|
33
33
|
function parseNode(value) {
|
|
34
34
|
if (!record(value))
|
|
35
35
|
throw new FleetUsageError("Each node must be an object.");
|
|
36
|
-
fields(value, [
|
|
36
|
+
fields(value, [
|
|
37
|
+
"id",
|
|
38
|
+
"ssh",
|
|
39
|
+
"workspace",
|
|
40
|
+
"runtime",
|
|
41
|
+
"required",
|
|
42
|
+
"tailscaleId",
|
|
43
|
+
"bootstrapViaSsh"
|
|
44
|
+
]);
|
|
37
45
|
const names = nodeNames(value);
|
|
38
46
|
if (!absolutePath(value["workspace"]))
|
|
39
47
|
throw new FleetUsageError("workspace must be an absolute POSIX path.");
|
|
@@ -42,11 +50,22 @@ function parseNode(value) {
|
|
|
42
50
|
}
|
|
43
51
|
return {
|
|
44
52
|
...names,
|
|
53
|
+
...enrollmentOptions(value),
|
|
45
54
|
workspace: value["workspace"],
|
|
46
55
|
...typeof value["runtime"] === "string" ? { runtime: value["runtime"] } : {},
|
|
47
56
|
required: requiredCapabilities(value["required"])
|
|
48
57
|
};
|
|
49
58
|
}
|
|
59
|
+
function enrollmentOptions(value) {
|
|
60
|
+
if (value["bootstrapViaSsh"] !== undefined && typeof value["bootstrapViaSsh"] !== "boolean")
|
|
61
|
+
throw new FleetUsageError("bootstrapViaSsh must explicitly authorize an existing trusted SSH bootstrap route.");
|
|
62
|
+
if (value["tailscaleId"] !== undefined && (typeof value["tailscaleId"] !== "string" || !/^[a-zA-Z0-9:_-]{1,128}$/.test(value["tailscaleId"])))
|
|
63
|
+
throw new FleetUsageError("tailscaleId must be the explicitly enrolled stable Tailscale device ID.");
|
|
64
|
+
return {
|
|
65
|
+
...value["bootstrapViaSsh"] === true ? { bootstrapViaSsh: true } : {},
|
|
66
|
+
...typeof value["tailscaleId"] === "string" ? { tailscaleId: value["tailscaleId"] } : {}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
50
69
|
function nodeNames(value) {
|
|
51
70
|
if (typeof value["id"] !== "string" || !/^[a-z][a-z0-9-]{0,62}$/.test(value["id"])) {
|
|
52
71
|
throw new FleetUsageError("Node IDs must be lowercase names (1–63 characters).");
|
|
@@ -88,16 +107,33 @@ function parseConfig(value) {
|
|
|
88
107
|
function validConfiguration(value) {
|
|
89
108
|
return record(value) && typeof value["revision"] === "string" && /^[a-f0-9]{40}$/.test(value["revision"]) && ["codex", "claude", "opencode", "gemini"].includes(String(value["tool"])) && ["direct", "proxy"].includes(String(value["profile"])) && typeof value["node"] === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value["node"]);
|
|
90
109
|
}
|
|
91
|
-
async function configureWorkspace(root, request, run
|
|
110
|
+
async function configureWorkspace(root, request, run, options = {
|
|
111
|
+
additionalTools: [],
|
|
112
|
+
bootstrappedDeployment: false
|
|
113
|
+
}) {
|
|
114
|
+
if (options.additionalTools.some((tool) => !validConfiguration({ ...request, tool })))
|
|
115
|
+
return failure("Invalid additional profile.");
|
|
92
116
|
if (!validConfiguration(request))
|
|
93
117
|
return failure("Invalid reviewed revision or configuration selection.");
|
|
94
|
-
const directory =
|
|
118
|
+
const directory = deploymentDirectory(root, request.revision, options.bootstrappedDeployment);
|
|
95
119
|
const head = await run(["git", "rev-parse", "--verify", `${request.revision}^{commit}`], { cwd: root });
|
|
96
120
|
if (!head.ok || head.stdout.trim() !== request.revision)
|
|
97
121
|
return failure("Reviewed commit is unavailable locally. Fetch it explicitly and retry.");
|
|
98
122
|
const prepared = await prepareRevision(root, directory, request.revision, run);
|
|
99
123
|
if (prepared.length)
|
|
100
124
|
return prepared;
|
|
125
|
+
if (!options.bootstrappedDeployment) {
|
|
126
|
+
const setup = await setupRevision(directory, run);
|
|
127
|
+
if (setup.length)
|
|
128
|
+
return setup;
|
|
129
|
+
}
|
|
130
|
+
const checks = [];
|
|
131
|
+
for (const tool of [request.tool, ...options.additionalTools]) {
|
|
132
|
+
checks.push(...await applyProfile(root, directory, { ...request, tool }, run));
|
|
133
|
+
}
|
|
134
|
+
return checks;
|
|
135
|
+
}
|
|
136
|
+
async function setupRevision(directory, run) {
|
|
101
137
|
const setup = await run([
|
|
102
138
|
"mise",
|
|
103
139
|
"exec",
|
|
@@ -110,6 +146,9 @@ async function configureWorkspace(root, request, run) {
|
|
|
110
146
|
], { cwd: directory, timeoutMs: 1200000 });
|
|
111
147
|
if (!setup.ok)
|
|
112
148
|
return failure("Existing setup owner failed. Partial tool/dependency installation is possible; no configuration receipt was advanced.");
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
async function applyProfile(root, directory, request, run) {
|
|
113
152
|
const applied = await applyOwner(directory, request, run);
|
|
114
153
|
if (!applied.ok)
|
|
115
154
|
return failure("personal workstation configuration failed. Inspect preview on this node; no rollout receipt was advanced.");
|
|
@@ -137,6 +176,8 @@ function failure(summary) {
|
|
|
137
176
|
return [{ id: "configuration:apply", status: "fail", summary }];
|
|
138
177
|
}
|
|
139
178
|
async function prepareRevision(root, directory, revision, run) {
|
|
179
|
+
if (root === directory)
|
|
180
|
+
return verifyRevision(directory, revision, run);
|
|
140
181
|
await mkdir(join(root, "node_modules", ".fleet-revisions"), {
|
|
141
182
|
recursive: true
|
|
142
183
|
});
|
|
@@ -147,6 +188,10 @@ async function prepareRevision(root, directory, revision, run) {
|
|
|
147
188
|
return failure("Cannot create pinned deployment worktree. Existing data was preserved.");
|
|
148
189
|
deployed = await run(["git", "rev-parse", "HEAD"], { cwd: directory });
|
|
149
190
|
}
|
|
191
|
+
return verifyRevision(directory, revision, run);
|
|
192
|
+
}
|
|
193
|
+
async function verifyRevision(directory, revision, run) {
|
|
194
|
+
const deployed = await run(["git", "rev-parse", "HEAD"], { cwd: directory });
|
|
150
195
|
const clean = await run(["git", "status", "--porcelain"], { cwd: directory });
|
|
151
196
|
if (!deployed.ok || deployed.stdout.trim() !== revision || !clean.ok || clean.stdout.trim())
|
|
152
197
|
return failure("Pinned deployment checkout differs or is dirty. Inspect it; fleet never resets it.");
|
|
@@ -161,9 +206,17 @@ async function recordConfiguration(root, request, receipt) {
|
|
|
161
206
|
const receipts = join(root, "node_modules", ".fleet-configuration");
|
|
162
207
|
await mkdir(receipts, { recursive: true, mode: 448 });
|
|
163
208
|
const path = join(receipts, `${request.node}-${request.tool}-${request.profile}.json`);
|
|
209
|
+
const contents = JSON.stringify({ version: 1, ...request, fingerprint: receipt }) + `
|
|
210
|
+
`;
|
|
211
|
+
try {
|
|
212
|
+
if (await readFile(path, "utf8") === contents)
|
|
213
|
+
return;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error.code !== "ENOENT")
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
164
218
|
const temp = `${path}.${randomUUID()}.tmp`;
|
|
165
|
-
await writeFile(temp,
|
|
166
|
-
`, { flag: "wx", mode: 384 });
|
|
219
|
+
await writeFile(temp, contents, { flag: "wx", mode: 384 });
|
|
167
220
|
await rename(temp, path);
|
|
168
221
|
}
|
|
169
222
|
async function applyOwner(directory, request, run) {
|
|
@@ -188,14 +241,17 @@ async function applyOwner(directory, request, run) {
|
|
|
188
241
|
request.profile
|
|
189
242
|
], { cwd: directory, timeoutMs: 120000 });
|
|
190
243
|
}
|
|
244
|
+
function deploymentDirectory(root, revision, bootstrapped) {
|
|
245
|
+
return bootstrapped ? root : join(root, "node_modules", ".fleet-revisions", revision);
|
|
246
|
+
}
|
|
191
247
|
|
|
192
248
|
// packages/fleet/src/fingerprint.ts
|
|
193
|
-
import { readFile as
|
|
249
|
+
import { readFile as readFile3, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
194
250
|
import { join as join3 } from "node:path";
|
|
195
251
|
|
|
196
252
|
// packages/fleet/src/input-hashes.ts
|
|
197
253
|
import { createHash } from "node:crypto";
|
|
198
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
254
|
+
import { readFile as readFile2, readdir } from "node:fs/promises";
|
|
199
255
|
import { join as join2 } from "node:path";
|
|
200
256
|
var INPUT_FILES = [
|
|
201
257
|
"package.json",
|
|
@@ -210,7 +266,7 @@ var FINGERPRINT_FILES = [
|
|
|
210
266
|
];
|
|
211
267
|
async function fileHash(path) {
|
|
212
268
|
try {
|
|
213
|
-
return digest(await
|
|
269
|
+
return digest(await readFile2(path));
|
|
214
270
|
} catch {
|
|
215
271
|
return null;
|
|
216
272
|
}
|
|
@@ -276,7 +332,7 @@ async function dependencyCheck(root, current) {
|
|
|
276
332
|
action: "Review fleet sync, then explicitly apply a frozen toolchain/dependency sync."
|
|
277
333
|
};
|
|
278
334
|
try {
|
|
279
|
-
const receipt = JSON.parse(await
|
|
335
|
+
const receipt = JSON.parse(await readFile3(join3(root, RECEIPT), "utf8"));
|
|
280
336
|
const matches = record(receipt) && receipt["version"] === 1 && JSON.stringify(receipt["files"]) === JSON.stringify(current.files);
|
|
281
337
|
return {
|
|
282
338
|
id: "dependencies",
|
|
@@ -626,7 +682,7 @@ function runCommand(command, options = {}) {
|
|
|
626
682
|
GIT_TERMINAL_PROMPT: "0",
|
|
627
683
|
CI: "1"
|
|
628
684
|
},
|
|
629
|
-
stdio:
|
|
685
|
+
stdio: childStdio(options.preserveOperationLock),
|
|
630
686
|
windowsHide: true,
|
|
631
687
|
detached: process.platform !== "win32"
|
|
632
688
|
});
|
|
@@ -680,6 +736,20 @@ function runCommand(command, options = {}) {
|
|
|
680
736
|
child.stdin.end(options.input);
|
|
681
737
|
});
|
|
682
738
|
}
|
|
739
|
+
function childStdio(preserveLock) {
|
|
740
|
+
return preserveLock ? [
|
|
741
|
+
"pipe",
|
|
742
|
+
"pipe",
|
|
743
|
+
"pipe",
|
|
744
|
+
"ignore",
|
|
745
|
+
"ignore",
|
|
746
|
+
"ignore",
|
|
747
|
+
"ignore",
|
|
748
|
+
"ignore",
|
|
749
|
+
"ignore",
|
|
750
|
+
9
|
|
751
|
+
] : ["pipe", "pipe", "pipe"];
|
|
752
|
+
}
|
|
683
753
|
|
|
684
754
|
// packages/fleet/src/sync.ts
|
|
685
755
|
import { unlink } from "node:fs/promises";
|
|
@@ -943,6 +1013,8 @@ var STATUSES = ["pass", "warn", "fail", "skip"];
|
|
|
943
1013
|
function validCheck(value) {
|
|
944
1014
|
if (!record(value) || typeof value["id"] !== "string" || !/^[a-z0-9:._-]{1,100}$/.test(value["id"]) || !STATUSES.includes(String(value["status"])) || typeof value["summary"] !== "string" || value["summary"].length > 2000)
|
|
945
1015
|
return false;
|
|
1016
|
+
if (!validDuration(value["phaseDurationMs"]))
|
|
1017
|
+
return false;
|
|
946
1018
|
const remedy = value["remedy"];
|
|
947
1019
|
return remedy === undefined || validRemedy(remedy);
|
|
948
1020
|
}
|
|
@@ -965,6 +1037,9 @@ function validFingerprint(value) {
|
|
|
965
1037
|
function validHash(value) {
|
|
966
1038
|
return value === null || typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
967
1039
|
}
|
|
1040
|
+
function validDuration(value) {
|
|
1041
|
+
return value === undefined || typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 86400000;
|
|
1042
|
+
}
|
|
968
1043
|
|
|
969
1044
|
// packages/fleet/src/remote-entry.ts
|
|
970
1045
|
async function main() {
|