@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
package/README.md CHANGED
@@ -24,8 +24,9 @@ The rest of it sits on [harnesses.agntn.dev](https://harnesses.agntn.dev).
24
24
  - ▶️ **Headless runs with real modes.** Advisor without tools, full agent, or a native read-only sandbox. A mode the CLI cannot enforce is rejected.
25
25
  - 🔌 **MCP across the dialects.** One master list at `~/.config/agntn/mcp.jsonc`. TOML edits keep the comments.
26
26
  - 🔗 **One AGENTS.md behind the global files.** Symlinks, so an edit through Claude or Gemini is the same bytes.
27
+ - 🗂️ **One prompt folder, supported harnesses.** Markdown lives under XDG data; Gemini TOML is generated from it.
27
28
  - 📜 **Session types when the format is stable.** JSONL, SQLite, JSON. Unstable shapes stay `unknown`.
28
- - 🤖 **Nine tools, three doors.** MCP, Pi and OMP call the same executors.
29
+ - 🤖 **Ten tools, three doors.** MCP, Pi and OMP call the same executors.
29
30
 
30
31
  ## 📦 Install
31
32
 
@@ -72,23 +73,25 @@ harnesses run claude "review this design"
72
73
  harnesses run codex --read-only "review this"
73
74
  harnesses mcp-servers list
74
75
  harnesses agents sync --check
76
+ harnesses prompts sync --check
75
77
  ```
76
78
 
77
79
  `run` without `--tools` is the advisor. `--tools` is the full agent. `--read-only` asks the CLI for a sandbox and implies tools. Timeouts, `--cwd` and `--model` sit in the [CLI guide](https://harnesses.agntn.dev/guide/cli).
78
80
 
79
81
  ### Commands
80
82
 
81
- | Command | What it does | Example |
82
- | ------------- | ------------------------------------------------------ | ------------------------------------------- |
83
- | `list` | Every known harness, id and name | `harnesses list` |
84
- | `detect` | Which ones are installed, with versions | `harnesses detect` |
85
- | `info` | Registry entry: modes, capabilities, path templates | `harnesses info claude` |
86
- | `paths` | Those templates expanded for this machine | `harnesses paths pi` |
87
- | `models` | Models the harness can use, through its native listing | `harnesses models pi` |
88
- | `run` | One prompt through headless mode | `harnesses run claude "review this design"` |
89
- | `mcp-servers` | MCP servers across the config dialects | `harnesses mcp-servers list` |
90
- | `agents sync` | Link global instructions files to one master | `harnesses agents sync --check` |
91
- | `mcp` | The MCP server on stdio | `harnesses mcp` |
83
+ | Command | What it does | Example |
84
+ | -------------- | ------------------------------------------------------ | ------------------------------------------- |
85
+ | `list` | Every known harness, id and name | `harnesses list` |
86
+ | `detect` | Which ones are installed, with versions | `harnesses detect` |
87
+ | `info` | Registry entry: modes, capabilities, path templates | `harnesses info claude` |
88
+ | `paths` | Those templates expanded for this machine | `harnesses paths pi` |
89
+ | `models` | Models the harness can use, through its native listing | `harnesses models pi` |
90
+ | `run` | One prompt through headless mode | `harnesses run claude "review this design"` |
91
+ | `mcp-servers` | MCP servers across the config dialects | `harnesses mcp-servers list` |
92
+ | `agents sync` | Link global instructions files to one master | `harnesses agents sync --check` |
93
+ | `prompts sync` | Sync one Markdown prompt directory across harnesses | `harnesses prompts sync --check` |
94
+ | `mcp` | The MCP server on stdio | `harnesses mcp` |
92
95
 
93
96
  `list`, `detect`, `info`, `paths` and `models` take `--json` or `--toon`. `run --json` is different: that one is the harness's own structured output.
94
97
 
@@ -109,6 +112,26 @@ await getHarness("codex").invoke("Review this patch", { readOnly: true });
109
112
 
110
113
  That's most of it, really. `getHarness` wants an exact id. `detectHarness` uses env vars first, then a single project marker. `invoke()` talks to the CLI. A mode the CLI cannot run comes back as an error, not a quieter one. The rest: [Registry](https://harnesses.agntn.dev/guide/registry), [Invoke](https://harnesses.agntn.dev/guide/invoke), [MCP servers](https://harnesses.agntn.dev/guide/mcp-servers), [Instructions files](https://harnesses.agntn.dev/guide/agents-sync).
111
114
 
115
+ ### Prompt templates
116
+
117
+ Put canonical Markdown templates in `$XDG_DATA_HOME/agntn/prompts/`, or
118
+ `~/.local/share/agntn/prompts/` when `XDG_DATA_HOME` is unset. Then sync every
119
+ supported harness:
120
+
121
+ ```bash
122
+ harnesses prompts sync --check
123
+ harnesses prompts sync
124
+ ```
125
+
126
+ Markdown harnesses receive symlinks to those files. Gemini receives generated
127
+ TOML commands, with `$ARGUMENTS` translated to `{{args}}`. Unmanaged files at a
128
+ managed name are backed up under the same XDG data tree before replacement.
129
+
130
+ `getHarness("pi").promptTemplates` still lists every verified reusable prompt
131
+ location. `promptTemplateSyncTarget` is the single stable user destination the
132
+ sync owns for that harness. Formats, frontmatter and CLI limits are in the
133
+ [registry guide](https://harnesses.agntn.dev/guide/registry#prompt-templates).
134
+
112
135
  ## 🗺️ Harnesses
113
136
 
114
137
  | ID | Name | Project skills |
@@ -145,7 +168,7 @@ omp install @agntn/harnesses
145
168
  }
146
169
  ```
147
170
 
148
- Nine tools, the same nine on MCP, Pi and OMP. `harnesses_detect`, `harnesses_info` and `harnesses_mcp_list` only read. `harnesses_run` is the one that can spend tokens. `tools` is required, so the model has to pick advisor or agent. What each call returns is on the [Agents page](https://harnesses.agntn.dev/guide/agents).
171
+ Ten tools, the same ten on MCP, Pi and OMP. `harnesses_detect`, `harnesses_info` and `harnesses_mcp_list` only read. `harnesses_run` is the one that can spend tokens. `tools` is required, so the model has to pick advisor or agent. What each call returns is on the [Agents page](https://harnesses.agntn.dev/guide/agents).
149
172
 
150
173
  ## 🚫 What this does not do
151
174
 
@@ -1,36 +1,8 @@
1
- import { isHarnessId, listHarnesses } from "./agents-sync.mjs";
1
+ import { isHarnessId, listHarnesses } from "./prompt-sync.mjs";
2
2
  import { agentsSync } from "./tool-operations.mjs";
3
- import { stripVTControlCharacters } from "node:util";
3
+ import { formatArgs, report } from "./output.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import { consola } from "consola";
6
- import { encode } from "@toon-format/toon";
7
- const formatArgs = {
8
- json: {
9
- type: "boolean",
10
- description: "Output as JSON"
11
- },
12
- toon: {
13
- type: "boolean",
14
- description: "Output as TOON"
15
- }
16
- };
17
- const UNSAFE_TERMINAL_CONTROLS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g;
18
- function report(result, args) {
19
- const sanitize = (text) => stripVTControlCharacters(text ?? "").replace(UNSAFE_TERMINAL_CONTROLS, " ");
20
- if (result.isError) {
21
- consola.error(sanitize(result.content[0]?.text));
22
- process.exit(1);
23
- }
24
- if (args.json) {
25
- console.log(JSON.stringify(result.details, null, 2));
26
- return;
27
- }
28
- if (args.toon) {
29
- console.log(encode(result.details));
30
- return;
31
- }
32
- console.log(sanitize(result.content[0]?.text));
33
- }
34
6
  var agents_default = defineCommand({
35
7
  meta: { description: "Manage the shared global instructions file across harnesses" },
36
8
  subCommands: { sync: defineCommand({
@@ -1 +1 @@
1
- {"version":3,"file":"agents.mjs","names":["toToon"],"sources":["../../src/commands/agents.ts"],"sourcesContent":["import { stripVTControlCharacters } from \"node:util\";\nimport { defineCommand } from \"citty\";\nimport { consola } from \"consola\";\nimport { encode as toToon } from \"@toon-format/toon\";\nimport { isHarnessId, listHarnesses } from \"../registry.ts\";\nimport { agentsSync, type ToolResult } from \"../tool-operations.ts\";\n\nconst formatArgs = {\n json: { type: \"boolean\" as const, description: \"Output as JSON\" },\n toon: { type: \"boolean\" as const, description: \"Output as TOON\" },\n};\n\n// Sync reports echo backup paths and config values, so strip terminal controls.\n// oxlint-disable-next-line no-control-regex -- Terminal control bytes are precisely what this boundary removes.\nconst UNSAFE_TERMINAL_CONTROLS = /[\\u0000-\\u0008\\u000B-\\u001F\\u007F-\\u009F]/g;\n\nfunction report(\n result: ToolResult<unknown>,\n args: Readonly<{ json?: boolean; toon?: boolean }>,\n): void {\n const sanitize = (text: string | undefined) =>\n stripVTControlCharacters(text ?? \"\").replace(UNSAFE_TERMINAL_CONTROLS, \" \");\n if (result.isError) {\n consola.error(sanitize(result.content[0]?.text));\n process.exit(1);\n }\n if (args.json) {\n console.log(JSON.stringify(result.details, null, 2));\n return;\n }\n if (args.toon) {\n console.log(toToon(result.details));\n return;\n }\n console.log(sanitize(result.content[0]?.text));\n}\n\nexport default defineCommand({\n meta: { description: \"Manage the shared global instructions file across harnesses\" },\n subCommands: {\n sync: defineCommand({\n meta: {\n description:\n \"Link global instructions and declared companions (agents.jsonc: source, companions, excludes)\",\n },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: false },\n check: {\n type: \"boolean\" as const,\n description: \"Report what would change without writing anything\",\n },\n ...formatArgs,\n },\n run({ args }) {\n const id = args.id === undefined ? undefined : (args.id as string);\n if (id !== undefined && !isHarnessId(id)) {\n consola.error(`Unknown harness: ${id}\\nKnown: ${listHarnesses().join(\", \")}`);\n process.exit(1);\n }\n report(agentsSync(id, args.check === true), args);\n },\n }),\n },\n});\n"],"mappings":";;;;;;AAOA,MAAM,aAAa;CACjB,MAAM;EAAE,MAAM;EAAoB,aAAa;CAAiB;CAChE,MAAM;EAAE,MAAM;EAAoB,aAAa;CAAiB;AAClE;AAIA,MAAM,2BAA2B;AAEjC,SAAS,OACP,QACA,MACM;CACN,MAAM,YAAY,SAChB,yBAAyB,QAAQ,EAAE,CAAC,CAAC,QAAQ,0BAA0B,GAAG;CAC5E,IAAI,OAAO,SAAS;EAClB,QAAQ,MAAM,SAAS,OAAO,QAAQ,EAAE,EAAE,IAAI,CAAC;EAC/C,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI,KAAK,MAAM;EACb,QAAQ,IAAI,KAAK,UAAU,OAAO,SAAS,MAAM,CAAC,CAAC;EACnD;CACF;CACA,IAAI,KAAK,MAAM;EACb,QAAQ,IAAIA,OAAO,OAAO,OAAO,CAAC;EAClC;CACF;CACA,QAAQ,IAAI,SAAS,OAAO,QAAQ,EAAE,EAAE,IAAI,CAAC;AAC/C;AAEA,IAAA,iBAAe,cAAc;CAC3B,MAAM,EAAE,aAAa,8DAA8D;CACnF,aAAa,EACX,MAAM,cAAc;EAClB,MAAM,EACJ,aACE,gGACJ;EACA,MAAM;GACJ,IAAI;IAAE,MAAM;IAAuB,aAAa;IAAc,UAAU;GAAM;GAC9E,OAAO;IACL,MAAM;IACN,aAAa;GACf;GACA,GAAG;EACL;EACA,IAAI,EAAE,QAAQ;GACZ,MAAM,KAAK,KAAK,OAAO,KAAA,IAAY,KAAA,IAAa,KAAK;GACrD,IAAI,OAAO,KAAA,KAAa,CAAC,YAAY,EAAE,GAAG;IACxC,QAAQ,MAAM,oBAAoB,GAAG,WAAW,cAAc,CAAC,CAAC,KAAK,IAAI,GAAG;IAC5E,QAAQ,KAAK,CAAC;GAChB;GACA,OAAO,WAAW,IAAI,KAAK,UAAU,IAAI,GAAG,IAAI;EAClD;CACF,CAAC,EACH;AACF,CAAC"}
1
+ {"version":3,"file":"agents.mjs","names":[],"sources":["../../src/commands/agents.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport { consola } from \"consola\";\nimport { isHarnessId, listHarnesses } from \"../registry.ts\";\nimport { agentsSync } from \"../tool-operations.ts\";\nimport { formatArgs, report } from \"./output.ts\";\n\nexport default defineCommand({\n meta: { description: \"Manage the shared global instructions file across harnesses\" },\n subCommands: {\n sync: defineCommand({\n meta: {\n description:\n \"Link global instructions and declared companions (agents.jsonc: source, companions, excludes)\",\n },\n args: {\n id: { type: \"positional\" as const, description: \"Harness id\", required: false },\n check: {\n type: \"boolean\" as const,\n description: \"Report what would change without writing anything\",\n },\n ...formatArgs,\n },\n run({ args }) {\n const id = args.id === undefined ? undefined : (args.id as string);\n if (id !== undefined && !isHarnessId(id)) {\n consola.error(`Unknown harness: ${id}\\nKnown: ${listHarnesses().join(\", \")}`);\n process.exit(1);\n }\n report(agentsSync(id, args.check === true), args);\n },\n }),\n },\n});\n"],"mappings":";;;;;AAMA,IAAA,iBAAe,cAAc;CAC3B,MAAM,EAAE,aAAa,8DAA8D;CACnF,aAAa,EACX,MAAM,cAAc;EAClB,MAAM,EACJ,aACE,gGACJ;EACA,MAAM;GACJ,IAAI;IAAE,MAAM;IAAuB,aAAa;IAAc,UAAU;GAAM;GAC9E,OAAO;IACL,MAAM;IACN,aAAa;GACf;GACA,GAAG;EACL;EACA,IAAI,EAAE,QAAQ;GACZ,MAAM,KAAK,KAAK,OAAO,KAAA,IAAY,KAAA,IAAa,KAAK;GACrD,IAAI,OAAO,KAAA,KAAa,CAAC,YAAY,EAAE,GAAG;IACxC,QAAQ,MAAM,oBAAoB,GAAG,WAAW,cAAc,CAAC,CAAC,KAAK,IAAI,GAAG;IAC5E,QAAQ,KAAK,CAAC;GAChB;GACA,OAAO,WAAW,IAAI,KAAK,UAAU,IAAI,GAAG,IAAI;EAClD;CACF,CAAC,EACH;AACF,CAAC"}
@@ -4176,6 +4176,9 @@ function ErrorAdditionalItems(stack, context, schemaPath, instancePath, schema,
4176
4176
  return IsLessThan(index, schema.items.length) || ErrorSchemaPushStack(stack, context, nextSchemaPath, nextInstancePath, schema.additionalItems, item) && context.AddIndex(index);
4177
4177
  });
4178
4178
  }
4179
+ function EncodeFragment(fragment) {
4180
+ return fragment.replace(/~/g, "~0").replace(/\//g, "~1");
4181
+ }
4179
4182
  function UnicodeRegExp(pattern) {
4180
4183
  return new RegExp(pattern, "u");
4181
4184
  }
@@ -4199,7 +4202,7 @@ function ErrorAdditionalProperties(stack, context, schemaPath, instancePath, sch
4199
4202
  const additionalProperties = [];
4200
4203
  return EveryAll(Keys(value), 0, (key, _index) => {
4201
4204
  const nextSchemaPath = `${schemaPath}/additionalProperties`;
4202
- const nextInstancePath = `${instancePath}/${key}`;
4205
+ const nextInstancePath = `${instancePath}/${EncodeFragment(key)}`;
4203
4206
  const isAdditionalProperty = regexp.test(key) || ErrorSchemaPushStack(stack, context, nextSchemaPath, nextInstancePath, schema.additionalProperties, value[key]) && context.AddKey(key);
4204
4207
  if (!isAdditionalProperty) additionalProperties.push(key);
4205
4208
  return isAdditionalProperty;
@@ -4278,7 +4281,7 @@ function CheckDependencies(stack, context, schema, value) {
4278
4281
  function ErrorDependencies(stack, context, schemaPath, instancePath, schema, value) {
4279
4282
  const isLength = IsEqual(Keys(value).length, 0);
4280
4283
  const isEvery = EveryAll(Entries(schema.dependencies), 0, ([key, schema]) => {
4281
- const nextSchemaPath = `${schemaPath}/dependencies/${key}`;
4284
+ const nextSchemaPath = `${schemaPath}/dependencies/${EncodeFragment(key)}`;
4282
4285
  return !HasPropertyKey(value, key) || (IsArray$1(schema) ? schema.every((dependency) => HasPropertyKey(value, dependency) || context.AddError("dependencies", schemaPath, instancePath, {
4283
4286
  property: key,
4284
4287
  dependencies: schema
@@ -4313,7 +4316,7 @@ function CheckDependentSchemas(stack, context, schema, value) {
4313
4316
  function ErrorDependentSchemas(stack, context, schemaPath, instancePath, schema, value) {
4314
4317
  const isLength = IsEqual(Keys(value).length, 0);
4315
4318
  const isEvery = EveryAll(Entries(schema.dependentSchemas), 0, ([key, schema]) => {
4316
- const nextSchemaPath = `${schemaPath}/dependentSchemas/${key}`;
4319
+ const nextSchemaPath = `${schemaPath}/dependentSchemas/${EncodeFragment(key)}`;
4317
4320
  return !HasPropertyKey(value, key) || ErrorSchema(stack, context, nextSchemaPath, instancePath, schema, value);
4318
4321
  });
4319
4322
  return isLength || isEvery;
@@ -5167,10 +5170,10 @@ function CheckPatternProperties(stack, context, schema, value) {
5167
5170
  }
5168
5171
  function ErrorPatternProperties(stack, context, schemaPath, instancePath, schema, value) {
5169
5172
  return EveryAll(Entries(schema.patternProperties), 0, ([pattern, schema]) => {
5170
- const nextSchemaPath = `${schemaPath}/patternProperties/${pattern}`;
5173
+ const nextSchemaPath = `${schemaPath}/patternProperties/${EncodeFragment(pattern)}`;
5171
5174
  const regexp = UnicodeRegExp(pattern);
5172
5175
  return EveryAll(Entries(value), 0, ([key, value]) => {
5173
- const nextInstancePath = `${instancePath}/${key}`;
5176
+ const nextInstancePath = `${instancePath}/${EncodeFragment(key)}`;
5174
5177
  return !regexp.test(key) || ErrorSchemaPushStack(stack, context, nextSchemaPath, nextInstancePath, schema, value) && context.AddKey(key);
5175
5178
  });
5176
5179
  });
@@ -5203,8 +5206,8 @@ function CheckProperties(stack, context, schema, value) {
5203
5206
  function ErrorProperties(stack, context, schemaPath, instancePath, schema, value) {
5204
5207
  const required = IsRequired(schema) ? schema.required : [];
5205
5208
  return EveryAll(Entries(schema.properties), 0, ([key, schema]) => {
5206
- const nextSchemaPath = `${schemaPath}/properties/${key}`;
5207
- const nextInstancePath = `${instancePath}/${key}`;
5209
+ const nextSchemaPath = `${schemaPath}/properties/${EncodeFragment(key)}`;
5210
+ const nextInstancePath = `${instancePath}/${EncodeFragment(key)}`;
5208
5211
  const isProperty = () => !HasPropertyKey(value, key) || ErrorSchemaPushStack(stack, context, nextSchemaPath, nextInstancePath, schema, value[key]) && context.AddKey(key);
5209
5212
  return IsExactOptional(required, key) ? isProperty() : InexactOptionalCheck(value, key) || isProperty();
5210
5213
  });
@@ -5215,7 +5218,7 @@ function CheckPropertyNames(stack, context, schema, value) {
5215
5218
  function ErrorPropertyNames(stack, context, schemaPath, instancePath, schema, value) {
5216
5219
  const propertyNames = [];
5217
5220
  return EveryAll(Keys(value), 0, (key, _index) => {
5218
- const nextInstancePath = `${instancePath}/${key}`;
5221
+ const nextInstancePath = `${instancePath}/${EncodeFragment(key)}`;
5219
5222
  const isPropertyName = ErrorSchema(stack, context, `${schemaPath}/propertyNames`, nextInstancePath, schema.propertyNames, key);
5220
5223
  if (!isPropertyName) propertyNames.push(key);
5221
5224
  return isPropertyName;