@zihanw/pi-forge 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,80 +1,31 @@
1
1
  # pi-forge
2
2
 
3
- Pi extension package for prompt stack and agent profile management. This first milestone implements file-backed prompt stacks.
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
4
 
5
- ## Package setup
5
+ **pi-forge** lets you customize how Pi thinks and behaves. It gives you prompt stacks — JSON files that can replace, append to, or prepend Pi's default system prompt while controlling the AI's personality, visible tools, conversation history layout, and cross-turn state.
6
6
 
7
- This repository is a Pi package. Its package manifest exposes `./src/index.ts` as the extension entrypoint.
7
+ Think of it as a character sheet for your AI agent.
8
8
 
9
- For local development in this checkout, `.pi/settings.json` points at the package root:
9
+ ## What you can do with it
10
10
 
11
- ```json
12
- {
13
- "packages": [".."]
14
- }
15
- ```
11
+ - **Give Pi a personality** — turn it into a creative writer, a roleplay partner, a strict code reviewer, or anything in between.
12
+ - **Switch contexts instantly** — one command to swap between "coding mode", "writing mode", and "translation mode".
13
+ - **Control what the AI sees** — choose which tools, skills, and project context appear in each prompt.
14
+ - **Remember things across turns** — let the agent track progress, store notes, and recall user preferences throughout a session.
15
+ - **Import SillyTavern presets** — bring your existing ST character presets into Pi with one command.
16
+ - **Debug your prompts** — intercept and inspect exactly what gets sent to the model.
16
17
 
17
- After starting Pi in this directory, trust the project and use `/reload` if needed.
18
+ ## Quick start
18
19
 
19
- After publishing under your chosen npm package name, install it with:
20
+ ### Install
20
21
 
21
22
  ```bash
22
23
  pi install npm:@zihanw/pi-forge
23
24
  ```
24
25
 
25
- ## Prompt stacks
26
-
27
- Prompt stacks live in:
28
-
29
- ```txt
30
- .pi/prompt-stacks/*.json
31
- ```
32
-
33
- Activation rules:
34
-
35
- 1. A restored `/preset use <id>` selection is used first when that stack still exists and has no validation errors.
36
- 2. A restored `/preset use none` selection disables prompt stack replacement.
37
- 3. `.pi/prompt-stacks/default.json` auto-activates when present unless the stack sets `"autoActivate": false`.
38
- 4. Otherwise, the first stack with `"autoActivate": true` is used.
39
- 5. `/preset use <id>` switches stacks for the session and persists that choice.
40
- 6. `/preset use none` disables prompt stack replacement and persists that choice.
41
-
42
- When a stack is active, pi-forge replaces Pi's default system prompt by default and rebuilds the first provider request for each user message around a movable `chat-history` slot. Tool-result follow-up turns use Pi's natural context so post-history instructions are not repeatedly re-appended after every tool call.
43
-
44
- ## Commands
45
-
46
- ```txt
47
- /preset list
48
- /preset use <id|none>
49
- /preset preview [id]
50
- /preset validate [id]
51
- /preset reload
52
- /preset vars [set <name> <value>|get <name>|clear [name]]
53
- /preset import-silly <path> [character_id]
54
- /intercept
55
- ```
56
-
57
- ## SillyTavern preset import
58
-
59
- Import a SillyTavern preset JSON into `.pi/prompt-stacks/<id>.json` and write a migration report to `.pi/forge/import-reports/<id>.md`:
60
-
61
- ```txt
62
- /preset import-silly <path> [character_id]
63
- ```
64
-
65
- If a preset contains multiple `prompt_order` entries, pass the desired `character_id`. Imported stacks are created with `autoActivate: false`; activate one with `/preset use <id>` after reviewing the report.
66
-
67
- ## Agent tools
68
-
69
- ### forge_set_var
70
-
71
- Sets a persistent session variable. Only variables starting with `agent.` can be written by the agent; other variables are read-only. Useful for cross-turn state tracking in roleplay (character mood, story progress) or coding (task checkpoints, discovered facts).
72
-
73
- When the `variables` slot is active in a prompt stack, the agent sees its variable state and can update it. Without the slot, variables still work for macro substitution but the agent won't have a structured view of the state.
74
-
75
- ## Stack format
26
+ ### Your first prompt stack
76
27
 
77
- Minimal example:
28
+ Create `.pi/prompt-stacks/default.json`:
78
29
 
