@agntn/harnesses 0.3.0 → 0.3.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 (50) hide show
  1. package/README.md +34 -4
  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} +103 -50
  13. package/dist/_chunks/skills-sync.d.mts.map +1 -0
  14. package/dist/_chunks/{prompt-sync.mjs → skills-sync.mjs} +298 -98
  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 +22 -2
  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 +18 -18
  43. package/packages/omp/extensions/harnesses.ts +18 -0
  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,8 @@ var MastraCode = class extends Harness {
1966
2034
  projectMarkers: [".mastracode"]
1967
2035
  };
1968
2036
  };
2037
+ //#endregion
2038
+ //#region src/harnesses/omp.ts
1969
2039
  var Omp = class extends Harness {
1970
2040
  id = "omp";
1971
2041
  name = "OMP (oh-my-pi)";
@@ -2105,6 +2175,11 @@ var Omp = class extends Harness {
2105
2175
  note: "Auto-generated managed skills."
2106
2176
  }
2107
2177
  ];
2178
+ skillsSyncTarget = {
2179
+ path: "~/.omp/agent/skills/",
2180
+ scope: "user",
2181
+ level: "official"
2182
+ };
2108
2183
  commands = [{
2109
2184
  path: ".omp/commands/",
2110
2185
  scope: "project",
@@ -2186,6 +2261,8 @@ var Omp = class extends Harness {
2186
2261
  projectMarkers: [".omp"]
2187
2262
  };
2188
2263
  };
2264
+ //#endregion
2265
+ //#region src/harnesses/opencode.ts
2189
2266
  var OpenCode = class extends Harness {
2190
2267
  id = "opencode";
2191
2268
  name = "OpenCode CLI";
@@ -2282,6 +2359,11 @@ var OpenCode = class extends Harness {
2282
2359
  scope: "project",
2283
2360
  level: "official"
2284
2361
  },
2362
+ {
2363
+ path: "~/.config/opencode/skills/",
2364
+ scope: "user",
2365
+ level: "official"
2366
+ },
2285
2367
  {
2286
2368
  path: "~/.claude/skills/",
2287
2369
  scope: "user",
@@ -2294,6 +2376,12 @@ var OpenCode = class extends Harness {
2294
2376
  level: "official"
2295
2377
  }
2296
2378
  ];
2379
+ skillsSyncTarget = {
2380
+ path: "~/.config/opencode/skills/",
2381
+ scope: "user",
2382
+ level: "official",
2383
+ note: "OpenCode also scans ~/.claude/skills/ and ~/.agents/skills/; skill names must be unique across them."
2384
+ };
2297
2385
  commands = [{
2298
2386
  path: ".opencode/commands/",
2299
2387
  scope: "project",
@@ -2349,6 +2437,8 @@ var OpenCode = class extends Harness {
2349
2437
  ]
2350
2438
  };
2351
2439
  };
2440
+ //#endregion
2441
+ //#region src/harnesses/pi.ts
2352
2442
  const MODEL_HEADERS = [
2353
2443
  "provider",
2354
2444
  "model",
@@ -2461,6 +2551,11 @@ var Pi = class extends Harness {
2461
2551
  level: "official",
2462
2552
  note: "Global user-level skills."
2463
2553
  }];
2554
+ skillsSyncTarget = {
2555
+ path: "~/.pi/agent/skills/",
2556
+ scope: "user",
2557
+ level: "official"
2558
+ };
2464
2559
  commands = [{
2465
2560
  path: ".pi/prompts/",
2466
2561
  scope: "project",
@@ -2559,6 +2654,8 @@ var Pi = class extends Harness {
2559
2654
  return parsePiModelTable(stdout, "Pi");
2560
2655
  }
2561
2656
  };
2657
+ //#endregion
2658
+ //#region src/harnesses/prime-agent.ts
2562
2659
  var PrimeAgent = class extends Harness {
2563
2660
  id = "prime-agent";
2564
2661
  name = "Prime Agent";
@@ -2660,6 +2757,11 @@ var PrimeAgent = class extends Harness {
2660
2757
  note: "Shared skills directory; root .md files are ignored there."
2661
2758
  }
2662
2759
  ];
2760
+ skillsSyncTarget = {
2761
+ path: "~/.prime/agent/skills/",
2762
+ scope: "user",
2763
+ level: "official"
2764
+ };
2663
2765
  commands = [{
2664
2766
  path: ".prime/agent/prompts/",
2665
2767
  scope: "project",
@@ -2731,6 +2833,8 @@ var PrimeAgent = class extends Harness {
2731
2833
  return parsePiModelTable(stdout, "Prime Agent");
2732
2834
  }
2733
2835
  };
2836
+ //#endregion
2837
+ //#region src/harnesses/index.ts
2734
2838
  const harnesses = [
2735
2839
  Antigravity,
2736
2840
  Claude,
@@ -2746,6 +2850,8 @@ const harnesses = [
2746
2850
  PrimeAgent,
2747
2851
  Pi
2748
2852
  ];
2853
+ //#endregion
2854
+ //#region src/registry.ts
2749
2855
  let registry;
2750
2856
  function getRegistry() {
2751
2857
  if (!registry) {
@@ -2793,6 +2899,8 @@ function detectHarness(cwd) {
2793
2899
  }
2794
2900
  return match;
2795
2901
  }
2902
+ //#endregion
2903
+ //#region src/mcp-servers.ts
2796
2904
  function resolveConfigs(harness, options) {
2797
2905
  return harness.resolveCandidates(harness.mcpConfigs, options).map((entry) => ({
2798
2906
  entry,
@@ -3436,6 +3544,8 @@ function syncMcpServers(harnesses, options = {}) {
3436
3544
  targets: harnesses.map((harness) => syncMcpTarget(harness, master, masterNames, options))
3437
3545
  };
3438
3546
  }
3547
+ //#endregion
3548
+ //#region src/agents-sync.ts
3439
3549
  function readIfExists(path) {
3440
3550
  try {
3441
3551
  return readFileSync(path, "utf8");
@@ -3583,7 +3693,7 @@ function syncFileTarget(id, path, source, check, options) {
3583
3693
  };
3584
3694
  return applyFileTarget(id, path, source, state, options);
3585
3695
  }
3586
- function syncTarget$1(harness, config, check, options) {
3696
+ function syncTarget$2(harness, config, check, options) {
3587
3697
  if (config.excludes.includes(harness.id)) return {
3588
3698
  id: harness.id,
3589
3699
  action: "skipped",
@@ -3634,9 +3744,134 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
3634
3744
  return {
3635
3745
  source: config.source,
3636
3746
  check,
3637
- targets: harnesses.map((harness) => syncTarget$1(harness, config, check, options))
3747
+ targets: harnesses.map((harness) => syncTarget$2(harness, config, check, options))
3638
3748
  };
3639
3749
  }
3750
+ //#endregion
3751
+ //#region src/directory-link.ts
3752
+ const CHECK_DETAIL = "check mode: not applied";
3753
+ function linkTarget(path) {
3754
+ const target = readlinkSync(path);
3755
+ return isAbsolute(target) ? target : resolve(dirname(path), target);
3756
+ }
3757
+ function isInsideDirectory(path, directory) {
3758
+ const remainder = relative(directory, path);
3759
+ return remainder !== "" && !remainder.startsWith("..") && !isAbsolute(remainder);
3760
+ }
3761
+ function tempPath(path, kind) {
3762
+ return join(dirname(path), `.${basename(path)}-${randomUUID()}.${kind}.tmp`);
3763
+ }
3764
+ function realDirectory(path) {
3765
+ try {
3766
+ return realpathSync(path);
3767
+ } catch {
3768
+ return resolve(path);
3769
+ }
3770
+ }
3771
+ function backupDestination(kind, id, path, options) {
3772
+ const backupDir = join(agntnDataDir(options), "diverged", kind, id);
3773
+ mkdirSync(backupDir, { recursive: true });
3774
+ const backup = join(backupDir, `${basename(path)}-${Date.now()}-${randomUUID()}`);
3775
+ if (lstatSync(path).isSymbolicLink()) {
3776
+ symlinkSync(linkTarget(path), backup);
3777
+ unlinkSync(path);
3778
+ return backup;
3779
+ }
3780
+ moveFile(path, backup);
3781
+ return backup;
3782
+ }
3783
+ function linkDirectory(path, source, kind) {
3784
+ mkdirSync(dirname(path), { recursive: true });
3785
+ const temp = tempPath(path, `${kind}-link`);
3786
+ symlinkSync(source, temp, "junction");
3787
+ renameSync(temp, path);
3788
+ }
3789
+ const DIRECTORY_ACTION = {
3790
+ missing: "linked",
3791
+ "correct-link": "unchanged",
3792
+ "wrong-link": "relinked",
3793
+ "legacy-links": "relinked",
3794
+ "local-files": "adopted",
3795
+ "not-directory": "skipped",
3796
+ source: "skipped"
3797
+ };
3798
+ const SKIP_DETAIL = {
3799
+ "not-directory": "destination exists and is not a directory or symlink",
3800
+ source: "destination holds the source directory"
3801
+ };
3802
+ function isEntryLink(path, sourceDirs) {
3803
+ if (!lstatSync(path).isSymbolicLink()) return false;
3804
+ const target = linkTarget(path);
3805
+ return sourceDirs.some((directory) => isInsideDirectory(target, directory));
3806
+ }
3807
+ function linksThrough(sourceDir, targetDir) {
3808
+ let path = sourceDir;
3809
+ for (let hop = 0; hop < 32; hop++) {
3810
+ if (path === targetDir || isInsideDirectory(path, targetDir)) return true;
3811
+ try {
3812
+ if (!lstatSync(path).isSymbolicLink()) return false;
3813
+ } catch {
3814
+ return false;
3815
+ }
3816
+ path = linkTarget(path);
3817
+ }
3818
+ return false;
3819
+ }
3820
+ function inspectDirectory(targetDir, sourceDir) {
3821
+ let stats;
3822
+ try {
3823
+ stats = lstatSync(targetDir);
3824
+ } catch (error) {
3825
+ if (error.code === "ENOENT") return { kind: "missing" };
3826
+ throw error;
3827
+ }
3828
+ if (stats.isSymbolicLink()) {
3829
+ if (linkTarget(targetDir) === sourceDir) return { kind: "correct-link" };
3830
+ return { kind: linksThrough(sourceDir, targetDir) ? "source" : "wrong-link" };
3831
+ }
3832
+ if (!stats.isDirectory()) return { kind: "not-directory" };
3833
+ return inspectLocalDirectory(targetDir, sourceDir);
3834
+ }
3835
+ function inspectLocalDirectory(targetDir, sourceDir) {
3836
+ const realSource = realDirectory(sourceDir);
3837
+ const realTarget = realpathSync(targetDir);
3838
+ if (realSource === realTarget || isInsideDirectory(realSource, realTarget)) return { kind: "source" };
3839
+ const sourceDirs = realSource === sourceDir ? [sourceDir] : [sourceDir, realSource];
3840
+ const entries = readdirSync(targetDir).map((entry) => join(targetDir, entry));
3841
+ return entries.every((entry) => isEntryLink(entry, sourceDirs)) ? {
3842
+ kind: "legacy-links",
3843
+ entries
3844
+ } : { kind: "local-files" };
3845
+ }
3846
+ function clearDirectory(kind, id, targetDir, state, options) {
3847
+ if (state.kind === "wrong-link" || state.kind === "local-files") return backupDestination(kind, id, targetDir, options);
3848
+ if (state.kind === "legacy-links") {
3849
+ for (const entry of state.entries) unlinkSync(entry);
3850
+ rmdirSync(targetDir);
3851
+ }
3852
+ }
3853
+ function syncDirectoryLink(kind, id, targetDir, sourceDir, check, options) {
3854
+ const state = inspectDirectory(targetDir, sourceDir);
3855
+ const action = DIRECTORY_ACTION[state.kind];
3856
+ if (state.kind === "correct-link") return { action };
3857
+ const skip = SKIP_DETAIL[state.kind];
3858
+ if (skip !== void 0) return {
3859
+ action,
3860
+ detail: skip
3861
+ };
3862
+ if (check) return {
3863
+ action,
3864
+ detail: CHECK_DETAIL
3865
+ };
3866
+ const detail = clearDirectory(kind, id, targetDir, state, options);
3867
+ linkDirectory(targetDir, sourceDir, kind);
3868
+ return {
3869
+ action,
3870
+ detail
3871
+ };
3872
+ }
3873
+ //#endregion
3874
+ //#region src/prompt-sync.ts
3640
3875
  const GENERATED_MARKER = "# Generated by @agntn/harnesses from ";
3641
3876
  const PORTABLE_PROMPT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
3642
3877
  const GEMINI_ACTION = {
@@ -3684,10 +3919,6 @@ function generatedSource(content) {
3684
3919
  return null;
3685
3920
  }
3686
3921
  }
3687
- function linkTarget(path) {
3688
- const target = readlinkSync(path);
3689
- return isAbsolute(target) ? target : resolve(dirname(path), target);
3690
- }
3691
3922
  function inspectDestination(path) {
3692
3923
  let stats;
3693
3924
  try {
@@ -3705,93 +3936,12 @@ function inspectDestination(path) {
3705
3936
  };
3706
3937
  return { kind: "unmanaged-file" };
3707
3938
  }
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
3939
  function writeTemplate(path, content) {
3718
3940
  mkdirSync(dirname(path), { recursive: true });
3719
3941
  const temp = tempPath(path, "prompt-write");
3720
3942
  writeFileSync(temp, content);
3721
3943
  renameSync(temp, path);
3722
3944
  }
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
3945
  function parseYaml(raw) {
3796
3946
  return createRequire(import.meta.url)("yaml").parse(raw);
3797
3947
  }
@@ -3844,10 +3994,10 @@ function syncGeminiTemplate(id, targetDir, template, check, options) {
3844
3994
  source: template.path,
3845
3995
  path,
3846
3996
  action,
3847
- detail: checkDetail()
3997
+ detail: CHECK_DETAIL
3848
3998
  };
3849
3999
  let detail;
3850
- if (GEMINI_BACKUP[state.kind]) detail = backupDestination(id, path, options);
4000
+ if (GEMINI_BACKUP[state.kind]) detail = backupDestination("prompts", id, path, options);
3851
4001
  writeTemplate(path, content);
3852
4002
  return {
3853
4003
  name: template.name,
@@ -3885,7 +4035,7 @@ function pruneGeminiTarget(id, targetDir, sourceDir, activeNames, check, options
3885
4035
  ...source === null ? {} : { source },
3886
4036
  path,
3887
4037
  action: "removed",
3888
- detail: checkDetail()
4038
+ detail: CHECK_DETAIL
3889
4039
  };
3890
4040
  if (source !== null) {
3891
4041
  unlinkSync(path);
@@ -3900,11 +4050,11 @@ function pruneGeminiTarget(id, targetDir, sourceDir, activeNames, check, options
3900
4050
  name,
3901
4051
  path,
3902
4052
  action: "removed",
3903
- detail: backupDestination(id, path, options)
4053
+ detail: backupDestination("prompts", id, path, options)
3904
4054
  };
3905
4055
  });
3906
4056
  }
3907
- function syncTarget(harness, templates, sourceDir, check, options) {
4057
+ function syncTarget$1(harness, templates, sourceDir, check, options) {
3908
4058
  const target = harness.promptTemplateSyncTarget;
3909
4059
  if (target === null) return {
3910
4060
  id: harness.id,
@@ -3922,7 +4072,7 @@ function syncTarget(harness, templates, sourceDir, check, options) {
3922
4072
  id: harness.id,
3923
4073
  path: targetDir,
3924
4074
  format: resolved.format,
3925
- ...syncMarkdownDirectory(harness.id, targetDir, sourceDir, check, options),
4075
+ ...syncDirectoryLink("prompts", harness.id, targetDir, sourceDir, check, options),
3926
4076
  templates: []
3927
4077
  };
3928
4078
  const results = templates.map((template) => syncGeminiTemplate(harness.id, targetDir, template, check, options));
@@ -3946,9 +4096,59 @@ function syncPromptTemplates(harnesses, check = false, options = {}) {
3946
4096
  source,
3947
4097
  check,
3948
4098
  templates: prepared.map((template) => template.name),
3949
- targets: harnesses.map((harness) => syncTarget(harness, prepared, source, check, options))
4099
+ targets: harnesses.map((harness) => syncTarget$1(harness, prepared, source, check, options))
4100
+ };
4101
+ }
4102
+ //#endregion
4103
+ //#region src/skills-sync.ts
4104
+ function readSkills(sourceDir, check) {
4105
+ let entries;
4106
+ try {
4107
+ entries = readdirSync(sourceDir);
4108
+ } catch (error) {
4109
+ if (error.code !== "ENOENT") throw error;
4110
+ if (!check) mkdirSync(sourceDir, { recursive: true });
4111
+ return [];
4112
+ }
4113
+ return entries.filter((entry) => {
4114
+ const path = join(sourceDir, entry);
4115
+ try {
4116
+ return statSync(path).isDirectory() && existsSync(join(path, "SKILL.md"));
4117
+ } catch {
4118
+ return false;
4119
+ }
4120
+ }).sort((left, right) => left.localeCompare(right));
4121
+ }
4122
+ function syncTarget(harness, sourceDir, check, options) {
4123
+ const target = harness.skillsSyncTarget;
4124
+ if (target === null) return {
4125
+ id: harness.id,
4126
+ action: "skipped",
4127
+ detail: "no stable user-scope skills directory"
4128
+ };
4129
+ const resolved = harness.resolveCandidates([target], options)[0];
4130
+ if (resolved === void 0) return {
4131
+ id: harness.id,
4132
+ action: "skipped",
4133
+ detail: "skills directory is unavailable on this platform"
4134
+ };
4135
+ const targetDir = resolve(resolved.path);
4136
+ return {
4137
+ id: harness.id,
4138
+ path: targetDir,
4139
+ ...syncDirectoryLink("skills", harness.id, targetDir, sourceDir, check, options)
4140
+ };
4141
+ }
4142
+ function syncSkills(harnesses, check = false, options = {}) {
4143
+ const source = join(agntnDataDir(options), "skills");
4144
+ return {
4145
+ source,
4146
+ check,
4147
+ skills: readSkills(source, check),
4148
+ targets: harnesses.map((harness) => syncTarget(harness, source, check, options))
3950
4149
  };
3951
4150
  }
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 };
4151
+ //#endregion
4152
+ export { agntnDataDir as C, agntnConfigDir as S, getHarness as _, McpEnvError as a, registerHarness as b, masterMcpPath as c, removeMcpServer as d, syncMcpServers as f, getAllHarnesses as g, detectProjectHarnesses as h, syncAgentsFiles as i, parseJsonc as l, detectHarnessFromEnv as m, syncPromptTemplates as n, addMcpServer as o, detectHarness as p, readAgentsConfig as r, listMcpServers as s, syncSkills as t, readMasterMcpServers as u, isHarnessId as v, resolvePathTemplate as w, Harness as x, listHarnesses as y };
3953
4153
 
3954
- //# sourceMappingURL=prompt-sync.mjs.map
4154
+ //# sourceMappingURL=skills-sync.mjs.map