pi-skillful 0.3.8 → 0.3.11
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/CHANGELOG.md +17 -0
- package/README.md +4 -2
- package/package.json +4 -4
- package/src/extensions/session-skill-toggles.ts +9 -6
- package/src/extensions/skill-visibility.ts +10 -9
- package/src/skills.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.11 - 2026-07-01
|
|
4
|
+
|
|
5
|
+
## 0.3.9 - 2026-06-14
|
|
6
|
+
|
|
3
7
|
All notable changes to this project will be documented in this file.
|
|
4
8
|
|
|
5
9
|
This project follows the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning for releases.
|
|
6
10
|
|
|
7
11
|
## [Unreleased]
|
|
8
12
|
|
|
13
|
+
## [0.3.10] - 2026-07-01
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Color hidden skills in the startup `[Skills]` list on `@earendil-works/pi-coding-agent` 0.80+. The 0.80 release moved the loaded-resources container out of `chatContainer`, so the existing prototype patch walked the wrong container and the red/dim colorization no longer applied. The patch now also walks `loadedResourcesContainer`, where 0.80 actually renders the `[Skills]` section.
|
|
18
|
+
- Bumped dev dependency range to `^0.80.0` to match the new floor.
|
|
19
|
+
|
|
20
|
+
## [0.3.9] - 2026-06-14
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Skipped Pi package-bundled skills when applying hidden-skill and toggle-slot configuration so `skillful` only affects global and project skills.
|
|
25
|
+
|
|
9
26
|
## [0.3.8] - 2026-06-06
|
|
10
27
|
|
|
11
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ Hidden skills:
|
|
|
37
37
|
- remain loaded by Pi;
|
|
38
38
|
- remain available for explicit invocation with `/skill:name`, including inline invocation.
|
|
39
39
|
|
|
40
|
+
Skills bundled in Pi packages are never affected by `skillful`; only global and project skills can be hidden or toggled.
|
|
41
|
+
|
|
40
42
|
Configuration is stored under the `skillful` key in Pi settings:
|
|
41
43
|
|
|
42
44
|
```json
|
|
@@ -60,7 +62,7 @@ Open the menu with:
|
|
|
60
62
|
/skillful
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
The menu lists
|
|
65
|
+
The menu lists configurable skills alphabetically. Toggle a skill off or on in the active scope. Use the Global/Project tabs to choose which settings file to edit. In the Project tab, inherited on/off values are shown normally; project overrides are highlighted. Press `1` through `9` on a selected skill to assign or clear that scope's session toggle slot. Visibility and toggle slots are independent.
|
|
64
66
|
|
|
65
67
|
Pi's startup `[Skills]` list also highlights hidden skills in the error color (red in the default dark theme).
|
|
66
68
|
|
|
@@ -88,7 +90,7 @@ Configured slots appear on the prompt editor's top border as `N skill-name`. Pro
|
|
|
88
90
|
|
|
89
91
|
`toggleModifier` defaults to `"alt"`. Supported values are `"alt"`, `"ctrl"`, `"ctrl+shift"`, `"alt+shift"`, `"ctrl+alt"`, and `"ctrl+alt+shift"`. Change it if your terminal reserves `alt+number` shortcuts.
|
|
90
92
|
|
|
91
|
-
On app startup, non-hidden skills are active and hidden skills are inactive. Within a running Pi process, `/new` preserves the current toggle state for the new session. Resuming, forking, cloning, reloading, or restarting Pi resets toggle state from settings. Inline `/skill:name` invocation remains explicit and works even when that skill is inactive.
|
|
93
|
+
On app startup, non-hidden skills are active and hidden skills are inactive. Within a running Pi process, `/new` preserves the current toggle state for the new session. Resuming, forking, cloning, reloading, or restarting Pi resets toggle state from settings. Inline `/skill:name` invocation remains explicit and works even when that skill is inactive. Skills bundled in Pi packages are never modified by these toggles.
|
|
92
94
|
|
|
93
95
|
## Installation
|
|
94
96
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-skillful",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "Pi package with skill invocation and visibility improvements.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"@earendil-works/pi-tui": "*"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
49
|
-
"@earendil-works/pi-tui": "^0.
|
|
50
|
-
"@types/node": "^25.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
49
|
+
"@earendil-works/pi-tui": "^0.80.0",
|
|
50
|
+
"@types/node": "^25.9.3",
|
|
51
51
|
"typescript": "^6.0.3"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type SkillToggleSlot,
|
|
12
12
|
} from "../config.js";
|
|
13
13
|
import { replaceSkillsSection } from "../skill-prompt.js";
|
|
14
|
-
import { listLoadedSkills } from "../skills.js";
|
|
14
|
+
import { isTopLevelSkill, listLoadedSkills } from "../skills.js";
|
|
15
15
|
|
|
16
16
|
const WIDGET_KEY = "pi-skillful-session-toggles";
|
|
17
17
|
const STORE_KEY = Symbol.for("pi-skillful.sessionSkillTogglesStore");
|
|
@@ -97,10 +97,9 @@ export default function sessionSkillToggles(pi: ExtensionAPI) {
|
|
|
97
97
|
pi.on("before_agent_start", (event) => {
|
|
98
98
|
if (state.slots.length === 0 || !event.systemPromptOptions.skills?.length) return;
|
|
99
99
|
|
|
100
|
-
const updatedSkills: Skill[] = event.systemPromptOptions.skills.map((skill) =>
|
|
101
|
-
...skill,
|
|
102
|
-
|
|
103
|
-
}));
|
|
100
|
+
const updatedSkills: Skill[] = event.systemPromptOptions.skills.map((skill) =>
|
|
101
|
+
isTopLevelSkill(skill) ? { ...skill, disableModelInvocation: !isSkillActive(normalizeSkillName(skill.name)) } : skill,
|
|
102
|
+
);
|
|
104
103
|
|
|
105
104
|
const systemPrompt = replaceSkillsSection(event.systemPrompt, updatedSkills);
|
|
106
105
|
if (!systemPrompt) return;
|
|
@@ -164,7 +163,11 @@ function configuredToggleSlots(
|
|
|
164
163
|
pi: ExtensionAPI,
|
|
165
164
|
toggleSlots: Partial<Record<SkillToggleSlot, string>>,
|
|
166
165
|
): ToggleSlotState[] {
|
|
167
|
-
const loadedSkillNames = new Set(
|
|
166
|
+
const loadedSkillNames = new Set(
|
|
167
|
+
listLoadedSkills(pi.getCommands())
|
|
168
|
+
.filter(isTopLevelSkill)
|
|
169
|
+
.map((skill) => skill.name),
|
|
170
|
+
);
|
|
168
171
|
return SKILL_TOGGLE_SLOTS.flatMap((slot): ToggleSlotState[] => {
|
|
169
172
|
const skillName = toggleSlots[slot];
|
|
170
173
|
if (!skillName || !loadedSkillNames.has(skillName)) return [];
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
writeToggleSlots,
|
|
21
21
|
} from "../config.js";
|
|
22
22
|
import { replaceSkillsSection } from "../skill-prompt.js";
|
|
23
|
-
import { listLoadedSkills, type LoadedSkillInfo } from "../skills.js";
|
|
23
|
+
import { isTopLevelSkill, listLoadedSkills, type LoadedSkillInfo } from "../skills.js";
|
|
24
24
|
import { hasActiveSessionSkillToggles, refreshSessionSkillToggles } from "./session-skill-toggles.js";
|
|
25
25
|
const SCOPES: SkillfulScope[] = ["global", "project"];
|
|
26
26
|
const STORE_KEY = Symbol.for("pi-skillful.skillVisibilityStore");
|
|
@@ -42,7 +42,7 @@ interface BoxLike {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface InteractiveModeLike {
|
|
45
|
-
|
|
45
|
+
loadedResourcesContainer?: BoxLike;
|
|
46
46
|
showLoadedResources?: (options?: unknown) => void;
|
|
47
47
|
session?: { resourceLoader?: { getSkills: () => { skills: Skill[]; diagnostics: unknown[] } } };
|
|
48
48
|
sessionManager?: { getCwd?: () => string };
|
|
@@ -95,7 +95,7 @@ export default function skillVisibility(pi: ExtensionAPI) {
|
|
|
95
95
|
if (hidden.size === 0 || !event.systemPromptOptions.skills?.length) return;
|
|
96
96
|
|
|
97
97
|
const filteredSkills: Skill[] = event.systemPromptOptions.skills.map((skill) =>
|
|
98
|
-
hidden.has(skill.name) ? { ...skill, disableModelInvocation: true } : skill,
|
|
98
|
+
isTopLevelSkill(skill) && hidden.has(skill.name) ? { ...skill, disableModelInvocation: true } : skill,
|
|
99
99
|
);
|
|
100
100
|
const systemPrompt = replaceSkillsSection(event.systemPrompt, filteredSkills);
|
|
101
101
|
if (!systemPrompt) return;
|
|
@@ -202,7 +202,7 @@ function installStartupSkillListPatch(): void {
|
|
|
202
202
|
return result;
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
-
const childrenBefore = this.
|
|
205
|
+
const childrenBefore = this.loadedResourcesContainer?.children.length ?? 0;
|
|
206
206
|
|
|
207
207
|
try {
|
|
208
208
|
original.call(this, options);
|
|
@@ -210,17 +210,18 @@ function installStartupSkillListPatch(): void {
|
|
|
210
210
|
loader.getSkills = originalGetSkills;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
if (rawSkillNames.length === 0 || !cwd || !this.
|
|
213
|
+
if (rawSkillNames.length === 0 || !cwd || !this.loadedResourcesContainer) return;
|
|
214
214
|
|
|
215
|
-
const children = this.
|
|
215
|
+
const children = this.loadedResourcesContainer.children;
|
|
216
216
|
for (let i = childrenBefore; i < children.length; i++) {
|
|
217
217
|
const child = children[i] as ExpandableTextLike | undefined;
|
|
218
218
|
if (!child || typeof child.getCollapsedText !== "function") continue;
|
|
219
219
|
const collapsed = child.getCollapsedText();
|
|
220
220
|
if (!collapsed.includes("[Skills]")) continue;
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
child.
|
|
222
|
+
const colorized = buildColorizedSkillList(rawSkillNames, store.lastHiddenSkills, store.theme);
|
|
223
|
+
child.getCollapsedText = () => colorized;
|
|
224
|
+
child.setText(colorized);
|
|
224
225
|
break;
|
|
225
226
|
}
|
|
226
227
|
};
|
|
@@ -239,7 +240,7 @@ function buildColorizedSkillList(names: string[], hidden: Set<string>, theme: Th
|
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
function getSkillItems(pi: ExtensionAPI): SkillListItem[] {
|
|
242
|
-
return listLoadedSkills(pi.getCommands());
|
|
243
|
+
return listLoadedSkills(pi.getCommands()).filter(isTopLevelSkill);
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
class SkillfulVisibilityMenu implements Component {
|
package/src/skills.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import type { SourceInfo } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import { normalizeSkillName } from "./config.js";
|
|
4
5
|
|
|
5
6
|
export interface SkillCommandInfo {
|
|
@@ -11,12 +12,14 @@ export interface SkillCommandInfo {
|
|
|
11
12
|
export interface LoadedSkillInfo {
|
|
12
13
|
name: string;
|
|
13
14
|
description: string;
|
|
15
|
+
sourceInfo: SourceInfo;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
interface CommandLike {
|
|
17
19
|
name: string;
|
|
18
20
|
source: string;
|
|
19
21
|
description?: string;
|
|
22
|
+
sourceInfo: SourceInfo;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function listLoadedSkills(commands: Iterable<CommandLike>): LoadedSkillInfo[] {
|
|
@@ -25,11 +28,15 @@ export function listLoadedSkills(commands: Iterable<CommandLike>): LoadedSkillIn
|
|
|
25
28
|
if (command.source !== "skill") continue;
|
|
26
29
|
const name = normalizeSkillName(command.name);
|
|
27
30
|
if (!name) continue;
|
|
28
|
-
byName.set(name, { name, description: command.description ?? "" });
|
|
31
|
+
byName.set(name, { name, description: command.description ?? "", sourceInfo: command.sourceInfo });
|
|
29
32
|
}
|
|
30
33
|
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
export function isTopLevelSkill(skill: { sourceInfo: { origin: SourceInfo["origin"] } }): boolean {
|
|
37
|
+
return skill.sourceInfo.origin === "top-level";
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
export function stripFrontmatter(markdown: string): string {
|
|
34
41
|
const normalized = markdown.replace(/^\uFEFF/, "");
|
|
35
42
|
const match = normalized.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
|