79
30
  ```json
80
31
  {
@@ -86,134 +37,271 @@ Minimal example:
86
37
  "items": [
87
38
  {
88
39
  "kind": "block",
89
- "id": "main-role",
40
+ "id": "role",
41
+ "name": "Main Role",
90
42
  "enabled": true,
91
43
  "role": "system",
92
- "content": "You are a precise coding assistant."
44
+ "content": "You are a friendly and concise coding assistant. Prefer short answers with code examples."
93
45
  },
94
46
  {
95
47
  "kind": "slot",
96
48
  "id": "tools",
49
+ "name": "Available Tools",
97
50
  "enabled": true,
98
51
  "role": "system",
99
52
  "slot": "tools"
100
53
  },
101
- {
102
- "kind": "block",
103
- "id": "history-open",
104
- "enabled": true,
105
- "role": "user",
106
- "content": "<conversation_context>"
107
- },
108
54
  {
109
55
  "kind": "slot",
110
56
  "id": "chat-history",
57
+ "name": "Chat History",
111
58
  "enabled": true,
112
59
  "slot": "chat-history"
113
- },
114
- {
115
- "kind": "block",
116
- "id": "history-close",
117
- "enabled": true,
118
- "role": "user",
119
- "content": "</conversation_context>"
120
60
  }
121
61
  ]
122
62
  }
123
63
  ```
124
64
 
125
- ## Items
65
+ That's it. Restart Pi or run `/preset reload`. If no stack is already selected, `default.json` auto-activates. If you previously chose another stack or `/preset use none`, run `/preset use default`.
126
66
 
127
- ### Blocks
67
+ ### Visual editor
128
68
 
129
- Static text:
69
+ Prefer clicking over typing JSON? pi-forge has a built-in web editor:
130
70
 
131
- ```json
132
- {
133
- "kind": "block",
134
- "id": "post-history-nudge",
135
- "enabled": true,
136
- "role": "user",
137
- "content": "Reason carefully about the latest request before answering."
138
- }
139
71
  ```
72
+ /preset ui
73
+ ```
74
+
75
+ Drag, drop, edit, validate, preview, import, export, fork, and delete stacks — all in your browser.
140
76
 
141
- ### Slots
77
+ Import accepts native pi-forge stack JSON and SillyTavern preset JSON. SillyTavern presets are converted to prompt stacks automatically; if a preset contains multiple `character_id` configs, the editor asks which one to use.
142
78
 
143
- Live Pi runtime content:
79
+ The editor runs on `127.0.0.1:41738` by default with a session token. Writes require a trusted project and stay inside `.pi/prompt-stacks`; successful save, import, fork, and delete actions reload into the current Pi session. Use `/preset ui restart` or `/preset ui stop` when needed.
80
+
81
+ To use a different fixed port, create `.pi/forge/config.json`:
144
82
 
145
83
  ```json
146
84
  {
147
- "kind": "slot",
148
- "id": "skills",
149
- "enabled": true,
150
- "role": "system",
151
- "slot": "skills"
85
+ "webEditor": {
86
+ "port": 41738
87
+ }
152
88
  }
153
89
  ```
154
90
 
155
- Supported slots:
156
-
157
- - `chat-history` — current Pi conversation context from the session
158
- - `tools` — active tool names and prompt snippets
159
- - `tool-guidelines` — active tool guidance
160
- - `skills` — loaded Pi skills
161
- - `project-context` — project instructions/context files
162
- - `append-system-prompt` — user-provided append system prompt text
163
- - `variables` — static/session/turn variables rendered as structured XML
164
- - `date`
165
- - `cwd`
166
- - `date-cwd`
167
- - `active-model`
168
- - `pi-docs`
169
-
170
- ### Variables slot
171
-
172
- Renders variable state as structured XML:
173
-
174
- ```xml
175
- <prompt_variables>
176
- <static>
177
- <char>Agent</char>
178
- </static>
179
- <session>
180
- <agent.mood>happy</agent.mood>
181
- <agent.progress>step 3</agent.progress>
182
- </session>
183
- <turn>
184
- <recent>just happened</recent>
185
- </turn>
186
- </prompt_variables>
91
+ ## Use cases
92
+
93
+ ### 🎭 Roleplay & creative writing
94
+
95
+ Turn Pi into a character. Define their personality in the system prompt, inject writing style rules as user messages, and use `{{lastUserMessage}}` to re-insert the user's input after the conversation history.
96
+
97
+ Useful pattern:
98
+ - Put long-term character rules in a `system` block.
99
+ - Keep Pi runtime context (tools, skills, project) in `user` slots.
100
+ - Set the `chat-history` slot to skip the latest user message.
101
+ - Add a final `user` block with `{{lastUserMessage}}`.
102
+
103
+ This keeps the latest request clear and avoids duplicating it.
104
+
105
+ For a copyable starter stack, see [examples/default-prompt-stack.json](examples/default-prompt-stack.json).
106
+
107
+ ### 🧑‍💻 Focused code review
108
+
109
+ Create a `reviewer.json` stack with a strict review block: "prioritize correctness, regressions, security, and missing tests." Keep the `tools`, `project-context`, `variables`, and `chat-history` slots enabled so Pi can still inspect the repo and remember review state.
110
+
111
+ Use `mode: "append"` if you want to keep Pi's normal coding behavior and only add the sharper review lens.
112
+
113
+ ### 🌐 Translation mode
114
+
115
+ Create a small `translator.json` stack with one system block for tone and target language, then keep `chat-history` and `{{lastUserMessage}}` in the layout. This works well for switching between bilingual editing, literal translation, and localization review without changing your default assistant.
116
+
117
+ ### 🔀 Multi-mode switching
118
+
119
+ Create separate stacks for different tasks:
120
+
187
121
  ```
122
+ .pi/prompt-stacks/
123
+ coder.json # strict coding assistant
124
+ writer.json # creative writing partner
125
+ translator.json # bilingual translator
126
+ ```
127
+
128
+ Switch with `/preset use coder`, `/preset use writer`, etc.
188
129
 
