@agntn/harnesses 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -55,20 +55,21 @@ import type { ClaudeSessionEntry, CodexThread, GeminiConversationRecord } from "
55
55
 
56
56
  ## Supported agents
57
57
 
58
- | Agent | ID | Detection | Skills | Hooks | Sessions |
59
- | --------------- | ---------------- | ------------- | --------------------- | ------------------------ | -------------- |
60
- | Antigravity CLI | `antigravity` | project | `.agents/skills/` | - | JSONL + SQLite |
61
- | Claude Code | `claude` | env + project | `.claude/skills/` | `.claude/hooks/` | JSONL |
62
- | Codex CLI | `codex` | project | `.agents/skills/` | - | SQLite + JSONL |
63
- | Gemini CLI | `gemini` | env + project | `.gemini/skills/` | - | JSON |
64
- | Grok CLI | `grok` | env + project | `.grok/skills/` | `.grok/hooks/` | TOML + JSONL |
65
- | OpenCode | `opencode` | project | `.opencode/skills/` | - | SQLite |
66
- | Cursor | `cursor` | env + project | `.cursor/skills/` | - | - |
67
- | GitHub Copilot | `github-copilot` | env + project | `.github/skills/` | - | - |
68
- | Mastra Code | `mastracode` | project | `.mastracode/skills/` | `.mastracode/hooks.json` | SQLite |
69
- | OMP (oh-my-pi) | `omp` | env + project | `.omp/skills/` | - | JSONL + SQLite |
70
- | Pi Coding Agent | `pi` | env + project | `.pi/skills/` | - | JSON + JSONL |
71
- | Freebuff | `freebuff` | project | `.agents/skills/` | - | JSON + JSONL |
58
+ | Agent | ID | Detection | Skills | Hooks | Sessions |
59
+ | --------------- | ---------------- | ------------- | ---------------------- | ------------------------ | -------------- |
60
+ | Antigravity CLI | `antigravity` | project | `.agents/skills/` | - | JSONL + SQLite |
61
+ | Claude Code | `claude` | env + project | `.claude/skills/` | `.claude/hooks/` | JSONL |
62
+ | Codex CLI | `codex` | project | `.agents/skills/` | - | SQLite + JSONL |
63
+ | Gemini CLI | `gemini` | env + project | `.gemini/skills/` | - | JSON |
64
+ | Grok CLI | `grok` | env + project | `.grok/skills/` | `.grok/hooks/` | TOML + JSONL |
65
+ | OpenCode | `opencode` | project | `.opencode/skills/` | - | SQLite |
66
+ | Cursor | `cursor` | env + project | `.cursor/skills/` | - | - |
67
+ | GitHub Copilot | `github-copilot` | env + project | `.github/skills/` | - | - |
68
+ | Mastra Code | `mastracode` | project | `.mastracode/skills/` | `.mastracode/hooks.json` | SQLite |
69
+ | OMP (oh-my-pi) | `omp` | env + project | `.omp/skills/` | - | JSONL + SQLite |
70
+ | Pi Coding Agent | `pi` | env + project | `.pi/skills/` | - | JSON + JSONL |
71
+ | Prime Agent | `prime-agent` | env + project | `.prime/agent/skills/` | - | JSONL + JSON |
72
+ | Freebuff | `freebuff` | project | `.agents/skills/` | - | JSON + JSONL |
72
73
 
73
74
  ### Native audio and video input
74
75
 
@@ -87,6 +88,7 @@ import type { ClaudeSessionEntry, CodexThread, GeminiConversationRecord } from "
87
88
  | Mastra Code | No | No | No verified native route |
88
89
  | OMP (oh-my-pi) | No | No | No verified native route |
89
90
  | Pi Coding Agent | No | No | No verified native route |
91
+ | Prime Agent | No | No | No verified native route |
90
92
  | Freebuff | No | No | No verified native route |
91
93
 
