my-pi 0.1.131 → 0.2.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.
@@ -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 to_loaded_prompt_presets(presets, source) {
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
- if (!existsSync(path)) return {};
280
- try {
281
- const presets = {};
282
- for (const entry of readdirSync(path, { withFileTypes: true }).filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
283
- const name = entry.name.slice(0, -3).trim();
284
- if (!name) continue;
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 Object.assign({}, to_loaded_prompt_presets(DEFAULT_PROMPT_PRESETS, "builtin"), to_loaded_prompt_presets(read_prompt_presets_file(get_global_presets_path()), "user"), to_loaded_prompt_presets(read_prompt_presets_dir(get_global_presets_dir()), "user"), ...should_load_project_prompt_presets() ? [to_loaded_prompt_presets(read_prompt_presets_file(get_project_presets_path(cwd)), "project"), to_loaded_prompt_presets(read_prompt_presets_dir(get_project_presets_dir(cwd)), "project")] : []);
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, on_change, on_edit) {
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
- const result = await show_modal(ctx, {
481
- title: "Prompt preset inspector",
482
- subtitle: () => `base: ${state.active_base_name ?? "(none)"} • ${state.active_layers.size} layer(s) currently active`,
483
- footer: "↑↓ navigate • space toggle/select • tab preview • e edit project • E edit global • enter apply • esc cancel",
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: "92%",
486
- minWidth: 72,
487
- maxHeight: "88%"
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 = 1;
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.skip_header(1);
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 (data === "e" || data === "E") {
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
- if (row?.type === "preset") this.done({
588
- action: "edit",
589
- name: row.preset.name,
590
- scope: data === "E" ? "global" : "project"
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(void 0);
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 lines = [this.theme.fg("accent", `Draft selection: ${this.selected_base ?? "(none)"} + ${this.enabled_layers.size} layer(s)`), ""];
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" ? "Effective prompt contribution" : "Selected preset";
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
- `# ${preset.name}`,
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
- await show_prompt_preset_manager(ctx, {
824
- presets,
825
- active_base_name,
826
- active_layers
827
- }, (selected_base, enabled_layers) => {
828
- commit_state(ctx, selected_base, enabled_layers, { notify: "Updated prompt preset selection" });
829
- }, async (name, scope) => {
830
- await edit_preset(name, ctx, scope);
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-Bje3FkjZ.js.map
1402
+ //# sourceMappingURL=prompt-presets-XraV03Ed.js.map