@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 CHANGED
@@ -1,247 +1,123 @@
1
- # rpiv-args
1
+ # @juicesharp/rpiv-args
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@juicesharp/rpiv-args.svg)](https://www.npmjs.com/package/@juicesharp/rpiv-args)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- <picture>
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 your skills like a shell command. `rpiv-args` adds `$1`, `$ARGUMENTS`, `$@`, `${@:N}`, and `${@:N:L}` placeholders to [Pi Agent](https://github.com/badlogic/pi-mono) skills - write `/skill:deploy api production` and your skill body sees `$1` = `api`, `$2` = `production`. Skills without placeholders are untouched, so installing `rpiv-args` is safe for any existing skill collection.
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
- ```bash
21
+ ```sh
16
22
  pi install npm:@juicesharp/rpiv-args
17
23
  ```
18
24
 
19
- Or run `/rpiv-setup` if you have `@juicesharp/rpiv-pi` installed.
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
- ### Full example
27
+ ## Quick start
138
28
 
139
- <details>
140
- <summary>Deploy skill - SKILL.md, invocation, and the exact text the LLM sees</summary>
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
- The LLM receives:
163
-
164
- ```xml
165
- <skill name="deploy" location="...">
166
- Deploy service api to production.
167
-
168
- ## Steps
169
- 1. Run the test suite for api
170
- 2. Build the Docker image
171
- 3. Push to the production registry
172
- 4. Verify the deployment
173
- </skill>
174
-
175
- api production
176
- ```
177
-
178
- Note: the raw arguments (`api production`) are also appended after the
179
- `</skill>` block - this is Pi's standard behavior and is preserved for
180
- backward compatibility.
181
-
182
- </details>
183
-
184
- ## Backward compatibility
185
-
186
- - Skills **without** placeholders output is byte-identical to Pi's built-in expansion
187
- - Skills **with** placeholders → body gets substitution, raw args still appended after block
188
- - The `argument-hint` frontmatter field is read but not enforced in v1
189
-
190
- ## Variables and shell execution
191
-
192
- Skills can reference runtime context and inline shell command output. These run on **every** invocation, regardless of whether the skill body uses `$N` / `$ARGUMENTS` tokens.
193
-
194
- | Syntax | Replaced with |
195
- |---|---|
196
- | `${SKILL_DIR}` | Absolute path to the skill's source directory (forward-slash normalized on Windows) |
197
- | `${SESSION_ID}` | The current Pi session id |
198
- | `` !`command` `` | Single-line shell command output (no newline crossing) |
199
- | ` ```!\n…\n``` ` | Multi-line shell program output (newlines preserved) |
200
-
201
- ### Shell execution semantics
202
-
203
- - **Working directory**: every shell command runs in `process.cwd()` (the Pi session's working directory).
204
- - **Sequential**: commands within one body run one at a time, in source order. `` !`mkdir x` `` then `` !`ls x` `` is safe.
205
- - **Output truncation**: combined stdout + stderr capped at 50 KB / 2000 lines (tail-truncated — failures at the end of the output survive).
206
- - **Errors are inlined** (the rest of the body still reaches the LLM):
207
- - Timeout → `[Shell error: timed out after Ns]`
208
- - Non-zero exit → `[Shell error: exit code N]\n<stderr>`
209
- - **`shell-timeout` frontmatter** (seconds, default 120 s):
210
-
211
- | Value | Effect |
212
- |---|---|
213
- | absent | 120 s (default) |
214
- | positive number (e.g. `5`, `0.5`) | converted to ms |
215
- | `0` | timer disabled (no timeout) |
216
- | any non-finite or negative value (string, `NaN`, `.inf`, `-1`, `true`) | silent fallback to default 120 s |
217
-
218
- ### Cross-platform skill authoring
219
-
220
- On Windows, rpiv-args runs each command via `powershell.exe -Command` (PowerShell 5.1+ ships with every supported Windows version). On macOS / Linux it uses `sh -c`. Most POSIX utilities work on both platforms because PowerShell exposes them as aliases:
221
-
222
- | POSIX command | Works on Windows via PowerShell alias |
223
- |---|---|
224
- | `ls`, `cat`, `pwd`, `cp`, `mv`, `rm`, `mkdir` | ✅ (aliases of `Get-ChildItem`, `Get-Content`, etc.) |
225
- | `git`, `npm`, `node`, `python` | ✅ (external binaries on PATH) |
226
- | `grep`, `sed`, `awk`, `find`, `xargs` | ❌ (not aliased — use PowerShell equivalents like `Select-String`) |
227
-
228
- > **POSIX flags are NOT translated.** Aliases match command NAMES only. `` !`rm -rf x` `` will FAIL under PowerShell because `Remove-Item` takes `-Recurse -Force`, not `-rf`. For destructive or flag-heavy commands, prefer external binaries (`git`, `npm`, `node`) or write a portable PowerShell-flavored block (`` ```! ``` ``) instead.
229
-
230
- **PowerShell cmdlet exit-code quirk**: external commands propagate their exit code via `$LASTEXITCODE`, which PowerShell reflects in its own exit code (so `` !`git status` `` reports failure correctly). However, **cmdlet errors return exit 0 by default**. If a skill relies on a cmdlet's failure to be visible, prepend `$ErrorActionPreference = "Stop"; ` or use `-ErrorAction Stop` per cmdlet. For maximum portability, prefer external commands (`git`, `npm`) over cmdlets where you care about exit codes.
231
-
232
- ## Limitations
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
- [![npm version](https://img.shields.io/npm/v/@juicesharp/rpiv-args.svg)](https://www.npmjs.com/package/@juicesharp/rpiv-args)
245
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.0.0",
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
  ],