pi-custom-system-prompt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-06-21
9
+
10
+ ### Added
11
+
12
+ - Initial release.
13
+ - Load a custom system prompt from `~/.pi/agent/system-prompts/<file>.md` and inject it on every agent turn via the `before_agent_start` event.
14
+ - Commands: `/system-prompt-info`, `/system-prompt-toggle`, `/system-prompt-reload`, `/system-prompt-mode`, `/system-prompt-show`, `/system-prompt-select`.
15
+ - Two modes: `append` (default; keep Pi's prompt as the base and add the custom prompt as an extra section) and `replace` (use the custom prompt as the base, with Pi's tools and user customizations appended after it).
16
+ - Multiple `.md` files can coexist in the prompt directory; use `/system-prompt-select` to switch between them.
17
+ - Persistent state (`enabled`, `mode`, `selectedFile`) saved to `~/.pi/agent/state/system-prompt.json` and reloaded on next start.
18
+ - Footer status widget: `system-prompt: <file>` when enabled, `system-prompt: disabled` when not.
19
+ - `PI_SYSTEM_PROMPT_DIR` environment variable to override the default prompt directory.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nocte
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # pi-custom-system-prompt
2
+
3
+ A [pi](https://pi.dev) extension that loads a custom system prompt from a markdown file and injects it into every agent turn. Works with any model or provider — nothing is hardcoded.
4
+
5
+ Multiple `.md` files can live in the prompt directory; pick which one is active with `/system-prompt-select`. All changes take effect on the very next message you send — no `/new` or session restart required.
6
+
7
+ ## Commands
8
+
9
+ | Command | What it does |
10
+ |---|---|
11
+ | `/system-prompt-info` | Show loaded path, size, mode, enabled state, available files |
12
+ | `/system-prompt-toggle` | Enable or disable the custom prompt |
13
+ | `/system-prompt-reload` | Re-read the selected prompt file from disk |
14
+ | `/system-prompt-mode [replace\|append]` | Toggle or set the merge mode |
15
+ | `/system-prompt-show` | Print the first ~800 chars of the loaded prompt |
16
+ | `/system-prompt-select` | Pick a different prompt file from the directory |
17
+
18
+ ## Modes
19
+
20
+ **`append`** (default) — keep Pi's default system prompt as the base, and add the custom prompt as an extra section. Safest for most models, since Pi's tool descriptions stay authoritative.
21
+
22
+ **`replace`** — use the custom prompt as the base system prompt. Pi's tool descriptions, current date, working directory, and any `--append-system-prompt` you passed are appended after it.
23
+
24
+ ## Setup
25
+
26
+ ### 1. Install the extension
27
+
28
+ ```bash
29
+ pi install npm:pi-custom-system-prompt
30
+ ```
31
+
32
+ Or try it without installing:
33
+
34
+ ```bash
35
+ pi -e npm:pi-custom-system-prompt
36
+ ```
37
+
38
+ ### 2. Create the prompt directory
39
+
40
+ ```bash
41
+ mkdir -p ~/.pi/agent/system-prompts
42
+ ```
43
+
44
+ The extension reads from this directory by default. To use a different location, set the `PI_SYSTEM_PROMPT_DIR` environment variable.
45
+
46
+ ### 3. Add a markdown file
47
+
48
+ ```bash
49
+ # Example: a focused prompt for a specific model
50
+ cat > ~/.pi/agent/system-prompts/claude-code.md <<'EOF'
51
+ You are a careful, terse coding assistant.
52
+ - Read files before editing them.
53
+ - Prefer minimal diffs.
54
+ - Explain non-obvious choices in one sentence.
55
+ EOF
56
+
57
+ # Another example: a personality-style prompt
58
+ cat > ~/.pi/agent/system-prompts/pirate.md <<'EOF'
59
+ You are a pirate captain. Speak in pirate vernacular but stay accurate
60
+ about technical content. Never break character.
61
+ EOF
62
+ ```
63
+
64
+ The first file (alphabetical) is auto-selected on first run. Use `/system-prompt-select` to switch between them.
65
+
66
+ ### 4. Verify and toggle
67
+
68
+ ```bash
69
+ # inside pi:
70
+ /system-prompt-info # confirm the file loaded
71
+ /system-prompt-toggle off # disable without deleting the file
72
+ /system-prompt-toggle on # re-enable
73
+ ```
74
+
75
+ The footer shows the active state:
76
+
77
+ ```
78
+ system-prompt: claude-code.md # enabled, showing the file
79
+ system-prompt: enabled # enabled, no file selected
80
+ system-prompt: disabled # disabled
81
+ ```
82
+
83
+ ## How it works
84
+
85
+ - The extension subscribes to `before_agent_start`, which fires on every user message. It reads the selected `.md` file from the prompt directory, then either appends it to or replaces Pi's resolved system prompt before sending to the model.
86
+ - State (`enabled`, `mode`, `selectedFile`) persists to `~/.pi/agent/state/system-prompt.json` and is reloaded on the next start.
87
+ - All commands are non-destructive: they mutate in-memory state, save to the state file, and take effect on the next message.
88
+ - If the prompt directory is missing, has no `.md` files, or the selected file is empty/unreadable, the extension logs the reason to `/system-prompt-info` and falls back to Pi's default prompt with no error.
89
+
90
+ ## Removing the extension
91
+
92
+ ```bash
93
+ pi remove npm:pi-custom-system-prompt
94
+ ```
95
+
96
+ This unloads the extension but does not delete your prompt files in `~/.pi/agent/system-prompts/` or your saved state in `~/.pi/agent/state/system-prompt.json`. Delete those manually if you want a clean uninstall:
97
+
98
+ ```bash
99
+ rm -rf ~/.pi/agent/system-prompts
100
+ rm -f ~/.pi/agent/state/system-prompt.json
101
+ ```
102
+
103
+ ## Environment variables
104
+
105
+ | Variable | Default | Effect |
106
+ |---|---|---|
107
+ | `PI_SYSTEM_PROMPT_DIR` | `~/.pi/agent/system-prompts` | Override the directory the extension scans for `.md` prompt files |
108
+
109
+ ## Development
110
+
111
+ The extension lives in [`extensions/system-prompt.ts`](extensions/system-prompt.ts). To load it from a local checkout during development:
112
+
113
+ ```bash
114
+ pi -e /absolute/path/to/pi-custom-system-prompt/extensions/system-prompt.ts
115
+ ```
116
+
117
+ For a hot-reload loop, symlink it into the global extensions directory:
118
+
119
+ ```bash
120
+ ln -s "$(pwd)/extensions/system-prompt.ts" ~/.pi/agent/extensions/system-prompt.ts
121
+ ```
122
+
123
+ Then `/reload` inside pi after each edit.
124
+
125
+ ### Layout
126
+
127
+ ```
128
+ pi-custom-system-prompt/
129
+ ├── extensions/
130
+ │ └── system-prompt.ts
131
+ ├── package.json
132
+ ├── tsconfig.json
133
+ ├── README.md
134
+ ├── CHANGELOG.md
135
+ └── LICENSE
136
+ ```
137
+
138
+ The `extensions/` directory is a pi convention: any `.ts` file inside it is auto-discovered. The `pi-package` keyword in `package.json` makes the package appear in the [pi package gallery](https://pi.dev/packages).
139
+
140
+ ## License
141
+
142
+ [MIT](LICENSE)
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Custom System Prompt Extension for Pi
3
+ *
4
+ * Loads a custom system prompt from ~/.pi/agent/system-prompts/ and injects
5
+ * it into every agent turn. Works with any model or agent — nothing is
6
+ * hardcoded.
7
+ *
8
+ * Multiple .md files can coexist in the prompt directory. Use
9
+ * /system-prompt-select to pick which one is active.
10
+ *
11
+ * Commands
12
+ * --------
13
+ * /system-prompt-info Show loaded path, size, mode, enabled state
14
+ * /system-prompt-toggle Enable or disable the custom prompt
15
+ * /system-prompt-reload Re-read the selected prompt file from disk
16
+ * /system-prompt-mode Toggle between "replace" and "append" mode
17
+ * /system-prompt-show Print the first ~800 chars of the loaded prompt
18
+ * /system-prompt-select Pick a different prompt file from the directory
19
+ *
20
+ * How changes take effect
21
+ * -----------------------
22
+ * The custom prompt is injected via the before_agent_start event, which
23
+ * fires on every user message. This means all changes (toggle, reload,
24
+ * mode, select) take effect on the very next message you send — no /new
25
+ * or session restart required.
26
+ *
27
+ * Modes
28
+ * -----
29
+ * append (default) — Keep Pi's default system prompt as the base and add
30
+ * the custom prompt as an extra section. Safest for
31
+ * most models since Pi's tool descriptions stay
32
+ * authoritative.
33
+ * replace — Use the custom prompt as the base system prompt.
34
+ * Pi's tool descriptions, date, cwd, and user
35
+ * customizations are appended after it.
36
+ *
37
+ * State persistence
38
+ * -----------------
39
+ * enabled, mode, and selectedFile are persisted to:
40
+ * ~/.pi/agent/state/system-prompt.json
41
+ * and survive Pi restarts.
42
+ *
43
+ * Footer indicator
44
+ * ----------------
45
+ * system-prompt: disabled (when disabled)
46
+ * system-prompt: claude-code.md (when enabled, showing filename)
47
+ *
48
+ * Environment variables
49
+ * ---------------------
50
+ * PI_SYSTEM_PROMPT_DIR Override the default prompt directory
51
+ * (default: ~/.pi/agent/system-prompts)
52
+ */
53
+
54
+ import * as fs from "node:fs";
55
+ import * as os from "node:os";
56
+ import * as path from "node:path";
57
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
58
+
59
+ const DEFAULT_PROMPT_DIR = path.join(
60
+ os.homedir(),
61
+ ".pi",
62
+ "agent",
63
+ "system-prompts",
64
+ );
65
+
66
+ const STATE_PATH = path.join(
67
+ os.homedir(),
68
+ ".pi",
69
+ "agent",
70
+ "state",
71
+ "system-prompt.json",
72
+ );
73
+
74
+ type Mode = "replace" | "append";
75
+
76
+ interface PersistedState {
77
+ enabled: boolean;
78
+ mode: Mode;
79
+ selectedFile: string;
80
+ }
81
+
82
+ export default function systemPromptExtension(pi: ExtensionAPI) {
83
+ const promptDir: string =
84
+ process.env.PI_SYSTEM_PROMPT_DIR?.trim() || DEFAULT_PROMPT_DIR;
85
+
86
+ let enabled: boolean;
87
+ let mode: Mode;
88
+ let selectedFile: string;
89
+ let promptContent: string | null = null;
90
+ let loadError: string | null = null;
91
+ let lastLoaded: number | null = null;
92
+
93
+ // --- State persistence ---------------------------------------------------
94
+
95
+ function loadState(): PersistedState {
96
+ try {
97
+ if (!fs.existsSync(STATE_PATH)) {
98
+ return { enabled: true, mode: "append", selectedFile: "" };
99
+ }
100
+ const raw = fs.readFileSync(STATE_PATH, "utf-8");
101
+ const parsed = JSON.parse(raw) as Partial<PersistedState>;
102
+ return {
103
+ enabled: parsed.enabled === false ? false : true,
104
+ mode: parsed.mode === "replace" ? "replace" : "append",
105
+ selectedFile:
106
+ typeof parsed.selectedFile === "string"
107
+ ? parsed.selectedFile
108
+ : "",
109
+ };
110
+ } catch {
111
+ return { enabled: true, mode: "append", selectedFile: "" };
112
+ }
113
+ }
114
+
115
+ function saveState(): void {
116
+ try {
117
+ fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true });
118
+ fs.writeFileSync(
119
+ STATE_PATH,
120
+ JSON.stringify(
121
+ { enabled, mode, selectedFile } satisfies PersistedState,
122
+ null,
123
+ 2,
124
+ ),
125
+ );
126
+ } catch {
127
+ // best-effort — state persistence failure should not break anything
128
+ }
129
+ }
130
+
131
+ // Initialize from persisted state
132
+ const initial = loadState();
133
+ enabled = initial.enabled;
134
+ mode = initial.mode;
135
+ selectedFile = initial.selectedFile;
136
+
137
+ // --- Prompt file management ----------------------------------------------
138
+
139
+ function listPromptFiles(): string[] {
140
+ try {
141
+ if (!fs.existsSync(promptDir)) return [];
142
+ return fs
143
+ .readdirSync(promptDir, { withFileTypes: true })
144
+ .filter((d) => d.isFile() && d.name.endsWith(".md"))
145
+ .map((d) => d.name)
146
+ .sort();
147
+ } catch {
148
+ return [];
149
+ }
150
+ }
151
+
152
+ function ensureSelectedFile(): void {
153
+ const files = listPromptFiles();
154
+ if (files.length === 0) {
155
+ selectedFile = "";
156
+ return;
157
+ }
158
+ if (!selectedFile || !files.includes(selectedFile)) {
159
+ selectedFile = files[0];
160
+ saveState();
161
+ }
162
+ }
163
+
164
+ function loadPrompt(): void {
165
+ ensureSelectedFile();
166
+
167
+ if (!selectedFile) {
168
+ promptContent = null;
169
+ loadError = fs.existsSync(promptDir)
170
+ ? `no .md files in ${shortPath(promptDir)}`
171
+ : `directory not found: ${shortPath(promptDir)}`;
172
+ return;
173
+ }
174
+
175
+ const filePath = path.join(promptDir, selectedFile);
176
+ try {
177
+ if (!fs.existsSync(filePath)) {
178
+ promptContent = null;
179
+ loadError = `file not found: ${shortPath(filePath)}`;
180
+ return;
181
+ }
182
+ const content = fs.readFileSync(filePath, "utf-8");
183
+ if (!content.trim()) {
184
+ promptContent = null;
185
+ loadError = `file is empty: ${shortPath(filePath)}`;
186
+ return;
187
+ }
188
+ promptContent = content;
189
+ loadError = null;
190
+ lastLoaded = Date.now();
191
+ } catch (err) {
192
+ promptContent = null;
193
+ loadError = `read failed: ${err instanceof Error ? err.message : String(err)}`;
194
+ }
195
+ }
196
+
197
+ function shortPath(p: string): string {
198
+ const home = os.homedir();
199
+ return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
200
+ }
201
+
202
+ // --- Footer status -------------------------------------------------------
203
+
204
+ function updateStatus(ctx: {
205
+ hasUI: boolean;
206
+ ui: { setStatus: (id: string, value: string | undefined) => void };
207
+ }): void {
208
+ if (!ctx.hasUI) return;
209
+ if (enabled && selectedFile) {
210
+ ctx.ui.setStatus("system-prompt", `system-prompt: ${selectedFile}`);
211
+ } else if (enabled) {
212
+ ctx.ui.setStatus("system-prompt", "system-prompt: enabled");
213
+ } else {
214
+ ctx.ui.setStatus("system-prompt", "system-prompt: disabled");
215
+ }
216
+ }
217
+
218
+ // --- Initial load --------------------------------------------------------
219
+ loadPrompt();
220
+
221
+ // --- Command handlers ----------------------------------------------------
222
+
223
+ pi.registerCommand("system-prompt-info", {
224
+ description: "Show custom system prompt info (path, size, mode, state)",
225
+ handler: async (_args, ctx) => {
226
+ const files = listPromptFiles();
227
+ const lines = [
228
+ `Directory: ${shortPath(promptDir)}`,
229
+ `File: ${selectedFile || "(none)"}`,
230
+ `Mode: ${mode}`,
231
+ `Enabled: ${enabled}`,
232
+ `Status: ${loadError ?? "loaded"}`,
233
+ `Size: ${promptContent?.length ?? 0} chars`,
234
+ `Loaded: ${lastLoaded ? new Date(lastLoaded).toLocaleTimeString() : "never"}`,
235
+ `Available: ${files.length > 0 ? files.join(", ") : "(none)"}`,
236
+ ];
237
+ ctx.ui.notify(lines.join("\n"), "info");
238
+ },
239
+ });
240
+
241
+ pi.registerCommand("system-prompt-toggle", {
242
+ description: "Enable or disable the custom system prompt",
243
+ handler: async (_args, ctx) => {
244
+ enabled = !enabled;
245
+ saveState();
246
+ updateStatus(ctx);
247
+ const state = enabled ? "enabled" : "disabled";
248
+ ctx.ui.notify(
249
+ `System prompt ${state}. Takes effect on next message.`,
250
+ "info",
251
+ );
252
+ },
253
+ });
254
+
255
+ pi.registerCommand("system-prompt-reload", {
256
+ description: "Reload the custom system prompt from disk",
257
+ handler: async (_args, ctx) => {
258
+ loadPrompt();
259
+ updateStatus(ctx);
260
+ if (loadError) {
261
+ ctx.ui.notify(`Reload failed: ${loadError}`, "error");
262
+ } else {
263
+ ctx.ui.notify(
264
+ `Reloaded ${selectedFile} (${promptContent!.length} chars). Takes effect on next message.`,
265
+ "info",
266
+ );
267
+ }
268
+ },
269
+ });
270
+
271
+ pi.registerCommand("system-prompt-select", {
272
+ description: "Select which .md file to use as the system prompt",
273
+ handler: async (_args, ctx) => {
274
+ const files = listPromptFiles();
275
+ if (files.length === 0) {
276
+ ctx.ui.notify(
277
+ `No .md files found in ${shortPath(promptDir)}\nCreate prompt files there to use this extension.`,
278
+ "error",
279
+ );
280
+ return;
281
+ }
282
+ if (files.length === 1) {
283
+ if (files[0] === selectedFile) {
284
+ ctx.ui.notify(`Only one prompt available: ${files[0]} (already selected)`, "info");
285
+ return;
286
+ }
287
+ selectedFile = files[0];
288
+ saveState();
289
+ loadPrompt();
290
+ updateStatus(ctx);
291
+ ctx.ui.notify(
292
+ `Selected: ${selectedFile} (${promptContent?.length ?? 0} chars). Takes effect on next message.`,
293
+ "info",
294
+ );
295
+ return;
296
+ }
297
+
298
+ const choice = await ctx.ui.select(
299
+ "Select system prompt",
300
+ files.map((f) => (f === selectedFile ? `${f} ✓` : f)),
301
+ );
302
+ if (!choice) return;
303
+
304
+ // Strip the checkmark suffix if present
305
+ const file = choice.replace(/\s+✓$/, "");
306
+ if (file === selectedFile) return;
307
+
308
+ selectedFile = file;
309
+ saveState();
310
+ loadPrompt();
311
+ updateStatus(ctx);
312
+ ctx.ui.notify(
313
+ `Selected: ${selectedFile} (${promptContent?.length ?? 0} chars). Takes effect on next message.`,
314
+ "info",
315
+ );
316
+ },
317
+ });
318
+
319
+ pi.registerCommand("system-prompt-mode", {
320
+ description: "Toggle mode (replace: use selected file | append: add along with Pi's prompt)",
321
+ handler: async (args, ctx) => {
322
+ const arg = args.trim().toLowerCase();
323
+ if (arg === "replace" || arg === "append") {
324
+ mode = arg;
325
+ } else if (arg === "" || arg === "toggle") {
326
+ mode = mode === "replace" ? "append" : "replace";
327
+ } else {
328
+ ctx.ui.notify(`Usage: /system-prompt-mode [replace|append]`, "error");
329
+ return;
330
+ }
331
+ saveState();
332
+ ctx.ui.notify(
333
+ `Mode: ${mode}. Takes effect on next message.`,
334
+ "info",
335
+ );
336
+ },
337
+ });
338
+
339
+ pi.registerCommand("system-prompt-show", {
340
+ description: "Show first ~800 chars of the loaded custom system prompt",
341
+ handler: async (_args, ctx) => {
342
+ if (!promptContent) {
343
+ ctx.ui.notify(`Not loaded: ${loadError ?? "unknown error"}`, "error");
344
+ return;
345
+ }
346
+ const preview = promptContent.slice(0, 800);
347
+ const tail = promptContent.length > 800 ? "\n\n[... truncated]" : "";
348
+ ctx.ui.notify(`${preview}${tail}`, "info");
349
+ },
350
+ });
351
+
352
+ // --- Lifecycle -----------------------------------------------------------
353
+
354
+ pi.on("session_start", async (_event, ctx) => {
355
+ // Re-read on every start so a hand-edited file gets picked up.
356
+ loadPrompt();
357
+ updateStatus(ctx);
358
+ });
359
+
360
+ pi.on("session_shutdown", (_event, ctx) => {
361
+ if (ctx.hasUI) ctx.ui.setStatus("system-prompt", undefined);
362
+ });
363
+
364
+ // --- Inject the prompt ---------------------------------------------------
365
+
366
+ pi.on("before_agent_start", async (event) => {
367
+ // Graceful no-op when disabled or when the prompt file is missing/unreadable.
368
+ if (!enabled || !promptContent) return;
369
+
370
+ const opts = event.systemPromptOptions;
371
+ const appendSystemPrompt: string = opts?.appendSystemPrompt ?? "";
372
+ const customPrompt: string = opts?.customPrompt ?? "";
373
+
374
+ // Build tool listing preserving tool names
375
+ const snippets = opts?.toolSnippets ?? {};
376
+ const toolLines = Object.entries(snippets)
377
+ .map(([name, desc]) => `- ${name}: ${desc}`)
378
+ .join("\n");
379
+ const toolsSection = toolLines
380
+ ? "\n\n## Available tools\n\n" +
381
+ "The following tools are available in this environment. " +
382
+ "Use them as described below instead of the tools mentioned in the system prompt above.\n\n" +
383
+ toolLines
384
+ : "";
385
+
386
+ if (mode === "replace") {
387
+ // Custom prompt is the base. Stack Pi's tools + user customizations after.
388
+ const now = new Date();
389
+ const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
390
+ const cwd = opts?.cwd ?? "unknown";
391
+ const customSection = customPrompt ? `\n\n${customPrompt}` : "";
392
+ const appendSection = appendSystemPrompt
393
+ ? `\n\n${appendSystemPrompt}`
394
+ : "";
395
+ return {
396
+ systemPrompt:
397
+ promptContent +
398
+ toolsSection +
399
+ customSection +
400
+ appendSection +
401
+ `\nCurrent date: ${dateStr}` +
402
+ `\nCurrent working directory: ${cwd}`,
403
+ };
404
+ }
405
+
406
+ // Append mode: keep Pi's prompt, add custom prompt as an extra section.
407
+ // event.systemPrompt already includes appendSystemPrompt, so no need
408
+ // to re-add it.
409
+ return {
410
+ systemPrompt:
411
+ event.systemPrompt +
412
+ "\n\n---\n\n## Custom system prompt\n\n" +
413
+ promptContent,
414
+ };
415
+ });
416
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "pi-custom-system-prompt",
3
+ "version": "0.1.0",
4
+ "description": "Pi extension that loads a custom system prompt from `~/.pi/agent/system-prompts/*.md` and injects it on every agent turn, with commands to select, toggle, reload, and switch between replace and append modes.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Nocte",
9
+ "email": "nocte19@gmail.com"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/nnocte/pi-custom-system-prompt.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/nnocte/pi-custom-system-prompt/issues"
17
+ },
18
+ "homepage": "https://github.com/nnocte/pi-custom-system-prompt#readme",
19
+ "keywords": [
20
+ "pi-package",
21
+ "pi-extension",
22
+ "pi",
23
+ "extension",
24
+ "system-prompt",
25
+ "custom-prompt",
26
+ "prompt"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "files": [
32
+ "extensions",
33
+ "README.md",
34
+ "LICENSE",
35
+ "CHANGELOG.md"
36
+ ],
37
+ "pi": {
38
+ "extensions": [
39
+ "./extensions"
40
+ ]
41
+ },
42
+ "peerDependencies": {
43
+ "@earendil-works/pi-coding-agent": "*"
44
+ }
45
+ }