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 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 all loaded 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.
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.8",
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.75.4",
49
- "@earendil-works/pi-tui": "^0.75.4",
50
- "@types/node": "^25.6.2",
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
- disableModelInvocation: !isSkillActive(normalizeSkillName(skill.name)),
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(listLoadedSkills(pi.getCommands()).map((skill) => skill.name));
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
- chatContainer?: BoxLike;
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.chatContainer?.children.length ?? 0;
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.chatContainer) return;
213
+ if (rawSkillNames.length === 0 || !cwd || !this.loadedResourcesContainer) return;
214
214
 
215
- const children = this.chatContainer.children;
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
- child.getCollapsedText = () => buildColorizedSkillList(rawSkillNames, store.lastHiddenSkills, store.theme);
223
- child.setText(child.getCollapsedText());
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?/);