92
94
  Primary references: [Antigravity prompting](https://antigravity.google/docs/cli/prompting/), [Antigravity changelog](https://github.com/google-antigravity/antigravity-cli/blob/main/CHANGELOG.md), [Gemini CLI tools](https://github.com/google-gemini/gemini-cli/blob/main/docs/reference/tools.md), [Gemini CLI video request](https://github.com/google-gemini/gemini-cli/issues/27194), [OpenCode attachments](https://opencode.ai/v2/docs/attachments/), [Cursor prompting](https://cursor.com/docs/agent/prompting), and [Copilot CLI voice input](https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli/voice-input).
@@ -109,7 +111,7 @@ console.log(result.aborted);
109
111
 
110
112
  This is command cleanup, not a sandbox. Descendants that leave the POSIX process group, or outlive an already exited root on Windows, cannot be reliably reached by these mechanisms. Inherited output pipes do not extend the wait after cleanup. Scheduling and OS delays can exceed the stated budgets.
111
113
 
112
- Each agent is a concrete subclass of the abstract `Harness` class. Custom subclasses can be added with `registerHarness`. Every harness exposes config paths, session locations, instruction files, skills dirs, hooks, commands, persistence formats, capabilities (MCP, vision, audio, video, tools, streaming), detection rules, a normalized non-interactive invocation (`harness.invoke(prompt, { model })`) where the CLI has a headless mode, native model listing (`harness.listModels()`) where the CLI supports it, and its MCP server config files (`listMcpServers`/`addMcpServer`/`removeMcpServer` normalize the dialects; writes rewrite JSON and surgically edit TOML with comments preserved). `syncMcpServers` treats `~/.config/agntn/mcp.jsonc` (JSONC, XDG-aware) as the single source of truth and resets every harness's user-scope MCP config to exactly that list; a top-level `"excludes": ["codex"]` array opts individual harnesses out of the sync (their own servers stay, master-listed names are withdrawn), and `~`/`${HOME}` in commands, args, and env values expand to absolute paths at sync time (harnesses spawn MCP servers without a shell). `syncAgentsFiles` links every harness's global instructions file (CLAUDE.md/AGENTS.md/GEMINI.md) to one master file as symlinks, so an edit made through any harness lands in the single physical copy; `~/.config/agntn/agents.jsonc` sets the `source`, `companions`, and `excludes`, diverged regular files are backed up and relinked, and check mode reports without writing. Companion paths are relative to the source directory and are linked at the same relative path beside each harness target.
114
+ Each agent is a concrete subclass of the abstract `Harness` class. Custom subclasses can be added with `registerHarness`. Every harness exposes config paths, session locations, instruction files, skills dirs, hooks, commands, persistence formats, capabilities (MCP, vision, audio, video, tools, streaming), detection rules, a normalized non-interactive invocation (`harness.invoke(prompt, { model })`) where the CLI has a headless mode, native model listing (`harness.listModels()`) where the CLI supports it, and its MCP server config files (`listMcpServers`/`addMcpServer`/`removeMcpServer` normalize the dialects; writes rewrite JSON and surgically edit TOML with comments preserved). `syncMcpServers` treats `~/.config/agntn/mcp.jsonc` (JSONC, XDG-aware) as the single source of truth and resets every harness's user-scope MCP config to exactly that list; a top-level `"excludes": ["codex"]` array opts individual harnesses out of the sync (their own servers stay, master-listed names are withdrawn), and `~`/`${HOME}` in commands, args, and env values expand to absolute paths at sync time (harnesses spawn MCP servers without a shell). An env value that is exactly `${NAME}` references that variable of the harness environment: Prime Agent stores it natively as `{"env": "NAME"}`, the other harnesses receive the value from the syncing process, and a server one dialect cannot hold is reported as `skipped` with a reason while the rest of the list syncs. `syncAgentsFiles` links every harness's global instructions file (CLAUDE.md/AGENTS.md/GEMINI.md) to one master file as symlinks, so an edit made through any harness lands in the single physical copy; `~/.config/agntn/agents.jsonc` sets the `source`, `companions`, and `excludes`, diverged regular files are backed up and relinked, and check mode reports without writing. Companion paths are relative to the source directory and are linked at the same relative path beside each harness target.
113
115
 
114
116
  ```jsonc
115
117
  {
@@ -121,7 +123,7 @@ Each agent is a concrete subclass of the abstract `Harness` class. Custom subcla
121
123
 
122
124
  All paths carry `scope` (user/project/system/data), `level` (official/community/inferred), and optional `platforms` tags.
123
125
 
124
- The `harnesses_info` agent tool accepts one harness id or a batch of up to 20 ids. Batch results keep the input order and include errors for unknown ids beside successful metadata.
126
+ The `harnesses_info` agent tool accepts one harness id or a batch of up to 20 ids. Batch results keep the input order and include errors for unknown ids beside successful metadata. Paths come once, resolved for the current platform. The templates stay with `harnesses info`.
125
127
 
126
128
  ## CLI
127
129
 
@@ -151,7 +153,7 @@ harnesses mcp # run the MCP server over stdio
151
153
 
152
154
  [unagent](https://github.com/onmax/unagent) covers similar ground but makes different tradeoffs.
153
155
 
154
- **harnesses is deep and narrow.** Each harness gets verified, platform-specific paths with scope, evidence level, and platform tags. Session formats are typed per harness. Twelve harnesses, each fully mapped.
156
+ **harnesses is deep and narrow.** Each harness gets verified, platform-specific paths with scope, evidence level, and platform tags. Session formats are typed per harness. Thirteen harnesses, each fully mapped.
155
157
 
156
158
  **unagent is wide and shallow.** 40+ agents detected by env vars, but each definition is just `configDir` + `rulesFile` + `skillsDir`. No platform-specific paths, no session schemas. In exchange, it ships runtime primitives harnesses doesn't touch yet: skill install/uninstall, vector stores, browser automation, sandboxes, queues, workflows.
157
159
 
@@ -1,5 +1,5 @@
1
1
  declare const version: string;
2
- type HarnessId = "antigravity" | "codex" | "gemini" | "grok" | "claude" | "opencode" | "cursor" | "freebuff" | "github-copilot" | "mastracode" | "omp" | "pi";
2
+ type HarnessId = "antigravity" | "codex" | "gemini" | "grok" | "claude" | "opencode" | "cursor" | "freebuff" | "github-copilot" | "mastracode" | "omp" | "pi" | "prime-agent";
3
3
  type EvidenceLevel = "official" | "community" | "inferred";
4
4
  type Platform = "linux" | "darwin" | "win32";
5
5
  interface PathCandidate {
@@ -122,6 +122,11 @@ interface McpServerConfig {
122
122
  transport: "stdio" | "http" | "sse";
123
123
  command?: string;
124
124
  args?: string[];
125
+ /**
126
+ * Environment for stdio servers. A value that is exactly "${NAME}" refers to
127
+ * the variable NAME of the harness environment: dialects with native
128
+ * references keep it, the others resolve it when the entry is written.
129
+ */
125
130
  env?: Record<string, string>;
126
131
  url?: string;
127
132
  headers?: Record<string, string>;
@@ -133,8 +138,11 @@ interface McpConfigFile extends PathCandidate {
133
138
  format: "json" | "toml";
134
139
  /** Object path to the server map inside the file, e.g. ["mcpServers"]. */
135
140
  key: string[];
136
- /** Shape of individual entries; "standard" is the {command, args, env, url} family. */
137
- dialect: "standard" | "antigravity" | "opencode" | "vscode";
141
+ /**
142
+ * Shape of individual entries; "standard" is the {command, args, env, url}
143
+ * family and "prime" is that family with env values as {"env": "NAME"} references.
144
+ */
145
+ dialect: "standard" | "antigravity" | "opencode" | "prime" | "vscode";
138
146
  }
139
147
  interface HarnessDetection {
140
148
  /** Environment variables that indicate running inside this agent. */
@@ -284,6 +292,10 @@ interface McpConfigListing {
284
292
  /** Set when the file exists but could not be parsed. */
285
293
  error?: string;
286
294
  }
295
+ /** Thrown when a server's env cannot be written the way the target dialect requires. */
296
+ declare class McpEnvError extends Error {
297
+ readonly name = "McpEnvError";
298
+ }
287
299
  /**
288
300
  * Lists the MCP servers a harness has configured, per declared config file.
289
301
  * Missing files come back with `exists: false`; unparsable ones carry `error`.
@@ -346,7 +358,9 @@ interface SyncTargetResult {
346
358
  excluded?: true;
347
359
  results: Array<{
348
360
  name: string;
349
- action: "added" | "replaced" | "removed" | "unchanged";
361
+ action: "added" | "replaced" | "removed" | "unchanged" | "skipped";
362
+ /** Why a master server could not be written to this harness. */
363
+ reason?: string;
350
364
  }>;
351
365
  }
352
366
  /** Outcome of resetting the harness configs to the master list. */
@@ -430,5 +444,5 @@ declare function readAgentsConfig(options?: ResolveOptions): AgentsConfig;
430
444
  * @returns {AgentsSyncReport} Per-harness synchronization outcomes.
431
445
  */
432
446
  declare function syncAgentsFiles(harnesses: readonly Harness[], check?: boolean, options?: ResolveOptions): AgentsSyncReport;
433
- export { AgentsCompanionTargetResult, AgentsConfig, AgentsSyncAction, AgentsSyncReport, AgentsTargetResult, AvailableModel, EvidenceLevel, Harness, HarnessCapabilities, HarnessConstructor, HarnessDetection, HarnessId, HarnessInvocation, HarnessInvocationModes, HarnessModelListing, InvokeOptions, InvokeResult, ListModelsOptions, ListModelsResult, McpConfigFile, McpConfigListing, McpServerConfig, PathCandidate, Platform, ResolveOptions, ResolvedPaths, StorageDescriptor, SyncReport, SyncTargetResult, addMcpServer, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, removeMcpServer, syncAgentsFiles, syncMcpServers, version };
447
+ export { AgentsCompanionTargetResult, AgentsConfig, AgentsSyncAction, AgentsSyncReport, AgentsTargetResult, AvailableModel, EvidenceLevel, Harness, HarnessCapabilities, HarnessConstructor, HarnessDetection, HarnessId, HarnessInvocation, HarnessInvocationModes, HarnessModelListing, InvokeOptions, InvokeResult, ListModelsOptions, ListModelsResult, McpConfigFile, McpConfigListing, McpEnvError, McpServerConfig, PathCandidate, Platform, ResolveOptions, ResolvedPaths, StorageDescriptor, SyncReport, SyncTargetResult, addMcpServer, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, removeMcpServer, syncAgentsFiles, syncMcpServers, version };
434
448
  //# sourceMappingURL=agents-sync.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents-sync.d.mts","names":[],"sources":["../../src/types.ts","../../src/harness.ts","../../src/mcp-servers.ts","../../src/agents-sync.ts"],"mappings":"AAEA,cAAa;KAED;KAaA;KACA;UAEK;EACf;EACA;EACA,OAAO;EACP,YAAY;EACZ;;UAGe;EACf;EACA,OAAO;EACP;;UAGe;EACf;EACA;;EAEA;;EAEA;EACA;EACA;;;UAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;EACA,OAAO;EACP;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;;EAEf;;EAEA;EACA,OAAO;EACP;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA,SAAS;;UAGM;EACf;EACA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;EAEA,SAAS;;EAET;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;;EAEA;;;UAIe,yBAAyB;;EAExC,QAAQ;;;UAIO;EACf;EACA;EACA;EACA;EACA,MAAM;EACN;EACA,UAAU;;EAEV;;;UAIe,sBAAsB;EACrC;;EAEA;;EAEA;;UAGe;;EAEf;;EAEA;;UAGe;EACf;EACA;EACA,WAAW;;UAGI;EACf,QAAQ;EACR,UAAU;EACV,cAAc;EACd,QAAQ;EACR,UAAU;EACV,OAAO;;KCnJJ,oBAAoB;EACvB;EACA;EACA;EACA;;uBA2OoB;oBACF,IAAI;oBACJ;oBACA;oBACA,QAAQ;oBACR,UAAU;oBACV,aAAa;oBACb,cAAc;oBACd,QAAQ;oBACR,UAAU;oBACV,OAAO;oBACP,cAAc;oBACd,WAAW;;oBAEX,YAAY;;WAErB,cAAc;;WAEd,YAAY;;;;;;WAMZ;EAET;EAYA;EAIA,cAAc;MAKV;;;;;;MAsBA,mBAAmB;;;;;;;;;;EAoBvB,gBACE,gBACA,UAAS;IACN;IAAiB;;;;;;;;EAmBtB,gBAAgB,UAAS;;;;;;;;;;EAqBzB,OAAO,gBAAgB,UAAS,gBAAqB,QAAQ;;;;;;;;;UA2BrD;;;;;;;EAgBR,yBAAyB;IAAoB;IAAiB;;;;;;;;;EAmBxD,WAAW,UAAS,oBAAyB,QAAQ;;;;;;YAyBjD,wBAAwB,kBAAkB;;;;;;;;;;EAapD,kBAAkB,UAAU,eAC1B,kBAAkB,KAClB,UAAS,iBACR;EAcH,QAAQ,UAAS,iBAAsB;;KAY7B,+BAA+B;;UCxgB1B;EACf;EACA,OAAO;EACP,QAAQ;EACR,OAAO;EACP;EACA;EACA,SAAS;;EAET;;;;;;;;;;iBAmRc,eAAe,SAAS,SAAS,UAAS,iBAAsB;;;;;;;;;;;;iBAiLhE,aACd,SAAS,SACT,QAAQ,iBACR,4BACA,UAAS;EACN;EAAc;;;;;;;;;;;iBA0BH,gBACd,SAAS,SACT,cACA,4BACA,UAAS;EACN;EAAc;;;;;;;;;iBA8FH,WAAW;;;;;;;iBAwBX,cAAc,UAAS;;UAKtB;EACf;EACA;;EAEA;;EAEA;EACA,SAAS;IAAQ;IAAc;;;;UAIhB;EACf;EACA;EACA,SAAS;;;;;;;;iBAuEK,qBAAqB,UAAS;EAC5C;EACA,SAAS;;EAET;;;;;;;;;;;iBA2Hc,eACd,oBAAoB,WACpB,UAAS,iBACR;;UC1zBc;;EAEf;;EAEA;;EAEA;;EAEA;;KAGU;;UAGK;WACN;WACA;WACA,QAAQ,QAAQ;;WAEhB;;;UAIM;EACf;EACA;EACA,QAAQ;;EAER;;EAEA,sBAAsB;;;UAIP;EACf;EACA;EACA,SAAS;;;;;;;;iBA2FK,iBAAiB,UAAS,iBAAsB;;;;;;;;;;iBAiMhD,gBACd,oBAAoB,WACpB,iBACA,UAAS,iBACR"}
1
+ {"version":3,"file":"agents-sync.d.mts","names":[],"sources":["../../src/types.ts","../../src/harness.ts","../../src/mcp-servers.ts","../../src/agents-sync.ts"],"mappings":"AAEA,cAAa;KAED;KAcA;KACA;UAEK;EACf;EACA;EACA,OAAO;EACP,YAAY;EACZ;;UAGe;EACf;EACA,OAAO;EACP;;UAGe;EACf;EACA;;EAEA;;EAEA;EACA;EACA;;;UAIe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;EACA,OAAO;EACP;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;;EAEf;;EAEA;EACA,OAAO;EACP;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA,SAAS;;UAGM;EACf;EACA,MAAM;;EAEN;;EAEA;;EAEA;;EAEA;;EAEA,SAAS;;EAET;;UAGe;EACf;EACA;EACA;EACA;;EAEA;EACA;;EAEA;;;UAIe,yBAAyB;;EAExC,QAAQ;;;UAIO;EACf;EACA;EACA;EACA;;;;;;EAMA,MAAM;EACN;EACA,UAAU;;EAEV;;;UAIe,sBAAsB;EACrC;;EAEA;;;;;EAKA;;UAGe;;EAEf;;EAEA;;UAGe;EACf;EACA;EACA,WAAW;;UAGI;EACf,QAAQ;EACR,UAAU;EACV,cAAc;EACd,QAAQ;EACR,UAAU;EACV,OAAO;;KC5JJ,oBAAoB;EACvB;EACA;EACA;EACA;;uBA2OoB;oBACF,IAAI;oBACJ;oBACA;oBACA,QAAQ;oBACR,UAAU;oBACV,aAAa;oBACb,cAAc;oBACd,QAAQ;oBACR,UAAU;oBACV,OAAO;oBACP,cAAc;oBACd,WAAW;;oBAEX,YAAY;;WAErB,cAAc;;WAEd,YAAY;;;;;;WAMZ;EAET;EAYA;EAIA,cAAc;MAKV;;;;;;MAsBA,mBAAmB;;;;;;;;;;EAoBvB,gBACE,gBACA,UAAS;IACN;IAAiB;;;;;;;;EAmBtB,gBAAgB,UAAS;;;;;;;;;;EAqBzB,OAAO,gBAAgB,UAAS,gBAAqB,QAAQ;;;;;;;;;UA2BrD;;;;;;;EAgBR,yBAAyB;IAAoB;IAAiB;;;;;;;;;EAmBxD,WAAW,UAAS,oBAAyB,QAAQ;;;;;;YAyBjD,wBAAwB,kBAAkB;;;;;;;;;;EAapD,kBAAkB,UAAU,eAC1B,kBAAkB,KAClB,UAAS,iBACR;EAcH,QAAQ,UAAS,iBAAsB;;KAY7B,+BAA+B;;UCxgB1B;EACf;EACA,OAAO;EACP,QAAQ;EACR,OAAO;EACP;EACA;EACA,SAAS;;EAET;;;cA2JW,oBAAoB;WACb;;;;;;;;;;iBAkOJ,eAAe,SAAS,SAAS,UAAS,iBAAsB;;;;;;;;;;;;iBAiLhE,aACd,SAAS,SACT,QAAQ,iBACR,4BACA,UAAS;EACN;EAAc;;;;;;;;;;;iBA2BH,gBACd,SAAS,SACT,cACA,4BACA,UAAS;EACN;EAAc;;;;;;;;;iBA8FH,WAAW;;;;;;;iBAwBX,cAAc,UAAS;;UAKtB;EACf;EACA;;EAEA;;EAEA;EACA,SAAS;IACP;IACA;;IAEA;;;;UAKa;EACf;EACA;EACA,SAAS;;;;;;;;iBAuEK,qBAAqB,UAAS;EAC5C;EACA,SAAS;;EAET;;;;;;;;;;;iBAmIc,eACd,oBAAoB,WACpB,UAAS,iBACR;;UCn7Bc;;EAEf;;EAEA;;EAEA;;EAEA;;KAGU;;UAGK;WACN;WACA;WACA,QAAQ,QAAQ;;WAEhB;;;UAIM;EACf;EACA;EACA,QAAQ;;EAER;;EAEA,sBAAsB;;;UAIP;EACf;EACA;EACA,SAAS;;;;;;;;iBA2FK,iBAAiB,UAAS,iBAAsB;;;;;;;;;;iBAiMhD,gBACd,oBAAoB,WACpB,iBACA,UAAS,iBACR"}
@@ -1896,16 +1896,37 @@ const MODEL_HEADERS = [
1896
1896
  "thinking",
1897
1897
  "images"
1898
1898
  ];
1899
- function parseTokenCount(value) {
1899
+ function parseTokenCount(value, harness) {
1900
1900
  const match = /^(\d+(?:\.\d+)?)([KM])?$/.exec(value);
1901
- if (!match?.[1]) throw new Error(`Invalid Pi token count: ${JSON.stringify(value)}`);
1901
+ if (!match?.[1]) throw new Error(`Invalid ${harness} token count: ${JSON.stringify(value)}`);
1902
1902
  const multiplier = match[2] === "M" ? 1e6 : match[2] === "K" ? 1e3 : 1;
1903
1903
  return Number(match[1]) * multiplier;
1904
1904
  }
1905
- function parseBoolean(value, column) {
1905
+ function parseBoolean(value, column, harness) {
1906
1906
  if (value === "yes") return true;
1907
1907
  if (value === "no") return false;
1908
- throw new Error(`Invalid Pi ${column} value: ${JSON.stringify(value)}`);
1908
+ throw new Error(`Invalid ${harness} ${column} value: ${JSON.stringify(value)}`);
1909
+ }
1910
+ function parsePiModelTable(stdout, harness) {
1911
+ const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1912
+ const first = lines[0];
1913
+ if (!first) throw new Error(`Unexpected empty ${harness} model-list output`);
1914
+ if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
1915
+ const headers = first.split(/\s{2,}/);
1916
+ if (headers.length !== MODEL_HEADERS.length || headers.some((value, i) => value !== MODEL_HEADERS[i])) throw new Error(`Unexpected ${harness} model-list header: ${JSON.stringify(first)}`);
1917
+ return lines.slice(1).map((line) => {
1918
+ const columns = line.split(/\s{2,}/);
1919
+ const [provider, id, context, maxOutput, thinking, images] = columns;
1920
+ if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected ${harness} model-list row: ${JSON.stringify(line)}`);
1921
+ return {
1922
+ provider,
1923
+ id,
1924
+ contextWindow: parseTokenCount(context, harness),
1925
+ maxOutputTokens: parseTokenCount(maxOutput, harness),
1926
+ thinking: parseBoolean(thinking, "thinking", harness),
1927
+ images: parseBoolean(images, "images", harness)
1928
+ };
1929
+ });
1909
1930
  }
