@agntn/harnesses 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/THIRD-PARTY-LICENSES.md +36 -0
  4. package/dist/_chunks/agents-sync.d.mts +417 -0
  5. package/dist/_chunks/agents-sync.d.mts.map +1 -0
  6. package/dist/_chunks/agents-sync.mjs +2719 -0
  7. package/dist/_chunks/agents-sync.mjs.map +1 -0
  8. package/dist/_chunks/agents.mjs +62 -0
  9. package/dist/_chunks/agents.mjs.map +1 -0
  10. package/dist/_chunks/libs/typebox.mjs +5512 -0
  11. package/dist/_chunks/libs/typebox.mjs.map +1 -0
  12. package/dist/_chunks/mcp-servers.mjs +149 -0
  13. package/dist/_chunks/mcp-servers.mjs.map +1 -0
  14. package/dist/_chunks/mcp.mjs +164 -0
  15. package/dist/_chunks/mcp.mjs.map +1 -0
  16. package/dist/_chunks/mcp2.mjs +17 -0
  17. package/dist/_chunks/mcp2.mjs.map +1 -0
  18. package/dist/_chunks/tool-operations.mjs +361 -0
  19. package/dist/_chunks/tool-operations.mjs.map +1 -0
  20. package/dist/_chunks/tool-schemas.d.mts +94 -0
  21. package/dist/_chunks/tool-schemas.d.mts.map +1 -0
  22. package/dist/_chunks/tool-schemas.mjs +112 -0
  23. package/dist/_chunks/tool-schemas.mjs.map +1 -0
  24. package/dist/_chunks/types.mjs +4 -0
  25. package/dist/_chunks/types.mjs.map +1 -0
  26. package/dist/cli.d.mts +1 -0
  27. package/dist/cli.mjs +350 -0
  28. package/dist/cli.mjs.map +1 -0
  29. package/dist/index.d.mts +300 -0
  30. package/dist/index.d.mts.map +1 -0
  31. package/dist/index.mjs +6 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/mcp.d.mts +15 -0
  34. package/dist/mcp.d.mts.map +1 -0
  35. package/dist/mcp.mjs +2 -0
  36. package/dist/tool-operations.d.mts +222 -0
  37. package/dist/tool-operations.d.mts.map +1 -0
  38. package/dist/tool-operations.mjs +3 -0
  39. package/dist/tool-schemas.d.mts +2 -0
  40. package/dist/tool-schemas.mjs +2 -0
  41. package/package.json +102 -0
  42. package/packages/omp/extensions/AGENTS.md +7 -0
  43. package/packages/omp/extensions/harnesses.ts +253 -0
  44. package/packages/pi/extensions/AGENTS.md +7 -0
  45. package/packages/pi/extensions/harnesses.ts +265 -0
  46. package/packages/shared/AGENTS.md +7 -0
  47. package/packages/shared/tui.ts +346 -0
