@juicesharp/rpiv-args 2.0.0 → 2.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 +92 -216
- package/docs/authoring-skills.md +135 -0
- package/docs/how-it-works.md +97 -0
- package/docs/placeholders.md +86 -0
- package/docs/shell-substitution.md +105 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,247 +1,123 @@
|
|
|
1
|
-
# rpiv-args
|
|
1
|
+
# @juicesharp/rpiv-args
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@juicesharp/rpiv-args)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
5
|
|
|
3
6
|
<div align="center">
|
|
4
7
|
<a href="https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-args">
|
|
5
|
-
<
|
|
6
|
-
<img src="https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/rpiv-args/docs/cover.png" alt="rpiv-args cover" width="50%">
|
|
7
|
-
</picture>
|
|
8
|
+
<img src="https://raw.githubusercontent.com/juicesharp/rpiv-mono/main/packages/rpiv-args/docs/cover.png" alt="Two panels: /skill:deploy api production filling $1 and $2 in a skill body, and a /skill:commit body whose !`git status -s` runs and pastes real git output into the prompt" width="50%">
|
|
8
9
|
</a>
|
|
9
10
|
</div>
|
|
10
11
|
|
|
11
|
-
Pass arguments to
|
|
12
|
+
Pass arguments to a skill the way you pass them to a shell command.
|
|
13
|
+
`rpiv-args` adds `$1`, `$2`, `$ARGUMENTS` and friends to
|
|
14
|
+
[Pi Agent](https://github.com/badlogic/pi-mono) skill bodies, and runs
|
|
15
|
+
`` !`cmd` `` and ```` ```! ```` blocks so real command output is in the prompt
|
|
16
|
+
before the model reads it. It is for anyone who writes Pi skills and wants to
|
|
17
|
+
parameterize them instead of keeping one hard-coded copy per case.
|
|
12
18
|
|
|
13
19
|
## Install
|
|
14
20
|
|
|
15
|
-
```
|
|
21
|
+
```sh
|
|
16
22
|
pi install npm:@juicesharp/rpiv-args
|
|
17
23
|
```
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Placeholders
|
|
22
|
-
|
|
23
|
-
| Placeholder | Replaced with | Example |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| `$1`, `$2`, … | Positional argument (1-indexed) | `/skill:foo a b c` → `$1` = `a`, `$2` = `b` |
|
|
26
|
-
| `$ARGUMENTS` | All arguments as a single string | `/skill:foo a b c` → `a b c` |
|
|
27
|
-
| `$@` | Same as `$ARGUMENTS` | `/skill:foo a b c` → `a b c` |
|
|
28
|
-
| `${@:N}` | Arguments from position N onward | `/skill:foo a b c` → `${@:2}` = `b c` |
|
|
29
|
-
| `${@:N:L}` | L arguments starting at position N | `/skill:foo a b c d` → `${@:2:2}` = `b c` |
|
|
30
|
-
|
|
31
|
-
**Indexing is 1-based** - `$1` is the first argument, `$2` is the second.
|
|
32
|
-
Out-of-range positions resolve to an empty string. For `${@:N[:L]}`, `N` is
|
|
33
|
-
clamped to `≥ 1` and out-of-range slices yield an empty string.
|
|
34
|
-
|
|
35
|
-
Multi-word values use shell-style quoting:
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
/skill:deploy "staging server" --force
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
→ `$1` = `staging server`, `$2` = `--force`, `$ARGUMENTS` = `staging server --force`
|
|
42
|
-
|
|
43
|
-
## How it works
|
|
44
|
-
|
|
45
|
-
rpiv-args intercepts the `input` event (fires before Pi's built-in skill
|
|
46
|
-
expansion). When a skill body contains at least one placeholder, the extension:
|
|
47
|
-
|
|
48
|
-
1. Parses arguments using shell-style quoting
|
|
49
|
-
2. Substitutes all placeholders in the body
|
|
50
|
-
3. Wraps the result in a `<skill>` block byte-identical to Pi's native format
|
|
51
|
-
4. Appends the raw arguments after the block - matches Pi's standard output so any tool that parses `<skill>` blocks continues to work unchanged
|
|
52
|
-
|
|
53
|
-
When no placeholders are found in the skill body, the output is byte-identical
|
|
54
|
-
to Pi's built-in expansion - zero behavioral change.
|
|
55
|
-
|
|
56
|
-
## Writing skills with arguments
|
|
57
|
-
|
|
58
|
-
### `$ARGUMENTS` vs `$1` - which to use
|
|
59
|
-
|
|
60
|
-
Use **`$ARGUMENTS`** (or `$@`) when the input is freeform text the LLM should
|
|
61
|
-
interpret naturally:
|
|
62
|
-
|
|
63
|
-
```yaml
|
|
64
|
-
---
|
|
65
|
-
name: fix-issue
|
|
66
|
-
description: Fix a GitHub issue by number or description
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
Fix the following issue: $ARGUMENTS
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
/skill:fix-issue login page crashes on mobile
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
→ `Fix the following issue: login page crashes on mobile`
|
|
77
|
-
|
|
78
|
-
Use **`$1`, `$2`** only for skills with a fixed, structured invocation pattern:
|
|
79
|
-
|
|
80
|
-
```yaml
|
|
81
|
-
---
|
|
82
|
-
name: migrate-component
|
|
83
|
-
description: Migrate a component between frameworks
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
Migrate the $1 component from $2 to $3.
|
|
87
|
-
Preserve all existing behavior and tests.
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
/skill:migrate-component SearchBar React Vue
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
→ `Migrate the SearchBar component from React to Vue.`
|
|
95
|
-
|
|
96
|
-
### Why this matters
|
|
97
|
-
|
|
98
|
-
If a positional skill receives natural language input:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
/skill:migrate-component can you migrate the search bar please
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
→ `Migrate the can component from you to migrate.` - **broken**.
|
|
105
|
-
|
|
106
|
-
The LLM is good at interpreting `$ARGUMENTS` as a whole, but positional
|
|
107
|
-
placeholders blindly split on spaces. Use `$ARGUMENTS` unless your skill has
|
|
108
|
-
a strict arg structure.
|
|
109
|
-
|
|
110
|
-
### `argument-hint` frontmatter
|
|
111
|
-
|
|
112
|
-
Add an `argument-hint` to document what the skill expects:
|
|
113
|
-
|
|
114
|
-
```yaml
|
|
115
|
-
---
|
|
116
|
-
name: fix-issue
|
|
117
|
-
description: Fix a GitHub issue
|
|
118
|
-
argument-hint: [issue-number-or-description]
|
|
119
|
-
---
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
```yaml
|
|
123
|
-
---
|
|
124
|
-
name: migrate-component
|
|
125
|
-
description: Migrate a component between frameworks
|
|
126
|
-
argument-hint: [component] [from] [to]
|
|
127
|
-
---
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
rpiv-args ignores this field - substitution is triggered by placeholders in the body, not the hint.
|
|
131
|
-
|
|
132
|
-
**Note**: Pi currently surfaces `argument-hint` in autocomplete for prompt
|
|
133
|
-
templates (`commands/*.md`) but **not** for skills (`/skill:<name>`). The
|
|
134
|
-
field is read by Pi but not displayed in the `/skill:` autocomplete UI at
|
|
135
|
-
present - treat it as documentation metadata until upstream Pi exposes it.
|
|
25
|
+
Restart your Pi session.
|
|
136
26
|
|
|
137
|
-
|
|
27
|
+
## Quick start
|
|
138
28
|
|
|
139
|
-
|
|
140
|
-
|
|
29
|
+
Create `.pi/skills/deploy/SKILL.md` in your project (or
|
|
30
|
+
`~/.pi/agent/skills/deploy/SKILL.md` for a personal skill) and put a
|
|
31
|
+
placeholder in the body:
|
|
141
32
|
|
|
142
33
|
```yaml
|
|
143
34
|
---
|
|
144
35
|
name: deploy
|
|
145
36
|
description: Deploy a service to an environment
|
|
146
|
-
argument-hint: [service] [environment]
|
|
147
37
|
---
|
|
148
38
|
|
|
149
39
|
Deploy service $1 to $2.
|
|
150
|
-
|
|
151
|
-
## Steps
|
|
152
|
-
1. Run the test suite for $1
|
|
153
|
-
2. Build the Docker image
|
|
154
|
-
3. Push to the $2 registry
|
|
155
|
-
4. Verify the deployment
|
|
40
|
+
Current branch: !`git branch --show-current`
|
|
156
41
|
```
|
|
157
42
|
|
|
43
|
+
Invoke it with arguments:
|
|
44
|
+
|
|
158
45
|
```
|
|
159
46
|
/skill:deploy api production
|
|
160
47
|
```
|
|
161
48
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
##
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
| Limitation | Detail |
|
|
235
|
-
|---|---|
|
|
236
|
-
| **No type validation** | `$1` expecting a file path receives whatever the user types |
|
|
237
|
-
| **No flag parsing** | `--env=prod` is a single positional token, not a parsed flag |
|
|
238
|
-
| **Literal substitution** | Placeholders are replaced even inside code blocks and inline code |
|
|
239
|
-
| **`steer()`/`followUp()` paths** | `session.steer()` / `session.followUp()` bypass the `input` event (see `agent-session.js:861-887`); placeholders are **not** resolved on those paths. Use the primary prompt path for argument-substituted skills. |
|
|
240
|
-
| **No recursive substitution** | A `$ARGUMENTS` value containing `$1` is not re-expanded |
|
|
49
|
+
The model receives the body with `$1` as `api`, `$2` as `production`, the real
|
|
50
|
+
branch name in place of the `git` command, and a trailing
|
|
51
|
+
`Skill input: api production` line marking your raw input.
|
|
52
|
+
|
|
53
|
+
## What you get
|
|
54
|
+
|
|
55
|
+
- **Skills take arguments like shell commands** — positionals, `$ARGUMENTS`
|
|
56
|
+
and `${@:N:L}` slices, split with shell-style quoting, so
|
|
57
|
+
`/skill:deploy "staging server" --force` puts `staging server` in `$1`.
|
|
58
|
+
- **Command output lands in the prompt, not in a tool call** — inline
|
|
59
|
+
`` !`git status -s` `` and ```` ```! ```` blocks execute first and the model
|
|
60
|
+
reads the evidence instead of deciding to go fetch it.
|
|
61
|
+
- **Installing it is a no-op for existing skills** — a body with no
|
|
62
|
+
placeholder and no shell syntax emits text byte-identical to Pi's built-in
|
|
63
|
+
expansion, pinned by a regression test.
|
|
64
|
+
- **The model stops reading your argument as a new instruction** — arguments
|
|
65
|
+
are emitted under an explicit `Skill input:` label and a skill-invocation
|
|
66
|
+
protocol is prepended to the system prompt every turn.
|
|
67
|
+
- **Runaway commands can't hang the turn or flood the context** — every
|
|
68
|
+
command is capped at 120 s by default and output is tail-truncated to
|
|
69
|
+
50 KB / 2000 lines; errors are inlined so the rest of the body still gets
|
|
70
|
+
through.
|
|
71
|
+
- **Commands run in the order you wrote them** — strictly sequential, never
|
|
72
|
+
parallel, so `` !`mkdir x` `` then `` !`ls x` `` behaves.
|
|
73
|
+
- **Skill-relative paths keep working** — `${SKILL_DIR}` always resolves to
|
|
74
|
+
the skill file's own directory, however the skill was installed;
|
|
75
|
+
`${SESSION_ID}` gives the current session id.
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
`rpiv-args` reads no config file and no environment variables. The one knob is
|
|
80
|
+
per-skill frontmatter, in the skill's own `SKILL.md`:
|
|
81
|
+
|
|
82
|
+
| Key | What it does | Default |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `shell-timeout` | Ceiling in **seconds** for each `` !`cmd` `` / ```` ```! ```` command in that skill. `0` disables the timer. | `120` |
|
|
85
|
+
|
|
86
|
+
Shell commands always run in the Pi session's working directory, not the skill
|
|
87
|
+
directory.
|
|
88
|
+
|
|
89
|
+
## Reference
|
|
90
|
+
|
|
91
|
+
- [Placeholders and variables](https://github.com/juicesharp/rpiv-mono/blob/main/packages/rpiv-args/docs/placeholders.md)
|
|
92
|
+
— every placeholder and runtime variable, indexing and slicing rules,
|
|
93
|
+
quoting, and what substitution deliberately does not do.
|
|
94
|
+
- [Authoring skills](https://github.com/juicesharp/rpiv-mono/blob/main/packages/rpiv-args/docs/authoring-skills.md)
|
|
95
|
+
— choosing `$ARGUMENTS` over positionals, empty-argument behaviour,
|
|
96
|
+
frontmatter, and a worked example with the exact text the model receives.
|
|
97
|
+
- [Shell substitution](https://github.com/juicesharp/rpiv-mono/blob/main/packages/rpiv-args/docs/shell-substitution.md)
|
|
98
|
+
— shell syntax, execution order, timeouts, error strings, output budgets,
|
|
99
|
+
and Windows / PowerShell authoring.
|
|
100
|
+
- [How it works](https://github.com/juicesharp/rpiv-mono/blob/main/packages/rpiv-args/docs/how-it-works.md)
|
|
101
|
+
— the three event hooks, the transformation pipeline, both emit paths, the
|
|
102
|
+
skill index, and the paths that are not covered.
|
|
103
|
+
|
|
104
|
+
## Requirements
|
|
105
|
+
|
|
106
|
+
- A Pi Agent host. No API key, no model selection, no native modules —
|
|
107
|
+
nothing here calls a model.
|
|
108
|
+
- **A POSIX shell or PowerShell** for `` !`cmd` `` / ```` ```! ```` blocks.
|
|
109
|
+
Commands run through `sh -c` on macOS and Linux and `powershell.exe -Command`
|
|
110
|
+
on Windows, where POSIX-only tools such as `grep`, `sed` and `awk` are not
|
|
111
|
+
aliased — see
|
|
112
|
+
[Shell substitution](https://github.com/juicesharp/rpiv-mono/blob/main/packages/rpiv-args/docs/shell-substitution.md).
|
|
113
|
+
|
|
114
|
+
## Related
|
|
115
|
+
|
|
116
|
+
- [@juicesharp/rpiv-pi](https://www.npmjs.com/package/@juicesharp/rpiv-pi) —
|
|
117
|
+
the umbrella package. Its `/rpiv-setup` command installs `rpiv-args` along
|
|
118
|
+
with the rest of the family, and its lane transcript hides the
|
|
119
|
+
`Skill input:` line from the displayed conversation.
|
|
241
120
|
|
|
242
121
|
## License
|
|
243
122
|
|
|
244
|
-
[
|
|
245
|
-
[](https://opensource.org/licenses/MIT)
|
|
246
|
-
|
|
247
|
-
MIT
|
|
123
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Writing skills that take arguments
|
|
2
|
+
|
|
3
|
+
How to choose between freeform and positional arguments, what the model
|
|
4
|
+
actually receives, and the frontmatter that affects it.
|
|
5
|
+
|
|
6
|
+
## `$ARGUMENTS` vs `$1` — which to use
|
|
7
|
+
|
|
8
|
+
Use **`$ARGUMENTS`** (or `$@`) when the input is freeform text the model
|
|
9
|
+
should interpret as a whole:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
---
|
|
13
|
+
name: fix-issue
|
|
14
|
+
description: Fix a GitHub issue by number or description
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Fix the following issue: $ARGUMENTS
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
/skill:fix-issue login page crashes on mobile
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
→ `Fix the following issue: login page crashes on mobile`
|
|
25
|
+
|
|
26
|
+
Use **`$1`, `$2`** only when the skill has a fixed, structured invocation:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
---
|
|
30
|
+
name: migrate-component
|
|
31
|
+
description: Migrate a component between frameworks
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
Migrate the $1 component from $2 to $3.
|
|
35
|
+
Preserve all existing behavior and tests.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
/skill:migrate-component SearchBar React Vue
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
→ `Migrate the SearchBar component from React to Vue.`
|
|
43
|
+
|
|
44
|
+
### Why the choice matters
|
|
45
|
+
|
|
46
|
+
Positional placeholders split blindly on whitespace. If a positional skill
|
|
47
|
+
receives natural language:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
/skill:migrate-component can you migrate the search bar please
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
→ `Migrate the can component from you to migrate.` — broken.
|
|
54
|
+
|
|
55
|
+
Prefer `$ARGUMENTS` unless the invocation really is structured. When you do
|
|
56
|
+
use positionals, document the shape in `description` and expect users to quote
|
|
57
|
+
multi-word values (`/skill:deploy "staging server" production`).
|
|
58
|
+
|
|
59
|
+
## Empty arguments
|
|
60
|
+
|
|
61
|
+
When a skill is invoked with no arguments, the emitted text ends at
|
|
62
|
+
`</skill>` — no trailing argument text and no `Skill input:` line at all. A
|
|
63
|
+
skill that has an "if no input was given" branch can rely on that absence.
|
|
64
|
+
|
|
65
|
+
## Worked example
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
---
|
|
69
|
+
name: deploy
|
|
70
|
+
description: Deploy a service to an environment
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
Deploy service $1 to $2.
|
|
74
|
+
|
|
75
|
+
## Steps
|
|
76
|
+
1. Run the test suite for $1
|
|
77
|
+
2. Build the Docker image
|
|
78
|
+
3. Push to the $2 registry
|
|
79
|
+
4. Verify the deployment
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
/skill:deploy api production
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The model receives:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
<skill name="deploy" location="/path/to/deploy/SKILL.md">
|
|
90
|
+
References are relative to /path/to/deploy.
|
|
91
|
+
|
|
92
|
+
Deploy service api to production.
|
|
93
|
+
|
|
94
|
+
## Steps
|
|
95
|
+
1. Run the test suite for api
|
|
96
|
+
2. Build the Docker image
|
|
97
|
+
3. Push to the production registry
|
|
98
|
+
4. Verify the deployment
|
|
99
|
+
</skill>
|
|
100
|
+
|
|
101
|
+
Skill input: api production
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The `Skill input:` trailer carries the **raw**, un-substituted argument string.
|
|
105
|
+
It appears only when the body contains argument placeholders; a body with no
|
|
106
|
+
placeholders gets the bare argument string after the block instead, exactly as
|
|
107
|
+
Pi's built-in expansion emits it.
|
|
108
|
+
|
|
109
|
+
## Frontmatter
|
|
110
|
+
|
|
111
|
+
| Key | Effect |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `shell-timeout` | Per-skill ceiling in seconds for `` !`cmd` `` / ```` ```! ```` execution. See [shell-substitution.md](shell-substitution.md). |
|
|
114
|
+
| `argument-hint` | Not read by `rpiv-args`. Substitution is triggered by placeholders in the body, never by a hint. Treat it as documentation for readers of your skill. |
|
|
115
|
+
|
|
116
|
+
There is no `arguments:` key and no required-argument enforcement: a skill
|
|
117
|
+
cannot declare that `$1` is mandatory, and nothing fails when it is missing —
|
|
118
|
+
the placeholder simply resolves to an empty string.
|
|
119
|
+
|
|
120
|
+
## Skill-relative assets
|
|
121
|
+
|
|
122
|
+
Use `${SKILL_DIR}` to point at files shipped next to the skill:
|
|
123
|
+
|
|
124
|
+
```md
|
|
125
|
+
Follow the checklist in ${SKILL_DIR}/checklist.md before editing anything.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`${SKILL_DIR}` resolves to the directory of the skill file itself, so the same
|
|
129
|
+
skill works whether it was loaded from a skills directory, a plugin, or
|
|
130
|
+
another extension's `pi.skills` manifest.
|
|
131
|
+
|
|
132
|
+
## New skills are picked up on reload
|
|
133
|
+
|
|
134
|
+
The skill index is built once per session and cached. After adding or renaming
|
|
135
|
+
a skill file, run `/reload` (or start a new session) so the index is rebuilt.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# How rpiv-args works
|
|
2
|
+
|
|
3
|
+
The internals: which Pi events the extension hooks, how a `/skill:` message is
|
|
4
|
+
transformed, what exactly it emits, and which paths it does not cover.
|
|
5
|
+
|
|
6
|
+
## Surface
|
|
7
|
+
|
|
8
|
+
`rpiv-args` registers no slash commands, no tools and no keybindings. Its
|
|
9
|
+
entire surface is three Pi event hooks.
|
|
10
|
+
|
|
11
|
+
| Hook | What it does |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `input` | Intercepts `/skill:<name> <args>` before Pi's built-in expander, runs the pipeline, and returns transformed text |
|
|
14
|
+
| `before_agent_start` | Prepends a `## Skill invocation protocol (CRITICAL)` section to the system prompt, every turn |
|
|
15
|
+
| `session_start` | Rebuilds the skill index when the session reason is `reload` or `startup` |
|
|
16
|
+
|
|
17
|
+
## Input dispatch
|
|
18
|
+
|
|
19
|
+
The `input` handler takes one of four branches:
|
|
20
|
+
|
|
21
|
+
1. Text already starts with `<skill ` → passed through untouched. This is the
|
|
22
|
+
re-entrancy guard: text transformed by this or any other extension is not
|
|
23
|
+
reprocessed.
|
|
24
|
+
2. Text does not start with `/skill:` → passed through untouched.
|
|
25
|
+
3. The skill name is unknown, or the skill file cannot be read → passed
|
|
26
|
+
through, so Pi emits its own handling or error.
|
|
27
|
+
4. Known skill → transformed.
|
|
28
|
+
|
|
29
|
+
## Pipeline
|
|
30
|
+
|
|
31
|
+
For a known skill, in this order:
|
|
32
|
+
|
|
33
|
+
1. Read the skill file and split frontmatter from body.
|
|
34
|
+
2. Record whether the body contains any argument placeholder — this flag is
|
|
35
|
+
captured **before** substitution and decides the emit path.
|
|
36
|
+
3. If it does, tokenise the argument string shell-style and substitute
|
|
37
|
+
`$N` / `${@:N[:L]}` / `$ARGUMENTS` / `$@`.
|
|
38
|
+
4. Substitute `${SKILL_DIR}` and `${SESSION_ID}` — always, on both paths.
|
|
39
|
+
5. Execute `` !`cmd` `` and ```` ```! ```` blocks — always, on both paths.
|
|
40
|
+
6. Wrap the result in a `<skill name="…" location="…">` block byte-identical
|
|
41
|
+
to Pi's native format, and append the trailer.
|
|
42
|
+
|
|
43
|
+
## Emit paths
|
|
44
|
+
|
|
45
|
+
| Body | Emitted after `</skill>` |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| No argument placeholders | Blank line, then the raw argument string — the suffix is byte-identical to Pi's built-in expansion |
|
|
48
|
+
| Has argument placeholders | Blank line, then `Skill input: <raw args>` |
|
|
49
|
+
| Either, invoked with no arguments | Nothing; the text ends at `</skill>` |
|
|
50
|
+
|
|
51
|
+
A body with no argument placeholders, no `${…}` variables and no shell syntax
|
|
52
|
+
emits bytes identical to Pi's built-in expansion; that is pinned by a
|
|
53
|
+
regression test, and it is what makes installing the extension a no-op for
|
|
54
|
+
existing skill collections. Bodies that do use variables or shell syntax still
|
|
55
|
+
get steps 4 and 5 on this path — `hadTokens` governs the trailer only, not the
|
|
56
|
+
substitution pipeline.
|
|
57
|
+
|
|
58
|
+
The trailer always carries the **raw**, un-substituted argument string. If a
|
|
59
|
+
user types `${SKILL_DIR}` as an argument, the occurrence woven into the body
|
|
60
|
+
is substituted while the trailer keeps the literal text.
|
|
61
|
+
|
|
62
|
+
## The `Skill input:` trailer and the protocol block
|
|
63
|
+
|
|
64
|
+
Bare trailing text after `</skill>` reads to a model like a second, separate
|
|
65
|
+
instruction — especially when the argument is phrased as an imperative
|
|
66
|
+
("delete the old migrations"). Two things fix that together:
|
|
67
|
+
|
|
68
|
+
- the labelled `Skill input:` trailer on the placeholder path, and
|
|
69
|
+
- the `## Skill invocation protocol (CRITICAL)` section prepended to the
|
|
70
|
+
system prompt on every turn, which tells the model that text after
|
|
71
|
+
`</skill>` is argument input to the skill, never a separate command, and
|
|
72
|
+
that the same value may also appear substituted inside the body.
|
|
73
|
+
|
|
74
|
+
The label string is a cross-package contract: `@juicesharp/rpiv-pi`'s lane
|
|
75
|
+
transcript and `@juicesharp/rpiv-warp`'s toast summariser both strip
|
|
76
|
+
`Skill input:` for display using a literal regex. Changing the label requires
|
|
77
|
+
changing them too.
|
|
78
|
+
|
|
79
|
+
## Skill index
|
|
80
|
+
|
|
81
|
+
The name → file-path index is built lazily from Pi's command registry the
|
|
82
|
+
first time a `/skill:` message arrives, then memoised for the session. It is
|
|
83
|
+
sourced from the registry rather than from a filesystem walk, so it also
|
|
84
|
+
recognises skills declared by another extension's `pi.skills` manifest.
|
|
85
|
+
|
|
86
|
+
That also means `sendUserMessage("/skill:… ")` calls made programmatically by
|
|
87
|
+
other extensions are expanded: those bypass Pi's built-in expander, leaving
|
|
88
|
+
`rpiv-args` as the only expander on that path.
|
|
89
|
+
|
|
90
|
+
The index is invalidated on `session_start` with reason `reload` or `startup`.
|
|
91
|
+
Other reasons — resuming a session, for example — keep the cached index.
|
|
92
|
+
|
|
93
|
+
## Paths not covered
|
|
94
|
+
|
|
95
|
+
`session.steer()` and `session.followUp()` do not go through the `input`
|
|
96
|
+
event, so placeholders are not resolved on those paths. Use the primary
|
|
97
|
+
prompt path for argument-substituted skills.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Placeholders and variables
|
|
2
|
+
|
|
3
|
+
Complete reference for every token `rpiv-args` substitutes into a skill body:
|
|
4
|
+
the positional argument family, the slice forms, and the always-on runtime
|
|
5
|
+
variables.
|
|
6
|
+
|
|
7
|
+
## Argument placeholders
|
|
8
|
+
|
|
9
|
+
Substitution runs only when the skill body contains at least one of these
|
|
10
|
+
tokens. A body with none of them is passed through untouched.
|
|
11
|
+
|
|
12
|
+
| Placeholder | Replaced with | Example (`/skill:foo a b c d`) |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `$1`, `$2`, … `$N` | The Nth argument, 1-indexed | `$2` → `b` |
|
|
15
|
+
| `$ARGUMENTS` | All arguments joined by a single space | `a b c d` |
|
|
16
|
+
| `$@` | Identical to `$ARGUMENTS` | `a b c d` |
|
|
17
|
+
| `${@:N}` | Arguments from position N onward | `${@:2}` → `b c d` |
|
|
18
|
+
| `${@:N:L}` | L arguments starting at position N | `${@:2:2}` → `b c` |
|
|
19
|
+
|
|
20
|
+
### Indexing rules
|
|
21
|
+
|
|
22
|
+
- Indexing is **1-based**: `$1` is the first argument.
|
|
23
|
+
- An out-of-range position resolves to an **empty string**, not to a literal
|
|
24
|
+
`$3`. `/skill:foo a` leaves `$2` as `""`.
|
|
25
|
+
- Digits are matched greedily: `$11` is the eleventh argument, never `$1`
|
|
26
|
+
followed by a literal `1`.
|
|
27
|
+
- In `${@:N}` and `${@:N:L}`, `N` is clamped to `≥ 1`, so `${@:0}` returns the
|
|
28
|
+
whole argument list. A slice that starts past the end of the list produces
|
|
29
|
+
an empty slice, which joins to an empty string.
|
|
30
|
+
|
|
31
|
+
### Substitution order
|
|
32
|
+
|
|
33
|
+
Replacements are applied in a fixed order: `$N` first, then `${@:N[:L]}`, then
|
|
34
|
+
`$ARGUMENTS`, then `$@`.
|
|
35
|
+
|
|
36
|
+
The order is load-bearing. Because `$N` runs first, an argument value that
|
|
37
|
+
itself contains `$1` is **not** re-expanded when it lands in the body via
|
|
38
|
+
`$ARGUMENTS` or a slice. There is no recursive substitution.
|
|
39
|
+
|
|
40
|
+
## Argument tokenisation
|
|
41
|
+
|
|
42
|
+
The argument string after `/skill:<name> ` is split shell-style:
|
|
43
|
+
|
|
44
|
+
- Splits on spaces **and** tabs; runs of whitespace collapse.
|
|
45
|
+
- Both `"` and `'` quote a multi-word value.
|
|
46
|
+
- Quote styles can mix inside one token: `"a b"c` produces the single
|
|
47
|
+
argument `a bc`.
|
|
48
|
+
- An unmatched quote flushes what it has instead of raising an error.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
/skill:deploy "staging server" --force
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
→ `$1` = `staging server`, `$2` = `--force`, `$ARGUMENTS` = `staging server --force`
|
|
55
|
+
|
|
56
|
+
Arguments are always plain strings. There is no type validation and no flag
|
|
57
|
+
parsing — `--env=prod` is one positional token, not a parsed option.
|
|
58
|
+
|
|
59
|
+
## Runtime variables
|
|
60
|
+
|
|
61
|
+
These are substituted on **every** invocation, whether or not the body uses
|
|
62
|
+
argument placeholders.
|
|
63
|
+
|
|
64
|
+
| Variable | Replaced with |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `${SKILL_DIR}` | Absolute path of the directory containing the skill file |
|
|
67
|
+
| `${SESSION_ID}` | The current Pi session id |
|
|
68
|
+
|
|
69
|
+
`${SKILL_DIR}` is always `dirname()` of the skill file itself, so a skill can
|
|
70
|
+
reference a sibling asset (`${SKILL_DIR}/template.md`) regardless of how the
|
|
71
|
+
skill was installed — filesystem skill directory, plugin, or a skill declared
|
|
72
|
+
by another extension's `pi.skills` manifest. On Windows the value is
|
|
73
|
+
normalised to forward slashes; on POSIX the path is preserved byte for byte,
|
|
74
|
+
including literal backslashes.
|
|
75
|
+
|
|
76
|
+
Unknown `${FOO}` placeholders are left in the body untouched.
|
|
77
|
+
|
|
78
|
+
## What substitution does not do
|
|
79
|
+
|
|
80
|
+
| Limit | Detail |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| No type validation | `$1` receives whatever the user typed; a skill expecting a path can get a sentence |
|
|
83
|
+
| No flag parsing | `--force` is a positional token like any other |
|
|
84
|
+
| Literal substitution | Placeholders are replaced inside fenced code blocks and inline code too — there is no fence awareness |
|
|
85
|
+
| No recursive substitution | A value containing `$1` is never re-expanded |
|
|
86
|
+
| Body-token trigger only | There is no `arguments:` frontmatter key; substitution keys off tokens present in the body |
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Shell substitution
|
|
2
|
+
|
|
3
|
+
Complete reference for `` !`command` `` and ```` ```! ```` blocks in skill
|
|
4
|
+
bodies: syntax, execution order, timeouts, error text, output budgets, and
|
|
5
|
+
cross-platform authoring.
|
|
6
|
+
|
|
7
|
+
## Syntax
|
|
8
|
+
|
|
9
|
+
| Form | Behaviour |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `` !`command` `` | Inline. Single line only — the pattern never crosses a newline. Requires at least one character between the backticks, so a literal empty `` !`` `` in prose is left verbatim and never executed. |
|
|
12
|
+
| ```` ```!\n…\n``` ```` | Block. Multi-line; newlines are preserved and the whole block is handed to the shell as one program. |
|
|
13
|
+
|
|
14
|
+
Both forms run on **every** invocation of the skill, whether or not the body
|
|
15
|
+
uses `$N` / `$ARGUMENTS` placeholders. The command's output replaces the
|
|
16
|
+
`` !`…` `` or fence in the body before the model sees anything.
|
|
17
|
+
|
|
18
|
+
## Execution semantics
|
|
19
|
+
|
|
20
|
+
- **Working directory** — every command runs in `process.cwd()`, the Pi
|
|
21
|
+
session's working directory. Not the skill directory. Use `${SKILL_DIR}` if
|
|
22
|
+
you need a skill-relative path.
|
|
23
|
+
- **Sequential** — commands in one body run one at a time, in source order,
|
|
24
|
+
never in parallel. `` !`mkdir x` `` followed by `` !`ls x` `` is safe.
|
|
25
|
+
- **Blocks before inlines** — fenced blocks execute first and their output is
|
|
26
|
+
masked while the inline pass runs, so block stdout that happens to contain
|
|
27
|
+
`` !`something` `` is never re-executed.
|
|
28
|
+
- **Shell** — `sh -c` on macOS and Linux, `powershell.exe -Command` on
|
|
29
|
+
Windows.
|
|
30
|
+
|
|
31
|
+
## Timeouts
|
|
32
|
+
|
|
33
|
+
`shell-timeout` in the skill's own frontmatter sets the ceiling, in **seconds**,
|
|
34
|
+
for every command in that skill.
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
---
|
|
38
|
+
name: commit
|
|
39
|
+
description: Draft a commit message from the working tree
|
|
40
|
+
shell-timeout: 30
|
|
41
|
+
---
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Value | Effect |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| absent | 120 s (the default) |
|
|
47
|
+
| positive number (`5`, `0.5`) | Converted to milliseconds; sub-second values are honoured |
|
|
48
|
+
| `0` | Timer disabled — no timeout |
|
|
49
|
+
| negative, string, `true`, `.nan`, `.inf` | Silent fallback to 120 s |
|
|
50
|
+
|
|
51
|
+
A timed-out command is reported as `[Shell error: timed out after Ns]`. The
|
|
52
|
+
displayed seconds value is floored at `1`, so a `shell-timeout: 0.5` timeout
|
|
53
|
+
still reads `after 1s`.
|
|
54
|
+
|
|
55
|
+
## Errors and output budget
|
|
56
|
+
|
|
57
|
+
Errors are inlined into the body, so the rest of the skill still reaches the
|
|
58
|
+
model:
|
|
59
|
+
|
|
60
|
+
| Situation | Text substituted into the body |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Timeout | `[Shell error: timed out after Ns]` |
|
|
63
|
+
| Non-zero exit | `[Shell error: exit code N]` followed by the command's stderr |
|
|
64
|
+
| Success with stderr output | stdout, then `[stderr]` on its own line, then stderr |
|
|
65
|
+
|
|
66
|
+
Timeout wins over exit code: a killed command reports the timeout message even
|
|
67
|
+
if it also produced a non-zero code.
|
|
68
|
+
|
|
69
|
+
Output is capped at **50 KB / 2000 lines**, tail-truncated — the *end* of the
|
|
70
|
+
output survives, which is where failures usually appear. When truncation
|
|
71
|
+
happens a footer is appended: `[truncated: hit 2000 lines]` or
|
|
72
|
+
`[truncated: hit 50.0KB]`. The cap applies to the non-zero-exit path too, so
|
|
73
|
+
a multi-megabyte stderr from a failed `` !`npm test` `` cannot blow past the
|
|
74
|
+
budget.
|
|
75
|
+
|
|
76
|
+
## Trust model
|
|
77
|
+
|
|
78
|
+
Substitution order is arguments → variables → shell. An argument that contains
|
|
79
|
+
`` !`echo hi` `` and lands in the body through `$ARGUMENTS` will therefore be
|
|
80
|
+
executed. This is deliberate: skill bodies and the local user are trusted, and
|
|
81
|
+
a skill author who interpolates arguments is interpolating into a program.
|
|
82
|
+
Treat `/skill:` invocations with the same trust you give your own shell.
|
|
83
|
+
|
|
84
|
+
## Cross-platform authoring
|
|
85
|
+
|
|
86
|
+
On Windows each command runs through `powershell.exe -Command`; on macOS and
|
|
87
|
+
Linux through `sh -c`. Many POSIX utilities work on both because PowerShell
|
|
88
|
+
exposes them as aliases:
|
|
89
|
+
|
|
90
|
+
| POSIX command | On Windows |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `ls`, `cat`, `pwd`, `cp`, `mv`, `rm`, `mkdir` | Works — PowerShell aliases of `Get-ChildItem`, `Get-Content`, etc. |
|
|
93
|
+
| `git`, `npm`, `node`, `python` | Works — external binaries on `PATH` |
|
|
94
|
+
| `grep`, `sed`, `awk`, `find`, `xargs` | Not aliased — use PowerShell equivalents such as `Select-String` |
|
|
95
|
+
|
|
96
|
+
**POSIX flags are not translated.** Aliases match command *names* only.
|
|
97
|
+
`` !`rm -rf x` `` fails under PowerShell, which expects `-Recurse -Force`. For
|
|
98
|
+
flag-heavy or destructive commands prefer external binaries (`git`, `npm`,
|
|
99
|
+
`node`) or write a PowerShell-flavoured ```` ```! ```` block.
|
|
100
|
+
|
|
101
|
+
**Exit-code quirk.** External commands propagate their exit code, so
|
|
102
|
+
`` !`git status` `` reports failure correctly. PowerShell *cmdlet* errors
|
|
103
|
+
return exit 0 by default. If a skill depends on a cmdlet failure being
|
|
104
|
+
visible, prepend `$ErrorActionPreference = "Stop"; ` or pass
|
|
105
|
+
`-ErrorAction Stop` per cmdlet.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-args",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Pi extension. Shell-style $1 / $ARGUMENTS placeholders and !`cmd` / ```! shell substitution, expanded into your Pi skills at invocation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"files": [
|
|
35
35
|
"index.ts",
|
|
36
36
|
"args.ts",
|
|
37
|
+
"docs/",
|
|
38
|
+
"!docs/*.png",
|
|
39
|
+
"!docs/*.jpg",
|
|
40
|
+
"!docs/*.svg",
|
|
37
41
|
"README.md",
|
|
38
42
|
"LICENSE"
|
|
39
43
|
],
|