1910
1931
  var Pi = class extends Harness {
1911
1932
  id = "pi";
@@ -2046,25 +2067,172 @@ var Pi = class extends Harness {
2046
2067
  projectMarkers: [".pi"]
2047
2068
  };
2048
2069
  parseModelListingOutput(stdout) {
2049
- const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2050
- const first = lines[0];
2051
- if (!first) throw new Error("Unexpected empty Pi model-list output");
2052
- if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
2053
- const headers = first.split(/\s{2,}/);
2054
- if (headers.length !== MODEL_HEADERS.length || headers.some((value, i) => value !== MODEL_HEADERS[i])) throw new Error(`Unexpected Pi model-list header: ${JSON.stringify(first)}`);
2055
- return lines.slice(1).map((line) => {
2056
- const columns = line.split(/\s{2,}/);
2057
- const [provider, id, context, maxOutput, thinking, images] = columns;
2058
- if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected Pi model-list row: ${JSON.stringify(line)}`);
2059
- return {
2060
- provider,
2061
- id,
2062
- contextWindow: parseTokenCount(context),
2063
- maxOutputTokens: parseTokenCount(maxOutput),
2064
- thinking: parseBoolean(thinking, "thinking"),
2065
- images: parseBoolean(images, "images")
2066
- };
2067
- });
2070
+ return parsePiModelTable(stdout, "Pi");
2071
+ }
2072
+ };
2073
+ var PrimeAgent = class extends Harness {
2074
+ id = "prime-agent";
2075
+ name = "Prime Agent";
2076
+ binaries = ["prime-agent"];
2077
+ capabilities = {
2078
+ mcp: true,
2079
+ vision: true,
2080
+ audio: false,
2081
+ video: false,
2082
+ tools: true,
2083
+ streaming: true
2084
+ };
2085
+ config = [{
2086
+ path: "~/.prime/agent/settings.json",
2087
+ scope: "user",
2088
+ level: "official",
2089
+ note: "Config dir overridable with PRIME_AGENT_CODING_AGENT_DIR."
2090
+ }, {
2091
+ path: ".prime/agent/settings.json",
2092
+ scope: "project",
2093
+ level: "official"
2094
+ }];
2095
+ sessions = [{
2096
+ path: "~/.prime/agent/sessions/<session-id>.jsonl",
2097
+ scope: "data",
2098
+ level: "official",
2099
+ note: "Flat directory of JSONL transcripts whose header records the working directory; session ids are UUIDv7. Overridable with PRIME_AGENT_SESSION_DIR or --session-dir."
2100
+ }, {
2101
+ path: "~/.prime/agent/session-artifacts/<session-id>/",
2102
+ scope: "data",
2103
+ level: "official",
2104
+ note: "Per-session Python kernel state, scheduled jobs, harness state and child session transcripts under sub-<id>/."
2105
+ }];
2106
+ persistence = [
2107
+ {
2108
+ format: "JSON",
2109
+ level: "official",
2110
+ note: "Settings, auth, telemetry, model cache and session artifact state files."
2111
+ },
2112
+ {
2113
+ format: "JSONL",
2114
+ level: "official",
2115
+ note: "Session transcripts, the RLM spawn ledger and structured logs."
2116
+ },
2117
+ {
2118
+ format: "dill",
2119
+ level: "official",
2120
+ note: "Pickled Python REPL state (kernel-state.dill) in session artifacts."
2121
+ }
2122
+ ];
2123
+ instructions = [
2124
+ {
2125
+ path: "AGENTS.md",
2126
+ scope: "project",
2127
+ level: "official",
2128
+ note: "Loaded from parent directories and cwd; CLAUDE.md accepted as an alternative."
2129
+ },
2130
+ {
2131
+ path: ".prime/agent/SYSTEM.md",
2132
+ scope: "project",
2133
+ level: "official",
2134
+ note: "Replaces the default system prompt; APPEND_SYSTEM.md appends instead."
2135
+ },
2136
+ {
2137
+ path: "~/.prime/agent/AGENTS.md",
2138
+ scope: "user",
2139
+ level: "official",
2140
+ note: "Global user-level instructions; CLAUDE.md accepted as an alternative."
2141
+ },
2142
+ {
2143
+ path: "~/.prime/agent/SYSTEM.md",
2144
+ scope: "user",
2145
+ level: "official",
2146
+ note: "Global system prompt replacement; APPEND_SYSTEM.md appends instead."
2147
+ }
2148
+ ];
2149
+ skills = [
2150
+ {
2151
+ path: ".prime/agent/skills/",
2152
+ scope: "project",
2153
+ level: "official"
2154
+ },
2155
+ {
2156
+ path: ".agents/skills/",
2157
+ scope: "project",
2158
+ level: "official",
2159
+ note: "Also scanned in ancestor directories up to the Git repository root; root .md files are ignored there."
2160
+ },
2161
+ {
2162
+ path: "~/.prime/agent/skills/",
2163
+ scope: "user",
2164
+ level: "official",
2165
+ note: "Global user-level skills."
2166
+ },
2167
+ {
2168
+ path: "~/.agents/skills/",
2169
+ scope: "user",
2170
+ level: "official",
2171
+ note: "Shared skills directory; root .md files are ignored there."
2172
+ }
2173
+ ];
2174
+ commands = [{
2175
+ path: ".prime/agent/prompts/",
2176
+ scope: "project",
2177
+ level: "official",
2178
+ note: "Prompt templates invoked as slash commands."
2179
+ }, {
2180
+ path: "~/.prime/agent/prompts/",
2181
+ scope: "user",
2182
+ level: "official",
2183
+ note: "Global user-level prompt templates."
2184
+ }];
2185
+ hooks = [];
2186
+ invocation = {
2187
+ args: ["-p", "{prompt}"],
2188
+ jsonArgs: [
2189
+ "-p",
2190
+ "--mode",
2191
+ "json",
2192
+ "{prompt}"
2193
+ ],
2194
+ noToolsArgs: [
2195
+ "-p",
2196
+ "--no-tools",
2197
+ "{prompt}"
2198
+ ],
2199
+ noToolsJsonArgs: [
2200
+ "-p",
2201
+ "--no-tools",
2202
+ "--mode",
2203
+ "json",
2204
+ "{prompt}"
2205
+ ],
2206
+ modelArgs: ["--model", "{model}"],
2207
+ level: "official",
2208
+ note: "Add --mode json for structured event output. No read-only mode: the only built-in tool is a Python REPL and the worker and kernel processes are not a security sandbox."
2209
+ };
2210
+ modelListing = {
2211
+ args: ["model", "list"],
2212
+ searchArgs: [
2213
+ "model",
2214
+ "list",
2215
+ "{search}"
2216
+ ],
2217
+ level: "official",
2218
+ note: "Lists models with configured provider authentication; accepts an optional fuzzy search. --list-models was removed."
2219
+ };
2220
+ mcpConfigs = [{
2221
+ path: "~/.prime/agent/settings.json",
2222
+ scope: "user",
2223
+ level: "official",
2224
+ format: "json",
2225
+ key: ["mcpServers"],
2226
+ dialect: "prime",
2227
+ note: "prime-agent mcp add/remove manage this map; project settings entries are ignored for execution. Stdio env values are {\"env\": \"NAME\"} references, written from \"${NAME}\" values; literals are refused because the kernel rejects them at connect time."
2228
+ }];
2229
+ agentsFile = "~/.prime/agent/AGENTS.md";
2230
+ detection = {
2231
+ envVars: ["PRIME_AGENT_KERNEL_OWNER_PID"],
2232
+ projectMarkers: [".prime/agent"]
2233
+ };
2234
+ parseModelListingOutput(stdout) {
2235
+ return parsePiModelTable(stdout, "Prime Agent");
2068
2236
  }
2069
2237
  };
2070
2238
  const harnesses = [
@@ -2079,6 +2247,7 @@ const harnesses = [
2079
2247
  MastraCode,
2080
2248
  Omp,
2081
2249
  OpenCode,
2250
+ PrimeAgent,
2082
2251
  Pi
2083
2252
  ];
2084
2253
  let registry;
@@ -2222,9 +2391,30 @@ function normalizeStandard(name, raw) {
2222
2391
  enabled: booleanField(raw, "enabled")
2223
2392
  });
2224
2393
  }
2394
+ const ENV_REFERENCE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
2395
+ var McpEnvError = class extends Error {
2396
+ name = "McpEnvError";
2397
+ };
2398
+ function envReference(value) {
2399
+ return ENV_REFERENCE.exec(value)?.[1];
2400
+ }
2401
+ function primeEnv(value) {
2402
+ if (!isObjectRecord(value) || Array.isArray(value)) return void 0;
2403
+ const out = {};
2404
+ for (const [key, raw] of Object.entries(value)) if (typeof raw === "string") out[key] = raw;
2405
+ else if (isObjectRecord(raw) && typeof raw.env === "string") out[key] = `\${${raw.env}}`;
2406
+ return Object.keys(out).length > 0 ? out : void 0;
2407
+ }
2408
+ function normalizePrime(name, raw) {
2409
+ return compact({
2410
+ ...normalizeStandard(name, raw),
2411
+ env: primeEnv(raw.env)
2412
+ });
2413
+ }
2225
2414
  function normalizeEntry(name, raw, dialect) {
2226
2415
  if (dialect === "antigravity") return normalizeAntigravity(name, raw);
2227
2416
  if (dialect === "opencode") return normalizeOpenCode(name, raw);
2417
+ if (dialect === "prime") return normalizePrime(name, raw);
2228
2418
  return normalizeStandard(name, raw);
2229
2419
  }
2230
2420
  function denormalizeAntigravity(server) {
@@ -2260,9 +2450,44 @@ function denormalizeStandard(server) {
2260
2450
  enabled: server.enabled
2261
2451
  });
2262
2452
  }
2453
+ function denormalizePrime(server) {
2454
+ const env = server.env ? Object.fromEntries(Object.entries(server.env).map(([key, value]) => [key, { env: requirePrimeReference(server, key, value) }])) : void 0;
2455
+ return compact({
2456
+ ...denormalizeStandard(server),
2457
+ env
2458
+ });
2459
+ }
2460
+ function requirePrimeReference(server, key, value) {
2461
+ const reference = envReference(value);
2462
+ if (reference === void 0) throw new McpEnvError(`${server.name}: Prime Agent takes only environment references in env; write ${key} as "\${NAME}" and export NAME where prime-agent runs`);
2463
+ return reference;
2464
+ }
2465
+ function resolveEnvReferences(server, dialect) {
2466
+ if (!server.env) return server;
2467
+ if (dialect === "prime") {
2468
+ for (const [key, value] of Object.entries(server.env)) requirePrimeReference(server, key, value);
2469
+ return server;
2470
+ }
2471
+ const env = {};
2472
+ for (const [key, value] of Object.entries(server.env)) {
2473
+ const reference = envReference(value);
2474
+ if (reference === void 0) {
2475
+ env[key] = value;
2476
+ continue;
2477
+ }
2478
+ const resolved = Object.hasOwn(process.env, reference) ? process.env[reference] : void 0;
2479
+ if (typeof resolved !== "string") throw new McpEnvError(`${server.name}: env ${key} references ${reference}, which is not set in this environment`);
2480
+ env[key] = resolved;
2481
+ }
2482
+ return {
2483
+ ...server,
2484
+ env
2485
+ };
2486
+ }
2263
2487
  function denormalizeEntry(server, dialect) {
2264
2488
  if (dialect === "antigravity") return denormalizeAntigravity(server);
2265
2489
  if (dialect === "opencode") return denormalizeOpenCode(server);
2490
+ if (dialect === "prime") return denormalizePrime(server);
2266
2491
  return denormalizeStandard(server);
2267
2492
  }
2268
2493
  function listingBase(entry, path) {
@@ -2399,15 +2624,16 @@ function removeTomlServer(path, key, name) {
2399
2624
  }
2400
2625
  function addMcpServer(harness, server, scope = "user", options = {}) {
2401
2626
  const { entry, path } = writableConfig(harness, scope, options);
2627
+ const resolved = resolveEnvReferences(server, entry.dialect);
2402
2628
  if (entry.format === "toml") return {
2403
2629
  path,
2404
- replaced: addTomlServer(path, entry.key, server)
2630
+ replaced: addTomlServer(path, entry.key, resolved)
2405
2631
  };
2406
2632
  const root = JSON.parse(readIfExists$1(path) ?? "{}");
2407
2633
  const map = drill(root, entry.key, "create");
2408
2634
  if (!map) throw new Error(`Config at ${path} has a non-object at ${entry.key.join(".")}`);
2409
2635
  const replaced = Object.hasOwn(map, server.name);
2410
- map[server.name] = denormalizeEntry(server, entry.dialect);
2636
+ map[server.name] = denormalizeEntry(resolved, entry.dialect);
2411
2637
  writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
2412
2638
  return {
2413
2639
  path,
@@ -2616,20 +2842,29 @@ function withdrawMasterServersFromExcludedHarness(harness, existing, masterNames
2616
2842
  function syncIncludedHarness(harness, master, existing, masterNames, initialPath, options) {
2617
2843
  const results = [];
2618
2844
  let path = initialPath;
2619
- for (const server of master.servers) {
2620
- if (existing.get(server.name) === canonical(server)) {
2845
+ const { dialect } = writableConfig(harness, "user", options).entry;
2846
+ for (const server of master.servers) try {
2847
+ const resolved = resolveEnvReferences(server, dialect);
2848
+ if (existing.get(server.name) === canonical(resolved)) {
2621
2849
  results.push({
2622
2850
  name: server.name,
2623
2851
  action: "unchanged"
2624
2852
  });
2625
2853
  continue;
2626
2854
  }
2627
- const written = addMcpServer(harness, server, "user", options);
2855
+ const written = addMcpServer(harness, resolved, "user", options);
2628
2856
  path = written.path;
2629
2857
  results.push({
2630
2858
  name: server.name,
2631
2859
  action: written.replaced ? "replaced" : "added"
2632
2860
  });
2861
+ } catch (error) {
2862
+ if (!(error instanceof McpEnvError)) throw error;
2863
+ results.push({
2864
+ name: server.name,
2865
+ action: "skipped",
2866
+ reason: error.message
2867
+ });
2633
2868
  }
2634
2869
  for (const name of existing.keys()) {
2635
2870
  if (masterNames.has(name)) continue;
@@ -2858,6 +3093,6 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
2858
3093
  targets: harnesses.map((harness) => syncTarget(harness, config, check, options))
2859
3094
  };
2860
3095
  }
2861
- export { Harness, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
3096
+ export { Harness, McpEnvError, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
2862
3097
 
2863
3098
  //# sourceMappingURL=agents-sync.mjs.map