@@ -0,0 +1,2719 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { execFileSync, spawn } from "node:child_process";
3
+ import { basename, dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
4
+ import os, { homedir } from "node:os";
5
+ import { stripVTControlCharacters } from "node:util";
6
+ import { parse } from "smol-toml";
7
+ function resolvePathTemplate(template, options = {}) {
8
+ const homeDir = options.homeDir ?? os.homedir();
9
+ const projectRoot = options.projectRoot ?? process.cwd();
10
+ return template.replace(/^~(?=\/|$)/, homeDir).replaceAll("${HOME}", homeDir).replaceAll("${PROJECT_ROOT}", projectRoot).replaceAll(/%([^%]+)%/g, (match, name) => process.env[name] ?? match);
11
+ }
12
+ function agntnConfigDir(options = {}) {
13
+ const base = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME !== "" ? process.env.XDG_CONFIG_HOME : join(options.homeDir ?? os.homedir(), ".config");
14
+ return join(base, "agntn");
15
+ }
16
+ const SUPPORTED_PLATFORMS = {
17
+ linux: true,
18
+ darwin: true,
19
+ win32: true
20
+ };
21
+ const ALTERNATE_INVOCATION_MODE = {
22
+ advisor: "agent",
23
+ advisorStructured: "agentStructured",
24
+ agent: "advisor",
25
+ agentStructured: "advisorStructured"
26
+ };
27
+ const INVOCATION_MODE_DESCRIPTION = {
28
+ advisor: "advisor without tools",
29
+ advisorStructured: "structured (JSON) advisor without tools",
30
+ readOnly: "read-only full agent",
31
+ readOnlyStructured: "structured (JSON) read-only full agent",
32
+ agent: "full agent",
33
+ agentStructured: "structured (JSON) full agent"
34
+ };
35
+ const INVOCATION_RETRY_HINT = {
36
+ advisor: "; retry with tools: true to start its full agent",
37
+ advisorStructured: "; retry with tools: true to start its full agent",
38
+ agent: "; retry with tools: false to use its advisor without tools",
39
+ agentStructured: "; retry with tools: false to use its advisor without tools"
40
+ };
41
+ function requestedInvocationMode(options) {
42
+ if (options.readOnly === true) return options.structured === true ? "readOnlyStructured" : "readOnly";
43
+ if (options.tools === true) return options.structured === true ? "agentStructured" : "agent";
44
+ return options.structured === true ? "advisorStructured" : "advisor";
45
+ }
46
+ function invocationTemplate(invocation, mode) {
47
+ switch (mode) {
48
+ case "advisor": return invocation.noToolsArgs;
49
+ case "advisorStructured": return invocation.noToolsJsonArgs;
50
+ case "readOnly": return invocation.readOnlyArgs;
51
+ case "readOnlyStructured": return invocation.readOnlyJsonArgs;
52
+ case "agent": return invocation.args;
53
+ case "agentStructured": return invocation.jsonArgs;
54
+ }
55
+ }
56
+ function buildInvocationArgs(template, prompt, modelArgs, model) {
57
+ const args = template.map((arg) => arg.replaceAll("{prompt}", prompt));
58
+ if (model !== void 0 && modelArgs) args.push(...modelArgs.map((arg) => arg.replaceAll("{model}", model)));
59
+ return args;
60
+ }
61
+ function executeCommand(command, args, options) {
62
+ return new Promise((resolve, reject) => {
63
+ const child = spawn(command, args, {
64
+ cwd: options.cwd,
65
+ env: options.env ? {
66
+ ...process.env,
67
+ ...options.env
68
+ } : process.env,
69
+ stdio: [
70
+ "ignore",
71
+ "pipe",
72
+ "pipe"
73
+ ]
74
+ });
75
+ let stdout = "";
76
+ let stderr = "";
77
+ let timedOut = false;
78
+ const timer = options.timeoutMs ? setTimeout(() => {
79
+ timedOut = true;
80
+ child.kill("SIGTERM");
81
+ }, options.timeoutMs) : void 0;
82
+ child.stdout.on("data", (chunk) => {
83
+ stdout += chunk.toString("utf8");
84
+ });
85
+ child.stderr.on("data", (chunk) => {
86
+ stderr += chunk.toString("utf8");
87
+ });
88
+ child.on("error", (error) => {
89
+ if (timer) clearTimeout(timer);
90
+ reject(error);
91
+ });
92
+ child.on("close", (code) => {
93
+ if (timer) clearTimeout(timer);
94
+ resolve({
95
+ command,
96
+ args: [...args],
97
+ stdout,
98
+ stderr,
99
+ exitCode: timedOut ? null : code,
100
+ timedOut
101
+ });
102
+ });
103
+ });
104
+ }
105
+ var Harness = class {
106
+ modelListing = null;
107
+ mcpConfigs = [];
108
+ agentsFile = null;
109
+ isInstalled() {
110
+ const cmd = process.platform === "win32" ? "where" : "which";
111
+ return this.binaries.some((binary) => {
112
+ try {
113
+ execFileSync(cmd, [binary], { stdio: "pipe" });
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ });
119
+ }
120
+ detectEnv() {
121
+ return this.detection.envVars.some((v) => !!process.env[v]);
122
+ }
123
+ detectProject(cwd) {
124
+ const dir = cwd ?? process.cwd();
125
+ return this.detection.projectMarkers.some((marker) => existsSync(join(dir, marker)));
126
+ }
127
+ get version() {
128
+ for (const binary of this.binaries) try {
129
+ const match = execFileSync(binary, ["--version"], {
130
+ encoding: "utf8",
131
+ stdio: [
132
+ "pipe",
133
+ "pipe",
134
+ "pipe"
135
+ ],
136
+ timeout: 5e3
137
+ }).match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
138
+ if (match?.[1]) return match[1];
139
+ } catch {
140
+ continue;
141
+ }
142
+ return null;
143
+ }
144
+ get invocationModes() {
145
+ return {
146
+ advisor: this.invocation?.noToolsArgs !== void 0,
147
+ advisorStructured: this.invocation?.noToolsJsonArgs !== void 0,
148
+ readOnly: this.invocation?.readOnlyArgs !== void 0,
149
+ readOnlyStructured: this.invocation?.readOnlyJsonArgs !== void 0,
150
+ agent: this.invocation?.args !== void 0,
151
+ agentStructured: this.invocation?.jsonArgs !== void 0
152
+ };
153
+ }
154
+ buildInvocation(prompt, options = {}) {
155
+ if (!this.invocation) return null;
156
+ const command = this.invocation.binary ?? this.binaries[0];
157
+ if (!command) return null;
158
+ if (options.model !== void 0 && !this.invocation.modelArgs) return null;
159
+ const template = invocationTemplate(this.invocation, requestedInvocationMode(options));
160
+ if (!template) return null;
161
+ return {
162
+ command,
163
+ args: buildInvocationArgs(template, prompt, this.invocation.modelArgs, options.model)
164
+ };
165
+ }
166
+ invocationError(options = {}) {
167
+ if (!this.invocation) return `Harness ${this.id} has no non-interactive invocation`;
168
+ if (options.model !== void 0 && !this.invocation.modelArgs) return `Harness ${this.id} does not support model selection`;
169
+ const mode = requestedInvocationMode(options);
170
+ if (invocationTemplate(this.invocation, mode)) return null;
171
+ const alternateMode = ALTERNATE_INVOCATION_MODE[mode];
172
+ const retry = (alternateMode === void 0 ? void 0 : invocationTemplate(this.invocation, alternateMode)) ? INVOCATION_RETRY_HINT[mode] ?? "" : "";
173
+ return `Harness ${this.id} has no ${INVOCATION_MODE_DESCRIPTION[mode]} invocation${retry}`;
174
+ }
175
+ invoke(prompt, options = {}) {
176
+ const invocationOptions = {
177
+ model: options.model,
178
+ structured: options.structured,
179
+ tools: options.tools,
180
+ readOnly: options.readOnly
181
+ };
182
+ const built = this.buildInvocation(prompt, invocationOptions);
183
+ if (!built) return Promise.reject(new Error(this.invocationError(invocationOptions) ?? "Invalid invocation"));
184
+ return executeCommand(built.command, built.args, options);
185
+ }
186
+ buildModelListInvocation(search) {
187
+ if (!this.modelListing) return null;
188
+ const command = this.binaries[0];
189
+ if (!command) return null;
190
+ const template = search === void 0 ? this.modelListing.args : this.modelListing.searchArgs;
191
+ if (!template) return null;
192
+ return {
193
+ command,
194
+ args: template.map((arg) => arg.replaceAll("{search}", search ?? ""))
195
+ };
196
+ }
197
+ async listModels(options = {}) {
198
+ const built = this.buildModelListInvocation(options.search);
199
+ if (!built) throw new Error(this.modelListing ? `Harness ${this.id} does not support filtering its model listing` : `Harness ${this.id} does not support model listing`);
200
+ const result = await executeCommand(built.command, built.args, options);
201
+ return {
202
+ ...result,
203
+ models: !result.timedOut && result.exitCode === 0 ? this.parseModelListingOutput(result.stdout) : []
204
+ };
205
+ }
206
+ parseModelListingOutput(_stdout) {
207
+ throw new Error(`Harness ${this.id} does not implement model-list output parsing`);
208
+ }
209
+ resolveCandidates(entries, options = {}) {
210
+ const raw = options.platform ?? process.platform;
211
+ if (!Object.hasOwn(SUPPORTED_PLATFORMS, raw)) throw new Error(`Unsupported platform: ${raw}. Expected one of: linux, darwin, win32`);
212
+ const platform = raw;
213
+ return entries.filter((entry) => !entry.platforms || entry.platforms.includes(platform)).map((entry) => ({
214
+ ...entry,
215
+ path: resolvePathTemplate(entry.path, options)
216
+ }));
217
+ }
218
+ resolve(options = {}) {
219
+ return {
220
+ config: this.resolveCandidates(this.config, options),
221
+ sessions: this.resolveCandidates(this.sessions, options),
222
+ instructions: this.resolveCandidates(this.instructions, options),
223
+ skills: this.resolveCandidates(this.skills, options),
224
+ commands: this.resolveCandidates(this.commands, options),
225
+ hooks: this.resolveCandidates(this.hooks, options)
226
+ };
227
+ }
228
+ };
229
+ var Antigravity = class extends Harness {
230
+ id = "antigravity";
231
+ name = "Google Antigravity CLI";
232
+ binaries = ["agy"];
233
+ capabilities = {
234
+ mcp: true,
235
+ vision: true,
236
+ audio: true,
237
+ video: true,
238
+ tools: true,
239
+ streaming: true
240
+ };
241
+ config = [
242
+ {
243
+ path: "~/.gemini/config/config.json",
244
+ scope: "user",
245
+ level: "official",
246
+ note: "Shared Antigravity configuration and user settings."
247
+ },
248
+ {
249
+ path: "~/.gemini/antigravity-cli/settings.json",
250
+ scope: "user",
251
+ level: "official",
252
+ note: "CLI-specific settings, including trusted workspaces."
253
+ },
254
+ {
255
+ path: "~/.gemini/config/skills.json",
256
+ scope: "user",
257
+ level: "official",
258
+ note: "Explicit registrations of skill directories outside the default locations."
259
+ },
260
+ {
261
+ path: "~/.gemini/config/plugins.json",
262
+ scope: "user",
263
+ level: "official",
264
+ note: "Explicit registrations of plugin directories outside the default locations."
265
+ },
266
+ {
267
+ path: ".agents/skills.json",
268
+ scope: "project",
269
+ level: "official",
270
+ note: "Workspace registrations of skill directories outside .agents/skills/."
271
+ },
272
+ {
273
+ path: ".agents/plugins.json",
274
+ scope: "project",
275
+ level: "official",
276
+ note: "Workspace registrations of plugin directories outside .agents/plugins/."
277
+ }
278
+ ];
279
+ sessions = [
280
+ {
281
+ path: "~/.gemini/antigravity-cli/conversations/",
282
+ scope: "data",
283
+ level: "official",
284
+ note: "Persisted CLI conversations."
285
+ },
286
+ {
287
+ path: "~/.gemini/antigravity-cli/history.jsonl",
288
+ scope: "data",
289
+ level: "official",
290
+ note: "CLI prompt history."
291
+ },
292
+ {
293
+ path: "~/.gemini/antigravity-cli/conversation_summaries.db",
294
+ scope: "data",
295
+ level: "official",
296
+ note: "Conversation summary index."
297
+ }
298
+ ];
299
+ persistence = [
300
+ {
301
+ format: "JSON",
302
+ level: "official",
303
+ note: "Settings and project metadata."
304
+ },
305
+ {
306
+ format: "JSONL",
307
+ level: "official",
308
+ note: "Prompt history."
309
+ },
310
+ {
311
+ format: "SQLite",
312
+ level: "official",
313
+ note: "Conversation summary index."
314
+ },
315
+ {
316
+ format: "Protocol Buffers",
317
+ level: "official",
318
+ note: "Conversation and runtime state."
319
+ }
320
+ ];
321
+ instructions = [
322
+ {
323
+ path: "~/.gemini/config/AGENTS.md",
324
+ scope: "user",
325
+ level: "official",
326
+ note: "Global rules in the shared Antigravity customization root."
327
+ },
328
+ {
329
+ path: "AGENTS.md",
330
+ scope: "project",
331
+ level: "official",
332
+ note: "Workspace instructions; GEMINI.md is also supported."
333
+ },
334
+ {
335
+ path: "GEMINI.md",
336
+ scope: "project",
337
+ level: "official",
338
+ note: "Workspace instructions compatible with Gemini CLI."
339
+ }
340
+ ];
341
+ skills = [{
342
+ path: ".agents/skills/",
343
+ scope: "project",
344
+ level: "official"
345
+ }, {
346
+ path: "~/.gemini/config/skills/",
347
+ scope: "user",
348
+ level: "official"
349
+ }];
350
+ commands = [];
351
+ hooks = [{
352
+ path: ".agents/hooks.json",
353
+ scope: "project",
354
+ level: "official"
355
+ }, {
356
+ path: "~/.gemini/config/hooks.json",
357
+ scope: "user",
358
+ level: "official"
359
+ }];
360
+ invocation = {
361
+ args: ["--print", "{prompt}"],
362
+ jsonArgs: [
363
+ "--print",
364
+ "--output-format",
365
+ "json",
366
+ "{prompt}"
367
+ ],
368
+ modelArgs: ["--model", "{model}"],
369
+ level: "official",
370
+ note: "Non-interactive print mode."
371
+ };
372
+ mcpConfigs = [{
373
+ path: "~/.gemini/config/mcp_config.json",
374
+ scope: "user",
375
+ level: "official",
376
+ format: "json",
377
+ key: ["mcpServers"],
378
+ dialect: "antigravity"
379
+ }];
380
+ agentsFile = "~/.gemini/config/AGENTS.md";
381
+ detection = {
382
+ envVars: [],
383
+ projectMarkers: [".antigravitycli"]
384
+ };
385
+ };
386
+ var Claude = class extends Harness {
387
+ id = "claude";
388
+ name = "Anthropic Claude Code";
389
+ binaries = ["claude"];
390
+ capabilities = {
391
+ mcp: true,
392
+ vision: true,
393
+ audio: false,
394
+ video: false,
395
+ tools: true,
396
+ streaming: true
397
+ };
398
+ config = [
399
+ {
400
+ path: "~/.claude/settings.json",
401
+ scope: "user",
402
+ level: "official"
403
+ },
404
+ {
405
+ path: "~/.claude.json",
406
+ scope: "user",
407
+ level: "official",
408
+ note: "Global MCP server configurations."
409
+ },
410
+ {
411
+ path: ".claude/settings.json",
412
+ scope: "project",
413
+ level: "official"
414
+ },
415
+ {
416
+ path: ".claude/settings.local.json",
417
+ scope: "project",
418
+ level: "official",
419
+ note: "Local overrides, typically gitignored."
420
+ },
421
+ {
422
+ path: ".claude/.mcp.json",
423
+ scope: "project",
424
+ level: "official",
425
+ note: "Project-scoped MCP server configurations."
426
+ },
427
+ {
428
+ path: "/etc/claude-code/managed-settings.json",
429
+ scope: "system",
430
+ level: "official",
431
+ platforms: ["linux"],
432
+ note: "Enterprise managed settings."
433
+ },
434
+ {
435
+ path: "/Library/Application Support/ClaudeCode/managed-settings.json",
436
+ scope: "system",
437
+ level: "official",
438
+ platforms: ["darwin"],
439
+ note: "Enterprise managed settings."
440
+ }
441
+ ];
442
+ sessions = [{
443
+ path: "~/.claude/projects/<dash-encoded-cwd>/*.jsonl",
444
+ scope: "data",
445
+ level: "official",
446
+ note: "Per-project session transcripts in JSONL format."
447
+ }, {
448
+ path: "~/.claude/history.jsonl",
449
+ scope: "data",
450
+ level: "official",
451
+ note: "Global prompt history across all projects."
452
+ }];
453
+ persistence = [{
454
+ format: "JSON",
455
+ level: "official",
456
+ note: "Settings and MCP config files."
457
+ }, {
458
+ format: "JSONL",
459
+ level: "official",
460
+ note: "Session transcripts and global history."
461
+ }];
462
+ instructions = [
463
+ {
464
+ path: "CLAUDE.md",
465
+ scope: "project",
466
+ level: "official"
467
+ },
468
+ {
469
+ path: ".claude/CLAUDE.md",
470
+ scope: "project",
471
+ level: "official",
472
+ note: "Alternative project-level location."
473
+ },
474
+ {
475
+ path: "~/.claude/CLAUDE.md",
476
+ scope: "user",
477
+ level: "official",
478
+ note: "Global user-level instructions."
479
+ }
480
+ ];
481
+ skills = [{
482
+ path: ".claude/skills/",
483
+ scope: "project",
484
+ level: "official"
485
+ }, {
486
+ path: "~/.claude/skills/",
487
+ scope: "user",
488
+ level: "official",
489
+ note: "Global user-level skills."
490
+ }];
491
+ commands = [{
492
+ path: ".claude/commands/",
493
+ scope: "project",
494
+ level: "official",
495
+ note: "Legacy slash commands, merged into skills."
496
+ }, {
497
+ path: "~/.claude/commands/",
498
+ scope: "user",
499
+ level: "official",
500
+ note: "Legacy global slash commands."
501
+ }];
502
+ hooks = [{
503
+ path: ".claude/hooks/",
504
+ scope: "project",
505
+ level: "official"
506
+ }, {
507
+ path: "~/.claude/hooks/",
508
+ scope: "user",
509
+ level: "official",
510
+ note: "Global user-level hooks."
511
+ }];
512
+ invocation = {
513
+ args: ["-p", "{prompt}"],
514
+ jsonArgs: [
515
+ "-p",
516
+ "--output-format",
517
+ "json",
518
+ "{prompt}"
519
+ ],
520
+ noToolsArgs: [
521
+ "-p",
522
+ "{prompt}",
523
+ "--tools",
524
+ ""
525
+ ],
526
+ noToolsJsonArgs: [
527
+ "-p",
528
+ "--output-format",
529
+ "json",
530
+ "{prompt}",
531
+ "--tools",
532
+ ""
533
+ ],
534
+ modelArgs: ["--model", "{model}"],
535
+ level: "official",
536
+ note: "Headless print mode; add --output-format json for structured output."
537
+ };
538
+ mcpConfigs = [{
539
+ path: "~/.claude.json",
540
+ scope: "user",
541
+ level: "official",
542
+ format: "json",
543
+ key: ["mcpServers"],
544
+ dialect: "standard"
545
+ }, {
546
+ path: ".mcp.json",
547
+ scope: "project",
548
+ level: "official",
549
+ format: "json",
550
+ key: ["mcpServers"],
551
+ dialect: "standard"
552
+ }];
553
+ agentsFile = "~/.claude/CLAUDE.md";
554
+ detection = {
555
+ envVars: [
556
+ "CLAUDE_CODE",
557
+ "CLAUDECODE",
558
+ "CLAUDE_CODE_ENTRYPOINT",
559
+ "CLAUDE_CONFIG_DIR"
560
+ ],
561
+ projectMarkers: [
562
+ ".claude/settings.json",
563
+ ".claude/settings.local.json",
564
+ ".claude/skills",
565
+ "CLAUDE.md"
566
+ ]
567
+ };
568
+ };
569
+ var Codex = class extends Harness {
570
+ id = "codex";
571
+ name = "OpenAI Codex CLI";
572
+ binaries = ["codex"];
573
+ capabilities = {
574
+ mcp: true,
575
+ vision: true,
576
+ audio: false,
577
+ video: false,
578
+ tools: true,
579
+ streaming: true
580
+ };
581
+ config = [
582
+ {
583
+ path: "~/.codex/config.toml",
584
+ scope: "user",
585
+ level: "official"
586
+ },
587
+ {
588
+ path: ".codex/config.toml",
589
+ scope: "project",
590
+ level: "official",
591
+ note: "Searched from cwd up to project root (.git boundary)."
592
+ },
593
+ {
594
+ path: "/etc/codex/config.toml",
595
+ scope: "system",
596
+ level: "official",
597
+ platforms: ["linux", "darwin"]
598
+ }
599
+ ];
600
+ sessions = [
601
+ {
602
+ path: "~/.codex/sessions/",
603
+ scope: "data",
604
+ level: "official",
605
+ note: "Session rollouts in JSONL format, organized by date."
606
+ },
607
+ {
608
+ path: "~/.codex/history.jsonl",
609
+ scope: "data",
610
+ level: "official",
611
+ note: "Command history."
612
+ },
613
+ {
614
+ path: "~/.codex/state_5.sqlite",
615
+ scope: "data",
616
+ level: "official",
617
+ note: "SQLite state database for threads and agent state."
618
+ }
619
+ ];
620
+ persistence = [
621
+ {
622
+ format: "TOML",
623
+ level: "official",
624
+ note: "Configuration files."
625
+ },
626
+ {
627
+ format: "JSONL",
628
+ level: "official",
629
+ note: "Conversation transcripts."
630
+ },
631
+ {
632
+ format: "SQLite",
633
+ level: "official",
634
+ note: "Thread and agent state."
635
+ }
636
+ ];
637
+ instructions = [
638
+ {
639
+ path: "AGENTS.md",
640
+ scope: "project",
641
+ level: "official"
642
+ },
643
+ {
644
+ path: "AGENTS.override.md",
645
+ scope: "project",
646
+ level: "official",
647
+ note: "Local override, takes precedence over AGENTS.md."
648
+ },
649
+ {
650
+ path: "~/.codex/AGENTS.md",
651
+ scope: "user",
652
+ level: "official",
653
+ note: "Global user-level instructions."
654
+ },
655
+ {
656
+ path: "~/.codex/AGENTS.override.md",
657
+ scope: "user",
658
+ level: "official",
659
+ note: "Global override, takes precedence over global AGENTS.md."
660
+ }
661
+ ];
662
+ skills = [
663
+ {
664
+ path: ".codex/skills/",
665
+ scope: "project",
666
+ level: "official"
667
+ },
668
+ {
669
+ path: ".agents/skills/",
670
+ scope: "project",
671
+ level: "official"
672
+ },
673
+ {
674
+ path: "~/.codex/skills/",
675
+ scope: "user",
676
+ level: "official",
677
+ note: "Deprecated, use ~/.agents/skills/ instead."
678
+ },
679
+ {
680
+ path: "~/.agents/skills/",
681
+ scope: "user",
682
+ level: "official"
683
+ },
684
+ {
685
+ path: "/etc/codex/skills/",
686
+ scope: "system",
687
+ level: "official",
688
+ platforms: ["linux", "darwin"]
689
+ }
690
+ ];
691
+ commands = [];
692
+ hooks = [];
693
+ invocation = {
694
+ args: ["exec", "{prompt}"],
695
+ jsonArgs: [
696
+ "exec",
697
+ "--json",
698
+ "{prompt}"
699
+ ],
700
+ readOnlyArgs: [
701
+ "exec",
702
+ "--sandbox",
703
+ "read-only",
704
+ "{prompt}"
705
+ ],
706
+ readOnlyJsonArgs: [
707
+ "exec",
708
+ "--sandbox",
709
+ "read-only",
710
+ "--json",
711
+ "{prompt}"
712
+ ],
713
+ modelArgs: ["--model", "{model}"],
714
+ level: "official",
715
+ note: "Non-interactive exec subcommand; add --json for structured output."
716
+ };
717
+ mcpConfigs = [{
718
+ path: "~/.codex/config.toml",
719
+ scope: "user",
720
+ level: "official",
721
+ format: "toml",
722
+ key: ["mcp_servers"],
723
+ dialect: "standard"
724
+ }, {
725
+ path: ".codex/config.toml",
726
+ scope: "project",
727
+ level: "official",
728
+ format: "toml",
729
+ key: ["mcp_servers"],
730
+ dialect: "standard"
731
+ }];
732
+ agentsFile = "~/.codex/AGENTS.md";
733
+ detection = {
734
+ envVars: [],
735
+ projectMarkers: [
736
+ ".codex",
737
+ "AGENTS.md",
738
+ "AGENTS.override.md",
739
+ ".agents/skills"
740
+ ]
741
+ };
742
+ };
743
+ var Cursor = class extends Harness {
744
+ id = "cursor";
745
+ name = "Cursor";
746
+ binaries = ["cursor"];
747
+ capabilities = {
748
+ mcp: true,
749
+ vision: true,
750
+ audio: false,
751
+ video: false,
752
+ tools: true,
753
+ streaming: true
754
+ };
755
+ config = [{
756
+ path: "~/.cursor/settings.json",
757
+ scope: "user",
758
+ level: "official"
759
+ }];
760
+ sessions = [];
761
+ persistence = [{
762
+ format: "JSON",
763
+ level: "official",
764
+ note: "Settings and rules files."
765
+ }];
766
+ instructions = [{
767
+ path: ".cursor/rules/skilld-activation.mdc",
768
+ scope: "project",
769
+ level: "official",
770
+ note: "MDC format rule for skill activation."
771
+ }, {
772
+ path: ".cursorrules",
773
+ scope: "project",
774
+ level: "official",
775
+ note: "Legacy project-level instructions."
776
+ }];
777
+ skills = [
778
+ {
779
+ path: ".cursor/skills/",
780
+ scope: "project",
781
+ level: "official"
782
+ },
783
+ {
784
+ path: "~/.cursor/skills/",
785
+ scope: "user",
786
+ level: "official",
787
+ note: "Global user-level skills."
788
+ },
789
+ {
790
+ path: ".claude/skills/",
791
+ scope: "project",
792
+ level: "official",
793
+ note: "Cursor natively scans Claude skills directory."
794
+ },
795
+ {
796
+ path: ".codex/skills/",
797
+ scope: "project",
798
+ level: "official",
799
+ note: "Cursor natively scans Codex skills directory."
800
+ }
801
+ ];
802
+ commands = [];
803
+ hooks = [];
804
+ invocation = {
805
+ binary: "cursor-agent",
806
+ args: ["-p", "{prompt}"],
807
+ jsonArgs: [
808
+ "-p",
809
+ "--output-format",
810
+ "json",
811
+ "{prompt}"
812
+ ],
813
+ level: "community",
814
+ note: "Cursor CLI agent binary, separate from the editor binary."
815
+ };
816
+ mcpConfigs = [{
817
+ path: "~/.cursor/mcp.json",
818
+ scope: "user",
819
+ level: "community",
820
+ format: "json",
821
+ key: ["mcpServers"],
822
+ dialect: "standard"
823
+ }, {
824
+ path: ".cursor/mcp.json",
825
+ scope: "project",
826
+ level: "community",
827
+ format: "json",
828
+ key: ["mcpServers"],
829
+ dialect: "standard"
830
+ }];
831
+ detection = {
832
+ envVars: ["CURSOR_SESSION", "CURSOR_TRACE_ID"],
833
+ projectMarkers: [".cursor", ".cursorrules"]
834
+ };
835
+ };
836
+ var Freebuff = class extends Harness {
837
+ id = "freebuff";
838
+ name = "Freebuff";
839
+ binaries = ["freebuff"];
840
+ capabilities = {
841
+ mcp: true,
842
+ vision: true,
843
+ audio: false,
844
+ video: false,
845
+ tools: true,
846
+ streaming: true
847
+ };
848
+ config = [{
849
+ path: "~/.config/manicode/settings.json",
850
+ scope: "user",
851
+ level: "official",
852
+ note: "Freebuff is Codebuff-based; user data lives under ~/.config/manicode/ on all platforms."
853
+ }, {
854
+ path: "~/.config/manicode/credentials.json",
855
+ scope: "user",
856
+ level: "official",
857
+ note: "Authentication credentials."
858
+ }];
859
+ sessions = [{
860
+ path: "~/.config/manicode/projects/<project-name>/chats/<timestamp>/",
861
+ scope: "data",
862
+ level: "official",
863
+ note: "Per-chat directories with chat-messages.json, chat-meta.json, run-state.json, and log.jsonl."
864
+ }, {
865
+ path: "~/.config/manicode/message-history.json",
866
+ scope: "data",
867
+ level: "official",
868
+ note: "Prompt history."
869
+ }];
870
+ persistence = [{
871
+ format: "JSON",
872
+ level: "official",
873
+ note: "Settings, credentials, chat messages, and run state."
874
+ }, {
875
+ format: "JSONL",
876
+ level: "official",
877
+ note: "Per-chat logs (log.jsonl)."
878
+ }];
879
+ instructions = [
880
+ {
881
+ path: "AGENTS.md",
882
+ scope: "project",
883
+ level: "official",
884
+ note: "Knowledge files; scaffolded by the init flow. knowledge.md, *.knowledge.md, and CLAUDE.md in any project directory are loaded the same way."
885
+ },
886
+ {
887
+ path: "knowledge.md",
888
+ scope: "project",
889
+ level: "official",
890
+ note: "Per-directory project knowledge."
891
+ },
892
+ {
893
+ path: "~/.AGENTS.md",
894
+ scope: "user",
895
+ level: "official",
896
+ note: "User-level knowledge files in the home directory; ~/.knowledge.md and ~/.CLAUDE.md are accepted too. Read-only for the agent."
897
+ }
898
+ ];
899
+ skills = [{
900
+ path: ".agents/skills/",
901
+ scope: "project",
902
+ level: "official",
903
+ note: "Also the install target for community skills via npx skills add; .agents/types/ holds agent template types."
904
+ }];
905
+ commands = [];
906
+ hooks = [];
907
+ invocation = null;
908
+ mcpConfigs = [{
909
+ path: "~/.agents/mcp.json",
910
+ scope: "user",
911
+ level: "official",
912
+ format: "json",
913
+ key: ["mcpServers"],
914
+ dialect: "standard"
915
+ }, {
916
+ path: ".agents/mcp.json",
917
+ scope: "project",
918
+ level: "official",
919
+ format: "json",
920
+ key: ["mcpServers"],
921
+ dialect: "standard",
922
+ note: "The parent directory's .agents/mcp.json is scanned too; agent templates can also embed their own mcpServers."
923
+ }];
924
+ agentsFile = "~/.AGENTS.md";
925
+ detection = {
926
+ envVars: [],
927
+ projectMarkers: [".agents", "knowledge.md"]
928
+ };
929
+ };
930
+ var Gemini = class extends Harness {
931
+ id = "gemini";
932
+ name = "Google Gemini CLI";
933
+ binaries = ["gemini"];
934
+ capabilities = {
935
+ mcp: true,
936
+ vision: true,
937
+ audio: true,
938
+ video: false,
939
+ tools: true,
940
+ streaming: true
941
+ };
942
+ config = [
943
+ {
944
+ path: "~/.gemini/settings.json",
945
+ scope: "user",
946
+ level: "official"
947
+ },
948
+ {
949
+ path: ".gemini/settings.json",
950
+ scope: "project",
951
+ level: "official"
952
+ },
953
+ {
954
+ path: "/etc/gemini-cli/settings.json",
955
+ scope: "system",
956
+ level: "official",
957
+ platforms: ["linux"]
958
+ },
959
+ {
960
+ path: "/Library/Application Support/GeminiCli/settings.json",
961
+ scope: "system",
962
+ level: "official",
963
+ platforms: ["darwin"]
964
+ },
965
+ {
966
+ path: "%PROGRAMDATA%/gemini-cli/settings.json",
967
+ scope: "system",
968
+ level: "official",
969
+ platforms: ["win32"]
970
+ }
971
+ ];
972
+ sessions = [{
973
+ path: "~/.gemini/history/<project-id>/",
974
+ scope: "data",
975
+ level: "official",
976
+ note: "Conversation history per project (project-id is a slug from projects.json)."
977
+ }, {
978
+ path: "~/.gemini/tmp/<project-id>/chats/",
979
+ scope: "data",
980
+ level: "official",
981
+ note: "Active chat session data."
982
+ }];
983
+ persistence = [{
984
+ format: "JSON",
985
+ level: "official",
986
+ note: "Settings files."
987
+ }, {
988
+ format: "JSON",
989
+ level: "official",
990
+ note: "Session and history records."
991
+ }];
992
+ instructions = [{
993
+ path: "GEMINI.md",
994
+ scope: "project",
995
+ level: "official"
996
+ }, {
997
+ path: "~/.gemini/GEMINI.md",
998
+ scope: "user",
999
+ level: "official",
1000
+ note: "Global instruction/memory file."
1001
+ }];
1002
+ skills = [
1003
+ {
1004
+ path: ".gemini/skills/",
1005
+ scope: "project",
1006
+ level: "official"
1007
+ },
1008
+ {
1009
+ path: ".agents/skills/",
1010
+ scope: "project",
1011
+ level: "official"
1012
+ },
1013
+ {
1014
+ path: "~/.gemini/skills/",
1015
+ scope: "user",
1016
+ level: "official"
1017
+ },
1018
+ {
1019
+ path: "~/.agents/skills/",
1020
+ scope: "user",
1021
+ level: "official"
1022
+ }
1023
+ ];
1024
+ commands = [{
1025
+ path: ".gemini/commands/",
1026
+ scope: "project",
1027
+ level: "official",
1028
+ note: "Custom commands in TOML format."
1029
+ }, {
1030
+ path: "~/.gemini/commands/",
1031
+ scope: "user",
1032
+ level: "official",
1033
+ note: "Global custom commands in TOML format."
1034
+ }];
1035
+ hooks = [];
1036
+ invocation = {
1037
+ args: ["-p", "{prompt}"],
1038
+ jsonArgs: [
1039
+ "-p",
1040
+ "{prompt}",
1041
+ "--output-format",
1042
+ "json"
1043
+ ],
1044
+ modelArgs: ["--model", "{model}"],
1045
+ level: "official",
1046
+ note: "Non-interactive (headless) mode."
1047
+ };
1048
+ mcpConfigs = [{
1049
+ path: "~/.gemini/settings.json",
1050
+ scope: "user",
1051
+ level: "official",
1052
+ format: "json",
1053
+ key: ["mcpServers"],
1054
+ dialect: "standard"
1055
+ }, {
1056
+ path: ".gemini/settings.json",
1057
+ scope: "project",
1058
+ level: "official",
1059
+ format: "json",
1060
+ key: ["mcpServers"],
1061
+ dialect: "standard"
1062
+ }];
1063
+ agentsFile = "~/.gemini/GEMINI.md";
1064
+ detection = {
1065
+ envVars: ["GEMINI_CLI"],
1066
+ projectMarkers: [".gemini", "GEMINI.md"]
1067
+ };
1068
+ };
1069
+ var GitHubCopilot = class extends Harness {
1070
+ id = "github-copilot";
1071
+ name = "GitHub Copilot";
1072
+ binaries = [];
1073
+ capabilities = {
1074
+ mcp: true,
1075
+ vision: true,
1076
+ audio: false,
1077
+ video: false,
1078
+ tools: true,
1079
+ streaming: true
1080
+ };
1081
+ config = [{
1082
+ path: "~/.copilot/config.json",
1083
+ scope: "user",
1084
+ level: "inferred"
1085
+ }];
1086
+ sessions = [];
1087
+ persistence = [{
1088
+ format: "JSON",
1089
+ level: "official",
1090
+ note: "Configuration files."
1091
+ }];
1092
+ instructions = [{
1093
+ path: ".github/copilot-instructions.md",
1094
+ scope: "project",
1095
+ level: "official"
1096
+ }, {
1097
+ path: ".github/instructions/",
1098
+ scope: "project",
1099
+ level: "official",
1100
+ note: "Modular instructions in .instructions.md files."
1101
+ }];
1102
+ skills = [{
1103
+ path: ".github/skills/",
1104
+ scope: "project",
1105
+ level: "official"
1106
+ }, {
1107
+ path: ".claude/skills/",
1108
+ scope: "project",
1109
+ level: "official",
1110
+ note: "Copilot auto-detects Claude skills directory."
1111
+ }];
1112
+ commands = [];
1113
+ hooks = [];
1114
+ invocation = {
1115
+ binary: "copilot",
1116
+ args: ["-p", "{prompt}"],
1117
+ level: "community",
1118
+ note: "GitHub Copilot CLI, separate from the editor integration."
1119
+ };
1120
+ mcpConfigs = [{
1121
+ path: ".vscode/mcp.json",
1122
+ scope: "project",
1123
+ level: "community",
1124
+ format: "json",
1125
+ key: ["servers"],
1126
+ dialect: "vscode"
1127
+ }];
1128
+ detection = {
1129
+ envVars: ["COPILOT_RUN_APP"],
1130
+ projectMarkers: [
1131
+ ".github/copilot-instructions.md",
1132
+ ".github/skills",
1133
+ ".github/instructions"
1134
+ ]
1135
+ };
1136
+ };
1137
+ var Grok = class extends Harness {
1138
+ id = "grok";
1139
+ name = "xAI Grok CLI";
1140
+ binaries = ["grok"];
1141
+ capabilities = {
1142
+ mcp: true,
1143
+ vision: true,
1144
+ audio: false,
1145
+ video: false,
1146
+ tools: true,
1147
+ streaming: true
1148
+ };
1149
+ config = [{
1150
+ path: "~/.grok/config.toml",
1151
+ scope: "user",
1152
+ level: "official",
1153
+ note: "Base directory overridable with GROK_HOME."
1154
+ }, {
1155
+ path: ".grok/config.toml",
1156
+ scope: "project",
1157
+ level: "official"
1158
+ }];
1159
+ sessions = [{
1160
+ path: "~/.grok/sessions/<encoded-cwd>/<session-id>/updates.jsonl",
1161
+ scope: "data",
1162
+ level: "official",
1163
+ note: "Authoritative ACP session update stream (conversation + tool calls); session dirs also hold chat_history.jsonl, summary.json, rewind_points.jsonl."
1164
+ }, {
1165
+ path: "~/.grok/sessions/<encoded-cwd>/prompt_history.jsonl",
1166
+ scope: "data",
1167
+ level: "official",
1168
+ note: "Per-project prompt history."
1169
+ }];
1170
+ persistence = [
1171
+ {
1172
+ format: "TOML",
1173
+ level: "official",
1174
+ note: "Configuration files."
1175
+ },
1176
+ {
1177
+ format: "JSONL",
1178
+ level: "official",
1179
+ note: "Session update streams, chat history, prompt history."
1180
+ },
1181
+ {
1182
+ format: "JSON",
1183
+ level: "official",
1184
+ note: "Session state files (summary.json, plan.json, signals.json)."
1185
+ },
1186
+ {
1187
+ format: "SQLite",
1188
+ level: "official",
1189
+ note: "Local FTS5 index over session titles and prompts for sessions search."
1190
+ }
1191
+ ];
1192
+ instructions = [
1193
+ {
1194
+ path: "AGENTS.md",
1195
+ scope: "project",
1196
+ level: "official",
1197
+ note: "Also accepts Agents.md, AGENT.md, Claude.md, CLAUDE.md, CLAUDE.local.md; every matching file in a directory is loaded."
1198
+ },
1199
+ {
1200
+ path: ".grok/rules/",
1201
+ scope: "project",
1202
+ level: "official",
1203
+ note: "*.md rules scanned at each level from repo root to cwd; .claude/rules/ and .cursor/rules/ scanned via vendor compatibility."
1204
+ },
1205
+ {
1206
+ path: "~/.grok/rules/",
1207
+ scope: "user",
1208
+ level: "official",
1209
+ note: "Global user-level rules."
1210
+ }
1211
+ ];
1212
+ skills = [{
1213
+ path: ".grok/skills/",
1214
+ scope: "project",
1215
+ level: "official"
1216
+ }, {
1217
+ path: "~/.grok/skills/",
1218
+ scope: "user",
1219
+ level: "official",
1220
+ note: "Global user-level skills."
1221
+ }];
1222
+ commands = [{
1223
+ path: ".grok/commands/",
1224
+ scope: "project",
1225
+ level: "official"
1226
+ }, {
1227
+ path: "~/.grok/commands/",
1228
+ scope: "user",
1229
+ level: "official",
1230
+ note: "Global user-level slash commands."
1231
+ }];
1232
+ hooks = [{
1233
+ path: ".grok/hooks/",
1234
+ scope: "project",
1235
+ level: "official"
1236
+ }, {
1237
+ path: "~/.grok/hooks/",
1238
+ scope: "user",
1239
+ level: "official",
1240
+ note: "JSON hook definitions (e.g. session-start.json); Claude/Cursor hook sources scanned via vendor compatibility."
1241
+ }];
1242
+ invocation = {
1243
+ args: ["-p", "{prompt}"],
1244
+ jsonArgs: [
1245
+ "-p",
1246
+ "{prompt}",
1247
+ "--output-format",
1248
+ "json"
1249
+ ],
1250
+ modelArgs: ["--model", "{model}"],
1251
+ level: "official",
1252
+ note: "-p is short for --single; add --output-format json for structured output."
1253
+ };
1254
+ mcpConfigs = [{
1255
+ path: "~/.grok/config.toml",
1256
+ scope: "user",
1257
+ level: "official",
1258
+ format: "toml",
1259
+ key: ["mcp_servers"],
1260
+ dialect: "standard",
1261
+ note: "grok mcp add/remove manage this file; Claude and Cursor configs are also scanned via vendor compatibility."
1262
+ }, {
1263
+ path: ".grok/config.toml",
1264
+ scope: "project",
1265
+ level: "official",
1266
+ format: "toml",
1267
+ key: ["mcp_servers"],
1268
+ dialect: "standard"
1269
+ }];
1270
+ detection = {
1271
+ envVars: [
1272
+ "GROK_SESSION_ID",
1273
+ "GROK_WORKSPACE_ROOT",
1274
+ "GROK_HOME"
1275
+ ],
1276
+ projectMarkers: [".grok"]
1277
+ };
1278
+ };
1279
+ var MastraCode = class extends Harness {
1280
+ id = "mastracode";
1281
+ name = "Mastra Code";
1282
+ binaries = ["mastracode"];
1283
+ capabilities = {
1284
+ mcp: true,
1285
+ vision: true,
1286
+ audio: false,
1287
+ video: false,
1288
+ tools: true,
1289
+ streaming: true
1290
+ };
1291
+ config = [
1292
+ {
1293
+ path: ".mastracode/mcp.json",
1294
+ scope: "project",
1295
+ level: "official",
1296
+ note: "Project-scoped MCP server configurations."
1297
+ },
1298
+ {
1299
+ path: "~/.mastracode/mcp.json",
1300
+ scope: "user",
1301
+ level: "official",
1302
+ note: "Global MCP server configurations."
1303
+ },
1304
+ {
1305
+ path: ".mastracode/hooks.json",
1306
+ scope: "project",
1307
+ level: "official",
1308
+ note: "Project-scoped hooks."
1309
+ },
1310
+ {
1311
+ path: "~/.mastracode/hooks.json",
1312
+ scope: "user",
1313
+ level: "official",
1314
+ note: "Global hooks."
1315
+ }
1316
+ ];
1317
+ sessions = [{
1318
+ path: "~/.local/share/mastracode/mastra.db",
1319
+ scope: "data",
1320
+ level: "official",
1321
+ platforms: ["linux"],
1322
+ note: "LibSQL database with threads, messages, and observational memory."
1323
+ }];
1324
+ persistence = [{
1325
+ format: "JSON",
1326
+ level: "official",
1327
+ note: "MCP and hooks config files."
1328
+ }, {
1329
+ format: "SQLite",
1330
+ level: "official",
1331
+ note: "LibSQL database for threads, messages, and observational memory."
1332
+ }];
1333
+ instructions = [
1334
+ {
1335
+ path: "AGENTS.md",
1336
+ scope: "project",
1337
+ level: "official"
1338
+ },
1339
+ {
1340
+ path: "CLAUDE.md",
1341
+ scope: "project",
1342
+ level: "official",
1343
+ note: "Supported for Claude Code compatibility."
1344
+ },
1345
+ {
1346
+ path: ".claude/CLAUDE.md",
1347
+ scope: "project",
1348
+ level: "official",
1349
+ note: "Claude Code compatible."
1350
+ },
1351
+ {
1352
+ path: ".mastracode/AGENTS.md",
1353
+ scope: "project",
1354
+ level: "official",
1355
+ note: "Alternative project-level location."
1356
+ },
1357
+ {
1358
+ path: "~/.claude/CLAUDE.md",
1359
+ scope: "user",
1360
+ level: "official",
1361
+ note: "Claude Code compatible."
1362
+ },
1363
+ {
1364
+ path: "~/.mastracode/AGENTS.md",
1365
+ scope: "user",
1366
+ level: "official",
1367
+ note: "Global user-level instructions."
1368
+ }
1369
+ ];
1370
+ skills = [
1371
+ {
1372
+ path: ".mastracode/skills/",
1373
+ scope: "project",
1374
+ level: "official"
1375
+ },
1376
+ {
1377
+ path: "~/.mastracode/skills/",
1378
+ scope: "user",
1379
+ level: "official",
1380
+ note: "Global user-level skills."
1381
+ },
1382
+ {
1383
+ path: ".claude/skills/",
1384
+ scope: "project",
1385
+ level: "official",
1386
+ note: "Claude Code compatible."
1387
+ },
1388
+ {
1389
+ path: "~/.claude/skills/",
1390
+ scope: "user",
1391
+ level: "official",
1392
+ note: "Claude Code compatible."
1393
+ }
1394
+ ];
1395
+ commands = [{
1396
+ path: ".mastracode/commands/",
1397
+ scope: "project",
1398
+ level: "official"
1399
+ }, {
1400
+ path: "~/.mastracode/commands/",
1401
+ scope: "user",
1402
+ level: "official",
1403
+ note: "Global commands."
1404
+ }];
1405
+ hooks = [{
1406
+ path: ".mastracode/hooks.json",
1407
+ scope: "project",
1408
+ level: "official"
1409
+ }, {
1410
+ path: "~/.mastracode/hooks.json",
1411
+ scope: "user",
1412
+ level: "official",
1413
+ note: "Global hooks."
1414
+ }];
1415
+ invocation = null;
1416
+ detection = {
1417
+ envVars: [],
1418
+ projectMarkers: [".mastracode"]
1419
+ };
1420
+ };
1421
+ var Omp = class extends Harness {
1422
+ id = "omp";
1423
+ name = "OMP (oh-my-pi)";
1424
+ binaries = ["omp"];
1425
+ capabilities = {
1426
+ mcp: true,
1427
+ vision: true,
1428
+ audio: false,
1429
+ video: false,
1430
+ tools: true,
1431
+ streaming: true
1432
+ };
1433
+ config = [
1434
+ {
1435
+ path: "~/.omp/agent/config.yml",
1436
+ scope: "user",
1437
+ level: "official",
1438
+ note: "Agent dir overridable with PI_CODING_AGENT_DIR; profiles use ~/.omp/profiles/<name>/agent."
1439
+ },
1440
+ {
1441
+ path: "~/.omp/agent/settings.json",
1442
+ scope: "user",
1443
+ level: "official"
1444
+ },
1445
+ {
1446
+ path: "~/.omp/agent/mcp.json",
1447
+ scope: "user",
1448
+ level: "official",
1449
+ note: "MCP server configurations."
1450
+ },
1451
+ {
1452
+ path: ".omp/config.yml",
1453
+ scope: "project",
1454
+ level: "official"
1455
+ },
1456
+ {
1457
+ path: ".omp/settings.json",
1458
+ scope: "project",
1459
+ level: "official"
1460
+ }
1461
+ ];
1462
+ sessions = [
1463
+ {
1464
+ path: "~/.omp/agent/sessions/<dash-encoded-cwd>/<timestamp>_<session-id>.jsonl",
1465
+ scope: "data",
1466
+ level: "official",
1467
+ note: "Per-project session transcripts in JSONL format; session ids are UUIDv7."
1468
+ },
1469
+ {
1470
+ path: "~/.omp/agent/history.db",
1471
+ scope: "data",
1472
+ level: "official",
1473
+ note: "SQLite prompt history."
1474
+ },
1475
+ {
1476
+ path: "~/.omp/agent/agent.db",
1477
+ scope: "data",
1478
+ level: "official",
1479
+ note: "SQLite agent state database."
1480
+ }
1481
+ ];
1482
+ persistence = [
1483
+ {
1484
+ format: "YAML",
1485
+ level: "official",
1486
+ note: "config.yml configuration files."
1487
+ },
1488
+ {
1489
+ format: "JSON",
1490
+ level: "official",
1491
+ note: "settings.json and mcp.json."
1492
+ },
1493
+ {
1494
+ format: "JSONL",
1495
+ level: "official",
1496
+ note: "Session transcripts."
1497
+ },
1498
+ {
1499
+ format: "SQLite",
1500
+ level: "official",
1501
+ note: "Agent state, prompt history, model catalog (agent.db, history.db, models.db)."
1502
+ }
1503
+ ];
1504
+ instructions = [
1505
+ {
1506
+ path: "AGENTS.md",
1507
+ scope: "project",
1508
+ level: "official",
1509
+ note: "Discovered by walking up from cwd; CLAUDE.md and other vendor files loaded via compatibility providers (claude, codex, gemini, cursor, opencode, and more)."
1510
+ },
1511
+ {
1512
+ path: ".omp/AGENTS.md",
1513
+ scope: "project",
1514
+ level: "official"
1515
+ },
1516
+ {
1517
+ path: ".omp/RULES.md",
1518
+ scope: "project",
1519
+ level: "official",
1520
+ note: "Sticky always-apply rules."
1521
+ },
1522
+ {
1523
+ path: "~/.omp/agent/AGENTS.md",
1524
+ scope: "user",
1525
+ level: "official",
1526
+ note: "Global user-level instructions."
1527
+ },
1528
+ {
1529
+ path: "~/.omp/agent/RULES.md",
1530
+ scope: "user",
1531
+ level: "official",
1532
+ note: "Global sticky always-apply rules."
1533
+ },
1534
+ {
1535
+ path: "~/.omp/agent/PERSONALITY.md",
1536
+ scope: "user",
1537
+ level: "official",
1538
+ note: "Overrides the system prompt's personality block; replaces the configured personality preset."
1539
+ }
1540
+ ];
1541
+ skills = [
1542
+ {
1543
+ path: ".omp/skills/",
1544
+ scope: "project",
1545
+ level: "official",
1546
+ note: "Scanned in ancestor directories from cwd, closest first."
1547
+ },
1548
+ {
1549
+ path: "~/.omp/agent/skills/",
1550
+ scope: "user",
1551
+ level: "official"
1552
+ },
1553
+ {
1554
+ path: "~/.omp/agent/managed-skills/",
1555
+ scope: "user",
1556
+ level: "official",
1557
+ note: "Auto-generated managed skills."
1558
+ }
1559
+ ];
1560
+ commands = [{
1561
+ path: ".omp/commands/",
1562
+ scope: "project",
1563
+ level: "official"
1564
+ }, {
1565
+ path: "~/.omp/agent/commands/",
1566
+ scope: "user",
1567
+ level: "official",
1568
+ note: "Global user-level prompt commands."
1569
+ }];
1570
+ hooks = [];
1571
+ invocation = {
1572
+ args: ["-p", "{prompt}"],
1573
+ jsonArgs: [
1574
+ "-p",
1575
+ "--mode",
1576
+ "json",
1577
+ "{prompt}"
1578
+ ],
1579
+ noToolsArgs: [
1580
+ "-p",
1581
+ "--no-tools",
1582
+ "{prompt}"
1583
+ ],
1584
+ noToolsJsonArgs: [
1585
+ "-p",
1586
+ "--no-tools",
1587
+ "--mode",
1588
+ "json",
1589
+ "{prompt}"
1590
+ ],
1591
+ modelArgs: ["--model={model}"],
1592
+ level: "official",
1593
+ note: "Add --mode json for structured event output."
1594
+ };
1595
+ mcpConfigs = [{
1596
+ path: "~/.omp/agent/mcp.json",
1597
+ scope: "user",
1598
+ level: "official",
1599
+ format: "json",
1600
+ key: ["mcpServers"],
1601
+ dialect: "standard"
1602
+ }, {
1603
+ path: ".omp/mcp.json",
1604
+ scope: "project",
1605
+ level: "official",
1606
+ format: "json",
1607
+ key: ["mcpServers"],
1608
+ dialect: "standard"
1609
+ }];
1610
+ agentsFile = "~/.omp/agent/AGENTS.md";
1611
+ detection = {
1612
+ envVars: ["OMP_PROFILE", "PI_CODING_AGENT_DIR"],
1613
+ projectMarkers: [".omp"]
1614
+ };
1615
+ };
1616
+ var OpenCode = class extends Harness {
1617
+ id = "opencode";
1618
+ name = "OpenCode CLI";
1619
+ binaries = ["opencode"];
1620
+ capabilities = {
1621
+ mcp: true,
1622
+ vision: true,
1623
+ audio: false,
1624
+ video: false,
1625
+ tools: true,
1626
+ streaming: true
1627
+ };
1628
+ config = [
1629
+ {
1630
+ path: "~/.config/opencode/opencode.jsonc",
1631
+ scope: "user",
1632
+ level: "official",
1633
+ note: "XDG-based, same path on all platforms."
1634
+ },
1635
+ {
1636
+ path: "~/.config/opencode/opencode.json",
1637
+ scope: "user",
1638
+ level: "official"
1639
+ },
1640
+ {
1641
+ path: "opencode.jsonc",
1642
+ scope: "project",
1643
+ level: "official"
1644
+ },
1645
+ {
1646
+ path: "opencode.json",
1647
+ scope: "project",
1648
+ level: "official"
1649
+ },
1650
+ {
1651
+ path: ".opencode/opencode.jsonc",
1652
+ scope: "project",
1653
+ level: "official"
1654
+ },
1655
+ {
1656
+ path: ".opencode/opencode.json",
1657
+ scope: "project",
1658
+ level: "official"
1659
+ }
1660
+ ];
1661
+ sessions = [{
1662
+ path: "~/.local/share/opencode/opencode.db",
1663
+ scope: "data",
1664
+ level: "official",
1665
+ note: "SQLite database with sessions, messages, and parts. XDG-based, same path on all platforms."
1666
+ }];
1667
+ persistence = [{
1668
+ format: "JSON/JSONC",
1669
+ level: "official",
1670
+ note: "Config files."
1671
+ }, {
1672
+ format: "SQLite",
1673
+ level: "official",
1674
+ note: "Session and message storage via Drizzle ORM."
1675
+ }];
1676
+ instructions = [
1677
+ {
1678
+ path: "AGENTS.md",
1679
+ scope: "project",
1680
+ level: "official"
1681
+ },
1682
+ {
1683
+ path: "CLAUDE.md",
1684
+ scope: "project",
1685
+ level: "official",
1686
+ note: "Supported for Claude Code compatibility."
1687
+ },
1688
+ {
1689
+ path: "~/.config/opencode/AGENTS.md",
1690
+ scope: "user",
1691
+ level: "official",
1692
+ note: "Global user-level instructions."
1693
+ }
1694
+ ];
1695
+ skills = [
1696
+ {
1697
+ path: ".opencode/skills/",
1698
+ scope: "project",
1699
+ level: "official"
1700
+ },
1701
+ {
1702
+ path: ".claude/skills/",
1703
+ scope: "project",
1704
+ level: "official",
1705
+ note: "Claude Code compatible."
1706
+ },
1707
+ {
1708
+ path: ".agents/skills/",
1709
+ scope: "project",
1710
+ level: "official"
1711
+ },
1712
+ {
1713
+ path: "~/.claude/skills/",
1714
+ scope: "user",
1715
+ level: "official",
1716
+ note: "Claude Code compatible."
1717
+ },
1718
+ {
1719
+ path: "~/.agents/skills/",
1720
+ scope: "user",
1721
+ level: "official"
1722
+ }
1723
+ ];
1724
+ commands = [];
1725
+ hooks = [];
1726
+ invocation = {
1727
+ args: ["run", "{prompt}"],
1728
+ jsonArgs: [
1729
+ "run",
1730
+ "--format",
1731
+ "json",
1732
+ "{prompt}"
1733
+ ],
1734
+ modelArgs: ["--model", "{model}"],
1735
+ level: "official"
1736
+ };
1737
+ mcpConfigs = [{
1738
+ path: "~/.config/opencode/opencode.json",
1739
+ scope: "user",
1740
+ level: "official",
1741
+ format: "json",
1742
+ key: ["mcp"],
1743
+ dialect: "opencode"
1744
+ }, {
1745
+ path: "opencode.json",
1746
+ scope: "project",
1747
+ level: "official",
1748
+ format: "json",
1749
+ key: ["mcp"],
1750
+ dialect: "opencode"
1751
+ }];
1752
+ agentsFile = "~/.config/opencode/AGENTS.md";
1753
+ detection = {
1754
+ envVars: [],
1755
+ projectMarkers: [
1756
+ ".opencode",
1757
+ "opencode.jsonc",
1758
+ "opencode.json"
1759
+ ]
1760
+ };
1761
+ };
1762
+ const MODEL_HEADERS = [
1763
+ "provider",
1764
+ "model",
1765
+ "context",
1766
+ "max-out",
1767
+ "thinking",
1768
+ "images"
1769
+ ];
1770
+ function parseTokenCount(value) {
1771
+ const match = /^(\d+(?:\.\d+)?)([KM])?$/.exec(value);
1772
+ if (!match?.[1]) throw new Error(`Invalid Pi token count: ${JSON.stringify(value)}`);
1773
+ const multiplier = match[2] === "M" ? 1e6 : match[2] === "K" ? 1e3 : 1;
1774
+ return Number(match[1]) * multiplier;
1775
+ }
1776
+ function parseBoolean(value, column) {
1777
+ if (value === "yes") return true;
1778
+ if (value === "no") return false;
1779
+ throw new Error(`Invalid Pi ${column} value: ${JSON.stringify(value)}`);
1780
+ }
1781
+ var Pi = class extends Harness {
1782
+ id = "pi";
1783
+ name = "Pi Coding Agent";
1784
+ binaries = ["pi"];
1785
+ capabilities = {
1786
+ mcp: true,
1787
+ vision: true,
1788
+ audio: false,
1789
+ video: false,
1790
+ tools: true,
1791
+ streaming: true
1792
+ };
1793
+ config = [{
1794
+ path: "~/.pi/agent/settings.json",
1795
+ scope: "user",
1796
+ level: "official",
1797
+ note: "Config dir overridable with PI_CODING_AGENT_DIR."
1798
+ }, {
1799
+ path: ".pi/settings.json",
1800
+ scope: "project",
1801
+ level: "official"
1802
+ }];
1803
+ sessions = [{
1804
+ path: "~/.pi/agent/sessions/<dash-encoded-cwd>/<timestamp>_<session-id>.jsonl",
1805
+ scope: "data",
1806
+ level: "official",
1807
+ note: "Per-project session transcripts in JSONL format; session ids are UUIDv7. Overridable with PI_CODING_AGENT_SESSION_DIR or --session-dir."
1808
+ }];
1809
+ persistence = [{
1810
+ format: "JSON",
1811
+ level: "official",
1812
+ note: "Settings, auth, trust, models, and keybindings files."
1813
+ }, {
1814
+ format: "JSONL",
1815
+ level: "official",
1816
+ note: "Session transcripts."
1817
+ }];
1818
+ instructions = [
1819
+ {
1820
+ path: "AGENTS.md",
1821
+ scope: "project",
1822
+ level: "official",
1823
+ note: "Loaded from parent directories and cwd; CLAUDE.md accepted as an alternative, AGENTS.override.md takes precedence over both in a directory."
1824
+ },
1825
+ {
1826
+ path: ".pi/SYSTEM.md",
1827
+ scope: "project",
1828
+ level: "official",
1829
+ note: "Replaces the default system prompt; APPEND_SYSTEM.md appends instead."
1830
+ },
1831
+ {
1832
+ path: "~/.pi/agent/AGENTS.md",
1833
+ scope: "user",
1834
+ level: "official",
1835
+ note: "Global user-level instructions."
1836
+ },
1837
+ {
1838
+ path: "~/.pi/agent/SYSTEM.md",
1839
+ scope: "user",
1840
+ level: "official",
1841
+ note: "Global system prompt replacement; APPEND_SYSTEM.md appends instead."
1842
+ }
1843
+ ];
1844
+ skills = [{
1845
+ path: ".pi/skills/",
1846
+ scope: "project",
1847
+ level: "official"
1848
+ }, {
1849
+ path: "~/.pi/agent/skills/",
1850
+ scope: "user",
1851
+ level: "official",
1852
+ note: "Global user-level skills."
1853
+ }];
1854
+ commands = [{
1855
+ path: ".pi/prompts/",
1856
+ scope: "project",
1857
+ level: "official",
1858
+ note: "Prompt templates invoked as slash commands."
1859
+ }, {
1860
+ path: "~/.pi/agent/prompts/",
1861
+ scope: "user",
1862
+ level: "official",
1863
+ note: "Global user-level prompt templates."
1864
+ }];
1865
+ hooks = [];
1866
+ invocation = {
1867
+ args: ["-p", "{prompt}"],
1868
+ jsonArgs: [
1869
+ "-p",
1870
+ "--mode",
1871
+ "json",
1872
+ "{prompt}"
1873
+ ],
1874
+ noToolsArgs: [
1875
+ "-p",
1876
+ "--no-tools",
1877
+ "{prompt}"
1878
+ ],
1879
+ noToolsJsonArgs: [
1880
+ "-p",
1881
+ "--no-tools",
1882
+ "--mode",
1883
+ "json",
1884
+ "{prompt}"
1885
+ ],
1886
+ modelArgs: ["--model", "{model}"],
1887
+ level: "official",
1888
+ note: "Add --mode json for structured event output."
1889
+ };
1890
+ modelListing = {
1891
+ args: ["--list-models"],
1892
+ searchArgs: ["--list-models", "{search}"],
1893
+ level: "official",
1894
+ note: "Lists models with configured provider authentication; accepts an optional fuzzy search."
1895
+ };
1896
+ agentsFile = "~/.pi/agent/AGENTS.md";
1897
+ detection = {
1898
+ envVars: [
1899
+ "PI_CODING_AGENT",
1900
+ "PI_SESSION_ID",
1901
+ "PI_SESSION_FILE"
1902
+ ],
1903
+ projectMarkers: [".pi"]
1904
+ };
1905
+ parseModelListingOutput(stdout) {
1906
+ const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1907
+ const first = lines[0];
1908
+ if (!first) throw new Error("Unexpected empty Pi model-list output");
1909
+ if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
1910
+ const headers = first.split(/\s{2,}/);
1911
+ if (headers.length !== MODEL_HEADERS.length || headers.some((value, i) => value !== MODEL_HEADERS[i])) throw new Error(`Unexpected Pi model-list header: ${JSON.stringify(first)}`);
1912
+ return lines.slice(1).map((line) => {
1913
+ const columns = line.split(/\s{2,}/);
1914
+ const [provider, id, context, maxOutput, thinking, images] = columns;
1915
+ if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected Pi model-list row: ${JSON.stringify(line)}`);
1916
+ return {
1917
+ provider,
1918
+ id,
1919
+ contextWindow: parseTokenCount(context),
1920
+ maxOutputTokens: parseTokenCount(maxOutput),
1921
+ thinking: parseBoolean(thinking, "thinking"),
1922
+ images: parseBoolean(images, "images")
1923
+ };
1924
+ });
1925
+ }
1926
+ };
1927
+ const harnesses = [
1928
+ Antigravity,
1929
+ Claude,
1930
+ Codex,
1931
+ Cursor,
1932
+ Freebuff,
1933
+ Gemini,
1934
+ GitHubCopilot,
1935
+ Grok,
1936
+ MastraCode,
1937
+ Omp,
1938
+ OpenCode,
1939
+ Pi
1940
+ ];
1941
+ let registry;
1942
+ function getRegistry() {
1943
+ if (!registry) {
1944
+ registry = /* @__PURE__ */ new Map();
1945
+ for (const HarnessClass of harnesses) {
1946
+ const harness = new HarnessClass();
1947
+ registry.set(harness.id, harness);
1948
+ }
1949
+ }
1950
+ return registry;
1951
+ }
1952
+ function registerHarness(HarnessClass) {
1953
+ const harness = new HarnessClass();
1954
+ getRegistry().set(harness.id, harness);
1955
+ return harness;
1956
+ }
1957
+ function getHarness(id) {
1958
+ const harness = getRegistry().get(id);
1959
+ if (!harness) throw new Error(`Unknown harness: ${id}`);
1960
+ return harness;
1961
+ }
1962
+ function listHarnesses() {
1963
+ return [...getRegistry().keys()];
1964
+ }
1965
+ function isHarnessId(id) {
1966
+ return getRegistry().has(id);
1967
+ }
1968
+ function getAllHarnesses() {
1969
+ return [...getRegistry().values()];
1970
+ }
1971
+ function detectHarnessFromEnv() {
1972
+ for (const harness of getRegistry().values()) if (harness.detectEnv()) return harness;
1973
+ return null;
1974
+ }
1975
+ function detectProjectHarnesses(cwd) {
1976
+ return [...getRegistry().values()].filter((harness) => harness.detectProject(cwd));
1977
+ }
1978
+ function detectHarness(cwd) {
1979
+ const fromEnv = detectHarnessFromEnv();
1980
+ if (fromEnv) return fromEnv;
1981
+ let match = null;
1982
+ for (const harness of getRegistry().values()) if (harness.detectProject(cwd)) {
1983
+ if (match) return null;
1984
+ match = harness;
1985
+ }
1986
+ return match;
1987
+ }
1988
+ function resolveConfigs(harness, options) {
1989
+ return harness.resolveCandidates(harness.mcpConfigs, options).map((entry) => ({
1990
+ entry,
1991
+ path: isAbsolute(entry.path) ? entry.path : join(options.projectRoot ?? process.cwd(), entry.path)
1992
+ }));
1993
+ }
1994
+ function readIfExists$1(path) {
1995
+ try {
1996
+ return readFileSync(path, "utf8");
1997
+ } catch (error) {
1998
+ if (error.code === "ENOENT") return null;
1999
+ throw error;
2000
+ }
2001
+ }
2002
+ function drill(root, key, create) {
2003
+ let node = root;
2004
+ for (const part of key) {
2005
+ if (typeof node !== "object" || node === null) return void 0;
2006
+ const record = node;
2007
+ if (create && (typeof record[part] !== "object" || record[part] === null)) record[part] = {};
2008
+ node = record[part];
2009
+ }
2010
+ return typeof node === "object" && node !== null ? node : void 0;
2011
+ }
2012
+ function isObjectRecord(value) {
2013
+ return typeof value === "object" && value !== null;
2014
+ }
2015
+ function asStringRecord(value) {
2016
+ if (!isObjectRecord(value) || Array.isArray(value)) return void 0;
2017
+ const out = {};
2018
+ for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
2019
+ return Object.keys(out).length > 0 ? out : void 0;
2020
+ }
2021
+ function asStringArray(value) {
2022
+ if (!Array.isArray(value)) return void 0;
2023
+ return value.filter((item) => typeof item === "string");
2024
+ }
2025
+ function compact(source) {
2026
+ return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== void 0));
2027
+ }
2028
+ function stringField(record, key) {
2029
+ const value = record[key];
2030
+ return typeof value === "string" ? value : void 0;
2031
+ }
2032
+ function booleanField(record, key) {
2033
+ const value = record[key];
2034
+ return typeof value === "boolean" ? value : void 0;
2035
+ }
2036
+ function normalizeAntigravity(name, raw) {
2037
+ const url = stringField(raw, "serverUrl");
2038
+ return compact({
2039
+ name,
2040
+ transport: url ? "sse" : "stdio",
2041
+ command: stringField(raw, "command"),
2042
+ args: asStringArray(raw.args),
2043
+ env: asStringRecord(raw.env),
2044
+ url
2045
+ });
2046
+ }
2047
+ function normalizeOpenCode(name, raw) {
2048
+ const command = asStringArray(raw.command) ?? [];
2049
+ const url = stringField(raw, "url");
2050
+ return compact({
2051
+ name,
2052
+ transport: raw.type === "remote" || url ? "http" : "stdio",
2053
+ command: command[0],
2054
+ args: command.length > 1 ? command.slice(1) : void 0,
2055
+ env: asStringRecord(raw.environment),
2056
+ url,
2057
+ headers: asStringRecord(raw.headers),
2058
+ enabled: booleanField(raw, "enabled")
2059
+ });
2060
+ }
2061
+ function standardUrl(raw) {
2062
+ return stringField(raw, "httpUrl") ?? stringField(raw, "url");
2063
+ }
2064
+ function standardTransport(declared, url) {
2065
+ if (declared === "sse") return "sse";
2066
+ if (declared === "http" || url) return "http";
2067
+ return "stdio";
2068
+ }
2069
+ function normalizeStandard(name, raw) {
2070
+ const url = standardUrl(raw);
2071
+ return compact({
2072
+ name,
2073
+ transport: standardTransport(stringField(raw, "type"), url),
2074
+ command: stringField(raw, "command"),
2075
+ args: asStringArray(raw.args),
2076
+ env: asStringRecord(raw.env),
2077
+ url,
2078
+ headers: asStringRecord(raw.headers),
2079
+ enabled: booleanField(raw, "enabled")
2080
+ });
2081
+ }
2082
+ function normalizeEntry(name, raw, dialect) {
2083
+ if (dialect === "antigravity") return normalizeAntigravity(name, raw);
2084
+ if (dialect === "opencode") return normalizeOpenCode(name, raw);
2085
+ return normalizeStandard(name, raw);
2086
+ }
2087
+ function denormalizeAntigravity(server) {
2088
+ if (server.url) return { serverUrl: server.url };
2089
+ return compact({
2090
+ command: server.command,
2091
+ args: server.args?.length ? server.args : void 0,
2092
+ env: server.env
2093
+ });
2094
+ }
2095
+ function denormalizeOpenCode(server) {
2096
+ if (server.url) return compact({
2097
+ type: "remote",
2098
+ url: server.url,
2099
+ headers: server.headers,
2100
+ enabled: server.enabled
2101
+ });
2102
+ return compact({
2103
+ type: "local",
2104
+ command: [server.command ?? "", ...server.args ?? []],
2105
+ environment: server.env,
2106
+ enabled: server.enabled
2107
+ });
2108
+ }
2109
+ function denormalizeStandard(server) {
2110
+ return compact({
2111
+ type: server.transport,
2112
+ command: server.command,
2113
+ args: server.args?.length ? server.args : void 0,
2114
+ env: server.env,
2115
+ url: server.url,
2116
+ headers: server.headers,
2117
+ enabled: server.enabled
2118
+ });
2119
+ }
2120
+ function denormalizeEntry(server, dialect) {
2121
+ if (dialect === "antigravity") return denormalizeAntigravity(server);
2122
+ if (dialect === "opencode") return denormalizeOpenCode(server);
2123
+ return denormalizeStandard(server);
2124
+ }
2125
+ function listingBase(entry, path) {
2126
+ return compact({
2127
+ path,
2128
+ scope: entry.scope,
2129
+ format: entry.format,
2130
+ level: entry.level,
2131
+ note: entry.note,
2132
+ exists: false,
2133
+ servers: []
2134
+ });
2135
+ }
2136
+ function configServerMap(raw, entry) {
2137
+ const parsed = entry.format === "toml" ? parse(raw) : JSON.parse(raw);
2138
+ return drill(isObjectRecord(parsed) ? parsed : {}, entry.key) ?? {};
2139
+ }
2140
+ function normalizeServerMap(map, dialect) {
2141
+ const servers = [];
2142
+ for (const [name, rawEntry] of Object.entries(map)) if (isObjectRecord(rawEntry)) servers.push(normalizeEntry(name, rawEntry, dialect));
2143
+ return servers;
2144
+ }
2145
+ function listMcpConfig(entry, path) {
2146
+ const base = listingBase(entry, path);
2147
+ try {
2148
+ const raw = readIfExists$1(path);
2149
+ if (raw === null) return base;
2150
+ const servers = normalizeServerMap(configServerMap(raw, entry), entry.dialect);
2151
+ return {
2152
+ ...base,
2153
+ exists: true,
2154
+ servers
2155
+ };
2156
+ } catch (error) {
2157
+ return {
2158
+ ...base,
2159
+ exists: true,
2160
+ error: error instanceof Error ? error.message : String(error)
2161
+ };
2162
+ }
2163
+ }
2164
+ function listMcpServers(harness, options = {}) {
2165
+ return resolveConfigs(harness, options).map(({ entry, path }) => listMcpConfig(entry, path));
2166
+ }
2167
+ function writableConfig(harness, scope, options) {
2168
+ const config = resolveConfigs(harness, options).find(({ entry }) => entry.scope === scope);
2169
+ if (!config) throw new Error(`Harness ${harness.id} has no ${scope}-scope MCP config`);
2170
+ return config;
2171
+ }
2172
+ function writeAtomically(path, content) {
2173
+ mkdirSync(dirname(path), { recursive: true });
2174
+ const temp = join(dirname(path), `.${Date.now()}-${process.pid}.mcp.tmp`);
2175
+ writeFileSync(temp, content);
2176
+ renameSync(temp, path);
2177
+ }
2178
+ function escapeRegExp(value) {
2179
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
2180
+ }
2181
+ function tomlValue(value) {
2182
+ return JSON.stringify(value);
2183
+ }
2184
+ function tomlKeyPart(name) {
2185
+ return /^[A-Za-z0-9_-]+$/.test(name) ? name : JSON.stringify(name);
2186
+ }
2187
+ function tomlSections(key, server) {
2188
+ const head = [...key, server.name].map(tomlKeyPart).join(".");
2189
+ const lines = [`[${head}]`];
2190
+ if (server.command) lines.push(`command = ${tomlValue(server.command)}`);
2191
+ if (server.args?.length) lines.push(`args = [${server.args.map(tomlValue).join(", ")}]`);
2192
+ if (server.url) lines.push(`url = ${tomlValue(server.url)}`);
2193
+ if (server.enabled !== void 0) lines.push(`enabled = ${server.enabled}`);
2194
+ for (const [subKey, record] of [["env", server.env], ["headers", server.headers]]) {
2195
+ if (!record) continue;
2196
+ lines.push("", `[${head}.${subKey}]`);
2197
+ for (const [k, v] of Object.entries(record)) lines.push(`${tomlKeyPart(k)} = ${tomlValue(v)}`);
2198
+ }
2199
+ return `${lines.join("\n")}\n`;
2200
+ }
2201
+ function stripTomlServer(raw, key, name) {
2202
+ const prefix = escapeRegExp(key.map(tomlKeyPart).join("."));
2203
+ const namePart = `(?:${escapeRegExp(tomlKeyPart(name))}|${escapeRegExp(JSON.stringify(name))})`;
2204
+ const sectionRe = new RegExp(`^\\s*\\[${prefix}\\.${namePart}(\\.[^\\]]+)?\\]`);
2205
+ const bareTableRe = new RegExp(`^\\s*\\[${prefix}\\]\\s*(#.*)?$`);
2206
+ const anyHeaderRe = /^\s*\[/;
2207
+ const inlineRe = new RegExp(`^\\s*${namePart}\\s*=`);
2208
+ const kept = [];
2209
+ let inTargetSection = false;
2210
+ let inBareTable = false;
2211
+ for (const line of raw.split("\n")) {
2212
+ if (anyHeaderRe.test(line)) {
2213
+ inTargetSection = sectionRe.test(line);
2214
+ inBareTable = bareTableRe.test(line);
2215
+ } else if (inBareTable && inlineRe.test(line)) continue;
2216
+ if (inTargetSection) continue;
2217
+ kept.push(line);
2218
+ }
2219
+ return kept.join("\n");
2220
+ }
2221
+ function serverMapFromToml(raw, key) {
2222
+ return drill(parse(raw), key) ?? {};
2223
+ }
2224
+ function assertTomlEdit(beforeMap, after, key, name, shouldExist) {
2225
+ let afterMap;
2226
+ try {
2227
+ afterMap = serverMapFromToml(after, key);
2228
+ } catch (error) {
2229
+ throw new Error(`TOML edit produced an unparsable file, aborting: ${error instanceof Error ? error.message : String(error)}`);
2230
+ }
2231
+ if (Object.hasOwn(afterMap, name) !== shouldExist) throw new Error(`TOML edit did not leave server ${name} ${shouldExist ? "present" : "absent"}, aborting`);
2232
+ for (const other of /* @__PURE__ */ new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)])) {
2233
+ if (other === name) continue;
2234
+ if (JSON.stringify(beforeMap[other]) !== JSON.stringify(afterMap[other])) throw new Error(`TOML edit touched servers other than ${name}, aborting`);
2235
+ }
2236
+ }
2237
+ function addTomlServer(path, key, server) {
2238
+ const before = readIfExists$1(path) ?? "";
2239
+ const beforeMap = serverMapFromToml(before, key);
2240
+ const replaced = Object.hasOwn(beforeMap, server.name);
2241
+ const body = (replaced ? stripTomlServer(before, key, server.name) : before).replace(/\s+$/, "");
2242
+ const after = body ? `${body}\n\n${tomlSections(key, server)}` : tomlSections(key, server);
2243
+ assertTomlEdit(beforeMap, after, key, server.name, true);
2244
+ writeAtomically(path, after);
2245
+ return replaced;
2246
+ }
2247
+ function removeTomlServer(path, key, name) {
2248
+ const before = readIfExists$1(path);
2249
+ if (before === null) return false;
2250
+ const beforeMap = serverMapFromToml(before, key);
2251
+ if (!Object.hasOwn(beforeMap, name)) return false;
2252
+ const after = stripTomlServer(before, key, name);
2253
+ assertTomlEdit(beforeMap, after, key, name, false);
2254
+ writeAtomically(path, after);
2255
+ return true;
2256
+ }
2257
+ function addMcpServer(harness, server, scope = "user", options = {}) {
2258
+ const { entry, path } = writableConfig(harness, scope, options);
2259
+ if (entry.format === "toml") return {
2260
+ path,
2261
+ replaced: addTomlServer(path, entry.key, server)
2262
+ };
2263
+ const root = JSON.parse(readIfExists$1(path) ?? "{}");
2264
+ const map = drill(root, entry.key, "create");
2265
+ if (!map) throw new Error(`Config at ${path} has a non-object at ${entry.key.join(".")}`);
2266
+ const replaced = Object.hasOwn(map, server.name);
2267
+ map[server.name] = denormalizeEntry(server, entry.dialect);
2268
+ writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
2269
+ return {
2270
+ path,
2271
+ replaced
2272
+ };
2273
+ }
2274
+ function removeMcpServer(harness, name, scope = "user", options = {}) {
2275
+ const { entry, path } = writableConfig(harness, scope, options);
2276
+ if (entry.format === "toml") return {
2277
+ path,
2278
+ removed: removeTomlServer(path, entry.key, name)
2279
+ };
2280
+ const raw = readIfExists$1(path);
2281
+ if (raw === null) return {
2282
+ path,
2283
+ removed: false
2284
+ };
2285
+ const root = JSON.parse(raw);
2286
+ const map = drill(root, entry.key);
2287
+ if (!map || !Object.hasOwn(map, name)) return {
2288
+ path,
2289
+ removed: false
2290
+ };
2291
+ delete map[name];
2292
+ writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
2293
+ return {
2294
+ path,
2295
+ removed: true
2296
+ };
2297
+ }
2298
+ function trailingJsoncComma(text, index) {
2299
+ const significant = text.slice(index + 1).replaceAll(/\/\/[^\n]*|\/\*[\s\S]*?\*\/|\s+/g, "");
2300
+ return significant.startsWith("}") || significant.startsWith("]");
2301
+ }
2302
+ function scanJsoncCode(context) {
2303
+ const { character, next } = context;
2304
+ if (character === "\"") return {
2305
+ state: "string",
2306
+ output: character,
2307
+ advance: 0
2308
+ };
2309
+ if (character === "/" && next === "/") return {
2310
+ state: "line-comment",
2311
+ output: "",
2312
+ advance: 1
2313
+ };
2314
+ if (character === "/" && next === "*") return {
2315
+ state: "block-comment",
2316
+ output: "",
2317
+ advance: 1
2318
+ };
2319
+ if (character === "," && trailingJsoncComma(context.text, context.index)) return {
2320
+ state: "code",
2321
+ output: "",
2322
+ advance: 0
2323
+ };
2324
+ return {
2325
+ state: "code",
2326
+ output: character,
2327
+ advance: 0
2328
+ };
2329
+ }
2330
+ function scanJsoncString({ character }) {
2331
+ if (character === "\\") return {
2332
+ state: "escape",
2333
+ output: character,
2334
+ advance: 0
2335
+ };
2336
+ if (character === "\"") return {
2337
+ state: "code",
2338
+ output: character,
2339
+ advance: 0
2340
+ };
2341
+ return {
2342
+ state: "string",
2343
+ output: character,
2344
+ advance: 0
2345
+ };
2346
+ }
2347
+ function scanJsoncEscape({ character }) {
2348
+ return {
2349
+ state: "string",
2350
+ output: character,
2351
+ advance: 0
2352
+ };
2353
+ }
2354
+ function scanJsoncLineComment({ character }) {
2355
+ if (character === "\n") return {
2356
+ state: "code",
2357
+ output: character,
2358
+ advance: 0
2359
+ };
2360
+ return {
2361
+ state: "line-comment",
2362
+ output: "",
2363
+ advance: 0
2364
+ };
2365
+ }
2366
+ function scanJsoncBlockComment({ character, next }) {
2367
+ if (character === "*" && next === "/") return {
2368
+ state: "code",
2369
+ output: "",
2370
+ advance: 1
2371
+ };
2372
+ return {
2373
+ state: "block-comment",
2374
+ output: "",
2375
+ advance: 0
2376
+ };
2377
+ }
2378
+ const JSONC_SCANNERS = {
2379
+ code: scanJsoncCode,
2380
+ string: scanJsoncString,
2381
+ escape: scanJsoncEscape,
2382
+ "line-comment": scanJsoncLineComment,
2383
+ "block-comment": scanJsoncBlockComment
2384
+ };
2385
+ function parseJsonc(text) {
2386
+ let out = "";
2387
+ let state = "code";
2388
+ for (let index = 0; index < text.length; index++) {
2389
+ const character = text[index];
2390
+ const step = JSONC_SCANNERS[state]({
2391
+ text,
2392
+ index,
2393
+ character,
2394
+ next: text[index + 1]
2395
+ });
2396
+ out += step.output;
2397
+ state = step.state;
2398
+ index += step.advance;
2399
+ }
2400
+ return JSON.parse(out);
2401
+ }
2402
+ function masterMcpPath(options = {}) {
2403
+ return join(agntnConfigDir(options), "mcp.jsonc");
2404
+ }
2405
+ function canonical(server) {
2406
+ return JSON.stringify(Object.fromEntries(Object.entries(server).sort(([a], [b]) => a.localeCompare(b))));
2407
+ }
2408
+ function expandHome(value, home) {
2409
+ return value.replace(/^~(?=\/|$)/, home).replaceAll("${HOME}", home);
2410
+ }
2411
+ function expandServerHome(server, home) {
2412
+ return compact({
2413
+ ...server,
2414
+ command: server.command === void 0 ? void 0 : expandHome(server.command, home),
2415
+ args: server.args?.map((arg) => expandHome(arg, home)),
2416
+ env: server.env ? Object.fromEntries(Object.entries(server.env).map(([k, v]) => [k, expandHome(v, home)])) : void 0
2417
+ });
2418
+ }
2419
+ function requiredMasterServerMap(root, path) {
2420
+ const map = drill(root, ["mcpServers"]);
2421
+ if (!map) throw new Error(`Master MCP list at ${path} has no mcpServers object`);
2422
+ return map;
2423
+ }
2424
+ function normalizeMasterServers(map, home) {
2425
+ const servers = [];
2426
+ for (const [name, rawEntry] of Object.entries(map)) if (isObjectRecord(rawEntry)) servers.push(expandServerHome(normalizeEntry(name, rawEntry, "standard"), home));
2427
+ return servers;
2428
+ }
2429
+ function masterExcludes(value, path) {
2430
+ if (value === void 0) return [];
2431
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`Master MCP list at ${path} has an invalid excludes: expected an array of harness ids`);
2432
+ return value;
2433
+ }
2434
+ function readMasterMcpServers(options = {}) {
2435
+ const path = masterMcpPath(options);
2436
+ const raw = readIfExists$1(path);
2437
+ if (raw === null) throw new Error(`No master MCP list at ${path}`);
2438
+ const parsed = parseJsonc(raw);
2439
+ const root = isObjectRecord(parsed) ? parsed : {};
2440
+ return {
2441
+ path,
2442
+ servers: normalizeMasterServers(requiredMasterServerMap(root, path), options.homeDir ?? homedir()),
2443
+ excludes: masterExcludes(root.excludes, path)
2444
+ };
2445
+ }
2446
+ function optionalPath(path) {
2447
+ return path === void 0 ? {} : { path };
2448
+ }
2449
+ function existingServers(listing) {
2450
+ return new Map((listing?.servers ?? []).map((server) => [server.name, canonical(server)]));
2451
+ }
2452
+ function withdrawMasterServersFromExcludedHarness(harness, existing, masterNames, initialPath, options) {
2453
+ const results = [];
2454
+ let path = initialPath;
2455
+ for (const name of existing.keys()) {
2456
+ if (!masterNames.has(name)) continue;
2457
+ const removed = removeMcpServer(harness, name, "user", options);
2458
+ if (removed.removed) {
2459
+ path = removed.path;
2460
+ results.push({
2461
+ name,
2462
+ action: "removed"
2463
+ });
2464
+ }
2465
+ }
2466
+ return {
2467
+ id: harness.id,
2468
+ ...optionalPath(path),
2469
+ excluded: true,
2470
+ results
2471
+ };
2472
+ }
2473
+ function syncIncludedHarness(harness, master, existing, masterNames, initialPath, options) {
2474
+ const results = [];
2475
+ let path = initialPath;
2476
+ for (const server of master.servers) {
2477
+ if (existing.get(server.name) === canonical(server)) {
2478
+ results.push({
2479
+ name: server.name,
2480
+ action: "unchanged"
2481
+ });
2482
+ continue;
2483
+ }
2484
+ const written = addMcpServer(harness, server, "user", options);
2485
+ path = written.path;
2486
+ results.push({
2487
+ name: server.name,
2488
+ action: written.replaced ? "replaced" : "added"
2489
+ });
2490
+ }
2491
+ for (const name of existing.keys()) {
2492
+ if (masterNames.has(name)) continue;
2493
+ const removed = removeMcpServer(harness, name, "user", options);
2494
+ if (removed.removed) {
2495
+ path = removed.path;
2496
+ results.push({
2497
+ name,
2498
+ action: "removed"
2499
+ });
2500
+ }
2501
+ }
2502
+ return {
2503
+ id: harness.id,
2504
+ ...optionalPath(path),
2505
+ results
2506
+ };
2507
+ }
2508
+ function syncMcpTarget(harness, master, masterNames, options) {
2509
+ if (!harness.mcpConfigs.some((candidate) => candidate.scope === "user")) return {
2510
+ id: harness.id,
2511
+ skipped: "no user-scope MCP config",
2512
+ results: []
2513
+ };
2514
+ const listing = listMcpServers(harness, options).find((item) => item.scope === "user");
2515
+ if (listing?.error) return {
2516
+ id: harness.id,
2517
+ path: listing.path,
2518
+ skipped: `existing config is unreadable: ${listing.error}`,
2519
+ results: []
2520
+ };
2521
+ const existing = existingServers(listing);
2522
+ if (master.excludes.includes(harness.id)) return withdrawMasterServersFromExcludedHarness(harness, existing, masterNames, listing?.path, options);
2523
+ return syncIncludedHarness(harness, master, existing, masterNames, listing?.path, options);
2524
+ }
2525
+ function syncMcpServers(harnesses, options = {}) {
2526
+ const master = readMasterMcpServers(options);
2527
+ const masterNames = new Set(master.servers.map((server) => server.name));
2528
+ return {
2529
+ source: master.path,
2530
+ servers: master.servers.map((server) => server.name),
2531
+ targets: harnesses.map((harness) => syncMcpTarget(harness, master, masterNames, options))
2532
+ };
2533
+ }
2534
+ function readIfExists(path) {
2535
+ try {
2536
+ return readFileSync(path, "utf8");
2537
+ } catch (error) {
2538
+ if (error.code === "ENOENT") return null;
2539
+ throw error;
2540
+ }
2541
+ }
2542
+ function agentsConfigRecord(raw, configPath) {
2543
+ const parsed = parseJsonc(raw);
2544
+ if (typeof parsed !== "object" || parsed === null) throw new Error(`Agents config at ${configPath} is not an object`);
2545
+ return parsed;
2546
+ }
2547
+ function agentsSource(value, defaultSource, options) {
2548
+ if (typeof value !== "string") return defaultSource;
2549
+ const expanded = resolvePathTemplate(value, options);
2550
+ return isAbsolute(expanded) ? expanded : join(agntnConfigDir(options), expanded);
2551
+ }
2552
+ function agentsExcludes(value, configPath) {
2553
+ if (value === void 0) return [];
2554
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`Agents config at ${configPath} has an invalid excludes: expected an array of harness ids`);
2555
+ return value;
2556
+ }
2557
+ function companionPath(value, configPath) {
2558
+ const normalized = normalize(value);
2559
+ if (normalized === "." || normalized === ".." || normalized.startsWith(`..${sep}`) || isAbsolute(normalized)) throw new Error(`Agents config at ${configPath} has an unsafe companion path: ${JSON.stringify(normalized)}`);
2560
+ return normalized.endsWith(sep) ? normalized.slice(0, -sep.length) : normalized;
2561
+ }
2562
+ function comparablePath(path) {
2563
+ return resolve(path).toLowerCase();
2564
+ }
2565
+ function agentsCompanions(value, configPath) {
2566
+ if (value === void 0) return [];
2567
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`Agents config at ${configPath} has invalid companions: expected an array of relative paths`);
2568
+ const companions = value.map((path) => companionPath(path, configPath));
2569
+ const comparable = companions.map(comparablePath);
2570
+ if (new Set(comparable).size !== comparable.length) throw new Error(`Agents config at ${configPath} has duplicate companion paths`);
2571
+ return companions;
2572
+ }
2573
+ function readAgentsConfig(options = {}) {
2574
+ const configPath = join(agntnConfigDir(options), "agents.jsonc");
2575
+ const defaults = {
2576
+ source: join(agntnConfigDir(options), "AGENTS.md"),
2577
+ companions: [],
2578
+ excludes: []
2579
+ };
2580
+ const raw = readIfExists(configPath);
2581
+ if (raw === null) return defaults;
2582
+ const record = agentsConfigRecord(raw, configPath);
2583
+ return {
2584
+ source: agentsSource(record.source, defaults.source, options),
2585
+ companions: agentsCompanions(record.companions, configPath),
2586
+ excludes: agentsExcludes(record.excludes, configPath),
2587
+ configPath
2588
+ };
2589
+ }
2590
+ function inspectRegularFile(path, source) {
2591
+ const content = readIfExists(path) ?? "";
2592
+ const sourceContent = readIfExists(source) ?? "";
2593
+ if (content.trim() === "" || content === sourceContent) return { kind: "identical-file" };
2594
+ return { kind: "diverged-file" };
2595
+ }
2596
+ function inspectTarget(path, source) {
2597
+ let stats;
2598
+ try {
2599
+ stats = lstatSync(path);
2600
+ } catch (error) {
2601
+ if (error.code === "ENOENT") return { kind: "missing" };
2602
+ throw error;
2603
+ }
2604
+ if (stats.isSymbolicLink()) {
2605
+ const target = readlinkSync(path);
2606
+ return (isAbsolute(target) ? target : resolve(dirname(path), target)) === source ? { kind: "correct-link" } : { kind: "wrong-link" };
2607
+ }
2608
+ return inspectRegularFile(path, source);
2609
+ }
2610
+ function relink(path, source) {
2611
+ mkdirSync(dirname(path), { recursive: true });
2612
+ const temp = join(dirname(path), `.${Date.now()}-${process.pid}.agents.tmp`);
2613
+ symlinkSync(source, temp);
2614
+ renameSync(temp, path);
2615
+ }
2616
+ const CHECK_ACTION = {
2617
+ missing: "linked",
2618
+ "correct-link": "unchanged",
2619
+ "wrong-link": "relinked",
2620
+ "identical-file": "relinked",
2621
+ "diverged-file": "adopted"
2622
+ };
2623
+ function applyFileTarget(id, path, source, state, options) {
2624
+ if (state.kind === "diverged-file") {
2625
+ const backupDir = join(agntnConfigDir(options), "diverged");
2626
+ mkdirSync(backupDir, { recursive: true });
2627
+ const backup = join(backupDir, `${id}-${basename(path)}-${Date.now()}.md`);
2628
+ renameSync(path, backup);
2629
+ relink(path, source);
2630
+ return {
2631
+ path,
2632
+ action: "adopted",
2633
+ detail: backup
2634
+ };
2635
+ }
2636
+ if (state.kind === "wrong-link") unlinkSync(path);
2637
+ if (state.kind === "wrong-link" || state.kind === "identical-file") {
2638
+ relink(path, source);
2639
+ return {
2640
+ path,
2641
+ action: "relinked"
2642
+ };
2643
+ }
2644
+ relink(path, source);
2645
+ return {
2646
+ path,
2647
+ action: "linked"
2648
+ };
2649
+ }
2650
+ function syncFileTarget(id, path, source, check, options) {
2651
+ const state = inspectTarget(path, source);
2652
+ if (state.kind === "correct-link") return {
2653
+ path,
2654
+ action: "unchanged"
2655
+ };
2656
+ if (check) return {
2657
+ path,
2658
+ action: CHECK_ACTION[state.kind],
2659
+ detail: "check mode: not applied"
2660
+ };
2661
+ return applyFileTarget(id, path, source, state, options);
2662
+ }
2663
+ function syncTarget(harness, config, check, options) {
2664
+ if (config.excludes.includes(harness.id)) return {
2665
+ id: harness.id,
2666
+ action: "skipped",
2667
+ detail: "excluded by the agents config"
2668
+ };
2669
+ if (!harness.agentsFile) return {
2670
+ id: harness.id,
2671
+ action: "skipped",
2672
+ detail: "no stable user-scope instructions file"
2673
+ };
2674
+ const path = resolvePathTemplate(harness.agentsFile, options);
2675
+ const target = syncFileTarget(harness.id, path, config.source, check, options);
2676
+ if (config.companions.length === 0) return {
2677
+ id: harness.id,
2678
+ ...target
2679
+ };
2680
+ const companions = config.companions.map((relativePath) => {
2681
+ const source = resolve(dirname(config.source), relativePath);
2682
+ const companionPath = resolve(dirname(path), relativePath);
2683
+ return {
2684
+ source,
2685
+ ...syncFileTarget(harness.id, companionPath, source, check, options)
2686
+ };
2687
+ });
2688
+ return {
2689
+ id: harness.id,
2690
+ ...target,
2691
+ companions
2692
+ };
2693
+ }
2694
+ function preflightCompanions(harnesses, config, options) {
2695
+ for (const relativePath of config.companions) {
2696
+ const source = resolve(dirname(config.source), relativePath);
2697
+ if (comparablePath(source) === comparablePath(config.source)) throw new Error(`Companion path resolves to the master agents file: ${relativePath}`);
2698
+ if (readIfExists(source) === null) throw new Error(`No companion agents file at ${source}`);
2699
+ }
2700
+ for (const harness of harnesses) {
2701
+ if (config.excludes.includes(harness.id) || !harness.agentsFile) continue;
2702
+ const path = resolvePathTemplate(harness.agentsFile, options);
2703
+ const collision = config.companions.find((relativePath) => comparablePath(resolve(dirname(path), relativePath)) === comparablePath(path));
2704
+ if (collision !== void 0) throw new Error(`Companion path collides with ${harness.id} instructions: ${collision}`);
2705
+ }
2706
+ }
2707
+ function syncAgentsFiles(harnesses, check = false, options = {}) {
2708
+ const config = readAgentsConfig(options);
2709
+ if (readIfExists(config.source) === null) throw new Error(`No master agents file at ${config.source}`);
2710
+ preflightCompanions(harnesses, config, options);
2711
+ return {
2712
+ source: config.source,
2713
+ check,
2714
+ targets: harnesses.map((harness) => syncTarget(harness, config, check, options))
2715
+ };
2716
+ }
2717
+ export { Harness, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
2718
+
2719
+ //# sourceMappingURL=agents-sync.mjs.map