189
- Options:
130
+ ### 🧠 Cross-turn memory
131
+
132
+ Define state the agent can read and write:
190
133
 
191
134
  ```json
192
- {
193
- "includeStatic": true,
194
- "includeSession": true,
195
- "includeTurn": true
135
+ "state": {
136
+ "definitions": {
137
+ "agent.progress": {
138
+ "type": "string",
139
+ "scope": "session",
140
+ "description": "What we're working on",
141
+ "agentWritable": true
142
+ }
143
+ }
196
144
  }
197
145
  ```
198
146
 
199
- Use this slot to give the agent visibility into mutable state that it can also update via `forge_set_var`.
147
+ The agent updates it with `forge_state_set`. You can also set state manually:
148
+
149
+ ```
150
+ /state set user.preference "use TypeScript, not JavaScript"
151
+ ```
152
+
153
+ ### 📦 SillyTavern migration
154
+
155
+ Bring your ST presets into Pi:
156
+
157
+ ```
158
+ /preset import-silly ~/SillyTavern/presets/my-preset.json
159
+ ```
160
+
161
+ pi-forge converts the preset to a prompt stack and generates a migration report showing what was handled and what needs manual tweaking.
162
+
163
+ ### 🔍 Prompt debugging
164
+
165
+ See exactly what gets sent to the model:
200
166
 
201
- ## Roles
167
+ ```
168
+ /payload next save=.pi/forge/payloads/last.json
169
+ ```
202
170
 
