my-pi 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { InteractiveMode as InteractiveMode$1, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, defineTool, getAgentDir, parseFrontmatter, runPrintMode as runPrintMode$1 } from "@mariozechner/pi-coding-agent";
2
- import { cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Type } from "@sinclair/typebox";
@@ -388,6 +388,17 @@ const BUILTIN_EXTENSIONS = [
388
388
  description: "Past session recall guidance and /recall command",
389
389
  cli_flag: "--no-recall",
390
390
  aliases: ["recall"]
391
+ },
392
+ {
393
+ key: "prompt-presets",
394
+ label: "Prompt presets",
395
+ description: "Runtime prompt preset selection and /preset command",
396
+ cli_flag: "--no-prompt-presets",
397
+ aliases: [
398
+ "prompt-presets",
399
+ "preset",
400
+ "presets"
401
+ ]
391
402
  }
392
403
  ];
393
404
  function get_builtin_extensions_config_path() {
@@ -452,8 +463,8 @@ function find_builtin_extension(query) {
452
463
  }
453
464
  //#endregion
454
465
  //#region src/extensions/extensions.ts
455
- const ENABLED$1 = "[x]";
456
- const DISABLED$1 = "[ ]";
466
+ const ENABLED$2 = "[x]";
467
+ const DISABLED$2 = "[ ]";
457
468
  function to_force_disabled_set(force_disabled) {
458
469
  return new Set(force_disabled ?? []);
459
470
  }
@@ -469,7 +480,7 @@ function format_extension_lines(states, options) {
469
480
  const disabled_now = states.length - enabled_now;
470
481
  lines.push(`${states.length} built-in extensions (${enabled_now} enabled now, ${disabled_now} disabled now)`, "");
471
482
  for (const state of states) {
472
- lines.push(`${state.saved_enabled ? ENABLED$1 : DISABLED$1} ${state.label}`);
483
+ lines.push(`${state.saved_enabled ? ENABLED$2 : DISABLED$2} ${state.label}`);
473
484
  lines.push(` key: ${state.key}`);
474
485
  lines.push(` saved config: ${state.saved_enabled ? "enabled" : "disabled"}`);
475
486
  lines.push(` current process: ${format_effective_state(state)}`);
@@ -488,11 +499,11 @@ function to_setting_item$1(state) {
488
499
  id: state.key,
489
500
  label: state.label,
490
501
  description: detail_lines.join("\n"),
491
- currentValue: state.saved_enabled ? ENABLED$1 : DISABLED$1,
492
- values: [ENABLED$1, DISABLED$1]
502
+ currentValue: state.saved_enabled ? ENABLED$2 : DISABLED$2,
503
+ values: [ENABLED$2, DISABLED$2]
493
504
  };
494
505
  }
495
- function sets_equal$1(a, b) {
506
+ function sets_equal$2(a, b) {
496
507
  if (a.size !== b.size) return false;
497
508
  for (const value of a) if (!b.has(value)) return false;
498
509
  return true;
@@ -538,7 +549,7 @@ function create_extensions_extension(options = {}) {
538
549
  const q = parts.slice(1).join(" ").toLowerCase();
539
550
  return resolve_builtin_extension_states(force_disabled).filter((state) => state.key.toLowerCase().includes(q) || state.label.toLowerCase().includes(q)).slice(0, 20).map((state) => ({
540
551
  value: `${parts[0]} ${state.key}`,
541
- label: `${state.key} ${state.saved_enabled ? ENABLED$1 : DISABLED$1}`
552
+ label: `${state.key} ${state.saved_enabled ? ENABLED$2 : DISABLED$2}`
542
553
  }));
543
554
  }
544
555
  return null;
@@ -570,7 +581,7 @@ function create_extensions_extension(options = {}) {
570
581
  cursor: theme.fg("accent", "›"),
571
582
  label: (text, selected) => selected ? theme.fg("accent", text) : text,
572
583
  value: (text, selected) => {
573
- const color = text === ENABLED$1 ? "success" : "dim";
584
+ const color = text === ENABLED$2 ? "success" : "dim";
574
585
  const rendered = theme.fg(color, text);
575
586
  return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
576
587
  },
@@ -578,7 +589,7 @@ function create_extensions_extension(options = {}) {
578
589
  hint: (text) => theme.fg("dim", text)
579
590
  }, (id, new_value) => {
580
591
  const key = id;
581
- const enabled = new_value === ENABLED$1;
592
+ const enabled = new_value === ENABLED$2;
582
593
  if (enabled) current_enabled.add(key);
583
594
  else current_enabled.delete(key);
584
595
  save_extension_enabled(key, enabled);
@@ -598,7 +609,7 @@ function create_extensions_extension(options = {}) {
598
609
  }
599
610
  };
600
611
  });
601
- if (!sets_equal$1(initial_enabled, current_enabled)) {
612
+ if (!sets_equal$2(initial_enabled, current_enabled)) {
602
613
  ctx.ui.notify(force_disabled.size > 0 ? "Reloading to apply updated built-in extensions. CLI --no-* flags still force-disable some extensions in this process." : "Reloading to apply updated built-in extensions...", "info");
603
614
  await ctx.reload();
604
615
  return;
@@ -1051,6 +1062,649 @@ async function mcp(pi) {
1051
1062
  });
1052
1063
  }
1053
1064
  //#endregion
1065
+ //#region src/extensions/prompt-presets.ts
1066
+ const PRESET_STATE_TYPE = "prompt-preset-state";
1067
+ const ENABLED$1 = "[x]";
1068
+ const DISABLED$1 = "[ ]";
1069
+ const SELECTED = "(x)";
1070
+ const UNSELECTED = "( )";
1071
+ const NONE_BASE_ID = "__base_none__";
1072
+ const DEFAULT_PROMPT_PRESETS = {
1073
+ terse: {
1074
+ kind: "base",
1075
+ description: "Short, direct, no fluff",
1076
+ instructions: "Be concise and direct. Default to the shortest response that fully solves the user's request. No purple prose, no filler, no repetitive caveats. Prefer a short paragraph or a few bullets. Only include extra detail when it materially affects the decision, implementation, or next step."
1077
+ },
1078
+ standard: {
1079
+ kind: "base",
1080
+ description: "Clear and concise with key context",
1081
+ instructions: "Be clear, direct, and concise. Include only the reasoning and implementation details that matter. Avoid filler, grandstanding, and ornamental language. Use bullets when they improve scanability."
1082
+ },
1083
+ detailed: {
1084
+ kind: "base",
1085
+ description: "More explanation when nuance matters",
1086
+ instructions: "Be thorough when the task is complex or tradeoffs matter, but stay practical. Explain only the details that help the user decide, verify, or implement. Avoid purple prose and unnecessary scene-setting."
1087
+ },
1088
+ "no-purple-prose": {
1089
+ kind: "layer",
1090
+ description: "Strip out ornamental language",
1091
+ instructions: "Do not use purple prose, flourish, motivational filler, or theatrical transitions. Prefer plain language and concrete statements."
1092
+ },
1093
+ bullets: {
1094
+ kind: "layer",
1095
+ description: "Prefer short bullets when useful",
1096
+ instructions: "When presenting options, findings, or steps, prefer short bullet lists over long paragraphs."
1097
+ },
1098
+ "clarify-first": {
1099
+ kind: "layer",
1100
+ description: "Ask brief clarifying questions when requirements are ambiguous",
1101
+ instructions: "If the request is materially ambiguous, ask the minimum clarifying question(s) needed before proceeding. Do not ask unnecessary questions."
1102
+ },
1103
+ "include-risks": {
1104
+ kind: "layer",
1105
+ description: "Call out notable risks or tradeoffs",
1106
+ instructions: "When making a recommendation or implementation plan, briefly mention the key risk, tradeoff, or caveat if one materially matters."
1107
+ }
1108
+ };
1109
+ function normalize_prompt_presets(input) {
1110
+ if (!input || typeof input !== "object") return {};
1111
+ const normalized = {};
1112
+ for (const [raw_name, raw_value] of Object.entries(input)) {
1113
+ const name = raw_name.trim();
1114
+ if (!name) continue;
1115
+ if (typeof raw_value === "string") {
1116
+ normalized[name] = {
1117
+ kind: "base",
1118
+ instructions: raw_value
1119
+ };
1120
+ continue;
1121
+ }
1122
+ if (!raw_value || typeof raw_value !== "object") continue;
1123
+ const candidate = raw_value;
1124
+ if (typeof candidate.instructions !== "string") continue;
1125
+ normalized[name] = {
1126
+ instructions: candidate.instructions,
1127
+ ...candidate.kind === "layer" ? { kind: "layer" } : {},
1128
+ ...typeof candidate.description === "string" ? { description: candidate.description } : {}
1129
+ };
1130
+ }
1131
+ return normalized;
1132
+ }
1133
+ function to_loaded_prompt_presets(presets, source) {
1134
+ return Object.fromEntries(Object.entries(presets).map(([name, preset]) => [name, {
1135
+ name,
1136
+ kind: preset.kind === "layer" ? "layer" : "base",
1137
+ source,
1138
+ ...preset
1139
+ }]));
1140
+ }
1141
+ function get_global_presets_path() {
1142
+ return join(getAgentDir(), "presets.json");
1143
+ }
1144
+ function get_project_presets_path(cwd) {
1145
+ return join(cwd, ".pi", "presets.json");
1146
+ }
1147
+ function read_prompt_presets_file(path) {
1148
+ if (!existsSync(path)) return {};
1149
+ try {
1150
+ return normalize_prompt_presets(JSON.parse(readFileSync(path, "utf-8")));
1151
+ } catch {
1152
+ return {};
1153
+ }
1154
+ }
1155
+ function load_prompt_presets(cwd) {
1156
+ 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_file(get_project_presets_path(cwd)), "project"));
1157
+ }
1158
+ function sort_prompt_presets(presets) {
1159
+ return Object.fromEntries(Object.entries(presets).sort(([a], [b]) => a.localeCompare(b)));
1160
+ }
1161
+ function save_project_prompt_presets(cwd, presets) {
1162
+ const path = get_project_presets_path(cwd);
1163
+ const dir = dirname(path);
1164
+ if (!existsSync(dir)) mkdirSync(dir, {
1165
+ recursive: true,
1166
+ mode: 448
1167
+ });
1168
+ const tmp = `${path}.tmp-${Date.now()}`;
1169
+ writeFileSync(tmp, JSON.stringify(sort_prompt_presets(presets), null, " ") + "\n", { mode: 384 });
1170
+ renameSync(tmp, path);
1171
+ return path;
1172
+ }
1173
+ function remove_project_prompt_preset(cwd, name) {
1174
+ const path = get_project_presets_path(cwd);
1175
+ const project_presets = read_prompt_presets_file(path);
1176
+ if (!(name in project_presets)) return {
1177
+ removed: false,
1178
+ path,
1179
+ remaining: Object.keys(project_presets).length
1180
+ };
1181
+ delete project_presets[name];
1182
+ const remaining = Object.keys(project_presets).length;
1183
+ if (remaining === 0) {
1184
+ if (existsSync(path)) unlinkSync(path);
1185
+ return {
1186
+ removed: true,
1187
+ path,
1188
+ remaining
1189
+ };
1190
+ }
1191
+ save_project_prompt_presets(cwd, project_presets);
1192
+ return {
1193
+ removed: true,
1194
+ path,
1195
+ remaining
1196
+ };
1197
+ }
1198
+ function get_last_preset_state(ctx) {
1199
+ const entries = ctx.sessionManager.getEntries();
1200
+ for (let i = entries.length - 1; i >= 0; i--) {
1201
+ const entry = entries[i];
1202
+ if (entry.type === "custom" && entry.customType === PRESET_STATE_TYPE && entry.data) return entry.data;
1203
+ }
1204
+ }
1205
+ function sets_equal$1(a, b) {
1206
+ if (a.size !== b.size) return false;
1207
+ for (const value of a) if (!b.has(value)) return false;
1208
+ return true;
1209
+ }
1210
+ function get_prompt_source_label(source) {
1211
+ switch (source) {
1212
+ case "builtin": return "built-in";
1213
+ case "user": return "user";
1214
+ case "project": return "project";
1215
+ }
1216
+ }
1217
+ function list_base_presets(presets) {
1218
+ return Object.values(presets).filter((preset) => preset.kind === "base").sort((a, b) => a.name.localeCompare(b.name));
1219
+ }
1220
+ function list_layer_presets(presets) {
1221
+ return Object.values(presets).filter((preset) => preset.kind === "layer").sort((a, b) => a.name.localeCompare(b.name));
1222
+ }
1223
+ function format_summary(active_base_name, active_layers, presets) {
1224
+ const lines = [`Base: ${active_base_name ?? "(none)"}`];
1225
+ const layer_names = [...active_layers].sort();
1226
+ if (layer_names.length === 0) lines.push("Layers: (none)");
1227
+ else {
1228
+ lines.push("Layers:");
1229
+ for (const name of layer_names) {
1230
+ const preset = presets[name];
1231
+ const description = preset?.description ? ` — ${preset.description}` : "";
1232
+ lines.push(`- ${name}${description}`);
1233
+ }
1234
+ }
1235
+ return lines.join("\n");
1236
+ }
1237
+ function format_active_details(active_base_name, active_layers, presets) {
1238
+ const parts = [];
1239
+ if (active_base_name) {
1240
+ const base = presets[active_base_name];
1241
+ if (base) {
1242
+ parts.push(`Base: ${base.name}`);
1243
+ if (base.description) parts.push(`Description: ${base.description}`);
1244
+ parts.push(`Source: ${get_prompt_source_label(base.source)}`);
1245
+ parts.push("", base.instructions.trim());
1246
+ }
1247
+ }
1248
+ const layer_names = [...active_layers].sort();
1249
+ if (layer_names.length > 0) {
1250
+ if (parts.length > 0) parts.push("", "---", "");
1251
+ parts.push("Layers:");
1252
+ for (const name of layer_names) {
1253
+ const layer = presets[name];
1254
+ if (!layer) continue;
1255
+ parts.push(`- ${layer.name} (${get_prompt_source_label(layer.source)})`);
1256
+ if (layer.description) parts.push(` ${layer.description}`);
1257
+ }
1258
+ }
1259
+ return parts.join("\n") || "No preset or layers active";
1260
+ }
1261
+ function set_status(ctx, active_base_name, active_layers) {
1262
+ const label = active_base_name ?? "none";
1263
+ const layer_suffix = active_layers.size > 0 ? ` +${active_layers.size}` : "";
1264
+ ctx.ui.setStatus("preset", `prompt:${label}${layer_suffix}`);
1265
+ }
1266
+ function persist_state(pi, active_base_name, active_layers) {
1267
+ pi.appendEntry(PRESET_STATE_TYPE, {
1268
+ base_name: active_base_name ?? null,
1269
+ layer_names: [...active_layers].sort()
1270
+ });
1271
+ }
1272
+ function normalize_active_state(presets, active_base_name, active_layers) {
1273
+ return {
1274
+ active_base_name: active_base_name && presets[active_base_name]?.kind === "base" ? active_base_name : void 0,
1275
+ active_layers: new Set([...active_layers].filter((name) => presets[name]?.kind === "layer"))
1276
+ };
1277
+ }
1278
+ function parse_preset_flag(flag) {
1279
+ return flag.split(",").map((item) => item.trim()).filter(Boolean);
1280
+ }
1281
+ function is_subcommand(command) {
1282
+ return [
1283
+ "list",
1284
+ "show",
1285
+ "clear",
1286
+ "edit",
1287
+ "delete",
1288
+ "reset",
1289
+ "reload",
1290
+ "base",
1291
+ "enable",
1292
+ "disable",
1293
+ "toggle"
1294
+ ].includes(command);
1295
+ }
1296
+ async function prompt_presets(pi) {
1297
+ let presets = {};
1298
+ let active_base_name;
1299
+ let active_layers = /* @__PURE__ */ new Set();
1300
+ function get_base(name) {
1301
+ return name ? presets[name] : void 0;
1302
+ }
1303
+ function get_layer(name) {
1304
+ const preset = presets[name];
1305
+ return preset?.kind === "layer" ? preset : void 0;
1306
+ }
1307
+ function commit_state(ctx, next_base_name, next_layers, options) {
1308
+ active_base_name = next_base_name;
1309
+ active_layers = new Set(next_layers);
1310
+ set_status(ctx, active_base_name, active_layers);
1311
+ if (options?.persist !== false) persist_state(pi, active_base_name, active_layers);
1312
+ if (options?.notify) ctx.ui.notify(options.notify, "info");
1313
+ }
1314
+ function activate_base(name, ctx, options) {
1315
+ if (!name) {
1316
+ commit_state(ctx, void 0, active_layers, {
1317
+ persist: options?.persist,
1318
+ notify: "Base preset cleared"
1319
+ });
1320
+ return true;
1321
+ }
1322
+ const preset = get_base(name);
1323
+ if (!preset) {
1324
+ ctx.ui.notify(`Unknown base preset: ${name}`, "warning");
1325
+ return false;
1326
+ }
1327
+ commit_state(ctx, preset.name, active_layers, {
1328
+ persist: options?.persist,
1329
+ notify: `Base preset "${preset.name}" activated`
1330
+ });
1331
+ return true;
1332
+ }
1333
+ function set_layer_enabled(name, enabled, ctx, options) {
1334
+ const preset = get_layer(name);
1335
+ if (!preset) {
1336
+ ctx.ui.notify(`Unknown prompt layer: ${name}`, "warning");
1337
+ return false;
1338
+ }
1339
+ const next_layers = new Set(active_layers);
1340
+ if (enabled) next_layers.add(preset.name);
1341
+ else next_layers.delete(preset.name);
1342
+ commit_state(ctx, active_base_name, next_layers, {
1343
+ persist: options?.persist,
1344
+ notify: enabled ? `Layer "${preset.name}" enabled` : `Layer "${preset.name}" disabled`
1345
+ });
1346
+ return true;
1347
+ }
1348
+ function toggle_layer(name, ctx, options) {
1349
+ return set_layer_enabled(name, !active_layers.has(name), ctx, options);
1350
+ }
1351
+ async function edit_preset(name, ctx) {
1352
+ const existing = presets[name];
1353
+ const kind_choice = await ctx.ui.select("Preset kind", [existing?.kind === "layer" ? "layer (current)" : "base (current)", existing?.kind === "layer" ? "base" : "layer"]);
1354
+ if (!kind_choice) return;
1355
+ const kind = kind_choice.startsWith("layer") ? "layer" : "base";
1356
+ const description = await ctx.ui.input(`Description for ${name}`, existing?.description ?? "");
1357
+ if (description === void 0) return;
1358
+ const instructions = await ctx.ui.editor(`Edit ${kind} preset: ${name}`, existing?.instructions ?? "");
1359
+ if (instructions === void 0) return;
1360
+ save_project_prompt_presets(ctx.cwd, {
1361
+ ...read_prompt_presets_file(get_project_presets_path(ctx.cwd)),
1362
+ [name]: {
1363
+ kind,
1364
+ instructions,
1365
+ ...description.trim() ? { description: description.trim() } : {}
1366
+ }
1367
+ });
1368
+ presets = load_prompt_presets(ctx.cwd);
1369
+ const normalized = normalize_active_state(presets, active_base_name, active_layers);
1370
+ active_base_name = normalized.active_base_name;
1371
+ active_layers = normalized.active_layers;
1372
+ if (kind === "base") activate_base(name, ctx);
1373
+ else set_layer_enabled(name, true, ctx);
1374
+ ctx.ui.notify(`Saved preset "${name}" to ${get_project_presets_path(ctx.cwd)}`, "info");
1375
+ }
1376
+ function remove_custom_preset(name, ctx, mode) {
1377
+ const result = remove_project_prompt_preset(ctx.cwd, name);
1378
+ if (!result.removed) {
1379
+ ctx.ui.notify(`No project-local preset named "${name}" to ${mode}`, "warning");
1380
+ return;
1381
+ }
1382
+ presets = load_prompt_presets(ctx.cwd);
1383
+ const normalized = normalize_active_state(presets, active_base_name, active_layers);
1384
+ active_base_name = normalized.active_base_name;
1385
+ active_layers = normalized.active_layers;
1386
+ set_status(ctx, active_base_name, active_layers);
1387
+ persist_state(pi, active_base_name, active_layers);
1388
+ const fallback = presets[name];
1389
+ if (mode === "reset" && fallback) {
1390
+ ctx.ui.notify(`Reset "${name}" to ${get_prompt_source_label(fallback.source)} preset`, "info");
1391
+ return;
1392
+ }
1393
+ ctx.ui.notify(result.remaining === 0 ? `Removed "${name}" and deleted ${result.path}` : `Removed "${name}" from ${result.path}`, "info");
1394
+ }
1395
+ async function show_manager(ctx) {
1396
+ const base_presets = list_base_presets(presets);
1397
+ const layer_presets = list_layer_presets(presets);
1398
+ if (base_presets.length === 0 && layer_presets.length === 0) {
1399
+ ctx.ui.notify("No prompt presets available", "warning");
1400
+ return;
1401
+ }
1402
+ const initial_base = active_base_name;
1403
+ const initial_layers = new Set(active_layers);
1404
+ let selected_base = active_base_name;
1405
+ const enabled_layers = new Set(active_layers);
1406
+ const items = [];
1407
+ const base_ids = /* @__PURE__ */ new Set();
1408
+ const layer_ids = /* @__PURE__ */ new Set();
1409
+ items.push({
1410
+ id: "__header_base__",
1411
+ label: `── Base presets (${base_presets.length + 1}) ──`,
1412
+ description: "",
1413
+ currentValue: ""
1414
+ });
1415
+ items.push({
1416
+ id: NONE_BASE_ID,
1417
+ label: "(none)",
1418
+ description: "No active base preset",
1419
+ currentValue: UNSELECTED,
1420
+ values: [SELECTED, UNSELECTED]
1421
+ });
1422
+ base_ids.add(NONE_BASE_ID);
1423
+ for (const preset of base_presets) {
1424
+ items.push({
1425
+ id: preset.name,
1426
+ label: preset.name,
1427
+ description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "base preset"}`].join("\n"),
1428
+ currentValue: UNSELECTED,
1429
+ values: [SELECTED, UNSELECTED]
1430
+ });
1431
+ base_ids.add(preset.name);
1432
+ }
1433
+ items.push({
1434
+ id: "__header_layers__",
1435
+ label: `── Prompt layers (${layer_presets.length}) ──`,
1436
+ description: "",
1437
+ currentValue: ""
1438
+ });
1439
+ for (const preset of layer_presets) {
1440
+ items.push({
1441
+ id: preset.name,
1442
+ label: preset.name,
1443
+ description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "layer"}`].join("\n"),
1444
+ currentValue: DISABLED$1,
1445
+ values: [ENABLED$1, DISABLED$1]
1446
+ });
1447
+ layer_ids.add(preset.name);
1448
+ }
1449
+ function sync_values() {
1450
+ for (const item of items) if (base_ids.has(item.id)) item.currentValue = item.id === NONE_BASE_ID && !selected_base || item.id === selected_base ? SELECTED : UNSELECTED;
1451
+ else if (layer_ids.has(item.id)) item.currentValue = enabled_layers.has(item.id) ? ENABLED$1 : DISABLED$1;
1452
+ }
1453
+ sync_values();
1454
+ await ctx.ui.custom((tui, theme, _kb, done) => {
1455
+ const list = new SettingsList(items, Math.min(Math.max(items.length + 4, 8), 24), {
1456
+ cursor: theme.fg("accent", "›"),
1457
+ label: (text, selected) => {
1458
+ if (text.startsWith("──") && text.endsWith("──")) return theme.fg("dim", theme.bold(text));
1459
+ return selected ? theme.fg("accent", text) : text;
1460
+ },
1461
+ value: (text, selected) => {
1462
+ const color = text === ENABLED$1 || text === SELECTED ? "success" : "dim";
1463
+ const rendered = theme.fg(color, text);
1464
+ return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
1465
+ },
1466
+ description: (text) => theme.fg("muted", text),
1467
+ hint: (text) => theme.fg("dim", text)
1468
+ }, (id, new_value) => {
1469
+ if (id.startsWith("__header_")) return;
1470
+ if (base_ids.has(id)) {
1471
+ selected_base = new_value === SELECTED && id !== NONE_BASE_ID ? id : void 0;
1472
+ sync_values();
1473
+ return;
1474
+ }
1475
+ if (layer_ids.has(id)) {
1476
+ if (new_value === ENABLED$1) enabled_layers.add(id);
1477
+ else enabled_layers.delete(id);
1478
+ sync_values();
1479
+ }
1480
+ }, () => done(void 0), { enableSearch: true });
1481
+ const container = new Container();
1482
+ container.addChild({
1483
+ render: () => [
1484
+ theme.fg("accent", theme.bold("Prompt presets")),
1485
+ theme.fg("muted", `base: ${selected_base ?? "(none)"} • ${enabled_layers.size} layer(s) enabled`),
1486
+ ""
1487
+ ],
1488
+ invalidate: () => {}
1489
+ });
1490
+ container.addChild({
1491
+ render(width) {
1492
+ return list.render(width);
1493
+ },
1494
+ invalidate() {
1495
+ list.invalidate();
1496
+ }
1497
+ });
1498
+ container.addChild(new Text(theme.fg("dim", "search filters • enter toggles • esc close"), 0, 1));
1499
+ return {
1500
+ render(width) {
1501
+ return container.render(width);
1502
+ },
1503
+ invalidate() {
1504
+ container.invalidate();
1505
+ },
1506
+ handleInput(data) {
1507
+ list.handleInput(data);
1508
+ tui.requestRender();
1509
+ }
1510
+ };
1511
+ });
1512
+ if (selected_base !== initial_base || !sets_equal$1(initial_layers, enabled_layers)) commit_state(ctx, selected_base, enabled_layers, { notify: "Updated prompt preset selection" });
1513
+ }
1514
+ pi.registerFlag("preset", {
1515
+ description: "Activate prompt config on startup. Accepts a base preset or comma-separated preset/layer names.",
1516
+ type: "string"
1517
+ });
1518
+ pi.registerCommand("preset", {
1519
+ description: "Manage base prompt presets and prompt layers",
1520
+ getArgumentCompletions: (prefix) => {
1521
+ const trimmed = prefix.trim();
1522
+ const parts = trimmed ? trimmed.split(/\s+/) : [];
1523
+ const base_names = list_base_presets(presets).map((preset) => preset.name);
1524
+ const layer_names = list_layer_presets(presets).map((preset) => preset.name);
1525
+ const all_names = [...base_names, ...layer_names];
1526
+ if (parts.length <= 1) {
1527
+ const query = parts[0] ?? "";
1528
+ return [...[
1529
+ "list",
1530
+ "show",
1531
+ "clear",
1532
+ "edit",
1533
+ "delete",
1534
+ "reset",
1535
+ "reload",
1536
+ "base",
1537
+ "enable",
1538
+ "disable",
1539
+ "toggle"
1540
+ ].filter((item) => item.startsWith(query)).map((item) => ({
1541
+ value: item,
1542
+ label: item
1543
+ })), ...all_names.filter((item) => item.startsWith(query)).map((item) => ({
1544
+ value: item,
1545
+ label: item
1546
+ }))];
1547
+ }
1548
+ const command = parts[0];
1549
+ const query = parts.slice(1).join(" ");
1550
+ if (command === "base") return base_names.filter((item) => item.startsWith(query)).map((item) => ({
1551
+ value: `base ${item}`,
1552
+ label: item
1553
+ }));
1554
+ if ([
1555
+ "enable",
1556
+ "disable",
1557
+ "toggle"
1558
+ ].includes(command)) return layer_names.filter((item) => item.startsWith(query)).map((item) => ({
1559
+ value: `${command} ${item}`,
1560
+ label: item
1561
+ }));
1562
+ if (command === "edit") return all_names.filter((item) => item.startsWith(query)).map((item) => ({
1563
+ value: `edit ${item}`,
1564
+ label: item
1565
+ }));
1566
+ if (["delete", "reset"].includes(command)) return all_names.filter((item) => item.startsWith(query)).map((item) => ({
1567
+ value: `${command} ${item}`,
1568
+ label: item
1569
+ }));
1570
+ return null;
1571
+ },
1572
+ handler: async (args, ctx) => {
1573
+ const trimmed = args.trim();
1574
+ if (!trimmed) {
1575
+ if (ctx.hasUI) {
1576
+ await show_manager(ctx);
1577
+ return;
1578
+ }
1579
+ ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
1580
+ return;
1581
+ }
1582
+ const [first, ...rest] = trimmed.split(/\s+/);
1583
+ const arg = rest.join(" ").trim();
1584
+ switch (first) {
1585
+ case "list":
1586
+ ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
1587
+ return;
1588
+ case "show":
1589
+ ctx.ui.notify(format_active_details(active_base_name, active_layers, presets), "info");
1590
+ return;
1591
+ case "clear":
1592
+ commit_state(ctx, void 0, /* @__PURE__ */ new Set(), { notify: "Cleared base preset and prompt layers" });
1593
+ return;
1594
+ case "reload": {
1595
+ presets = load_prompt_presets(ctx.cwd);
1596
+ const normalized = normalize_active_state(presets, active_base_name, active_layers);
1597
+ active_base_name = normalized.active_base_name;
1598
+ active_layers = normalized.active_layers;
1599
+ set_status(ctx, active_base_name, active_layers);
1600
+ ctx.ui.notify("Reloaded prompt presets", "info");
1601
+ return;
1602
+ }
1603
+ case "base":
1604
+ if (!arg) {
1605
+ ctx.ui.notify("Usage: /preset base <name>", "warning");
1606
+ return;
1607
+ }
1608
+ activate_base(arg, ctx);
1609
+ return;
1610
+ case "enable":
1611
+ if (!arg) {
1612
+ ctx.ui.notify("Usage: /preset enable <layer>", "warning");
1613
+ return;
1614
+ }
1615
+ set_layer_enabled(arg, true, ctx);
1616
+ return;
1617
+ case "disable":
1618
+ if (!arg) {
1619
+ ctx.ui.notify("Usage: /preset disable <layer>", "warning");
1620
+ return;
1621
+ }
1622
+ set_layer_enabled(arg, false, ctx);
1623
+ return;
1624
+ case "toggle":
1625
+ if (!arg) {
1626
+ ctx.ui.notify("Usage: /preset toggle <layer>", "warning");
1627
+ return;
1628
+ }
1629
+ toggle_layer(arg, ctx);
1630
+ return;
1631
+ case "edit":
1632
+ if (!arg) {
1633
+ ctx.ui.notify("Usage: /preset edit <name>", "warning");
1634
+ return;
1635
+ }
1636
+ await edit_preset(arg, ctx);
1637
+ return;
1638
+ case "delete":
1639
+ if (!arg) {
1640
+ ctx.ui.notify("Usage: /preset delete <name>", "warning");
1641
+ return;
1642
+ }
1643
+ remove_custom_preset(arg, ctx, "delete");
1644
+ return;
1645
+ case "reset":
1646
+ if (!arg) {
1647
+ ctx.ui.notify("Usage: /preset reset <name>", "warning");
1648
+ return;
1649
+ }
1650
+ remove_custom_preset(arg, ctx, "reset");
1651
+ return;
1652
+ }
1653
+ if (is_subcommand(first)) {
1654
+ ctx.ui.notify(`Unsupported preset command: ${first}`, "warning");
1655
+ return;
1656
+ }
1657
+ const preset = presets[trimmed];
1658
+ if (!preset) {
1659
+ ctx.ui.notify(`Unknown preset or layer: ${trimmed}`, "warning");
1660
+ return;
1661
+ }
1662
+ if (preset.kind === "base") activate_base(preset.name, ctx);
1663
+ else toggle_layer(preset.name, ctx);
1664
+ }
1665
+ });
1666
+ pi.on("session_start", async (_event, ctx) => {
1667
+ presets = load_prompt_presets(ctx.cwd);
1668
+ active_base_name = void 0;
1669
+ active_layers = /* @__PURE__ */ new Set();
1670
+ const preset_flag = pi.getFlag("preset");
1671
+ if (typeof preset_flag === "string" && preset_flag.trim()) {
1672
+ for (const name of parse_preset_flag(preset_flag)) {
1673
+ const preset = presets[name];
1674
+ if (!preset) continue;
1675
+ if (preset.kind === "base") active_base_name = name;
1676
+ else active_layers.add(name);
1677
+ }
1678
+ const normalized = normalize_active_state(presets, active_base_name, active_layers);
1679
+ active_base_name = normalized.active_base_name;
1680
+ active_layers = normalized.active_layers;
1681
+ set_status(ctx, active_base_name, active_layers);
1682
+ return;
1683
+ }
1684
+ const restored = get_last_preset_state(ctx);
1685
+ if (restored) {
1686
+ active_base_name = restored.base_name ?? void 0;
1687
+ active_layers = new Set(restored.layer_names ?? []);
1688
+ }
1689
+ const normalized = normalize_active_state(presets, active_base_name, active_layers);
1690
+ active_base_name = normalized.active_base_name;
1691
+ active_layers = normalized.active_layers;
1692
+ set_status(ctx, active_base_name, active_layers);
1693
+ });
1694
+ pi.on("before_agent_start", async (event) => {
1695
+ const blocks = [];
1696
+ const base = get_base(active_base_name);
1697
+ if (base?.instructions.trim()) blocks.push(`## Active Base Prompt: ${base.name}\n${base.instructions.trim()}`);
1698
+ const layer_blocks = [...active_layers].sort().map((name) => presets[name]).filter((preset) => Boolean(preset?.instructions.trim())).map((preset) => `### ${preset.name}\n${preset.instructions.trim()}`);
1699
+ if (layer_blocks.length > 0) blocks.push(`## Active Prompt Layers\n\n${layer_blocks.join("\n\n")}`);
1700
+ if (blocks.length === 0) return;
1701
+ return { systemPrompt: `${event.systemPrompt}\n\n${blocks.join("\n\n")}` };
1702
+ });
1703
+ pi.on("session_shutdown", async (_event, ctx) => {
1704
+ ctx.ui.setStatus("preset", void 0);
1705
+ });
1706
+ }
1707
+ //#endregion
1054
1708
  //#region src/extensions/recall.ts
