@agntn/harnesses 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +35 -5
  2. package/dist/_chunks/agents.mjs +5 -3
  3. package/dist/_chunks/agents.mjs.map +1 -1
  4. package/dist/_chunks/mcp-servers.mjs +5 -3
  5. package/dist/_chunks/mcp-servers.mjs.map +1 -1
  6. package/dist/_chunks/mcp.mjs +10 -1
  7. package/dist/_chunks/mcp.mjs.map +1 -1
  8. package/dist/_chunks/output.mjs +7 -1
  9. package/dist/_chunks/output.mjs.map +1 -1
  10. package/dist/_chunks/prompts.mjs +4 -2
  11. package/dist/_chunks/prompts.mjs.map +1 -1
  12. package/dist/_chunks/{prompt-sync.d.mts → skills-sync.d.mts} +111 -53
  13. package/dist/_chunks/skills-sync.d.mts.map +1 -0
  14. package/dist/_chunks/{prompt-sync.mjs → skills-sync.mjs} +392 -100
  15. package/dist/_chunks/skills-sync.mjs.map +1 -0
  16. package/dist/_chunks/skills.mjs +30 -0
  17. package/dist/_chunks/skills.mjs.map +1 -0
  18. package/dist/_chunks/tool-operations.mjs +36 -8
  19. package/dist/_chunks/tool-operations.mjs.map +1 -1
  20. package/dist/_chunks/tool-schemas.d.mts +21 -13
  21. package/dist/_chunks/tool-schemas.d.mts.map +1 -1
  22. package/dist/_chunks/tool-schemas.mjs +7 -1
  23. package/dist/_chunks/tool-schemas.mjs.map +1 -1
  24. package/dist/_chunks/types.mjs +5 -2
  25. package/dist/_chunks/types.mjs.map +1 -1
  26. package/dist/cli.mjs +9 -3
  27. package/dist/cli.mjs.map +1 -1
  28. package/dist/index.d.mts +16 -2
  29. package/dist/index.d.mts.map +1 -1
  30. package/dist/index.mjs +5 -3
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/mcp.d.mts +2 -0
  33. package/dist/mcp.d.mts.map +1 -1
  34. package/dist/mcp.mjs +6256 -3
  35. package/dist/mcp.mjs.map +1 -1
  36. package/dist/tool-operations.d.mts +14 -3
  37. package/dist/tool-operations.d.mts.map +1 -1
  38. package/dist/tool-operations.mjs +3 -3
  39. package/dist/tool-schemas.d.mts +2 -2
  40. package/dist/tool-schemas.mjs +1 -1
  41. package/dist/typebox.LICENSE +23 -0
  42. package/package.json +19 -19
  43. package/packages/omp/extensions/harnesses.ts +27 -40
  44. package/packages/pi/extensions/harnesses.ts +26 -45
  45. package/packages/shared/tui.ts +13 -0
  46. package/dist/THIRD-PARTY-LICENSES.md +0 -36
  47. package/dist/_chunks/libs/typebox.mjs +0 -5439
  48. package/dist/_chunks/libs/typebox.mjs.map +0 -1
  49. package/dist/_chunks/prompt-sync.d.mts.map +0 -1
  50. package/dist/_chunks/prompt-sync.mjs.map +0 -1
@@ -8,6 +8,7 @@ import os, { homedir } from "node:os";
8
8
  import { stripVTControlCharacters } from "node:util";
9
9
  import { parse } from "smol-toml";
10
10
  import { randomUUID } from "node:crypto";
11
+ //#region src/resolve.ts
11
12
  const PATH_MARKERS = /^~(?=\/|$)|\$\{HOME\}|\$\{PROJECT_ROOT\}|\$\{TMPDIR\}|%([^%]+)%/g;
