my-pi 0.3.6 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -252
- package/dist/extensions.d.ts +11 -0
- package/dist/extensions.js +148 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +40 -519
- package/dist/index.js.map +1 -1
- package/dist/manager.d.ts +3 -0
- package/dist/manager.js +71 -0
- package/dist/manager.js.map +1 -0
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +16 -0
- package/dist/settings.js.map +1 -0
- package/package.json +31 -76
- package/dist/api.d.ts +0 -472
- package/dist/api.js +0 -525
- package/dist/api.js.map +0 -1
- package/dist/builtin-registry-C0CQdd45.js +0 -562
- package/dist/builtin-registry-C0CQdd45.js.map +0 -1
- package/dist/footer-BAZIMVSb.js +0 -1
- package/dist/handoff-B2wpybTf.js +0 -27
- package/dist/handoff-B2wpybTf.js.map +0 -1
- package/dist/hooks-resolution-kzoVsQAX.js +0 -478
- package/dist/hooks-resolution-kzoVsQAX.js.map +0 -1
- package/dist/openrouter-fusion-config-CPXLfmCA.js +0 -64
- package/dist/openrouter-fusion-config-CPXLfmCA.js.map +0 -1
- package/dist/prompt-presets-DOn1oZFJ.js +0 -1392
- package/dist/prompt-presets-DOn1oZFJ.js.map +0 -1
- package/dist/session-name-qyrX-OES.js +0 -157
- package/dist/session-name-qyrX-OES.js.map +0 -1
- package/dist/settings-UYlhEo3S.js +0 -172
- package/dist/settings-UYlhEo3S.js.map +0 -1
- package/dist/startup-screen-D5YGuwge.js +0 -217
- package/dist/startup-screen-D5YGuwge.js.map +0 -1
- package/dist/turn-timer-mowT6nwv.js +0 -63
- package/dist/turn-timer-mowT6nwv.js.map +0 -1
- package/dist/umans-provider-DIupQTRK.js +0 -306
- package/dist/umans-provider-DIupQTRK.js.map +0 -1
|
@@ -1,1392 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { get_settings_path, read_settings, write_settings } from "@spences10/pi-settings";
|
|
5
|
-
import { show_modal } from "@spences10/pi-tui-modal";
|
|
6
|
-
import "@spences10/pi-footer";
|
|
7
|
-
import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
8
|
-
// src/extensions/prompt-presets/catalog.ts
|
|
9
|
-
function get_prompt_source_label(source) {
|
|
10
|
-
switch (source) {
|
|
11
|
-
case "builtin": return "built-in";
|
|
12
|
-
case "user": return "user";
|
|
13
|
-
case "project": return "project";
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function list_base_presets(presets) {
|
|
17
|
-
return Object.values(presets).filter((preset) => preset.kind === "base").sort((a, b) => a.name.localeCompare(b.name));
|
|
18
|
-
}
|
|
19
|
-
function list_layer_presets(presets) {
|
|
20
|
-
return Object.values(presets).filter((preset) => preset.kind === "layer").sort((a, b) => a.name.localeCompare(b.name));
|
|
21
|
-
}
|
|
22
|
-
function format_summary(active_base_name, active_layers, presets) {
|
|
23
|
-
const lines = [`Base: ${active_base_name ?? "(none)"}`];
|
|
24
|
-
const layer_names = [...active_layers].sort();
|
|
25
|
-
if (layer_names.length === 0) lines.push("Layers: (none)");
|
|
26
|
-
else {
|
|
27
|
-
lines.push("Layers:");
|
|
28
|
-
for (const name of layer_names) {
|
|
29
|
-
const preset = presets[name];
|
|
30
|
-
const description = preset?.description ? ` — ${preset.description}` : "";
|
|
31
|
-
lines.push(`- ${name}${description}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return lines.join("\n");
|
|
35
|
-
}
|
|
36
|
-
function format_active_details(active_base_name, active_layers, presets) {
|
|
37
|
-
const parts = [];
|
|
38
|
-
if (active_base_name) {
|
|
39
|
-
const base = presets[active_base_name];
|
|
40
|
-
if (base) {
|
|
41
|
-
parts.push(`Base: ${base.name}`);
|
|
42
|
-
if (base.description) parts.push(`Description: ${base.description}`);
|
|
43
|
-
parts.push(`Source: ${get_prompt_source_label(base.source)}`);
|
|
44
|
-
parts.push("", base.instructions.trim());
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const layer_names = [...active_layers].sort();
|
|
48
|
-
if (layer_names.length > 0) {
|
|
49
|
-
if (parts.length > 0) parts.push("", "---", "");
|
|
50
|
-
parts.push("Layers:");
|
|
51
|
-
for (const name of layer_names) {
|
|
52
|
-
const layer = presets[name];
|
|
53
|
-
if (!layer) continue;
|
|
54
|
-
parts.push(`- ${layer.name} (${get_prompt_source_label(layer.source)})`);
|
|
55
|
-
if (layer.description) parts.push(` ${layer.description}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return parts.join("\n") || "No preset or layers active";
|
|
59
|
-
}
|
|
60
|
-
// src/extensions/prompt-presets/completions.ts
|
|
61
|
-
const SUBCOMMANDS = [
|
|
62
|
-
"help",
|
|
63
|
-
"list",
|
|
64
|
-
"show",
|
|
65
|
-
"clear",
|
|
66
|
-
"edit",
|
|
67
|
-
"edit-global",
|
|
68
|
-
"export-defaults",
|
|
69
|
-
"delete",
|
|
70
|
-
"reset",
|
|
71
|
-
"reload",
|
|
72
|
-
"base",
|
|
73
|
-
"enable",
|
|
74
|
-
"disable",
|
|
75
|
-
"toggle"
|
|
76
|
-
];
|
|
77
|
-
function get_prompt_preset_completions(presets, prefix) {
|
|
78
|
-
const trimmed = prefix.trim();
|
|
79
|
-
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
80
|
-
const base_names = list_base_presets(presets).map((preset) => preset.name);
|
|
81
|
-
const layer_names = list_layer_presets(presets).map((preset) => preset.name);
|
|
82
|
-
const all_names = [...base_names, ...layer_names];
|
|
83
|
-
if (parts.length <= 1) {
|
|
84
|
-
const query = parts[0] ?? "";
|
|
85
|
-
return [...SUBCOMMANDS.filter((item) => item.startsWith(query)).map((item) => ({
|
|
86
|
-
value: item,
|
|
87
|
-
label: item
|
|
88
|
-
})), ...all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
89
|
-
value: item,
|
|
90
|
-
label: item
|
|
91
|
-
}))];
|
|
92
|
-
}
|
|
93
|
-
const command = parts[0];
|
|
94
|
-
const query = parts.slice(1).join(" ");
|
|
95
|
-
if (command === "base") return base_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
96
|
-
value: `base ${item}`,
|
|
97
|
-
label: item
|
|
98
|
-
}));
|
|
99
|
-
if ([
|
|
100
|
-
"enable",
|
|
101
|
-
"disable",
|
|
102
|
-
"toggle"
|
|
103
|
-
].includes(command)) return layer_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
104
|
-
value: `${command} ${item}`,
|
|
105
|
-
label: item
|
|
106
|
-
}));
|
|
107
|
-
if (command === "edit" || command === "edit-global") return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
108
|
-
value: `${command} ${item}`,
|
|
109
|
-
label: item
|
|
110
|
-
}));
|
|
111
|
-
if (["delete", "reset"].includes(command)) return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
112
|
-
value: `${command} ${item}`,
|
|
113
|
-
label: item
|
|
114
|
-
}));
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
const DEFAULT_PROMPT_PRESET_FILES = {
|
|
118
|
-
terse: "---\nkind: base\ndescription: Short, direct, no fluff\n---\n\nBe concise and direct. Default to the shortest response that fully\nsolves the user's request. Use at most one short paragraph or 3\nbullets unless the user explicitly asks for detail. For implementation\nreports, include only what changed, validation, and the next step if\nrelevant. No purple prose, no filler, no repetitive caveats.\n",
|
|
119
|
-
standard: "---\nkind: base\ndescription: Clear and concise with key context\n---\n\nBe clear, direct, and concise. Include only the reasoning and\nimplementation details that matter. Avoid filler, grandstanding, and\nornamental language. Use bullets when they improve scanability.\n",
|
|
120
|
-
detailed: "---\nkind: base\ndescription: More explanation when nuance matters\n---\n\nBe thorough when the task is complex or tradeoffs matter, but stay\npractical. Explain only the details that help the user decide, verify,\nor implement. Avoid purple prose and unnecessary scene-setting.\n",
|
|
121
|
-
"no-purple-prose": "---\nkind: layer\ndescription: Strip out ornamental language\n---\n\nDo not use purple prose, flourish, motivational filler, or theatrical\ntransitions. Prefer plain language and concrete statements.\n",
|
|
122
|
-
"asd-ste100": "---\nkind: layer\ndescription: Use ASD-STE100 Simplified Technical English\n---\n\nUse ASD-STE100 Simplified Technical English in all replies. Use short,\ndirect sentences. Use simple and consistent words. Avoid jargon,\nidioms, vague language, and unnecessary explanation.\n",
|
|
123
|
-
bullets: "---\nkind: layer\ndescription: Prefer short bullets when useful\n---\n\nWhen presenting options, findings, or steps, prefer short bullet lists\nover long paragraphs.\n",
|
|
124
|
-
"clarify-first": "---\nkind: layer\ndescription:\n Ask brief clarifying questions when requirements are ambiguous\n---\n\nIf the request is materially ambiguous, ask the minimum clarifying\nquestion(s) needed before proceeding. Do not ask unnecessary\nquestions.\n",
|
|
125
|
-
"include-risks": "---\nkind: layer\ndescription: Call out notable risks or tradeoffs\n---\n\nWhen making a recommendation or implementation plan, briefly mention\nthe key risk, tradeoff, or caveat if one materially matters.\n",
|
|
126
|
-
karpathy: "---\nkind: layer\ndescription: Bias toward simple, surgical, verified code changes\n---\n\nBefore coding, state assumptions when unclear and surface meaningful\ntradeoffs. Prefer the minimum code that solves the request; avoid\nspeculative abstractions, flexibility, and unrelated cleanup. Make\nsurgical changes: touch only lines directly tied to the task, match\nexisting style, and mention unrelated issues instead of fixing them.\nFor multi-step work, use brief success criteria and verify with\nfocused checks before reporting.\n",
|
|
127
|
-
spence: "---\nkind: layer\ndescription:\n Discuss critically, then execute the agreed outcome autonomously\n---\n\nMatch the user's working mode. When they are exploring, asking what\nyou think, or requesting discussion or research, investigate and\nrespond critically before making changes. Challenge weak assumptions,\nconsider the wider outcome, and do not agree merely to be agreeable.\n\nOnce the user authorises implementation, treat the agreed requirements\nand decisions as the work contract. Complete that outcome and validate\nit without stopping at a plan, an arbitrary partial slice, or repeated\npermission checks. Keep the implementation focused, but never shrink\nthe requested outcome merely to keep the change small.\n\nRespect explicit review and approval boundaries.\n"
|
|
128
|
-
};
|
|
129
|
-
function parse_default_prompt_preset(content) {
|
|
130
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
131
|
-
return {
|
|
132
|
-
kind: frontmatter.kind === "layer" ? "layer" : "base",
|
|
133
|
-
instructions: body.trim(),
|
|
134
|
-
...typeof frontmatter.description === "string" && frontmatter.description.trim() ? { description: frontmatter.description.trim() } : {}
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
const DEFAULT_PROMPT_PRESETS = Object.fromEntries(Object.entries(DEFAULT_PROMPT_PRESET_FILES).map(([name, content]) => [name, parse_default_prompt_preset(content)]));
|
|
138
|
-
// src/extensions/prompt-presets/footer.ts
|
|
139
|
-
function get_footer_prompt_status(active_base_name, active_layers) {
|
|
140
|
-
if (!active_base_name && active_layers.size === 0) return;
|
|
141
|
-
return `prompt:${active_base_name ?? "none"}${active_layers.size > 0 ? ` +${active_layers.size}` : ""}`;
|
|
142
|
-
}
|
|
143
|
-
function set_status(ctx, active_base_name, active_layers) {
|
|
144
|
-
ctx.ui.setStatus("preset", get_footer_prompt_status(active_base_name, active_layers));
|
|
145
|
-
}
|
|
146
|
-
// src/extensions/prompt-presets/help.ts
|
|
147
|
-
function is_subcommand(command) {
|
|
148
|
-
return [
|
|
149
|
-
"help",
|
|
150
|
-
"list",
|
|
151
|
-
"show",
|
|
152
|
-
"clear",
|
|
153
|
-
"edit",
|
|
154
|
-
"edit-global",
|
|
155
|
-
"export-defaults",
|
|
156
|
-
"delete",
|
|
157
|
-
"reset",
|
|
158
|
-
"reload",
|
|
159
|
-
"base",
|
|
160
|
-
"enable",
|
|
161
|
-
"disable",
|
|
162
|
-
"toggle"
|
|
163
|
-
].includes(command);
|
|
164
|
-
}
|
|
165
|
-
function format_prompt_preset_help() {
|
|
166
|
-
return `Prompt presets append instructions to the system prompt.
|
|
167
|
-
|
|
168
|
-
Commands:
|
|
169
|
-
- /prompt-preset Open the preset picker
|
|
170
|
-
- /prompt-preset show Show the active base and layers
|
|
171
|
-
- /prompt-preset <name> Activate a base preset or toggle a layer
|
|
172
|
-
- /prompt-preset base <name> Activate a base preset
|
|
173
|
-
- /prompt-preset enable <layer> Enable a layer
|
|
174
|
-
- /prompt-preset disable <layer> Disable a layer
|
|
175
|
-
- /prompt-preset edit <name> Edit/create .pi/presets/<name>.md
|
|
176
|
-
- /prompt-preset edit-global <name> Edit/create ~/.pi/agent/presets/<name>.md
|
|
177
|
-
- /prompt-preset export-defaults Export built-ins to ~/.pi/agent/presets/*.md
|
|
178
|
-
- /prompt-preset export-defaults project Export built-ins to .pi/presets/*.md
|
|
179
|
-
- /prompt-preset reload Reload presets after manual file edits
|
|
180
|
-
- /prompt-preset clear Clear active base and layers
|
|
181
|
-
|
|
182
|
-
Examples:
|
|
183
|
-
- /prompt-preset export-defaults
|
|
184
|
-
- /prompt-preset edit-global terse
|
|
185
|
-
- /prompt-preset base detailed
|
|
186
|
-
- /prompt-preset enable bullets
|
|
187
|
-
- /prompt-preset show
|
|
188
|
-
|
|
189
|
-
Alias: /preset`;
|
|
190
|
-
}
|
|
191
|
-
// src/extensions/prompt-presets/storage.ts
|
|
192
|
-
const PROJECT_PROMPT_PRESETS_ENV = "MY_PI_PROMPT_PRESETS_PROJECT";
|
|
193
|
-
function normalize_prompt_presets(input) {
|
|
194
|
-
if (!input || typeof input !== "object") return {};
|
|
195
|
-
const normalized = {};
|
|
196
|
-
for (const [raw_name, raw_value] of Object.entries(input)) {
|
|
197
|
-
const name = raw_name.trim();
|
|
198
|
-
if (!name) continue;
|
|
199
|
-
if (typeof raw_value === "string") {
|
|
200
|
-
normalized[name] = {
|
|
201
|
-
kind: "base",
|
|
202
|
-
instructions: raw_value
|
|
203
|
-
};
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
if (!raw_value || typeof raw_value !== "object") continue;
|
|
207
|
-
const candidate = raw_value;
|
|
208
|
-
if (typeof candidate.instructions !== "string") continue;
|
|
209
|
-
normalized[name] = {
|
|
210
|
-
instructions: candidate.instructions,
|
|
211
|
-
...candidate.kind === "layer" ? { kind: "layer" } : {},
|
|
212
|
-
...typeof candidate.description === "string" ? { description: candidate.description } : {}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
return normalized;
|
|
216
|
-
}
|
|
217
|
-
function to_prompt_preset_definitions(presets, source, origin, path, editable) {
|
|
218
|
-
return Object.fromEntries(Object.entries(presets).map(([name, preset]) => [name, {
|
|
219
|
-
name,
|
|
220
|
-
kind: preset.kind === "layer" ? "layer" : "base",
|
|
221
|
-
source,
|
|
222
|
-
origin,
|
|
223
|
-
path,
|
|
224
|
-
editable,
|
|
225
|
-
...preset
|
|
226
|
-
}]));
|
|
227
|
-
}
|
|
228
|
-
function read_prompt_preset_markdown_definitions(dir, source, origin) {
|
|
229
|
-
const definitions = {};
|
|
230
|
-
const diagnostics = [];
|
|
231
|
-
if (!existsSync(dir)) return {
|
|
232
|
-
definitions,
|
|
233
|
-
diagnostics
|
|
234
|
-
};
|
|
235
|
-
let entries;
|
|
236
|
-
try {
|
|
237
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
238
|
-
} catch (error) {
|
|
239
|
-
diagnostics.push({
|
|
240
|
-
code: "invalid-source",
|
|
241
|
-
message: `Could not read prompt preset directory: ${String(error)}`,
|
|
242
|
-
path: dir,
|
|
243
|
-
origin
|
|
244
|
-
});
|
|
245
|
-
return {
|
|
246
|
-
definitions,
|
|
247
|
-
diagnostics
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
251
|
-
const name = entry.name.slice(0, -3).trim();
|
|
252
|
-
const path = join(dir, entry.name);
|
|
253
|
-
if (!name) continue;
|
|
254
|
-
try {
|
|
255
|
-
const { metadata, body } = parse_prompt_preset_markdown(readFileSync(path, "utf-8"));
|
|
256
|
-
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)}`);
|
|
257
|
-
if (!body) throw new Error("instructions body is empty");
|
|
258
|
-
definitions[name] = {
|
|
259
|
-
name,
|
|
260
|
-
kind: metadata.kind === "layer" ? "layer" : "base",
|
|
261
|
-
instructions: body,
|
|
262
|
-
source,
|
|
263
|
-
origin,
|
|
264
|
-
path,
|
|
265
|
-
editable: true,
|
|
266
|
-
...typeof metadata.description === "string" && metadata.description.trim() ? { description: metadata.description } : {}
|
|
267
|
-
};
|
|
268
|
-
} catch (error) {
|
|
269
|
-
diagnostics.push({
|
|
270
|
-
code: "invalid-source",
|
|
271
|
-
name,
|
|
272
|
-
message: `Could not read prompt preset Markdown: ${String(error)}`,
|
|
273
|
-
path,
|
|
274
|
-
origin
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
definitions,
|
|
280
|
-
diagnostics
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function get_global_presets_path() {
|
|
284
|
-
return join(getAgentDir(), "presets.json");
|
|
285
|
-
}
|
|
286
|
-
function get_project_presets_path(cwd) {
|
|
287
|
-
return join(cwd, ".pi", "presets.json");
|
|
288
|
-
}
|
|
289
|
-
function get_global_presets_dir() {
|
|
290
|
-
return join(getAgentDir(), "presets");
|
|
291
|
-
}
|
|
292
|
-
function get_project_presets_dir(cwd) {
|
|
293
|
-
return join(cwd, ".pi", "presets");
|
|
294
|
-
}
|
|
295
|
-
function sanitize_prompt_preset_file_name(name) {
|
|
296
|
-
const sanitized = name.trim().replace(/[\\/:*?"<>|]/g, "-").replace(/^\.+$/, "").replace(/^\.+/, "").replace(/\.+$/, "");
|
|
297
|
-
if (!sanitized) throw new Error("Prompt preset name must contain a file-safe character");
|
|
298
|
-
return sanitized;
|
|
299
|
-
}
|
|
300
|
-
function get_prompt_preset_file_path(dir, name) {
|
|
301
|
-
return join(dir, `${sanitize_prompt_preset_file_name(name)}.md`);
|
|
302
|
-
}
|
|
303
|
-
function get_persisted_prompt_state_path() {
|
|
304
|
-
return join(getAgentDir(), "prompt-preset-state.json");
|
|
305
|
-
}
|
|
306
|
-
function read_prompt_presets_file(path) {
|
|
307
|
-
try {
|
|
308
|
-
if (path === get_global_presets_path()) return normalize_prompt_presets(read_settings().promptPresets?.global ?? {});
|
|
309
|
-
if (!existsSync(path)) return {};
|
|
310
|
-
return normalize_prompt_presets(JSON.parse(readFileSync(path, "utf-8")));
|
|
311
|
-
} catch {
|
|
312
|
-
return {};
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
function read_global_prompt_presets_source() {
|
|
316
|
-
const path = get_settings_path();
|
|
317
|
-
try {
|
|
318
|
-
return {
|
|
319
|
-
presets: normalize_prompt_presets(read_settings().promptPresets?.global ?? {}),
|
|
320
|
-
diagnostics: []
|
|
321
|
-
};
|
|
322
|
-
} catch (error) {
|
|
323
|
-
return {
|
|
324
|
-
presets: {},
|
|
325
|
-
diagnostics: [{
|
|
326
|
-
code: "invalid-source",
|
|
327
|
-
message: `Could not read legacy global prompt presets: ${String(error)}`,
|
|
328
|
-
path,
|
|
329
|
-
origin: "legacy-global-settings"
|
|
330
|
-
}]
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
function read_prompt_presets_json_source(path, origin) {
|
|
335
|
-
if (!existsSync(path)) return {
|
|
336
|
-
presets: {},
|
|
337
|
-
diagnostics: []
|
|
338
|
-
};
|
|
339
|
-
try {
|
|
340
|
-
return {
|
|
341
|
-
presets: normalize_prompt_presets(JSON.parse(readFileSync(path, "utf-8"))),
|
|
342
|
-
diagnostics: []
|
|
343
|
-
};
|
|
344
|
-
} catch (error) {
|
|
345
|
-
return {
|
|
346
|
-
presets: {},
|
|
347
|
-
diagnostics: [{
|
|
348
|
-
code: "invalid-source",
|
|
349
|
-
message: `Could not read prompt preset JSON: ${String(error)}`,
|
|
350
|
-
path,
|
|
351
|
-
origin
|
|
352
|
-
}]
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function parse_prompt_preset_markdown(content) {
|
|
357
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
358
|
-
return {
|
|
359
|
-
metadata: frontmatter,
|
|
360
|
-
body: body.trim()
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function read_prompt_presets_dir(path) {
|
|
364
|
-
const { definitions } = read_prompt_preset_markdown_definitions(path, "user", "global-markdown");
|
|
365
|
-
return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [name, {
|
|
366
|
-
kind: definition.kind,
|
|
367
|
-
instructions: definition.instructions,
|
|
368
|
-
...definition.description ? { description: definition.description } : {}
|
|
369
|
-
}]));
|
|
370
|
-
}
|
|
371
|
-
function format_prompt_preset_markdown(preset) {
|
|
372
|
-
const lines = ["---", `kind: ${preset.kind === "layer" ? "layer" : "base"}`];
|
|
373
|
-
if (preset.description?.trim()) lines.push(`description: ${JSON.stringify(preset.description.trim())}`);
|
|
374
|
-
lines.push("---", "", preset.instructions.trim(), "");
|
|
375
|
-
return lines.join("\n");
|
|
376
|
-
}
|
|
377
|
-
function save_prompt_preset_file(dir, name, preset) {
|
|
378
|
-
if (!existsSync(dir)) mkdirSync(dir, {
|
|
379
|
-
recursive: true,
|
|
380
|
-
mode: 448
|
|
381
|
-
});
|
|
382
|
-
const path = get_prompt_preset_file_path(dir, name);
|
|
383
|
-
const tmp = `${path}.tmp-${Date.now()}`;
|
|
384
|
-
writeFileSync(tmp, format_prompt_preset_markdown(preset), { mode: 384 });
|
|
385
|
-
renameSync(tmp, path);
|
|
386
|
-
return path;
|
|
387
|
-
}
|
|
388
|
-
function save_project_prompt_preset_file(cwd, name, preset) {
|
|
389
|
-
return save_prompt_preset_file(get_project_presets_dir(cwd), name, preset);
|
|
390
|
-
}
|
|
391
|
-
function save_global_prompt_preset_file(name, preset) {
|
|
392
|
-
return save_prompt_preset_file(get_global_presets_dir(), name, preset);
|
|
393
|
-
}
|
|
394
|
-
function should_load_project_prompt_presets() {
|
|
395
|
-
const normalized = process.env[PROJECT_PROMPT_PRESETS_ENV]?.trim().toLowerCase();
|
|
396
|
-
return ![
|
|
397
|
-
"0",
|
|
398
|
-
"false",
|
|
399
|
-
"no",
|
|
400
|
-
"skip",
|
|
401
|
-
"disable"
|
|
402
|
-
].includes(normalized ?? "");
|
|
403
|
-
}
|
|
404
|
-
function load_prompt_preset_catalog(cwd) {
|
|
405
|
-
const legacy_global = read_global_prompt_presets_source();
|
|
406
|
-
const global_markdown = read_prompt_preset_markdown_definitions(get_global_presets_dir(), "user", "global-markdown");
|
|
407
|
-
const project_json = read_prompt_presets_json_source(get_project_presets_path(cwd), "project-json");
|
|
408
|
-
const project_markdown = read_prompt_preset_markdown_definitions(get_project_presets_dir(cwd), "project", "project-markdown");
|
|
409
|
-
const source_diagnostics = [...legacy_global.diagnostics, ...global_markdown.diagnostics];
|
|
410
|
-
const sources = [
|
|
411
|
-
to_prompt_preset_definitions(DEFAULT_PROMPT_PRESETS, "builtin", "builtin", null, false),
|
|
412
|
-
to_prompt_preset_definitions(legacy_global.presets, "user", "legacy-global-settings", get_settings_path(), false),
|
|
413
|
-
global_markdown.definitions
|
|
414
|
-
];
|
|
415
|
-
if (should_load_project_prompt_presets()) {
|
|
416
|
-
source_diagnostics.push(...project_json.diagnostics, ...project_markdown.diagnostics);
|
|
417
|
-
sources.push(to_prompt_preset_definitions(project_json.presets, "project", "project-json", get_project_presets_path(cwd), false), project_markdown.definitions);
|
|
418
|
-
}
|
|
419
|
-
const chains = /* @__PURE__ */ new Map();
|
|
420
|
-
for (const source of sources) for (const definition of Object.values(source)) {
|
|
421
|
-
const chain = chains.get(definition.name) ?? [];
|
|
422
|
-
chain.push(definition);
|
|
423
|
-
chains.set(definition.name, chain);
|
|
424
|
-
}
|
|
425
|
-
const diagnostics = [...source_diagnostics];
|
|
426
|
-
const presets = {};
|
|
427
|
-
for (const [name, chain] of chains) {
|
|
428
|
-
const winner = chain.at(-1);
|
|
429
|
-
const fallbacks = chain.slice(0, -1).reverse();
|
|
430
|
-
presets[name] = {
|
|
431
|
-
...winner,
|
|
432
|
-
fallbacks
|
|
433
|
-
};
|
|
434
|
-
for (const shadowed of fallbacks) diagnostics.push({
|
|
435
|
-
code: "shadowed",
|
|
436
|
-
name,
|
|
437
|
-
message: `${shadowed.origin} definition is shadowed by ${winner.origin}`,
|
|
438
|
-
path: shadowed.path,
|
|
439
|
-
origin: shadowed.origin
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
return {
|
|
443
|
-
presets,
|
|
444
|
-
diagnostics
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
function load_prompt_presets(cwd) {
|
|
448
|
-
return load_prompt_preset_catalog(cwd).presets;
|
|
449
|
-
}
|
|
450
|
-
function sort_prompt_presets(presets) {
|
|
451
|
-
return Object.fromEntries(Object.entries(presets).sort(([a], [b]) => a.localeCompare(b)));
|
|
452
|
-
}
|
|
453
|
-
function save_project_prompt_presets(cwd, presets) {
|
|
454
|
-
const path = get_project_presets_path(cwd);
|
|
455
|
-
const dir = dirname(path);
|
|
456
|
-
if (!existsSync(dir)) mkdirSync(dir, {
|
|
457
|
-
recursive: true,
|
|
458
|
-
mode: 448
|
|
459
|
-
});
|
|
460
|
-
const tmp = `${path}.tmp-${Date.now()}`;
|
|
461
|
-
writeFileSync(tmp, JSON.stringify(sort_prompt_presets(presets), null, " ") + "\n", { mode: 384 });
|
|
462
|
-
renameSync(tmp, path);
|
|
463
|
-
return path;
|
|
464
|
-
}
|
|
465
|
-
function remove_project_prompt_preset(cwd, name) {
|
|
466
|
-
const json_path = get_project_presets_path(cwd);
|
|
467
|
-
const project_presets = read_prompt_presets_file(json_path);
|
|
468
|
-
let removed = false;
|
|
469
|
-
let removed_path = json_path;
|
|
470
|
-
if (name in project_presets) {
|
|
471
|
-
delete project_presets[name];
|
|
472
|
-
removed = true;
|
|
473
|
-
removed_path = json_path;
|
|
474
|
-
if (Object.keys(project_presets).length === 0) {
|
|
475
|
-
if (existsSync(json_path)) unlinkSync(json_path);
|
|
476
|
-
} else save_project_prompt_presets(cwd, project_presets);
|
|
477
|
-
}
|
|
478
|
-
const file_path = get_prompt_preset_file_path(get_project_presets_dir(cwd), name);
|
|
479
|
-
if (existsSync(file_path)) {
|
|
480
|
-
unlinkSync(file_path);
|
|
481
|
-
removed = true;
|
|
482
|
-
removed_path = file_path;
|
|
483
|
-
}
|
|
484
|
-
const remaining = Object.keys(read_prompt_presets_file(json_path)).length + Object.keys(read_prompt_presets_dir(get_project_presets_dir(cwd))).length;
|
|
485
|
-
return {
|
|
486
|
-
removed,
|
|
487
|
-
path: removed_path,
|
|
488
|
-
remaining
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
function normalize_prompt_preset_state(input) {
|
|
492
|
-
if (!input || typeof input !== "object") return void 0;
|
|
493
|
-
const candidate = input;
|
|
494
|
-
return {
|
|
495
|
-
base_name: typeof candidate.base_name === "string" && candidate.base_name.trim() ? candidate.base_name.trim() : null,
|
|
496
|
-
layer_names: Array.isArray(candidate.layer_names) ? [...new Set(candidate.layer_names.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()))].sort() : []
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
function read_persisted_prompt_states(path = get_persisted_prompt_state_path()) {
|
|
500
|
-
try {
|
|
501
|
-
const parsed = path === get_persisted_prompt_state_path() ? read_settings().promptPresets?.state ?? {} : existsSync(path) ? JSON.parse(readFileSync(path, "utf-8")) : {};
|
|
502
|
-
const raw_projects = parsed.projects && typeof parsed.projects === "object" ? parsed.projects : {};
|
|
503
|
-
const projects = {};
|
|
504
|
-
for (const [cwd, value] of Object.entries(raw_projects)) {
|
|
505
|
-
const normalized = normalize_prompt_preset_state(value);
|
|
506
|
-
if (!normalized) continue;
|
|
507
|
-
projects[cwd] = normalized;
|
|
508
|
-
}
|
|
509
|
-
return {
|
|
510
|
-
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
511
|
-
projects
|
|
512
|
-
};
|
|
513
|
-
} catch {
|
|
514
|
-
return {
|
|
515
|
-
version: 1,
|
|
516
|
-
projects: {}
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function load_persisted_prompt_state(cwd, path = get_persisted_prompt_state_path()) {
|
|
521
|
-
return read_persisted_prompt_states(path).projects[cwd];
|
|
522
|
-
}
|
|
523
|
-
function save_persisted_prompt_state(cwd, state, path = get_persisted_prompt_state_path()) {
|
|
524
|
-
const persisted = read_persisted_prompt_states(path);
|
|
525
|
-
persisted.projects[cwd] = normalize_prompt_preset_state(state) ?? {
|
|
526
|
-
base_name: null,
|
|
527
|
-
layer_names: []
|
|
528
|
-
};
|
|
529
|
-
const next = {
|
|
530
|
-
version: 1,
|
|
531
|
-
projects: Object.fromEntries(Object.entries(persisted.projects).sort(([a], [b]) => a.localeCompare(b)))
|
|
532
|
-
};
|
|
533
|
-
if (path === get_persisted_prompt_state_path()) {
|
|
534
|
-
const settings = read_settings();
|
|
535
|
-
write_settings({
|
|
536
|
-
...settings,
|
|
537
|
-
promptPresets: {
|
|
538
|
-
...settings.promptPresets,
|
|
539
|
-
state: next
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
return path;
|
|
543
|
-
}
|
|
544
|
-
const dir = dirname(path);
|
|
545
|
-
if (!existsSync(dir)) mkdirSync(dir, {
|
|
546
|
-
recursive: true,
|
|
547
|
-
mode: 448
|
|
548
|
-
});
|
|
549
|
-
const tmp = `${path}.tmp-${Date.now()}`;
|
|
550
|
-
writeFileSync(tmp, JSON.stringify(next, null, " ") + "\n", { mode: 384 });
|
|
551
|
-
renameSync(tmp, path);
|
|
552
|
-
return path;
|
|
553
|
-
}
|
|
554
|
-
// src/extensions/prompt-presets/library.ts
|
|
555
|
-
var PromptPresetLibraryError = class extends Error {
|
|
556
|
-
code;
|
|
557
|
-
details;
|
|
558
|
-
constructor(code, message, details = {}) {
|
|
559
|
-
super(message);
|
|
560
|
-
this.code = code;
|
|
561
|
-
this.details = details;
|
|
562
|
-
this.name = "PromptPresetLibraryError";
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
const PORTABLE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
566
|
-
const WINDOWS_RESERVED_BASENAME = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
|
|
567
|
-
function validate_scope(scope) {
|
|
568
|
-
if (scope !== "global" && scope !== "project") throw new PromptPresetLibraryError("invalid-scope", `Invalid prompt preset scope: ${scope}`, { scope });
|
|
569
|
-
}
|
|
570
|
-
function validate_kind(kind) {
|
|
571
|
-
if (kind !== "base" && kind !== "layer") throw new PromptPresetLibraryError("invalid-kind", `Invalid prompt preset kind: ${kind}`, { kind });
|
|
572
|
-
}
|
|
573
|
-
function validate_prompt_preset_name(name) {
|
|
574
|
-
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 });
|
|
575
|
-
const path = get_prompt_preset_file_path("/presets", name);
|
|
576
|
-
if (basename(path) !== `${name}.md`) throw new PromptPresetLibraryError("invalid-name", `Prompt preset name cannot round-trip to Markdown: ${name}`, { name });
|
|
577
|
-
return name;
|
|
578
|
-
}
|
|
579
|
-
function get_scope_dir(cwd, scope) {
|
|
580
|
-
validate_scope(scope);
|
|
581
|
-
if (scope === "project" && [
|
|
582
|
-
"0",
|
|
583
|
-
"false",
|
|
584
|
-
"no",
|
|
585
|
-
"skip",
|
|
586
|
-
"disable"
|
|
587
|
-
].includes(process.env.MY_PI_PROMPT_PRESETS_PROJECT?.trim().toLowerCase() ?? "")) throw new PromptPresetLibraryError("project-disabled", "Project prompt presets are disabled for this repository", { scope });
|
|
588
|
-
return scope === "global" ? get_global_presets_dir() : get_project_presets_dir(cwd);
|
|
589
|
-
}
|
|
590
|
-
function get_scope_origin(scope) {
|
|
591
|
-
return scope === "global" ? "global-markdown" : "project-markdown";
|
|
592
|
-
}
|
|
593
|
-
function assert_portable_path_available(dir, name, ignore_path) {
|
|
594
|
-
const path = get_prompt_preset_file_path(dir, name);
|
|
595
|
-
if (existsSync(dir)) {
|
|
596
|
-
const collision = readdirSync(dir).find((entry) => {
|
|
597
|
-
return join(dir, entry) !== ignore_path && entry.toLocaleLowerCase("en-US") === `${name}.md`.toLocaleLowerCase("en-US");
|
|
598
|
-
});
|
|
599
|
-
if (collision) throw new PromptPresetLibraryError("collision", `Prompt preset filename collides with ${collision}`, {
|
|
600
|
-
name,
|
|
601
|
-
path: join(dir, collision)
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
return path;
|
|
605
|
-
}
|
|
606
|
-
function find_scope_definition(cwd, scope, name) {
|
|
607
|
-
const origin = get_scope_origin(scope);
|
|
608
|
-
const loaded = load_prompt_preset_catalog(cwd).presets[name];
|
|
609
|
-
if (loaded?.origin === origin) return loaded;
|
|
610
|
-
return loaded?.fallbacks.find((item) => item.origin === origin);
|
|
611
|
-
}
|
|
612
|
-
function normalize_preset(preset) {
|
|
613
|
-
const kind = preset.kind ?? "base";
|
|
614
|
-
validate_kind(kind);
|
|
615
|
-
if (!preset.instructions.trim()) throw new PromptPresetLibraryError("invalid-instructions", "Prompt preset instructions cannot be empty");
|
|
616
|
-
return {
|
|
617
|
-
kind,
|
|
618
|
-
instructions: preset.instructions,
|
|
619
|
-
...preset.description?.trim() ? { description: preset.description.trim() } : {}
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function reload_prompt_preset_library(cwd) {
|
|
623
|
-
return load_prompt_preset_catalog(cwd);
|
|
624
|
-
}
|
|
625
|
-
function create_prompt_preset(cwd, scope, name, preset) {
|
|
626
|
-
validate_scope(scope);
|
|
627
|
-
validate_prompt_preset_name(name);
|
|
628
|
-
const dir = get_scope_dir(cwd, scope);
|
|
629
|
-
const path = assert_portable_path_available(dir, name);
|
|
630
|
-
if (existsSync(path)) throw new PromptPresetLibraryError("collision", `Preset already exists: ${name}`, {
|
|
631
|
-
name,
|
|
632
|
-
path
|
|
633
|
-
});
|
|
634
|
-
save_prompt_preset_file(dir, name, normalize_preset(preset));
|
|
635
|
-
const catalog = reload_prompt_preset_library(cwd);
|
|
636
|
-
return {
|
|
637
|
-
preset: catalog.presets[name],
|
|
638
|
-
catalog,
|
|
639
|
-
path
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
function copy_prompt_preset(cwd, name, source_scope, target_scope, target_name = name) {
|
|
643
|
-
validate_prompt_preset_name(name);
|
|
644
|
-
validate_prompt_preset_name(target_name);
|
|
645
|
-
if (source_scope !== "builtin") {
|
|
646
|
-
validate_scope(source_scope);
|
|
647
|
-
get_scope_dir(cwd, source_scope);
|
|
648
|
-
}
|
|
649
|
-
validate_scope(target_scope);
|
|
650
|
-
const loaded = load_prompt_preset_catalog(cwd).presets[name];
|
|
651
|
-
const source = (loaded ? [loaded, ...loaded.fallbacks] : []).find((definition) => {
|
|
652
|
-
if (source_scope === "builtin") return definition.origin === "builtin";
|
|
653
|
-
return source_scope === "global" ? ["global-markdown", "legacy-global-settings"].includes(definition.origin) : ["project-markdown", "project-json"].includes(definition.origin);
|
|
654
|
-
});
|
|
655
|
-
if (!source) throw new PromptPresetLibraryError(loaded ? "source-mismatch" : "not-found", loaded ? `Preset ${name} has no ${source_scope} definition` : `Preset not found: ${name}`, {
|
|
656
|
-
name,
|
|
657
|
-
source_scope
|
|
658
|
-
});
|
|
659
|
-
return create_prompt_preset(cwd, target_scope, target_name, source);
|
|
660
|
-
}
|
|
661
|
-
function require_custom_scope_definition(cwd, scope, name) {
|
|
662
|
-
validate_prompt_preset_name(name);
|
|
663
|
-
get_scope_dir(cwd, scope);
|
|
664
|
-
const definition = find_scope_definition(cwd, scope, name);
|
|
665
|
-
if (!definition?.path) {
|
|
666
|
-
const winner = load_prompt_preset_catalog(cwd).presets[name];
|
|
667
|
-
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}`, {
|
|
668
|
-
name,
|
|
669
|
-
scope,
|
|
670
|
-
origin: winner?.origin
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
return definition;
|
|
674
|
-
}
|
|
675
|
-
function rename_state(state, old_name, new_name, kind) {
|
|
676
|
-
if (!state) return void 0;
|
|
677
|
-
return {
|
|
678
|
-
base_name: kind === "base" && state.base_name === old_name ? new_name : state.base_name,
|
|
679
|
-
layer_names: kind === "layer" ? [...new Set(state.layer_names.map((name) => name === old_name ? new_name : name))].sort() : state.layer_names
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
function rename_prompt_preset(cwd, scope, old_name, new_name, active_state) {
|
|
683
|
-
const definition = require_custom_scope_definition(cwd, scope, old_name);
|
|
684
|
-
validate_prompt_preset_name(new_name);
|
|
685
|
-
const dir = get_scope_dir(cwd, scope);
|
|
686
|
-
const old_path = definition.path;
|
|
687
|
-
const new_path = assert_portable_path_available(dir, new_name, old_path);
|
|
688
|
-
if (existsSync(new_path) && new_path !== old_path) throw new PromptPresetLibraryError("collision", `Preset already exists: ${new_name}`);
|
|
689
|
-
const next_persisted = rename_state(load_persisted_prompt_state(cwd), old_name, new_name, definition.kind);
|
|
690
|
-
const next_active = rename_state(active_state, old_name, new_name, definition.kind);
|
|
691
|
-
renameSync(old_path, new_path);
|
|
692
|
-
try {
|
|
693
|
-
if (next_persisted) save_persisted_prompt_state(cwd, next_persisted);
|
|
694
|
-
} catch (error) {
|
|
695
|
-
renameSync(new_path, old_path);
|
|
696
|
-
throw error;
|
|
697
|
-
}
|
|
698
|
-
const catalog = reload_prompt_preset_library(cwd);
|
|
699
|
-
return {
|
|
700
|
-
preset: catalog.presets[new_name],
|
|
701
|
-
catalog,
|
|
702
|
-
path: new_path,
|
|
703
|
-
...next_active ? { state: next_active } : {}
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
function get_delete_prompt_preset_confirmation(cwd, scope, name, state = load_persisted_prompt_state(cwd)) {
|
|
707
|
-
const definition = require_custom_scope_definition(cwd, scope, name);
|
|
708
|
-
const winner = load_prompt_preset_catalog(cwd).presets[name];
|
|
709
|
-
const fallback = winner?.origin === get_scope_origin(scope) ? winner.fallbacks[0] : winner;
|
|
710
|
-
return {
|
|
711
|
-
name,
|
|
712
|
-
scope,
|
|
713
|
-
path: definition.path,
|
|
714
|
-
kind: definition.kind,
|
|
715
|
-
fallback,
|
|
716
|
-
active: state?.base_name === name || state?.layer_names.includes(name) === true
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
function delete_prompt_preset(cwd, scope, name, confirmed = false) {
|
|
720
|
-
const confirmation = get_delete_prompt_preset_confirmation(cwd, scope, name);
|
|
721
|
-
if (!confirmed) throw new PromptPresetLibraryError("confirmation-required", `Confirmation required to delete ${name}`, { confirmation });
|
|
722
|
-
unlinkSync(confirmation.path);
|
|
723
|
-
const catalog = reload_prompt_preset_library(cwd);
|
|
724
|
-
return {
|
|
725
|
-
preset: catalog.presets[name],
|
|
726
|
-
catalog,
|
|
727
|
-
path: confirmation.path
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
function reset_prompt_preset_override(cwd, scope, name) {
|
|
731
|
-
const definition = require_custom_scope_definition(cwd, scope, name);
|
|
732
|
-
unlinkSync(definition.path);
|
|
733
|
-
const catalog = reload_prompt_preset_library(cwd);
|
|
734
|
-
return {
|
|
735
|
-
preset: catalog.presets[name],
|
|
736
|
-
catalog,
|
|
737
|
-
path: definition.path
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
// src/extensions/prompt-presets/prompt-blocks.ts
|
|
741
|
-
function build_active_prompt_blocks(presets, active_base_name, active_layers) {
|
|
742
|
-
const blocks = [];
|
|
743
|
-
const base = active_base_name ? presets[active_base_name] : void 0;
|
|
744
|
-
if (base?.instructions.trim()) blocks.push(`## Active Base Prompt: ${base.name}\n${base.instructions.trim()}`);
|
|
745
|
-
const layer_blocks = [...active_layers].sort().map((name) => presets[name]).filter((preset) => Boolean(preset?.instructions.trim())).map((preset) => `### ${preset.name}\n${preset.instructions.trim()}`);
|
|
746
|
-
if (layer_blocks.length > 0) blocks.push(`## Active Prompt Layers\n\n${layer_blocks.join("\n\n")}`);
|
|
747
|
-
return blocks;
|
|
748
|
-
}
|
|
749
|
-
// src/extensions/prompt-presets/state.ts
|
|
750
|
-
const PRESET_STATE_TYPE = "prompt-preset-state";
|
|
751
|
-
const NONE_BASE_ID = "__base_none__";
|
|
752
|
-
function get_last_preset_state(ctx) {
|
|
753
|
-
const entries = ctx.sessionManager.getEntries();
|
|
754
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
755
|
-
const entry = entries[i];
|
|
756
|
-
if (entry.type === "custom" && entry.customType === "prompt-preset-state" && entry.data) return entry.data;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function persist_state(pi, ctx, active_base_name, active_layers) {
|
|
760
|
-
const state = {
|
|
761
|
-
base_name: active_base_name ?? null,
|
|
762
|
-
layer_names: [...active_layers].sort()
|
|
763
|
-
};
|
|
764
|
-
pi.appendEntry(PRESET_STATE_TYPE, state);
|
|
765
|
-
save_persisted_prompt_state(ctx.cwd, state);
|
|
766
|
-
}
|
|
767
|
-
function normalize_active_state(presets, active_base_name, active_layers) {
|
|
768
|
-
return {
|
|
769
|
-
active_base_name: active_base_name && presets[active_base_name]?.kind === "base" ? active_base_name : void 0,
|
|
770
|
-
active_layers: new Set([...active_layers].filter((name) => presets[name]?.kind === "layer"))
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
function parse_preset_flag(flag) {
|
|
774
|
-
return flag.split(",").map((item) => item.trim()).filter(Boolean);
|
|
775
|
-
}
|
|
776
|
-
// src/extensions/prompt-presets/manager.ts
|
|
777
|
-
async function show_prompt_preset_manager(ctx, state) {
|
|
778
|
-
const base_presets = list_base_presets(state.presets);
|
|
779
|
-
const layer_presets = list_layer_presets(state.presets);
|
|
780
|
-
if (base_presets.length === 0 && layer_presets.length === 0) {
|
|
781
|
-
ctx.ui.notify("No prompt presets available", "warning");
|
|
782
|
-
return { action: "cancel" };
|
|
783
|
-
}
|
|
784
|
-
return await show_modal(ctx, {
|
|
785
|
-
title: "Prompt presets",
|
|
786
|
-
subtitle: "Choose one base preset, then add optional layers.",
|
|
787
|
-
footer: "↑↓ move • space select • enter Apply • esc Cancel",
|
|
788
|
-
overlay_options: {
|
|
789
|
-
width: "94%",
|
|
790
|
-
minWidth: 44,
|
|
791
|
-
maxHeight: "92%"
|
|
792
|
-
}
|
|
793
|
-
}, ({ done }, theme, layout) => new PromptPresetInspectorBody(state, theme, () => layout.get_max_body_lines(), done)) ?? { action: "cancel" };
|
|
794
|
-
}
|
|
795
|
-
var PromptPresetInspectorBody = class {
|
|
796
|
-
state;
|
|
797
|
-
theme;
|
|
798
|
-
get_max_lines;
|
|
799
|
-
done;
|
|
800
|
-
rows;
|
|
801
|
-
selected_index;
|
|
802
|
-
selected_base;
|
|
803
|
-
enabled_layers;
|
|
804
|
-
preview_mode = "item";
|
|
805
|
-
preview_offset = 0;
|
|
806
|
-
preview_lines = [];
|
|
807
|
-
constructor(state, theme, get_max_lines, done) {
|
|
808
|
-
this.state = state;
|
|
809
|
-
this.theme = theme;
|
|
810
|
-
this.get_max_lines = get_max_lines;
|
|
811
|
-
this.done = done;
|
|
812
|
-
const base_presets = list_base_presets(state.presets);
|
|
813
|
-
const layer_presets = list_layer_presets(state.presets);
|
|
814
|
-
this.rows = [
|
|
815
|
-
{
|
|
816
|
-
type: "header",
|
|
817
|
-
id: "__base_header__",
|
|
818
|
-
label: `Base presets (${base_presets.length + 1})`
|
|
819
|
-
},
|
|
820
|
-
{
|
|
821
|
-
type: "base-none",
|
|
822
|
-
id: NONE_BASE_ID,
|
|
823
|
-
label: "(none)"
|
|
824
|
-
},
|
|
825
|
-
...base_presets.map((preset) => ({
|
|
826
|
-
type: "preset",
|
|
827
|
-
id: preset.name,
|
|
828
|
-
preset
|
|
829
|
-
})),
|
|
830
|
-
{
|
|
831
|
-
type: "header",
|
|
832
|
-
id: "__layer_header__",
|
|
833
|
-
label: `Prompt layers (${layer_presets.length})`
|
|
834
|
-
},
|
|
835
|
-
...layer_presets.map((preset) => ({
|
|
836
|
-
type: "preset",
|
|
837
|
-
id: preset.name,
|
|
838
|
-
preset
|
|
839
|
-
}))
|
|
840
|
-
];
|
|
841
|
-
this.selected_base = state.active_base_name;
|
|
842
|
-
this.enabled_layers = new Set(state.active_layers);
|
|
843
|
-
this.selected_index = this.get_initial_selected_index();
|
|
844
|
-
}
|
|
845
|
-
render(width) {
|
|
846
|
-
const body_lines = Math.max(8, this.get_max_lines());
|
|
847
|
-
if (width < 80) return this.render_stacked(width, body_lines);
|
|
848
|
-
const list_width = Math.min(38, Math.max(28, Math.floor(width * .38)));
|
|
849
|
-
const preview_width = width - list_width - 3;
|
|
850
|
-
const list = this.render_list(list_width, body_lines);
|
|
851
|
-
const preview = this.render_preview(preview_width, body_lines);
|
|
852
|
-
const lines = [];
|
|
853
|
-
for (let i = 0; i < body_lines; i++) lines.push(`${this.pad_line(list[i] ?? "", list_width)} ${this.theme.fg("dim", "│")} ${preview[i] ?? ""}`);
|
|
854
|
-
return lines.map((line) => truncateToWidth(line, width));
|
|
855
|
-
}
|
|
856
|
-
invalidate() {}
|
|
857
|
-
handleInput(data) {
|
|
858
|
-
if (matchesKey(data, Key.up) || data === "k") this.move(-1);
|
|
859
|
-
else if (matchesKey(data, Key.down) || data === "j") this.move(1);
|
|
860
|
-
else if (data === " " || matchesKey(data, Key.enter)) {
|
|
861
|
-
if (matchesKey(data, Key.enter)) {
|
|
862
|
-
this.done({
|
|
863
|
-
action: "apply",
|
|
864
|
-
base_name: this.selected_base,
|
|
865
|
-
layers: new Set(this.enabled_layers)
|
|
866
|
-
});
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
this.toggle_selected();
|
|
870
|
-
} else if (data === " ") {
|
|
871
|
-
this.preview_mode = this.preview_mode === "item" ? "effective" : "item";
|
|
872
|
-
this.preview_offset = 0;
|
|
873
|
-
} else if (data === "c") {
|
|
874
|
-
this.selected_base = void 0;
|
|
875
|
-
this.enabled_layers.clear();
|
|
876
|
-
this.preview_mode = "effective";
|
|
877
|
-
this.preview_offset = 0;
|
|
878
|
-
} else if (data === "a") this.done({
|
|
879
|
-
action: "apply",
|
|
880
|
-
base_name: this.selected_base,
|
|
881
|
-
layers: new Set(this.enabled_layers)
|
|
882
|
-
});
|
|
883
|
-
else if ("neyrdxl".includes(data)) {
|
|
884
|
-
const actions = {
|
|
885
|
-
n: "create",
|
|
886
|
-
e: "edit",
|
|
887
|
-
y: "copy",
|
|
888
|
-
r: "rename",
|
|
889
|
-
d: "delete",
|
|
890
|
-
x: "reset",
|
|
891
|
-
l: "reload"
|
|
892
|
-
};
|
|
893
|
-
const row = this.rows[this.selected_index];
|
|
894
|
-
const action = actions[data];
|
|
895
|
-
if (action === "create" || action === "reload") this.done({ action });
|
|
896
|
-
else if (row?.type === "preset") this.done({
|
|
897
|
-
action,
|
|
898
|
-
preset: row.preset
|
|
899
|
-
});
|
|
900
|
-
} else if (matchesKey(data, Key.escape) || data === "q") this.done({ action: "cancel" });
|
|
901
|
-
else if (data === "J") this.scroll_preview(1);
|
|
902
|
-
else if (data === "K") this.scroll_preview(-1);
|
|
903
|
-
}
|
|
904
|
-
render_stacked(width, body_lines) {
|
|
905
|
-
const list_lines = Math.max(5, Math.floor(body_lines / 2));
|
|
906
|
-
return [
|
|
907
|
-
...this.render_list(width, list_lines),
|
|
908
|
-
this.theme.fg("dim", "─".repeat(Math.min(width, 80))),
|
|
909
|
-
...this.render_preview(width, body_lines - list_lines - 1)
|
|
910
|
-
];
|
|
911
|
-
}
|
|
912
|
-
render_list(width, max_lines) {
|
|
913
|
-
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));
|
|
914
|
-
const lines = [
|
|
915
|
-
this.theme.fg("accent", `Draft: ${this.selected_base ?? "(none)"} + ${this.enabled_layers.size} layer(s)`),
|
|
916
|
-
...action_lines,
|
|
917
|
-
""
|
|
918
|
-
];
|
|
919
|
-
const visible_rows = Math.max(1, max_lines - lines.length);
|
|
920
|
-
const start = Math.max(0, Math.min(this.selected_index - Math.floor(visible_rows / 2), this.rows.length - visible_rows));
|
|
921
|
-
const end = Math.min(start + visible_rows, this.rows.length);
|
|
922
|
-
for (let index = start; index < end; index++) lines.push(this.render_row(this.rows[index], index, width));
|
|
923
|
-
return lines.map((line) => truncateToWidth(line, width));
|
|
924
|
-
}
|
|
925
|
-
render_preview(width, max_lines) {
|
|
926
|
-
const heading = this.preview_mode === "effective" ? "Full prompt contribution" : this.get_selected_heading();
|
|
927
|
-
const text = this.preview_mode === "effective" ? this.get_effective_prompt_text() : this.get_selected_prompt_text();
|
|
928
|
-
const content_width = Math.max(10, width);
|
|
929
|
-
this.preview_lines = text.split("\n").flatMap((line) => line.trim().length === 0 ? [""] : wrapTextWithAnsi(line, content_width));
|
|
930
|
-
const available = Math.max(1, max_lines - 2);
|
|
931
|
-
const max_offset = Math.max(0, this.preview_lines.length - available);
|
|
932
|
-
this.preview_offset = Math.max(0, Math.min(this.preview_offset, max_offset));
|
|
933
|
-
const end = Math.min(this.preview_offset + available, this.preview_lines.length);
|
|
934
|
-
const range = this.preview_lines.length > available ? ` (${this.preview_offset + 1}-${end}/${this.preview_lines.length})` : "";
|
|
935
|
-
return [
|
|
936
|
-
this.theme.fg("accent", this.theme.bold(`${heading}${range}`)),
|
|
937
|
-
"",
|
|
938
|
-
...this.preview_lines.slice(this.preview_offset, end).map((line) => truncateToWidth(line, width))
|
|
939
|
-
];
|
|
940
|
-
}
|
|
941
|
-
render_row(row, index, width) {
|
|
942
|
-
if (row.type === "header") return this.theme.fg("muted", ` ${row.label}`);
|
|
943
|
-
const cursor = index === this.selected_index ? this.theme.fg("accent", "› ") : " ";
|
|
944
|
-
const marker = this.get_marker(row);
|
|
945
|
-
const label = row.type === "base-none" ? row.label : row.preset.name;
|
|
946
|
-
const suffix = row.type === "preset" ? ` ${this.theme.fg("dim", get_prompt_source_label(row.preset.source))}` : "";
|
|
947
|
-
return truncateToWidth(`${cursor}${marker} ${this.pad(label, 18)}${suffix}`, width);
|
|
948
|
-
}
|
|
949
|
-
get_marker(row) {
|
|
950
|
-
if (row.type === "header") return " ";
|
|
951
|
-
if (row.type === "base-none") return this.selected_base ? "○" : "●";
|
|
952
|
-
if (row.preset.kind === "base") return this.selected_base === row.preset.name ? "●" : "○";
|
|
953
|
-
return this.enabled_layers.has(row.preset.name) ? "☑" : "☐";
|
|
954
|
-
}
|
|
955
|
-
get_selected_prompt_text() {
|
|
956
|
-
const row = this.rows[this.selected_index];
|
|
957
|
-
if (!row || row.type === "header") return "";
|
|
958
|
-
if (row.type === "base-none") return "No active base preset. Only enabled prompt layers will be appended.";
|
|
959
|
-
const preset = row.preset;
|
|
960
|
-
return [
|
|
961
|
-
preset.description || void 0,
|
|
962
|
-
preset.description ? "" : void 0,
|
|
963
|
-
preset.instructions.trim() || "(empty preset)"
|
|
964
|
-
].filter((line) => line !== void 0).join("\n");
|
|
965
|
-
}
|
|
966
|
-
get_effective_prompt_text() {
|
|
967
|
-
const blocks = build_active_prompt_blocks(this.state.presets, this.selected_base, this.enabled_layers);
|
|
968
|
-
return blocks.length > 0 ? blocks.join("\n\n") : "(No prompt preset text will be appended.)";
|
|
969
|
-
}
|
|
970
|
-
get_selected_heading() {
|
|
971
|
-
const row = this.rows[this.selected_index];
|
|
972
|
-
if (!row || row.type === "header") return "Highlighted preset";
|
|
973
|
-
return row.type === "base-none" ? "No base preset" : row.preset.name;
|
|
974
|
-
}
|
|
975
|
-
get_initial_selected_index() {
|
|
976
|
-
if (this.selected_base) {
|
|
977
|
-
const base_index = this.rows.findIndex((row) => row.type === "preset" && row.id === this.selected_base);
|
|
978
|
-
if (base_index >= 0) return base_index;
|
|
979
|
-
}
|
|
980
|
-
const active_layer_index = this.rows.findIndex((row) => row.type === "preset" && this.enabled_layers.has(row.id));
|
|
981
|
-
return active_layer_index >= 0 ? active_layer_index : 1;
|
|
982
|
-
}
|
|
983
|
-
toggle_selected() {
|
|
984
|
-
const row = this.rows[this.selected_index];
|
|
985
|
-
if (!row || row.type === "header") return;
|
|
986
|
-
if (row.type === "base-none") this.selected_base = void 0;
|
|
987
|
-
else if (row.preset.kind === "base") this.selected_base = row.preset.name;
|
|
988
|
-
else if (this.enabled_layers.has(row.preset.name)) this.enabled_layers.delete(row.preset.name);
|
|
989
|
-
else this.enabled_layers.add(row.preset.name);
|
|
990
|
-
this.preview_mode = "effective";
|
|
991
|
-
this.preview_offset = 0;
|
|
992
|
-
}
|
|
993
|
-
move(delta) {
|
|
994
|
-
let next = this.selected_index;
|
|
995
|
-
do
|
|
996
|
-
next = (next + delta + this.rows.length) % this.rows.length;
|
|
997
|
-
while (this.rows[next]?.type === "header");
|
|
998
|
-
this.selected_index = next;
|
|
999
|
-
this.preview_offset = 0;
|
|
1000
|
-
}
|
|
1001
|
-
scroll_preview(delta) {
|
|
1002
|
-
const max_offset = Math.max(0, this.preview_lines.length - 1);
|
|
1003
|
-
this.preview_offset = Math.max(0, Math.min(this.preview_offset + delta, max_offset));
|
|
1004
|
-
}
|
|
1005
|
-
pad(text, width) {
|
|
1006
|
-
const visible = visibleWidth(text);
|
|
1007
|
-
return text + " ".repeat(Math.max(0, width - visible));
|
|
1008
|
-
}
|
|
1009
|
-
pad_line(text, width) {
|
|
1010
|
-
const truncated = truncateToWidth(text, width);
|
|
1011
|
-
return truncated + " ".repeat(Math.max(0, width - visibleWidth(truncated)));
|
|
1012
|
-
}
|
|
1013
|
-
};
|
|
1014
|
-
// src/extensions/prompt-presets/index.ts
|
|
1015
|
-
async function prompt_presets(pi) {
|
|
1016
|
-
let presets = {};
|
|
1017
|
-
let active_base_name;
|
|
1018
|
-
let active_layers = /* @__PURE__ */ new Set();
|
|
1019
|
-
function get_base(name) {
|
|
1020
|
-
return name ? presets[name] : void 0;
|
|
1021
|
-
}
|
|
1022
|
-
function get_layer(name) {
|
|
1023
|
-
const preset = presets[name];
|
|
1024
|
-
return preset?.kind === "layer" ? preset : void 0;
|
|
1025
|
-
}
|
|
1026
|
-
function commit_state(ctx, next_base_name, next_layers, options) {
|
|
1027
|
-
active_base_name = next_base_name;
|
|
1028
|
-
active_layers = new Set(next_layers);
|
|
1029
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1030
|
-
if (options?.persist !== false) persist_state(pi, ctx, active_base_name, active_layers);
|
|
1031
|
-
if (options?.notify) ctx.ui.notify(options.notify, "info");
|
|
1032
|
-
}
|
|
1033
|
-
function activate_base(name, ctx, options) {
|
|
1034
|
-
if (!name) {
|
|
1035
|
-
commit_state(ctx, void 0, active_layers, {
|
|
1036
|
-
persist: options?.persist,
|
|
1037
|
-
notify: "Base preset cleared"
|
|
1038
|
-
});
|
|
1039
|
-
return true;
|
|
1040
|
-
}
|
|
1041
|
-
const preset = get_base(name);
|
|
1042
|
-
if (!preset) {
|
|
1043
|
-
ctx.ui.notify(`Unknown base preset: ${name}`, "warning");
|
|
1044
|
-
return false;
|
|
1045
|
-
}
|
|
1046
|
-
commit_state(ctx, preset.name, active_layers, {
|
|
1047
|
-
persist: options?.persist,
|
|
1048
|
-
notify: `Base preset "${preset.name}" activated`
|
|
1049
|
-
});
|
|
1050
|
-
return true;
|
|
1051
|
-
}
|
|
1052
|
-
function set_layer_enabled(name, enabled, ctx, options) {
|
|
1053
|
-
const preset = get_layer(name);
|
|
1054
|
-
if (!preset) {
|
|
1055
|
-
ctx.ui.notify(`Unknown prompt layer: ${name}`, "warning");
|
|
1056
|
-
return false;
|
|
1057
|
-
}
|
|
1058
|
-
const next_layers = new Set(active_layers);
|
|
1059
|
-
if (enabled) next_layers.add(preset.name);
|
|
1060
|
-
else next_layers.delete(preset.name);
|
|
1061
|
-
commit_state(ctx, active_base_name, next_layers, {
|
|
1062
|
-
persist: options?.persist,
|
|
1063
|
-
notify: enabled ? `Layer "${preset.name}" enabled` : `Layer "${preset.name}" disabled`
|
|
1064
|
-
});
|
|
1065
|
-
return true;
|
|
1066
|
-
}
|
|
1067
|
-
function toggle_layer(name, ctx, options) {
|
|
1068
|
-
return set_layer_enabled(name, !active_layers.has(name), ctx, options);
|
|
1069
|
-
}
|
|
1070
|
-
async function edit_preset(name, ctx, scope = "project") {
|
|
1071
|
-
const existing = presets[name];
|
|
1072
|
-
const path = get_prompt_preset_file_path(scope === "global" ? get_global_presets_dir() : get_project_presets_dir(ctx.cwd), name);
|
|
1073
|
-
const initial_markdown = existsSync(path) ? readFileSync(path, "utf-8") : format_prompt_preset_markdown({
|
|
1074
|
-
kind: existing?.kind ?? "layer",
|
|
1075
|
-
instructions: existing?.instructions ?? "",
|
|
1076
|
-
...existing?.description ? { description: existing.description } : {}
|
|
1077
|
-
});
|
|
1078
|
-
const edited = await ctx.ui.editor(`Edit ${scope} preset markdown: ${name}`, initial_markdown);
|
|
1079
|
-
if (edited === void 0) return;
|
|
1080
|
-
const { metadata, body } = parse_prompt_preset_markdown(edited);
|
|
1081
|
-
if (!body.trim()) {
|
|
1082
|
-
ctx.ui.notify("Preset instructions cannot be empty", "warning");
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
const kind = metadata.kind === "base" ? "base" : "layer";
|
|
1086
|
-
const preset = {
|
|
1087
|
-
kind,
|
|
1088
|
-
instructions: body,
|
|
1089
|
-
...typeof metadata.description === "string" && metadata.description.trim() ? { description: metadata.description.trim() } : {}
|
|
1090
|
-
};
|
|
1091
|
-
const saved_path = scope === "global" ? save_global_prompt_preset_file(name, preset) : save_project_prompt_preset_file(ctx.cwd, name, preset);
|
|
1092
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1093
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1094
|
-
active_base_name = normalized.active_base_name;
|
|
1095
|
-
active_layers = normalized.active_layers;
|
|
1096
|
-
if (kind === "base") activate_base(name, ctx);
|
|
1097
|
-
else set_layer_enabled(name, true, ctx);
|
|
1098
|
-
ctx.ui.notify(`Saved preset "${name}" to ${saved_path}`, "info");
|
|
1099
|
-
}
|
|
1100
|
-
function export_default_presets(ctx, scope) {
|
|
1101
|
-
const dir = scope === "global" ? get_global_presets_dir() : get_project_presets_dir(ctx.cwd);
|
|
1102
|
-
let written = 0;
|
|
1103
|
-
let skipped = 0;
|
|
1104
|
-
for (const [name, preset] of Object.entries(DEFAULT_PROMPT_PRESETS)) {
|
|
1105
|
-
const path = get_prompt_preset_file_path(dir, name);
|
|
1106
|
-
if (existsSync(path)) {
|
|
1107
|
-
skipped += 1;
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
save_prompt_preset_file(dir, name, preset);
|
|
1111
|
-
written += 1;
|
|
1112
|
-
}
|
|
1113
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1114
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1115
|
-
active_base_name = normalized.active_base_name;
|
|
1116
|
-
active_layers = normalized.active_layers;
|
|
1117
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1118
|
-
ctx.ui.notify(`Exported ${written} built-in preset file(s) to ${dir}${skipped ? ` (${skipped} already existed)` : ""}`, "info");
|
|
1119
|
-
}
|
|
1120
|
-
function remove_custom_preset(name, ctx, mode) {
|
|
1121
|
-
const result = remove_project_prompt_preset(ctx.cwd, name);
|
|
1122
|
-
if (!result.removed) {
|
|
1123
|
-
ctx.ui.notify(`No project-local preset named "${name}" to ${mode}`, "warning");
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1127
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1128
|
-
active_base_name = normalized.active_base_name;
|
|
1129
|
-
active_layers = normalized.active_layers;
|
|
1130
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1131
|
-
persist_state(pi, ctx, active_base_name, active_layers);
|
|
1132
|
-
const fallback = presets[name];
|
|
1133
|
-
if (mode === "reset" && fallback) {
|
|
1134
|
-
ctx.ui.notify(`Reset "${name}" to ${get_prompt_source_label(fallback.source)} preset`, "info");
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
ctx.ui.notify(result.remaining === 0 ? `Removed "${name}" and deleted ${result.path}` : `Removed "${name}" from ${result.path}`, "info");
|
|
1138
|
-
}
|
|
1139
|
-
async function choose_scope(ctx, action) {
|
|
1140
|
-
const choice = await ctx.ui.select(`${action}: choose where to write`, ["Project (.pi/presets)", "Global (~/.pi/agent/presets)"]);
|
|
1141
|
-
return choice?.startsWith("Project") ? "project" : choice ? "global" : void 0;
|
|
1142
|
-
}
|
|
1143
|
-
function refresh_after_management(ctx) {
|
|
1144
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1145
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1146
|
-
active_base_name = normalized.active_base_name;
|
|
1147
|
-
active_layers = normalized.active_layers;
|
|
1148
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1149
|
-
persist_state(pi, ctx, active_base_name, active_layers);
|
|
1150
|
-
}
|
|
1151
|
-
async function show_manager(ctx) {
|
|
1152
|
-
while (true) {
|
|
1153
|
-
const result = await show_prompt_preset_manager(ctx, {
|
|
1154
|
-
presets,
|
|
1155
|
-
active_base_name,
|
|
1156
|
-
active_layers
|
|
1157
|
-
});
|
|
1158
|
-
if (result.action === "cancel") return;
|
|
1159
|
-
if (result.action === "apply") {
|
|
1160
|
-
commit_state(ctx, result.base_name, result.layers, { notify: "Updated prompt preset selection" });
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
try {
|
|
1164
|
-
if (result.action === "reload") {
|
|
1165
|
-
const catalog = reload_prompt_preset_library(ctx.cwd);
|
|
1166
|
-
presets = catalog.presets;
|
|
1167
|
-
refresh_after_management(ctx);
|
|
1168
|
-
const diagnostics = catalog.diagnostics;
|
|
1169
|
-
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");
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
const selected = result.preset;
|
|
1173
|
-
if (result.action === "create") {
|
|
1174
|
-
const name = await ctx.ui.input("Create preset", "Preset name");
|
|
1175
|
-
if (!name) continue;
|
|
1176
|
-
const scope = await choose_scope(ctx, "Create preset");
|
|
1177
|
-
if (!scope) continue;
|
|
1178
|
-
await edit_preset(name.trim(), ctx, scope);
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
if (!selected) continue;
|
|
1182
|
-
if (result.action === "edit" && !selected.editable) {
|
|
1183
|
-
ctx.ui.notify("Built-in presets are read-only. Copy it before editing.", "warning");
|
|
1184
|
-
continue;
|
|
1185
|
-
}
|
|
1186
|
-
const scope = await choose_scope(ctx, `${result.action} ${selected.name}`);
|
|
1187
|
-
if (!scope) continue;
|
|
1188
|
-
if (result.action === "edit") await edit_preset(selected.name, ctx, scope);
|
|
1189
|
-
else if (result.action === "copy") {
|
|
1190
|
-
const target = await ctx.ui.input("Copy preset", selected.name);
|
|
1191
|
-
if (!target) continue;
|
|
1192
|
-
copy_prompt_preset(ctx.cwd, selected.name, selected.origin === "builtin" ? "builtin" : selected.origin.startsWith("project") ? "project" : "global", scope, target.trim());
|
|
1193
|
-
} else if (result.action === "rename") {
|
|
1194
|
-
if (!selected.editable) {
|
|
1195
|
-
ctx.ui.notify("Built-in presets are read-only. Copy it before renaming.", "warning");
|
|
1196
|
-
continue;
|
|
1197
|
-
}
|
|
1198
|
-
const target = await ctx.ui.input("Rename preset", selected.name);
|
|
1199
|
-
if (!target) continue;
|
|
1200
|
-
const mutation = rename_prompt_preset(ctx.cwd, scope, selected.name, target.trim(), {
|
|
1201
|
-
base_name: active_base_name ?? null,
|
|
1202
|
-
layer_names: [...active_layers]
|
|
1203
|
-
});
|
|
1204
|
-
if (mutation.state) {
|
|
1205
|
-
active_base_name = mutation.state.base_name ?? void 0;
|
|
1206
|
-
active_layers = new Set(mutation.state.layer_names);
|
|
1207
|
-
}
|
|
1208
|
-
} else {
|
|
1209
|
-
if (!selected.editable) {
|
|
1210
|
-
ctx.ui.notify("Built-in presets are read-only.", "warning");
|
|
1211
|
-
continue;
|
|
1212
|
-
}
|
|
1213
|
-
if (!await ctx.ui.confirm(result.action === "delete" ? "Delete preset?" : "Reset override?", `${selected.name} (${scope})`)) continue;
|
|
1214
|
-
if (result.action === "delete") delete_prompt_preset(ctx.cwd, scope, selected.name, true);
|
|
1215
|
-
else reset_prompt_preset_override(ctx.cwd, scope, selected.name);
|
|
1216
|
-
}
|
|
1217
|
-
refresh_after_management(ctx);
|
|
1218
|
-
ctx.ui.notify(`${result.action} completed for "${selected.name}"`, "info");
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
pi.registerFlag("preset", {
|
|
1225
|
-
description: "Activate prompt config on startup. Accepts a base preset or comma-separated preset/layer names.",
|
|
1226
|
-
type: "string"
|
|
1227
|
-
});
|
|
1228
|
-
const prompt_preset_command = {
|
|
1229
|
-
description: "Manage prompt presets and layers. Try: /prompt-preset help, /prompt-preset export-defaults, /prompt-preset edit-global terse",
|
|
1230
|
-
getArgumentCompletions: (prefix) => get_prompt_preset_completions(presets, prefix),
|
|
1231
|
-
handler: async (args, ctx) => {
|
|
1232
|
-
const trimmed = args.trim();
|
|
1233
|
-
if (!trimmed) {
|
|
1234
|
-
if (ctx.hasUI && ctx.mode === "tui") {
|
|
1235
|
-
await show_manager(ctx);
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
const [first, ...rest] = trimmed.split(/\s+/);
|
|
1242
|
-
const arg = rest.join(" ").trim();
|
|
1243
|
-
switch (first) {
|
|
1244
|
-
case "help":
|
|
1245
|
-
ctx.ui.notify(format_prompt_preset_help(), "info");
|
|
1246
|
-
return;
|
|
1247
|
-
case "list":
|
|
1248
|
-
ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
|
|
1249
|
-
return;
|
|
1250
|
-
case "show":
|
|
1251
|
-
ctx.ui.notify(format_active_details(active_base_name, active_layers, presets), "info");
|
|
1252
|
-
return;
|
|
1253
|
-
case "clear":
|
|
1254
|
-
commit_state(ctx, void 0, /* @__PURE__ */ new Set(), { notify: "Cleared base preset and prompt layers" });
|
|
1255
|
-
return;
|
|
1256
|
-
case "reload": {
|
|
1257
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1258
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1259
|
-
active_base_name = normalized.active_base_name;
|
|
1260
|
-
active_layers = normalized.active_layers;
|
|
1261
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1262
|
-
ctx.ui.notify("Reloaded prompt presets", "info");
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
case "base":
|
|
1266
|
-
if (!arg) {
|
|
1267
|
-
ctx.ui.notify("Usage: /prompt-preset base <name> (alias: /preset)", "warning");
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
activate_base(arg, ctx);
|
|
1271
|
-
return;
|
|
1272
|
-
case "enable":
|
|
1273
|
-
if (!arg) {
|
|
1274
|
-
ctx.ui.notify("Usage: /prompt-preset enable <layer> (alias: /preset)", "warning");
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
set_layer_enabled(arg, true, ctx);
|
|
1278
|
-
return;
|
|
1279
|
-
case "disable":
|
|
1280
|
-
if (!arg) {
|
|
1281
|
-
ctx.ui.notify("Usage: /prompt-preset disable <layer> (alias: /preset)", "warning");
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
set_layer_enabled(arg, false, ctx);
|
|
1285
|
-
return;
|
|
1286
|
-
case "toggle":
|
|
1287
|
-
if (!arg) {
|
|
1288
|
-
ctx.ui.notify("Usage: /prompt-preset toggle <layer> (alias: /preset)", "warning");
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
toggle_layer(arg, ctx);
|
|
1292
|
-
return;
|
|
1293
|
-
case "edit": {
|
|
1294
|
-
let scope = "project";
|
|
1295
|
-
let name = arg;
|
|
1296
|
-
if (arg.startsWith("--global ")) {
|
|
1297
|
-
scope = "global";
|
|
1298
|
-
name = arg.slice(9).trim();
|
|
1299
|
-
} else if (arg.startsWith("--project ")) name = arg.slice(10).trim();
|
|
1300
|
-
if (!name) {
|
|
1301
|
-
ctx.ui.notify("Usage: /prompt-preset edit [--global|--project] <name> (alias: /preset)", "warning");
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1304
|
-
await edit_preset(name, ctx, scope);
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
case "edit-global":
|
|
1308
|
-
if (!arg) {
|
|
1309
|
-
ctx.ui.notify("Usage: /prompt-preset edit-global <name> (alias: /preset)", "warning");
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
await edit_preset(arg, ctx, "global");
|
|
1313
|
-
return;
|
|
1314
|
-
case "export-defaults": {
|
|
1315
|
-
const scope = arg || "global";
|
|
1316
|
-
if (scope !== "global" && scope !== "project") {
|
|
1317
|
-
ctx.ui.notify("Usage: /prompt-preset export-defaults [global|project] (alias: /preset)", "warning");
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
export_default_presets(ctx, scope);
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
case "delete":
|
|
1324
|
-
if (!arg) {
|
|
1325
|
-
ctx.ui.notify("Usage: /prompt-preset delete <name> (alias: /preset)", "warning");
|
|
1326
|
-
return;
|
|
1327
|
-
}
|
|
1328
|
-
remove_custom_preset(arg, ctx, "delete");
|
|
1329
|
-
return;
|
|
1330
|
-
case "reset":
|
|
1331
|
-
if (!arg) {
|
|
1332
|
-
ctx.ui.notify("Usage: /prompt-preset reset <name> (alias: /preset)", "warning");
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
remove_custom_preset(arg, ctx, "reset");
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
if (is_subcommand(first)) {
|
|
1339
|
-
ctx.ui.notify(`Unsupported preset command: ${first}`, "warning");
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
const preset = presets[trimmed];
|
|
1343
|
-
if (!preset) {
|
|
1344
|
-
ctx.ui.notify(`Unknown preset or layer: ${trimmed}. Try /prompt-preset help.`, "warning");
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1347
|
-
if (preset.kind === "base") activate_base(preset.name, ctx);
|
|
1348
|
-
else toggle_layer(preset.name, ctx);
|
|
1349
|
-
}
|
|
1350
|
-
};
|
|
1351
|
-
for (const command_name of ["prompt-preset", "preset"]) pi.registerCommand(command_name, prompt_preset_command);
|
|
1352
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1353
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
1354
|
-
active_base_name = void 0;
|
|
1355
|
-
active_layers = /* @__PURE__ */ new Set();
|
|
1356
|
-
const preset_flag = pi.getFlag("preset");
|
|
1357
|
-
if (typeof preset_flag === "string" && preset_flag.trim()) {
|
|
1358
|
-
for (const name of parse_preset_flag(preset_flag)) {
|
|
1359
|
-
const preset = presets[name];
|
|
1360
|
-
if (!preset) continue;
|
|
1361
|
-
if (preset.kind === "base") active_base_name = name;
|
|
1362
|
-
else active_layers.add(name);
|
|
1363
|
-
}
|
|
1364
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1365
|
-
active_base_name = normalized.active_base_name;
|
|
1366
|
-
active_layers = normalized.active_layers;
|
|
1367
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
const restored = get_last_preset_state(ctx) ?? load_persisted_prompt_state(ctx.cwd) ?? {
|
|
1371
|
-
base_name: "terse",
|
|
1372
|
-
layer_names: []
|
|
1373
|
-
};
|
|
1374
|
-
active_base_name = restored.base_name ?? void 0;
|
|
1375
|
-
active_layers = new Set(restored.layer_names ?? []);
|
|
1376
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
1377
|
-
active_base_name = normalized.active_base_name;
|
|
1378
|
-
active_layers = normalized.active_layers;
|
|
1379
|
-
set_status(ctx, active_base_name, active_layers);
|
|
1380
|
-
});
|
|
1381
|
-
pi.on("before_agent_start", async (event) => {
|
|
1382
|
-
const blocks = build_active_prompt_blocks(presets, active_base_name, active_layers);
|
|
1383
|
-
if (blocks.length === 0) return;
|
|
1384
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${blocks.join("\n\n")}` };
|
|
1385
|
-
});
|
|
1386
|
-
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1387
|
-
ctx.ui.setStatus("preset", void 0);
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
export { prompt_presets as default };
|
|
1391
|
-
|
|
1392
|
-
//# sourceMappingURL=prompt-presets-DOn1oZFJ.js.map
|