@agntn/harnesses 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -9
- package/dist/_chunks/agents.mjs +5 -3
- package/dist/_chunks/agents.mjs.map +1 -1
- package/dist/_chunks/mcp-servers.mjs +5 -3
- package/dist/_chunks/mcp-servers.mjs.map +1 -1
- package/dist/_chunks/mcp.mjs +10 -1
- package/dist/_chunks/mcp.mjs.map +1 -1
- package/dist/_chunks/output.mjs +7 -1
- package/dist/_chunks/output.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +4 -2
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/{prompt-sync.d.mts → skills-sync.d.mts} +174 -57
- package/dist/_chunks/skills-sync.d.mts.map +1 -0
- package/dist/_chunks/{prompt-sync.mjs → skills-sync.mjs} +665 -148
- package/dist/_chunks/skills-sync.mjs.map +1 -0
- package/dist/_chunks/skills.mjs +30 -0
- package/dist/_chunks/skills.mjs.map +1 -0
- package/dist/_chunks/tool-operations.mjs +38 -13
- package/dist/_chunks/tool-operations.mjs.map +1 -1
- package/dist/_chunks/tool-schemas.d.mts +22 -13
- package/dist/_chunks/tool-schemas.d.mts.map +1 -1
- package/dist/_chunks/tool-schemas.mjs +18 -11
- package/dist/_chunks/tool-schemas.mjs.map +1 -1
- package/dist/_chunks/types.mjs +5 -2
- package/dist/_chunks/types.mjs.map +1 -1
- package/dist/cli.mjs +29 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +16 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.d.mts +2 -0
- package/dist/mcp.d.mts.map +1 -1
- package/dist/mcp.mjs +6261 -5
- package/dist/mcp.mjs.map +1 -1
- package/dist/tool-operations.d.mts +21 -4
- package/dist/tool-operations.d.mts.map +1 -1
- package/dist/tool-operations.mjs +3 -3
- package/dist/tool-schemas.d.mts +2 -2
- package/dist/tool-schemas.mjs +1 -1
- package/dist/typebox.LICENSE +23 -0
- package/package.json +18 -18
- package/packages/omp/extensions/harnesses.ts +19 -1
- package/packages/pi/extensions/harnesses.ts +27 -46
- package/packages/shared/tui.ts +13 -0
- package/dist/THIRD-PARTY-LICENSES.md +0 -36
- package/dist/_chunks/libs/typebox.mjs +0 -5439
- package/dist/_chunks/libs/typebox.mjs.map +0 -1
- package/dist/_chunks/prompt-sync.d.mts.map +0 -1
- package/dist/_chunks/prompt-sync.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { addAbortListener } from "node:events";
|
|
3
|
-
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, 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
6
|
import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, win32 } from "node:path";
|
|
@@ -8,13 +8,14 @@ import os, { homedir } from "node:os";
|
|
|
8
8
|
import { stripVTControlCharacters } from "node:util";
|
|
9
9
|
import { parse } from "smol-toml";
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
|
-
|
|
12
|
-
const PATH_MARKERS = /^~(?=\/|$)|\$\{HOME\}|\$\{PROJECT_ROOT\}|%([^%]+)%/g;
|
|
11
|
+
//#region src/resolve.ts
|
|
12
|
+
const PATH_MARKERS = /^~(?=\/|$)|\$\{HOME\}|\$\{PROJECT_ROOT\}|\$\{TMPDIR\}|%([^%]+)%/g;
|
|
13
13
|
function resolvePathTemplate(template, options = {}) {
|
|
14
14
|
const homeDir = options.homeDir ?? os.homedir();
|
|
15
15
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
16
16
|
return template.replaceAll(PATH_MARKERS, (match, name) => {
|
|
17
17
|
if (match === "${PROJECT_ROOT}") return projectRoot;
|
|
18
|
+
if (match === "${TMPDIR}") return options.tempDir ?? os.tmpdir();
|
|
18
19
|
if (name === void 0) return homeDir;
|
|
19
20
|
return process.env[name] ?? match;
|
|
20
21
|
});
|
|
@@ -27,11 +28,19 @@ function agntnDataDir(options = {}) {
|
|
|
27
28
|
const base = process.env.XDG_DATA_HOME && process.env.XDG_DATA_HOME !== "" ? process.env.XDG_DATA_HOME : join(options.homeDir ?? os.homedir(), ".local", "share");
|
|
28
29
|
return join(base, "agntn");
|
|
29
30
|
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/harness.ts
|
|
33
|
+
function moveRoot(path, root, value) {
|
|
34
|
+
if (path === root) return value;
|
|
35
|
+
const rest = path.slice(root.length);
|
|
36
|
+
return path.startsWith(root) && (rest.startsWith("/") || rest.startsWith("\\")) ? value + rest : path;
|
|
37
|
+
}
|
|
30
38
|
const SUPPORTED_PLATFORMS = {
|
|
31
39
|
linux: true,
|
|
32
40
|
darwin: true,
|
|
33
41
|
win32: true
|
|
34
42
|
};
|
|
43
|
+
const PARENT_ENV = "AGNTN_HARNESSES_PARENT";
|
|
35
44
|
const ALTERNATE_INVOCATION_MODE = {
|
|
36
45
|
advisor: "agent",
|
|
37
46
|
advisorStructured: "agentStructured",
|
|
@@ -57,6 +66,17 @@ function requestedInvocationMode(options) {
|
|
|
57
66
|
if (options.tools === true) return options.structured === true ? "agentStructured" : "agent";
|
|
58
67
|
return options.structured === true ? "advisorStructured" : "advisor";
|
|
59
68
|
}
|
|
69
|
+
function accessConflictError(id, invocation, options) {
|
|
70
|
+
if (options.readOnly !== true || options.tools !== false) return null;
|
|
71
|
+
const retries = [];
|
|
72
|
+
if (invocationTemplate(invocation, requestedInvocationMode(options))) retries.push("tools: true to keep its read-only tools");
|
|
73
|
+
if (invocationTemplate(invocation, requestedInvocationMode({
|
|
74
|
+
...options,
|
|
75
|
+
readOnly: false
|
|
76
|
+
}))) retries.push("readOnly: false to use its advisor without tools");
|
|
77
|
+
const error = `Harness ${id} cannot run readOnly with tools: false, since read-only access still uses tools`;
|
|
78
|
+
return retries.length === 0 ? error : `${error}; retry with ${retries.join(", or with ")}`;
|
|
79
|
+
}
|
|
60
80
|
function invocationTemplate(invocation, mode) {
|
|
61
81
|
switch (mode) {
|
|
62
82
|
case "advisor": return invocation.noToolsArgs;
|
|
@@ -123,11 +143,29 @@ async function terminateCommand(pid) {
|
|
|
123
143
|
await setTimeout$1(500);
|
|
124
144
|
signalProcessGroup(pid, "SIGKILL");
|
|
125
145
|
}
|
|
146
|
+
function spawnError(command, cwd, error) {
|
|
147
|
+
if (!(error instanceof Error && "code" in error)) return error;
|
|
148
|
+
if (error.code !== "ENOENT" && error.code !== "ENOTDIR") return error;
|
|
149
|
+
let message;
|
|
150
|
+
if (cwd !== void 0 && !isDirectory(cwd)) message = existsSync(cwd) ? `Working directory is not a directory: ${cwd}` : `Working directory not found: ${cwd}`;
|
|
151
|
+
else if (error.code === "ENOENT") message = `Command not found: ${command}. Install it or add it to PATH`;
|
|
152
|
+
else return error;
|
|
153
|
+
return Object.assign(new Error(message, { cause: error }), { code: error.code });
|
|
154
|
+
}
|
|
155
|
+
function isDirectory(path) {
|
|
156
|
+
try {
|
|
157
|
+
return statSync(path).isDirectory();
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
126
162
|
function executeCommand(command, args, options) {
|
|
127
163
|
return new Promise((resolve, reject) => {
|
|
128
164
|
let stdout = "";
|
|
129
165
|
let stderr = "";
|
|
130
166
|
let stopped;
|
|
167
|
+
let lastOutputAt = performance.now();
|
|
168
|
+
let idleMs;
|
|
131
169
|
let settled = false;
|
|
132
170
|
let timer;
|
|
133
171
|
let abortListener;
|
|
@@ -136,28 +174,38 @@ function executeCommand(command, args, options) {
|
|
|
136
174
|
finish(null);
|
|
137
175
|
return;
|
|
138
176
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
let child;
|
|
178
|
+
try {
|
|
179
|
+
child = spawn(command, args, {
|
|
180
|
+
cwd: options.cwd,
|
|
181
|
+
env: options.env ? {
|
|
182
|
+
...process.env,
|
|
183
|
+
...options.env
|
|
184
|
+
} : process.env,
|
|
185
|
+
stdio: [
|
|
186
|
+
"ignore",
|
|
187
|
+
"pipe",
|
|
188
|
+
"pipe"
|
|
189
|
+
],
|
|
190
|
+
detached: process.platform !== "win32" && (Boolean(options.timeoutMs) || options.signal !== void 0)
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
fail(spawnError(command, options.cwd, error));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
152
196
|
if (options.timeoutMs) timer = setTimeout(() => stop("timeout"), options.timeoutMs);
|
|
153
197
|
if (options.signal) abortListener = addAbortListener(options.signal, () => stop("abort"));
|
|
198
|
+
child.stdout.setEncoding("utf8");
|
|
199
|
+
child.stderr.setEncoding("utf8");
|
|
154
200
|
child.stdout.on("data", (chunk) => {
|
|
155
|
-
stdout += chunk
|
|
201
|
+
stdout += chunk;
|
|
202
|
+
lastOutputAt = performance.now();
|
|
156
203
|
});
|
|
157
204
|
child.stderr.on("data", (chunk) => {
|
|
158
|
-
stderr += chunk
|
|
205
|
+
stderr += chunk;
|
|
206
|
+
lastOutputAt = performance.now();
|
|
159
207
|
});
|
|
160
|
-
child.on("error", fail);
|
|
208
|
+
child.on("error", (error) => fail(spawnError(command, options.cwd, error)));
|
|
161
209
|
child.on("close", (code) => {
|
|
162
210
|
if (stopped === void 0) finish(code);
|
|
163
211
|
});
|
|
@@ -174,6 +222,7 @@ function executeCommand(command, args, options) {
|
|
|
174
222
|
function stop(reason) {
|
|
175
223
|
if (settled || stopped !== void 0) return;
|
|
176
224
|
stopped = reason;
|
|
225
|
+
idleMs = Math.round(performance.now() - lastOutputAt);
|
|
177
226
|
cleanup();
|
|
178
227
|
if (child.pid === void 0) return;
|
|
179
228
|
terminateCommand(child.pid).then(() => {
|
|
@@ -197,14 +246,18 @@ function executeCommand(command, args, options) {
|
|
|
197
246
|
stderr,
|
|
198
247
|
exitCode: stopped === void 0 ? code : null,
|
|
199
248
|
timedOut: stopped === "timeout",
|
|
200
|
-
aborted: stopped === "abort"
|
|
249
|
+
aborted: stopped === "abort",
|
|
250
|
+
...idleMs === void 0 ? {} : { idleMs }
|
|
201
251
|
});
|
|
202
252
|
}
|
|
203
253
|
});
|
|
204
254
|
}
|
|
205
255
|
var Harness = class {
|
|
256
|
+
skillsSyncTarget = null;
|
|
206
257
|
promptTemplates = [];
|
|
207
258
|
promptTemplateSyncTarget = null;
|
|
259
|
+
temp = [];
|
|
260
|
+
envOverrides = [];
|
|
208
261
|
modelListing = null;
|
|
209
262
|
mcpConfigs = [];
|
|
210
263
|
agentsFile = null;
|
|
@@ -258,6 +311,7 @@ var Harness = class {
|
|
|
258
311
|
const command = this.invocation.binary ?? this.binaries[0];
|
|
259
312
|
if (!command) return null;
|
|
260
313
|
if (options.model !== void 0 && !this.invocation.modelArgs) return null;
|
|
314
|
+
if (accessConflictError(this.id, this.invocation, options) !== null) return null;
|
|
261
315
|
const template = invocationTemplate(this.invocation, requestedInvocationMode(options));
|
|
262
316
|
if (!template) return null;
|
|
263
317
|
return {
|
|
@@ -268,11 +322,15 @@ var Harness = class {
|
|
|
268
322
|
invocationError(options = {}) {
|
|
269
323
|
if (!this.invocation) return `Harness ${this.id} has no non-interactive invocation`;
|
|
270
324
|
if (options.model !== void 0 && !this.invocation.modelArgs) return `Harness ${this.id} does not support model selection`;
|
|
325
|
+
const conflict = accessConflictError(this.id, this.invocation, options);
|
|
326
|
+
if (conflict !== null) return conflict;
|
|
271
327
|
const mode = requestedInvocationMode(options);
|
|
272
328
|
if (invocationTemplate(this.invocation, mode)) return null;
|
|
273
329
|
return `${`Harness ${this.id} has no ${INVOCATION_MODE_DESCRIPTION[mode]} invocation`}${invocationRetryHint(this.invocation, mode, options)}`;
|
|
274
330
|
}
|
|
275
331
|
invoke(prompt, options = {}) {
|
|
332
|
+
const parent = process.env[PARENT_ENV];
|
|
333
|
+
if (parent) return Promise.reject(/* @__PURE__ */ new Error(`Nested run refused: this process already runs inside ${parent}, started by harnesses. ${this.id} under it would stack agents, and in a read-only sandbox it gets no network and hangs until the deadline. Answer without it`));
|
|
276
334
|
const invocationOptions = {
|
|
277
335
|
model: options.model,
|
|
278
336
|
structured: options.structured,
|
|
@@ -283,7 +341,22 @@ var Harness = class {
|
|
|
283
341
|
if (!built) return Promise.reject(new Error(this.invocationError(invocationOptions) ?? "Invalid invocation"));
|
|
284
342
|
const versionError = this.readOnlyVersionError(invocationOptions);
|
|
285
343
|
if (versionError) return Promise.reject(new Error(versionError));
|
|
286
|
-
|
|
344
|
+
const command = {
|
|
345
|
+
...options,
|
|
346
|
+
env: {
|
|
347
|
+
...options.env,
|
|
348
|
+
[PARENT_ENV]: this.id
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const streamArgs = options.structured ? void 0 : this.invocation?.streamArgs;
|
|
352
|
+
if (!streamArgs) return executeCommand(built.command, built.args, command);
|
|
353
|
+
return executeCommand(built.command, [...built.args, ...streamArgs], command).then((result) => ({
|
|
354
|
+
...result,
|
|
355
|
+
stdout: this.foldStreamOutput(result.stdout, !result.timedOut && !result.aborted)
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
foldStreamOutput(_stdout, _complete) {
|
|
359
|
+
throw new Error(`Harness ${this.id} does not implement stream output folding`);
|
|
287
360
|
}
|
|
288
361
|
readOnlyVersionError(options) {
|
|
289
362
|
const floor = this.invocation?.readOnlyMinVersion;
|
|
@@ -298,22 +371,33 @@ var Harness = class {
|
|
|
298
371
|
if (!this.modelListing) return null;
|
|
299
372
|
const command = this.binaries[0];
|
|
300
373
|
if (!command) return null;
|
|
301
|
-
const template = search === void 0 ? this.modelListing.args : this.modelListing.searchArgs;
|
|
302
|
-
if (!template) return null;
|
|
303
374
|
return {
|
|
304
375
|
command,
|
|
305
|
-
args:
|
|
376
|
+
args: (search === void 0 ? this.modelListing.args : this.modelListing.searchArgs ?? this.modelListing.args).map((arg) => arg.replaceAll("{search}", () => search ?? ""))
|
|
306
377
|
};
|
|
307
378
|
}
|
|
308
379
|
async listModels(options = {}) {
|
|
309
380
|
const built = this.buildModelListInvocation(options.search);
|
|
310
|
-
if (!built) throw new Error(
|
|
381
|
+
if (!built) throw new Error(`Harness ${this.id} does not support model listing`);
|
|
311
382
|
const result = await executeCommand(built.command, built.args, options);
|
|
383
|
+
if (result.timedOut || result.aborted || result.exitCode !== 0) return {
|
|
384
|
+
...result,
|
|
385
|
+
models: []
|
|
386
|
+
};
|
|
387
|
+
const models = this.parseModelListingOutput(result.stdout);
|
|
388
|
+
if (options.search === void 0 || this.modelListing?.searchArgs) return {
|
|
389
|
+
...result,
|
|
390
|
+
models
|
|
391
|
+
};
|
|
392
|
+
const search = options.search.toLowerCase();
|
|
312
393
|
return {
|
|
313
394
|
...result,
|
|
314
|
-
models:
|
|
395
|
+
models: models.filter((model) => model.id.toLowerCase().includes(search))
|
|
315
396
|
};
|
|
316
397
|
}
|
|
398
|
+
modelSelector(model) {
|
|
399
|
+
return `${model.provider}/${model.id}`;
|
|
400
|
+
}
|
|
317
401
|
parseModelListingOutput(_stdout) {
|
|
318
402
|
throw new Error(`Harness ${this.id} does not implement model-list output parsing`);
|
|
319
403
|
}
|
|
@@ -332,13 +416,39 @@ var Harness = class {
|
|
|
332
416
|
sessions: this.resolveCandidates(this.sessions, options),
|
|
333
417
|
instructions: this.resolveCandidates(this.instructions, options),
|
|
334
418
|
skills: this.resolveCandidates(this.skills, options),
|
|
419
|
+
skillsSyncTarget: this.skillsSyncTarget === null ? null : this.resolveCandidates([this.skillsSyncTarget], options)[0] ?? null,
|
|
335
420
|
commands: this.resolveCandidates(this.commands, options),
|
|
336
421
|
promptTemplates: this.resolveCandidates(this.promptTemplates, options),
|
|
337
422
|
promptTemplateSyncTarget: this.promptTemplateSyncTarget === null ? null : this.resolveCandidates([this.promptTemplateSyncTarget], options)[0] ?? null,
|
|
338
|
-
hooks: this.resolveCandidates(this.hooks, options)
|
|
423
|
+
hooks: this.resolveCandidates(this.hooks, options),
|
|
424
|
+
temp: this.resolveTemp(options)
|
|
339
425
|
};
|
|
340
426
|
}
|
|
427
|
+
resolveTemp(options) {
|
|
428
|
+
const temp = this.resolveCandidates(this.temp, options);
|
|
429
|
+
const override = this.resolveCandidates(this.envOverrides, options).find((entry) => entry.relocates.includes("temp"));
|
|
430
|
+
const value = override ? process.env[override.variable] : void 0;
|
|
431
|
+
if (!override || !value) return temp;
|
|
432
|
+
return temp.map((entry) => ({
|
|
433
|
+
...entry,
|
|
434
|
+
path: moveRoot(entry.path, override.path, value)
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
341
437
|
};
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/harnesses/antigravity.ts
|
|
440
|
+
function parseAntigravityModels(stdout) {
|
|
441
|
+
const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
442
|
+
if (lines.length === 0) throw new Error("Unexpected empty Antigravity model-list output");
|
|
443
|
+
return lines.map((line) => {
|
|
444
|
+
const match = /^(\S+)\t.+$/.exec(line);
|
|
445
|
+
if (!match?.[1]) throw new Error(`Unexpected Antigravity model-list row: ${JSON.stringify(line)}`);
|
|
446
|
+
return {
|
|
447
|
+
provider: "google",
|
|
448
|
+
id: match[1]
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
}
|
|
342
452
|
var Antigravity = class extends Harness {
|
|
343
453
|
id = "antigravity";
|
|
344
454
|
name = "Google Antigravity CLI";
|
|
@@ -460,6 +570,11 @@ var Antigravity = class extends Harness {
|
|
|
460
570
|
scope: "user",
|
|
461
571
|
level: "official"
|
|
462
572
|
}];
|
|
573
|
+
skillsSyncTarget = {
|
|
574
|
+
path: "~/.gemini/config/skills/",
|
|
575
|
+
scope: "user",
|
|
576
|
+
level: "official"
|
|
577
|
+
};
|
|
463
578
|
commands = [];
|
|
464
579
|
hooks = [{
|
|
465
580
|
path: ".agents/hooks.json",
|
|
@@ -482,6 +597,11 @@ var Antigravity = class extends Harness {
|
|
|
482
597
|
level: "official",
|
|
483
598
|
note: "Non-interactive print mode."
|
|
484
599
|
};
|
|
600
|
+
modelListing = {
|
|
601
|
+
args: ["models"],
|
|
602
|
+
level: "official",
|
|
603
|
+
note: "Prints one id and display name per row, with no default marked and no filter; verified with 1.2.5."
|
|
604
|
+
};
|
|
485
605
|
mcpConfigs = [{
|
|
486
606
|
path: "~/.gemini/config/mcp_config.json",
|
|
487
607
|
scope: "user",
|
|
@@ -495,7 +615,62 @@ var Antigravity = class extends Harness {
|
|
|
495
615
|
envVars: [],
|
|
496
616
|
projectMarkers: [".antigravitycli"]
|
|
497
617
|
};
|
|
618
|
+
parseModelListingOutput(stdout) {
|
|
619
|
+
return parseAntigravityModels(stdout);
|
|
620
|
+
}
|
|
621
|
+
modelSelector(model) {
|
|
622
|
+
return model.id;
|
|
623
|
+
}
|
|
498
624
|
};
|
|
625
|
+
//#endregion
|
|
626
|
+
//#region src/harnesses/claude.ts
|
|
627
|
+
function parseStreamLine(line) {
|
|
628
|
+
try {
|
|
629
|
+
const parsed = JSON.parse(line);
|
|
630
|
+
return typeof parsed === "object" && parsed !== null && "type" in parsed && typeof parsed.type === "string" ? parsed : void 0;
|
|
631
|
+
} catch {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function startsTextBlock(event) {
|
|
636
|
+
return event.type === "stream_event" && event.event?.type === "content_block_start" && event.event.content_block?.type === "text";
|
|
637
|
+
}
|
|
638
|
+
function textDelta(event) {
|
|
639
|
+
if (event.type !== "stream_event" || event.event?.type !== "content_block_delta") return void 0;
|
|
640
|
+
const delta = event.event.delta;
|
|
641
|
+
return delta?.type === "text_delta" && typeof delta.text === "string" ? delta.text : void 0;
|
|
642
|
+
}
|
|
643
|
+
var ClaudeStreamFold = class {
|
|
644
|
+
blocks = [];
|
|
645
|
+
other = [];
|
|
646
|
+
result;
|
|
647
|
+
add(line) {
|
|
648
|
+
if (line.trim().length === 0) return;
|
|
649
|
+
const event = parseStreamLine(line);
|
|
650
|
+
if (!event) {
|
|
651
|
+
this.other.push(line);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (event.type === "result" && typeof event.result === "string") this.result = event.result;
|
|
655
|
+
if (startsTextBlock(event)) this.blocks.push("");
|
|
656
|
+
const text = textDelta(event);
|
|
657
|
+
if (text !== void 0) this.blocks.push((this.blocks.pop() ?? "") + text);
|
|
658
|
+
}
|
|
659
|
+
text(complete) {
|
|
660
|
+
const prefix = this.other.map((line) => `${line}\n`).join("");
|
|
661
|
+
if (complete && this.result !== void 0) return `${prefix}${this.result}\n`;
|
|
662
|
+
const text = this.blocks.filter((block) => block.length > 0).join("\n\n");
|
|
663
|
+
return text.length > 0 ? `${prefix}${text}\n` : prefix;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
function foldClaudeStream(stdout, complete) {
|
|
667
|
+
const lines = stdout.split("\n");
|
|
668
|
+
const tail = lines.at(-1);
|
|
669
|
+
if (!complete && tail?.startsWith("{") && parseStreamLine(tail) === void 0) lines.pop();
|
|
670
|
+
const fold = new ClaudeStreamFold();
|
|
671
|
+
for (const line of lines) fold.add(line);
|
|
672
|
+
return fold.text(complete);
|
|
673
|
+
}
|
|
499
674
|
var Claude = class extends Harness {
|
|
500
675
|
id = "claude";
|
|
501
676
|
name = "Anthropic Claude Code";
|
|
@@ -601,6 +776,11 @@ var Claude = class extends Harness {
|
|
|
601
776
|
level: "official",
|
|
602
777
|
note: "Global user-level skills."
|
|
603
778
|
}];
|
|
779
|
+
skillsSyncTarget = {
|
|
780
|
+
path: "~/.claude/skills/",
|
|
781
|
+
scope: "user",
|
|
782
|
+
level: "official"
|
|
783
|
+
};
|
|
604
784
|
commands = [{
|
|
605
785
|
path: ".claude/commands/",
|
|
606
786
|
scope: "project",
|
|
@@ -671,8 +851,14 @@ var Claude = class extends Harness {
|
|
|
671
851
|
],
|
|
672
852
|
readOnlyMinVersion: "2.1.175",
|
|
673
853
|
modelArgs: ["--model", "{model}"],
|
|
854
|
+
streamArgs: [
|
|
855
|
+
"--output-format",
|
|
856
|
+
"stream-json",
|
|
857
|
+
"--verbose",
|
|
858
|
+
"--include-partial-messages"
|
|
859
|
+
],
|
|
674
860
|
level: "official",
|
|
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."
|
|
861
|
+
note: "Headless print mode; add --output-format json for structured output. Plain text runs stream events instead and fold them back, since text mode prints nothing until the answer is complete and a timeout would lose all of it; --include-partial-messages streams a single long answer too, checked on 2.1.280. --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."
|
|
676
862
|
};
|
|
677
863
|
mcpConfigs = [{
|
|
678
864
|
path: "~/.claude.json",
|
|
@@ -689,6 +875,63 @@ var Claude = class extends Harness {
|
|
|
689
875
|
key: ["mcpServers"],
|
|
690
876
|
dialect: "standard"
|
|
691
877
|
}];
|
|
878
|
+
temp = [
|
|
879
|
+
{
|
|
880
|
+
path: "${TMPDIR}/claude-<uid>/",
|
|
881
|
+
scope: "data",
|
|
882
|
+
level: "official",
|
|
883
|
+
platforms: ["linux"],
|
|
884
|
+
note: "Session scratchpads sit under <dash-encoded-cwd>/<session-id>/scratchpad."
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
path: "/tmp/claude-<uid>/",
|
|
888
|
+
scope: "data",
|
|
889
|
+
level: "official",
|
|
890
|
+
platforms: ["darwin"]
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
path: "${TMPDIR}/claude/",
|
|
894
|
+
scope: "data",
|
|
895
|
+
level: "official",
|
|
896
|
+
platforms: ["win32"]
|
|
897
|
+
}
|
|
898
|
+
];
|
|
899
|
+
envOverrides = [
|
|
900
|
+
{
|
|
901
|
+
variable: "CLAUDE_CONFIG_DIR",
|
|
902
|
+
path: "~/.claude",
|
|
903
|
+
scope: "user",
|
|
904
|
+
level: "official",
|
|
905
|
+
relocates: [
|
|
906
|
+
"config",
|
|
907
|
+
"sessions",
|
|
908
|
+
"instructions",
|
|
909
|
+
"skills",
|
|
910
|
+
"commands",
|
|
911
|
+
"promptTemplates",
|
|
912
|
+
"hooks"
|
|
913
|
+
],
|
|
914
|
+
note: "Read from the shell or the env block of user or managed settings, not project settings."
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
variable: "CLAUDE_CODE_TMPDIR",
|
|
918
|
+
path: "${TMPDIR}",
|
|
919
|
+
scope: "data",
|
|
920
|
+
level: "official",
|
|
921
|
+
platforms: ["linux", "win32"],
|
|
922
|
+
relocates: ["temp"],
|
|
923
|
+
note: "Read from the shell or the env block of user or managed settings, not project settings."
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
variable: "CLAUDE_CODE_TMPDIR",
|
|
927
|
+
path: "/tmp",
|
|
928
|
+
scope: "data",
|
|
929
|
+
level: "official",
|
|
930
|
+
platforms: ["darwin"],
|
|
931
|
+
relocates: ["temp"],
|
|
932
|
+
note: "Read from the shell or the env block of user or managed settings, not project settings."
|
|
933
|
+
}
|
|
934
|
+
];
|
|
692
935
|
agentsFile = "~/.claude/CLAUDE.md";
|
|
693
936
|
detection = {
|
|
694
937
|
envVars: [
|
|
@@ -704,7 +947,12 @@ var Claude = class extends Harness {
|
|
|
704
947
|
"CLAUDE.md"
|
|
705
948
|
]
|
|
706
949
|
};
|
|
950
|
+
foldStreamOutput(stdout, complete) {
|
|
951
|
+
return foldClaudeStream(stdout, complete);
|
|
952
|
+
}
|
|
707
953
|
};
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/harnesses/codex.ts
|
|
708
956
|
var Codex = class extends Harness {
|
|
709
957
|
id = "codex";
|
|
710
958
|
name = "OpenAI Codex CLI";
|
|
@@ -827,11 +1075,16 @@ var Codex = class extends Harness {
|
|
|
827
1075
|
platforms: ["linux", "darwin"]
|
|
828
1076
|
}
|
|
829
1077
|
];
|
|
1078
|
+
skillsSyncTarget = {
|
|
1079
|
+
path: "~/.agents/skills/",
|
|
1080
|
+
scope: "user",
|
|
1081
|
+
level: "official"
|
|
1082
|
+
};
|
|
830
1083
|
commands = [{
|
|
831
1084
|
path: "~/.codex/prompts/",
|
|
832
1085
|
scope: "user",
|
|
833
1086
|
level: "official",
|
|
834
|
-
note: "Deprecated Markdown prompts invoked as /prompts:name. Top-level files only; use skills for shared prompts.
|
|
1087
|
+
note: "Deprecated Markdown prompts invoked as /prompts:name. Top-level files only; use skills for shared prompts."
|
|
835
1088
|
}];
|
|
836
1089
|
promptTemplates = this.commands;
|
|
837
1090
|
promptTemplateSyncTarget = {
|
|
@@ -887,6 +1140,20 @@ var Codex = class extends Harness {
|
|
|
887
1140
|
key: ["mcp_servers"],
|
|
888
1141
|
dialect: "standard"
|
|
889
1142
|
}];
|
|
1143
|
+
envOverrides = [{
|
|
1144
|
+
variable: "CODEX_HOME",
|
|
1145
|
+
path: "~/.codex",
|
|
1146
|
+
scope: "user",
|
|
1147
|
+
level: "official",
|
|
1148
|
+
relocates: [
|
|
1149
|
+
"config",
|
|
1150
|
+
"sessions",
|
|
1151
|
+
"instructions",
|
|
1152
|
+
"skills",
|
|
1153
|
+
"commands",
|
|
1154
|
+
"promptTemplates"
|
|
1155
|
+
]
|
|
1156
|
+
}];
|
|
890
1157
|
agentsFile = "~/.codex/AGENTS.md";
|
|
891
1158
|
detection = {
|
|
892
1159
|
envVars: [],
|
|
@@ -898,6 +1165,8 @@ var Codex = class extends Harness {
|
|
|
898
1165
|
]
|
|
899
1166
|
};
|
|
900
1167
|
};
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/harnesses/cursor.ts
|
|
901
1170
|
var Cursor = class extends Harness {
|
|
902
1171
|
id = "cursor";
|
|
903
1172
|
name = "Cursor";
|
|
@@ -957,6 +1226,11 @@ var Cursor = class extends Harness {
|
|
|
957
1226
|
note: "Cursor natively scans Codex skills directory."
|
|
958
1227
|
}
|
|
959
1228
|
];
|
|
1229
|
+
skillsSyncTarget = {
|
|
1230
|
+
path: "~/.cursor/skills/",
|
|
1231
|
+
scope: "user",
|
|
1232
|
+
level: "official"
|
|
1233
|
+
};
|
|
960
1234
|
commands = [];
|
|
961
1235
|
hooks = [];
|
|
962
1236
|
invocation = {
|
|
@@ -991,6 +1265,8 @@ var Cursor = class extends Harness {
|
|
|
991
1265
|
projectMarkers: [".cursor", ".cursorrules"]
|
|
992
1266
|
};
|
|
993
1267
|
};
|
|
1268
|
+
//#endregion
|
|
1269
|
+
//#region src/harnesses/freebuff.ts
|
|
994
1270
|
var Freebuff = class extends Harness {
|
|
995
1271
|
id = "freebuff";
|
|
996
1272
|
name = "Freebuff";
|
|
@@ -1097,6 +1373,8 @@ var Freebuff = class extends Harness {
|
|
|
1097
1373
|
projectMarkers: [".agents", "knowledge.md"]
|
|
1098
1374
|
};
|
|
1099
1375
|
};
|
|
1376
|
+
//#endregion
|
|
1377
|
+
//#region src/harnesses/gemini.ts
|
|
1100
1378
|
var Gemini = class extends Harness {
|
|
1101
1379
|
id = "gemini";
|
|
1102
1380
|
name = "Google Gemini CLI";
|
|
@@ -1191,6 +1469,11 @@ var Gemini = class extends Harness {
|
|
|
1191
1469
|
level: "official"
|
|
1192
1470
|
}
|
|
1193
1471
|
];
|
|
1472
|
+
skillsSyncTarget = {
|
|
1473
|
+
path: "~/.gemini/skills/",
|
|
1474
|
+
scope: "user",
|
|
1475
|
+
level: "official"
|
|
1476
|
+
};
|
|
1194
1477
|
commands = [{
|
|
1195
1478
|
path: ".gemini/commands/",
|
|
1196
1479
|
scope: "project",
|
|
@@ -1243,6 +1526,8 @@ var Gemini = class extends Harness {
|
|
|
1243
1526
|
projectMarkers: [".gemini", "GEMINI.md"]
|
|
1244
1527
|
};
|
|
1245
1528
|
};
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/harnesses/github-copilot.ts
|
|
1246
1531
|
var GitHubCopilot = class extends Harness {
|
|
1247
1532
|
id = "github-copilot";
|
|
1248
1533
|
name = "GitHub Copilot";
|
|
@@ -1311,6 +1596,22 @@ var GitHubCopilot = class extends Harness {
|
|
|
1311
1596
|
]
|
|
1312
1597
|
};
|
|
1313
1598
|
};
|
|
1599
|
+
//#endregion
|
|
1600
|
+
//#region src/harnesses/grok.ts
|
|
1601
|
+
function parseGrokModels(stdout) {
|
|
1602
|
+
const lines = stripVTControlCharacters(stdout).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1603
|
+
const start = lines.indexOf("Available models:");
|
|
1604
|
+
if (start === -1) throw new Error(`Unexpected Grok model-list output: ${JSON.stringify(stdout)}`);
|
|
1605
|
+
return lines.slice(start + 1).map((line) => {
|
|
1606
|
+
const match = /^([*-])\s+(\S+)(?:\s+\(default\))?$/.exec(line);
|
|
1607
|
+
if (!match?.[2]) throw new Error(`Unexpected Grok model-list row: ${JSON.stringify(line)}`);
|
|
1608
|
+
return {
|
|
1609
|
+
provider: "xai",
|
|
1610
|
+
id: match[2],
|
|
1611
|
+
...match[1] === "*" ? { default: true } : {}
|
|
1612
|
+
};
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1314
1615
|
var Grok = class extends Harness {
|
|
1315
1616
|
id = "grok";
|
|
1316
1617
|
name = "xAI Grok CLI";
|
|
@@ -1396,6 +1697,11 @@ var Grok = class extends Harness {
|
|
|
1396
1697
|
level: "official",
|
|
1397
1698
|
note: "Global user-level skills."
|
|
1398
1699
|
}];
|
|
1700
|
+
skillsSyncTarget = {
|
|
1701
|
+
path: "~/.grok/skills/",
|
|
1702
|
+
scope: "user",
|
|
1703
|
+
level: "official"
|
|
1704
|
+
};
|
|
1399
1705
|
commands = [{
|
|
1400
1706
|
path: ".grok/commands/",
|
|
1401
1707
|
scope: "project",
|
|
@@ -1443,6 +1749,11 @@ var Grok = class extends Harness {
|
|
|
1443
1749
|
level: "official",
|
|
1444
1750
|
note: "-p is short for --single; add --output-format json for structured output. --sandbox read-only is kernel-enforced (Landlock, Seatbelt) regardless of the inherited permission mode and still allows writes to ~/.grok and temp dirs; verified on Linux with 1.0.13 and 1.0.25."
|
|
1445
1751
|
};
|
|
1752
|
+
modelListing = {
|
|
1753
|
+
args: ["models"],
|
|
1754
|
+
level: "official",
|
|
1755
|
+
note: "Prints bare ids with the default marked; verified with 1.0.41."
|
|
1756
|
+
};
|
|
1446
1757
|
mcpConfigs = [{
|
|
1447
1758
|
path: "~/.grok/config.toml",
|
|
1448
1759
|
scope: "user",
|
|
@@ -1467,7 +1778,12 @@ var Grok = class extends Harness {
|
|
|
1467
1778
|
],
|
|
1468
1779
|
projectMarkers: [".grok"]
|
|
1469
1780
|
};
|
|
1781
|
+
parseModelListingOutput(stdout) {
|
|
1782
|
+
return parseGrokModels(stdout);
|
|
1783
|
+
}
|
|
1470
1784
|
};
|
|
1785
|
+
//#endregion
|
|
1786
|
+
//#region src/harnesses/mastracode.ts
|
|
1471
1787
|
function packageVersion(commandPath) {
|
|
1472
1788
|
try {
|
|
1473
1789
|
const require = createRequire(realpathSync(commandPath));
|
|
@@ -1623,6 +1939,11 @@ var MastraCode = class extends Harness {
|
|
|
1623
1939
|
note: "Claude Code compatible."
|
|
1624
1940
|
}
|
|
1625
1941
|
];
|
|
1942
|
+
skillsSyncTarget = {
|
|
1943
|
+
path: "~/.mastracode/skills/",
|
|
1944
|
+
scope: "user",
|
|
1945
|
+
level: "official"
|
|
1946
|
+
};
|
|
1626
1947
|
commands = [
|
|
1627
1948
|
{
|
|
1628
1949
|
path: ".mastracode/commands/",
|
|
@@ -1713,6 +2034,8 @@ var MastraCode = class extends Harness {
|
|
|
1713
2034
|
projectMarkers: [".mastracode"]
|
|
1714
2035
|
};
|
|
1715
2036
|
};
|
|
2037
|
+
//#endregion
|
|
2038
|
+
//#region src/harnesses/omp.ts
|
|
1716
2039
|
var Omp = class extends Harness {
|
|
1717
2040
|
id = "omp";
|
|
1718
2041
|
name = "OMP (oh-my-pi)";
|
|
@@ -1730,7 +2053,7 @@ var Omp = class extends Harness {
|
|
|
1730
2053
|
path: "~/.omp/agent/config.yml",
|
|
1731
2054
|
scope: "user",
|
|
1732
2055
|
level: "official",
|
|
1733
|
-
note: "
|
|
2056
|
+
note: "Profiles use ~/.omp/profiles/<name>/agent."
|
|
1734
2057
|
},
|
|
1735
2058
|
{
|
|
1736
2059
|
path: "~/.omp/agent/settings.json",
|
|
@@ -1852,6 +2175,11 @@ var Omp = class extends Harness {
|
|
|
1852
2175
|
note: "Auto-generated managed skills."
|
|
1853
2176
|
}
|
|
1854
2177
|
];
|
|
2178
|
+
skillsSyncTarget = {
|
|
2179
|
+
path: "~/.omp/agent/skills/",
|
|
2180
|
+
scope: "user",
|
|
2181
|
+
level: "official"
|
|
2182
|
+
};
|
|
1855
2183
|
commands = [{
|
|
1856
2184
|
path: ".omp/commands/",
|
|
1857
2185
|
scope: "project",
|
|
@@ -1912,12 +2240,29 @@ var Omp = class extends Harness {
|
|
|
1912
2240
|
key: ["mcpServers"],
|
|
1913
2241
|
dialect: "standard"
|
|
1914
2242
|
}];
|
|
2243
|
+
envOverrides = [{
|
|
2244
|
+
variable: "PI_CODING_AGENT_DIR",
|
|
2245
|
+
path: "~/.omp/agent",
|
|
2246
|
+
scope: "user",
|
|
2247
|
+
level: "official",
|
|
2248
|
+
relocates: [
|
|
2249
|
+
"config",
|
|
2250
|
+
"sessions",
|
|
2251
|
+
"instructions",
|
|
2252
|
+
"skills",
|
|
2253
|
+
"commands",
|
|
2254
|
+
"promptTemplates"
|
|
2255
|
+
],
|
|
2256
|
+
note: "Ignored under a named profile (OMP_PROFILE). On Linux, after omp config init-xdg, data, state and cache live under $XDG_*_HOME/omp instead."
|
|
2257
|
+
}];
|
|
1915
2258
|
agentsFile = "~/.omp/agent/AGENTS.md";
|
|
1916
2259
|
detection = {
|
|
1917
2260
|
envVars: ["OMP_PROFILE", "PI_CODING_AGENT_DIR"],
|
|
1918
2261
|
projectMarkers: [".omp"]
|
|
1919
2262
|
};
|
|
1920
2263
|
};
|
|
2264
|
+
//#endregion
|
|
2265
|
+
//#region src/harnesses/opencode.ts
|
|
1921
2266
|
var OpenCode = class extends Harness {
|
|
1922
2267
|
id = "opencode";
|
|
1923
2268
|
name = "OpenCode CLI";
|
|
@@ -2014,6 +2359,11 @@ var OpenCode = class extends Harness {
|
|
|
2014
2359
|
scope: "project",
|
|
2015
2360
|
level: "official"
|
|
2016
2361
|
},
|
|
2362
|
+
{
|
|
2363
|
+
path: "~/.config/opencode/skills/",
|
|
2364
|
+
scope: "user",
|
|
2365
|
+
level: "official"
|
|
2366
|
+
},
|
|
2017
2367
|
{
|
|
2018
2368
|
path: "~/.claude/skills/",
|
|
2019
2369
|
scope: "user",
|
|
@@ -2026,6 +2376,12 @@ var OpenCode = class extends Harness {
|
|
|
2026
2376
|
level: "official"
|
|
2027
2377
|
}
|
|
2028
2378
|
];
|
|
2379
|
+
skillsSyncTarget = {
|
|
2380
|
+
path: "~/.config/opencode/skills/",
|
|
2381
|
+
scope: "user",
|
|
2382
|
+
level: "official",
|
|
2383
|
+
note: "OpenCode also scans ~/.claude/skills/ and ~/.agents/skills/; skill names must be unique across them."
|
|
2384
|
+
};
|
|
2029
2385
|
commands = [{
|
|
2030
2386
|
path: ".opencode/commands/",
|
|
2031
2387
|
scope: "project",
|
|
@@ -2081,6 +2437,8 @@ var OpenCode = class extends Harness {
|
|
|
2081
2437
|
]
|
|
2082
2438
|
};
|
|
2083
2439
|
};
|
|
2440
|
+
//#endregion
|
|
2441
|
+
//#region src/harnesses/pi.ts
|
|
2084
2442
|
const MODEL_HEADERS = [
|
|
2085
2443
|
"provider",
|
|
2086
2444
|
"model",
|
|
@@ -2136,8 +2494,7 @@ var Pi = class extends Harness {
|
|
|
2136
2494
|
config = [{
|
|
2137
2495
|
path: "~/.pi/agent/settings.json",
|
|
2138
2496
|
scope: "user",
|
|
2139
|
-
level: "official"
|
|
2140
|
-
note: "Config dir overridable with PI_CODING_AGENT_DIR."
|
|
2497
|
+
level: "official"
|
|
2141
2498
|
}, {
|
|
2142
2499
|
path: ".pi/settings.json",
|
|
2143
2500
|
scope: "project",
|
|
@@ -2147,7 +2504,7 @@ var Pi = class extends Harness {
|
|
|
2147
2504
|
path: "~/.pi/agent/sessions/<dash-encoded-cwd>/<timestamp>_<session-id>.jsonl",
|
|
2148
2505
|
scope: "data",
|
|
2149
2506
|
level: "official",
|
|
2150
|
-
note: "Per-project session transcripts in JSONL format; session ids are UUIDv7. Overridable with
|
|
2507
|
+
note: "Per-project session transcripts in JSONL format; session ids are UUIDv7. Overridable with --session-dir."
|
|
2151
2508
|
}];
|
|
2152
2509
|
persistence = [{
|
|
2153
2510
|
format: "JSON",
|
|
@@ -2194,6 +2551,11 @@ var Pi = class extends Harness {
|
|
|
2194
2551
|
level: "official",
|
|
2195
2552
|
note: "Global user-level skills."
|
|
2196
2553
|
}];
|
|
2554
|
+
skillsSyncTarget = {
|
|
2555
|
+
path: "~/.pi/agent/skills/",
|
|
2556
|
+
scope: "user",
|
|
2557
|
+
level: "official"
|
|
2558
|
+
};
|
|
2197
2559
|
commands = [{
|
|
2198
2560
|
path: ".pi/prompts/",
|
|
2199
2561
|
scope: "project",
|
|
@@ -2257,6 +2619,28 @@ var Pi = class extends Harness {
|
|
|
2257
2619
|
level: "official",
|
|
2258
2620
|
note: "Lists models with configured provider authentication; accepts an optional fuzzy search."
|
|
2259
2621
|
};
|
|
2622
|
+
envOverrides = [{
|
|
2623
|
+
variable: "PI_CODING_AGENT_DIR",
|
|
2624
|
+
path: "~/.pi/agent",
|
|
2625
|
+
scope: "user",
|
|
2626
|
+
level: "official",
|
|
2627
|
+
relocates: [
|
|
2628
|
+
"config",
|
|
2629
|
+
"sessions",
|
|
2630
|
+
"instructions",
|
|
2631
|
+
"skills",
|
|
2632
|
+
"commands",
|
|
2633
|
+
"promptTemplates"
|
|
2634
|
+
],
|
|
2635
|
+
note: "A leading ~ in the value is expanded."
|
|
2636
|
+
}, {
|
|
2637
|
+
variable: "PI_CODING_AGENT_SESSION_DIR",
|
|
2638
|
+
path: "~/.pi/agent/sessions",
|
|
2639
|
+
scope: "data",
|
|
2640
|
+
level: "official",
|
|
2641
|
+
relocates: ["sessions"],
|
|
2642
|
+
note: "Takes precedence over PI_CODING_AGENT_DIR for sessions; --session-dir beats both."
|
|
2643
|
+
}];
|
|
2260
2644
|
agentsFile = "~/.pi/agent/AGENTS.md";
|
|
2261
2645
|
detection = {
|
|
2262
2646
|
envVars: [
|
|
@@ -2270,6 +2654,8 @@ var Pi = class extends Harness {
|
|
|
2270
2654
|
return parsePiModelTable(stdout, "Pi");
|
|
2271
2655
|
}
|
|
2272
2656
|
};
|
|
2657
|
+
//#endregion
|
|
2658
|
+
//#region src/harnesses/prime-agent.ts
|
|
2273
2659
|
var PrimeAgent = class extends Harness {
|
|
2274
2660
|
id = "prime-agent";
|
|
2275
2661
|
name = "Prime Agent";
|
|
@@ -2371,6 +2757,11 @@ var PrimeAgent = class extends Harness {
|
|
|
2371
2757
|
note: "Shared skills directory; root .md files are ignored there."
|
|
2372
2758
|
}
|
|
2373
2759
|
];
|
|
2760
|
+
skillsSyncTarget = {
|
|
2761
|
+
path: "~/.prime/agent/skills/",
|
|
2762
|
+
scope: "user",
|
|
2763
|
+
level: "official"
|
|
2764
|
+
};
|
|
2374
2765
|
commands = [{
|
|
2375
2766
|
path: ".prime/agent/prompts/",
|
|
2376
2767
|
scope: "project",
|
|
@@ -2442,6 +2833,8 @@ var PrimeAgent = class extends Harness {
|
|
|
2442
2833
|
return parsePiModelTable(stdout, "Prime Agent");
|
|
2443
2834
|
}
|
|
2444
2835
|
};
|
|
2836
|
+
//#endregion
|
|
2837
|
+
//#region src/harnesses/index.ts
|
|
2445
2838
|
const harnesses = [
|
|
2446
2839
|
Antigravity,
|
|
2447
2840
|
Claude,
|
|
@@ -2457,6 +2850,8 @@ const harnesses = [
|
|
|
2457
2850
|
PrimeAgent,
|
|
2458
2851
|
Pi
|
|
2459
2852
|
];
|
|
2853
|
+
//#endregion
|
|
2854
|
+
//#region src/registry.ts
|
|
2460
2855
|
let registry;
|
|
2461
2856
|
function getRegistry() {
|
|
2462
2857
|
if (!registry) {
|
|
@@ -2504,6 +2899,8 @@ function detectHarness(cwd) {
|
|
|
2504
2899
|
}
|
|
2505
2900
|
return match;
|
|
2506
2901
|
}
|
|
2902
|
+
//#endregion
|
|
2903
|
+
//#region src/mcp-servers.ts
|
|
2507
2904
|
function resolveConfigs(harness, options) {
|
|
2508
2905
|
return harness.resolveCandidates(harness.mcpConfigs, options).map((entry) => ({
|
|
2509
2906
|
entry,
|
|
@@ -3147,6 +3544,8 @@ function syncMcpServers(harnesses, options = {}) {
|
|
|
3147
3544
|
targets: harnesses.map((harness) => syncMcpTarget(harness, master, masterNames, options))
|
|
3148
3545
|
};
|
|
3149
3546
|
}
|
|
3547
|
+
//#endregion
|
|
3548
|
+
//#region src/agents-sync.ts
|
|
3150
3549
|
function readIfExists(path) {
|
|
3151
3550
|
try {
|
|
3152
3551
|
return readFileSync(path, "utf8");
|
|
@@ -3235,6 +3634,14 @@ function moveFile(from, to) {
|
|
|
3235
3634
|
renameSync(from, to);
|
|
3236
3635
|
} catch (error) {
|
|
3237
3636
|
if (error.code !== "EXDEV") throw error;
|
|
3637
|
+
if (lstatSync(from).isDirectory()) {
|
|
3638
|
+
cpSync(from, to, {
|
|
3639
|
+
recursive: true,
|
|
3640
|
+
verbatimSymlinks: true
|
|
3641
|
+
});
|
|
3642
|
+
rmSync(from, { recursive: true });
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3238
3645
|
copyFileSync(from, to);
|
|
3239
3646
|
unlinkSync(from);
|
|
3240
3647
|
}
|
|
@@ -3286,7 +3693,7 @@ function syncFileTarget(id, path, source, check, options) {
|
|
|
3286
3693
|
};
|
|
3287
3694
|
return applyFileTarget(id, path, source, state, options);
|
|
3288
3695
|
}
|
|
3289
|
-
function syncTarget$
|
|
3696
|
+
function syncTarget$2(harness, config, check, options) {
|
|
3290
3697
|
if (config.excludes.includes(harness.id)) return {
|
|
3291
3698
|
id: harness.id,
|
|
3292
3699
|
action: "skipped",
|
|
@@ -3337,23 +3744,144 @@ function syncAgentsFiles(harnesses, check = false, options = {}) {
|
|
|
3337
3744
|
return {
|
|
3338
3745
|
source: config.source,
|
|
3339
3746
|
check,
|
|
3340
|
-
targets: harnesses.map((harness) => syncTarget$
|
|
3747
|
+
targets: harnesses.map((harness) => syncTarget$2(harness, config, check, options))
|
|
3341
3748
|
};
|
|
3342
3749
|
}
|
|
3750
|
+
//#endregion
|
|
3751
|
+
//#region src/directory-link.ts
|
|
3752
|
+
const CHECK_DETAIL = "check mode: not applied";
|
|
3753
|
+
function linkTarget(path) {
|
|
3754
|
+
const target = readlinkSync(path);
|
|
3755
|
+
return isAbsolute(target) ? target : resolve(dirname(path), target);
|
|
3756
|
+
}
|
|
3757
|
+
function isInsideDirectory(path, directory) {
|
|
3758
|
+
const remainder = relative(directory, path);
|
|
3759
|
+
return remainder !== "" && !remainder.startsWith("..") && !isAbsolute(remainder);
|
|
3760
|
+
}
|
|
3761
|
+
function tempPath(path, kind) {
|
|
3762
|
+
return join(dirname(path), `.${basename(path)}-${randomUUID()}.${kind}.tmp`);
|
|
3763
|
+
}
|
|
3764
|
+
function realDirectory(path) {
|
|
3765
|
+
try {
|
|
3766
|
+
return realpathSync(path);
|
|
3767
|
+
} catch {
|
|
3768
|
+
return resolve(path);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
function backupDestination(kind, id, path, options) {
|
|
3772
|
+
const backupDir = join(agntnDataDir(options), "diverged", kind, id);
|
|
3773
|
+
mkdirSync(backupDir, { recursive: true });
|
|
3774
|
+
const backup = join(backupDir, `${basename(path)}-${Date.now()}-${randomUUID()}`);
|
|
3775
|
+
if (lstatSync(path).isSymbolicLink()) {
|
|
3776
|
+
symlinkSync(linkTarget(path), backup);
|
|
3777
|
+
unlinkSync(path);
|
|
3778
|
+
return backup;
|
|
3779
|
+
}
|
|
3780
|
+
moveFile(path, backup);
|
|
3781
|
+
return backup;
|
|
3782
|
+
}
|
|
3783
|
+
function linkDirectory(path, source, kind) {
|
|
3784
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
3785
|
+
const temp = tempPath(path, `${kind}-link`);
|
|
3786
|
+
symlinkSync(source, temp, "junction");
|
|
3787
|
+
renameSync(temp, path);
|
|
3788
|
+
}
|
|
3789
|
+
const DIRECTORY_ACTION = {
|
|
3790
|
+
missing: "linked",
|
|
3791
|
+
"correct-link": "unchanged",
|
|
3792
|
+
"wrong-link": "relinked",
|
|
3793
|
+
"legacy-links": "relinked",
|
|
3794
|
+
"local-files": "adopted",
|
|
3795
|
+
"not-directory": "skipped",
|
|
3796
|
+
source: "skipped"
|
|
3797
|
+
};
|
|
3798
|
+
const SKIP_DETAIL = {
|
|
3799
|
+
"not-directory": "destination exists and is not a directory or symlink",
|
|
3800
|
+
source: "destination holds the source directory"
|
|
3801
|
+
};
|
|
3802
|
+
function isEntryLink(path, sourceDirs) {
|
|
3803
|
+
if (!lstatSync(path).isSymbolicLink()) return false;
|
|
3804
|
+
const target = linkTarget(path);
|
|
3805
|
+
return sourceDirs.some((directory) => isInsideDirectory(target, directory));
|
|
3806
|
+
}
|
|
3807
|
+
function linksThrough(sourceDir, targetDir) {
|
|
3808
|
+
let path = sourceDir;
|
|
3809
|
+
for (let hop = 0; hop < 32; hop++) {
|
|
3810
|
+
if (path === targetDir || isInsideDirectory(path, targetDir)) return true;
|
|
3811
|
+
try {
|
|
3812
|
+
if (!lstatSync(path).isSymbolicLink()) return false;
|
|
3813
|
+
} catch {
|
|
3814
|
+
return false;
|
|
3815
|
+
}
|
|
3816
|
+
path = linkTarget(path);
|
|
3817
|
+
}
|
|
3818
|
+
return false;
|
|
3819
|
+
}
|
|
3820
|
+
function inspectDirectory(targetDir, sourceDir) {
|
|
3821
|
+
let stats;
|
|
3822
|
+
try {
|
|
3823
|
+
stats = lstatSync(targetDir);
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
3826
|
+
throw error;
|
|
3827
|
+
}
|
|
3828
|
+
if (stats.isSymbolicLink()) {
|
|
3829
|
+
if (linkTarget(targetDir) === sourceDir) return { kind: "correct-link" };
|
|
3830
|
+
return { kind: linksThrough(sourceDir, targetDir) ? "source" : "wrong-link" };
|
|
3831
|
+
}
|
|
3832
|
+
if (!stats.isDirectory()) return { kind: "not-directory" };
|
|
3833
|
+
return inspectLocalDirectory(targetDir, sourceDir);
|
|
3834
|
+
}
|
|
3835
|
+
function inspectLocalDirectory(targetDir, sourceDir) {
|
|
3836
|
+
const realSource = realDirectory(sourceDir);
|
|
3837
|
+
const realTarget = realpathSync(targetDir);
|
|
3838
|
+
if (realSource === realTarget || isInsideDirectory(realSource, realTarget)) return { kind: "source" };
|
|
3839
|
+
const sourceDirs = realSource === sourceDir ? [sourceDir] : [sourceDir, realSource];
|
|
3840
|
+
const entries = readdirSync(targetDir).map((entry) => join(targetDir, entry));
|
|
3841
|
+
return entries.every((entry) => isEntryLink(entry, sourceDirs)) ? {
|
|
3842
|
+
kind: "legacy-links",
|
|
3843
|
+
entries
|
|
3844
|
+
} : { kind: "local-files" };
|
|
3845
|
+
}
|
|
3846
|
+
function clearDirectory(kind, id, targetDir, state, options) {
|
|
3847
|
+
if (state.kind === "wrong-link" || state.kind === "local-files") return backupDestination(kind, id, targetDir, options);
|
|
3848
|
+
if (state.kind === "legacy-links") {
|
|
3849
|
+
for (const entry of state.entries) unlinkSync(entry);
|
|
3850
|
+
rmdirSync(targetDir);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
function syncDirectoryLink(kind, id, targetDir, sourceDir, check, options) {
|
|
3854
|
+
const state = inspectDirectory(targetDir, sourceDir);
|
|
3855
|
+
const action = DIRECTORY_ACTION[state.kind];
|
|
3856
|
+
if (state.kind === "correct-link") return { action };
|
|
3857
|
+
const skip = SKIP_DETAIL[state.kind];
|
|
3858
|
+
if (skip !== void 0) return {
|
|
3859
|
+
action,
|
|
3860
|
+
detail: skip
|
|
3861
|
+
};
|
|
3862
|
+
if (check) return {
|
|
3863
|
+
action,
|
|
3864
|
+
detail: CHECK_DETAIL
|
|
3865
|
+
};
|
|
3866
|
+
const detail = clearDirectory(kind, id, targetDir, state, options);
|
|
3867
|
+
linkDirectory(targetDir, sourceDir, kind);
|
|
3868
|
+
return {
|
|
3869
|
+
action,
|
|
3870
|
+
detail
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
//#endregion
|
|
3874
|
+
//#region src/prompt-sync.ts
|
|
3343
3875
|
const GENERATED_MARKER = "# Generated by @agntn/harnesses from ";
|
|
3344
3876
|
const PORTABLE_PROMPT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
|
|
3345
3877
|
const GEMINI_ACTION = {
|
|
3346
3878
|
missing: "generated",
|
|
3347
|
-
"correct-link": "adopted",
|
|
3348
|
-
"wrong-link": "adopted",
|
|
3349
3879
|
"owned-file": "replaced",
|
|
3350
3880
|
"unmanaged-file": "adopted",
|
|
3351
3881
|
unsupported: "skipped"
|
|
3352
3882
|
};
|
|
3353
3883
|
const GEMINI_BACKUP = {
|
|
3354
3884
|
missing: false,
|
|
3355
|
-
"correct-link": true,
|
|
3356
|
-
"wrong-link": true,
|
|
3357
3885
|
"owned-file": true,
|
|
3358
3886
|
"unmanaged-file": true,
|
|
3359
3887
|
unsupported: false
|
|
@@ -3391,7 +3919,7 @@ function generatedSource(content) {
|
|
|
3391
3919
|
return null;
|
|
3392
3920
|
}
|
|
3393
3921
|
}
|
|
3394
|
-
function inspectDestination(path
|
|
3922
|
+
function inspectDestination(path) {
|
|
3395
3923
|
let stats;
|
|
3396
3924
|
try {
|
|
3397
3925
|
stats = lstatSync(path);
|
|
@@ -3399,11 +3927,7 @@ function inspectDestination(path, source) {
|
|
|
3399
3927
|
if (error.code === "ENOENT") return { kind: "missing" };
|
|
3400
3928
|
throw error;
|
|
3401
3929
|
}
|
|
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
|
-
}
|
|
3930
|
+
if (stats.isSymbolicLink()) return { kind: "unmanaged-file" };
|
|
3407
3931
|
if (!stats.isFile()) return { kind: "unsupported" };
|
|
3408
3932
|
const content = readFileSync(path, "utf8");
|
|
3409
3933
|
if (generatedSource(content) !== null) return {
|
|
@@ -3412,75 +3936,17 @@ function inspectDestination(path, source) {
|
|
|
3412
3936
|
};
|
|
3413
3937
|
return { kind: "unmanaged-file" };
|
|
3414
3938
|
}
|
|
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
3939
|
function writeTemplate(path, content) {
|
|
3425
3940
|
mkdirSync(dirname(path), { recursive: true });
|
|
3426
3941
|
const temp = tempPath(path, "prompt-write");
|
|
3427
3942
|
writeFileSync(temp, content);
|
|
3428
3943
|
renameSync(temp, path);
|
|
3429
3944
|
}
|
|
3430
|
-
function
|
|
3431
|
-
|
|
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
|
-
};
|
|
3945
|
+
function parseYaml(raw) {
|
|
3946
|
+
return createRequire(import.meta.url)("yaml").parse(raw);
|
|
3481
3947
|
}
|
|
3482
3948
|
function frontmatterDescription(raw, path) {
|
|
3483
|
-
const frontmatter =
|
|
3949
|
+
const frontmatter = parseYaml(raw);
|
|
3484
3950
|
if (frontmatter === null) return void 0;
|
|
3485
3951
|
if (typeof frontmatter !== "object" || Array.isArray(frontmatter)) throw new TypeError(`Prompt template frontmatter is not an object: ${path}`);
|
|
3486
3952
|
const description = frontmatter.description;
|
|
@@ -3528,10 +3994,10 @@ function syncGeminiTemplate(id, targetDir, template, check, options) {
|
|
|
3528
3994
|
source: template.path,
|
|
3529
3995
|
path,
|
|
3530
3996
|
action,
|
|
3531
|
-
detail:
|
|
3997
|
+
detail: CHECK_DETAIL
|
|
3532
3998
|
};
|
|
3533
3999
|
let detail;
|
|
3534
|
-
if (GEMINI_BACKUP[state.kind]) detail = backupDestination(id, path, options);
|
|
4000
|
+
if (GEMINI_BACKUP[state.kind]) detail = backupDestination("prompts", id, path, options);
|
|
3535
4001
|
writeTemplate(path, content);
|
|
3536
4002
|
return {
|
|
3537
4003
|
name: template.name,
|
|
@@ -3541,23 +4007,6 @@ function syncGeminiTemplate(id, targetDir, template, check, options) {
|
|
|
3541
4007
|
detail
|
|
3542
4008
|
};
|
|
3543
4009
|
}
|
|
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
4010
|
function staleGeminiSource(path, sourceDir) {
|
|
3562
4011
|
let content;
|
|
3563
4012
|
try {
|
|
@@ -3569,31 +4018,43 @@ function staleGeminiSource(path, sourceDir) {
|
|
|
3569
4018
|
if (source === null) return null;
|
|
3570
4019
|
return isInsideDirectory(source, sourceDir) ? source : null;
|
|
3571
4020
|
}
|
|
3572
|
-
function
|
|
4021
|
+
function pruneGeminiTarget(id, targetDir, sourceDir, activeNames, check, options) {
|
|
3573
4022
|
let entries;
|
|
3574
4023
|
try {
|
|
3575
|
-
entries = readdirSync(targetDir
|
|
4024
|
+
entries = readdirSync(targetDir);
|
|
3576
4025
|
} catch (error) {
|
|
3577
4026
|
if (error.code === "ENOENT") return [];
|
|
3578
4027
|
throw error;
|
|
3579
4028
|
}
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
const
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
return [{
|
|
3588
|
-
name: basename(entry.name, extension),
|
|
3589
|
-
source,
|
|
4029
|
+
return entries.filter((entry) => !activeNames.has(entry)).map((entry) => {
|
|
4030
|
+
const path = join(targetDir, entry);
|
|
4031
|
+
const name = basename(entry, extname(entry));
|
|
4032
|
+
const source = staleGeminiSource(path, sourceDir);
|
|
4033
|
+
if (check) return {
|
|
4034
|
+
name,
|
|
4035
|
+
...source === null ? {} : { source },
|
|
3590
4036
|
path,
|
|
3591
4037
|
action: "removed",
|
|
3592
|
-
|
|
3593
|
-
}
|
|
4038
|
+
detail: CHECK_DETAIL
|
|
4039
|
+
};
|
|
4040
|
+
if (source !== null) {
|
|
4041
|
+
unlinkSync(path);
|
|
4042
|
+
return {
|
|
4043
|
+
name,
|
|
4044
|
+
source,
|
|
4045
|
+
path,
|
|
4046
|
+
action: "removed"
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
return {
|
|
4050
|
+
name,
|
|
4051
|
+
path,
|
|
4052
|
+
action: "removed",
|
|
4053
|
+
detail: backupDestination("prompts", id, path, options)
|
|
4054
|
+
};
|
|
3594
4055
|
});
|
|
3595
4056
|
}
|
|
3596
|
-
function syncTarget(harness, templates, sourceDir, check, options) {
|
|
4057
|
+
function syncTarget$1(harness, templates, sourceDir, check, options) {
|
|
3597
4058
|
const target = harness.promptTemplateSyncTarget;
|
|
3598
4059
|
if (target === null) return {
|
|
3599
4060
|
id: harness.id,
|
|
@@ -3606,11 +4067,17 @@ function syncTarget(harness, templates, sourceDir, check, options) {
|
|
|
3606
4067
|
detail: "prompt template destination is unavailable on this platform",
|
|
3607
4068
|
templates: []
|
|
3608
4069
|
};
|
|
3609
|
-
const targetDir = resolved.path;
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4070
|
+
const targetDir = resolve(resolved.path);
|
|
4071
|
+
if (resolved.format === "markdown") return {
|
|
4072
|
+
id: harness.id,
|
|
4073
|
+
path: targetDir,
|
|
4074
|
+
format: resolved.format,
|
|
4075
|
+
...syncDirectoryLink("prompts", harness.id, targetDir, sourceDir, check, options),
|
|
4076
|
+
templates: []
|
|
4077
|
+
};
|
|
4078
|
+
const results = templates.map((template) => syncGeminiTemplate(harness.id, targetDir, template, check, options));
|
|
4079
|
+
const activeNames = new Set(templates.map((template) => `${template.name}.toml`));
|
|
4080
|
+
results.push(...pruneGeminiTarget(harness.id, targetDir, sourceDir, activeNames, check, options));
|
|
3614
4081
|
return {
|
|
3615
4082
|
id: harness.id,
|
|
3616
4083
|
path: targetDir,
|
|
@@ -3629,9 +4096,59 @@ function syncPromptTemplates(harnesses, check = false, options = {}) {
|
|
|
3629
4096
|
source,
|
|
3630
4097
|
check,
|
|
3631
4098
|
templates: prepared.map((template) => template.name),
|
|
3632
|
-
targets: harnesses.map((harness) => syncTarget(harness, prepared, source, check, options))
|
|
4099
|
+
targets: harnesses.map((harness) => syncTarget$1(harness, prepared, source, check, options))
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
//#endregion
|
|
4103
|
+
//#region src/skills-sync.ts
|
|
4104
|
+
function readSkills(sourceDir, check) {
|
|
4105
|
+
let entries;
|
|
4106
|
+
try {
|
|
4107
|
+
entries = readdirSync(sourceDir);
|
|
4108
|
+
} catch (error) {
|
|
4109
|
+
if (error.code !== "ENOENT") throw error;
|
|
4110
|
+
if (!check) mkdirSync(sourceDir, { recursive: true });
|
|
4111
|
+
return [];
|
|
4112
|
+
}
|
|
4113
|
+
return entries.filter((entry) => {
|
|
4114
|
+
const path = join(sourceDir, entry);
|
|
4115
|
+
try {
|
|
4116
|
+
return statSync(path).isDirectory() && existsSync(join(path, "SKILL.md"));
|
|
4117
|
+
} catch {
|
|
4118
|
+
return false;
|
|
4119
|
+
}
|
|
4120
|
+
}).sort((left, right) => left.localeCompare(right));
|
|
4121
|
+
}
|
|
4122
|
+
function syncTarget(harness, sourceDir, check, options) {
|
|
4123
|
+
const target = harness.skillsSyncTarget;
|
|
4124
|
+
if (target === null) return {
|
|
4125
|
+
id: harness.id,
|
|
4126
|
+
action: "skipped",
|
|
4127
|
+
detail: "no stable user-scope skills directory"
|
|
4128
|
+
};
|
|
4129
|
+
const resolved = harness.resolveCandidates([target], options)[0];
|
|
4130
|
+
if (resolved === void 0) return {
|
|
4131
|
+
id: harness.id,
|
|
4132
|
+
action: "skipped",
|
|
4133
|
+
detail: "skills directory is unavailable on this platform"
|
|
4134
|
+
};
|
|
4135
|
+
const targetDir = resolve(resolved.path);
|
|
4136
|
+
return {
|
|
4137
|
+
id: harness.id,
|
|
4138
|
+
path: targetDir,
|
|
4139
|
+
...syncDirectoryLink("skills", harness.id, targetDir, sourceDir, check, options)
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
function syncSkills(harnesses, check = false, options = {}) {
|
|
4143
|
+
const source = join(agntnDataDir(options), "skills");
|
|
4144
|
+
return {
|
|
4145
|
+
source,
|
|
4146
|
+
check,
|
|
4147
|
+
skills: readSkills(source, check),
|
|
4148
|
+
targets: harnesses.map((harness) => syncTarget(harness, source, check, options))
|
|
3633
4149
|
};
|
|
3634
4150
|
}
|
|
3635
|
-
|
|
4151
|
+
//#endregion
|
|
4152
|
+
export { agntnDataDir as C, agntnConfigDir as S, getHarness as _, McpEnvError as a, registerHarness as b, masterMcpPath as c, removeMcpServer as d, syncMcpServers as f, getAllHarnesses as g, detectProjectHarnesses as h, syncAgentsFiles as i, parseJsonc as l, detectHarnessFromEnv as m, syncPromptTemplates as n, addMcpServer as o, detectHarness as p, readAgentsConfig as r, listMcpServers as s, syncSkills as t, readMasterMcpServers as u, isHarnessId as v, resolvePathTemplate as w, Harness as x, listHarnesses as y };
|
|
3636
4153
|
|
|
3637
|
-
//# sourceMappingURL=
|
|
4154
|
+
//# sourceMappingURL=skills-sync.mjs.map
|