203
- - `system` items are compiled into the replacement system prompt.
204
- - `user` items are inserted as ephemeral user messages.
205
- - `assistant` items are inserted as ephemeral assistant messages.
206
- - `custom` items are inserted as hidden Pi custom messages and converted to user context by Pi.
171
+ Or preview your compiled prompt without sending anything:
172
+
173
+ ```
174
+ /preset preview
175
+ ```
176
+
177
+ ## How it works
178
+
179
+ A prompt stack is a JSON file with two kinds of items:
180
+
181
+ | Kind | What it does |
182
+ |------|-------------|
183
+ | **Block** | Static text inserted at a specific position (system prompt, user message, assistant message) |
184
+ | **Slot** | Dynamic content from Pi's runtime — tools, skills, chat history, date, project context, etc. |
185
+
186
+ Items are arranged in order. When the stack is active, pi-forge:
187
+
188
+ 1. Builds a system prompt from your `system`-role blocks and slots, then applies it with the stack's `mode`.
189
+ 2. Inserts `user`/`assistant` blocks and slots around the conversation history.
190
+ 3. Expands `{{macros}}` like `{{lastUserMessage}}`, `{{date}}`, and custom variables.
191
+
192
+ ### Slots at a glance
193
+
194
+ | Slot | What it inserts |
195
+ |------|----------------|
196
+ | `chat-history` | The current conversation |
197
+ | `tools` | Available tools and their descriptions |
198
+ | `tool-guidelines` | Tool usage instructions |
199
+ | `skills` | Loaded Pi skills |
200
+ | `project-context` | Project instructions and context files |
201
+ | `variables` | Agent and user state (progress, preferences, notes) |
202
+ | `date` / `cwd` / `date-cwd` | Current date and working directory |
203
+ | `active-model` | Which model is being used |
204
+ | `append-system-prompt` | User's appended system prompt text |
205
+ | `pi-docs` | Pi documentation guidance |
206
+
207
+ ### Modes
208
+
209
+ - **replace** (default) — your stack replaces Pi's system prompt entirely.
210
+ - **append** — your stack is added after Pi's default system prompt.
211
+ - **prepend** — your stack is added before Pi's default system prompt.
212
+
213
+ ## Common commands
214
+
215
+ ### Managing stacks
216
+
217
+ | Command | What it does |
218
+ |---------|-------------|
219
+ | `/preset list` | Show all available stacks |
220
+ | `/preset use <id>` | Activate a stack |
221
+ | `/preset use none` | Disable prompt stacks for the session |
222
+ | `/preset preview [id]` | See the compiled prompt |
223
+ | `/preset validate [id]` | Check a stack for issues |
224
+ | `/preset status` | Show the active stack and diagnostics summary |
225
+ | `/preset diagnostics` | Show runtime diagnostics |
226
+ | `/preset reload` | Reload stacks from disk |
227
+ | `/preset ui [stop\|restart]` | Open, stop, or restart the web editor |
228
+
229
+ ### State management
230
+
231
+ | Command | What it does |
232
+ |---------|-------------|
233
+ | `/state list` | Show all session state |
234
+ | `/state status` | Show state definitions and current values |
235
+ | `/state set <name> <value>` | Set a state variable |
236
+ | `/state get <name>` | Read a state variable |
237
+ | `/state clear [name]` | Clear state (all or by name) |
238
+ | `/preset vars ...` | Legacy variable commands kept for older stacks |
239
+
240
+ ### Import & debug
241
+
242
+ | Command | What it does |
243
+ |---------|-------------|
244
+ | `/preset import-silly <path>` | Import a SillyTavern preset |
245
+ | `/intercept` | Show the next provider payload |
246
+ | `/payload next [save=<path>]` | Show and optionally save the next payload |
247
+
248
+ ## Common macros
249
+
250
+ Use these in block content to insert dynamic values:
251
+
252
+ | Macro | Expands to |
253
+ |-------|-----------|
254
+ | `{{lastUserMessage}}` | The user's latest message |
255
+ | `{{date}}` | Current date (YYYY-MM-DD) |
256
+ | `{{time}}` | Current time (HH:MM:SS) |
257
+ | `{{cwd}}` | Current working directory |
258
+ | `{{tools}}` | Comma-separated tool names |
259
+ | `{{selectedTools}}` | Alias for selected tool names |
260
+ | `{{activeModel}}` | Current model (provider/id) |
261
+ | `{{char}}` / `{{user}}` | Custom variables from your stack |
262
+
263
+ ### Variable macros
264
+
265
+ ```
266
+ {{setvar::name::value}} set a turn variable (cleared each message)
267
+ {{setsessionvar::name::value}} set a session variable (persists)
268
+ {{setvar::session::name::value}} also set a session variable
269
+ {{getvar::name}} read a variable (turn → session → static)
270
+ {{getturnvar::name}} read only a turn variable
271
+ {{getsessionvar::name}} read only a session variable
272
+ {{clearvar::name}} clear a variable
273
+ {{clearturnvar::name}} clear a turn variable
274
+ {{clearsessionvar::name}} clear a session variable
275
+ ```
276
+
277
+ ## Stack reference
278
+
279
+ ### Full item types
280
+
281
+ **Block:**
282
+
283
+ ```json
284
+ {
285
+ "kind": "block",
286
+ "id": "unique-id",
287
+ "name": "Readable label",
288
+ "enabled": true,
289
+ "role": "system",
290
+ "content": "Your text here. Use {{macros}} for dynamic content."
291
+ }
292
+ ```
207
293
 
208
- `chat-history` expands to the live conversation at its position. By default, only the first enabled `chat-history` slot is expanded.
294
+ Valid roles: `system`, `user`, `assistant`, `custom`.
209
295
 
210
- To omit the latest user message from a chat history slot, use:
296
+ **Slot:**
211
297
 
212
298
  ```json
213
299
  {
214
300
  "kind": "slot",
215
- "id": "chat-history",
301
+ "id": "unique-id",
302
+ "name": "Chat History",
216
303
  "enabled": true,
304
+ "role": "user",
217
305
  "slot": "chat-history",
218
306
  "options": {
219
307
  "includeLastUserMessage": false
@@ -221,66 +309,97 @@ To omit the latest user message from a chat history slot, use:
221
309
  }
222
310
  ```