1055
1709
  const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
1056
1710
  async function recall(pi) {
@@ -1845,7 +2499,8 @@ const BUILTIN_EXTENSION_FACTORIES = {
1845
2499
  chain,
1846
2500
  "filter-output": filter_output,
1847
2501
  handoff,
1848
- recall
2502
+ recall,
2503
+ "prompt-presets": prompt_presets
1849
2504
  };
1850
2505
  const PACKAGE_THEME_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "themes");
1851
2506
  function get_force_disabled_builtins(options) {
@@ -1856,6 +2511,7 @@ function get_force_disabled_builtins(options) {
1856
2511
  if (!options.filter_output) force_disabled.add("filter-output");
1857
2512
  if (!options.handoff) force_disabled.add("handoff");
1858
2513
  if (!options.recall) force_disabled.add("recall");
2514
+ if (!options.prompt_presets) force_disabled.add("prompt-presets");
1859
2515
  return force_disabled;
1860
2516
  }
1861
2517
  function create_builtin_extension_factory(key, extension, force_disabled) {
@@ -1877,7 +2533,7 @@ function create_extensions_override(managed_inline_paths) {
1877
2533
  };
1878
2534
  }
1879
2535
  async function create_my_pi(options = {}) {
1880
- const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, model } = options;
2536
+ const { cwd = process.cwd(), extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, model, system_prompt, append_system_prompt } = options;
1881
2537
  const resolved_extensions = extensions.map((p) => resolve(cwd, p));
1882
2538
  const force_disabled = get_force_disabled_builtins({
1883
2539
  mcp,
@@ -1885,7 +2541,8 @@ async function create_my_pi(options = {}) {
1885
2541
  chain,
1886
2542
  filter_output,
1887
2543
  handoff,
1888
- recall
2544
+ recall,
2545
+ prompt_presets
1889
2546
  });
1890
2547
  const managed_extension_factories = [create_extensions_extension({ force_disabled }), ...BUILTIN_EXTENSIONS.map((extension) => create_builtin_extension_factory(extension.key, BUILTIN_EXTENSION_FACTORIES[extension.key], force_disabled))];
1891
2548
  const managed_inline_paths = managed_extension_factories.map((_, index) => `<inline:${index + 1}>`);
@@ -1899,6 +2556,8 @@ async function create_my_pi(options = {}) {
1899
2556
  cwd: runtime_cwd,
1900
2557
  ...settings_manager ? { settingsManager: settings_manager } : {},
1901
2558
  resourceLoaderOptions: {
2559
+ ...system_prompt !== void 0 ? { systemPromptOverride: () => system_prompt } : {},
2560
+ ...append_system_prompt !== void 0 ? { appendSystemPromptOverride: (base) => [...base, append_system_prompt] } : {},
1902
2561
  additionalExtensionPaths: [...resolved_extensions],
1903
2562
  additionalThemePaths: [PACKAGE_THEME_DIR],
1904
2563
  extensionFactories: [...managed_extension_factories, ...user_factories],
@@ -1932,4 +2591,4 @@ async function create_my_pi(options = {}) {
1932
2591
  //#endregion
1933
2592
  export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
1934
2593
 
1935
- //# sourceMappingURL=api-S06lPaJV.js.map
2594
+ //# sourceMappingURL=api-DtthDVIa.js.map