@agntn/harnesses 0.2.0 → 0.2.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 (46) hide show
  1. package/README.md +36 -13
  2. package/dist/_chunks/agents.mjs +2 -30
  3. package/dist/_chunks/agents.mjs.map +1 -1
  4. package/dist/_chunks/libs/typebox.mjs +11 -8
  5. package/dist/_chunks/libs/typebox.mjs.map +1 -1
  6. package/dist/_chunks/mcp-servers.mjs +5 -34
  7. package/dist/_chunks/mcp-servers.mjs.map +1 -1
  8. package/dist/_chunks/mcp.mjs +13 -163
  9. package/dist/_chunks/mcp.mjs.map +1 -1
  10. package/dist/_chunks/output.mjs +28 -0
  11. package/dist/_chunks/output.mjs.map +1 -0
  12. package/dist/_chunks/{agents-sync.d.mts → prompt-sync.d.mts} +51 -3
  13. package/dist/_chunks/prompt-sync.d.mts.map +1 -0
  14. package/dist/_chunks/{agents-sync.mjs → prompt-sync.mjs} +517 -21
  15. package/dist/_chunks/prompt-sync.mjs.map +1 -0
  16. package/dist/_chunks/prompts.mjs +28 -0
  17. package/dist/_chunks/prompts.mjs.map +1 -0
  18. package/dist/_chunks/tool-operations.mjs +43 -10
  19. package/dist/_chunks/tool-operations.mjs.map +1 -1
  20. package/dist/_chunks/tool-schemas.d.mts +5 -0
  21. package/dist/_chunks/tool-schemas.d.mts.map +1 -1
  22. package/dist/_chunks/tool-schemas.mjs +4 -0
  23. package/dist/_chunks/tool-schemas.mjs.map +1 -1
  24. package/dist/_chunks/types.mjs +1 -1
  25. package/dist/_chunks/types.mjs.map +1 -1
  26. package/dist/cli.mjs +6 -2
  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 +2 -2
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/mcp.d.mts.map +1 -1
  33. package/dist/mcp.mjs +176 -1
  34. package/dist/mcp.mjs.map +1 -0
  35. package/dist/tool-operations.d.mts +12 -2
  36. package/dist/tool-operations.d.mts.map +1 -1
  37. package/dist/tool-operations.mjs +2 -2
  38. package/dist/tool-schemas.d.mts +2 -2
  39. package/package.json +16 -14
  40. package/packages/omp/extensions/harnesses.ts +19 -1
  41. package/packages/pi/extensions/harnesses.ts +23 -1
  42. package/packages/shared/tui.ts +22 -1
  43. package/dist/_chunks/agents-sync.d.mts.map +0 -1
  44. package/dist/_chunks/agents-sync.mjs.map +0 -1
  45. package/dist/_chunks/mcp2.mjs +0 -17
  46. package/dist/_chunks/mcp2.mjs.map +0 -1
@@ -1,12 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { addAbortListener } from "node:events";
3
- import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { execFile, execFileSync, spawn } from "node:child_process";
5
5
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
6
- import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
6
+ import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, win32 } from "node:path";
7
7
  import os, { homedir } from "node:os";
8
8
  import { stripVTControlCharacters } from "node:util";
9
9
  import { parse } from "smol-toml";
10
+ import { randomUUID } from "node:crypto";
11
+ import { parse as parse$1 } from "yaml";
10
12
  const PATH_MARKERS = /^~(?=\/|$)|\$\{HOME\}|\$\{PROJECT_ROOT\}|%([^%]+)%/g;
11
13
  function resolvePathTemplate(template, options = {}) {
12
14
  const homeDir = options.homeDir ?? os.homedir();
@@ -21,6 +23,10 @@ function agntnConfigDir(options = {}) {
21
23
  const base = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME !== "" ? process.env.XDG_CONFIG_HOME : join(options.homeDir ?? os.homedir(), ".config");
22
24
  return join(base, "agntn");
23
25
  }
26
+ function agntnDataDir(options = {}) {
27
+ 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
+ return join(base, "agntn");
29
+ }
24
30
  const SUPPORTED_PLATFORMS = {
25
31
  linux: true,
26
32
  darwin: true,
@@ -197,6 +203,8 @@ function executeCommand(command, args, options) {
197
203
  });
198
204
  }
