@agntn/harnesses 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
+ import { createRequire } from "node:module";
1
2
  import { addAbortListener } from "node:events";
2
- import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
4
  import { execFile, execFileSync, spawn } from "node:child_process";
4
5
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
5
6
  import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
@@ -963,6 +964,18 @@ var Freebuff = class extends Harness {
963
964
  id = "freebuff";
964
965
  name = "Freebuff";
965
966
  binaries = ["freebuff"];
967
+ get version() {
968
+ const configDir = join(homedir(), ".config", "manicode");
969
+ const binaryName = process.platform === "win32" ? "freebuff.exe" : "freebuff";
970
+ if (!existsSync(join(configDir, binaryName))) return null;
971
+ try {
972
+ const metadata = JSON.parse(readFileSync(join(configDir, "freebuff-metadata.json"), "utf8"));
973
+ if (typeof metadata === "object" && metadata !== null && "version" in metadata && typeof metadata.version === "string" && metadata.version !== "") return metadata.version;
974
+ } catch {
975
+ return null;
976
+ }
977
+ return null;
978
+ }
966
979
  capabilities = {
967
980
  mcp: true,
968
981
  vision: true,
@@ -1417,10 +1430,43 @@ var Grok = class extends Harness {
1417
1430
  projectMarkers: [".grok"]
1418
1431
  };
1419
1432
  };
1433
+ function packageVersion(commandPath) {
1434
+ try {
1435
+ const require = createRequire(realpathSync(commandPath));
1436
+ const metadata = JSON.parse(readFileSync(require.resolve("mastracode/package.json"), "utf8"));
1437
+ if (typeof metadata === "object" && metadata !== null && "version" in metadata && typeof metadata.version === "string" && metadata.version !== "") return metadata.version;
1438
+ } catch {
1439
+ return null;
1440
+ }
1441
+ return null;
1442
+ }
1420
1443
  var MastraCode = class extends Harness {
1421
1444
  id = "mastracode";
1422
1445
  name = "Mastra Code";
1423
1446
  binaries = ["mastracode"];
1447
+ get version() {
1448
+ const locator = process.platform === "win32" ? "where" : "which";
1449
+ let output;
1450
+ try {
1451
+ output = execFileSync(locator, [this.binaries[0] ?? "mastracode"], {
1452
+ encoding: "utf8",
1453
+ stdio: [
1454
+ "pipe",
1455
+ "pipe",
1456
+ "pipe"
1457
+ ],
1458
+ timeout: 5e3
1459
+ });
1460
+ } catch {
1461
+ return null;
1462
+ }
1463
+ const paths = output.split(/\r?\n/u).filter((path) => path !== "");
1464
+ for (const path of paths) {
1465
+ const version = packageVersion(path);
1466
+ if (version !== null) return version;
1467
+ }
1468
+ return null;
1469
+ }
1424
1470
  capabilities = {
1425
1471
  mcp: true,
1426
1472
  vision: true,
@@ -1896,16 +1942,37 @@ const MODEL_HEADERS = [
1896
1942
  "thinking",
1897
1943
  "images"
1898
1944
  ];
1899
- function parseTokenCount(value) {
1945
+ function parseTokenCount(value, harness) {
1900
1946
  const match = /^(\d+(?:\.\d+)?)([KM])?$/.exec(value);
1901
- if (!match?.[1]) throw new Error(`Invalid Pi token count: ${JSON.stringify(value)}`);
1947
+ if (!match?.[1]) throw new Error(`Invalid ${harness} token count: ${JSON.stringify(value)}`);
1902
1948
  const multiplier = match[2] === "M" ? 1e6 : match[2] === "K" ? 1e3 : 1;
1903
1949
  return Number(match[1]) * multiplier;
1904
1950
  }
1905
- function parseBoolean(value, column) {
1951
+ function parseBoolean(value, column, harness) {
1906
1952
  if (value === "yes") return true;
1907
1953
  if (value === "no") return false;
1908
- throw new Error(`Invalid Pi ${column} value: ${JSON.stringify(value)}`);
1954
+ throw new Error(`Invalid ${harness} ${column} value: ${JSON.stringify(value)}`);
1955
+ }
1956
+ function parsePiModelTable(stdout, harness) {
1957
+ const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1958
+ const first = lines[0];
1959
+ if (!first) throw new Error(`Unexpected empty ${harness} model-list output`);
1960
+ if (first.startsWith("No models available.") || first.startsWith("No models matching ")) return [];
1961
+ const headers = first.split(/\s{2,}/);
1962
+ 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)}`);
1963
+ return lines.slice(1).map((line) => {
1964
+ const columns = line.split(/\s{2,}/);
1965
+ const [provider, id, context, maxOutput, thinking, images] = columns;
1966
+ if (columns.length !== MODEL_HEADERS.length || !provider || !id || !context || !maxOutput || !thinking || !images) throw new Error(`Unexpected ${harness} model-list row: ${JSON.stringify(line)}`);
1967
+ return {
1968
+ provider,
1969
+ id,
1970
+ contextWindow: parseTokenCount(context, harness),
1971
+ maxOutputTokens: parseTokenCount(maxOutput, harness),
1972
+ thinking: parseBoolean(thinking, "thinking", harness),
1973
+ images: parseBoolean(images, "images", harness)
1974
+ };
1975
+ });
1909
1976
  }
1910
1977
  var Pi = class extends Harness {
1911
1978
  id = "pi";
@@ -2046,25 +2113,172 @@ var Pi = class extends Harness {
2046
2113
  projectMarkers: [".pi"]
2047
2114
  };
2048
2115
  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
- });
2116
+ return parsePiModelTable(stdout, "Pi");
2117
+ }
2118
+ };
2119
+ var PrimeAgent = class extends Harness {
2120
+ id = "prime-agent";
2121
+ name = "Prime Agent";
2122
+ binaries = ["prime-agent"];
2123
+ capabilities = {
2124
+ mcp: true,
2125
+ vision: true,
2126
+ audio: false,
2127
+ video: false,
2128
+ tools: true,
2129
+ streaming: true
2130
+ };
2131
+ config = [{
2132
+ path: "~/.prime/agent/settings.json",
2133
+ scope: "user",
2134
+ level: "official",
2135
+ note: "Config dir overridable with PRIME_AGENT_CODING_AGENT_DIR."
2136
+ }, {
2137
+ path: ".prime/agent/settings.json",
2138
+ scope: "project",
2139
+ level: "official"
2140
+ }];
2141
+ sessions = [{
2142
+ path: "~/.prime/agent/sessions/<session-id>.jsonl",
2143
+ scope: "data",
2144
+ level: "official",
2145
+ 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."
2146
+ }, {
2147
+ path: "~/.prime/agent/session-artifacts/<session-id>/",
2148
+ scope: "data",
2149
+ level: "official",
2150
+ note: "Per-session Python kernel state, scheduled jobs, harness state and child session transcripts under sub-<id>/."
2151
+ }];
2152
+ persistence = [
2153
+ {
2154
+ format: "JSON",
2155
+ level: "official",
2156
+ note: "Settings, auth, telemetry, model cache and session artifact state files."
2157
+ },
2158
+ {
2159
+ format: "JSONL",
2160
+ level: "official",
2161
+ note: "Session transcripts, the RLM spawn ledger and structured logs."
2162
+ },
2163
+ {
2164
+ format: "dill",
2165
+ level: "official",
2166
+ note: "Pickled Python REPL state (kernel-state.dill) in session artifacts."
2167
+ }
2168
+ ];
2169
+ instructions = [
2170
+ {
2171
+ path: "AGENTS.md",
2172
+ scope: "project",
2173
+ level: "official",
2174
+ note: "Loaded from parent directories and cwd; CLAUDE.md accepted as an alternative."
2175
+ },
2176
+ {
2177
+ path: ".prime/agent/SYSTEM.md",
2178
+ scope: "project",
2179
+ level: "official",
2180
+ note: "Replaces the default system prompt; APPEND_SYSTEM.md appends instead."
2181
+ },
2182
+ {
2183
+ path: "~/.prime/agent/AGENTS.md",
2184
+ scope: "user",
2185
+ level: "official",
2186
+ note: "Global user-level instructions; CLAUDE.md accepted as an alternative."
2187
+ },
2188
+ {
2189
+ path: "~/.prime/agent/SYSTEM.md",
2190
+ scope: "user",
2191
+ level: "official",
2192
+ note: "Global system prompt replacement; APPEND_SYSTEM.md appends instead."
2193
+ }
2194
+ ];
2195
+ skills = [
2196
+ {
2197
+ path: ".prime/agent/skills/",
2198
+ scope: "project",
2199
+ level: "official"
2200
+ },
2201
+ {
2202
+ path: ".agents/skills/",
2203
+ scope: "project",
2204
+ level: "official",
2205
+ note: "Also scanned in ancestor directories up to the Git repository root; root .md files are ignored there."
2206
+ },
2207
+ {
2208
+ path: "~/.prime/agent/skills/",
2209
+ scope: "user",
2210
+ level: "official",
2211
+ note: "Global user-level skills."
2212
+ },
2213
+ {
2214
+ path: "~/.agents/skills/",
2215
+ scope: "user",
2216
+ level: "official",
2217
+ note: "Shared skills directory; root .md files are ignored there."
2218
+ }
2219
+ ];
2220
+ commands = [{
2221
+ path: ".prime/agent/prompts/",
2222
+ scope: "project",
2223
+ level: "official",
2224
+ note: "Prompt templates invoked as slash commands."
2225
+ }, {
2226
+ path: "~/.prime/agent/prompts/",
2227
+ scope: "user",
2228
+ level: "official",
2229
+ note: "Global user-level prompt templates."
2230
+ }];
2231
+ hooks = [];
2232
+ invocation = {
2233
+ args: ["-p", "{prompt}"],
2234
+ jsonArgs: [
2235
+ "-p",
2236
+ "--mode",
2237
+ "json",
2238
+ "{prompt}"
2239
+ ],
2240
+ noToolsArgs: [
2241
+ "-p",
2242
+ "--no-tools",
2243
+ "{prompt}"
2244
+ ],
2245
+ noToolsJsonArgs: [
2246
+ "-p",
2247
+ "--no-tools",
2248
+ "--mode",
2249
+ "json",
2250
+ "{prompt}"
2251
+ ],
2252
+ modelArgs: ["--model", "{model}"],
2253
+ level: "official",
2254
+ 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."
2255
+ };
2256
+ modelListing = {
2257
+ args: ["model", "list"],
2258
+ searchArgs: [
2259
+ "model",
2260
+ "list",
2261
+ "{search}"
2262
+ ],
2263
+ level: "official",
2264
+ note: "Lists models with configured provider authentication; accepts an optional fuzzy search. --list-models was removed."
2265
+ };
2266
+ mcpConfigs = [{
2267
+ path: "~/.prime/agent/settings.json",
2268
+ scope: "user",
2269
+ level: "official",
2270
+ format: "json",
2271
+ key: ["mcpServers"],
2272
+ dialect: "prime",
2273
+ 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."
2274
+ }];
2275
+ agentsFile = "~/.prime/agent/AGENTS.md";
2276
+ detection = {
2277
+ envVars: ["PRIME_AGENT_KERNEL_OWNER_PID"],
2278
+ projectMarkers: [".prime/agent"]
2279
+ };
2280
+ parseModelListingOutput(stdout) {
2281
+ return parsePiModelTable(stdout, "Prime Agent");
2068
2282
  }
2069
2283
  };
2070
2284
  const harnesses = [
@@ -2079,6 +2293,7 @@ const harnesses = [
2079
2293
  MastraCode,
2080
2294
  Omp,
2081
2295
  OpenCode,
2296
+ PrimeAgent,
2082
2297
  Pi
2083
2298
  ];
2084
2299
  let registry;
@@ -2222,9 +2437,30 @@ function normalizeStandard(name, raw) {
2222
2437
  enabled: booleanField(raw, "enabled")
2223
2438
  });
2224
2439
  }
2440
+ const ENV_REFERENCE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
2441
+ var McpEnvError = class extends Error {
2442
+ name = "McpEnvError";
2443
+ };
2444
+ function envReference(value) {
2445
+ return ENV_REFERENCE.exec(value)?.[1];
2446
+ }
2447
+ function primeEnv(value) {
2448
+ if (!isObjectRecord(value) || Array.isArray(value)) return void 0;
2449
+ const out = {};
2450
+ for (const [key, raw] of Object.entries(value)) if (typeof raw === "string") out[key] = raw;
2451
+ else if (isObjectRecord(raw) && typeof raw.env === "string") out[key] = `\${${raw.env}}`;
2452
+ return Object.keys(out).length > 0 ? out : void 0;
2453
+ }
2454
+ function normalizePrime(name, raw) {
2455
+ return compact({
2456
+ ...normalizeStandard(name, raw),
2457
+ env: primeEnv(raw.env)
2458
+ });
2459
+ }
2225
2460
  function normalizeEntry(name, raw, dialect) {
2226
2461
  if (dialect === "antigravity") return normalizeAntigravity(name, raw);
2227
2462
  if (dialect === "opencode") return normalizeOpenCode(name, raw);
2463
+ if (dialect === "prime") return normalizePrime(name, raw);
2228
2464
  return normalizeStandard(name, raw);
2229
2465
  }
2230
2466
  function denormalizeAntigravity(server) {
@@ -2260,9 +2496,44 @@ function denormalizeStandard(server) {
2260
2496
  enabled: server.enabled
2261
2497
  });
2262
2498
  }
2499
+ function denormalizePrime(server) {
2500
+ const env = server.env ? Object.fromEntries(Object.entries(server.env).map(([key, value]) => [key, { env: requirePrimeReference(server, key, value) }])) : void 0;
2501
+ return compact({
2502
+ ...denormalizeStandard(server),
2503
+ env
2504
+ });
2505
+ }
2506
+ function requirePrimeReference(server, key, value) {
2507
+ const reference = envReference(value);
2508
+ 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`);
2509
+ return reference;
2510
+ }
2511
+ function resolveEnvReferences(server, dialect) {
2512
+ if (!server.env) return server;
2513
+ if (dialect === "prime") {
2514
+ for (const [key, value] of Object.entries(server.env)) requirePrimeReference(server, key, value);
2515
+ return server;
2516
+ }
2517
+ const env = {};
2518
+ for (const [key, value] of Object.entries(server.env)) {
2519
+ const reference = envReference(value);
2520
+ if (reference === void 0) {
2521
+ env[key] = value;
2522
+ continue;
2523
+ }
2524
+ const resolved = Object.hasOwn(process.env, reference) ? process.env[reference] : void 0;
2525
+ if (typeof resolved !== "string") throw new McpEnvError(`${server.name}: env ${key} references ${reference}, which is not set in this environment`);
2526
+ env[key] = resolved;
2527
+ }
2528
+ return {
2529
+ ...server,
2530
+ env
2531
+ };
2532
+ }
2263
2533
  function denormalizeEntry(server, dialect) {
2264
2534
  if (dialect === "antigravity") return denormalizeAntigravity(server);
2265
2535
  if (dialect === "opencode") return denormalizeOpenCode(server);
2536
+ if (dialect === "prime") return denormalizePrime(server);
2266
2537
  return denormalizeStandard(server);
2267
2538
  }
2268
2539
  function listingBase(entry, path) {
@@ -2318,9 +2589,6 @@ function writeAtomically(path, content) {
2318
2589
  writeFileSync(temp, content);
2319
2590
  renameSync(temp, path);
2320
2591
  }
2321
- function escapeRegExp(value) {
2322
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
2323
- }
2324
2592
  function tomlValue(value) {
2325
2593
  return JSON.stringify(value);
2326
2594
  }
@@ -2342,8 +2610,8 @@ function tomlSections(key, server) {
2342
2610
  return `${lines.join("\n")}\n`;
2343
2611
  }
2344
2612
  function stripTomlServer(raw, key, name) {
2345
- const prefix = escapeRegExp(key.map(tomlKeyPart).join("."));
2346
- const namePart = `(?:${escapeRegExp(tomlKeyPart(name))}|${escapeRegExp(JSON.stringify(name))})`;
2613
+ const prefix = RegExp.escape(key.map(tomlKeyPart).join("."));
2614
+ const namePart = `(?:${RegExp.escape(tomlKeyPart(name))}|${RegExp.escape(JSON.stringify(name))})`;
2347
2615
  const sectionRe = new RegExp(`^\\s*\\[${prefix}\\.${namePart}(\\.[^\\]]+)?\\]`);
2348
2616
  const bareTableRe = new RegExp(`^\\s*\\[${prefix}\\]\\s*(#.*)?$`);
2349
2617
  const anyHeaderRe = /^\s*\[/;
@@ -2399,15 +2667,16 @@ function removeTomlServer(path, key, name) {
2399
2667
  }
2400
2668
  function addMcpServer(harness, server, scope = "user", options = {}) {
2401
2669
  const { entry, path } = writableConfig(harness, scope, options);
2670
+ const resolved = resolveEnvReferences(server, entry.dialect);
2402
2671
  if (entry.format === "toml") return {
2403
2672
  path,
2404
- replaced: addTomlServer(path, entry.key, server)
2673
+ replaced: addTomlServer(path, entry.key, resolved)
2405
2674
  };
2406
2675
  const root = JSON.parse(readIfExists$1(path) ?? "{}");
2407
2676
  const map = drill(root, entry.key, "create");
2408
2677
  if (!map) throw new Error(`Config at ${path} has a non-object at ${entry.key.join(".")}`);
2409
2678
  const replaced = Object.hasOwn(map, server.name);
2410
- map[server.name] = denormalizeEntry(server, entry.dialect);
2679
+ map[server.name] = denormalizeEntry(resolved, entry.dialect);
2411
2680
  writeAtomically(path, `${JSON.stringify(root, null, 2)}\n`);
2412
2681
  return {
2413
2682
  path,
@@ -2616,20 +2885,29 @@ function withdrawMasterServersFromExcludedHarness(harness, existing, masterNames
2616
2885
  function syncIncludedHarness(harness, master, existing, masterNames, initialPath, options) {
2617
2886
  const results = [];
2618
2887
  let path = initialPath;
2619
- for (const server of master.servers) {
2620
- if (existing.get(server.name) === canonical(server)) {
2888
+ const { dialect } = writableConfig(harness, "user", options).entry;
2889
+ for (const server of master.servers) try {
2890
+ const resolved = resolveEnvReferences(server, dialect);
2891
+ if (existing.get(server.name) === canonical(resolved)) {
2621
2892
  results.push({
2622
2893
  name: server.name,
2623
2894
  action: "unchanged"
2624
2895
  });
2625
2896
  continue;
2626
2897
  }
2627
- const written = addMcpServer(harness, server, "user", options);
2898
+ const written = addMcpServer(harness, resolved, "user", options);
2628
2899
  path = written.path;
2629
2900
  results.push({
2630
2901
  name: server.name,
2631
2902
  action: written.replaced ? "replaced" : "added"
2632
2903
  });
2904
+ } catch (error) {
2905
+ if (!(error instanceof McpEnvError)) throw error;
2906
+ results.push({
2907
+ name: server.name,
2908
+ action: "skipped",
2909
+ reason: error.message
2910
+ });
2633
2911
  }
2634
2912
  for (const name of existing.keys()) {
2635
2913
  if (masterNames.has(name)) continue;
@@ -2858,6 +3136,6 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
2858
3136
  targets: harnesses.map((harness) => syncTarget(harness, config, check, options))
2859
3137
  };
2860
3138
  }
2861
- export { Harness, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
3139
+ export { Harness, McpEnvError, addMcpServer, detectHarness, detectHarnessFromEnv, detectProjectHarnesses, getAllHarnesses, getHarness, isHarnessId, listHarnesses, listMcpServers, masterMcpPath, parseJsonc, readAgentsConfig, readMasterMcpServers, registerHarness, removeMcpServer, resolvePathTemplate, syncAgentsFiles, syncMcpServers };
2862
3140
 
2863
3141
  //# sourceMappingURL=agents-sync.mjs.map