pi-extensions 0.1.17 → 0.1.20
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 +8 -1
- package/agent-guidance/CHANGELOG.md +5 -0
- package/agent-guidance/package.json +1 -1
- package/arcade/CHANGELOG.md +3 -0
- package/arcade/package.json +3 -2
- package/code-actions/CHANGELOG.md +3 -0
- package/code-actions/package.json +3 -2
- package/files-widget/CHANGELOG.md +5 -0
- package/files-widget/README.md +4 -4
- package/files-widget/package.json +3 -2
- package/package.json +3 -1
- package/pi-skill-creator/CHANGELOG.md +11 -0
- package/pi-skill-creator/LICENSE +21 -0
- package/pi-skill-creator/README.md +6 -0
- package/pi-skill-creator/SKILL.md +139 -0
- package/pi-skill-creator/package.json +22 -0
- package/pi-skill-creator/scripts/validate_skill.py +208 -0
- package/ralph-wiggum/CHANGELOG.md +5 -0
- package/ralph-wiggum/package.json +3 -2
- package/raw-paste/CHANGELOG.md +3 -0
- package/raw-paste/package.json +3 -2
- package/tab-status/CHANGELOG.md +3 -0
- package/tab-status/package.json +3 -2
- package/usage-extension/CHANGELOG.md +3 -0
- package/usage-extension/package.json +3 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Personal extensions for the [Pi coding agent](https://github.com/badlogic/pi-mon
|
|
|
6
6
|
|
|
7
7
|
| Extension | Description |
|
|
8
8
|
|-----------|-------------|
|
|
9
|
-
| [/
|
|
9
|
+
| [/readfiles](files-widget/) | In-terminal file browser and viewer widget. Navigate files, view diffs, select code, send comments to agent - without leaving Pi, and without interrupting your agent |
|
|
10
10
|
| [tab-status](tab-status/) | Manage as many parallel sessions as your mind can handle. Terminal tab indicators for <br>✅ done / 🚧 stuck / 🛑 timed out |
|
|
11
11
|
| [ralph-wiggum](ralph-wiggum/) | Run arbitrarily-long tasks without diluting model attention. Flat version without subagents like [ralph-loop](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/ralph-loop) |
|
|
12
12
|
| [agent-guidance](agent-guidance/) | Switch between Claude/Codex/Gemini with model-specific guidance (CLAUDE.md, CODEX.md, GEMINI.md) |
|
|
@@ -15,6 +15,13 @@ Personal extensions for the [Pi coding agent](https://github.com/badlogic/pi-mon
|
|
|
15
15
|
| [/code](code-actions/) | Pick code blocks or inline snippets from assistant messages to copy, insert, or run with `/code` |
|
|
16
16
|
| [arcade](arcade/) | Play minigames while your tests run: 👾 sPIce-invaders, 👻 picman, 🏓 ping, 🧩 tetris, 🍄 mario-not |
|
|
17
17
|
|
|
18
|
+
## Skills
|
|
19
|
+
|
|
20
|
+
| Skill | Description |
|
|
21
|
+
|-------|-------------|
|
|
22
|
+
| [pi-skill-creator](pi-skill-creator/) | Guidelines and templates for creating Pi skills. |
|
|
23
|
+
| [ralph-wiggum](ralph-wiggum/) | Skill instructions for long-running development loops. |
|
|
24
|
+
|
|
18
25
|
## Install (pi package manager)
|
|
19
26
|
|
|
20
27
|
```bash
|
package/arcade/CHANGELOG.md
CHANGED
package/arcade/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-arcade",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Arcade minigames for the Pi coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"ping.ts",
|
|
22
22
|
"tetris.ts",
|
|
23
23
|
"mario-not/mario-not.ts"
|
|
24
|
-
]
|
|
24
|
+
],
|
|
25
|
+
"video": "https://raw.githubusercontent.com/tmustier/pi-extensions/main/arcade/assets/demo.mp4"
|
|
25
26
|
}
|
|
26
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-code-actions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Pick code blocks or inline snippets from recent assistant messages to copy or insert.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"pi": {
|
|
18
18
|
"extensions": [
|
|
19
19
|
"index.ts"
|
|
20
|
-
]
|
|
20
|
+
],
|
|
21
|
+
"image": "https://github.com/user-attachments/assets/0dc10a64-d61f-4b56-9684-5e448c759385"
|
|
21
22
|
}
|
|
22
23
|
}
|
package/files-widget/README.md
CHANGED
|
@@ -14,7 +14,7 @@ In-terminal file browser and diff viewer widget for Pi. Navigate files, view dif
|
|
|
14
14
|
pi install npm:@tmustier/pi-files-widget
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Required deps (needed for /
|
|
17
|
+
Required deps (needed for /readfiles):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# macOS (Homebrew)
|
|
@@ -75,11 +75,11 @@ Then reference it in your settings:
|
|
|
75
75
|
- `delta`: formatted diffs
|
|
76
76
|
- `glow`: markdown rendering
|
|
77
77
|
|
|
78
|
-
The `/
|
|
78
|
+
The `/readfiles` browser requires these tools and will refuse to open until they are installed.
|
|
79
79
|
|
|
80
80
|
## Commands
|
|
81
81
|
|
|
82
|
-
- `/
|
|
82
|
+
- `/readfiles` - open the file browser
|
|
83
83
|
- `/review` - open tuicr review flow
|
|
84
84
|
- `/diff` - open critique (bunx critique)
|
|
85
85
|
|
|
@@ -127,4 +127,4 @@ If missing, `/review` or `/diff` will show a clear install prompt.
|
|
|
127
127
|
- Folder LOCs are shown only when the folder is collapsed (expanded folders would duplicate counts).
|
|
128
128
|
- Line counts load asynchronously; the header shows activity while counts are computed.
|
|
129
129
|
- Large non-git folders load progressively and may show `[partial]` while loading in safe mode.
|
|
130
|
-
- Git status refreshes every 3 seconds while `/
|
|
130
|
+
- Git status refreshes every 3 seconds while `/readfiles` is open.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-files-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "In-terminal file browser and viewer for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"pi": {
|
|
25
25
|
"extensions": [
|
|
26
26
|
"index.ts"
|
|
27
|
-
]
|
|
27
|
+
],
|
|
28
|
+
"video": "https://raw.githubusercontent.com/tmustier/pi-extensions/main/files-widget/demo.mp4"
|
|
28
29
|
}
|
|
29
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"private": false,
|
|
5
6
|
"keywords": [
|
|
6
7
|
"pi-package"
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
"./ready-status/ready-status.ts"
|
|
26
27
|
],
|
|
27
28
|
"skills": [
|
|
29
|
+
"./pi-skill-creator/SKILL.md",
|
|
28
30
|
"./ralph-wiggum/SKILL.md"
|
|
29
31
|
]
|
|
30
32
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 - 2026-02-05
|
|
4
|
+
- Consolidate into standalone SKILL.md: remove references/ directory (design-patterns, workflows, output-patterns).
|
|
5
|
+
- Reframe directives as context throughout — add "Context over directives" principle.
|
|
6
|
+
- Add Activation principle (description is the sole trigger).
|
|
7
|
+
- Fold Pi-specific reference guidelines (one level deep, no duplication) into step 7.
|
|
8
|
+
- Clarify optional frontmatter fields, `disable-model-invocation`, and name rules.
|
|
9
|
+
|
|
10
|
+
## 0.1.0 - 2026-02-05
|
|
11
|
+
- Initial release.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Mustier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-skill-creator
|
|
3
|
+
description: Create or update Pi skills (SKILL.md plus optional scripts, references, or assets). Use when someone asks to design a new Pi skill, refine an existing one, or structure skills for Pi discovery or packaging.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pi Skill Creator
|
|
7
|
+
|
|
8
|
+
Provide guidance for creating effective Pi skills.
|
|
9
|
+
|
|
10
|
+
## Principles
|
|
11
|
+
|
|
12
|
+
- **Conciseness**: the agent is already very capable - only domain-specific knowledge, workflows, and tooling it can't infer earn their token cost.
|
|
13
|
+
- **Progressive disclosure**: Pi loads skills in tiers - frontmatter (always in context), body (on trigger), bundled resources (on demand) - so splitting content across them keeps context lean.
|
|
14
|
+
- **Activation**: Pi decides whether to load a skill based solely on the frontmatter `description`. "When to use" information in the body comes too late.
|
|
15
|
+
- **Context over directives**: the agent makes better decisions when it understands why. For tasks requiring judgment, context like "X helps Y because Z" is more robust than instructions like "You MUST do X".
|
|
16
|
+
|
|
17
|
+
## Pi Agent Skills format
|
|
18
|
+
|
|
19
|
+
- Required frontmatter: `name`, `description`. Directory name must equal `name`.
|
|
20
|
+
- Name rules: 1–64 chars, lowercase letters/digits/hyphens, no leading/trailing/consecutive hyphens.
|
|
21
|
+
- Optional frontmatter: `license`, `compatibility`, `metadata` (arbitrary key-value pairs for tooling), `allowed-tools` (restrict which tools the skill may invoke).
|
|
22
|
+
- `disable-model-invocation`: when set, Pi won't auto-trigger the skill; the user must invoke it explicitly with `/skill:name`.
|
|
23
|
+
- Paths are relative to the skill directory; `{baseDir}` placeholders are not supported.
|
|
24
|
+
- Skill locations: `~/.pi/agent/skills/`, `.pi/skills/`, `skills/` in a package, settings `skills`, or `--skill <path>`.
|
|
25
|
+
|
|
26
|
+
## Recommended structure
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pi-skill/
|
|
30
|
+
├── SKILL.md
|
|
31
|
+
├── README.md # Optional: human summary + installation
|
|
32
|
+
├── scripts/ # Optional executables
|
|
33
|
+
├── references/ # Optional docs loaded on demand
|
|
34
|
+
└── assets/ # Optional templates/assets
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
### 1) Clarify use cases
|
|
40
|
+
|
|
41
|
+
2-4 concrete example requests usually suffice to scope triggers and functionality.
|
|
42
|
+
|
|
43
|
+
### 2) Plan reusable resources
|
|
44
|
+
|
|
45
|
+
For each example, decide if you need:
|
|
46
|
+
- **scripts/** for deterministic tasks
|
|
47
|
+
- **references/** for long docs or schemas
|
|
48
|
+
- **assets/** for templates or boilerplate
|
|
49
|
+
|
|
50
|
+
### 3) Create the skeleton
|
|
51
|
+
|
|
52
|
+
A skill needs a directory containing a SKILL.md. Only add resource sub-directories that are actually needed.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mkdir -p ~/.pi/agent/skills/my-skill
|
|
56
|
+
touch ~/.pi/agent/skills/my-skill/SKILL.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4) Optional: Write README.md (humans + installation)
|
|
60
|
+
|
|
61
|
+
If you plan to share the skill with humans, a README.md helps discovery and installation. If you have a README, installation info can live there to save space in SKILL.md.
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
# My Skill
|
|
65
|
+
|
|
66
|
+
Short summary for humans discovering the skill.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
`pi install git:github.com/org/my-skill`
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 5) Write frontmatter
|
|
73
|
+
|
|
74
|
+
Use only the fields you need.
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
---
|
|
78
|
+
name: my-skill
|
|
79
|
+
description: What it does + when to use it.
|
|
80
|
+
---
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If you need to hide auto-invocation, set:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
disable-model-invocation: true
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 6) Write the body
|
|
90
|
+
|
|
91
|
+
- Imperative phrasing works well for procedural instructions; context framing works better for guidance (see Principles).
|
|
92
|
+
- ~500 lines is a practical ceiling for SKILL.md; beyond that, split content into references.
|
|
93
|
+
- The agent won't know a reference file exists unless SKILL.md says when to read it.
|
|
94
|
+
|
|
95
|
+
### 7) Add resources
|
|
96
|
+
|
|
97
|
+
SKILL.md is the agent's interface to the skill — usage examples and input/output descriptions let it call scripts without needing to understand internals.
|
|
98
|
+
|
|
99
|
+
- **scripts/**: usage examples in SKILL.md inform the agent how to call them; scripts should be executable.
|
|
100
|
+
- **references/**: a table of contents helps the agent navigate files longer than ~100 lines. References work best one level deep from SKILL.md, and each fact should live in one place (SKILL.md or a reference, not both).
|
|
101
|
+
- **assets/**: templates, boilerplate, or data used in final output — typically not loaded into context.
|
|
102
|
+
|
|
103
|
+
### 8) Validate and test
|
|
104
|
+
|
|
105
|
+
- Run the validator script:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
scripts/validate_skill.py /path/to/my-skill
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- Load only the skill to spot warnings:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pi --no-skills --skill /path/to/my-skill
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- Invoke it explicitly:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
/skill:my-skill
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- After edits, use `/reload`.
|
|
124
|
+
|
|
125
|
+
### 9) Publish (optional)
|
|
126
|
+
|
|
127
|
+
To share beyond a single machine, publish as a Pi package (package.json-based, not a .skill archive).
|
|
128
|
+
|
|
129
|
+
- Add `package.json` with a `pi` manifest (or rely on the conventional `skills/` directory).
|
|
130
|
+
- Add `"keywords": ["pi-package"]` for discoverability.
|
|
131
|
+
- Publish to npm or host in git; install with `pi install <source>` and enable via `pi config` if needed.
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"name": "my-pi-skills",
|
|
136
|
+
"keywords": ["pi-package"],
|
|
137
|
+
"pi": { "skills": ["./skills"] }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tmustier/pi-skill-creator",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Skill-creation guidelines for Pi (Agent Skills format).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Thomas Mustier",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/tmustier/pi-extensions.git",
|
|
13
|
+
"directory": "pi-skill-creator"
|
|
14
|
+
},
|
|
15
|
+
"bugs": "https://github.com/tmustier/pi-extensions/issues",
|
|
16
|
+
"homepage": "https://github.com/tmustier/pi-extensions/tree/main/pi-skill-creator",
|
|
17
|
+
"pi": {
|
|
18
|
+
"skills": [
|
|
19
|
+
"SKILL.md"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Validate a Pi skill directory for Agent Skills + README requirements.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
scripts/validate_skill.py <skill_directory>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
MAX_NAME_LENGTH = 64
|
|
18
|
+
MAX_DESCRIPTION_LENGTH = 1024
|
|
19
|
+
|
|
20
|
+
ALLOWED_FRONTMATTER_FIELDS = {
|
|
21
|
+
"name",
|
|
22
|
+
"description",
|
|
23
|
+
"license",
|
|
24
|
+
"compatibility",
|
|
25
|
+
"metadata",
|
|
26
|
+
"allowed-tools",
|
|
27
|
+
"disable-model-invocation",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
NAME_PATTERN = re.compile(r"^[a-z0-9-]+$")
|
|
31
|
+
FRONTMATTER_PATTERN = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
|
|
32
|
+
INSTALLATION_HEADING = re.compile(r"^#{1,6}\s+installation\b", re.IGNORECASE)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def read_frontmatter(skill_md: Path) -> tuple[dict | None, str | None]:
|
|
36
|
+
content = skill_md.read_text()
|
|
37
|
+
if not content.startswith("---"):
|
|
38
|
+
return None, "No YAML frontmatter found"
|
|
39
|
+
|
|
40
|
+
match = FRONTMATTER_PATTERN.match(content)
|
|
41
|
+
if not match:
|
|
42
|
+
return None, "Invalid frontmatter format"
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
frontmatter = yaml.safe_load(match.group(1))
|
|
46
|
+
except yaml.YAMLError as exc:
|
|
47
|
+
return None, f"Invalid YAML in frontmatter: {exc}"
|
|
48
|
+
|
|
49
|
+
if not isinstance(frontmatter, dict):
|
|
50
|
+
return None, "Frontmatter must be a YAML mapping"
|
|
51
|
+
|
|
52
|
+
return frontmatter, None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_readme(readme_path: Path) -> tuple[list[str], list[str]]:
|
|
56
|
+
errors: list[str] = []
|
|
57
|
+
warnings: list[str] = []
|
|
58
|
+
|
|
59
|
+
if not readme_path.exists():
|
|
60
|
+
return ["README.md not found"], warnings
|
|
61
|
+
|
|
62
|
+
content = readme_path.read_text().strip()
|
|
63
|
+
if not content:
|
|
64
|
+
return ["README.md is empty"], warnings
|
|
65
|
+
|
|
66
|
+
lines = readme_path.read_text().splitlines()
|
|
67
|
+
installation_index = None
|
|
68
|
+
for idx, line in enumerate(lines):
|
|
69
|
+
if INSTALLATION_HEADING.match(line.strip()):
|
|
70
|
+
installation_index = idx
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if installation_index is None:
|
|
74
|
+
errors.append("README.md is missing an Installation section")
|
|
75
|
+
else:
|
|
76
|
+
summary_lines = [
|
|
77
|
+
line.strip()
|
|
78
|
+
for line in lines[:installation_index]
|
|
79
|
+
if line.strip() and not line.strip().startswith("#")
|
|
80
|
+
]
|
|
81
|
+
if not summary_lines:
|
|
82
|
+
warnings.append("README.md has no summary text before Installation")
|
|
83
|
+
|
|
84
|
+
return errors, warnings
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def validate_frontmatter(frontmatter: dict, skill_dir: Path) -> tuple[list[str], list[str]]:
|
|
88
|
+
errors: list[str] = []
|
|
89
|
+
warnings: list[str] = []
|
|
90
|
+
|
|
91
|
+
keys = set(frontmatter.keys())
|
|
92
|
+
unknown = sorted(keys - ALLOWED_FRONTMATTER_FIELDS)
|
|
93
|
+
if unknown:
|
|
94
|
+
warnings.append(f"Unknown frontmatter field(s): {', '.join(unknown)}")
|
|
95
|
+
|
|
96
|
+
name = frontmatter.get("name")
|
|
97
|
+
if not isinstance(name, str) or not name.strip():
|
|
98
|
+
errors.append("Missing or invalid 'name' in frontmatter")
|
|
99
|
+
else:
|
|
100
|
+
normalized = name.strip()
|
|
101
|
+
parent_dir = skill_dir.name
|
|
102
|
+
if normalized != parent_dir:
|
|
103
|
+
errors.append(
|
|
104
|
+
f"Frontmatter name '{normalized}' does not match directory '{parent_dir}'"
|
|
105
|
+
)
|
|
106
|
+
if len(normalized) > MAX_NAME_LENGTH:
|
|
107
|
+
errors.append(
|
|
108
|
+
f"Name is too long ({len(normalized)}). Max {MAX_NAME_LENGTH}."
|
|
109
|
+
)
|
|
110
|
+
if not NAME_PATTERN.match(normalized):
|
|
111
|
+
errors.append(
|
|
112
|
+
"Name must be lowercase letters, digits, and hyphens only"
|
|
113
|
+
)
|
|
114
|
+
if normalized.startswith("-") or normalized.endswith("-"):
|
|
115
|
+
errors.append("Name must not start or end with a hyphen")
|
|
116
|
+
if "--" in normalized:
|
|
117
|
+
errors.append("Name must not contain consecutive hyphens")
|
|
118
|
+
|
|
119
|
+
description = frontmatter.get("description")
|
|
120
|
+
if not isinstance(description, str) or not description.strip():
|
|
121
|
+
errors.append("Missing or invalid 'description' in frontmatter")
|
|
122
|
+
else:
|
|
123
|
+
desc = description.strip()
|
|
124
|
+
if len(desc) > MAX_DESCRIPTION_LENGTH:
|
|
125
|
+
errors.append(
|
|
126
|
+
f"Description is too long ({len(desc)}). Max {MAX_DESCRIPTION_LENGTH}."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
allowed_tools = frontmatter.get("allowed-tools")
|
|
130
|
+
if allowed_tools is not None and not isinstance(allowed_tools, str):
|
|
131
|
+
warnings.append("'allowed-tools' should be a space-delimited string")
|
|
132
|
+
|
|
133
|
+
compatibility = frontmatter.get("compatibility")
|
|
134
|
+
if compatibility is not None and not isinstance(compatibility, str):
|
|
135
|
+
warnings.append("'compatibility' should be a string")
|
|
136
|
+
|
|
137
|
+
license_value = frontmatter.get("license")
|
|
138
|
+
if license_value is not None and not isinstance(license_value, str):
|
|
139
|
+
warnings.append("'license' should be a string")
|
|
140
|
+
|
|
141
|
+
metadata = frontmatter.get("metadata")
|
|
142
|
+
if metadata is not None and not isinstance(metadata, dict):
|
|
143
|
+
warnings.append("'metadata' should be a mapping")
|
|
144
|
+
|
|
145
|
+
disable_invocation = frontmatter.get("disable-model-invocation")
|
|
146
|
+
if disable_invocation is not None and not isinstance(disable_invocation, bool):
|
|
147
|
+
warnings.append("'disable-model-invocation' should be a boolean")
|
|
148
|
+
|
|
149
|
+
return errors, warnings
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def validate_skill(skill_path: Path) -> tuple[list[str], list[str]]:
|
|
153
|
+
errors: list[str] = []
|
|
154
|
+
warnings: list[str] = []
|
|
155
|
+
|
|
156
|
+
if not skill_path.exists():
|
|
157
|
+
return [f"Skill directory not found: {skill_path}"], warnings
|
|
158
|
+
|
|
159
|
+
if not skill_path.is_dir():
|
|
160
|
+
return [f"Skill path is not a directory: {skill_path}"], warnings
|
|
161
|
+
|
|
162
|
+
skill_md = skill_path / "SKILL.md"
|
|
163
|
+
if not skill_md.exists():
|
|
164
|
+
errors.append("SKILL.md not found")
|
|
165
|
+
return errors, warnings
|
|
166
|
+
|
|
167
|
+
frontmatter, frontmatter_error = read_frontmatter(skill_md)
|
|
168
|
+
if frontmatter_error:
|
|
169
|
+
errors.append(frontmatter_error)
|
|
170
|
+
return errors, warnings
|
|
171
|
+
|
|
172
|
+
fm_errors, fm_warnings = validate_frontmatter(frontmatter, skill_path)
|
|
173
|
+
errors.extend(fm_errors)
|
|
174
|
+
warnings.extend(fm_warnings)
|
|
175
|
+
|
|
176
|
+
readme_errors, readme_warnings = validate_readme(skill_path / "README.md")
|
|
177
|
+
errors.extend(readme_errors)
|
|
178
|
+
warnings.extend(readme_warnings)
|
|
179
|
+
|
|
180
|
+
return errors, warnings
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main() -> None:
|
|
184
|
+
if len(sys.argv) != 2:
|
|
185
|
+
print("Usage: scripts/validate_skill.py <skill_directory>")
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
skill_path = Path(sys.argv[1]).resolve()
|
|
189
|
+
errors, warnings = validate_skill(skill_path)
|
|
190
|
+
|
|
191
|
+
if warnings:
|
|
192
|
+
print("Warnings:")
|
|
193
|
+
for warning in warnings:
|
|
194
|
+
print(f" - {warning}")
|
|
195
|
+
print()
|
|
196
|
+
|
|
197
|
+
if errors:
|
|
198
|
+
print("Errors:")
|
|
199
|
+
for error in errors:
|
|
200
|
+
print(f" - {error}")
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
print("Skill is valid!")
|
|
204
|
+
sys.exit(0)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if __name__ == "__main__":
|
|
208
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-ralph-wiggum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Long-running agent loops for iterative development in Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"skills": [
|
|
22
22
|
"SKILL.md"
|
|
23
|
-
]
|
|
23
|
+
],
|
|
24
|
+
"image": "https://github.com/user-attachments/assets/68cdab11-76c6-4aed-9ea1-558cbb267ea6"
|
|
24
25
|
}
|
|
25
26
|
}
|
package/raw-paste/CHANGELOG.md
CHANGED
package/raw-paste/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-raw-paste",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "One-shot raw paste support for Pi (/paste).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"pi": {
|
|
18
18
|
"extensions": [
|
|
19
19
|
"index.ts"
|
|
20
|
-
]
|
|
20
|
+
],
|
|
21
|
+
"image": "https://github.com/user-attachments/assets/292c059a-8b06-40c2-abdd-795c0699336a"
|
|
21
22
|
}
|
|
22
23
|
}
|
package/tab-status/CHANGELOG.md
CHANGED
package/tab-status/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-tab-status",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Terminal tab status indicators for Pi sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"pi": {
|
|
18
18
|
"extensions": [
|
|
19
19
|
"tab-status.ts"
|
|
20
|
-
]
|
|
20
|
+
],
|
|
21
|
+
"image": "https://raw.githubusercontent.com/tmustier/pi-extensions/main/tab-status/assets/tab-status.png"
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmustier/pi-usage-extension",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Usage statistics dashboard for Pi sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Thomas Mustier",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"pi": {
|
|
18
18
|
"extensions": [
|
|
19
19
|
"index.ts"
|
|
20
|
-
]
|
|
20
|
+
],
|
|
21
|
+
"image": "https://raw.githubusercontent.com/tmustier/pi-extensions/main/usage-extension/screenshot.png"
|
|
21
22
|
}
|
|
22
23
|
}
|