prompt_manager 1.0.2 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -27
- data/README.md +127 -271
- data/lib/pm/configuration.rb +34 -4
- data/lib/pm/core_directives.rb +21 -20
- data/lib/pm/directive.rb +26 -34
- data/lib/pm/directives.rb +55 -16
- data/lib/pm/errors.rb +71 -0
- data/lib/pm/metadata.rb +110 -9
- data/lib/pm/parsed.rb +51 -46
- data/lib/pm/render_scope.rb +74 -0
- data/lib/pm/renderer.rb +59 -0
- data/lib/pm/shell.rb +95 -40
- data/lib/pm/version.rb +1 -1
- data/lib/pm.rb +116 -57
- data/lib/prompt_manager.rb +6 -0
- metadata +13 -113
- data/.envrc +0 -3
- data/.irbrc +0 -14
- data/COMMITS.md +0 -196
- data/Rakefile +0 -12
- data/docs/api/configuration.md +0 -59
- data/docs/api/constants.md +0 -60
- data/docs/api/index.md +0 -14
- data/docs/api/metadata.md +0 -99
- data/docs/api/parsed.md +0 -98
- data/docs/api/pm-module.md +0 -169
- data/docs/api/render-context.md +0 -51
- data/docs/architecture/design-decisions.md +0 -70
- data/docs/architecture/index.md +0 -6
- data/docs/architecture/processing-pipeline.md +0 -112
- data/docs/assets/css/custom.css +0 -1
- data/docs/assets/images/prompt_manager.gif +0 -0
- data/docs/assets/images/prompt_manager.mp4 +0 -0
- data/docs/examples/ai-agent-prompts.md +0 -173
- data/docs/examples/code-review-prompt.md +0 -107
- data/docs/examples/index.md +0 -7
- data/docs/examples/multi-file-composition.md +0 -123
- data/docs/getting-started/configuration.md +0 -106
- data/docs/getting-started/index.md +0 -7
- data/docs/getting-started/installation.md +0 -34
- data/docs/getting-started/quick-start.md +0 -94
- data/docs/guides/comment-stripping.md +0 -64
- data/docs/guides/custom-directives.md +0 -180
- data/docs/guides/erb-rendering.md +0 -102
- data/docs/guides/includes.md +0 -184
- data/docs/guides/index.md +0 -11
- data/docs/guides/parameters.md +0 -96
- data/docs/guides/parsing.md +0 -151
- data/docs/guides/shell-expansion.md +0 -108
- data/docs/index.md +0 -70
- data/mkdocs.yml +0 -181
data/docs/guides/includes.md
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# Including Files
|
|
2
|
-
|
|
3
|
-
The `include` directive lets you compose prompts from multiple files.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
```markdown
|
|
8
|
-
---
|
|
9
|
-
title: Full Review
|
|
10
|
-
parameters:
|
|
11
|
-
code: null
|
|
12
|
-
---
|
|
13
|
-
<%= include 'common/header.md' %>
|
|
14
|
-
|
|
15
|
-
Review this code:
|
|
16
|
-
<%= code %>
|
|
17
|
-
|
|
18
|
-
<%= include 'common/footer.md' %>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
parsed = PM.parse('full_review.md')
|
|
23
|
-
puts parsed.to_s('code' => File.read('app.rb'))
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## How Includes Work
|
|
27
|
-
|
|
28
|
-
Each included file goes through the full processing pipeline:
|
|
29
|
-
|
|
30
|
-
1. HTML comments stripped
|
|
31
|
-
2. YAML metadata extracted
|
|
32
|
-
3. Shell expansion applied
|
|
33
|
-
4. ERB rendered (with the parent's parameter values)
|
|
34
|
-
|
|
35
|
-
The included file's rendered content replaces the `<%= include ... %>` tag.
|
|
36
|
-
|
|
37
|
-
## Path Resolution
|
|
38
|
-
|
|
39
|
-
Include paths are resolved relative to the **parent file's directory**:
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
prompts/
|
|
43
|
-
review.md # contains: <%= include 'common/header.md' %>
|
|
44
|
-
common/
|
|
45
|
-
header.md # resolved from prompts/common/header.md
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Parameter Passing
|
|
49
|
-
|
|
50
|
-
The parent's parameter values are passed to included files. Child files can use the same parameter names:
|
|
51
|
-
|
|
52
|
-
```markdown
|
|
53
|
-
<!-- parent.md -->
|
|
54
|
-
---
|
|
55
|
-
parameters:
|
|
56
|
-
name: null
|
|
57
|
-
---
|
|
58
|
-
<%= include 'greeting.md' %>
|
|
59
|
-
Done.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
```markdown
|
|
63
|
-
<!-- greeting.md -->
|
|
64
|
-
---
|
|
65
|
-
title: Greeting
|
|
66
|
-
---
|
|
67
|
-
Hello, <%= name %>!
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
```ruby
|
|
71
|
-
parsed = PM.parse('parent.md')
|
|
72
|
-
parsed.to_s('name' => 'Alice')
|
|
73
|
-
#=> "Hello, Alice!\nDone."
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Nested Includes
|
|
77
|
-
|
|
78
|
-
Includes can be nested. File A can include File B, which includes File C:
|
|
79
|
-
|
|
80
|
-
```markdown
|
|
81
|
-
<!-- a.md -->
|
|
82
|
-
<%= include 'b.md' %>
|
|
83
|
-
|
|
84
|
-
<!-- b.md -->
|
|
85
|
-
<%= include 'c.md' %>
|
|
86
|
-
|
|
87
|
-
<!-- c.md -->
|
|
88
|
-
Content from C
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Circular Include Detection
|
|
92
|
-
|
|
93
|
-
PM detects circular includes and raises an error:
|
|
94
|
-
|
|
95
|
-
```markdown
|
|
96
|
-
<!-- circular_a.md -->
|
|
97
|
-
<%= include 'circular_b.md' %>
|
|
98
|
-
|
|
99
|
-
<!-- circular_b.md -->
|
|
100
|
-
<%= include 'circular_a.md' %>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
PM.parse('circular_a.md').to_s
|
|
105
|
-
#=> RuntimeError: Circular include detected: /path/to/circular_a.md
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Includes Metadata
|
|
109
|
-
|
|
110
|
-
After calling `to_s`, the parent's metadata has an `includes` key with a tree of what was included:
|
|
111
|
-
|
|
112
|
-
```ruby
|
|
113
|
-
parsed = PM.parse('full_review.md')
|
|
114
|
-
parsed.to_s('code' => source)
|
|
115
|
-
|
|
116
|
-
parsed.metadata.includes
|
|
117
|
-
#=> [
|
|
118
|
-
# {
|
|
119
|
-
# path: "/prompts/common/header.md",
|
|
120
|
-
# depth: 1,
|
|
121
|
-
# metadata: { title: "Header", ... },
|
|
122
|
-
# includes: []
|
|
123
|
-
# },
|
|
124
|
-
# {
|
|
125
|
-
# path: "/prompts/common/footer.md",
|
|
126
|
-
# depth: 1,
|
|
127
|
-
# metadata: { title: "Footer", ... },
|
|
128
|
-
# includes: []
|
|
129
|
-
# }
|
|
130
|
-
# ]
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Nested includes form a tree -- each entry's `includes` array contains its own children.
|
|
134
|
-
|
|
135
|
-
The `includes` array is `nil` before `to_s` is called and is reset on each call.
|
|
136
|
-
|
|
137
|
-
## `insert` / `read` — Raw File Insertion
|
|
138
|
-
|
|
139
|
-
Use `insert` (or its alias `read`) to insert a file's content verbatim. Unlike `include`, the content is not parsed, shell-expanded, or ERB-rendered:
|
|
140
|
-
|
|
141
|
-
```markdown
|
|
142
|
-
---
|
|
143
|
-
title: Code Review
|
|
144
|
-
---
|
|
145
|
-
Review this code:
|
|
146
|
-
|
|
147
|
-
```ruby
|
|
148
|
-
<%= insert 'src/app.rb' %>
|
|
149
|
-
```
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Differences from `include`
|
|
153
|
-
|
|
154
|
-
| | `insert` | `include` |
|
|
155
|
-
|---|---|---|
|
|
156
|
-
| File types | Any | `.md` only |
|
|
157
|
-
| ERB in content | Preserved as literal text | Rendered |
|
|
158
|
-
| Shell expansion | Not applied | Applied |
|
|
159
|
-
| Recursion | None | Nested includes supported |
|
|
160
|
-
| Metadata tracking | None | `metadata.includes` tree |
|
|
161
|
-
|
|
162
|
-
### Path resolution
|
|
163
|
-
|
|
164
|
-
Same as `include` — paths resolve relative to the parent file's directory. Absolute paths work from any context, including string-parsed prompts.
|
|
165
|
-
|
|
166
|
-
### Missing files
|
|
167
|
-
|
|
168
|
-
Raises an error:
|
|
169
|
-
|
|
170
|
-
```ruby
|
|
171
|
-
PM.parse("---\n---\n<%= insert '/no/such/file' %>").to_s
|
|
172
|
-
#=> RuntimeError: insert: file not found: /no/such/file
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Requirements
|
|
176
|
-
|
|
177
|
-
- The `include` directive requires file context. Using it with string-parsed prompts raises an error:
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
PM.parse("---\n---\n<%= include 'other.md' %>").to_s
|
|
181
|
-
#=> RuntimeError: include requires a file context (use PM.parse with a file path)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
- When `erb: false`, the include directive does not execute and `metadata.includes` is an empty array.
|
data/docs/guides/index.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Guides
|
|
2
|
-
|
|
3
|
-
In-depth guides for each PM feature.
|
|
4
|
-
|
|
5
|
-
- [Parsing Prompts](parsing.md) -- File paths vs raw strings, metadata extraction
|
|
6
|
-
- [Parameters](parameters.md) -- Required and default parameters, rendering with `to_s`
|
|
7
|
-
- [Shell Expansion](shell-expansion.md) -- Environment variables and command substitution
|
|
8
|
-
- [ERB Rendering](erb-rendering.md) -- Template evaluation and disabling
|
|
9
|
-
- [Including Files](includes.md) -- Composing prompts from multiple files
|
|
10
|
-
- [Custom Directives](custom-directives.md) -- Registering your own ERB helpers
|
|
11
|
-
- [Comment Stripping](comment-stripping.md) -- HTML comment removal
|
data/docs/guides/parameters.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Parameters
|
|
2
|
-
|
|
3
|
-
Parameters are declared in the YAML `parameters` hash and used as ERB template variables.
|
|
4
|
-
|
|
5
|
-
## Declaring Parameters
|
|
6
|
-
|
|
7
|
-
```markdown
|
|
8
|
-
---
|
|
9
|
-
title: Code Review
|
|
10
|
-
parameters:
|
|
11
|
-
language: ruby
|
|
12
|
-
code: null
|
|
13
|
-
style_guide: ~/guides/default.md
|
|
14
|
-
---
|
|
15
|
-
Review the following <%= language %> code using the style guide
|
|
16
|
-
at <%= style_guide %>:
|
|
17
|
-
|
|
18
|
-
<%= code %>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Required vs Default
|
|
22
|
-
|
|
23
|
-
- **`null` value** -- the parameter is **required**. `to_s` raises `ArgumentError` if not provided.
|
|
24
|
-
- **Any other value** -- used as the **default**. Can be overridden when calling `to_s`.
|
|
25
|
-
|
|
26
|
-
```ruby
|
|
27
|
-
parsed = PM.parse('code_review.md')
|
|
28
|
-
|
|
29
|
-
parsed.metadata.parameters
|
|
30
|
-
#=> {"language" => "ruby", "code" => nil, "style_guide" => "~/guides/default.md"}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Rendering with to_s
|
|
34
|
-
|
|
35
|
-
`to_s` accepts a Hash of parameter values:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
# Supply required params, accept defaults for the rest
|
|
39
|
-
parsed.to_s('code' => File.read('app.rb'))
|
|
40
|
-
|
|
41
|
-
# Override a default
|
|
42
|
-
parsed.to_s('code' => File.read('app.py'), 'language' => 'python')
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Symbol keys work too:
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
parsed.to_s(code: File.read('app.rb'))
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### All Defaults
|
|
52
|
-
|
|
53
|
-
When every parameter has a default value, `to_s` needs no arguments:
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
parsed.to_s #=> renders with all defaults
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Missing Required Parameters
|
|
60
|
-
|
|
61
|
-
Calling `to_s` without supplying a required parameter raises `ArgumentError`:
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
parsed.to_s
|
|
65
|
-
#=> ArgumentError: Missing required parameters: code
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The error message lists all missing parameters.
|
|
69
|
-
|
|
70
|
-
## Parameters in Included Files
|
|
71
|
-
|
|
72
|
-
When a file includes another file via `<%= include 'child.md' %>`, the parent's parameter values are passed to the child. The child can use the same parameter names:
|
|
73
|
-
|
|
74
|
-
```markdown
|
|
75
|
-
<!-- parent.md -->
|
|
76
|
-
---
|
|
77
|
-
title: Parent
|
|
78
|
-
parameters:
|
|
79
|
-
name: null
|
|
80
|
-
---
|
|
81
|
-
<%= include 'greeting.md' %>
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
```markdown
|
|
85
|
-
<!-- greeting.md -->
|
|
86
|
-
---
|
|
87
|
-
title: Greeting
|
|
88
|
-
---
|
|
89
|
-
Hello, <%= name %>!
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```ruby
|
|
93
|
-
parsed = PM.parse('parent.md')
|
|
94
|
-
parsed.to_s('name' => 'Alice')
|
|
95
|
-
#=> "Hello, Alice!"
|
|
96
|
-
```
|
data/docs/guides/parsing.md
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
# Parsing Prompts
|
|
2
|
-
|
|
3
|
-
`PM.parse` is the main entry point. It accepts a file path, a Symbol, a single word, or a raw string.
|
|
4
|
-
|
|
5
|
-
## File Paths
|
|
6
|
-
|
|
7
|
-
A source is treated as a file path when it:
|
|
8
|
-
|
|
9
|
-
- Is a `Pathname` object
|
|
10
|
-
- Responds to `to_path`
|
|
11
|
-
- Is a String ending in `.md`
|
|
12
|
-
- Is a Symbol (`.md` is appended, e.g. `:code_review` → `code_review.md`)
|
|
13
|
-
- Is a single word containing only letters, digits, or underscores (`.md` is appended, e.g. `"code_review"` → `code_review.md`)
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
# String file path
|
|
17
|
-
parsed = PM.parse('code_review.md')
|
|
18
|
-
|
|
19
|
-
# Pathname
|
|
20
|
-
parsed = PM.parse(Pathname.new('code_review.md'))
|
|
21
|
-
|
|
22
|
-
# Symbol (basename — .md appended)
|
|
23
|
-
parsed = PM.parse(:code_review)
|
|
24
|
-
|
|
25
|
-
# Single word (basename — .md appended)
|
|
26
|
-
parsed = PM.parse('code_review')
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
When parsing a file, PM adds these keys to the metadata automatically:
|
|
30
|
-
|
|
31
|
-
| Key | Type | Description |
|
|
32
|
-
|-----|------|-------------|
|
|
33
|
-
| `directory` | String | Absolute path to the file's parent directory |
|
|
34
|
-
| `name` | String | Filename (e.g., `code_review.md`) |
|
|
35
|
-
| `created_at` | Time | File creation timestamp |
|
|
36
|
-
| `modified_at` | Time | Last modification timestamp |
|
|
37
|
-
|
|
38
|
-
### prompts_dir
|
|
39
|
-
|
|
40
|
-
Relative file paths are resolved against `PM.config.prompts_dir`:
|
|
41
|
-
|
|
42
|
-
```ruby
|
|
43
|
-
PM.configure { |c| c.prompts_dir = '~/.prompts' }
|
|
44
|
-
|
|
45
|
-
PM.parse('agents/summarize.md')
|
|
46
|
-
#=> reads ~/.prompts/agents/summarize.md
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Absolute paths bypass `prompts_dir`:
|
|
50
|
-
|
|
51
|
-
```ruby
|
|
52
|
-
PM.parse('/etc/prompts/system.md')
|
|
53
|
-
#=> reads /etc/prompts/system.md regardless of prompts_dir
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Raw Strings
|
|
57
|
-
|
|
58
|
-
Any other string is parsed directly:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
parsed = PM.parse("---\ntitle: Hello\n---\nContent here")
|
|
62
|
-
|
|
63
|
-
parsed.metadata.title #=> "Hello"
|
|
64
|
-
parsed.content #=> "\nContent here\n"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
String-parsed prompts do not have `directory`, `name`, `created_at`, or `modified_at` in their metadata.
|
|
68
|
-
|
|
69
|
-
!!! warning "Include limitations"
|
|
70
|
-
The `include` directive requires file context to resolve relative paths. It raises an error when used with string-parsed prompts.
|
|
71
|
-
|
|
72
|
-
### PM.parse_string vs PM.parse
|
|
73
|
-
|
|
74
|
-
Because `PM.parse` treats single words as prompt IDs (appending `.md` and looking them up as files), short strings like `"hello"` or `"summarize"` will trigger a file lookup instead of being parsed as content. Use `PM.parse_string` to always parse a string as content:
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
# PM.parse treats single words as prompt IDs
|
|
78
|
-
PM.parse('hello') #=> looks for hello.md in prompts_dir
|
|
79
|
-
PM.parse('summarize') #=> looks for summarize.md in prompts_dir
|
|
80
|
-
|
|
81
|
-
# PM.parse_string always parses as string content
|
|
82
|
-
PM.parse_string('hello') #=> Parsed with content "hello"
|
|
83
|
-
PM.parse_string('summarize') #=> Parsed with content "summarize"
|
|
84
|
-
|
|
85
|
-
# Multi-word strings without .md extension work the same in both
|
|
86
|
-
PM.parse("Hello world") #=> Parsed with content "Hello world"
|
|
87
|
-
PM.parse_string("Hello world") #=> Parsed with content "Hello world"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Use `PM.parse_string` when:
|
|
91
|
-
|
|
92
|
-
- Your input may be a single word that should not be treated as a file
|
|
93
|
-
- You know the source is always a string, never a file path
|
|
94
|
-
- You want to skip the file-detection logic entirely
|
|
95
|
-
|
|
96
|
-
## Metadata Extraction
|
|
97
|
-
|
|
98
|
-
YAML front-matter is delimited by `---` fences:
|
|
99
|
-
|
|
100
|
-
```markdown
|
|
101
|
-
---
|
|
102
|
-
title: My Prompt
|
|
103
|
-
provider: openai
|
|
104
|
-
model: gpt-4
|
|
105
|
-
temperature: 0.3
|
|
106
|
-
---
|
|
107
|
-
The prompt content starts here.
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
All YAML keys become accessible via dot notation on the `PM::Metadata` object:
|
|
111
|
-
|
|
112
|
-
```ruby
|
|
113
|
-
parsed.metadata.title #=> "My Prompt"
|
|
114
|
-
parsed.metadata.provider #=> "openai"
|
|
115
|
-
parsed.metadata.temperature #=> 0.3
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Boolean keys also get predicate methods:
|
|
119
|
-
|
|
120
|
-
```ruby
|
|
121
|
-
parsed.metadata.shell #=> true
|
|
122
|
-
parsed.metadata.shell? #=> true
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### No Metadata
|
|
126
|
-
|
|
127
|
-
Files without front-matter are valid. The metadata object is empty and the entire content is preserved:
|
|
128
|
-
|
|
129
|
-
```ruby
|
|
130
|
-
parsed = PM.parse("Just plain text")
|
|
131
|
-
parsed.metadata.title #=> nil
|
|
132
|
-
parsed.content #=> "Just plain text"
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Bracket Accessor
|
|
136
|
-
|
|
137
|
-
You can also access metadata with bracket notation:
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
parsed[:title] #=> "My Prompt"
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## Pipeline Stages
|
|
144
|
-
|
|
145
|
-
After parsing, the content has been through these stages:
|
|
146
|
-
|
|
147
|
-
1. HTML comments stripped
|
|
148
|
-
2. YAML metadata extracted
|
|
149
|
-
3. Shell expansion applied (if `shell: true`)
|
|
150
|
-
|
|
151
|
-
ERB rendering happens later, on demand, when `to_s` is called.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Shell Expansion
|
|
2
|
-
|
|
3
|
-
Shell expansion replaces environment variable references and command substitutions with their values at parse time.
|
|
4
|
-
|
|
5
|
-
## Syntax
|
|
6
|
-
|
|
7
|
-
| Pattern | Description | Example |
|
|
8
|
-
|---------|-------------|---------|
|
|
9
|
-
| `$VAR` | Environment variable | `$USER` |
|
|
10
|
-
| `${VAR}` | Environment variable (braced) | `${HOME}` |
|
|
11
|
-
| `$(command)` | Command substitution | `$(date +%Y-%m-%d)` |
|
|
12
|
-
|
|
13
|
-
## Environment Variables
|
|
14
|
-
|
|
15
|
-
```markdown
|
|
16
|
-
---
|
|
17
|
-
title: Info
|
|
18
|
-
---
|
|
19
|
-
Current user: $USER
|
|
20
|
-
Home directory: ${HOME}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
After parsing:
|
|
24
|
-
|
|
25
|
-
```ruby
|
|
26
|
-
parsed = PM.parse('info.md')
|
|
27
|
-
parsed.content
|
|
28
|
-
#=> "Current user: dewayne\nHome directory: /Users/dewayne\n"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Only UPPERCASE variable names are expanded. Lowercase names like `$foo` are left as-is.
|
|
32
|
-
|
|
33
|
-
Missing environment variables are replaced with an empty string.
|
|
34
|
-
|
|
35
|
-
## Command Substitution
|
|
36
|
-
|
|
37
|
-
```markdown
|
|
38
|
-
---
|
|
39
|
-
title: Context
|
|
40
|
-
---
|
|
41
|
-
Date: $(date +%Y-%m-%d)
|
|
42
|
-
Branch: $(git rev-parse --abbrev-ref HEAD)
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Commands are executed via the shell and replaced with their stdout (trailing newline stripped).
|
|
46
|
-
|
|
47
|
-
### Nested Commands
|
|
48
|
-
|
|
49
|
-
Nested parentheses are handled correctly:
|
|
50
|
-
|
|
51
|
-
```markdown
|
|
52
|
-
$(echo $(echo hello))
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Failed Commands
|
|
56
|
-
|
|
57
|
-
A command that exits with a non-zero status raises `RuntimeError`:
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
PM.parse("Output: $(exit 1)")
|
|
61
|
-
#=> RuntimeError: Command failed with exit status 1: exit 1
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Direct Access
|
|
65
|
-
|
|
66
|
-
Shell expansion is available as a standalone method:
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
PM.expand_shell("Hello $USER from $(hostname)")
|
|
70
|
-
#=> "Hello dewayne from venus"
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Disabling Shell Expansion
|
|
74
|
-
|
|
75
|
-
Set `shell: false` in the file's YAML metadata:
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
title: Raw
|
|
80
|
-
shell: false
|
|
81
|
-
---
|
|
82
|
-
This $USER reference is preserved as-is.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Or disable globally:
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
PM.configure { |c| c.shell = false }
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Per-file metadata always overrides the global setting. A file with `shell: true` gets shell expansion even when the global default is `false`.
|
|
92
|
-
|
|
93
|
-
## Interaction with ERB
|
|
94
|
-
|
|
95
|
-
Shell expansion happens at parse time (step 3 of the pipeline). ERB rendering happens later when `to_s` is called (step 4). This means shell references in the raw template are expanded before ERB sees the content.
|
|
96
|
-
|
|
97
|
-
If you need dynamic shell output at render time, use a custom directive instead:
|
|
98
|
-
|
|
99
|
-
```ruby
|
|
100
|
-
PM.register(:sh) { |_ctx, cmd| `#{cmd}`.chomp }
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
```markdown
|
|
104
|
-
---
|
|
105
|
-
title: Dynamic
|
|
106
|
-
---
|
|
107
|
-
Branch: <%= sh 'git rev-parse --abbrev-ref HEAD' %>
|
|
108
|
-
```
|
data/docs/index.md
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# PM (PromptManager)
|
|
2
|
-
|
|
3
|
-
<table>
|
|
4
|
-
<tr>
|
|
5
|
-
<td width="40%" align="center" valign="top">
|
|
6
|
-
<img src="assets/images/prompt_manager.gif" alt="PromptManager" width="100%"><br>
|
|
7
|
-
<em>"Prompts with superpowers"</em>
|
|
8
|
-
</td>
|
|
9
|
-
<td width="60%" valign="top">
|
|
10
|
-
<strong>Parse YAML metadata from markdown, expand shell references, and render ERB templates on demand</strong><br><br>
|
|
11
|
-
PM (PromptManager) treats prompt files as composable, parameterized templates. Write prompts in markdown with YAML front matter, shell references, and ERB — PM handles the rest.
|
|
12
|
-
|
|
13
|
-
<h3>Key Features</h3>
|
|
14
|
-
|
|
15
|
-
<li> <strong>YAML Metadata</strong> - Parse from markdown strings or files<br>
|
|
16
|
-
<li> <strong>Shell Expansion</strong> - $VAR, ${VAR}, and $(command) substitution<br>
|
|
17
|
-
<li> <strong>ERB Rendering</strong> - On-demand rendering with named parameters<br>
|
|
18
|
-
<li> <strong>File Includes</strong> - Compose prompts from multiple files<br>
|
|
19
|
-
<li> <strong>Custom Directives</strong> - Register custom methods for ERB templates<br>
|
|
20
|
-
<li> <strong>Configurable Pipeline</strong> - Enable/disable stages per prompt or globally<br>
|
|
21
|
-
<li> <strong>Comment Stripping</strong> - HTML comments removed before processing
|
|
22
|
-
</td>
|
|
23
|
-
</tr>
|
|
24
|
-
</table>
|
|
25
|
-
|
|
26
|
-
## Processing Pipeline
|
|
27
|
-
|
|
28
|
-
Every prompt passes through four stages:
|
|
29
|
-
|
|
30
|
-
```mermaid
|
|
31
|
-
graph LR
|
|
32
|
-
A[Strip HTML Comments] --> B[Extract YAML Metadata]
|
|
33
|
-
B --> C[Shell Expansion]
|
|
34
|
-
C --> D["ERB Rendering (on to_s)"]
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
1. **Strip HTML comments** -- `<!-- ... -->` removed before anything else
|
|
38
|
-
2. **Extract YAML metadata** -- Front-matter between `---` fences parsed into `PM::Metadata`
|
|
39
|
-
3. **Shell expansion** -- Environment variables and commands expanded (when `shell: true`)
|
|
40
|
-
4. **ERB rendering** -- Templates evaluated on demand when `to_s` is called (when `erb: true`)
|
|
41
|
-
|
|
42
|
-
## Quick Example
|
|
43
|
-
|
|
44
|
-
Given a file `review.md`:
|
|
45
|
-
|
|
46
|
-
```markdown
|
|
47
|
-
---
|
|
48
|
-
title: Code Review
|
|
49
|
-
parameters:
|
|
50
|
-
language: ruby
|
|
51
|
-
code: null
|
|
52
|
-
---
|
|
53
|
-
Review the following <%= language %> code:
|
|
54
|
-
|
|
55
|
-
<%= code %>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Parse and render:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
require 'pm'
|
|
62
|
-
|
|
63
|
-
parsed = PM.parse('review.md')
|
|
64
|
-
puts parsed.metadata.title #=> "Code Review"
|
|
65
|
-
puts parsed.to_s('code' => source) #=> rendered prompt
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Getting Started
|
|
69
|
-
|
|
70
|
-
Head to [Installation](getting-started/installation.md) to add PM to your project, then follow the [Quick Start](getting-started/quick-start.md) guide.
|