@difflab/pi 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.
- package/README.md +13 -1
- package/agents/diffpi-copilot.md +22 -0
- package/agents/diffpi-orchestrator.md +23 -0
- package/agents/diffpi-tutor.md +20 -0
- package/agents/diffpi-worker.md +19 -0
- package/dist/assets.d.ts +2 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/extensions/index.js +666 -123
- package/dist/fsx.d.ts +6 -0
- package/dist/fsx.d.ts.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +540 -98
- package/dist/modes.d.ts +59 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/pi.d.ts +21 -5
- package/dist/pi.d.ts.map +1 -1
- package/dist/setup.d.ts +7 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +386 -111
- package/dist/tools/modes.d.ts +4 -0
- package/dist/tools/modes.d.ts.map +1 -0
- package/dist/tools/setup.d.ts.map +1 -1
- package/package.json +2 -1
- package/skills/diffpi-setup/SKILL.md +2 -0
- package/skills/mode/SKILL.md +38 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,106 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
// src/fsx.ts
|
|
8
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
9
|
+
async function readDirectoryIfExists(path) {
|
|
10
|
+
try {
|
|
11
|
+
return await readdir(path, { withFileTypes: true });
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (isMissingPath(error))
|
|
14
|
+
return [];
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function readTextIfExists(path) {
|
|
19
|
+
try {
|
|
20
|
+
return await readFile(path, "utf8");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (isMissingPath(error))
|
|
23
|
+
return;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isMissingPath(error) {
|
|
28
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/config.ts
|
|
32
|
+
var modelReferenceSchema = z.string().trim().min(1);
|
|
33
|
+
var agentConfigSchema = z.object({
|
|
34
|
+
models: z.array(modelReferenceSchema).optional()
|
|
35
|
+
}).strict();
|
|
36
|
+
var diffpiConfigSchema = z.object({
|
|
37
|
+
agents: z.record(z.string(), agentConfigSchema).optional()
|
|
38
|
+
}).strict();
|
|
39
|
+
function diffpiConfigPaths(homeDir = homedir()) {
|
|
40
|
+
const directory = join(homeDir, ".difflab", "diffpi");
|
|
41
|
+
return {
|
|
42
|
+
yaml: join(directory, "config.yaml"),
|
|
43
|
+
json: join(directory, "config.json")
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function loadDiffpiConfig(options = {}) {
|
|
47
|
+
const paths = diffpiConfigPaths(options.homeDir);
|
|
48
|
+
for (const [format, path] of [
|
|
49
|
+
["yaml", paths.yaml],
|
|
50
|
+
["json", paths.json]
|
|
51
|
+
]) {
|
|
52
|
+
const content = await readTextIfExists(path);
|
|
53
|
+
if (content === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
try {
|
|
56
|
+
const value = format === "yaml" ? parseYamlConfig(content) : JSON.parse(content);
|
|
57
|
+
return { config: diffpiConfigSchema.parse(value ?? {}), path };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
60
|
+
throw new Error(`Invalid Diffpi config at ${path}: ${reason}`, { cause: error });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { config: {} };
|
|
64
|
+
}
|
|
65
|
+
function resolveAgentModelPreferences(agentId, profilePreferences, config) {
|
|
66
|
+
const override = config.agents?.[agentId];
|
|
67
|
+
if (override && Object.hasOwn(override, "models"))
|
|
68
|
+
return [...override.models ?? []];
|
|
69
|
+
return [...profilePreferences];
|
|
70
|
+
}
|
|
71
|
+
function findPreferredModel(models, preference) {
|
|
72
|
+
const normalizedPreference = normalizeModelReference(preference);
|
|
73
|
+
const exactReference = models.find((model) => normalizeModelReference(`${model.provider}/${model.id}`) === normalizedPreference);
|
|
74
|
+
if (exactReference)
|
|
75
|
+
return exactReference;
|
|
76
|
+
const idPreference = preference.includes("/") ? preference.slice(preference.indexOf("/") + 1) : preference;
|
|
77
|
+
const normalizedIdPreference = normalizeModelReference(idPreference);
|
|
78
|
+
const exactId = models.find((model) => normalizeModelReference(model.id) === normalizedIdPreference);
|
|
79
|
+
if (exactId)
|
|
80
|
+
return exactId;
|
|
81
|
+
const preferenceTokens = normalizedIdPreference.split("-").filter(Boolean);
|
|
82
|
+
return models.find((model) => {
|
|
83
|
+
const modelTokens = new Set(normalizeModelReference(model.id).split("-").filter(Boolean));
|
|
84
|
+
return preferenceTokens.every((token) => modelTokens.has(token));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function parseYamlConfig(content) {
|
|
88
|
+
const document = content.replace(/^\uFEFF/, "").replace(/^---[^\S\r\n]*(?:#.*)?(?:\r?\n|$)/, "");
|
|
89
|
+
return parseFrontmatter(`---
|
|
90
|
+
${document}
|
|
91
|
+
---
|
|
92
|
+
`).frontmatter;
|
|
93
|
+
}
|
|
94
|
+
function normalizeModelReference(value) {
|
|
95
|
+
return value.toLowerCase().replace(/^~/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
96
|
+
}
|
|
97
|
+
// src/mcp.ts
|
|
98
|
+
import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
99
|
+
import { homedir as homedir2 } from "node:os";
|
|
100
|
+
import { dirname, join as join2 } from "node:path";
|
|
5
101
|
var mcp = {
|
|
6
|
-
globalConfigPath(homeDir =
|
|
7
|
-
return
|
|
102
|
+
globalConfigPath(homeDir = homedir2()) {
|
|
103
|
+
return join2(homeDir, ".config", "mcp", "mcp.json");
|
|
8
104
|
},
|
|
9
105
|
async serversEnsure(servers, options = {}) {
|
|
10
106
|
const path = options.path ?? mcp.globalConfigPath();
|
|
@@ -47,7 +143,7 @@ function getParsedConfig(content, path) {
|
|
|
47
143
|
}
|
|
48
144
|
async function getOptionalFile(path) {
|
|
49
145
|
try {
|
|
50
|
-
return await
|
|
146
|
+
return await readFile2(path, "utf8");
|
|
51
147
|
} catch (error) {
|
|
52
148
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
53
149
|
return;
|
|
@@ -58,14 +154,14 @@ function isRecord(value) {
|
|
|
58
154
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
59
155
|
}
|
|
60
156
|
// src/mise.ts
|
|
61
|
-
import { mkdir as mkdir2, readFile as
|
|
62
|
-
import { homedir as
|
|
63
|
-
import { basename, dirname as dirname2, join as
|
|
157
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
158
|
+
import { homedir as homedir3 } from "node:os";
|
|
159
|
+
import { basename, dirname as dirname2, join as join4 } from "node:path";
|
|
64
160
|
|
|
65
161
|
// src/process.ts
|
|
66
162
|
import { constants } from "node:fs";
|
|
67
163
|
import { access } from "node:fs/promises";
|
|
68
|
-
import { delimiter, join as
|
|
164
|
+
import { delimiter, join as join3 } from "node:path";
|
|
69
165
|
import { spawn } from "node:child_process";
|
|
70
166
|
var MAX_CAPTURED_OUTPUT_LENGTH = 65536;
|
|
71
167
|
async function findExecutable(name) {
|
|
@@ -80,7 +176,7 @@ async function findExecutable(name) {
|
|
|
80
176
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
81
177
|
if (!directory)
|
|
82
178
|
continue;
|
|
83
|
-
const candidate =
|
|
179
|
+
const candidate = join3(directory, name);
|
|
84
180
|
try {
|
|
85
181
|
await access(candidate, constants.X_OK);
|
|
86
182
|
return candidate;
|
|
@@ -127,11 +223,11 @@ var mise = {
|
|
|
127
223
|
return findExecutable(name);
|
|
128
224
|
},
|
|
129
225
|
async install(options = {}) {
|
|
130
|
-
const homeDir = options.homeDir ??
|
|
226
|
+
const homeDir = options.homeDir ?? homedir3();
|
|
131
227
|
const platform = options.platform ?? process.platform;
|
|
132
228
|
if (platform === "win32")
|
|
133
229
|
throw new Error("Automatic mise installation supports macOS and Linux only.");
|
|
134
|
-
const installedPath =
|
|
230
|
+
const installedPath = join4(homeDir, ".local", "bin", "mise");
|
|
135
231
|
if (options.dryRun)
|
|
136
232
|
return installedPath;
|
|
137
233
|
await runChecked("sh", ["-c", "curl -fsSL https://mise.run | sh"]);
|
|
@@ -141,7 +237,7 @@ var mise = {
|
|
|
141
237
|
return executable;
|
|
142
238
|
},
|
|
143
239
|
async hookEnsure(executable, options = {}) {
|
|
144
|
-
const homeDir = options.homeDir ??
|
|
240
|
+
const homeDir = options.homeDir ?? homedir3();
|
|
145
241
|
const hook = getShellHook(basename(options.shell ?? process.env.SHELL ?? ""), executable, homeDir);
|
|
146
242
|
const current = await getOptionalFile2(hook.path);
|
|
147
243
|
if (current.includes(MISE_HOOK_START))
|
|
@@ -169,7 +265,7 @@ var mise = {
|
|
|
169
265
|
async toolInstallLocal(executable, specification, cwd = process.cwd()) {
|
|
170
266
|
await runChecked(executable, ["use", "--path", cwd, specification], { cwd });
|
|
171
267
|
},
|
|
172
|
-
async toolUpdateAllGlobal(executable, homeDir =
|
|
268
|
+
async toolUpdateAllGlobal(executable, homeDir = homedir3()) {
|
|
173
269
|
await runChecked(executable, ["upgrade"], { cwd: homeDir });
|
|
174
270
|
}
|
|
175
271
|
};
|
|
@@ -178,7 +274,7 @@ function getShellHook(shell, executable, homeDir) {
|
|
|
178
274
|
switch (shell.toLowerCase()) {
|
|
179
275
|
case "zsh":
|
|
180
276
|
return {
|
|
181
|
-
path:
|
|
277
|
+
path: join4(homeDir, ".zshrc"),
|
|
182
278
|
content: `${MISE_HOOK_START}
|
|
183
279
|
eval "$(${command} activate zsh)"
|
|
184
280
|
${MISE_HOOK_END}
|
|
@@ -186,7 +282,7 @@ ${MISE_HOOK_END}
|
|
|
186
282
|
};
|
|
187
283
|
case "fish":
|
|
188
284
|
return {
|
|
189
|
-
path:
|
|
285
|
+
path: join4(homeDir, ".config", "fish", "config.fish"),
|
|
190
286
|
content: `${MISE_HOOK_START}
|
|
191
287
|
${command} activate fish | source
|
|
192
288
|
${MISE_HOOK_END}
|
|
@@ -195,7 +291,7 @@ ${MISE_HOOK_END}
|
|
|
195
291
|
case "nu":
|
|
196
292
|
case "nushell":
|
|
197
293
|
return {
|
|
198
|
-
path:
|
|
294
|
+
path: join4(homeDir, ".config", "nushell", "config.nu"),
|
|
199
295
|
content: `${MISE_HOOK_START}
|
|
200
296
|
let mise_bin = ${command}
|
|
201
297
|
let mise_path = $nu.default-config-dir | path join mise.nu
|
|
@@ -206,7 +302,7 @@ ${MISE_HOOK_END}
|
|
|
206
302
|
};
|
|
207
303
|
case "xonsh":
|
|
208
304
|
return {
|
|
209
|
-
path:
|
|
305
|
+
path: join4(homeDir, ".xonshrc"),
|
|
210
306
|
content: `${MISE_HOOK_START}
|
|
211
307
|
execx($(${command} activate xonsh))
|
|
212
308
|
${MISE_HOOK_END}
|
|
@@ -214,7 +310,7 @@ ${MISE_HOOK_END}
|
|
|
214
310
|
};
|
|
215
311
|
case "elvish":
|
|
216
312
|
return {
|
|
217
|
-
path:
|
|
313
|
+
path: join4(homeDir, ".config", "elvish", "rc.elv"),
|
|
218
314
|
content: `${MISE_HOOK_START}
|
|
219
315
|
var mise: = (ns [&])
|
|
220
316
|
eval (${command} activate elvish | slurp) &ns=$mise: &on-end={|ns| set mise: = $ns }
|
|
@@ -225,7 +321,7 @@ ${MISE_HOOK_END}
|
|
|
225
321
|
case "pwsh":
|
|
226
322
|
case "powershell":
|
|
227
323
|
return {
|
|
228
|
-
path:
|
|
324
|
+
path: join4(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
|
|
229
325
|
content: `${MISE_HOOK_START}
|
|
230
326
|
(& ${command} activate pwsh) | Out-String | Invoke-Expression
|
|
231
327
|
${MISE_HOOK_END}
|
|
@@ -234,7 +330,7 @@ ${MISE_HOOK_END}
|
|
|
234
330
|
case "bash":
|
|
235
331
|
default:
|
|
236
332
|
return {
|
|
237
|
-
path:
|
|
333
|
+
path: join4(homeDir, ".bashrc"),
|
|
238
334
|
content: `${MISE_HOOK_START}
|
|
239
335
|
eval "$(${command} activate bash)"
|
|
240
336
|
${MISE_HOOK_END}
|
|
@@ -273,7 +369,7 @@ function isVersionAtLeast(version, minimumVersion) {
|
|
|
273
369
|
}
|
|
274
370
|
async function getOptionalFile2(path) {
|
|
275
371
|
try {
|
|
276
|
-
return await
|
|
372
|
+
return await readFile3(path, "utf8");
|
|
277
373
|
} catch (error) {
|
|
278
374
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
279
375
|
return "";
|
|
@@ -283,80 +379,354 @@ async function getOptionalFile2(path) {
|
|
|
283
379
|
function getShellQuoted(value) {
|
|
284
380
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
285
381
|
}
|
|
286
|
-
// src/
|
|
287
|
-
import {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
382
|
+
// src/modes.ts
|
|
383
|
+
import {
|
|
384
|
+
getAgentDir,
|
|
385
|
+
parseFrontmatter as parseFrontmatter2
|
|
386
|
+
} from "@earendil-works/pi-coding-agent";
|
|
387
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
388
|
+
import { homedir as homedir4 } from "node:os";
|
|
389
|
+
import { basename as basename2, extname, join as join6 } from "node:path";
|
|
390
|
+
|
|
391
|
+
// src/assets.ts
|
|
392
|
+
import { existsSync } from "node:fs";
|
|
393
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
394
|
+
import { fileURLToPath } from "node:url";
|
|
395
|
+
function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
|
|
396
|
+
const moduleDir = dirname3(fileURLToPath(moduleUrl));
|
|
397
|
+
const candidates = [
|
|
398
|
+
join5(moduleDir, "agents"),
|
|
399
|
+
join5(moduleDir, "..", "agents"),
|
|
400
|
+
join5(moduleDir, "..", "..", "agents")
|
|
401
|
+
];
|
|
402
|
+
return candidates.find((path) => existsSync(path)) ?? candidates[1];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/modes.ts
|
|
406
|
+
async function discoverAgentModes(options) {
|
|
407
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
408
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
409
|
+
const modes = new Map;
|
|
410
|
+
const diagnostics = [];
|
|
411
|
+
await loadAgentModes(options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR, "diffpi agent", modes, diagnostics);
|
|
412
|
+
await loadAgentModes(join6(agentDir, "agents"), "user agent", modes, diagnostics);
|
|
413
|
+
if (options.includeSkills) {
|
|
414
|
+
await loadSkillModes(join6(homeDir, ".agents", "skills"), "user skill", modes, diagnostics);
|
|
415
|
+
await loadSkillModes(join6(agentDir, "skills"), "pi user skill", modes, diagnostics);
|
|
416
|
+
}
|
|
417
|
+
if (options.projectTrusted === true) {
|
|
418
|
+
if (options.includeSkills) {
|
|
419
|
+
await loadSkillModes(join6(options.cwd, ".agents", "skills"), "project skill", modes, diagnostics);
|
|
420
|
+
await loadSkillModes(join6(options.cwd, ".pi", "skills"), "pi project skill", modes, diagnostics);
|
|
313
421
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
422
|
+
await loadAgentModes(join6(options.cwd, ".agents", "agents"), "project agent", modes, diagnostics);
|
|
423
|
+
await loadAgentModes(join6(options.cwd, ".pi", "agents"), "pi project agent", modes, diagnostics);
|
|
424
|
+
}
|
|
425
|
+
const userConfig = await loadDiffpiConfig({ homeDir });
|
|
426
|
+
const configuredModes = [...modes.values()].map((mode) => ({
|
|
427
|
+
...mode,
|
|
428
|
+
modelPreferences: resolveAgentModelPreferences(mode.id, mode.modelPreferences, userConfig.config)
|
|
429
|
+
}));
|
|
430
|
+
return {
|
|
431
|
+
modes: configuredModes.sort((left, right) => left.id.localeCompare(right.id)),
|
|
432
|
+
diagnostics
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function resolveAgentMode(modes, requested) {
|
|
436
|
+
const name = requested.trim();
|
|
437
|
+
if (!name)
|
|
438
|
+
return { ok: false, message: "Agent name is required." };
|
|
439
|
+
const exact = modes.find((mode) => mode.id === name);
|
|
440
|
+
if (exact)
|
|
441
|
+
return { ok: true, active: exact, message: `Active inline agent: ${exact.id}.` };
|
|
442
|
+
const lowerName = name.toLowerCase();
|
|
443
|
+
const matches = modes.filter((mode) => mode.id.toLowerCase() === lowerName);
|
|
444
|
+
if (matches.length === 1) {
|
|
445
|
+
const active = matches[0];
|
|
446
|
+
return { ok: true, active, message: `Active inline agent: ${active.id}.` };
|
|
447
|
+
}
|
|
448
|
+
if (matches.length > 1) {
|
|
449
|
+
return {
|
|
450
|
+
ok: false,
|
|
451
|
+
message: `Inline agent "${name}" is ambiguous. Use one of: ${matches.map((mode) => mode.id).join(", ")}.`
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return { ok: false, message: `Unknown inline agent "${name}". Run /skill:mode or diffpi_modes_list.` };
|
|
455
|
+
}
|
|
456
|
+
function createModeController(pi, options = {}) {
|
|
457
|
+
let active;
|
|
458
|
+
let baseline;
|
|
459
|
+
const updateStatus = (ctx) => {
|
|
460
|
+
ctx.ui.setStatus(MODE_STATUS_KEY, active ? `mode: ${active.id}` : undefined);
|
|
461
|
+
};
|
|
462
|
+
const list = (ctx, listOptions = {}) => discoverAgentModes({
|
|
463
|
+
cwd: ctx.cwd,
|
|
464
|
+
agentDir: options.agentDir,
|
|
465
|
+
bundledAgentsDir: options.bundledAgentsDir,
|
|
466
|
+
homeDir: options.homeDir,
|
|
467
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
468
|
+
includeSkills: listOptions.includeSkills
|
|
469
|
+
});
|
|
470
|
+
return {
|
|
471
|
+
list,
|
|
472
|
+
async set(agent, ctx) {
|
|
473
|
+
const catalog = await list(ctx, { includeSkills: agent.includes(":") });
|
|
474
|
+
const result = resolveAgentMode(catalog.modes, agent);
|
|
475
|
+
if (!result.ok || !result.active)
|
|
476
|
+
return result;
|
|
477
|
+
baseline ??= captureRuntime(pi, ctx);
|
|
478
|
+
if (active && baseline)
|
|
479
|
+
await restoreRuntime(pi, baseline, ctx);
|
|
480
|
+
active = result.active;
|
|
481
|
+
const runtimeMessage = await applyModeRuntime(pi, active, ctx);
|
|
482
|
+
pi.appendEntry(MODE_STATE_ENTRY, { active, baseline });
|
|
483
|
+
updateStatus(ctx);
|
|
484
|
+
return { ...result, message: `${result.message} ${runtimeMessage}` };
|
|
485
|
+
},
|
|
486
|
+
async unset(ctx) {
|
|
487
|
+
if (!active)
|
|
488
|
+
return { ok: true, message: "Inline agent is already clear." };
|
|
489
|
+
if (baseline)
|
|
490
|
+
await restoreRuntime(pi, baseline, ctx);
|
|
491
|
+
active = undefined;
|
|
492
|
+
pi.appendEntry(MODE_STATE_ENTRY, { active: null });
|
|
493
|
+
baseline = undefined;
|
|
494
|
+
updateStatus(ctx);
|
|
495
|
+
return { ok: true, message: "Inline agent cleared. The previous model, thinking, tools, and prompt resume." };
|
|
496
|
+
},
|
|
497
|
+
async restore(ctx) {
|
|
498
|
+
const previousActive = active;
|
|
499
|
+
const previousBaseline = baseline;
|
|
500
|
+
const entry = [...ctx.sessionManager.getBranch()].reverse().find((candidate) => candidate.type === "custom" && candidate.customType === MODE_STATE_ENTRY);
|
|
501
|
+
const restored = entry?.data?.active;
|
|
502
|
+
const restoredBaseline = entry?.data?.baseline;
|
|
503
|
+
if (isAgentModeSnapshot(restored)) {
|
|
504
|
+
active = restored;
|
|
505
|
+
baseline = isModeBaseline(restoredBaseline) ? restoredBaseline : previousBaseline;
|
|
506
|
+
await applyModeRuntime(pi, active, ctx);
|
|
507
|
+
} else {
|
|
508
|
+
if (previousActive && previousBaseline)
|
|
509
|
+
pi.setActiveTools(previousBaseline.tools);
|
|
510
|
+
active = undefined;
|
|
511
|
+
baseline = undefined;
|
|
512
|
+
}
|
|
513
|
+
updateStatus(ctx);
|
|
514
|
+
},
|
|
515
|
+
apply(systemPrompt) {
|
|
516
|
+
if (!active)
|
|
517
|
+
return systemPrompt;
|
|
518
|
+
if (active.promptStrategy === "replace")
|
|
519
|
+
return active.systemPrompt;
|
|
520
|
+
return `${systemPrompt}
|
|
521
|
+
|
|
522
|
+
## Active inline agent: ${active.label}
|
|
523
|
+
|
|
524
|
+
${active.systemPrompt}`;
|
|
525
|
+
},
|
|
526
|
+
getActive() {
|
|
527
|
+
return active;
|
|
343
528
|
}
|
|
344
|
-
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
var MODE_STATE_ENTRY = "diffpi-mode-state";
|
|
532
|
+
var MODE_STATUS_KEY = "diffpi-mode";
|
|
533
|
+
var MODE_CONTROL_TOOLS = ["ask_user_question", "diffpi_modes_list", "diffpi_modes_set", "diffpi_modes_unset"];
|
|
534
|
+
var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
|
|
535
|
+
var THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
536
|
+
async function applyModeRuntime(pi, mode, ctx) {
|
|
537
|
+
let selectedModel;
|
|
538
|
+
if (mode.modelPreferences.length > 0) {
|
|
539
|
+
const scoped = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : undefined;
|
|
540
|
+
const availableModels = scoped ?? ctx.modelRegistry.getAvailable();
|
|
541
|
+
for (const preference of mode.modelPreferences) {
|
|
542
|
+
const model = findPreferredModel(availableModels, preference);
|
|
543
|
+
if (model && await pi.setModel(model)) {
|
|
544
|
+
selectedModel = `${model.provider}/${model.id}`;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (mode.thinkingLevel)
|
|
550
|
+
pi.setThinkingLevel(mode.thinkingLevel);
|
|
551
|
+
if (mode.tools.length > 0) {
|
|
552
|
+
const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
553
|
+
const selectedTools = [...new Set([...mode.tools, ...MODE_CONTROL_TOOLS])].filter((tool) => availableTools.has(tool));
|
|
554
|
+
if (selectedTools.length > 0)
|
|
555
|
+
pi.setActiveTools(selectedTools);
|
|
345
556
|
}
|
|
557
|
+
const parts = [];
|
|
558
|
+
if (mode.modelPreferences.length > 0) {
|
|
559
|
+
parts.push(selectedModel ? `Model: ${selectedModel}.` : "No preferred model was available; kept the current model.");
|
|
560
|
+
}
|
|
561
|
+
if (mode.thinkingLevel)
|
|
562
|
+
parts.push(`Thinking: ${mode.thinkingLevel}.`);
|
|
563
|
+
if (mode.tools.length > 0)
|
|
564
|
+
parts.push("Applied the profile tool set.");
|
|
565
|
+
return parts.join(" ") || "The profile changes the prompt only.";
|
|
566
|
+
}
|
|
567
|
+
async function restoreRuntime(pi, state, ctx) {
|
|
568
|
+
if (state.model) {
|
|
569
|
+
const model = ctx.modelRegistry.find(state.model.provider, state.model.id);
|
|
570
|
+
if (model)
|
|
571
|
+
await pi.setModel(model);
|
|
572
|
+
}
|
|
573
|
+
pi.setThinkingLevel(state.thinkingLevel);
|
|
574
|
+
pi.setActiveTools(state.tools);
|
|
575
|
+
}
|
|
576
|
+
function captureRuntime(pi, ctx) {
|
|
577
|
+
return {
|
|
578
|
+
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
579
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
580
|
+
tools: pi.getActiveTools()
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
async function loadSkillModes(skillsDir, source, modes, diagnostics) {
|
|
584
|
+
const entries = await readDirectoryIfExists(skillsDir);
|
|
585
|
+
for (const entry of entries.filter((item) => item.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
586
|
+
await loadAgentModes(join6(skillsDir, entry.name, "agents"), `${source} ${entry.name}`, modes, diagnostics, entry.name);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async function loadAgentModes(directory, source, modes, diagnostics, skillName) {
|
|
590
|
+
const entries = await readDirectoryIfExists(directory);
|
|
591
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
592
|
+
const path = join6(directory, entry.name);
|
|
593
|
+
try {
|
|
594
|
+
const content = await readFile4(path, "utf8");
|
|
595
|
+
const { frontmatter, body } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
596
|
+
if (frontmatter.enabled === false || frontmatter.inline === false)
|
|
597
|
+
continue;
|
|
598
|
+
const name = getFrontmatterText(frontmatter.name) ?? basename2(path, extname(path));
|
|
599
|
+
const systemPrompt = body.trim();
|
|
600
|
+
if (!name || name.includes(":") || !systemPrompt) {
|
|
601
|
+
diagnostics.push(`Skipped ${path}: agent name must not contain ":" and prompt body is required.`);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const id = skillName ? `${skillName}:${name}` : name;
|
|
605
|
+
modes.set(id, {
|
|
606
|
+
id,
|
|
607
|
+
label: getFrontmatterText(frontmatter.display_name) ?? name,
|
|
608
|
+
description: getFrontmatterText(frontmatter.description) ?? `Inline agent from ${basename2(path)}`,
|
|
609
|
+
systemPrompt,
|
|
610
|
+
promptStrategy: frontmatter.prompt_mode === "append" ? "append" : "replace",
|
|
611
|
+
modelPreferences: [
|
|
612
|
+
...getFrontmatterList(frontmatter.model),
|
|
613
|
+
...getFrontmatterList(frontmatter.model_fallbacks)
|
|
614
|
+
],
|
|
615
|
+
thinkingLevel: getThinkingLevel(frontmatter.thinking),
|
|
616
|
+
tools: getFrontmatterList(frontmatter.tools),
|
|
617
|
+
source,
|
|
618
|
+
sourcePath: path
|
|
619
|
+
});
|
|
620
|
+
} catch (error) {
|
|
621
|
+
diagnostics.push(`Skipped ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function getFrontmatterText(value) {
|
|
626
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
627
|
+
}
|
|
628
|
+
function getFrontmatterList(value) {
|
|
629
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
630
|
+
return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
631
|
+
}
|
|
632
|
+
function getThinkingLevel(value) {
|
|
633
|
+
const level = getFrontmatterText(value);
|
|
634
|
+
return level && THINKING_LEVELS.has(level) ? level : undefined;
|
|
635
|
+
}
|
|
636
|
+
function isAgentModeSnapshot(value) {
|
|
637
|
+
if (!value || typeof value !== "object")
|
|
638
|
+
return false;
|
|
639
|
+
const candidate = value;
|
|
640
|
+
return typeof candidate.id === "string" && typeof candidate.label === "string" && typeof candidate.description === "string" && typeof candidate.systemPrompt === "string" && (candidate.promptStrategy === "append" || candidate.promptStrategy === "replace") && Array.isArray(candidate.modelPreferences) && (candidate.thinkingLevel === undefined || THINKING_LEVELS.has(candidate.thinkingLevel)) && Array.isArray(candidate.tools) && typeof candidate.source === "string" && typeof candidate.sourcePath === "string";
|
|
641
|
+
}
|
|
642
|
+
function isModeBaseline(value) {
|
|
643
|
+
if (!value || typeof value !== "object")
|
|
644
|
+
return false;
|
|
645
|
+
const candidate = value;
|
|
646
|
+
const model = candidate.model;
|
|
647
|
+
return (model === undefined || typeof model.provider === "string" && typeof model.id === "string") && candidate.thinkingLevel !== undefined && THINKING_LEVELS.has(candidate.thinkingLevel) && Array.isArray(candidate.tools) && candidate.tools.every((tool) => typeof tool === "string");
|
|
648
|
+
}
|
|
649
|
+
// src/pi.ts
|
|
650
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
651
|
+
import { homedir as homedir5 } from "node:os";
|
|
652
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
653
|
+
var pi = {
|
|
654
|
+
executableCheck: findPiExecutable,
|
|
655
|
+
packageList: listPiPackages,
|
|
656
|
+
packageCheck: hasPiPackage,
|
|
657
|
+
packageInstall: installPiPackage,
|
|
658
|
+
agentDir: resolvePiAgentDir,
|
|
659
|
+
agentEnsure: ensurePiAgent,
|
|
660
|
+
skillCheckGlobal: checkGlobalPiSkill,
|
|
661
|
+
skillInstallGlobal: installGlobalPiSkills,
|
|
662
|
+
configEnsure: ensurePiConfig
|
|
346
663
|
};
|
|
347
|
-
function
|
|
348
|
-
|
|
664
|
+
async function ensurePiAgent(filename, content, agentDir = resolvePiAgentDir(), dryRun = false) {
|
|
665
|
+
const path = join7(agentDir, "agents", filename);
|
|
666
|
+
const currentText = await readTextIfExists(path);
|
|
667
|
+
const changed = currentText !== content;
|
|
668
|
+
if (changed && !dryRun) {
|
|
669
|
+
await mkdir3(dirname4(path), { recursive: true });
|
|
670
|
+
await writeFile3(path, content, "utf8");
|
|
671
|
+
}
|
|
672
|
+
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
349
673
|
}
|
|
350
|
-
async function
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
674
|
+
async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir = join7(homedir5(), ".agents", "skills")) {
|
|
675
|
+
const roots = [join7(agentDir, "skills"), sharedSkillsDir];
|
|
676
|
+
for (const root of roots) {
|
|
677
|
+
if (await readTextIfExists(join7(root, name, "SKILL.md")) !== undefined)
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
async function installGlobalPiSkills(miseExecutable, source, names) {
|
|
683
|
+
const selection = names.flatMap((name) => ["--skill", name]);
|
|
684
|
+
await runChecked(miseExecutable, [
|
|
685
|
+
"x",
|
|
686
|
+
"node@22",
|
|
687
|
+
"--",
|
|
688
|
+
"npx",
|
|
689
|
+
"-y",
|
|
690
|
+
"skills",
|
|
691
|
+
"add",
|
|
692
|
+
source,
|
|
693
|
+
...selection,
|
|
694
|
+
"--global",
|
|
695
|
+
"--agent",
|
|
696
|
+
"pi",
|
|
697
|
+
"--yes"
|
|
698
|
+
]);
|
|
699
|
+
}
|
|
700
|
+
async function ensurePiConfig(path, update, dryRun = false) {
|
|
701
|
+
const currentText = await readTextIfExists(path);
|
|
702
|
+
const current = parseJsonObject(currentText, path);
|
|
703
|
+
const next = update(current);
|
|
704
|
+
const changed = JSON.stringify(current) !== JSON.stringify(next);
|
|
705
|
+
if (changed && !dryRun) {
|
|
706
|
+
await mkdir3(dirname4(path), { recursive: true });
|
|
707
|
+
await writeFile3(path, `${JSON.stringify(next, null, 2)}
|
|
708
|
+
`, "utf8");
|
|
357
709
|
}
|
|
710
|
+
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
711
|
+
}
|
|
712
|
+
async function findPiExecutable() {
|
|
713
|
+
return findExecutable("pi");
|
|
714
|
+
}
|
|
715
|
+
async function listPiPackages(executable) {
|
|
716
|
+
return (await runChecked(executable, ["list"])).stdout;
|
|
717
|
+
}
|
|
718
|
+
function hasPiPackage(listOutput, source) {
|
|
719
|
+
if (listOutput.includes(source))
|
|
720
|
+
return true;
|
|
721
|
+
return source.startsWith("https://") && listOutput.includes(source.slice("https://".length));
|
|
358
722
|
}
|
|
359
|
-
function
|
|
723
|
+
async function installPiPackage(executable, source) {
|
|
724
|
+
await runChecked(executable, ["install", source]);
|
|
725
|
+
}
|
|
726
|
+
function resolvePiAgentDir(homeDir = homedir5()) {
|
|
727
|
+
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join7(process.env.XDG_CONFIG_HOME, "pi") : join7(homeDir, ".pi", "agent"));
|
|
728
|
+
}
|
|
729
|
+
function parseJsonObject(content, path) {
|
|
360
730
|
if (!content?.trim())
|
|
361
731
|
return {};
|
|
362
732
|
try {
|
|
@@ -367,8 +737,10 @@ function getParsedObject(content, path) {
|
|
|
367
737
|
throw new Error(`Expected valid JSON object in ${path}.`);
|
|
368
738
|
}
|
|
369
739
|
// src/setup.ts
|
|
370
|
-
import {
|
|
371
|
-
import {
|
|
740
|
+
import { parseFrontmatter as parseFrontmatter3 } from "@earendil-works/pi-coding-agent";
|
|
741
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
742
|
+
import { homedir as homedir6 } from "node:os";
|
|
743
|
+
import { basename as basename3, join as join8 } from "node:path";
|
|
372
744
|
var MISE_DEPENDENCIES = [
|
|
373
745
|
{ name: "node", tool: "node", spec: "node@22", minimumVersion: "22.19.0" },
|
|
374
746
|
{ name: "zellij", tool: "zellij", spec: "zellij@latest", minimumVersion: undefined },
|
|
@@ -399,9 +771,10 @@ var PI_SKILL_SOURCES = [
|
|
|
399
771
|
{ repository: "AminBlg/SimpleEnglish", skills: ["simple-english"] }
|
|
400
772
|
];
|
|
401
773
|
var MCP_ADAPTER_PACKAGE = "npm:pi-mcp-adapter";
|
|
774
|
+
var BUNDLED_AGENTS_DIR2 = resolveBundledAgentsDir();
|
|
402
775
|
async function ensureMise(options = {}) {
|
|
403
|
-
const homeDir = options.homeDir ??
|
|
404
|
-
const current = await mise.executableCheck() ?? await mise.executableCheck(
|
|
776
|
+
const homeDir = options.homeDir ?? homedir6();
|
|
777
|
+
const current = await mise.executableCheck() ?? await mise.executableCheck(join8(homeDir, ".local", "bin", "mise"));
|
|
405
778
|
if (current)
|
|
406
779
|
return { executable: current, action: createSetupAction("mise", "ready", current) };
|
|
407
780
|
reportProgress(options, "Installing mise");
|
|
@@ -446,18 +819,33 @@ async function ensureMiseDeps(miseExecutable, options = {}) {
|
|
|
446
819
|
async function ensurePiPlugins(options = {}) {
|
|
447
820
|
const actions = await ensurePiPackages(PI_PACKAGES, options);
|
|
448
821
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
449
|
-
const webSearch = await pi.configEnsure(
|
|
822
|
+
const webSearch = await pi.configEnsure(join8(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
|
|
450
823
|
actions.push(getConfigSetupAction("web search settings", webSearch));
|
|
451
|
-
const lsp = await pi.configEnsure(
|
|
824
|
+
const lsp = await pi.configEnsure(join8(agentDir, "pi-lsp.json"), (config) => ({
|
|
452
825
|
...config,
|
|
453
826
|
progressive: { ...getRecord(config.progressive), enabled: true, inject: "none" }
|
|
454
827
|
}), options.dryRun);
|
|
455
828
|
actions.push(getConfigSetupAction("pi-lsp settings", lsp));
|
|
456
829
|
return actions;
|
|
457
830
|
}
|
|
831
|
+
async function ensurePiAgents(options = {}) {
|
|
832
|
+
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
833
|
+
const bundledAgentsDir = options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR2;
|
|
834
|
+
const userConfig = await loadDiffpiConfig({ homeDir: options.homeDir });
|
|
835
|
+
const entries = (await readdir2(bundledAgentsDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.startsWith("diffpi-") && entry.name.endsWith(".md")).sort((left, right) => left.name.localeCompare(right.name));
|
|
836
|
+
const actions = [];
|
|
837
|
+
for (const entry of entries) {
|
|
838
|
+
const id = basename3(entry.name, ".md").replace(/^diffpi-/, "");
|
|
839
|
+
const source = await readFile5(join8(bundledAgentsDir, entry.name), "utf8");
|
|
840
|
+
const content = materializeAgentModels(source, id, userConfig.config, options.availableModels);
|
|
841
|
+
const result = await pi.agentEnsure(entry.name, content, agentDir, options.dryRun);
|
|
842
|
+
actions.push(getConfigSetupAction(`pi agent ${id}`, result));
|
|
843
|
+
}
|
|
844
|
+
return actions;
|
|
845
|
+
}
|
|
458
846
|
async function ensurePiSkills(miseExecutable, options = {}) {
|
|
459
847
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
460
|
-
const sharedSkillsDir =
|
|
848
|
+
const sharedSkillsDir = join8(options.homeDir ?? homedir6(), ".agents", "skills");
|
|
461
849
|
const actions = [];
|
|
462
850
|
for (const source of PI_SKILL_SOURCES) {
|
|
463
851
|
const missing = [];
|
|
@@ -514,13 +902,55 @@ async function setupPi(options = {}) {
|
|
|
514
902
|
actions.push(await ensureMiseHooks(miseResult.executable, options));
|
|
515
903
|
actions.push(...await ensureMiseDeps(miseResult.executable, options));
|
|
516
904
|
actions.push(...await ensurePiPlugins(options));
|
|
905
|
+
actions.push(...await ensurePiAgents(options));
|
|
517
906
|
actions.push(...await ensurePiSkills(miseResult.executable, options));
|
|
518
907
|
actions.push(...await ensureMcpAdapters(miseResult.executable, options));
|
|
519
908
|
return {
|
|
520
909
|
actions,
|
|
521
|
-
restartPi: actions
|
|
910
|
+
restartPi: setupRequiresRestart(actions)
|
|
522
911
|
};
|
|
523
912
|
}
|
|
913
|
+
function setupRequiresRestart(actions) {
|
|
914
|
+
return actions.some((item) => (item.status === "installed" || item.status === "updated") && (item.name.startsWith("pi package ") || item.name.startsWith("pi agent ") || item.name.startsWith("pi skill ") || item.name === "MCP configuration" || item.name === "web search settings" || item.name === "pi-lsp settings"));
|
|
915
|
+
}
|
|
916
|
+
function materializeAgentModels(content, agentId, config, availableModels) {
|
|
917
|
+
const { frontmatter } = parseFrontmatter3(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
918
|
+
const profilePreferences = [...getTextList(frontmatter.model), ...getTextList(frontmatter.model_fallbacks)];
|
|
919
|
+
const preferences = resolveAgentModelPreferences(agentId, profilePreferences, config);
|
|
920
|
+
let selectedIndex = availableModels === undefined && preferences.length > 0 ? 0 : -1;
|
|
921
|
+
let selectedModel = selectedIndex === 0 ? preferences[0] : undefined;
|
|
922
|
+
if (availableModels) {
|
|
923
|
+
for (const [index, preference] of preferences.entries()) {
|
|
924
|
+
const match = findPreferredModel(availableModels, preference);
|
|
925
|
+
if (!match)
|
|
926
|
+
continue;
|
|
927
|
+
selectedIndex = index;
|
|
928
|
+
selectedModel = `${match.provider}/${match.id}`;
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const fallbacks = preferences.filter((_preference, index) => index !== selectedIndex);
|
|
933
|
+
return replaceAgentModelFields(content, selectedModel, fallbacks);
|
|
934
|
+
}
|
|
935
|
+
function replaceAgentModelFields(content, model, fallbacks) {
|
|
936
|
+
const newline = content.includes(`\r
|
|
937
|
+
`) ? `\r
|
|
938
|
+
` : `
|
|
939
|
+
`;
|
|
940
|
+
const lines = content.replaceAll(`\r
|
|
941
|
+
`, `
|
|
942
|
+
`).split(`
|
|
943
|
+
`);
|
|
944
|
+
const closingDelimiter = lines.indexOf("---", 1);
|
|
945
|
+
if (lines[0] !== "---" || closingDelimiter < 0)
|
|
946
|
+
return content;
|
|
947
|
+
const frontmatter = lines.slice(1, closingDelimiter).filter((line) => !/^model(?:_fallbacks)?:/.test(line));
|
|
948
|
+
if (model)
|
|
949
|
+
frontmatter.push(`model: ${model}`);
|
|
950
|
+
if (fallbacks.length > 0)
|
|
951
|
+
frontmatter.push(`model_fallbacks: ${fallbacks.join(", ")}`);
|
|
952
|
+
return ["---", ...frontmatter, "---", ...lines.slice(closingDelimiter + 1)].join(newline);
|
|
953
|
+
}
|
|
524
954
|
async function ensurePiPackages(packages, options) {
|
|
525
955
|
const executable = await pi.executableCheck();
|
|
526
956
|
if (!executable && !options.dryRun)
|
|
@@ -542,6 +972,10 @@ ${source}`;
|
|
|
542
972
|
}
|
|
543
973
|
return actions;
|
|
544
974
|
}
|
|
975
|
+
function getTextList(value) {
|
|
976
|
+
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
977
|
+
return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
978
|
+
}
|
|
545
979
|
function getConfigSetupAction(name, result) {
|
|
546
980
|
if (!result.changed)
|
|
547
981
|
return createSetupAction(name, "ready", result.path);
|
|
@@ -559,14 +993,22 @@ function getRecord(value) {
|
|
|
559
993
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
560
994
|
}
|
|
561
995
|
export {
|
|
996
|
+
createModeController,
|
|
997
|
+
diffpiConfigPaths,
|
|
998
|
+
discoverAgentModes,
|
|
562
999
|
ensureMcpAdapters,
|
|
563
1000
|
ensureMise,
|
|
564
1001
|
ensureMiseDeps,
|
|
565
1002
|
ensureMiseHooks,
|
|
1003
|
+
ensurePiAgents,
|
|
566
1004
|
ensurePiPlugins,
|
|
567
1005
|
ensurePiSkills,
|
|
1006
|
+
findPreferredModel,
|
|
1007
|
+
loadDiffpiConfig,
|
|
568
1008
|
mcp,
|
|
569
1009
|
mise,
|
|
570
1010
|
pi,
|
|
1011
|
+
resolveAgentMode,
|
|
1012
|
+
resolveAgentModelPreferences,
|
|
571
1013
|
setupPi
|
|
572
1014
|
};
|