199
205
  var Harness = class {
206
+ promptTemplates = [];
207
+ promptTemplateSyncTarget = null;
200
208
  modelListing = null;
201
209
  mcpConfigs = [];
202
210
  agentsFile = null;
@@ -325,6 +333,8 @@ var Harness = class {
325
333
  instructions: this.resolveCandidates(this.instructions, options),
326
334
  skills: this.resolveCandidates(this.skills, options),
327
335
  commands: this.resolveCandidates(this.commands, options),
336
+ promptTemplates: this.resolveCandidates(this.promptTemplates, options),
337
+ promptTemplateSyncTarget: this.promptTemplateSyncTarget === null ? null : this.resolveCandidates([this.promptTemplateSyncTarget], options)[0] ?? null,
328
338
  hooks: this.resolveCandidates(this.hooks, options)
329
339
  };
330
340
  }
@@ -602,6 +612,13 @@ var Claude = class extends Harness {
602
612
  level: "official",
603
613
  note: "Legacy global slash commands."
604
614
  }];
615
+ promptTemplates = this.commands;
616
+ promptTemplateSyncTarget = {
617
+ path: "~/.claude/commands/",
618
+ scope: "user",
619
+ level: "official",
620
+ format: "markdown"
621
+ };
605
622
  hooks = [{
606
623
  path: ".claude/hooks/",
607
624
  scope: "project",
@@ -623,6 +640,7 @@ var Claude = class extends Harness {
623
640
  noToolsArgs: [
624
641
  "-p",
625
642
  "{prompt}",
643
+ "--strict-mcp-config",
626
644
  "--tools",
627
645
  ""
628
646
  ],
@@ -631,6 +649,7 @@ var Claude = class extends Harness {
631
649
  "--output-format",
632
650
  "json",
633
651
  "{prompt}",
652
+ "--strict-mcp-config",
634
653
  "--tools",
635
654
  ""
636
655
  ],
@@ -653,7 +672,7 @@ var Claude = class extends Harness {
653
672
  readOnlyMinVersion: "2.1.175",
654
673
  modelArgs: ["--model", "{model}"],
655
674
  level: "official",
656
- note: "Headless print mode; add --output-format json for structured output. Read-only runs keep the built-in Read, Glob and Grep tools and --strict-mcp-config drops every configured MCP server, so no write tool exists whatever permission mode the settings carry; verified on 2.1.175 and 2.1.268. --permission-mode plan does not qualify: its shell commands run through the auto mode classifier, which let a file write through."
675
+ note: "Headless print mode; add --output-format json for structured output. --tools only covers the built-in set, so every mode without the full agent adds --strict-mcp-config to drop configured MCP servers too; without it an advisor still sees every user and project MCP tool, checked on 2.1.280. Read-only runs keep the built-in Read, Glob and Grep tools, so no write tool exists whatever permission mode the settings carry; verified on 2.1.175 and 2.1.268. --permission-mode plan does not qualify: its shell commands run through the auto mode classifier, which let a file write through."
657
676
  };
658
677
  mcpConfigs = [{
659
678
  path: "~/.claude.json",
@@ -808,7 +827,19 @@ var Codex = class extends Harness {
808
827
  platforms: ["linux", "darwin"]
809
828
  }
810
829
  ];
811
- commands = [];
830
+ commands = [{
831
+ path: "~/.codex/prompts/",
832
+ scope: "user",
833
+ level: "official",
834
+ note: "Deprecated Markdown prompts invoked as /prompts:name. Top-level files only; use skills for shared prompts. Base directory follows CODEX_HOME."
835
+ }];
836
+ promptTemplates = this.commands;
837
+ promptTemplateSyncTarget = {
838
+ path: "~/.codex/prompts/",
839
+ scope: "user",
840
+ level: "official",
841
+ format: "markdown"
842
+ };
812
843
  hooks = [];
813
844
  invocation = {
814
845
  args: [
@@ -1171,6 +1202,13 @@ var Gemini = class extends Harness {
1171
1202
  level: "official",
1172
1203
  note: "Global custom commands in TOML format."
1173
1204
  }];
1205
+ promptTemplates = this.commands;
1206
+ promptTemplateSyncTarget = {
1207
+ path: "~/.gemini/commands/",
1208
+ scope: "user",
1209
+ level: "official",
1210
+ format: "gemini-toml"
1211
+ };
1174
1212
  hooks = [];
1175
1213
  invocation = {
1176
1214
  args: ["-p", "{prompt}"],
@@ -1488,6 +1526,12 @@ var MastraCode = class extends Harness {
1488
1526
  level: "official",
1489
1527
  note: "Global MCP server configurations."
1490
1528
  },
1529
+ {
1530
+ path: ".mcp.json",
1531
+ scope: "project",
1532
+ level: "official",
1533
+ note: "Claude Code compatible project MCP servers, read below .mastracode/mcp.json."
1534
+ },
1491
1535
  {
1492
1536
  path: ".mastracode/hooks.json",
1493
1537
  scope: "project",
@@ -1579,16 +1623,51 @@ var MastraCode = class extends Harness {
1579
1623
  note: "Claude Code compatible."
1580
1624
  }
1581
1625
  ];
1582
- commands = [{
1583
- path: ".mastracode/commands/",
1584
- scope: "project",
1585
- level: "official"
1586
- }, {
1626
+ commands = [
1627
+ {
1628
+ path: ".mastracode/commands/",
1629
+ scope: "project",
1630
+ level: "official",
1631
+ note: "Markdown prompt commands; subdirectories become colon-separated command names."
1632
+ },
1633
+ {
1634
+ path: "~/.mastracode/commands/",
1635
+ scope: "user",
1636
+ level: "official",
1637
+ note: "Global Markdown prompt commands."
1638
+ },
1639
+ {
1640
+ path: ".claude/commands/",
1641
+ scope: "project",
1642
+ level: "official",
1643
+ note: "Claude compatibility."
1644
+ },
1645
+ {
1646
+ path: "~/.claude/commands/",
1647
+ scope: "user",
1648
+ level: "official",
1649
+ note: "Claude compatibility."
1650
+ },
1651
+ {
1652
+ path: ".opencode/command/",
1653
+ scope: "project",
1654
+ level: "official",
1655
+ note: "Mastra's OpenCode compatibility loader uses the singular command directory."
1656
+ },
1657
+ {
1658
+ path: "~/.opencode/command/",
1659
+ scope: "user",
1660
+ level: "official",
1661
+ note: "Mastra's OpenCode compatibility loader uses the singular command directory."
1662
+ }
1663
+ ];
1664
+ promptTemplates = this.commands;
1665
+ promptTemplateSyncTarget = {
1587
1666
  path: "~/.mastracode/commands/",
1588
1667
  scope: "user",
1589
1668
  level: "official",
1590
- note: "Global commands."
1591
- }];
1669
+ format: "markdown"
1670
+ };
1592
1671
  hooks = [{
1593
1672
  path: ".mastracode/hooks.json",
1594
1673
  scope: "project",
@@ -1600,6 +1679,35 @@ var MastraCode = class extends Harness {
1600
1679
  note: "Global hooks."
1601
1680
  }];
1602
1681
  invocation = null;
1682
+ mcpConfigs = [
1683
+ {
1684
+ path: "~/.mastracode/mcp.json",
1685
+ scope: "user",
1686
+ level: "official",
1687
+ format: "json",
1688
+ key: ["mcpServers"],
1689
+ dialect: "mastracode",
1690
+ note: "An entry with command is stdio and one with url is HTTP, type is ignored. Loaded above mcpServers from the project's .claude/settings.local.json and below both project files, later names winning."
1691
+ },
1692
+ {
1693
+ path: ".mastracode/mcp.json",
1694
+ scope: "project",
1695
+ level: "official",
1696
+ format: "json",
1697
+ key: ["mcpServers"],
1698
+ dialect: "mastracode",
1699
+ note: "Highest priority, overrides the other files entry by entry."
1700
+ },
1701
+ {
1702
+ path: ".mcp.json",
1703
+ scope: "project",
1704
+ level: "official",
1705
+ format: "json",
1706
+ key: ["mcpServers"],
1707
+ dialect: "mastracode",
1708
+ note: "Claude Code's project file, read between the user file and .mastracode/mcp.json. Project writes go to .mastracode/mcp.json, this one is Claude's to edit."
1709
+ }
1710
+ ];
1603
1711
  detection = {
1604
1712
  envVars: [],
1605
1713
  projectMarkers: [".mastracode"]
@@ -1747,13 +1855,35 @@ var Omp = class extends Harness {
1747
1855
  commands = [{
1748
1856
  path: ".omp/commands/",
1749
1857
  scope: "project",
1750
- level: "official"
1858
+ level: "official",
1859
+ note: "Markdown prompt commands and TypeScript commands; only Markdown files are templates."
1751
1860
  }, {
1752
1861
  path: "~/.omp/agent/commands/",
1753
1862
  scope: "user",
1754
1863
  level: "official",
1755
- note: "Global user-level prompt commands."
1864
+ note: "Global Markdown prompt commands and TypeScript commands; only Markdown files are templates."
1756
1865
  }];
1866
+ promptTemplates = [
1867
+ ...this.commands,
1868
+ {
1869
+ path: ".omp/prompts/",
1870
+ scope: "project",
1871
+ level: "official",
1872
+ note: "Markdown prompt templates invoked as /name; subdirectories are scanned."
1873
+ },
1874
+ {
1875
+ path: "~/.omp/agent/prompts/",
1876
+ scope: "user",
1877
+ level: "official",
1878
+ note: "Global Markdown prompt templates, separate from executable commands."
1879
+ }
1880
+ ];
1881
+ promptTemplateSyncTarget = {
1882
+ path: "~/.omp/agent/prompts/",
1883
+ scope: "user",
1884
+ level: "official",
1885
+ format: "markdown"
1886
+ };
1757
1887
  hooks = [];
1758
1888
  invocation = {
1759
1889
  args: ["-p", "{prompt}"],
@@ -1896,7 +2026,24 @@ var OpenCode = class extends Harness {
1896
2026
  level: "official"
1897
2027
  }
1898
2028
  ];
1899
- commands = [];
2029
+ commands = [{
2030
+ path: ".opencode/commands/",
2031
+ scope: "project",
2032
+ level: "official",
2033
+ note: "Markdown prompt commands; also configurable through command entries in opencode.json."
2034
+ }, {
2035
+ path: "~/.config/opencode/commands/",
2036
+ scope: "user",
2037
+ level: "official",
2038
+ note: "Global Markdown prompt commands."
2039
+ }];
2040
+ promptTemplates = this.commands;
2041
+ promptTemplateSyncTarget = {
2042
+ path: "~/.config/opencode/commands/",
2043
+ scope: "user",
2044
+ level: "official",
2045
+ format: "markdown"
2046
+ };
1900
2047
  hooks = [];
1901
2048
  invocation = {
1902
2049
  args: ["run", "{prompt}"],
@@ -2058,6 +2205,13 @@ var Pi = class extends Harness {
2058
2205
  level: "official",
2059
2206
  note: "Global user-level prompt templates."
2060
2207
  }];
2208
+ promptTemplates = this.commands;
2209
+ promptTemplateSyncTarget = {
2210
+ path: "~/.pi/agent/prompts/",
2211
+ scope: "user",
2212
+ level: "official",
2213
+ format: "markdown"
2214
+ };
2061
2215
  hooks = [];
2062
2216
  invocation = {
2063
2217
  args: ["-p", "{prompt}"],
@@ -2228,6 +2382,13 @@ var PrimeAgent = class extends Harness {
2228
2382
  level: "official",
2229
2383
  note: "Global user-level prompt templates."
2230
2384
  }];
2385
+ promptTemplates = this.commands;
2386
+ promptTemplateSyncTarget = {
2387
+ path: "~/.prime/agent/prompts/",
2388
+ scope: "user",
2389
+ level: "official",
2390
+ format: "markdown"
2391
+ };
2231
2392
  hooks = [];
2232
2393
  invocation = {
2233
2394
  args: ["-p", "{prompt}"],
@@ -2457,8 +2618,21 @@ function normalizePrime(name, raw) {
2457
2618
  env: primeEnv(raw.env)
2458
2619
  });
2459
2620
  }
2621
+ function normalizeMastracode(name, raw) {
2622
+ const url = stringField(raw, "url");
2623
+ return compact({
2624
+ name,
2625
+ transport: url ? "http" : "stdio",
2626
+ command: stringField(raw, "command"),
2627
+ args: asStringArray(raw.args),
2628
+ env: asStringRecord(raw.env),
2629
+ url,
2630
+ headers: asStringRecord(raw.headers)
2631
+ });
2632
+ }
2460
2633
  function normalizeEntry(name, raw, dialect) {
2461
2634
  if (dialect === "antigravity") return normalizeAntigravity(name, raw);
2635
+ if (dialect === "mastracode") return normalizeMastracode(name, raw);
2462
2636
  if (dialect === "opencode") return normalizeOpenCode(name, raw);
2463
2637
  if (dialect === "prime") return normalizePrime(name, raw);
2464
2638
  return normalizeStandard(name, raw);
@@ -2485,6 +2659,17 @@ function denormalizeOpenCode(server) {
2485
2659
  enabled: server.enabled
2486
2660
  });
2487
2661
  }
2662
+ function denormalizeMastracode(server) {
2663
+ if (server.url) return compact({
2664
+ url: server.url,
2665
+ headers: server.headers
2666
+ });
2667
+ return compact({
2668
+ command: server.command,
2669
+ args: server.args?.length ? server.args : void 0,
2670
+ env: server.env
2671
+ });
2672
+ }
2488
2673
  function denormalizeStandard(server) {
2489
2674
  return compact({
2490
2675
  type: server.transport,
@@ -2530,8 +2715,18 @@ function resolveEnvReferences(server, dialect) {
2530
2715
  env
2531
2716
  };
2532
2717
  }
2718
+ function shapeForDialect(server, dialect) {
2719
+ const shaped = resolveEnvReferences(server, dialect);
2720
+ if (dialect !== "mastracode") return shaped;
2721
+ return compact({
2722
+ ...shaped,
2723
+ transport: shaped.url ? "http" : "stdio",
2724
+ enabled: void 0
2725
+ });
2726
+ }
2533
2727
  function denormalizeEntry(server, dialect) {
2534
2728
  if (dialect === "antigravity") return denormalizeAntigravity(server);
2729
+ if (dialect === "mastracode") return denormalizeMastracode(server);
2535
2730
  if (dialect === "opencode") return denormalizeOpenCode(server);
2536
2731
  if (dialect === "prime") return denormalizePrime(server);
2537
2732
  return denormalizeStandard(server);
@@ -2667,7 +2862,7 @@ function removeTomlServer(path, key, name) {
2667
2862
  }
2668
2863
  function addMcpServer(harness, server, scope = "user", options = {}) {
2669
2864
  const { entry, path } = writableConfig(harness, scope, options);
2670
- const resolved = resolveEnvReferences(server, entry.dialect);
2865
+ const resolved = shapeForDialect(server, entry.dialect);
2671
2866
  if (entry.format === "toml") return {
2672
2867
  path,
2673
2868
  replaced: addTomlServer(path, entry.key, resolved)
@@ -2887,7 +3082,7 @@ function syncIncludedHarness(harness, master, existing, masterNames, initialPath
2887
3082
  let path = initialPath;
2888
3083
  const { dialect } = writableConfig(harness, "user", options).entry;
2889
3084
  for (const server of master.servers) try {
2890
- const resolved = resolveEnvReferences(server, dialect);
3085
+ const resolved = shapeForDialect(server, dialect);
2891
3086
  if (existing.get(server.name) === canonical(resolved)) {
2892
3087
  results.push({
2893
3088
  name: server.name,
@@ -3035,6 +3230,15 @@ function relink(path, source) {
3035
3230
  symlinkSync(source, temp);
3036
3231
  renameSync(temp, path);
3037
3232
  }
3233
+ function moveFile(from, to) {
3234
+ try {
3235
+ renameSync(from, to);
3236
+ } catch (error) {
3237
+ if (error.code !== "EXDEV") throw error;
3238
+ copyFileSync(from, to);
3239
+ unlinkSync(from);
3240
+ }
3241
+ }
3038
3242
  const CHECK_ACTION = {
3039
3243
  missing: "linked",
3040
3244
  "correct-link": "unchanged",
@@ -3047,7 +3251,7 @@ function applyFileTarget(id, path, source, state, options) {
3047
3251
  const backupDir = join(agntnConfigDir(options), "diverged");
3048
3252
  mkdirSync(backupDir, { recursive: true });
3049
3253
  const backup = join(backupDir, `${id}-${basename(path)}-${Date.now()}.md`);
3050
- renameSync(path, backup);
3254
+ moveFile(path, backup);
3051
3255
  relink(path, source);
3052
3256
  return {
3053
3257
  path,
@@ -3082,7 +3286,7 @@ function syncFileTarget(id, path, source, check, options) {
3082
3286
  };
3083
3287
  return applyFileTarget(id, path, source, state, options);
3084
3288
  }
3085
- function syncTarget(harness, config, check, options) {
3289
+ function syncTarget$1(harness, config, check, options) {
3086
3290
  if (config.excludes.includes(harness.id)) return {
3087
3291
  id: harness.id,
3088
3292
  action: "skipped",
@@ -3133,9 +3337,301 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
3133
3337
  return {
3134
3338
  source: config.source,
3135
3339
  check,
3136
- targets: harnesses.map((harness) => syncTarget(harness, config, check, options))
3340
+ targets: harnesses.map((harness) => syncTarget$1(harness, config, check, options))
3341
+ };
3342
+ }
3343
+ const GENERATED_MARKER = "# Generated by @agntn/harnesses from ";
3344
+ const PORTABLE_PROMPT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
3345
+ const GEMINI_ACTION = {
3346
+ missing: "generated",
3347
+ "correct-link": "adopted",
3348
+ "wrong-link": "adopted",
3349
+ "owned-file": "replaced",
3350
+ "unmanaged-file": "adopted",
3351
+ unsupported: "skipped"
3352
+ };
3353
+ const GEMINI_BACKUP = {
3354
+ missing: false,
3355
+ "correct-link": true,
3356
+ "wrong-link": true,
3357
+ "owned-file": true,
3358
+ "unmanaged-file": true,
3359
+ unsupported: false
3360
+ };
3361
+ function readTemplates(sourceDir, check) {
3362
+ let entries;
3363
+ try {
3364
+ entries = readdirSync(sourceDir, { withFileTypes: true });
3365
+ } catch (error) {
3366
+ if (error.code !== "ENOENT") throw error;
3367
+ if (!check) mkdirSync(sourceDir, { recursive: true });
3368
+ return [];
3369
+ }
3370
+ const templates = entries.filter((entry) => (entry.isFile() || entry.isSymbolicLink()) && extname(entry.name).toLowerCase() === ".md").map((entry) => {
3371
+ const name = basename(entry.name, extname(entry.name));
3372
+ if (!PORTABLE_PROMPT_NAME.test(name)) throw new TypeError(`Invalid prompt template filename: ${entry.name}`);
3373
+ const path = join(sourceDir, entry.name);
3374
+ return {
3375
+ name,
3376
+ path,
3377
+ content: readFileSync(path, "utf8")
3378
+ };
3379
+ }).sort((left, right) => left.name.localeCompare(right.name));
3380
+ const names = templates.map((template) => template.name.toLowerCase());
3381
+ if (new Set(names).size !== names.length) throw new Error(`Duplicate prompt template names in ${sourceDir}`);
3382
+ return templates;
3383
+ }
3384
+ function generatedSource(content) {
3385
+ const firstLine = content.split("\n", 1)[0] ?? "";
3386
+ if (!firstLine.startsWith(GENERATED_MARKER)) return null;
3387
+ try {
3388
+ const source = JSON.parse(firstLine.slice(37));
3389
+ return typeof source === "string" ? source : null;
3390
+ } catch {
3391
+ return null;
3392
+ }
3393
+ }
3394
+ function inspectDestination(path, source) {
3395
+ let stats;
3396
+ try {
3397
+ stats = lstatSync(path);
3398
+ } catch (error) {
3399
+ if (error.code === "ENOENT") return { kind: "missing" };
3400
+ throw error;
3401
+ }
3402
+ if (stats.isSymbolicLink()) {
3403
+ if (source === void 0) return { kind: "unmanaged-file" };
3404
+ const target = readlinkSync(path);
3405
+ return (isAbsolute(target) ? target : resolve(dirname(path), target)) === source ? { kind: "correct-link" } : { kind: "wrong-link" };
3406
+ }
3407
+ if (!stats.isFile()) return { kind: "unsupported" };
3408
+ const content = readFileSync(path, "utf8");
3409
+ if (generatedSource(content) !== null) return {
3410
+ kind: "owned-file",
3411
+ content
3412
+ };
3413
+ return { kind: "unmanaged-file" };
3414
+ }
3415
+ function tempPath(path, kind) {
3416
+ return join(dirname(path), `.${basename(path)}-${randomUUID()}.${kind}.tmp`);
3417
+ }
3418
+ function linkTemplate(path, source) {
3419
+ mkdirSync(dirname(path), { recursive: true });
3420
+ const temp = tempPath(path, "prompt-link");
3421
+ symlinkSync(source, temp);
3422
+ renameSync(temp, path);
3423
+ }
3424
+ function writeTemplate(path, content) {
3425
+ mkdirSync(dirname(path), { recursive: true });
3426
+ const temp = tempPath(path, "prompt-write");
3427
+ writeFileSync(temp, content);
3428
+ renameSync(temp, path);
3429
+ }
3430
+ function backupDestination(id, path, options) {
3431
+ const backupDir = join(agntnDataDir(options), "diverged", "prompts", id);
3432
+ mkdirSync(backupDir, { recursive: true });
3433
+ const backup = join(backupDir, `${basename(path)}-${Date.now()}-${randomUUID()}`);
3434
+ if (lstatSync(path).isSymbolicLink()) {
3435
+ const target = readlinkSync(path);
3436
+ symlinkSync(isAbsolute(target) ? target : resolve(dirname(path), target), backup);
3437
+ unlinkSync(path);
3438
+ return backup;
3439
+ }
3440
+ moveFile(path, backup);
3441
+ return backup;
3442
+ }
3443
+ function checkDetail() {
3444
+ return "check mode: not applied";
3445
+ }
3446
+ function syncMarkdownTemplate(id, targetDir, template, check, options) {
3447
+ const path = join(targetDir, `${template.name}.md`);
3448
+ const state = inspectDestination(path, template.path);
3449
+ if (state.kind === "correct-link") return {
3450
+ name: template.name,
3451
+ source: template.path,
3452
+ path,
3453
+ action: "unchanged"
3454
+ };
3455
+ if (state.kind === "unsupported") return {
3456
+ name: template.name,
3457
+ source: template.path,
3458
+ path,
3459
+ action: "skipped",
3460
+ detail: "destination exists and is not a file or symlink"
3461
+ };
3462
+ const action = state.kind === "missing" ? "linked" : state.kind === "wrong-link" ? "relinked" : "adopted";
3463
+ if (check) return {
3464
+ name: template.name,
3465
+ source: template.path,
3466
+ path,
3467
+ action,
3468
+ detail: checkDetail()
3469
+ };
3470
+ let detail;
3471
+ if (state.kind === "wrong-link") unlinkSync(path);
3472
+ if (state.kind === "owned-file" || state.kind === "unmanaged-file") detail = backupDestination(id, path, options);
3473
+ linkTemplate(path, template.path);
3474
+ return {
3475
+ name: template.name,
3476
+ source: template.path,
3477
+ path,
3478
+ action,
3479
+ detail
3480
+ };
3481
+ }
3482
+ function frontmatterDescription(raw, path) {
3483
+ const frontmatter = parse$1(raw);
3484
+ if (frontmatter === null) return void 0;
3485
+ if (typeof frontmatter !== "object" || Array.isArray(frontmatter)) throw new TypeError(`Prompt template frontmatter is not an object: ${path}`);
3486
+ const description = frontmatter.description;
3487
+ if (description !== void 0 && typeof description !== "string") throw new TypeError(`Prompt template description is not a string: ${path}`);
3488
+ return description;
3489
+ }
3490
+ function parseMarkdownTemplate(template) {
3491
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(template.content);
3492
+ if (match === null) return { prompt: template.content.replaceAll("$ARGUMENTS", "{{args}}") };
3493
+ const description = frontmatterDescription(match[1] ?? "", template.path);
3494
+ return {
3495
+ ...description === void 0 ? {} : { description },
3496
+ prompt: template.content.slice(match[0].length).replaceAll("$ARGUMENTS", "{{args}}")
3497
+ };
3498
+ }
3499
+ function geminiContent(template) {
3500
+ const parsed = parseMarkdownTemplate(template);
3501
+ return [
3502
+ `${GENERATED_MARKER}${JSON.stringify(template.path)}`,
3503
+ ...parsed.description === void 0 ? [] : [`description = ${JSON.stringify(parsed.description)}`],
3504
+ `prompt = ${JSON.stringify(parsed.prompt)}`,
3505
+ ""
3506
+ ].join("\n");
3507
+ }
3508
+ function syncGeminiTemplate(id, targetDir, template, check, options) {
3509
+ const path = join(targetDir, `${template.name}.toml`);
3510
+ const content = template.gemini ?? geminiContent(template);
3511
+ const state = inspectDestination(path);
3512
+ if (state.kind === "owned-file" && state.content === content) return {
3513
+ name: template.name,
3514
+ source: template.path,
3515
+ path,
3516
+ action: "unchanged"
3517
+ };
3518
+ if (state.kind === "unsupported") return {
3519
+ name: template.name,
3520
+ source: template.path,
3521
+ path,
3522
+ action: "skipped",
3523
+ detail: "destination exists and is not a file or symlink"
3524
+ };
3525
+ const action = GEMINI_ACTION[state.kind];
3526
+ if (check) return {
3527
+ name: template.name,
3528
+ source: template.path,
3529
+ path,
3530
+ action,
3531
+ detail: checkDetail()
3532
+ };
3533
+ let detail;
3534
+ if (GEMINI_BACKUP[state.kind]) detail = backupDestination(id, path, options);
3535
+ writeTemplate(path, content);
3536
+ return {
3537
+ name: template.name,
3538
+ source: template.path,
3539
+ path,
3540
+ action,
3541
+ detail
3542
+ };
3543
+ }
3544
+ function isInsideDirectory(path, directory) {
3545
+ const remainder = relative(directory, path);
3546
+ return remainder !== "" && !remainder.startsWith("..") && !isAbsolute(remainder);
3547
+ }
3548
+ function staleMarkdownSource(path, sourceDir) {
3549
+ if (inspectDestination(path).kind !== "unmanaged-file") return null;
3550
+ let stats;
3551
+ try {
3552
+ stats = lstatSync(path);
3553
+ } catch {
3554
+ return null;
3555
+ }
3556
+ if (!stats.isSymbolicLink()) return null;
3557
+ const target = readlinkSync(path);
3558
+ const resolved = isAbsolute(target) ? target : resolve(dirname(path), target);
3559
+ return isInsideDirectory(resolved, sourceDir) ? resolved : null;
3560
+ }
3561
+ function staleGeminiSource(path, sourceDir) {
3562
+ let content;
3563
+ try {
3564
+ content = readFileSync(path, "utf8");
3565
+ } catch {
3566
+ return null;
3567
+ }
3568
+ const source = generatedSource(content);
3569
+ if (source === null) return null;
3570
+ return isInsideDirectory(source, sourceDir) ? source : null;
3571
+ }
3572
+ function pruneTarget(targetDir, format, sourceDir, activeSources, check) {
3573
+ let entries;
3574
+ try {
3575
+ entries = readdirSync(targetDir, { withFileTypes: true });
3576
+ } catch (error) {
3577
+ if (error.code === "ENOENT") return [];
3578
+ throw error;
3579
+ }
3580
+ const extension = format === "markdown" ? ".md" : ".toml";
3581
+ const staleSource = format === "markdown" ? staleMarkdownSource : staleGeminiSource;
3582
+ return entries.filter((entry) => entry.name.toLowerCase().endsWith(extension)).flatMap((entry) => {
3583
+ const path = join(targetDir, entry.name);
3584
+ const source = staleSource(path, sourceDir);
3585
+ if (source === null || activeSources.has(source)) return [];
3586
+ if (!check) unlinkSync(path);
3587
+ return [{
3588
+ name: basename(entry.name, extension),
3589
+ source,
3590
+ path,
3591
+ action: "removed",
3592
+ ...check ? { detail: checkDetail() } : {}
3593
+ }];
3594
+ });
3595
+ }
3596
+ function syncTarget(harness, templates, sourceDir, check, options) {
3597
+ const target = harness.promptTemplateSyncTarget;
3598
+ if (target === null) return {
3599
+ id: harness.id,
3600
+ detail: "no stable user-scope prompt template destination",
3601
+ templates: []
3602
+ };
3603
+ const resolved = harness.resolveCandidates([target], options)[0];
3604
+ if (resolved === void 0) return {
3605
+ id: harness.id,
3606
+ detail: "prompt template destination is unavailable on this platform",
3607
+ templates: []
3608
+ };
3609
+ const targetDir = resolved.path;
3610
+ const syncTemplate = resolved.format === "markdown" ? syncMarkdownTemplate : syncGeminiTemplate;
3611
+ const results = templates.map((template) => syncTemplate(harness.id, targetDir, template, check, options));
3612
+ const activeSources = new Set(templates.map((template) => template.path));
3613
+ results.push(...pruneTarget(targetDir, resolved.format, sourceDir, activeSources, check));
3614
+ return {
3615
+ id: harness.id,
3616
+ path: targetDir,
3617
+ format: resolved.format,
3618
+ templates: results
3619
+ };
3620
+ }
3621
+ function syncPromptTemplates(harnesses, check = false, options = {}) {
3622
+ const source = join(agntnDataDir(options), "prompts");
3623
+ const templates = readTemplates(source, check);
3624
+ const prepared = harnesses.some((harness) => harness.promptTemplateSyncTarget?.format === "gemini-toml") ? templates.map((template) => ({
3625
+ ...template,
3626
+ gemini: geminiContent(template)
3627
+ })) : templates;
3628
+ return {
3629
+ source,
3630
+ check,
3631
+ templates: prepared.map((template) => template.name),
3632
+ targets: harnesses.map((harness) => syncTarget(harness, prepared, source, check, options))
3137
3633
  };
3138
3634
  }
3139
- export { Harness, McpEnvError, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
3635
+ export { Harness, McpEnvError, addMcpServer, agntnConfigDir, agntnDataDir, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers, syncPromptTemplates };
3140
3636
 
3141
- //# sourceMappingURL=agents-sync.mjs.map
3637
+ //# sourceMappingURL=prompt-sync.mjs.map