12
13
  function resolvePathTemplate(template, options = {}) {
13
14
  const homeDir = options.homeDir ?? os.homedir();
@@ -27,6 +28,8 @@ function agntnDataDir(options = {}) {
27
28
  const base = process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME !== "" ? process.env.XDG_DATA_HOME : join(options.homeDir ?? os.homedir(), ".local", "share");
28
29
  return join(base, "agntn");
29
30
  }
31
+ //#endregion
32
+ //#region src/harness.ts
30
33
  function moveRoot(path, root, value) {
31
34
  if (path === root) return value;
32
35
  const rest = path.slice(root.length);
@@ -37,6 +40,7 @@ const SUPPORTED_PLATFORMS = {
37
40
  darwin: true,
38
41
  win32: true
39
42
  };
43
+ const PARENT_ENV = "AGNTN_HARNESSES_PARENT";
40
44
  const ALTERNATE_INVOCATION_MODE = {
41
45
  advisor: "agent",
42
46
  advisorStructured: "agentStructured",
@@ -249,6 +253,7 @@ function executeCommand(command, args, options) {
249
253
  });
250
254
  }
251
255
  var Harness = class {
256
+ skillsSyncTarget = null;
252
257
  promptTemplates = [];
253
258
  promptTemplateSyncTarget = null;
254
259
  temp = [];
@@ -324,6 +329,8 @@ var Harness = class {
324
329
  return `${`Harness ${this.id} has no ${INVOCATION_MODE_DESCRIPTION[mode]} invocation`}${invocationRetryHint(this.invocation, mode, options)}`;
325
330
  }
326
331
  invoke(prompt, options = {}) {
332
+ const parent = process.env[PARENT_ENV];
333
+ if (parent) return Promise.reject(/* @__PURE__ */ new Error(`Nested run refused: this process already runs inside ${parent}, started by harnesses. ${this.id} under it would stack agents, and in a read-only sandbox it gets no network and hangs until the deadline. Answer without it`));
327
334
  const invocationOptions = {
328
335
  model: options.model,
329
336
  structured: options.structured,
@@ -334,9 +341,16 @@ var Harness = class {
334
341
  if (!built) return Promise.reject(new Error(this.invocationError(invocationOptions) ?? "Invalid invocation"));
335
342
  const versionError = this.readOnlyVersionError(invocationOptions);
336
343
  if (versionError) return Promise.reject(new Error(versionError));
344
+ const command = {
345
+ ...options,
346
+ env: {
347
+ ...options.env,
348
+ [PARENT_ENV]: this.id
349
+ }
350
+ };
337
351
  const streamArgs = options.structured ? void 0 : this.invocation?.streamArgs;
338
- if (!streamArgs) return executeCommand(built.command, built.args, options);
339
- return executeCommand(built.command, [...built.args, ...streamArgs], options).then((result) => ({
352
+ if (!streamArgs) return executeCommand(built.command, built.args, command);
353
+ return executeCommand(built.command, [...built.args, ...streamArgs], command).then((result) => ({
340
354
  ...result,
341
355
  stdout: this.foldStreamOutput(result.stdout, !result.timedOut && !result.aborted)
342
356
  }));
@@ -402,6 +416,7 @@ var Harness = class {
402
416
  sessions: this.resolveCandidates(this.sessions, options),
403
417
  instructions: this.resolveCandidates(this.instructions, options),
404
418
  skills: this.resolveCandidates(this.skills, options),
419
+ skillsSyncTarget: this.skillsSyncTarget === null ? null : this.resolveCandidates([this.skillsSyncTarget], options)[0] ?? null,
405
420
  commands: this.resolveCandidates(this.commands, options),
406
421
  promptTemplates: this.resolveCandidates(this.promptTemplates, options),
407
422
  promptTemplateSyncTarget: this.promptTemplateSyncTarget === null ? null : this.resolveCandidates([this.promptTemplateSyncTarget], options)[0] ?? null,
@@ -420,6 +435,8 @@ var Harness = class {
420
435
  }));
421
436
  }
422
437
  };
438
+ //#endregion
439
+ //#region src/harnesses/antigravity.ts
423
440
  function parseAntigravityModels(stdout) {
424
441
  const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
425
442
  if (lines.length === 0) throw new Error("Unexpected empty Antigravity model-list output");
@@ -553,6 +570,11 @@ var Antigravity = class extends Harness {
553
570
  scope: "user",
554
571
  level: "official"
555
572
  }];
573
+ skillsSyncTarget = {
574
+ path: "~/.gemini/config/skills/",
575
+ scope: "user",
576
+ level: "official"
577
+ };
556
578
  commands = [];
557
579
  hooks = [{
558
580
  path: ".agents/hooks.json",
@@ -600,6 +622,8 @@ var Antigravity = class extends Harness {
600
622
  return model.id;
601
623
  }
602
624
  };
625
+ //#endregion
626
+ //#region src/harnesses/claude.ts
603
627
  function parseStreamLine(line) {
604
628
  try {
605
629
  const parsed = JSON.parse(line);
@@ -752,6 +776,11 @@ var Claude = class extends Harness {
752
776
  level: "official",
753
777
  note: "Global user-level skills."
754
778
  }];
779
+ skillsSyncTarget = {
780
+ path: "~/.claude/skills/",
781
+ scope: "user",
782
+ level: "official"
783
+ };
755
784
  commands = [{
756
785
  path: ".claude/commands/",
757
786
  scope: "project",
@@ -922,6 +951,8 @@ var Claude = class extends Harness {
922
951
  return foldClaudeStream(stdout, complete);
923
952
  }
924
953
  };
954
+ //#endregion
955
+ //#region src/harnesses/codex.ts
925
956
  var Codex = class extends Harness {
926
957
  id = "codex";
927
958
  name = "OpenAI Codex CLI";
@@ -1044,6 +1075,11 @@ var Codex = class extends Harness {
1044
1075
  platforms: ["linux", "darwin"]
1045
1076
  }
1046
1077
  ];
1078
+ skillsSyncTarget = {
1079
+ path: "~/.agents/skills/",
1080
+ scope: "user",
1081
+ level: "official"
1082
+ };
1047
1083
  commands = [{
1048
1084
  path: "~/.codex/prompts/",
1049
1085
  scope: "user",
@@ -1129,6 +1165,8 @@ var Codex = class extends Harness {
1129
1165
  ]
1130
1166
  };
1131
1167
  };
1168
+ //#endregion
1169
+ //#region src/harnesses/cursor.ts
1132
1170
  var Cursor = class extends Harness {
1133
1171
  id = "cursor";
1134
1172
  name = "Cursor";
@@ -1188,6 +1226,11 @@ var Cursor = class extends Harness {
1188
1226
  note: "Cursor natively scans Codex skills directory."
1189
1227
  }
1190
1228
  ];
1229
+ skillsSyncTarget = {
1230
+ path: "~/.cursor/skills/",
1231
+ scope: "user",
1232
+ level: "official"
1233
+ };
1191
1234
  commands = [];
1192
1235
  hooks = [];
1193
1236
  invocation = {
@@ -1222,6 +1265,8 @@ var Cursor = class extends Harness {
1222
1265
  projectMarkers: [".cursor", ".cursorrules"]
1223
1266
  };
1224
1267
  };
1268
+ //#endregion
1269
+ //#region src/harnesses/freebuff.ts
1225
1270
  var Freebuff = class extends Harness {
1226
1271
  id = "freebuff";
1227
1272
  name = "Freebuff";
@@ -1328,6 +1373,8 @@ var Freebuff = class extends Harness {
1328
1373
  projectMarkers: [".agents", "knowledge.md"]
1329
1374
  };
1330
1375
  };
1376
+ //#endregion
1377
+ //#region src/harnesses/gemini.ts
1331
1378
  var Gemini = class extends Harness {
1332
1379
  id = "gemini";
1333
1380
  name = "Google Gemini CLI";
@@ -1422,6 +1469,11 @@ var Gemini = class extends Harness {
1422
1469
  level: "official"
1423
1470
  }
1424
1471
  ];
1472
+ skillsSyncTarget = {
1473
+ path: "~/.gemini/skills/",
1474
+ scope: "user",
1475
+ level: "official"
1476
+ };
1425
1477
  commands = [{
1426
1478
  path: ".gemini/commands/",
1427
1479
  scope: "project",
@@ -1474,6 +1526,8 @@ var Gemini = class extends Harness {
1474
1526
  projectMarkers: [".gemini", "GEMINI.md"]
1475
1527
  };
1476
1528
  };
1529
+ //#endregion
1530
+ //#region src/harnesses/github-copilot.ts
1477
1531
  var GitHubCopilot = class extends Harness {
1478
1532
  id = "github-copilot";
1479
1533
  name = "GitHub Copilot";
@@ -1542,6 +1596,8 @@ var GitHubCopilot = class extends Harness {
1542
1596
  ]
1543
1597
  };
1544
1598
  };
1599
+ //#endregion
1600
+ //#region src/harnesses/grok.ts
1545
1601
  function parseGrokModels(stdout) {
1546
1602
  const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1547
1603
  const start = lines.indexOf("Available models:");
@@ -1641,6 +1697,11 @@ var Grok = class extends Harness {
1641
1697
  level: "official",
1642
1698
  note: "Global user-level skills."
1643
1699
  }];
1700
+ skillsSyncTarget = {
1701
+ path: "~/.grok/skills/",
1702
+ scope: "user",
1703
+ level: "official"
1704
+ };
1644
1705
  commands = [{
1645
1706
  path: ".grok/commands/",
1646
1707
  scope: "project",
@@ -1721,6 +1782,8 @@ var Grok = class extends Harness {
1721
1782
  return parseGrokModels(stdout);
1722
1783
  }
1723
1784
  };
1785
+ //#endregion
1786
+ //#region src/harnesses/mastracode.ts
1724
1787
  function packageVersion(commandPath) {
1725
1788
  try {
1726
1789
  const require = createRequire(realpathSync(commandPath));
@@ -1876,6 +1939,11 @@ var MastraCode = class extends Harness {
1876
1939
  note: "Claude Code compatible."
1877
1940
  }
1878
1941
  ];
1942
+ skillsSyncTarget = {
1943
+ path: "~/.mastracode/skills/",
1944
+ scope: "user",
1945
+ level: "official"
1946
+ };
1879
1947
  commands = [
1880
1948
  {
1881
1949
  path: ".mastracode/commands/",
@@ -1966,6 +2034,32 @@ var MastraCode = class extends Harness {
1966
2034
  projectMarkers: [".mastracode"]
1967
2035
  };
1968
2036
  };
2037
+ //#endregion
2038
+ //#region src/harnesses/omp.ts
2039
+ function isRecord(value) {
2040
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2041
+ }
2042
+ function parseOmpModels(stdout) {
2043
+ let parsed;
2044
+ try {
2045
+ parsed = JSON.parse(stdout);
2046
+ } catch {
2047
+ throw new Error(`Unexpected OMP model-list output: ${JSON.stringify(stdout.slice(0, 200))}`);
2048
+ }
2049
+ if (!isRecord(parsed) || !Array.isArray(parsed["models"])) throw new Error(`Unexpected OMP model-list output: ${JSON.stringify(stdout.slice(0, 200))}`);
2050
+ return parsed["models"].map((entry) => {
2051
+ if (!isRecord(entry) || typeof entry["provider"] !== "string" || typeof entry["id"] !== "string") throw new Error(`Unexpected OMP model-list entry: ${JSON.stringify(entry)}`);
2052
+ const { contextWindow, maxTokens, reasoning, input } = entry;
2053
+ return {
2054
+ provider: entry["provider"],
2055
+ id: entry["id"],
2056
+ ...typeof contextWindow === "number" ? { contextWindow } : {},
2057
+ ...typeof maxTokens === "number" ? { maxOutputTokens: maxTokens } : {},
2058
+ ...typeof reasoning === "boolean" ? { thinking: reasoning } : {},
2059
+ ...Array.isArray(input) ? { images: input.includes("image") } : {}
2060
+ };
2061
+ });
2062
+ }
1969
2063
  var Omp = class extends Harness {
1970
2064
  id = "omp";
1971
2065
  name = "OMP (oh-my-pi)";
@@ -2105,6 +2199,11 @@ var Omp = class extends Harness {
2105
2199
  note: "Auto-generated managed skills."
2106
2200
  }
2107
2201
  ];
2202
+ skillsSyncTarget = {
2203
+ path: "~/.omp/agent/skills/",
2204
+ scope: "user",
2205
+ level: "official"
2206
+ };
2108
2207
  commands = [{
2109
2208
  path: ".omp/commands/",
2110
2209
  scope: "project",
@@ -2150,6 +2249,27 @@ var Omp = class extends Harness {
2150
2249
  level: "official",
2151
2250
  note: "Add --mode json for structured event output. --no-tools disables only OMP's bundled tools, so it cannot provide an advisor mode without tools."
2152
2251
  };
2252
+ modelListing = {
2253
+ args: [
2254
+ "models",
2255
+ "--json",
2256
+ "--kind",
2257
+ "chat",
2258
+ "--no-extensions"
2259
+ ],
2260
+ searchArgs: [
2261
+ "models",
2262
+ "find",
2263
+ "--json",
2264
+ "--kind",
2265
+ "chat",
2266
+ "--no-extensions",
2267
+ "--",
2268
+ "{search}"
2269
+ ],
2270
+ level: "official",
2271
+ note: "Lists chat models with configured provider authentication as JSON; find matches a substring of the provider, id or name, ignoring case. --no-extensions keeps extension load errors out of the run; verified with 18.4.10."
2272
+ };
2153
2273
  mcpConfigs = [{
2154
2274
  path: "~/.omp/agent/mcp.json",
2155
2275
  scope: "user",
@@ -2185,7 +2305,12 @@ var Omp = class extends Harness {
2185
2305
  envVars: ["OMP_PROFILE", "PI_CODING_AGENT_DIR"],
2186
2306
  projectMarkers: [".omp"]
2187
2307
  };
2308
+ parseModelListingOutput(stdout) {
2309
+ return parseOmpModels(stdout);
2310
+ }
2188
2311
  };
2312
+ //#endregion
2313
+ //#region src/harnesses/opencode.ts
2189
2314
  var OpenCode = class extends Harness {
2190
2315
  id = "opencode";
2191
2316
  name = "OpenCode CLI";
@@ -2282,6 +2407,11 @@ var OpenCode = class extends Harness {
2282
2407
  scope: "project",
2283
2408
  level: "official"
2284
2409
  },
2410
+ {
2411
+ path: "~/.config/opencode/skills/",
2412
+ scope: "user",
2413
+ level: "official"
2414
+ },
2285
2415
  {
2286
2416
  path: "~/.claude/skills/",
2287
2417
  scope: "user",
@@ -2294,6 +2424,12 @@ var OpenCode = class extends Harness {
2294
2424
  level: "official"
2295
2425
  }
2296
2426
  ];
2427
+ skillsSyncTarget = {
2428
+ path: "~/.config/opencode/skills/",
2429
+ scope: "user",
2430
+ level: "official",
2431
+ note: "OpenCode also scans ~/.claude/skills/ and ~/.agents/skills/; skill names must be unique across them."
2432
+ };
2297
2433
  commands = [{
2298
2434
  path: ".opencode/commands/",
2299
2435
  scope: "project",
@@ -2349,6 +2485,8 @@ var OpenCode = class extends Harness {
2349
2485
  ]
2350
2486
  };
2351
2487
  };
2488
+ //#endregion
2489
+ //#region src/harnesses/pi.ts
2352
2490
  const MODEL_HEADERS = [
2353
2491
  "provider",
2354
2492
  "model",
@@ -2461,6 +2599,11 @@ var Pi = class extends Harness {
2461
2599
  level: "official",
2462
2600
  note: "Global user-level skills."
2463
2601
  }];
2602
+ skillsSyncTarget = {
2603
+ path: "~/.pi/agent/skills/",
2604
+ scope: "user",
2605
+ level: "official"
2606
+ };
2464
2607
  commands = [{
2465
2608
  path: ".pi/prompts/",
2466
2609
  scope: "project",
@@ -2524,6 +2667,23 @@ var Pi = class extends Harness {
2524
2667
  level: "official",
2525
2668
  note: "Lists models with configured provider authentication; accepts an optional fuzzy search."
2526
2669
  };
2670
+ mcpConfigs = [{
2671
+ path: "~/.pi/agent/mcp.json",
2672
+ scope: "user",
2673
+ level: "official",
2674
+ format: "json",
2675
+ key: ["mcpServers"],
2676
+ dialect: "pi",
2677
+ note: "Read by the built-in MCP extension, which -builtin:mcp in the extensions setting turns off."
2678
+ }, {
2679
+ path: ".pi/mcp.json",
2680
+ scope: "project",
2681
+ level: "official",
2682
+ format: "json",
2683
+ key: ["mcpServers"],
2684
+ dialect: "pi",
2685
+ note: "Read only after the project is trusted."
2686
+ }];
2527
2687
  envOverrides = [{
2528
2688
  variable: "PI_CODING_AGENT_DIR",
2529
2689
  path: "~/.pi/agent",
@@ -2559,6 +2719,8 @@ var Pi = class extends Harness {
2559
2719
  return parsePiModelTable(stdout, "Pi");
2560
2720
  }
2561
2721
  };
2722
+ //#endregion
2723
+ //#region src/harnesses/prime-agent.ts
2562
2724
  var PrimeAgent = class extends Harness {
2563
2725
  id = "prime-agent";
2564
2726
  name = "Prime Agent";
@@ -2660,6 +2822,11 @@ var PrimeAgent = class extends Harness {
2660
2822
  note: "Shared skills directory; root .md files are ignored there."
2661
2823
  }
2662
2824
  ];
2825
+ skillsSyncTarget = {
2826
+ path: "~/.prime/agent/skills/",
2827
+ scope: "user",
2828
+ level: "official"
2829
+ };
2663
2830
  commands = [{
2664
2831
  path: ".prime/agent/prompts/",
2665
2832
  scope: "project",
@@ -2731,6 +2898,8 @@ var PrimeAgent = class extends Harness {
2731
2898
  return parsePiModelTable(stdout, "Prime Agent");
2732
2899
  }
2733
2900
  };
2901
+ //#endregion
2902
+ //#region src/harnesses/index.ts
2734
2903
  const harnesses = [
2735
2904
  Antigravity,
2736
2905
  Claude,
@@ -2746,6 +2915,8 @@ const harnesses = [
2746
2915
  PrimeAgent,
2747
2916
  Pi
2748
2917
  ];
2918
+ //#endregion
2919
+ //#region src/registry.ts
2749
2920
  let registry;
2750
2921
  function getRegistry() {
2751
2922
  if (!registry) {
@@ -2793,6 +2964,8 @@ function detectHarness(cwd) {
2793
2964
  }
2794
2965
  return match;
2795
2966
  }
2967
+ //#endregion
2968
+ //#region src/mcp-servers.ts
2796
2969
  function resolveConfigs(harness, options) {
2797
2970
  return harness.resolveCandidates(harness.mcpConfigs, options).map((entry) => ({
2798
2971
  entry,
@@ -2891,6 +3064,9 @@ const ENV_REFERENCE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
2891
3064
  var McpEnvError = class extends Error {
2892
3065
  name = "McpEnvError";
2893
3066
  };
3067
+ var McpTransportError = class extends Error {
3068
+ name = "McpTransportError";
3069
+ };
2894
3070
  function envReference(value) {
2895
3071
  return ENV_REFERENCE.exec(value)?.[1];
2896
3072
  }
@@ -3006,6 +3182,7 @@ function resolveEnvReferences(server, dialect) {
3006
3182
  }
3007
3183
  function shapeForDialect(server, dialect) {
3008
3184
  const shaped = resolveEnvReferences(server, dialect);
3185
+ if (dialect === "pi" && shaped.transport === "sse") throw new McpTransportError(`${server.name}: Pi takes stdio and streamable HTTP servers, not sse; point url at the server's streamable HTTP endpoint`);
3009
3186
  if (dialect !== "mastracode") return shaped;
3010
3187
  return compact({
3011
3188
  ...shaped,
@@ -3020,6 +3197,26 @@ function denormalizeEntry(server, dialect) {
3020
3197
  if (dialect === "prime") return denormalizePrime(server);
3021
3198
  return denormalizeStandard(server);
3022
3199
  }
3200
+ const STANDARD_FIELDS = /* @__PURE__ */ new Set([
3201
+ "type",
3202
+ "command",
3203
+ "args",
3204
+ "env",
3205
+ "url",
3206
+ "httpUrl",
3207
+ "headers",
3208
+ "enabled"
3209
+ ]);
3210
+ function replacementEntry(previous, server, dialect) {
3211
+ const entry = denormalizeEntry(server, dialect);
3212
+ if (dialect !== "pi" || !isObjectRecord(previous)) return entry;
3213
+ if (normalizeStandard(server.name, previous).transport !== server.transport) return entry;
3214
+ const kept = Object.entries(previous).filter(([key]) => !STANDARD_FIELDS.has(key));
3215
+ return {
3216
+ ...entry,
3217
+ ...Object.fromEntries(kept)
3218
+ };
3219
+ }
3023
3220
  function listingBase(entry, path) {
3024
3221
  return compact({
3025
3222
  path,
@@ -3160,7 +3357,7 @@ function addMcpServer(harness, server, scope = "user", options = {}) {
3160
3357
  const map = drill(root, entry.key, "create");
3161
3358
  if (!map) throw new Error(`Config at ${path} has a non-object at ${entry.key.join(".")}`);
3162
3359
  const replaced = Object.hasOwn(map, server.name);
3163
- map[server.name] = denormalizeEntry(resolved, entry.dialect);
3360
+ map[server.name] = replacementEntry(map[server.name], resolved, entry.dialect);
3164
3361
  writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
3165
3362
  return {
3166
3363
  path,
@@ -3366,6 +3563,9 @@ function withdrawMasterServersFromExcludedHarness(harness, existing, masterNames
3366
3563
  results
3367
3564
  };
3368
3565
  }
3566
+ function cannotHold(error) {
3567
+ return error instanceof McpEnvError || error instanceof McpTransportError;
3568
+ }
3369
3569
  function syncIncludedHarness(harness, master, existing, masterNames, initialPath, options) {
3370
3570
  const results = [];
3371
3571
  let path = initialPath;
@@ -3386,7 +3586,7 @@ function syncIncludedHarness(harness, master, existing, masterNames, initialPath
3386
3586
  action: written.replaced ? "replaced" : "added"
3387
3587
  });
3388
3588
  } catch (error) {
3389
- if (!(error instanceof McpEnvError)) throw error;
3589
+ if (!cannotHold(error)) throw error;
3390
3590
  results.push({
3391
3591
  name: server.name,
3392
3592
  action: "skipped",
@@ -3436,6 +3636,8 @@ function syncMcpServers(harnesses, options = {}) {
3436
3636
  targets: harnesses.map((harness) => syncMcpTarget(harness, master, masterNames, options))
3437
3637
  };
3438
3638
  }
3639
+ //#endregion
3640
+ //#region src/agents-sync.ts
3439
3641
  function readIfExists(path) {
3440
3642
  try {
3441
3643
  return readFileSync(path, "utf8");
@@ -3583,7 +3785,7 @@ function syncFileTarget(id, path, source, check, options) {
3583
3785
  };
3584
3786
  return applyFileTarget(id, path, source, state, options);
3585
3787
  }
3586
- function syncTarget$1(harness, config, check, options) {
3788
+ function syncTarget$2(harness, config, check, options) {
3587
3789
  if (config.excludes.includes(harness.id)) return {
3588
3790
  id: harness.id,
3589
3791
  action: "skipped",
@@ -3634,9 +3836,134 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
3634
3836
  return {
3635
3837
  source: config.source,
3636
3838
  check,
3637
- targets: harnesses.map((harness) => syncTarget$1(harness, config, check, options))
3839
+ targets: harnesses.map((harness) => syncTarget$2(harness, config, check, options))
3638
3840
  };
3639
3841
  }
3842
+ //#endregion
3843
+ //#region src/directory-link.ts
3844
+ const CHECK_DETAIL = "check mode: not applied";
3845
+ function linkTarget(path) {
3846
+ const target = readlinkSync(path);
3847
+ return isAbsolute(target) ? target : resolve(dirname(path), target);
3848
+ }
3849
+ function isInsideDirectory(path, directory) {
3850
+ const remainder = relative(directory, path);
3851
+ return remainder !== "" && !remainder.startsWith("..") && !isAbsolute(remainder);
3852
+ }
3853
+ function tempPath(path, kind) {
3854
+ return join(dirname(path), `.${basename(path)}-${randomUUID()}.${kind}.tmp`);
3855
+ }
3856
+ function realDirectory(path) {
3857
+ try {
3858
+ return realpathSync(path);
3859
+ } catch {
3860
+ return resolve(path);
3861
+ }
3862
+ }
3863
+ function backupDestination(kind, id, path, options) {
3864
+ const backupDir = join(agntnDataDir(options), "diverged", kind, id);
3865
+ mkdirSync(backupDir, { recursive: true });
3866
+ const backup = join(backupDir, `${basename(path)}-${Date.now()}-${randomUUID()}`);
3867
+ if (lstatSync(path).isSymbolicLink()) {
3868
+ symlinkSync(linkTarget(path), backup);
3869
+ unlinkSync(path);
3870
+ return backup;
3871
+ }
3872
+ moveFile(path, backup);
3873
+ return backup;
3874
+ }
3875
+ function linkDirectory(path, source, kind) {
3876
+ mkdirSync(dirname(path), { recursive: true });
3877
+ const temp = tempPath(path, `${kind}-link`);
3878
+ symlinkSync(source, temp, "junction");
3879
+ renameSync(temp, path);
3880
+ }
3881
+ const DIRECTORY_ACTION = {
3882
+ missing: "linked",
3883
+ "correct-link": "unchanged",
3884
+ "wrong-link": "relinked",
3885
+ "legacy-links": "relinked",
3886
+ "local-files": "adopted",
3887
+ "not-directory": "skipped",
3888
+ source: "skipped"
3889
+ };
3890
+ const SKIP_DETAIL = {
3891
+ "not-directory": "destination exists and is not a directory or symlink",
3892
+ source: "destination holds the source directory"
3893
+ };
3894
+ function isEntryLink(path, sourceDirs) {
3895
+ if (!lstatSync(path).isSymbolicLink()) return false;
3896
+ const target = linkTarget(path);
3897
+ return sourceDirs.some((directory) => isInsideDirectory(target, directory));
3898
+ }
3899
+ function linksThrough(sourceDir, targetDir) {
3900
+ let path = sourceDir;
3901
+ for (let hop = 0; hop < 32; hop++) {
3902
+ if (path === targetDir || isInsideDirectory(path, targetDir)) return true;
3903
+ try {
3904
+ if (!lstatSync(path).isSymbolicLink()) return false;
3905
+ } catch {
3906
+ return false;
3907
+ }
3908
+ path = linkTarget(path);
3909
+ }
3910
+ return false;
3911
+ }
3912
+ function inspectDirectory(targetDir, sourceDir) {
3913
+ let stats;
3914
+ try {
3915
+ stats = lstatSync(targetDir);
3916
+ } catch (error) {
3917
+ if (error.code === "ENOENT") return { kind: "missing" };
3918
+ throw error;
3919
+ }
3920
+ if (stats.isSymbolicLink()) {
3921
+ if (linkTarget(targetDir) === sourceDir) return { kind: "correct-link" };
3922
+ return { kind: linksThrough(sourceDir, targetDir) ? "source" : "wrong-link" };
3923
+ }
3924
+ if (!stats.isDirectory()) return { kind: "not-directory" };
3925
+ return inspectLocalDirectory(targetDir, sourceDir);
3926
+ }
3927
+ function inspectLocalDirectory(targetDir, sourceDir) {
3928
+ const realSource = realDirectory(sourceDir);
3929
+ const realTarget = realpathSync(targetDir);
3930
+ if (realSource === realTarget || isInsideDirectory(realSource, realTarget)) return { kind: "source" };
3931
+ const sourceDirs = realSource === sourceDir ? [sourceDir] : [sourceDir, realSource];
3932
+ const entries = readdirSync(targetDir).map((entry) => join(targetDir, entry));
3933
+ return entries.every((entry) => isEntryLink(entry, sourceDirs)) ? {
3934
+ kind: "legacy-links",
3935
+ entries
3936
+ } : { kind: "local-files" };
3937
+ }
3938
+ function clearDirectory(kind, id, targetDir, state, options) {
3939
+ if (state.kind === "wrong-link" || state.kind === "local-files") return backupDestination(kind, id, targetDir, options);
3940
+ if (state.kind === "legacy-links") {
3941
+ for (const entry of state.entries) unlinkSync(entry);
3942
+ rmdirSync(targetDir);
3943
+ }
3944
+ }
3945
+ function syncDirectoryLink(kind, id, targetDir, sourceDir, check, options) {
3946
+ const state = inspectDirectory(targetDir, sourceDir);
3947
+ const action = DIRECTORY_ACTION[state.kind];
3948
+ if (state.kind === "correct-link") return { action };
3949
+ const skip = SKIP_DETAIL[state.kind];
3950
+ if (skip !== void 0) return {
3951
+ action,
3952
+ detail: skip
3953
+ };
3954
+ if (check) return {
3955
+ action,
3956
+ detail: CHECK_DETAIL
3957
+ };
3958
+ const detail = clearDirectory(kind, id, targetDir, state, options);
3959
+ linkDirectory(targetDir, sourceDir, kind);
3960
+ return {
3961
+ action,
3962
+ detail
3963
+ };
3964
+ }
3965
+ //#endregion
3966
+ //#region src/prompt-sync.ts
3640
3967
  const GENERATED_MARKER = "# Generated by @agntn/harnesses from ";
3641
3968
  const PORTABLE_PROMPT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
3642
3969
  const GEMINI_ACTION = {
@@ -3684,10 +4011,6 @@ function generatedSource(content) {
3684
4011
  return null;
3685
4012
  }
3686
4013
  }
3687
- function linkTarget(path) {
3688
- const target = readlinkSync(path);
3689
- return isAbsolute(target) ? target : resolve(dirname(path), target);
3690
- }
3691
4014
  function inspectDestination(path) {
3692
4015
  let stats;
3693
4016
  try {
@@ -3705,93 +4028,12 @@ function inspectDestination(path) {
3705
4028
  };
3706
4029
  return { kind: "unmanaged-file" };
3707
4030
  }
3708
- function tempPath(path, kind) {
3709
- return join(dirname(path), `.${basename(path)}-${randomUUID()}.${kind}.tmp`);
3710
- }
3711
- function linkDirectory(path, source) {
3712
- mkdirSync(dirname(path), { recursive: true });
3713
- const temp = tempPath(path, "prompt-link");
3714
- symlinkSync(source, temp, "junction");
3715
- renameSync(temp, path);
3716
- }
3717
4031
  function writeTemplate(path, content) {
3718
4032
  mkdirSync(dirname(path), { recursive: true });
3719
4033
  const temp = tempPath(path, "prompt-write");
3720
4034
  writeFileSync(temp, content);
3721
4035
  renameSync(temp, path);
3722
4036
  }
3723
- function backupDestination(id, path, options) {
3724
- const backupDir = join(agntnDataDir(options), "diverged", "prompts", id);
3725
- mkdirSync(backupDir, { recursive: true });
3726
- const backup = join(backupDir, `${basename(path)}-${Date.now()}-${randomUUID()}`);
3727
- if (lstatSync(path).isSymbolicLink()) {
3728
- symlinkSync(linkTarget(path), backup);
3729
- unlinkSync(path);
3730
- return backup;
3731
- }
3732
- moveFile(path, backup);
3733
- return backup;
3734
- }
3735
- function checkDetail() {
3736
- return "check mode: not applied";
3737
- }
3738
- function isInsideDirectory(path, directory) {
3739
- const remainder = relative(directory, path);
3740
- return remainder !== "" && !remainder.startsWith("..") && !isAbsolute(remainder);
3741
- }
3742
- function isLegacyTemplateLink(path, sourceDir) {
3743
- return lstatSync(path).isSymbolicLink() && isInsideDirectory(linkTarget(path), sourceDir);
3744
- }
3745
- const DIRECTORY_ACTION = {
3746
- missing: "linked",
3747
- "correct-link": "unchanged",
3748
- "wrong-link": "relinked",
3749
- "legacy-links": "relinked",
3750
- "local-files": "adopted",
3751
- "not-directory": "skipped"
3752
- };
3753
- function inspectDirectory(targetDir, sourceDir) {
3754
- let stats;
3755
- try {
3756
- stats = lstatSync(targetDir);
3757
- } catch (error) {
3758
- if (error.code === "ENOENT") return { kind: "missing" };
3759
- throw error;
3760
- }
3761
- if (stats.isSymbolicLink()) return { kind: linkTarget(targetDir) === sourceDir ? "correct-link" : "wrong-link" };
3762
- if (!stats.isDirectory()) return { kind: "not-directory" };
3763
- const entries = readdirSync(targetDir).map((entry) => join(targetDir, entry));
3764
- return entries.every((entry) => isLegacyTemplateLink(entry, sourceDir)) ? {
3765
- kind: "legacy-links",
3766
- entries
3767
- } : { kind: "local-files" };
3768
- }
3769
- function clearDirectory(id, targetDir, state, options) {
3770
- if (state.kind === "wrong-link" || state.kind === "local-files") return backupDestination(id, targetDir, options);
3771
- if (state.kind === "legacy-links") {
3772
- for (const entry of state.entries) unlinkSync(entry);
3773
- rmdirSync(targetDir);
3774
- }
3775
- }
3776
- function syncMarkdownDirectory(id, targetDir, sourceDir, check, options) {
3777
- const state = inspectDirectory(targetDir, sourceDir);
3778
- const action = DIRECTORY_ACTION[state.kind];
3779
- if (state.kind === "correct-link") return { action };
3780
- if (state.kind === "not-directory") return {
3781
- action,
3782
- detail: "destination exists and is not a directory or symlink"
3783
- };
3784
- if (check) return {
3785
- action,
3786
- detail: checkDetail()
3787
- };
3788
- const detail = clearDirectory(id, targetDir, state, options);
3789
- linkDirectory(targetDir, sourceDir);
3790
- return {
3791
- action,
3792
- detail
3793
- };
3794
- }
3795
4037
  function parseYaml(raw) {
3796
4038
  return createRequire(import.meta.url)("yaml").parse(raw);
3797
4039
  }
@@ -3844,10 +4086,10 @@ function syncGeminiTemplate(id, targetDir, template, check, options) {
3844
4086
  source: template.path,
3845
4087
  path,
3846
4088
  action,
3847
- detail: checkDetail()
4089
+ detail: CHECK_DETAIL
3848
4090
  };
3849
4091
  let detail;
3850
- if (GEMINI_BACKUP[state.kind]) detail = backupDestination(id, path, options);
4092
+ if (GEMINI_BACKUP[state.kind]) detail = backupDestination("prompts", id, path, options);
3851
4093
  writeTemplate(path, content);
3852
4094
  return {
3853
4095
  name: template.name,
@@ -3885,7 +4127,7 @@ function pruneGeminiTarget(id, targetDir, sourceDir, activeNames, check, options
3885
4127
  ...source === null ? {} : { source },
3886
4128
  path,
3887
4129
  action: "removed",
3888
- detail: checkDetail()
4130
+ detail: CHECK_DETAIL
3889
4131
  };
3890
4132
  if (source !== null) {
3891
4133
  unlinkSync(path);
@@ -3900,11 +4142,11 @@ function pruneGeminiTarget(id, targetDir, sourceDir, activeNames, check, options
3900
4142
  name,
3901
4143
  path,
3902
4144
  action: "removed",
3903
- detail: backupDestination(id, path, options)
4145
+ detail: backupDestination("prompts", id, path, options)
3904
4146
  };
3905
4147
  });
3906
4148
  }
3907
- function syncTarget(harness, templates, sourceDir, check, options) {
4149
+ function syncTarget$1(harness, templates, sourceDir, check, options) {
3908
4150
  const target = harness.promptTemplateSyncTarget;
3909
4151
  if (target === null) return {
3910
4152
  id: harness.id,
@@ -3922,7 +4164,7 @@ function syncTarget(harness, templates, sourceDir, check, options) {
3922
4164
  id: harness.id,
3923
4165
  path: targetDir,
3924
4166
  format: resolved.format,
3925
- ...syncMarkdownDirectory(harness.id, targetDir, sourceDir, check, options),
4167
+ ...syncDirectoryLink("prompts", harness.id, targetDir, sourceDir, check, options),
3926
4168
  templates: []
3927
4169
  };
3928
4170
  const results = templates.map((template) => syncGeminiTemplate(harness.id, targetDir, template, check, options));
@@ -3946,9 +4188,59 @@ function syncPromptTemplates(harnesses, check = false, options = {}) {
3946
4188
  source,
3947
4189
  check,
3948
4190
  templates: prepared.map((template) => template.name),
3949
- targets: harnesses.map((harness) => syncTarget(harness, prepared, source, check, options))
4191
+ targets: harnesses.map((harness) => syncTarget$1(harness, prepared, source, check, options))
4192
+ };
4193
+ }
4194
+ //#endregion
4195
+ //#region src/skills-sync.ts
4196
+ function readSkills(sourceDir, check) {
4197
+ let entries;
4198
+ try {
4199
+ entries = readdirSync(sourceDir);
4200
+ } catch (error) {
4201
+ if (error.code !== "ENOENT") throw error;
4202
+ if (!check) mkdirSync(sourceDir, { recursive: true });
4203
+ return [];
4204
+ }
4205
+ return entries.filter((entry) => {
4206
+ const path = join(sourceDir, entry);
4207
+ try {
4208
+ return statSync(path).isDirectory() && existsSync(join(path, "SKILL.md"));
4209
+ } catch {
4210
+ return false;
4211
+ }
4212
+ }).sort((left, right) => left.localeCompare(right));
4213
+ }
4214
+ function syncTarget(harness, sourceDir, check, options) {
4215
+ const target = harness.skillsSyncTarget;
4216
+ if (target === null) return {
4217
+ id: harness.id,
4218
+ action: "skipped",
4219
+ detail: "no stable user-scope skills directory"
4220
+ };
4221
+ const resolved = harness.resolveCandidates([target], options)[0];
4222
+ if (resolved === void 0) return {
4223
+ id: harness.id,
4224
+ action: "skipped",
4225
+ detail: "skills directory is unavailable on this platform"
4226
+ };
4227
+ const targetDir = resolve(resolved.path);
4228
+ return {
4229
+ id: harness.id,
4230
+ path: targetDir,
4231
+ ...syncDirectoryLink("skills", harness.id, targetDir, sourceDir, check, options)
4232
+ };
4233
+ }
4234
+ function syncSkills(harnesses, check = false, options = {}) {
4235
+ const source = join(agntnDataDir(options), "skills");
4236
+ return {
4237
+ source,
4238
+ check,
4239
+ skills: readSkills(source, check),
4240
+ targets: harnesses.map((harness) => syncTarget(harness, source, check, options))
3950
4241
  };
3951
4242
  }
3952
- export { Harness, McpEnvError, addMcpServer, agntnConfigDir, agntnDataDir, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers, syncPromptTemplates };
4243
+ //#endregion
4244
+ export { agntnConfigDir as C, Harness as S, resolvePathTemplate as T, getAllHarnesses as _, McpEnvError as a, listHarnesses as b, listMcpServers as c, readMasterMcpServers as d, removeMcpServer as f, detectProjectHarnesses as g, detectHarnessFromEnv as h, syncAgentsFiles as i, masterMcpPath as l, detectHarness as m, syncPromptTemplates as n, McpTransportError as o, syncMcpServers as p, readAgentsConfig as r, addMcpServer as s, syncSkills as t, parseJsonc as u, getHarness as v, agntnDataDir as w, registerHarness as x, isHarnessId as y };
3953
4245
 
3954
- //# sourceMappingURL=prompt-sync.mjs.map
4246
+ //# sourceMappingURL=skills-sync.mjs.map