agent-purge 0.2.10 → 0.2.12
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/CONTRIBUTING.md +64 -0
- package/LICENSE +21 -21
- package/README.md +169 -138
- package/backends/index.mjs +204 -244
- package/bench/bench-armor.mjs +844 -832
- package/bench/capture-hot.mjs +58 -0
- package/bench/capture-provider.mjs +66 -0
- package/bench/capture-report.mjs +15 -0
- package/bench/capture-server-hot.mjs +55 -0
- package/bench/capture-system.mjs +36 -0
- package/bench/judge-armor.mjs +119 -119
- package/bin/agent-purge.mjs +956 -780
- package/docs/LEGAL.md +27 -0
- package/docs/architecture.md +39 -0
- package/docs/compatibility.md +78 -0
- package/docs/images/console-desktop.png +0 -0
- package/docs/images/console-mobile.png +0 -0
- package/docs/releasing.md +87 -0
- package/docs/usage.md +158 -0
- package/lib/activation.mjs +38 -0
- package/lib/apply.mjs +227 -0
- package/lib/armed.mjs +38 -38
- package/lib/armor.mjs +225 -222
- package/lib/caps.mjs +182 -173
- package/lib/capture.mjs +324 -0
- package/lib/clean.mjs +124 -0
- package/lib/control.mjs +60 -0
- package/lib/detect.mjs +74 -70
- package/lib/doctor.mjs +7 -7
- package/lib/dsh.mjs +495 -453
- package/lib/environment.mjs +36 -0
- package/lib/evidence.mjs +41 -0
- package/lib/extension.mjs +140 -0
- package/lib/footprints.mjs +59 -0
- package/lib/home.mjs +47 -47
- package/lib/launch-conflicts.mjs +24 -0
- package/lib/launch.mjs +57 -0
- package/lib/opencode.mjs +62 -0
- package/lib/paths.mjs +25 -0
- package/lib/persist.mjs +439 -280
- package/lib/persona-import.mjs +351 -0
- package/lib/personas.mjs +17 -7
- package/lib/private-server.mjs +54 -0
- package/lib/project.mjs +134 -0
- package/lib/restore.mjs +131 -114
- package/lib/rewrite.mjs +124 -0
- package/lib/root.mjs +37 -0
- package/lib/shim.mjs +203 -160
- package/lib/slashes.mjs +227 -628
- package/lib/spawn.mjs +182 -126
- package/lib/state.mjs +85 -137
- package/lib/templates/personas/neutral.md +30 -30
- package/lib/update.mjs +137 -0
- package/lib/verification.mjs +23 -0
- package/lib/window.mjs +58 -0
- package/lib/wrappers.mjs +49 -0
- package/magpie-plugin/README.md +44 -0
- package/matrix.json +325 -341
- package/package.json +30 -4
- package/web/app.js +590 -0
- package/web/assets/agent-purge.ico +0 -0
- package/web/assets/claude.ico +0 -0
- package/web/assets/codebuddy.png +0 -0
- package/web/assets/codex.webp +0 -0
- package/web/assets/dsh.ico +0 -0
- package/web/assets/grok.ico +0 -0
- package/web/assets/hermes.ico +0 -0
- package/web/assets/omp.png +0 -0
- package/web/assets/opencode.ico +0 -0
- package/web/assets/opencode2.ico +0 -0
- package/web/assets/pi.png +0 -0
- package/web/assets/zcode.png +0 -0
- package/web/model.mjs +49 -0
- package/web/server.mjs +330 -191
- package/web/styles.css +1072 -0
- package/web/ui.html +76 -1163
package/bin/agent-purge.mjs
CHANGED
|
@@ -1,780 +1,956 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// agent-purge — 一个入口,把各家 harness 的系统提示词替换收进来。
|
|
3
|
-
// 能力与实测数字见 matrix.json。
|
|
4
|
-
//
|
|
5
|
-
// 命令只有两类,别再分第三类:
|
|
6
|
-
// 选东西(写 state.json) use / mode / install / arm / slash
|
|
7
|
-
// 用东西(跑起来) <harness> / 裸敲已 arm 的命令名 / web / ls / status
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { installCrashHandlers, recover } from "../lib/restore.mjs";
|
|
15
|
-
import { capsFor } from "../lib/caps.mjs";
|
|
16
|
-
import { MODES, armedOf, loadState,
|
|
17
|
-
import { ensureDataHome } from "../lib/home.mjs";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case "
|
|
75
|
-
|
|
76
|
-
break;
|
|
77
|
-
case "
|
|
78
|
-
await
|
|
79
|
-
break;
|
|
80
|
-
case "
|
|
81
|
-
|
|
82
|
-
break;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
console.log(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (!
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
]
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async function
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
};
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
if (
|
|
672
|
-
console.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// agent-purge — 一个入口,把各家 harness 的系统提示词替换收进来。
|
|
3
|
+
// 能力与实测数字见 matrix.json。
|
|
4
|
+
//
|
|
5
|
+
// 命令只有两类,别再分第三类:
|
|
6
|
+
// 选东西(写 state.json) use / mode / install / arm / slash
|
|
7
|
+
// 用东西(跑起来) <harness> / 裸敲已 arm 的命令名 / web / ls / status
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
import { backends, oneshots } from "../backends/index.mjs";
|
|
11
|
+
import { resolveBin, launchConfig, prepareCommand, spawnPrepared } from "../lib/spawn.mjs";
|
|
12
|
+
import { launchEnvironment } from "../lib/environment.mjs";
|
|
13
|
+
import { createLaunch } from "../lib/launch.mjs";
|
|
14
|
+
import { installCrashHandlers, recover, swapFile } from "../lib/restore.mjs";
|
|
15
|
+
import { capsFor } from "../lib/caps.mjs";
|
|
16
|
+
import { MODES, armedOf, loadState, resolveFor } from "../lib/state.mjs";
|
|
17
|
+
import { dataHome, ensureDataHome } from "../lib/home.mjs";
|
|
18
|
+
import { applyConfiguration, changeChannel, configureHarness, disableHarness } from "../lib/apply.mjs";
|
|
19
|
+
import { ROOT, isCompiled, readResource } from "../lib/root.mjs";
|
|
20
|
+
// 静态导入:bun 单文件产物里,被动态 import 过的模块会被拆成独立 chunk,命名空间就空了
|
|
21
|
+
import { serveWeb } from "../web/server.mjs";
|
|
22
|
+
|
|
23
|
+
// 数据(state.json + personas/)放用户目录,npm 升级不会连人设一起清掉
|
|
24
|
+
// 编译态没有「包目录」可搬旧数据,传 null 跳过 legacy 迁移
|
|
25
|
+
const DATA = dataHome();
|
|
26
|
+
const matrix = (() => {
|
|
27
|
+
try { return JSON.parse(readResource("matrix.json").toString("utf8")); }
|
|
28
|
+
catch (error) { throw new Error(`无法加载 matrix.json:${error.message}`, { cause: error }); }
|
|
29
|
+
})();
|
|
30
|
+
// 这些家只有“整段替换/追加”两个启动口,没有宿主提示词可读:replace 实际按 strict 执行。
|
|
31
|
+
const STRICT_REPLACE = new Set(["codex", "claude", "grok", "codebuddy", "hermes"]);
|
|
32
|
+
|
|
33
|
+
const argv = process.argv.slice(2);
|
|
34
|
+
const separator = argv.indexOf("--");
|
|
35
|
+
const controlArgs = separator < 0 ? argv : argv.slice(0, separator);
|
|
36
|
+
if (controlArgs.includes("--help") || controlArgs.includes("-h")) { help(); process.exit(0); }
|
|
37
|
+
if (argv[0] === "self-uninstall" && !argv.includes("--yes") && !argv.includes("--dry-run")) {
|
|
38
|
+
console.error("self-uninstall 会永久删除用户人设、设置和恢复资料。请先备份;仅撤销接管并保留数据请用 agent-purge clean。\n先预览:agent-purge self-uninstall --dry-run\n确认删除:agent-purge self-uninstall --yes");
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
ensureDataHome(isCompiled() ? null : ROOT);
|
|
42
|
+
|
|
43
|
+
installCrashHandlers();
|
|
44
|
+
const recovered = recover(); // 只在真实命令入口恢复;--help 不读写 journal
|
|
45
|
+
if (recovered > 0) console.log(`[restore] 回滚了 ${recovered} 个上次残留的配置文件`);
|
|
46
|
+
|
|
47
|
+
// 编译态与脚本态使用同一个 Web 入口;0 表示系统分配空闲端口,供隔离 smoke 使用。
|
|
48
|
+
if (argv[0] === "--serve") {
|
|
49
|
+
const port = argv[1] === undefined ? 7788 : Number(argv[1]);
|
|
50
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error("端口必须为 0..65535 的整数");
|
|
51
|
+
serveWeb(port);
|
|
52
|
+
await new Promise(() => {});
|
|
53
|
+
}
|
|
54
|
+
const flag = (name, fallback) => {
|
|
55
|
+
// 支持 --mode=append 这种写法(以前只认空格分隔,等号形式会被静默忽略)
|
|
56
|
+
const inline = controlArgs.find((a) => a.startsWith(name + "="));
|
|
57
|
+
if (inline !== undefined) return inline.slice(name.length + 1);
|
|
58
|
+
const i = controlArgs.indexOf(name);
|
|
59
|
+
return i === -1 ? fallback : controlArgs[i + 1];
|
|
60
|
+
};
|
|
61
|
+
let cmd = argv[0];
|
|
62
|
+
|
|
63
|
+
// 便携版 exe 双击无参数启动时,默认直接打开原生桌面窗口应用
|
|
64
|
+
if (!cmd && isCompiled()) {
|
|
65
|
+
await webCmd();
|
|
66
|
+
// 挂住事件循环
|
|
67
|
+
await new Promise(() => {});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!cmd || cmd.startsWith("-")) cmd = await defaultHarness();
|
|
71
|
+
|
|
72
|
+
// ── 选东西 ────────────────────────────────────────────────────────────────
|
|
73
|
+
switch (cmd) {
|
|
74
|
+
case "use":
|
|
75
|
+
await useCmd();
|
|
76
|
+
break;
|
|
77
|
+
case "sync":
|
|
78
|
+
await syncCmd();
|
|
79
|
+
break;
|
|
80
|
+
case "mode":
|
|
81
|
+
modeCmd();
|
|
82
|
+
break;
|
|
83
|
+
case "pause":
|
|
84
|
+
case "resume":
|
|
85
|
+
printApply(applyConfiguration(DATA, { enabled: cmd === "resume" }));
|
|
86
|
+
break;
|
|
87
|
+
case "install":
|
|
88
|
+
case "uninstall":
|
|
89
|
+
await installCmd(cmd, argv.slice(1));
|
|
90
|
+
break;
|
|
91
|
+
case "arm":
|
|
92
|
+
case "disarm":
|
|
93
|
+
case "register":
|
|
94
|
+
case "注册":
|
|
95
|
+
await armCmd(cmd === "arm" || cmd === "register" || cmd === "注册" ? "arm" : "disarm", argv.slice(1));
|
|
96
|
+
break;
|
|
97
|
+
case "slash":
|
|
98
|
+
await slashCmd(argv.slice(1));
|
|
99
|
+
break;
|
|
100
|
+
case "project":
|
|
101
|
+
await projectCmd(argv.slice(1));
|
|
102
|
+
break;
|
|
103
|
+
case "status":
|
|
104
|
+
await statusCmd();
|
|
105
|
+
break;
|
|
106
|
+
case "ls":
|
|
107
|
+
await lsCmd();
|
|
108
|
+
break;
|
|
109
|
+
case "pull":
|
|
110
|
+
await personaImportCmd(argv.slice(1));
|
|
111
|
+
break;
|
|
112
|
+
case "persona":
|
|
113
|
+
await personaImportCmd(argv.slice(1));
|
|
114
|
+
break;
|
|
115
|
+
case "doctor":
|
|
116
|
+
case "detect": {
|
|
117
|
+
const { doctor } = await import("../lib/doctor.mjs");
|
|
118
|
+
await doctor(argv);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "matrix":
|
|
122
|
+
printMatrix();
|
|
123
|
+
break;
|
|
124
|
+
case "armor":
|
|
125
|
+
await armorCli(argv.slice(1));
|
|
126
|
+
break;
|
|
127
|
+
case "update":
|
|
128
|
+
case "upgrade": {
|
|
129
|
+
const { checkForUpdate, downloadFile, replaceSelfExecutable, canReplaceSelf, RELEASES_PAGE } = await import("../lib/update.mjs");
|
|
130
|
+
const doDownload = argv.includes("--download") || argv.includes("-d") || argv.includes("--yes") || argv.includes("-y");
|
|
131
|
+
console.log("正在检查 GitHub Release 最新版本...");
|
|
132
|
+
const info = await checkForUpdate({ force: true });
|
|
133
|
+
if (!info.ok) {
|
|
134
|
+
console.log(`✗ 检查更新失败:${info.error}`);
|
|
135
|
+
console.log(` 可手动访问发布页查看:${RELEASES_PAGE}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
console.log(`当前运行版本:v${info.current}`);
|
|
140
|
+
if (info.published === false) {
|
|
141
|
+
console.log("尚无公开 Release;这不表示 npm 已是最新版。npm 安装可运行 npm outdated -g agent-purge。");
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
console.log(`最新发布版本:v${info.latest} (${info.tagName || info.latest})`);
|
|
145
|
+
if (!info.hasUpdate) {
|
|
146
|
+
console.log("\n✓ 当前已是最新版本!");
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
console.log(`\n⚡ 发现新版本:v${info.latest}!`);
|
|
150
|
+
if (info.body) {
|
|
151
|
+
console.log(`\n【更新说明】\n${info.body.trim().slice(0, 500)}${info.body.length > 500 ? "..." : ""}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const isExe = canReplaceSelf();
|
|
155
|
+
const targetExeAsset = info.assets?.find((a) => a.name.toLowerCase() === "agent-purge.exe");
|
|
156
|
+
const targetSetupAsset = info.assets?.find((a) => a.name.toLowerCase().endsWith("-setup.exe"));
|
|
157
|
+
|
|
158
|
+
if (isExe && targetExeAsset) {
|
|
159
|
+
console.log(`\n检测到当前为独立 exe 运行环境,可直接原地升级。`);
|
|
160
|
+
if (doDownload) {
|
|
161
|
+
const { tmpdir } = await import("node:os");
|
|
162
|
+
const tempTarget = join(tmpdir(), `agent-purge-new-${Date.now()}.exe`);
|
|
163
|
+
console.log(`正在下载 ${targetExeAsset.name} (${(targetExeAsset.size / 1024 / 1024).toFixed(1)} MB)...`);
|
|
164
|
+
await downloadFile(targetExeAsset.downloadUrl, tempTarget, (rec, tot) => {
|
|
165
|
+
if (tot > 0) process.stdout.write(`\r下载进度: ${((rec / tot) * 100).toFixed(1)}%`);
|
|
166
|
+
}, { expectedSize: targetExeAsset.size, digest: targetExeAsset.digest });
|
|
167
|
+
console.log("\n下载完成,正在安全替换当前可执行文件...");
|
|
168
|
+
try { replaceSelfExecutable(tempTarget); } finally { const { rmSync } = await import("node:fs"); rmSync(tempTarget, { force: true }); }
|
|
169
|
+
console.log(`✓ 升级成功!当前 exe 已就绪为 v${info.latest}。重启 agent-purge 即可生效。`);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
console.log(` · 自动下载并就地升级当前 exe:\n agent-purge update --download`);
|
|
173
|
+
} else if (process.platform === "win32" && targetSetupAsset && doDownload) {
|
|
174
|
+
const { tmpdir } = await import("node:os");
|
|
175
|
+
const tempSetup = join(tmpdir(), targetSetupAsset.name);
|
|
176
|
+
console.log(`正在下载安装包 ${targetSetupAsset.name}...`);
|
|
177
|
+
await downloadFile(targetSetupAsset.downloadUrl, tempSetup, undefined, { expectedSize: targetSetupAsset.size, digest: targetSetupAsset.digest });
|
|
178
|
+
console.log(`\n下载完成,正在启动安装程序:${tempSetup}`);
|
|
179
|
+
const { spawn } = await import("node:child_process");
|
|
180
|
+
spawn(tempSetup, [], { detached: true, stdio: "ignore" }).unref();
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log("\n【下载与升级方式】");
|
|
185
|
+
console.log(` 1. 访问 GitHub Release 页面直接下载:\n ${info.htmlUrl}`);
|
|
186
|
+
if (info.assets?.length) {
|
|
187
|
+
console.log(" 2. Release 包含的安装/便携产物:");
|
|
188
|
+
for (const a of info.assets) {
|
|
189
|
+
const sizeMb = (a.size / 1024 / 1024).toFixed(1);
|
|
190
|
+
console.log(` · ${a.name} (${sizeMb} MB) → ${a.downloadUrl}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
console.log(" 3. 若通过 npm 全局安装,可直接执行:\n npm install -g agent-purge@latest");
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case "web":
|
|
197
|
+
await webCmd();
|
|
198
|
+
break;
|
|
199
|
+
case "clean":
|
|
200
|
+
case "self-uninstall": {
|
|
201
|
+
const { clean } = await import("../lib/clean.mjs");
|
|
202
|
+
const dryRun = argv.includes("--dry-run");
|
|
203
|
+
if (dryRun) console.log("dry-run:只报会做什么,一条也不写\n");
|
|
204
|
+
const r = clean(DATA, { all: cmd === "self-uninstall", dryRun });
|
|
205
|
+
for (const s of r.steps) {
|
|
206
|
+
const detail = typeof s.detail === "string" ? s.detail : JSON.stringify(s.detail);
|
|
207
|
+
console.log(`${s.ok ? "✓" : "✗"} ${s.what}${s.detail === undefined ? "" : ` — ${detail}`}`);
|
|
208
|
+
}
|
|
209
|
+
if (r.leftovers.length) console.log(`\n还剩:\n${r.leftovers.map((l) => ` · ${l}`).join("\n")}`);
|
|
210
|
+
console.log(r.ok && !r.leftovers.length ? "\n干净了:没有残留" : "\n有残留,见上");
|
|
211
|
+
if (!r.ok) process.exitCode = 1;
|
|
212
|
+
if (cmd === "self-uninstall") console.log("\n代码本身用包管理器卸:npm uninstall -g agent-purge");
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case "verify": {
|
|
216
|
+
const { verifyHarnesses, cleanupRoots } = await import("../lib/capture.mjs");
|
|
217
|
+
const wanted = argv.slice(1).filter((a) => !a.startsWith("-"));
|
|
218
|
+
const hs = wanted.length ? wanted : Object.keys(matrix.harnesses ?? {});
|
|
219
|
+
const mode = loadState(DATA).state?.default?.mode ?? "replace";
|
|
220
|
+
const report = await verifyHarnesses(hs, { root: DATA, mode });
|
|
221
|
+
for (const r of report.harnesses) {
|
|
222
|
+
if (r.status === "captured") {
|
|
223
|
+
const m = r.modes[0];
|
|
224
|
+
const where = ["sys", "user"].filter((k) => m?.injected?.[k]).join("+") || "-";
|
|
225
|
+
console.log(`✓ ${r.harness} 实测:命中 ${where} · ${m?.chars ?? 0} 字 · ${m?.requests?.length ?? 0} 个请求`);
|
|
226
|
+
} else if (r.status === "missing") console.log(`· ${r.harness} 未安装,跳过`);
|
|
227
|
+
else console.log(`✗ ${r.harness} ${r.status}${r.reason ? ` — ${r.reason}` : ""}`);
|
|
228
|
+
}
|
|
229
|
+
console.log(`\n证据:${join(DATA, "verification", "latest.json")}`);
|
|
230
|
+
cleanupRoots();
|
|
231
|
+
if (report.harnesses.some((r) => ["mismatch", "error"].includes(r.status))) process.exitCode = 1;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
await launch(cmd);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── 用东西 ────────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
/** 无参:state.default 说跑谁就跑谁;没有默认就跑到 Web 去选 */
|
|
241
|
+
async function defaultHarness() {
|
|
242
|
+
const { state } = loadState(DATA);
|
|
243
|
+
const harness = state.default?.harness;
|
|
244
|
+
if (!harness) {
|
|
245
|
+
console.log("还没有默认 harness。两种办法:\n agent-purge web 在浏览器里选,再点“设为默认执行器”\n agent-purge <harness> 直接点名跑(如 agent-purge codex)");
|
|
246
|
+
process.exit(0);
|
|
247
|
+
}
|
|
248
|
+
const eff = resolveFor(state, harness);
|
|
249
|
+
const mark = (from) => (from === "override" ? "(单独指定)" : "(跟随全局)");
|
|
250
|
+
console.log(`[default] ${harness} · ${eff.mode}${mark(eff.modeFrom)} · persona=${eff.persona ?? "-"}${mark(eff.personaFrom)}(state.json)`);
|
|
251
|
+
return harness;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function launch(name) {
|
|
255
|
+
const spec = matrix.harnesses[name];
|
|
256
|
+
if (!spec) die(`未知 harness: ${name}(可用: ${Object.keys(matrix.harnesses).join(", ")};子命令见 --help)`);
|
|
257
|
+
if (spec.class === "unsupported") die(`${name} 做不到:${spec.reason}`);
|
|
258
|
+
|
|
259
|
+
const { state } = loadState(DATA);
|
|
260
|
+
// 由 state.json 决定注入方式;显式 --mode 永远压过它
|
|
261
|
+
const explicitMode = flag("--mode", null);
|
|
262
|
+
if (explicitMode !== null && !MODES.includes(explicitMode)) die(`--mode 只能是 ${MODES.join(" / ")}`);
|
|
263
|
+
const config = launchConfig(state, name, { mode: explicitMode, persona: flag("--persona", null) === "active" ? state.persona : flag("--persona", null) });
|
|
264
|
+
const mode = config.mode;
|
|
265
|
+
|
|
266
|
+
// 裸敲 pi 的持久位(写 state.json,扩展下一会话读它)
|
|
267
|
+
const autoFlag = name === "pi" || name === "omp" ? flag("--auto", null) : null;
|
|
268
|
+
if (autoFlag !== null) {
|
|
269
|
+
if (autoFlag !== "on" && autoFlag !== "off") die("--auto 只能是 on / off");
|
|
270
|
+
const { setPiAuto, setOmpAuto } = await import("../lib/state.mjs");
|
|
271
|
+
if (name === "pi") setPiAuto(DATA, autoFlag === "on");
|
|
272
|
+
else setOmpAuto(DATA, autoFlag === "on");
|
|
273
|
+
console.log(`[auto] 裸敲 ${name} ${autoFlag === "on" ? "会" : "不会"}自动注入(已写入 state.json)`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// pi 的人设落在哪一层:sys(默认,真系统提示词)或 user(塞进用户消息,给忽略 system 的模型)
|
|
277
|
+
const layerFlag = name === "pi" ? flag("--layer", null) : null;
|
|
278
|
+
if (layerFlag !== null) {
|
|
279
|
+
if (layerFlag !== "sys" && layerFlag !== "user") die("--layer 只能是 sys / user");
|
|
280
|
+
const { setPiUserLayer } = await import("../lib/state.mjs");
|
|
281
|
+
setPiUserLayer(DATA, layerFlag === "user");
|
|
282
|
+
console.log(
|
|
283
|
+
layerFlag === "user"
|
|
284
|
+
? "[layer] 人设额外塞进用户消息(给忽略 system 的模型用;代价是每条消息多一段)"
|
|
285
|
+
: "[layer] 人设走 system 层(默认)",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 实测策略:该 harness 的哪一层真的能注入(数据来自 matrix.json armor 段)
|
|
290
|
+
const { plan } = await import("../lib/armor.mjs");
|
|
291
|
+
const strategy = plan(matrix, name);
|
|
292
|
+
if (!strategy.error) {
|
|
293
|
+
if (strategy.requiresSeed) console.log(`[策略] ${name}:裸注入 sys 层实测 ${strategy.bare.sys}%,带人设词种子 ${strategy.persona.sys}% —— 需要 seed 层`);
|
|
294
|
+
const noSys = strategy.blocked.find((b) => b.layer === "sys");
|
|
295
|
+
if (noSys && mode !== "off") console.log(`[策略] ${name}:sys 层不可用(${noSys.reason}),本次只能靠 user 层`);
|
|
296
|
+
}
|
|
297
|
+
if (mode === "replace" && STRICT_REPLACE.has(name)) {
|
|
298
|
+
console.log(`[mode] ${name} 没有宿主提示词读取口:replace 实际按 strict 执行;要保留宿主请用 --mode append`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 通道全废的家(闭源桌面 / 提示词在服务端)说清楚:这次只是原样启动,没注入任何东西
|
|
302
|
+
{
|
|
303
|
+
const c = capsFor(name);
|
|
304
|
+
if (!c.shim.ok && !c.install.ok && !c.slash.ok && mode !== "off") {
|
|
305
|
+
const why = [c.shim, c.install, c.slash].map((x) => x.why).filter(Boolean)[0];
|
|
306
|
+
console.log(`[提示] ${name} 注入不进去,本次是原样启动:${why}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let picked = null, personaPath = null, personaText = "";
|
|
311
|
+
if (mode !== "off") {
|
|
312
|
+
const { createStore } = await import("../lib/personas.mjs");
|
|
313
|
+
const store = createStore(DATA);
|
|
314
|
+
const id = config.persona ?? store.activeId();
|
|
315
|
+
picked = store.read(id);
|
|
316
|
+
if (!picked) die(`没有 persona: ${id}`);
|
|
317
|
+
personaPath = picked.id === store.activeId() ? store.refreshActive() : store.materialize(picked.id);
|
|
318
|
+
personaText = picked.body.trim();
|
|
319
|
+
}
|
|
320
|
+
const cwd = resolve(flag("--cwd", process.cwd()));
|
|
321
|
+
|
|
322
|
+
// --ask:一次性问答,与 Web UI 共用 oneshots 规格;不给才启动交互式 harness
|
|
323
|
+
// 透传:有 -- 就只收 -- 后面的;没有就把“不是 agent-purge 自己的 flag”全交给 harness
|
|
324
|
+
// (这样裸命令 `pi -p "…"` / `codex exec "…"` 经 shim 进来也能把参数带到,--ask 也一样)
|
|
325
|
+
const dd = argv.indexOf("--");
|
|
326
|
+
let passthrough = [];
|
|
327
|
+
if (dd !== -1) {
|
|
328
|
+
passthrough = argv.slice(dd + 1);
|
|
329
|
+
} else {
|
|
330
|
+
const ownWithValue = new Set(["--ask", "--mode", "--persona", "--cwd", "--auto", "--layer"]);
|
|
331
|
+
const tail = argv.slice(1);
|
|
332
|
+
for (let i = 0; i < tail.length; i++) {
|
|
333
|
+
const t = tail[i];
|
|
334
|
+
if (ownWithValue.has(t)) {
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (t === "--dry-run" || ownWithValue.has(t.split("=")[0])) continue;
|
|
339
|
+
passthrough.push(t);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const askEq = controlArgs.find((a) => a.startsWith("--ask="));
|
|
344
|
+
const askIdx = controlArgs.indexOf("--ask");
|
|
345
|
+
const askPrompt = askEq !== undefined ? askEq.slice(6) : askIdx !== -1 ? controlArgs[askIdx + 1] : null;
|
|
346
|
+
let built;
|
|
347
|
+
if (askPrompt !== null) {
|
|
348
|
+
const raw = askPrompt;
|
|
349
|
+
if (!raw || raw.startsWith("--")) die('--ask 需要一个 prompt,例如: agent-purge codex --ask "解释这段代码"');
|
|
350
|
+
const prompt = raw.trim();
|
|
351
|
+
const one = oneshots[name];
|
|
352
|
+
if (!one) die(`${name} 没有一次性运行模式(可用: ${Object.keys(oneshots).join(", ")})`);
|
|
353
|
+
built = one({ prompt, personaPath, personaText, mode, cwd, root: DATA });
|
|
354
|
+
if (passthrough.length) built.args = [...built.args, ...passthrough];
|
|
355
|
+
} else {
|
|
356
|
+
built = backends[name]?.({ personaPath, personaText, mode, args: passthrough, cwd, root: DATA, reason: spec.reason }) ?? {
|
|
357
|
+
unsupported: "该 harness 还没有后端实现",
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (built.manual) {
|
|
362
|
+
console.log(`${name}: 需要人工介入`);
|
|
363
|
+
built.manual.forEach((line) => console.log(" - " + line));
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
if (built.unsupported) die(`${name} 做不到:${built.unsupported}`);
|
|
367
|
+
|
|
368
|
+
if (built.note) console.log(built.note);
|
|
369
|
+
const dryRun = controlArgs.includes("--dry-run");
|
|
370
|
+
const ompProfile = name === "omp" ? (flag("--profile", null) ?? process.env.OMP_PROFILE ?? null) : null;
|
|
371
|
+
const ompNoExtensions = name === "omp" && argv.includes("--no-extensions");
|
|
372
|
+
const useExtension = mode !== "off" && (name === "pi" || (name === "omp" && askPrompt === null && !ompProfile && !ompNoExtensions));
|
|
373
|
+
let extFile = null;
|
|
374
|
+
if (useExtension) {
|
|
375
|
+
if (name === "omp" && ["--system-prompt", "--append-system-prompt"].includes(built.args[0])) built.args.splice(0, 2);
|
|
376
|
+
const { resolveSlash } = await import("../lib/slashes.mjs");
|
|
377
|
+
extFile = resolveSlash(name, DATA)?.file;
|
|
378
|
+
if (!extFile) die(`${name}: 没有扩展目录`);
|
|
379
|
+
// 临时 -e 只供本次进程;不会悄悄选择常驻 /purge 或覆盖用户已有扩展。
|
|
380
|
+
if (!state.armed?.[name]?.includes("slash")) {
|
|
381
|
+
extFile = join(DATA, "runtime", "extension-" + name + ".mjs");
|
|
382
|
+
built.args = ["-e", extFile, ...built.args];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (mode === "off") {
|
|
386
|
+
const { launchConflicts } = await import("../lib/launch-conflicts.mjs");
|
|
387
|
+
const conflicts = launchConflicts(DATA, name, cwd);
|
|
388
|
+
if (conflicts.length) die("本次 off 与仍在磁盘上的常驻/项目内容冲突:\n" + conflicts.join("\n") + "\n请先 agent-purge pause 撤下登记内容,再重试。全局选择保持不变。");
|
|
389
|
+
console.log(state.default?.mode === "off" ? "[off] 总电源关着:本次不安装、不 materialize、不注入;恢复请用 agent-purge resume" : "[off] 本次透传;全局选择保持不变");
|
|
390
|
+
}
|
|
391
|
+
console.log(`[cmd] ${[built.cmd, ...previewArgs(built.args)].join(" ")}`);
|
|
392
|
+
if (extFile) console.log(`[ext] ${extFile}`);
|
|
393
|
+
if (dryRun) return;
|
|
394
|
+
if (!resolveBin(built.cmd)) die(`${name}: 找不到可执行文件 ${built.cmd}`);
|
|
395
|
+
if (name === "opencode" && built.args.some(v => v === "--attach" || v.startsWith("--attach="))) die("一次性启用使用独立宿主;--attach 指向已有服务,请使用该服务已登记的常驻通道");
|
|
396
|
+
const needsLease = useExtension || built.runtime || ["pi", "omp", "dsh", "opencode", "opencode2"].includes(name);
|
|
397
|
+
const lease = needsLease ? createLaunch(DATA, name, { persona: picked?.id, mode, writer: useExtension ? "extension" : built.runtime ? "temporary" : "cli" }) : null;
|
|
398
|
+
let undoExtension = null, cleaned = false, child = null, server = null;
|
|
399
|
+
const cleanup = () => {
|
|
400
|
+
if (cleaned) return true;
|
|
401
|
+
try {
|
|
402
|
+
lease?.close();
|
|
403
|
+
if (server) { server.close(); server = null; }
|
|
404
|
+
built.cleanup?.();
|
|
405
|
+
if (undoExtension) { undoExtension(); undoExtension = null; }
|
|
406
|
+
cleaned = true; return true;
|
|
407
|
+
} catch (error) { console.error("[cleanup] " + error.message + ";恢复凭据已保留,可重试"); return false; }
|
|
408
|
+
};
|
|
409
|
+
process.once("exit", cleanup);
|
|
410
|
+
try {
|
|
411
|
+
const env = { ...launchEnvironment(process.env), ...built.env, ...lease?.env };
|
|
412
|
+
if (useExtension) {
|
|
413
|
+
const { extensionSource } = await import("../lib/extension.mjs");
|
|
414
|
+
const { mkdirSync } = await import("node:fs");
|
|
415
|
+
mkdirSync(join(extFile, ".."), { recursive: true });
|
|
416
|
+
undoExtension = swapFile(extFile, extensionSource(DATA, name));
|
|
417
|
+
}
|
|
418
|
+
built.setup?.();
|
|
419
|
+
if (built.privateServer) {
|
|
420
|
+
const { startPrivateServer } = await import("../lib/private-server.mjs");
|
|
421
|
+
server = await startPrivateServer(built.cmd, { env, cwd, lease });
|
|
422
|
+
built.args = server.clientArgs(built.args);
|
|
423
|
+
}
|
|
424
|
+
const stdio = askPrompt !== null ? ["ignore", "inherit", "inherit"] : "inherit";
|
|
425
|
+
const descriptor = prepareCommand(built.cmd, built.args, { cwd, stdio, env });
|
|
426
|
+
if (lease && !server && !descriptor.hostDirect) throw new Error("临时启用尚未验证这个启动器的宿主归属,请指向原生 exe 或标准 Node 入口");
|
|
427
|
+
const startedAt = Date.now(); child = spawnPrepared(descriptor);
|
|
428
|
+
if (lease && !server && child.pid) lease.publish(child.pid, startedAt);
|
|
429
|
+
let finished = false;
|
|
430
|
+
const finish = code => { if (finished) return; finished = true; process.exitCode = cleanup() ? code ?? 1 : 1; };
|
|
431
|
+
child.once("error", error => { console.error(`${name}: ${error.message}`); finish(1); });
|
|
432
|
+
child.once("close", finish);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
if (child) child.kill(); cleanup(); process.exitCode = 1; console.error(`${name}: ${error.message}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── 选东西的实现 ───────────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
function printApply(r) {
|
|
442
|
+
console.log(`persona ${r.persona ?? "(未设置)"} · mode ${r.mode} · revision ${r.revision}`);
|
|
443
|
+
for (const row of r.results) {
|
|
444
|
+
// 被例外覆盖的那家写进去的不是全局值,直接标出来,否则看不出来它是怎么生效的
|
|
445
|
+
const odd = row.mode && row.mode !== r.mode ? ` [${row.persona ?? "-"} · ${row.mode}]` : "";
|
|
446
|
+
console.log(`${row.status === "failed" ? "✗" : "✓"} ${row.harness}:${row.channel}${odd}${row.channel === "project" && row.file ? ` → ${row.file}` : ""} — ${row.error ?? row.message}`);
|
|
447
|
+
}
|
|
448
|
+
if (!r.ok) process.exitCode = 1;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** 只列跟全局不一样的几家;没例外就什么都不打印 */
|
|
452
|
+
function printOverrides(state) {
|
|
453
|
+
for (const [h, o] of Object.entries(state.overrides ?? {})) {
|
|
454
|
+
const bits = [o.persona ? `人设=${o.persona}` : "", o.mode ? `模式=${o.mode}` : ""].filter(Boolean);
|
|
455
|
+
console.log(` ${h}: ${bits.join(" ")}(其余跟随全局;--for ${h} follow 恢复)`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function useCmd() {
|
|
460
|
+
const id = argv[1];
|
|
461
|
+
const forHarness = flag("--for", null);
|
|
462
|
+
if (forHarness) {
|
|
463
|
+
if (!id) die("用法:agent-purge use <persona|follow> --for <harness>");
|
|
464
|
+
const follow = id === "follow";
|
|
465
|
+
const { createStore } = await import("../lib/personas.mjs");
|
|
466
|
+
const store = createStore(DATA);
|
|
467
|
+
if (!follow && !store.read(id)) die(`没有人设: ${id}(可用: ${store.list().map((p) => p.id).join(", ")})`);
|
|
468
|
+
const report = configureHarness(DATA, forHarness, { persona: follow ? null : id });
|
|
469
|
+
const eff = resolveFor(loadState(DATA).state, forHarness);
|
|
470
|
+
console.log(`${forHarness} 人设 → ${eff.persona ?? "(未设置)"}(${eff.personaFrom === "override" ? "单独指定" : "跟随全局"})`);
|
|
471
|
+
printApply(report);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (!id) {
|
|
475
|
+
console.log(`当前 persona: ${loadState(DATA).state.persona ?? "(未设置)"}`);
|
|
476
|
+
printOverrides(loadState(DATA).state);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
printApply(applyConfiguration(DATA, { persona: id }));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function syncCmd() {
|
|
483
|
+
printApply(applyConfiguration(DATA, { onlyFailed: argv.includes("--failed") }));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function modeCmd() {
|
|
487
|
+
const mode = argv[1];
|
|
488
|
+
const forHarness = flag("--for", null);
|
|
489
|
+
if (forHarness) {
|
|
490
|
+
if (!mode) die("用法:agent-purge mode <strict|replace|append|off|follow> --for <harness>");
|
|
491
|
+
const follow = mode === "follow";
|
|
492
|
+
if (!follow && !MODES.includes(mode)) die(`未知模式: ${mode}(可用: ${MODES.join("/")}/follow)`);
|
|
493
|
+
const report = configureHarness(DATA, forHarness, { mode: follow ? null : mode });
|
|
494
|
+
const eff = resolveFor(loadState(DATA).state, forHarness);
|
|
495
|
+
console.log(`${forHarness} 模式 → ${eff.mode}(${eff.modeFrom === "override" ? "单独指定" : "跟随全局"})`);
|
|
496
|
+
printApply(report);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (!mode) {
|
|
500
|
+
console.log(`当前模式: ${loadState(DATA).state.default?.mode ?? "replace"}\nstrict=只发人设;replace=移除宿主身份句;append=宿主后追加;off=暂停并撤下静态内容`);
|
|
501
|
+
printOverrides(loadState(DATA).state);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
printApply(applyConfiguration(DATA, { mode }));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function installCmd(verb, rest) {
|
|
508
|
+
const { targets } = await import("../lib/persist.mjs");
|
|
509
|
+
const named = rest.filter(value => !value.startsWith("-"));
|
|
510
|
+
const ids = named.length ? named : Object.keys(targets);
|
|
511
|
+
for (const harness of ids) {
|
|
512
|
+
const r = changeChannel(DATA, { harness, channel: "install", enabled: verb === "install", force: rest.includes("--force") });
|
|
513
|
+
console.log(`${r.ok ? "✓" : "✗"} ${verb} ${harness} — ${r.error ?? r.action ?? r.message}${r.file ? " → " + r.file : ""}`);
|
|
514
|
+
if (r.profiles) console.log(` 实际 profile:${r.profiles.join(", ") || "没有已安装的插件"}`);
|
|
515
|
+
for (const file of r.preserved ?? []) console.log(` 保留用户修改/旧版文件:${file}`);
|
|
516
|
+
if (verb === "uninstall" && harness === "dsh") console.log(" uninstall 仅撤常驻通道;撤销包含 /purge 在内的全部入口请用 agent-purge disarm dsh。");
|
|
517
|
+
if (!r.ok) process.exitCode = 1;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function armCmd(verb, rest) {
|
|
522
|
+
const named = rest.filter(value => !value.startsWith("-"));
|
|
523
|
+
const ids = named.length ? named : Object.keys(matrix.harnesses).filter(h => matrix.harnesses[h].class !== "unsupported");
|
|
524
|
+
for (const h of ids) if (!matrix.harnesses[h]) die(`未知执行器: ${h}`);
|
|
525
|
+
if (rest.includes("--all")) {
|
|
526
|
+
console.log("总电源:" + (verb === "arm" ? "恢复" : "暂停"));
|
|
527
|
+
printApply(applyConfiguration(DATA, { enabled: verb === "arm" }));
|
|
528
|
+
}
|
|
529
|
+
const channels = ["install", "shim", "slash", "project"].filter(c => rest.includes("--" + c));
|
|
530
|
+
let shim = false;
|
|
531
|
+
for (const harness of ids) {
|
|
532
|
+
if (verb === "disarm" && !channels.length) {
|
|
533
|
+
const r = disableHarness(DATA, harness);
|
|
534
|
+
console.log(`${r.ok ? "✓" : "✗"} disarm ${harness}`);
|
|
535
|
+
for (const row of r.results) if (!row.ok) console.log(" " + row.channel + ": " + row.error);
|
|
536
|
+
if (!r.ok) process.exitCode = 1;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
for (const channel of channels.length ? channels : ["install", "shim", "slash"]) {
|
|
540
|
+
if (verb === "arm" && !capsFor(harness)[channel]?.ok) { console.log(`- ${harness}:${channel} — ${capsFor(harness)[channel]?.why}`); continue; }
|
|
541
|
+
const r = changeChannel(DATA, { harness, channel, enabled: verb === "arm", force: rest.includes("--force") });
|
|
542
|
+
console.log(`${r.ok ? "✓" : "✗"} ${verb} ${harness}:${channel} — ${r.error ?? r.action ?? r.message}`);
|
|
543
|
+
if (!r.ok) process.exitCode = 1;
|
|
544
|
+
if (r.ok && verb === "arm" && channel === "shim") shim = true;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (shim) { const { ensureUserPath } = await import("../lib/shim.mjs"); const r = ensureUserPath(); console.log("[path] " + r.how); }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function slashCmd(rest) {
|
|
551
|
+
const { slashStatus } = await import("../lib/slashes.mjs");
|
|
552
|
+
const verb = rest[0];
|
|
553
|
+
if (!verb || verb === "status") {
|
|
554
|
+
for (const row of slashStatus(DATA)) console.log(`${row.installed ? "✓" : "·"} ${row.id}: ${row.note} → ${row.file}`);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const enabled = verb !== "off";
|
|
558
|
+
const named = (enabled ? rest : rest.slice(1)).filter(v => !v.startsWith("-"));
|
|
559
|
+
const ids = named.length ? named : slashStatus(DATA).filter(s => enabled ? s.offered : s.installed).map(s => s.id);
|
|
560
|
+
for (const harness of ids) {
|
|
561
|
+
const r = changeChannel(DATA, { harness, channel: "slash", enabled });
|
|
562
|
+
console.log(`${r.ok ? "✓" : "✗"} /purge ${harness} — ${r.error ?? r.action ?? r.message}`);
|
|
563
|
+
if (!r.ok) process.exitCode = 1;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function projectCmd(rest) {
|
|
568
|
+
const { projectStatus, projectTargets, gitHint } = await import("../lib/project.mjs");
|
|
569
|
+
const dir = resolve(flag("--dir", process.cwd()));
|
|
570
|
+
const verb = ["on", "off", "status"].includes(rest[0]) ? rest[0] : "status";
|
|
571
|
+
const named = [];
|
|
572
|
+
for (let i = rest[0] === verb ? 1 : 0; i < rest.length; i++) {
|
|
573
|
+
if (rest[i] === "--dir") { i++; continue; }
|
|
574
|
+
if (!rest[i].startsWith("-")) named.push(rest[i]);
|
|
575
|
+
}
|
|
576
|
+
const capable = Object.keys(projectTargets);
|
|
577
|
+
if (verb === "status") {
|
|
578
|
+
const rows = projectStatus(dir, DATA);
|
|
579
|
+
console.log(`项目目录:${dir}`);
|
|
580
|
+
for (const r of rows) console.log(`${r.on || r.selected ? "✓" : " "} ${r.id.padEnd(9)} ${r.file}${r.selected && !r.on ? "(已选,尚未写入)" : r.hasBlock && !r.on ? "(有块但不是我们记的)" : ""}`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (verb !== "on" && verb !== "off") die(`用法:agent-purge project on|off [harness…] [--dir X]`);
|
|
584
|
+
const ids = named.length
|
|
585
|
+
? named
|
|
586
|
+
: verb === "on"
|
|
587
|
+
? Object.keys(loadState(DATA).state.armed ?? {}).filter((h) => capable.includes(h))
|
|
588
|
+
: projectStatus(dir, DATA).filter((r) => r.on || r.selected).map((r) => r.id);
|
|
589
|
+
if (!ids.length) {
|
|
590
|
+
console.log(verb === "on" ? `没有已接管的项目级 harness(可用: ${capable.join(", ")});点名写:agent-purge project on pi` : "这个目录里没有托管块");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
for (const h of ids) {
|
|
594
|
+
if (!capable.includes(h)) {
|
|
595
|
+
console.log(`- ${h}: 没有项目级规则文件`);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const r = changeChannel(DATA, { harness: h, channel: "project", enabled: verb === "on", dir });
|
|
599
|
+
if (!r.ok) {
|
|
600
|
+
console.log(`✗ ${h}: ${r.error ?? "写入失败"}`);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
console.log(`${verb === "on" ? "✓" : "-"} ${h} ${r.file}${r.action ? `(${r.action})` : ""}`);
|
|
604
|
+
if (verb === "on") r.hint = gitHint(h, dir);
|
|
605
|
+
if (r.hint) console.log(` 注意:${r.hint}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function statusCmd() {
|
|
610
|
+
const { migrated } = loadState(DATA);
|
|
611
|
+
migrated.forEach((m) => console.log(`[migrate] ${m}`));
|
|
612
|
+
// armed 是缓存,磁盘才是真相:先把假记录摘掉,再报数
|
|
613
|
+
const { reconcileArmed } = await import("../lib/armed.mjs");
|
|
614
|
+
const fixed = await reconcileArmed(DATA);
|
|
615
|
+
if (fixed.length) console.log(`[对账] armed 纠正:${fixed.join(", ")}`);
|
|
616
|
+
const { state } = loadState(DATA);
|
|
617
|
+
return Promise.all([import("../lib/shim.mjs"), import("../lib/slashes.mjs")]).then(([shimMod, slashMod]) => {
|
|
618
|
+
const shims = shimMod.shimStatus();
|
|
619
|
+
const slashes = slashMod.slashStatus(DATA);
|
|
620
|
+
|
|
621
|
+
console.log(`persona ${state.persona ?? "(未设置)"}`);
|
|
622
|
+
console.log(`default ${state.default ? `${state.default.harness ?? "(未选默认)"} · ${state.default.mode}` : "(未设置)"} 裸敲 agent-purge 用它`);
|
|
623
|
+
console.log(`state ${join(DATA, "state.json")}`);
|
|
624
|
+
console.log("");
|
|
625
|
+
console.log("harness shim slash install /purge层 常驻落点");
|
|
626
|
+
for (const [h] of Object.entries(matrix.harnesses)) {
|
|
627
|
+
const armed = armedOf(state, h);
|
|
628
|
+
const sh = shims.find((s) => s.id === h);
|
|
629
|
+
const sl = slashes.find((s) => s.id === h);
|
|
630
|
+
const cap = capsFor(h, { bin: sh?.target ?? null });
|
|
631
|
+
const mark = (on) => (on ? " ✓ " : " · ");
|
|
632
|
+
const slashCol = sl && sl.offered ? (sl.layer === "sys" ? "sys(热)" : "user") : "—";
|
|
633
|
+
// install 列不写“注册没注册”,写“能不能装”:装了是 ✓,能装没装是「可用」,没落点才是 —
|
|
634
|
+
const installCol = armed.includes("install") ? " ✓ " : cap.install.ok ? "可用" : " — ";
|
|
635
|
+
const landing = cap.install.ok ? `${cap.install.layer || "?"}${cap.install.verified === false ? "(未实测)" : ""}` : (cap.install.why || "");
|
|
636
|
+
console.log(
|
|
637
|
+
`${h.padEnd(11)}${mark(sh?.installed).padEnd(7)}${mark(sl?.installed).padEnd(7)}${installCol.padEnd(10)}${slashCol.padEnd(10)}${landing}`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
const dir = shimMod.shimDir();
|
|
641
|
+
const ps = shimMod.pathState();
|
|
642
|
+
const where = !ps.present ? "不在 PATH" : ps.first ? "已在 PATH 最前" : "在 PATH 但不最前(真身可能先被命中)";
|
|
643
|
+
console.log(`\nshim 目录 ${dir}(${where})`);
|
|
644
|
+
if (!ps.first) console.log(`修好它:agent-purge arm <h> --shim(会自动修正 PATH)`);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function personaImportCmd(args = []) {
|
|
649
|
+
let sub = args[0];
|
|
650
|
+
let source = args[1];
|
|
651
|
+
if (sub !== "pull" && sub !== "import") {
|
|
652
|
+
source = sub;
|
|
653
|
+
sub = "pull";
|
|
654
|
+
}
|
|
655
|
+
if (!source && sub === "pull") {
|
|
656
|
+
source = "ZiChuanLan/some-magic";
|
|
657
|
+
}
|
|
658
|
+
if (!source) {
|
|
659
|
+
die("用法:agent-purge pull [owner/repo 或 本地目录] 或 agent-purge persona import <dir>");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const noOverwrite = argv.includes("--no-overwrite");
|
|
663
|
+
const { importPersonas } = await import("../lib/persona-import.mjs");
|
|
664
|
+
console.log(`正在从 ${source} 拉取人设...`);
|
|
665
|
+
try {
|
|
666
|
+
const res = await importPersonas(source, DATA, { overwrite: !noOverwrite });
|
|
667
|
+
console.log(`\n拉取完成!共导入 ${res.total} 个人设:`);
|
|
668
|
+
for (const item of res.imported) {
|
|
669
|
+
console.log(` ✓ ${item.id.padEnd(14)} ${item.name} (${item.chars} 字, ${item.action === "created" ? "新增" : "更新"})`);
|
|
670
|
+
}
|
|
671
|
+
if (res.skipped?.length) {
|
|
672
|
+
console.log(`\n跳过 ${res.skipped.length} 个文件:`);
|
|
673
|
+
for (const item of res.skipped) {
|
|
674
|
+
console.log(` - ${item.file || item.id}: ${item.reason}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (res.errors?.length) {
|
|
678
|
+
console.log(`\n错误 ${res.errors.length} 项:`);
|
|
679
|
+
for (const item of res.errors) {
|
|
680
|
+
console.log(` ✗ ${item.file}: ${item.error}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
console.log(`\n查看已安装人设:agent-purge ls`);
|
|
684
|
+
console.log(`切换当前人设:agent-purge use <id>`);
|
|
685
|
+
} catch (e) {
|
|
686
|
+
die(`拉取失败:${e.message}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async function lsCmd() {
|
|
691
|
+
const { createStore } = await import("../lib/personas.mjs");
|
|
692
|
+
const store = createStore(DATA);
|
|
693
|
+
const { state } = loadState(DATA);
|
|
694
|
+
const fit = store.resolve({ harness: state.default?.harness, model: null });
|
|
695
|
+
for (const p of store.list()) {
|
|
696
|
+
const mark = p.id === store.activeId() ? "*" : " ";
|
|
697
|
+
const best = fit?.persona?.id === p.id && fit?.matched ? " ← 最匹配当前 harness" : "";
|
|
698
|
+
const desc = p.meta?.description ?? p.meta?.desc ?? "";
|
|
699
|
+
console.log(`${mark} ${p.id.padEnd(14)} ${String(p.meta?.name ?? "").padEnd(12)} ${desc.slice(0, 44)}${best}`);
|
|
700
|
+
}
|
|
701
|
+
console.log(`\n* = active(agent-purge use <id> 切换)`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function webCmd() {
|
|
705
|
+
const port = Number(flag("--port", "7788"));
|
|
706
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) die("端口必须为 0..65535 的整数");
|
|
707
|
+
const noOpen = argv.includes("--no-open");
|
|
708
|
+
const url = `http://127.0.0.1:${port}`;
|
|
709
|
+
const { openAppWindow } = await import("../lib/window.mjs");
|
|
710
|
+
|
|
711
|
+
// --detach:脱离当前终端后台存活,日志丢弃
|
|
712
|
+
if (argv.includes("--detach")) {
|
|
713
|
+
if (port === 0) die("--detach 需要明确端口;随机端口请用前台 web --port 0 --no-open");
|
|
714
|
+
// 源码态 execPath 是 node,得连脚本路径一起给;编译态 execPath 就是 exe 本体
|
|
715
|
+
const serve = isCompiled() ? ["--serve", String(port)] : [join(ROOT, "web", "server.mjs"), "--serve", String(port)];
|
|
716
|
+
const child = spawn(process.execPath, serve, {
|
|
717
|
+
detached: true,
|
|
718
|
+
stdio: "ignore",
|
|
719
|
+
shell: false,
|
|
720
|
+
windowsHide: true,
|
|
721
|
+
env: { ...process.env, AGENT_PURGE_HOME: DATA },
|
|
722
|
+
});
|
|
723
|
+
child.unref();
|
|
724
|
+
console.log(`agent-purge 后台启动请求已发送 → ${url}(pid ${child.pid};请确认服务可连接)`);
|
|
725
|
+
if (!noOpen) {
|
|
726
|
+
const res = openAppWindow(url);
|
|
727
|
+
if (res.mode === "app") console.log(`已启动独立桌面窗口(${res.browser})`);
|
|
728
|
+
}
|
|
729
|
+
process.exit(0);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const server = serveWeb(port);
|
|
733
|
+
if (!noOpen) {
|
|
734
|
+
server.once("listening", () => {
|
|
735
|
+
const res = openAppWindow(`http://127.0.0.1:${server.address().port}`);
|
|
736
|
+
if (res.mode === "app") console.log(`✓ 已作为独立桌面窗口打开(无标签/地址栏,原生客户端体验)`);
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return server;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ── 其余 ──────────────────────────────────────────────────────────────────
|
|
743
|
+
|
|
744
|
+
async function armorCli(rest) {
|
|
745
|
+
const { renderMatrix, renderPlan, plans, plan, preflight, runBench, runJudge } = await import("../lib/armor.mjs");
|
|
746
|
+
const color = !argv.includes("--no-color") && Boolean(process.stdout.isTTY);
|
|
747
|
+
const sub = rest[0]?.startsWith("-") ? "matrix" : rest[0] ?? "matrix";
|
|
748
|
+
const opts = (name, fallback) => {
|
|
749
|
+
const i = rest.indexOf(name);
|
|
750
|
+
return i === -1 ? fallback : rest[i + 1];
|
|
751
|
+
};
|
|
752
|
+
const flags = rest.filter((x) => x.startsWith("--"));
|
|
753
|
+
// 位置参数 ≠ 非 - 开头的 token:`--base https://x` 的值不是 harness 名
|
|
754
|
+
const VALUE_FLAGS = new Set(["--base", "--key", "--model", "--task", "--layers", "--only", "--repeat", "--concurrency", "--timeout", "--out", "--dir", "--probe"]);
|
|
755
|
+
const positionals = (from = 1) => {
|
|
756
|
+
const out = [];
|
|
757
|
+
for (let i = from; i < rest.length; i++) {
|
|
758
|
+
const a = rest[i];
|
|
759
|
+
if (a.startsWith("--")) {
|
|
760
|
+
if (VALUE_FLAGS.has(a)) i++;
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (!a.startsWith("-")) out.push(a);
|
|
764
|
+
}
|
|
765
|
+
return out;
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
if (sub === "matrix") return renderMatrix(matrix, { color });
|
|
769
|
+
|
|
770
|
+
if (sub === "plan") {
|
|
771
|
+
const ids = positionals();
|
|
772
|
+
const list = ids.length ? ids.map((id) => plan(matrix, id)) : plans(matrix);
|
|
773
|
+
list.forEach((p, i) => {
|
|
774
|
+
if (i) console.log();
|
|
775
|
+
renderPlan(p, { color });
|
|
776
|
+
});
|
|
777
|
+
if (!ids.length) console.log(`\n${"─".repeat(48)}\n批量跑:agent-purge armor run --task crack-bare --layers off,sys,user --only ${list.map((p) => p.id).join(",")}`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (sub === "preflight" || sub === "check") {
|
|
782
|
+
const ids = positionals();
|
|
783
|
+
const env = { base: opts("--base", process.env.MG_BASE), key: opts("--key", process.env.MG_KEY), model: opts("--model", process.env.MG_MODEL) };
|
|
784
|
+
let bad = 0;
|
|
785
|
+
for (const id of ids.length ? ids : ["pi"]) {
|
|
786
|
+
const r = preflight(matrix, id, env);
|
|
787
|
+
console.log(`${r.ok ? "✓" : "✗"} ${id}`);
|
|
788
|
+
r.checks.forEach((c) => console.log(` ${c.ok ? "ok " : "MISS"} ${c.name}${c.detail ? ` — ${c.detail}` : ""}`));
|
|
789
|
+
if (!r.ok) bad++;
|
|
790
|
+
}
|
|
791
|
+
if (bad) process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (sub === "run") {
|
|
796
|
+
if (flags.includes("--help")) {
|
|
797
|
+
console.log(`agent-purge armor run --task crack-bare --layers off,sys,user --only pi,codex --repeat 2 --concurrency 3
|
|
798
|
+
--base <url> --key <key> --model <id> 上游(默认读 MG_BASE / MG_KEY / MG_MODEL)
|
|
799
|
+
--task crack-bare|crack-persona 问法(裸问法 / 带人设词种子)
|
|
800
|
+
--layers off,sys,user|off,sys
|
|
801
|
+
--out <dir>`);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const env = {
|
|
805
|
+
MG_BASE: opts("--base", process.env.MG_BASE),
|
|
806
|
+
MG_KEY: opts("--key", process.env.MG_KEY),
|
|
807
|
+
MG_MODEL: opts("--model", process.env.MG_MODEL),
|
|
808
|
+
};
|
|
809
|
+
if (!env.MG_BASE || !env.MG_KEY) {
|
|
810
|
+
console.error("缺少上游:--base / --key(或环境变量 MG_BASE / MG_KEY)。例:\n agent-purge armor run --base https://api.deepseek.com/v1 --key $env:MG_KEY --model deepseek-flash --task crack-bare --layers off,sys --only pi");
|
|
811
|
+
process.exit(2);
|
|
812
|
+
}
|
|
813
|
+
const pass = [];
|
|
814
|
+
for (let i = 0; i < rest.length; i++) {
|
|
815
|
+
const a = rest[i];
|
|
816
|
+
if (["--base", "--key", "--model"].includes(a)) { i++; continue; }
|
|
817
|
+
pass.push(a);
|
|
818
|
+
}
|
|
819
|
+
const { code } = runBench(pass, env);
|
|
820
|
+
process.exitCode = code;
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (sub === "judge") {
|
|
825
|
+
const pass = [];
|
|
826
|
+
for (let i = 0; i < rest.length; i++) {
|
|
827
|
+
const a = rest[i];
|
|
828
|
+
if (["--base", "--key", "--model"].includes(a)) { i++; continue; }
|
|
829
|
+
pass.push(a);
|
|
830
|
+
}
|
|
831
|
+
const { code } = runJudge(pass, {
|
|
832
|
+
MG_BASE: opts("--base", process.env.MG_BASE),
|
|
833
|
+
MG_KEY: opts("--key", process.env.MG_KEY),
|
|
834
|
+
MG_MODEL: opts("--model", process.env.MG_MODEL),
|
|
835
|
+
});
|
|
836
|
+
process.exitCode = code;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
console.error(`未知子命令: armor ${sub}(可用: matrix | plan | preflight | run | judge)`);
|
|
841
|
+
process.exit(2);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function printMatrix() {
|
|
845
|
+
const rows = Object.entries(matrix.harnesses).map(([name, s]) => ({
|
|
846
|
+
harness: name,
|
|
847
|
+
class: s.class,
|
|
848
|
+
override: s.override === true ? "yes" : s.override ? String(s.override) : "no",
|
|
849
|
+
inject: s.inject ? "yes" : "no",
|
|
850
|
+
hot: s.hotSwitch ? "yes" : "no",
|
|
851
|
+
chars: s.defaultSystemChars != null ? `${s.defaultSystemChars} → ${s.purgedSystemChars ?? "?"}` : "—",
|
|
852
|
+
armor: s.armor ? `${s.armor.probe.bare.sys ?? "—"}${s.armor.probe.bare.sys == null ? "" : "%"} / ${s.armor.probe.bare.user ?? "—"}${s.armor.probe.bare.user == null ? "" : "%"}` : "未测",
|
|
853
|
+
version: s.measuredVersion ?? "—",
|
|
854
|
+
}));
|
|
855
|
+
const head = ["harness", "class", "override", "inject", "hot", "system chars", "sys/user 注入", "version"];
|
|
856
|
+
const width = head.map((h, i) => Math.max(h.length, ...rows.map((r) => String(Object.values(r)[i]).length)));
|
|
857
|
+
const line = (cells) => cells.map((c, i) => String(c).padEnd(width[i])).join(" ");
|
|
858
|
+
console.log(line(head));
|
|
859
|
+
console.log(width.map((w) => "-".repeat(w)).join(" "));
|
|
860
|
+
rows.forEach((r) => console.log(line(Object.values(r))));
|
|
861
|
+
const residues = Object.entries(matrix.harnesses).filter(([, s]) => s.residue);
|
|
862
|
+
if (residues.length) {
|
|
863
|
+
console.log("\n残留:");
|
|
864
|
+
residues.forEach(([n, s]) => console.log(` ${n}: ${s.residue}`));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function help() {
|
|
869
|
+
console.log(`agent-purge —— 把任意 harness 的系统提示词换成你的 persona
|
|
870
|
+
|
|
871
|
+
一句话:先「选」,再「用」。选一次,之后所有入口都跟着变。
|
|
872
|
+
|
|
873
|
+
① 选(写共享配置并同步;已加载动态插件下一轮读取,静态入口需新会话)
|
|
874
|
+
agent-purge use <id> 换 persona(ls 看有哪些,* = 当前;自动刷新已武装的常驻落点)
|
|
875
|
+
agent-purge use <id> --for <h> 只给某一家换 persona;--for <h> follow 恢复跟随全局
|
|
876
|
+
agent-purge pull [repo] 从 GitHub(默认 ZiChuanLan/some-magic)拉取人设
|
|
877
|
+
agent-purge persona import <dir> 从本地目录批量导入人设 (.md)
|
|
878
|
+
agent-purge sync 只刷新常驻落点/命令文件,不换 persona
|
|
879
|
+
agent-purge mode <m> replace(换身份, 默认) / strict(只留人设) / append(追加) / off
|
|
880
|
+
agent-purge mode <m> --for <h> 只给某一家设模式;--for <h> follow 恢复跟随全局
|
|
881
|
+
agent-purge pause 暂停并撤下静态托管内容,保留通道选择
|
|
882
|
+
agent-purge resume 恢复上次模式与原选择范围
|
|
883
|
+
agent-purge clean 从所有 harness 彻底拔出插件与接管命令并还原配置
|
|
884
|
+
agent-purge update 检查 GitHub Release 最新版本与下载更新
|
|
885
|
+
agent-purge web 浏览器里点选 harness · persona · mode
|
|
886
|
+
|
|
887
|
+
② 用(命令入口与常驻配置的生效时机不同)
|
|
888
|
+
agent-purge 裸敲 = 跑 default 里那家(web 选过就用选过的)
|
|
889
|
+
agent-purge <h> [--ask "…"] 指定一家启动;--dry-run 只看命令,-- 后面透传
|
|
890
|
+
<h> web / arm 之后直接敲命令名,就已经注入
|
|
891
|
+
|
|
892
|
+
③ 装(让「别的入口」也带人设;一条命令全装,想只做一项就点名)
|
|
893
|
+
agent-purge arm <h>|--all 常驻 + 裸命令接管 + /purge 全开(= 注册)
|
|
894
|
+
agent-purge 注册 [--all] 中文别名,同上
|
|
895
|
+
agent-purge arm <h> --shim 只做裸命令接管(写 PATH 最前)
|
|
896
|
+
agent-purge arm <h> --install 只做配置文件常驻
|
|
897
|
+
agent-purge arm <h> --slash 只做会话内 /purge
|
|
898
|
+
agent-purge disarm <h>|--all 以上全部撤掉;不带 <h> 就是所有 harness
|
|
899
|
+
agent-purge clean 按账本撒回所有写入(保留 state 与人设)
|
|
900
|
+
agent-purge self-uninstall --yes 撤回写入 + 永久删除数据目录(先备份人设)
|
|
901
|
+
agent-purge self-uninstall --dry-run 只预览将撤销和删除的项目
|
|
902
|
+
agent-purge project on|off [<h>…] 项目级:写当前目录的 AGENTS.md / CLAUDE.md
|
|
903
|
+
[--dir X] 托管块;只影响这个项目,删块即逐字节还原
|
|
904
|
+
|
|
905
|
+
注:--all 是总电源(state.json 的 default.mode)。带 <h> 时只撤那家,
|
|
906
|
+
但同样把总电源关掉——不然裸敲 agent-purge、agent-purge <h> 照样注入。
|
|
907
|
+
彻底别管我:agent-purge disarm --all
|
|
908
|
+
|
|
909
|
+
④ 看
|
|
910
|
+
agent-purge status 一张表:每家的 shim / slash / install / 能做什么
|
|
911
|
+
agent-purge ls persona 列表
|
|
912
|
+
agent-purge verify [harness…] 实测:本机真跑一次,看人设到底进没进 system(写 verification/latest.json)
|
|
913
|
+
agent-purge matrix 能力矩阵(matrix.json)
|
|
914
|
+
agent-purge armor [...] plan|preflight|run|judge 实测热力图
|
|
915
|
+
agent-purge doctor 本机探测(哪家装了、版本)
|
|
916
|
+
|
|
917
|
+
通道语义
|
|
918
|
+
shim 在 PATH 最前放同名 cmd,裸敲即经 agent-purge 启动(sys 层注入)
|
|
919
|
+
install 改 harness 自己的配置/插件文件(连别人启动的它也会带人设)
|
|
920
|
+
slash pi / omp / dsh 通过扩展提供 /purge,会话下一轮读取
|
|
921
|
+
project 写当前项目根的 AGENTS.md / CLAUDE.md(和全局块同时开会两份都进)
|
|
922
|
+
热切 pi / omp / dsh / opencode / opencode2 已验证同会话 A→B→strict→off
|
|
923
|
+
首次安装或更新插件代码需重新加载;其他启动参数入口需新会话
|
|
924
|
+
证据 npm run test:harness;Web「验证与诊断」展示请求角色与宿主残留
|
|
925
|
+
配置写入不代表当前会话已读取;Codex 实际请求角色为 developer。
|
|
926
|
+
|
|
927
|
+
harness: pi, opencode, opencode2, dsh, codex, claude, grok, omp, hermes, zcode, antigravity, codebuddy
|
|
928
|
+
opencode2 = @opencode/cli 2.x(v2 插件 API),dsh = DeepSeek Harness(cordis 插件,desktop/web/headless 三形态)。
|
|
929
|
+
`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/** [cmd] 只用于复现启动参数;人设正文不能整段落进终端/CI 日志。 */
|
|
933
|
+
function previewArgs(args) {
|
|
934
|
+
const secret = new Set(["--system-prompt", "--append-system-prompt", "--system-prompt-override", "--rules"]);
|
|
935
|
+
const out = [];
|
|
936
|
+
for (let i = 0; i < args.length; i++) {
|
|
937
|
+
const a = String(args[i]);
|
|
938
|
+
if (a === "-c" && typeof args[i + 1] === "string" && args[i + 1].startsWith("developer_instructions=")) {
|
|
939
|
+
out.push(a, `developer_instructions=<persona ${args[i + 1].length} 字>`);
|
|
940
|
+
i++;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (secret.has(a) && args[i + 1] !== undefined) {
|
|
944
|
+
out.push(a, `<persona ${String(args[i + 1]).length} 字>`);
|
|
945
|
+
i++;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
out.push(a.includes(" ") ? JSON.stringify(a) : a);
|
|
949
|
+
}
|
|
950
|
+
return out;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function die(message) {
|
|
954
|
+
console.error(message);
|
|
955
|
+
process.exit(2);
|
|
956
|
+
}
|