223
311
 
224
- This is useful for SillyTavern-style stacks that re-insert `{{lastUserMessage}}` later as a post-history instruction.
312
+ ### Chat history options
313
+
314
+ ```json
315
+ "options": {
316
+ "includeLastUserMessage": false
317
+ }
318
+ ```
319
+
320
+ Set to `false` when you use `{{lastUserMessage}}` after the history — prevents the user's message from appearing twice.
225
321
 
226
- To intentionally duplicate history:
322
+ ### Variables slot options
227
323
 
228
324
  ```json
229
325
  {
230
- "context": {
231
- "allowDuplicateChatHistory": true
326
+ "kind": "slot",
327
+ "id": "state",
328
+ "enabled": true,
329
+ "role": "user",
330
+ "slot": "variables",
331
+ "options": {
332
+ "includeScopes": ["session"],
333
+ "includeNamespaces": ["user.*", "agent.*"],
334
+ "includeMetadata": true,
335
+ "format": "xml",
336
+ "maxValueChars": 1200
232
337
  }
233
338
  }
234
339
  ```
235
340
 
236
- ## Macros
341
+ ### State definitions
237
342
 
238
- Supported macros in block content:
343
+ ```json
344
+ "state": {
345
+ "schemaVersion": 1,
346
+ "definitions": {
347
+ "agent.progress": {
348
+ "type": "string",
349
+ "scope": "session",
350
+ "description": "Current task progress",
351
+ "agentWritable": true
352
+ },
353
+ "user.preference": {
354
+ "type": "string",
355
+ "scope": "session",
356
+ "description": "User's preference for this session",
357
+ "userWritable": true
358
+ }
359
+ }
360
+ }
361
+ ```
239
362
 
240
- - `{{cwd}}`
241
- - `{{date}}`
242
- - `{{lastUserMessage}}`
243
- - `{{selectedTools}}` / `{{tools}}`
244
- - `{{activeModel}}`
245
- - custom variables from the stack `variables` object, e.g. `{{char}}`
363
+ Supported types: `string`, `number`, `boolean`, `null`, `object`, `array`, `string[]`, `number[]`, `boolean[]`, `unknown`, and unions like `string | null`.
246
364
 
247
- ### Variables
365
+ ## Agent tools
248
366
 
249
- Static variables come from the stack file:
367
+ pi-forge registers two tools that the AI agent can call:
250
368
 
251
- ```json
252
- "variables": {
253
- "char": "Assistant",
254
- "user": "USER"
255
- }
256
- ```
369
+ ### `forge_state_set`
370
+
371
+ Batch-update persistent state. Only `agent.*` names are writable. Use for cross-turn tracking:
372
+
373
+ - Task progress (`agent.progress`)
374
+ - Open questions (`agent.openQuestions`)
375
+ - Story state (`agent.storyState`)
376
+ - User-requested notes (`agent.notes`)
257
377
 
258
- Mutable turn variables are cleared for each user message:
378
+ ### `forge_set_var`
259
379
 
260
- ```txt
261
- {{setvar::name::value}}
262
- {{setturnvar::name::value}}
263
- {{getvar::name}}
264
- {{getturnvar::name}}
265
- {{var::name}}
266
- {{clearvar::name}}
380
+ Legacy alias for setting a single string value. Prefer `forge_state_set`.
381
+
382
+ ## Package setup for development
383
+
384
+ ```bash
385
+ git clone <repo>
386
+ cd pi-forge
387
+ # .pi/settings.json already points at the package root
388
+ pi # start Pi, trust the project, /reload if needed
267
389
  ```
268
390
 
269
- Mutable session variables persist in the Pi session as extension state:
391
+ Run tests:
270
392
 
271
- ```txt
272
- {{setsessionvar::name::value}}
273
- {{setvar::session::name::value}}
274
- {{getsessionvar::name}}
275
- {{getvar::name}}
276
- {{clearsessionvar::name}}
277
- {{clearvar::session::name}}
393
+ ```bash
394
+ npm test
278
395
  ```
279
396
 
280
- Lookup order for `{{getvar::name}}`, `{{var::name}}`, and bare `{{name}}` is:
397
+ Typecheck:
398
+
399
+ ```bash
400
+ npm run typecheck
401
+ ```
281
402
 
282
- 1. turn variables
283
- 2. session variables
284
- 3. static stack variables
403
+ ## License
285
404
 
286
- `setvar` macros output empty text. Unknown macros warn by default and are kept literally.
405
+ MIT