my-pi 0.1.131 → 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/dist/api.js +1 -1
- package/dist/{builtin-registry-D9SIZIC0.js → builtin-registry-D0Y9F9lN.js} +2 -2
- package/dist/{builtin-registry-D9SIZIC0.js.map → builtin-registry-D0Y9F9lN.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/{prompt-presets-Bje3FkjZ.js → prompt-presets-XraV03Ed.js} +484 -84
- package/dist/prompt-presets-XraV03Ed.js.map +1 -0
- package/package.json +7 -7
- package/dist/prompt-presets-Bje3FkjZ.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { read_settings, write_settings } from "@spences10/pi-settings";
|
|
4
|
+
import { get_settings_path, read_settings, write_settings } from "@spences10/pi-settings";
|
|
5
5
|
import { show_modal } from "@spences10/pi-tui-modal";
|
|
6
6
|
import "@spences10/pi-footer";
|
|
7
7
|
import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
@@ -192,16 +192,6 @@ Examples:
|
|
|
192
192
|
Alias: /preset`;
|
|
193
193
|
}
|
|
194
194
|
//#endregion
|
|
195
|
-
//#region src/extensions/prompt-presets/prompt-blocks.ts
|
|
196
|
-
function build_active_prompt_blocks(presets, active_base_name, active_layers) {
|
|
197
|
-
const blocks = [];
|
|
198
|
-
const base = active_base_name ? presets[active_base_name] : void 0;
|
|
199
|
-
if (base?.instructions.trim()) blocks.push(`## Active Base Prompt: ${base.name}\n${base.instructions.trim()}`);
|
|
200
|
-
const layer_blocks = [...active_layers].sort().map((name) => presets[name]).filter((preset) => Boolean(preset?.instructions.trim())).map((preset) => `### ${preset.name}\n${preset.instructions.trim()}`);
|
|
201
|
-
if (layer_blocks.length > 0) blocks.push(`## Active Prompt Layers\n\n${layer_blocks.join("\n\n")}`);
|
|
202
|
-
return blocks;
|
|
203
|
-
}
|
|
204
|
-
//#endregion
|
|
205
195
|
//#region src/extensions/prompt-presets/storage.ts
|
|
206
196
|
const PROJECT_PROMPT_PRESETS_ENV = "MY_PI_PROMPT_PRESETS_PROJECT";
|
|
207
197
|
function normalize_prompt_presets(input) {
|
|
@@ -228,14 +218,72 @@ function normalize_prompt_presets(input) {
|
|
|
228
218
|
}
|
|
229
219
|
return normalized;
|
|
230
220
|
}
|
|
231
|
-
function
|
|
221
|
+
function to_prompt_preset_definitions(presets, source, origin, path, editable) {
|
|
232
222
|
return Object.fromEntries(Object.entries(presets).map(([name, preset]) => [name, {
|
|
233
223
|
name,
|
|
234
224
|
kind: preset.kind === "layer" ? "layer" : "base",
|
|
235
225
|
source,
|
|
226
|
+
origin,
|
|
227
|
+
path,
|
|
228
|
+
editable,
|
|
236
229
|
...preset
|
|
237
230
|
}]));
|
|
238
231
|
}
|
|
232
|
+
function read_prompt_preset_markdown_definitions(dir, source, origin) {
|
|
233
|
+
const definitions = {};
|
|
234
|
+
const diagnostics = [];
|
|
235
|
+
if (!existsSync(dir)) return {
|
|
236
|
+
definitions,
|
|
237
|
+
diagnostics
|
|
238
|
+
};
|
|
239
|
+
let entries;
|
|
240
|
+
try {
|
|
241
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
242
|
+
} catch (error) {
|
|
243
|
+
diagnostics.push({
|
|
244
|
+
code: "invalid-source",
|
|
245
|
+
message: `Could not read prompt preset directory: ${String(error)}`,
|
|
246
|
+
path: dir,
|
|
247
|
+
origin
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
definitions,
|
|
251
|
+
diagnostics
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
255
|
+
const name = entry.name.slice(0, -3).trim();
|
|
256
|
+
const path = join(dir, entry.name);
|
|
257
|
+
if (!name) continue;
|
|
258
|
+
try {
|
|
259
|
+
const { metadata, body } = parse_prompt_preset_markdown(readFileSync(path, "utf-8"));
|
|
260
|
+
if (metadata.kind !== void 0 && metadata.kind !== "base" && metadata.kind !== "layer") throw new Error(`kind must be "base" or "layer", received ${JSON.stringify(metadata.kind)}`);
|
|
261
|
+
if (!body) throw new Error("instructions body is empty");
|
|
262
|
+
definitions[name] = {
|
|
263
|
+
name,
|
|
264
|
+
kind: metadata.kind === "layer" ? "layer" : "base",
|
|
265
|
+
instructions: body,
|
|
266
|
+
source,
|
|
267
|
+
origin,
|
|
268
|
+
path,
|
|
269
|
+
editable: true,
|
|
270
|
+
...typeof metadata.description === "string" && metadata.description.trim() ? { description: metadata.description } : {}
|
|
271
|
+
};
|
|
272
|
+
} catch (error) {
|
|
273
|
+
diagnostics.push({
|
|
274
|
+
code: "invalid-source",
|
|
275
|
+
name,
|
|
276
|
+
message: `Could not read prompt preset Markdown: ${String(error)}`,
|
|
277
|
+
path,
|
|
278
|
+
origin
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
definitions,
|
|
284
|
+
diagnostics
|
|
285
|
+
};
|
|
286
|
+
}
|
|
239
287
|
function get_global_presets_path() {
|
|
240
288
|
return join(getAgentDir(), "presets.json");
|
|
241
289
|
}
|
|
@@ -268,6 +316,47 @@ function read_prompt_presets_file(path) {
|
|
|
268
316
|
return {};
|
|
269
317
|
}
|
|
270
318
|
}
|
|
319
|
+
function read_global_prompt_presets_source() {
|
|
320
|
+
const path = get_settings_path();
|
|
321
|
+
try {
|
|
322
|
+
return {
|
|
323
|
+
presets: normalize_prompt_presets(read_settings().promptPresets?.global ?? {}),
|
|
324
|
+
diagnostics: []
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return {
|
|
328
|
+
presets: {},
|
|
329
|
+
diagnostics: [{
|
|
330
|
+
code: "invalid-source",
|
|
331
|
+
message: `Could not read legacy global prompt presets: ${String(error)}`,
|
|
332
|
+
path,
|
|
333
|
+
origin: "legacy-global-settings"
|
|
334
|
+
}]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function read_prompt_presets_json_source(path, origin) {
|
|
339
|
+
if (!existsSync(path)) return {
|
|
340
|
+
presets: {},
|
|
341
|
+
diagnostics: []
|
|
342
|
+
};
|
|
343
|
+
try {
|
|
344
|
+
return {
|
|
345
|
+
presets: normalize_prompt_presets(JSON.parse(readFileSync(path, "utf-8"))),
|
|
346
|
+
diagnostics: []
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
return {
|
|
350
|
+
presets: {},
|
|
351
|
+
diagnostics: [{
|
|
352
|
+
code: "invalid-source",
|
|
353
|
+
message: `Could not read prompt preset JSON: ${String(error)}`,
|
|
354
|
+
path,
|
|
355
|
+
origin
|
|
356
|
+
}]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
271
360
|
function parse_prompt_preset_markdown(content) {
|
|
272
361
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
273
362
|
return {
|
|
@@ -276,24 +365,12 @@ function parse_prompt_preset_markdown(content) {
|
|
|
276
365
|
};
|
|
277
366
|
}
|
|
278
367
|
function read_prompt_presets_dir(path) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const { metadata, body } = parse_prompt_preset_markdown(readFileSync(join(path, entry.name), "utf-8"));
|
|
286
|
-
if (!body) continue;
|
|
287
|
-
presets[name] = {
|
|
288
|
-
kind: metadata.kind === "layer" ? "layer" : "base",
|
|
289
|
-
instructions: body,
|
|
290
|
-
...typeof metadata.description === "string" && metadata.description.trim() ? { description: metadata.description } : {}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
return presets;
|
|
294
|
-
} catch {
|
|
295
|
-
return {};
|
|
296
|
-
}
|
|
368
|
+
const { definitions } = read_prompt_preset_markdown_definitions(path, "user", "global-markdown");
|
|
369
|
+
return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, {
|
|
370
|
+
kind: definition.kind,
|
|
371
|
+
instructions: definition.instructions,
|
|
372
|
+
...definition.description ? { description: definition.description } : {}
|
|
373
|
+
}]));
|
|
297
374
|
}
|
|
298
375
|
function format_prompt_preset_markdown(preset) {
|
|
299
376
|
const lines = ["---", `kind: ${preset.kind === "layer" ? "layer" : "base"}`];
|
|
@@ -328,8 +405,51 @@ function should_load_project_prompt_presets() {
|
|
|
328
405
|
"disable"
|
|
329
406
|
].includes(normalized ?? "");
|
|
330
407
|
}
|
|
408
|
+
function load_prompt_preset_catalog(cwd) {
|
|
409
|
+
const legacy_global = read_global_prompt_presets_source();
|
|
410
|
+
const global_markdown = read_prompt_preset_markdown_definitions(get_global_presets_dir(), "user", "global-markdown");
|
|
411
|
+
const project_json = read_prompt_presets_json_source(get_project_presets_path(cwd), "project-json");
|
|
412
|
+
const project_markdown = read_prompt_preset_markdown_definitions(get_project_presets_dir(cwd), "project", "project-markdown");
|
|
413
|
+
const source_diagnostics = [...legacy_global.diagnostics, ...global_markdown.diagnostics];
|
|
414
|
+
const sources = [
|
|
415
|
+
to_prompt_preset_definitions(DEFAULT_PROMPT_PRESETS, "builtin", "builtin", null, false),
|
|
416
|
+
to_prompt_preset_definitions(legacy_global.presets, "user", "legacy-global-settings", get_settings_path(), false),
|
|
417
|
+
global_markdown.definitions
|
|
418
|
+
];
|
|
419
|
+
if (should_load_project_prompt_presets()) {
|
|
420
|
+
source_diagnostics.push(...project_json.diagnostics, ...project_markdown.diagnostics);
|
|
421
|
+
sources.push(to_prompt_preset_definitions(project_json.presets, "project", "project-json", get_project_presets_path(cwd), false), project_markdown.definitions);
|
|
422
|
+
}
|
|
423
|
+
const chains = /* @__PURE__ */ new Map();
|
|
424
|
+
for (const source of sources) for (const definition of Object.values(source)) {
|
|
425
|
+
const chain = chains.get(definition.name) ?? [];
|
|
426
|
+
chain.push(definition);
|
|
427
|
+
chains.set(definition.name, chain);
|
|
428
|
+
}
|
|
429
|
+
const diagnostics = [...source_diagnostics];
|
|
430
|
+
const presets = {};
|
|
431
|
+
for (const [name, chain] of chains) {
|
|
432
|
+
const winner = chain.at(-1);
|
|
433
|
+
const fallbacks = chain.slice(0, -1).reverse();
|
|
434
|
+
presets[name] = {
|
|
435
|
+
...winner,
|
|
436
|
+
fallbacks
|
|
437
|
+
};
|
|
438
|
+
for (const shadowed of fallbacks) diagnostics.push({
|
|
439
|
+
code: "shadowed",
|
|
440
|
+
name,
|
|
441
|
+
message: `${shadowed.origin} definition is shadowed by ${winner.origin}`,
|
|
442
|
+
path: shadowed.path,
|
|
443
|
+
origin: shadowed.origin
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
presets,
|
|
448
|
+
diagnostics
|
|
449
|
+
};
|
|
450
|
+
}
|
|
331
451
|
function load_prompt_presets(cwd) {
|
|
332
|
-
return
|
|
452
|
+
return load_prompt_preset_catalog(cwd).presets;
|
|
333
453
|
}
|
|
334
454
|
function sort_prompt_presets(presets) {
|
|
335
455
|
return Object.fromEntries(Object.entries(presets).sort(([a], [b]) => a.localeCompare(b)));
|
|
@@ -436,6 +556,203 @@ function save_persisted_prompt_state(cwd, state, path = get_persisted_prompt_sta
|
|
|
436
556
|
return path;
|
|
437
557
|
}
|
|
438
558
|
//#endregion
|
|
559
|
+
//#region src/extensions/prompt-presets/library.ts
|
|
560
|
+
var PromptPresetLibraryError = class extends Error {
|
|
561
|
+
code;
|
|
562
|
+
details;
|
|
563
|
+
constructor(code, message, details = {}) {
|
|
564
|
+
super(message);
|
|
565
|
+
this.code = code;
|
|
566
|
+
this.details = details;
|
|
567
|
+
this.name = "PromptPresetLibraryError";
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
const PORTABLE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
571
|
+
const WINDOWS_RESERVED_BASENAME = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
|
|
572
|
+
function validate_scope(scope) {
|
|
573
|
+
if (scope !== "global" && scope !== "project") throw new PromptPresetLibraryError("invalid-scope", `Invalid prompt preset scope: ${scope}`, { scope });
|
|
574
|
+
}
|
|
575
|
+
function validate_kind(kind) {
|
|
576
|
+
if (kind !== "base" && kind !== "layer") throw new PromptPresetLibraryError("invalid-kind", `Invalid prompt preset kind: ${kind}`, { kind });
|
|
577
|
+
}
|
|
578
|
+
function validate_prompt_preset_name(name) {
|
|
579
|
+
if (name !== name.trim() || !PORTABLE_NAME_PATTERN.test(name) || name.endsWith(".") || WINDOWS_RESERVED_BASENAME.test(name)) throw new PromptPresetLibraryError("invalid-name", `Prompt preset name is not portable: ${JSON.stringify(name)}`, { name });
|
|
580
|
+
const path = get_prompt_preset_file_path("/presets", name);
|
|
581
|
+
if (basename(path) !== `${name}.md`) throw new PromptPresetLibraryError("invalid-name", `Prompt preset name cannot round-trip to Markdown: ${name}`, { name });
|
|
582
|
+
return name;
|
|
583
|
+
}
|
|
584
|
+
function get_scope_dir(cwd, scope) {
|
|
585
|
+
validate_scope(scope);
|
|
586
|
+
if (scope === "project" && [
|
|
587
|
+
"0",
|
|
588
|
+
"false",
|
|
589
|
+
"no",
|
|
590
|
+
"skip",
|
|
591
|
+
"disable"
|
|
592
|
+
].includes(process.env.MY_PI_PROMPT_PRESETS_PROJECT?.trim().toLowerCase() ?? "")) throw new PromptPresetLibraryError("project-disabled", "Project prompt presets are disabled for this repository", { scope });
|
|
593
|
+
return scope === "global" ? get_global_presets_dir() : get_project_presets_dir(cwd);
|
|
594
|
+
}
|
|
595
|
+
function get_scope_origin(scope) {
|
|
596
|
+
return scope === "global" ? "global-markdown" : "project-markdown";
|
|
597
|
+
}
|
|
598
|
+
function assert_portable_path_available(dir, name, ignore_path) {
|
|
599
|
+
const path = get_prompt_preset_file_path(dir, name);
|
|
600
|
+
if (existsSync(dir)) {
|
|
601
|
+
const collision = readdirSync(dir).find((entry) => {
|
|
602
|
+
return join(dir, entry) !== ignore_path && entry.toLocaleLowerCase("en-US") === `${name}.md`.toLocaleLowerCase("en-US");
|
|
603
|
+
});
|
|
604
|
+
if (collision) throw new PromptPresetLibraryError("collision", `Prompt preset filename collides with ${collision}`, {
|
|
605
|
+
name,
|
|
606
|
+
path: join(dir, collision)
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return path;
|
|
610
|
+
}
|
|
611
|
+
function find_scope_definition(cwd, scope, name) {
|
|
612
|
+
const origin = get_scope_origin(scope);
|
|
613
|
+
const loaded = load_prompt_preset_catalog(cwd).presets[name];
|
|
614
|
+
if (loaded?.origin === origin) return loaded;
|
|
615
|
+
return loaded?.fallbacks.find((item) => item.origin === origin);
|
|
616
|
+
}
|
|
617
|
+
function normalize_preset(preset) {
|
|
618
|
+
const kind = preset.kind ?? "base";
|
|
619
|
+
validate_kind(kind);
|
|
620
|
+
if (!preset.instructions.trim()) throw new PromptPresetLibraryError("invalid-instructions", "Prompt preset instructions cannot be empty");
|
|
621
|
+
return {
|
|
622
|
+
kind,
|
|
623
|
+
instructions: preset.instructions,
|
|
624
|
+
...preset.description?.trim() ? { description: preset.description.trim() } : {}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
function reload_prompt_preset_library(cwd) {
|
|
628
|
+
return load_prompt_preset_catalog(cwd);
|
|
629
|
+
}
|
|
630
|
+
function create_prompt_preset(cwd, scope, name, preset) {
|
|
631
|
+
validate_scope(scope);
|
|
632
|
+
validate_prompt_preset_name(name);
|
|
633
|
+
const dir = get_scope_dir(cwd, scope);
|
|
634
|
+
const path = assert_portable_path_available(dir, name);
|
|
635
|
+
if (existsSync(path)) throw new PromptPresetLibraryError("collision", `Preset already exists: ${name}`, {
|
|
636
|
+
name,
|
|
637
|
+
path
|
|
638
|
+
});
|
|
639
|
+
save_prompt_preset_file(dir, name, normalize_preset(preset));
|
|
640
|
+
const catalog = reload_prompt_preset_library(cwd);
|
|
641
|
+
return {
|
|
642
|
+
preset: catalog.presets[name],
|
|
643
|
+
catalog,
|
|
644
|
+
path
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function copy_prompt_preset(cwd, name, source_scope, target_scope, target_name = name) {
|
|
648
|
+
validate_prompt_preset_name(name);
|
|
649
|
+
validate_prompt_preset_name(target_name);
|
|
650
|
+
if (source_scope !== "builtin") {
|
|
651
|
+
validate_scope(source_scope);
|
|
652
|
+
get_scope_dir(cwd, source_scope);
|
|
653
|
+
}
|
|
654
|
+
validate_scope(target_scope);
|
|
655
|
+
const loaded = load_prompt_preset_catalog(cwd).presets[name];
|
|
656
|
+
const source = (loaded ? [loaded, ...loaded.fallbacks] : []).find((definition) => {
|
|
657
|
+
if (source_scope === "builtin") return definition.origin === "builtin";
|
|
658
|
+
return source_scope === "global" ? ["global-markdown", "legacy-global-settings"].includes(definition.origin) : ["project-markdown", "project-json"].includes(definition.origin);
|
|
659
|
+
});
|
|
660
|
+
if (!source) throw new PromptPresetLibraryError(loaded ? "source-mismatch" : "not-found", loaded ? `Preset ${name} has no ${source_scope} definition` : `Preset not found: ${name}`, {
|
|
661
|
+
name,
|
|
662
|
+
source_scope
|
|
663
|
+
});
|
|
664
|
+
return create_prompt_preset(cwd, target_scope, target_name, source);
|
|
665
|
+
}
|
|
666
|
+
function require_custom_scope_definition(cwd, scope, name) {
|
|
667
|
+
validate_prompt_preset_name(name);
|
|
668
|
+
get_scope_dir(cwd, scope);
|
|
669
|
+
const definition = find_scope_definition(cwd, scope, name);
|
|
670
|
+
if (!definition?.path) {
|
|
671
|
+
const winner = load_prompt_preset_catalog(cwd).presets[name];
|
|
672
|
+
throw new PromptPresetLibraryError(!winner ? "not-found" : winner.origin === "builtin" ? "read-only" : "source-mismatch", winner?.origin === "builtin" ? `Built-in preset is read-only: ${name}` : winner ? `Preset ${name} is owned by ${winner.origin}, not ${scope} Markdown` : `No ${scope} Markdown preset found: ${name}`, {
|
|
673
|
+
name,
|
|
674
|
+
scope,
|
|
675
|
+
origin: winner?.origin
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return definition;
|
|
679
|
+
}
|
|
680
|
+
function rename_state(state, old_name, new_name, kind) {
|
|
681
|
+
if (!state) return void 0;
|
|
682
|
+
return {
|
|
683
|
+
base_name: kind === "base" && state.base_name === old_name ? new_name : state.base_name,
|
|
684
|
+
layer_names: kind === "layer" ? [...new Set(state.layer_names.map((name) => name === old_name ? new_name : name))].sort() : state.layer_names
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function rename_prompt_preset(cwd, scope, old_name, new_name, active_state) {
|
|
688
|
+
const definition = require_custom_scope_definition(cwd, scope, old_name);
|
|
689
|
+
validate_prompt_preset_name(new_name);
|
|
690
|
+
const dir = get_scope_dir(cwd, scope);
|
|
691
|
+
const old_path = definition.path;
|
|
692
|
+
const new_path = assert_portable_path_available(dir, new_name, old_path);
|
|
693
|
+
if (existsSync(new_path) && new_path !== old_path) throw new PromptPresetLibraryError("collision", `Preset already exists: ${new_name}`);
|
|
694
|
+
const next_persisted = rename_state(load_persisted_prompt_state(cwd), old_name, new_name, definition.kind);
|
|
695
|
+
const next_active = rename_state(active_state, old_name, new_name, definition.kind);
|
|
696
|
+
renameSync(old_path, new_path);
|
|
697
|
+
try {
|
|
698
|
+
if (next_persisted) save_persisted_prompt_state(cwd, next_persisted);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
renameSync(new_path, old_path);
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
const catalog = reload_prompt_preset_library(cwd);
|
|
704
|
+
return {
|
|
705
|
+
preset: catalog.presets[new_name],
|
|
706
|
+
catalog,
|
|
707
|
+
path: new_path,
|
|
708
|
+
...next_active ? { state: next_active } : {}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function get_delete_prompt_preset_confirmation(cwd, scope, name, state = load_persisted_prompt_state(cwd)) {
|
|
712
|
+
const definition = require_custom_scope_definition(cwd, scope, name);
|
|
713
|
+
const winner = load_prompt_preset_catalog(cwd).presets[name];
|
|
714
|
+
const fallback = winner?.origin === get_scope_origin(scope) ? winner.fallbacks[0] : winner;
|
|
715
|
+
return {
|
|
716
|
+
name,
|
|
717
|
+
scope,
|
|
718
|
+
path: definition.path,
|
|
719
|
+
kind: definition.kind,
|
|
720
|
+
fallback,
|
|
721
|
+
active: state?.base_name === name || state?.layer_names.includes(name) === true
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function delete_prompt_preset(cwd, scope, name, confirmed = false) {
|
|
725
|
+
const confirmation = get_delete_prompt_preset_confirmation(cwd, scope, name);
|
|
726
|
+
if (!confirmed) throw new PromptPresetLibraryError("confirmation-required", `Confirmation required to delete ${name}`, { confirmation });
|
|
727
|
+
unlinkSync(confirmation.path);
|
|
728
|
+
const catalog = reload_prompt_preset_library(cwd);
|
|
729
|
+
return {
|
|
730
|
+
preset: catalog.presets[name],
|
|
731
|
+
catalog,
|
|
732
|
+
path: confirmation.path
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function reset_prompt_preset_override(cwd, scope, name) {
|
|
736
|
+
const definition = require_custom_scope_definition(cwd, scope, name);
|
|
737
|
+
unlinkSync(definition.path);
|
|
738
|
+
const catalog = reload_prompt_preset_library(cwd);
|
|
739
|
+
return {
|
|
740
|
+
preset: catalog.presets[name],
|
|
741
|
+
catalog,
|
|
742
|
+
path: definition.path
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/extensions/prompt-presets/prompt-blocks.ts
|
|
747
|
+
function build_active_prompt_blocks(presets, active_base_name, active_layers) {
|
|
748
|
+
const blocks = [];
|
|
749
|
+
const base = active_base_name ? presets[active_base_name] : void 0;
|
|
750
|
+
if (base?.instructions.trim()) blocks.push(`## Active Base Prompt: ${base.name}\n${base.instructions.trim()}`);
|
|
751
|
+
const layer_blocks = [...active_layers].sort().map((name) => presets[name]).filter((preset) => Boolean(preset?.instructions.trim())).map((preset) => `### ${preset.name}\n${preset.instructions.trim()}`);
|
|
752
|
+
if (layer_blocks.length > 0) blocks.push(`## Active Prompt Layers\n\n${layer_blocks.join("\n\n")}`);
|
|
753
|
+
return blocks;
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
439
756
|
//#region src/extensions/prompt-presets/state.ts
|
|
440
757
|
const PRESET_STATE_TYPE = "prompt-preset-state";
|
|
441
758
|
const NONE_BASE_ID = "__base_none__";
|
|
@@ -446,11 +763,6 @@ function get_last_preset_state(ctx) {
|
|
|
446
763
|
if (entry.type === "custom" && entry.customType === "prompt-preset-state" && entry.data) return entry.data;
|
|
447
764
|
}
|
|
448
765
|
}
|
|
449
|
-
function sets_equal(a, b) {
|
|
450
|
-
if (a.size !== b.size) return false;
|
|
451
|
-
for (const value of a) if (!b.has(value)) return false;
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
766
|
function persist_state(pi, ctx, active_base_name, active_layers) {
|
|
455
767
|
const state = {
|
|
456
768
|
base_name: active_base_name ?? null,
|
|
@@ -470,29 +782,23 @@ function parse_preset_flag(flag) {
|
|
|
470
782
|
}
|
|
471
783
|
//#endregion
|
|
472
784
|
//#region src/extensions/prompt-presets/manager.ts
|
|
473
|
-
async function show_prompt_preset_manager(ctx, state
|
|
785
|
+
async function show_prompt_preset_manager(ctx, state) {
|
|
474
786
|
const base_presets = list_base_presets(state.presets);
|
|
475
787
|
const layer_presets = list_layer_presets(state.presets);
|
|
476
788
|
if (base_presets.length === 0 && layer_presets.length === 0) {
|
|
477
789
|
ctx.ui.notify("No prompt presets available", "warning");
|
|
478
|
-
return;
|
|
790
|
+
return { action: "cancel" };
|
|
479
791
|
}
|
|
480
|
-
|
|
481
|
-
title: "Prompt
|
|
482
|
-
subtitle:
|
|
483
|
-
footer: "↑↓
|
|
792
|
+
return await show_modal(ctx, {
|
|
793
|
+
title: "Prompt presets",
|
|
794
|
+
subtitle: "Choose one base preset, then add optional layers.",
|
|
795
|
+
footer: "↑↓ move • space select • enter Apply • esc Cancel",
|
|
484
796
|
overlay_options: {
|
|
485
|
-
width: "
|
|
486
|
-
minWidth:
|
|
487
|
-
maxHeight: "
|
|
797
|
+
width: "94%",
|
|
798
|
+
minWidth: 44,
|
|
799
|
+
maxHeight: "92%"
|
|
488
800
|
}
|
|
489
|
-
}, ({ done }, theme, layout) => new PromptPresetInspectorBody(state, theme, () => layout.get_max_body_lines(), done));
|
|
490
|
-
if (!result) return;
|
|
491
|
-
if (result.action === "edit") {
|
|
492
|
-
await on_edit?.(result.name, result.scope);
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (result.base_name !== state.active_base_name || !sets_equal(new Set(state.active_layers), result.layers)) on_change(result.base_name, result.layers);
|
|
801
|
+
}, ({ done }, theme, layout) => new PromptPresetInspectorBody(state, theme, () => layout.get_max_body_lines(), done)) ?? { action: "cancel" };
|
|
496
802
|
}
|
|
497
803
|
var PromptPresetInspectorBody = class {
|
|
498
804
|
state;
|
|
@@ -500,7 +806,7 @@ var PromptPresetInspectorBody = class {
|
|
|
500
806
|
get_max_lines;
|
|
501
807
|
done;
|
|
502
808
|
rows;
|
|
503
|
-
selected_index
|
|
809
|
+
selected_index;
|
|
504
810
|
selected_base;
|
|
505
811
|
enabled_layers;
|
|
506
812
|
preview_mode = "item";
|
|
@@ -542,7 +848,7 @@ var PromptPresetInspectorBody = class {
|
|
|
542
848
|
];
|
|
543
849
|
this.selected_base = state.active_base_name;
|
|
544
850
|
this.enabled_layers = new Set(state.active_layers);
|
|
545
|
-
this.
|
|
851
|
+
this.selected_index = this.get_initial_selected_index();
|
|
546
852
|
}
|
|
547
853
|
render(width) {
|
|
548
854
|
const body_lines = Math.max(8, this.get_max_lines());
|
|
@@ -582,14 +888,24 @@ var PromptPresetInspectorBody = class {
|
|
|
582
888
|
base_name: this.selected_base,
|
|
583
889
|
layers: new Set(this.enabled_layers)
|
|
584
890
|
});
|
|
585
|
-
else if (
|
|
891
|
+
else if ("neyrdxl".includes(data)) {
|
|
892
|
+
const actions = {
|
|
893
|
+
n: "create",
|
|
894
|
+
e: "edit",
|
|
895
|
+
y: "copy",
|
|
896
|
+
r: "rename",
|
|
897
|
+
d: "delete",
|
|
898
|
+
x: "reset",
|
|
899
|
+
l: "reload"
|
|
900
|
+
};
|
|
586
901
|
const row = this.rows[this.selected_index];
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
902
|
+
const action = actions[data];
|
|
903
|
+
if (action === "create" || action === "reload") this.done({ action });
|
|
904
|
+
else if (row?.type === "preset") this.done({
|
|
905
|
+
action,
|
|
906
|
+
preset: row.preset
|
|
591
907
|
});
|
|
592
|
-
} else if (matchesKey(data, Key.escape) || data === "q") this.done(
|
|
908
|
+
} else if (matchesKey(data, Key.escape) || data === "q") this.done({ action: "cancel" });
|
|
593
909
|
else if (data === "J") this.scroll_preview(1);
|
|
594
910
|
else if (data === "K") this.scroll_preview(-1);
|
|
595
911
|
}
|
|
@@ -602,7 +918,12 @@ var PromptPresetInspectorBody = class {
|
|
|
602
918
|
];
|
|
603
919
|
}
|
|
604
920
|
render_list(width, max_lines) {
|
|
605
|
-
const
|
|
921
|
+
const action_lines = wrapTextWithAnsi("N Create E Edit Y Copy R Rename D Delete X Reset L Reload", Math.max(20, width)).map((line) => this.theme.fg("dim", line));
|
|
922
|
+
const lines = [
|
|
923
|
+
this.theme.fg("accent", `Draft: ${this.selected_base ?? "(none)"} + ${this.enabled_layers.size} layer(s)`),
|
|
924
|
+
...action_lines,
|
|
925
|
+
""
|
|
926
|
+
];
|
|
606
927
|
const visible_rows = Math.max(1, max_lines - lines.length);
|
|
607
928
|
const start = Math.max(0, Math.min(this.selected_index - Math.floor(visible_rows / 2), this.rows.length - visible_rows));
|
|
608
929
|
const end = Math.min(start + visible_rows, this.rows.length);
|
|
@@ -610,7 +931,7 @@ var PromptPresetInspectorBody = class {
|
|
|
610
931
|
return lines.map((line) => truncateToWidth(line, width));
|
|
611
932
|
}
|
|
612
933
|
render_preview(width, max_lines) {
|
|
613
|
-
const heading = this.preview_mode === "effective" ? "
|
|
934
|
+
const heading = this.preview_mode === "effective" ? "Full prompt contribution" : this.get_selected_heading();
|
|
614
935
|
const text = this.preview_mode === "effective" ? this.get_effective_prompt_text() : this.get_selected_prompt_text();
|
|
615
936
|
const content_width = Math.max(10, width);
|
|
616
937
|
this.preview_lines = text.split("\n").flatMap((line) => line.trim().length === 0 ? [""] : wrapTextWithAnsi(line, content_width));
|
|
@@ -645,12 +966,8 @@ var PromptPresetInspectorBody = class {
|
|
|
645
966
|
if (row.type === "base-none") return "No active base preset. Only enabled prompt layers will be appended.";
|
|
646
967
|
const preset = row.preset;
|
|
647
968
|
return [
|
|
648
|
-
|
|
649
|
-
"",
|
|
650
|
-
`Kind: ${preset.kind}`,
|
|
651
|
-
`Source: ${get_prompt_source_label(preset.source)}`,
|
|
652
|
-
preset.description ? `Description: ${preset.description}` : void 0,
|
|
653
|
-
"",
|
|
969
|
+
preset.description || void 0,
|
|
970
|
+
preset.description ? "" : void 0,
|
|
654
971
|
preset.instructions.trim() || "(empty preset)"
|
|
655
972
|
].filter((line) => line !== void 0).join("\n");
|
|
656
973
|
}
|
|
@@ -658,6 +975,19 @@ var PromptPresetInspectorBody = class {
|
|
|
658
975
|
const blocks = build_active_prompt_blocks(this.state.presets, this.selected_base, this.enabled_layers);
|
|
659
976
|
return blocks.length > 0 ? blocks.join("\n\n") : "(No prompt preset text will be appended.)";
|
|
660
977
|
}
|
|
978
|
+
get_selected_heading() {
|
|
979
|
+
const row = this.rows[this.selected_index];
|
|
980
|
+
if (!row || row.type === "header") return "Highlighted preset";
|
|
981
|
+
return row.type === "base-none" ? "No base preset" : row.preset.name;
|
|
982
|
+
}
|
|
983
|
+
get_initial_selected_index() {
|
|
984
|
+
if (this.selected_base) {
|
|
985
|
+
const base_index = this.rows.findIndex((row) => row.type === "preset" && row.id === this.selected_base);
|
|
986
|
+
if (base_index >= 0) return base_index;
|
|
987
|
+
}
|
|
988
|
+
const active_layer_index = this.rows.findIndex((row) => row.type === "preset" && this.enabled_layers.has(row.id));
|
|
989
|
+
return active_layer_index >= 0 ? active_layer_index : 1;
|
|
990
|
+
}
|
|
661
991
|
toggle_selected() {
|
|
662
992
|
const row = this.rows[this.selected_index];
|
|
663
993
|
if (!row || row.type === "header") return;
|
|
@@ -676,10 +1006,6 @@ var PromptPresetInspectorBody = class {
|
|
|
676
1006
|
this.selected_index = next;
|
|
677
1007
|
this.preview_offset = 0;
|
|
678
1008
|
}
|
|
679
|
-
skip_header(fallback) {
|
|
680
|
-
if (this.rows[this.selected_index]?.type !== "header") return;
|
|
681
|
-
this.selected_index = fallback;
|
|
682
|
-
}
|
|
683
1009
|
scroll_preview(delta) {
|
|
684
1010
|
const max_offset = Math.max(0, this.preview_lines.length - 1);
|
|
685
1011
|
this.preview_offset = Math.max(0, Math.min(this.preview_offset + delta, max_offset));
|
|
@@ -819,16 +1145,90 @@ async function prompt_presets(pi) {
|
|
|
819
1145
|
}
|
|
820
1146
|
ctx.ui.notify(result.remaining === 0 ? `Removed "${name}" and deleted ${result.path}` : `Removed "${name}" from ${result.path}`, "info");
|
|
821
1147
|
}
|
|
1148
|
+
async function choose_scope(ctx, action) {
|
|
1149
|
+
const choice = await ctx.ui.select(`${action}: choose where to write`, ["Project (.pi/presets)", "Global (~/.pi/agent/presets)"]);
|
|
1150
|
+
return choice?.startsWith("Project") ? "project" : choice ? "global" : void 0;
|
|
1151
|
+
}
|
|
1152
|
+
function refresh_after_management(ctx) {
|
|
1153
|
+
presets = load_prompt_presets(ctx.cwd);
|
|
1154
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1155
|
+
active_base_name = normalized.active_base_name;
|
|
1156
|
+
active_layers = normalized.active_layers;
|
|
1157
|
+
set_status(ctx, active_base_name, active_layers);
|
|
1158
|
+
persist_state(pi, ctx, active_base_name, active_layers);
|
|
1159
|
+
}
|
|
822
1160
|
async function show_manager(ctx) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1161
|
+
while (true) {
|
|
1162
|
+
const result = await show_prompt_preset_manager(ctx, {
|
|
1163
|
+
presets,
|
|
1164
|
+
active_base_name,
|
|
1165
|
+
active_layers
|
|
1166
|
+
});
|
|
1167
|
+
if (result.action === "cancel") return;
|
|
1168
|
+
if (result.action === "apply") {
|
|
1169
|
+
commit_state(ctx, result.base_name, result.layers, { notify: "Updated prompt preset selection" });
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
if (result.action === "reload") {
|
|
1174
|
+
const catalog = reload_prompt_preset_library(ctx.cwd);
|
|
1175
|
+
presets = catalog.presets;
|
|
1176
|
+
refresh_after_management(ctx);
|
|
1177
|
+
const diagnostics = catalog.diagnostics;
|
|
1178
|
+
ctx.ui.notify(diagnostics.length ? `Reloaded with ${diagnostics.length} validation issue(s):\n${diagnostics.map((item) => `${item.path}: ${item.message}`).join("\n")}` : "Reloaded prompt presets", diagnostics.length ? "warning" : "info");
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
const selected = result.preset;
|
|
1182
|
+
if (result.action === "create") {
|
|
1183
|
+
const name = await ctx.ui.input("Create preset", "Preset name");
|
|
1184
|
+
if (!name) continue;
|
|
1185
|
+
const scope = await choose_scope(ctx, "Create preset");
|
|
1186
|
+
if (!scope) continue;
|
|
1187
|
+
await edit_preset(name.trim(), ctx, scope);
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (!selected) continue;
|
|
1191
|
+
if (result.action === "edit" && !selected.editable) {
|
|
1192
|
+
ctx.ui.notify("Built-in presets are read-only. Copy it before editing.", "warning");
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
const scope = await choose_scope(ctx, `${result.action} ${selected.name}`);
|
|
1196
|
+
if (!scope) continue;
|
|
1197
|
+
if (result.action === "edit") await edit_preset(selected.name, ctx, scope);
|
|
1198
|
+
else if (result.action === "copy") {
|
|
1199
|
+
const target = await ctx.ui.input("Copy preset", selected.name);
|
|
1200
|
+
if (!target) continue;
|
|
1201
|
+
copy_prompt_preset(ctx.cwd, selected.name, selected.origin === "builtin" ? "builtin" : selected.origin.startsWith("project") ? "project" : "global", scope, target.trim());
|
|
1202
|
+
} else if (result.action === "rename") {
|
|
1203
|
+
if (!selected.editable) {
|
|
1204
|
+
ctx.ui.notify("Built-in presets are read-only. Copy it before renaming.", "warning");
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
const target = await ctx.ui.input("Rename preset", selected.name);
|
|
1208
|
+
if (!target) continue;
|
|
1209
|
+
const mutation = rename_prompt_preset(ctx.cwd, scope, selected.name, target.trim(), {
|
|
1210
|
+
base_name: active_base_name ?? null,
|
|
1211
|
+
layer_names: [...active_layers]
|
|
1212
|
+
});
|
|
1213
|
+
if (mutation.state) {
|
|
1214
|
+
active_base_name = mutation.state.base_name ?? void 0;
|
|
1215
|
+
active_layers = new Set(mutation.state.layer_names);
|
|
1216
|
+
}
|
|
1217
|
+
} else {
|
|
1218
|
+
if (!selected.editable) {
|
|
1219
|
+
ctx.ui.notify("Built-in presets are read-only.", "warning");
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (!await ctx.ui.confirm(result.action === "delete" ? "Delete preset?" : "Reset override?", `${selected.name} (${scope})`)) continue;
|
|
1223
|
+
if (result.action === "delete") delete_prompt_preset(ctx.cwd, scope, selected.name, true);
|
|
1224
|
+
else reset_prompt_preset_override(ctx.cwd, scope, selected.name);
|
|
1225
|
+
}
|
|
1226
|
+
refresh_after_management(ctx);
|
|
1227
|
+
ctx.ui.notify(`${result.action} completed for "${selected.name}"`, "info");
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
832
1232
|
}
|
|
833
1233
|
pi.registerFlag("preset", {
|
|
834
1234
|
description: "Activate prompt config on startup. Accepts a base preset or comma-separated preset/layer names.",
|
|
@@ -840,7 +1240,7 @@ async function prompt_presets(pi) {
|
|
|
840
1240
|
handler: async (args, ctx) => {
|
|
841
1241
|
const trimmed = args.trim();
|
|
842
1242
|
if (!trimmed) {
|
|
843
|
-
if (ctx.hasUI) {
|
|
1243
|
+
if (ctx.hasUI && ctx.mode === "tui") {
|
|
844
1244
|
await show_manager(ctx);
|
|
845
1245
|
return;
|
|
846
1246
|
}
|
|
@@ -999,4 +1399,4 @@ async function prompt_presets(pi) {
|
|
|
999
1399
|
//#endregion
|
|
1000
1400
|
export { prompt_presets as default };
|
|
1001
1401
|
|
|
1002
|
-
//# sourceMappingURL=prompt-presets-
|
|
1402
|
+
//# sourceMappingURL=prompt-presets-XraV03Ed.js.map
|