@kmiyh/pi-skills-menu 1.1.0 → 1.2.1
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 +102 -80
- package/package.json +1 -1
- package/src/create-skill.ts +75 -21
- package/src/images/skill-create-description.jpg +0 -0
- package/src/images/skill-create-generate.jpg +0 -0
- package/src/images/skill-create-name.jpg +0 -0
- package/src/images/skill-create-visibility.jpg +0 -0
- package/src/images/skill-delete.jpg +0 -0
- package/src/images/skill-disable.jpg +0 -0
- package/src/images/skill-edit.jpg +0 -0
- package/src/images/skill-preview.jpg +0 -0
- package/src/images/skill-rename.jpg +0 -0
- package/src/images/skills-menu.jpg +0 -0
- package/src/index.ts +31 -71
- package/src/skill-registry.ts +0 -4
- package/src/ui/skill-preview.ts +3 -3
- package/src/ui/skills-manager.ts +1357 -0
- package/src/ui/skills-selector.ts +4 -4
- package/src/images/skill-create-generating.jpg +0 -0
package/README.md
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
# @kmiyh/pi-skills-menu
|
|
2
2
|
|
|
3
|
-
`@kmiyh/pi-skills-menu` is a Pi
|
|
3
|
+
`@kmiyh/pi-skills-menu` is a Pi extension that moves skill browsing and selection into a dedicated `/skills` menu.
|
|
4
|
+
|
|
5
|
+
Instead of filling Pi's main menu with many `/skill:<name>` entries, it gives you one focused place to search, preview, insert, create, edit, rename, delete, and enable or disable skills.
|
|
6
|
+
|
|
7
|
+
## Why use it
|
|
8
|
+
|
|
9
|
+
- keeps the main menu cleaner when many skills are installed
|
|
10
|
+
- puts project, global, and package-provided skills in one searchable list
|
|
11
|
+
- makes skill selection faster in interactive sessions
|
|
12
|
+
- lets you manage your own skills without leaving the TUI
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
4
15
|
|
|
5
16
|
```bash
|
|
6
|
-
/skills
|
|
17
|
+
pi install npm:@kmiyh/pi-skills-menu
|
|
7
18
|
```
|
|
8
19
|
|
|
9
|
-
|
|
20
|
+
## What changes after installation
|
|
10
21
|
|
|
11
|
-
When the extension is installed, it automatically writes
|
|
22
|
+
When the extension is installed, it automatically writes this to `settings.json`:
|
|
12
23
|
|
|
13
24
|
```json
|
|
14
25
|
{
|
|
@@ -16,135 +27,148 @@ When the extension is installed, it automatically writes the following to `setti
|
|
|
16
27
|
}
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Installation
|
|
30
|
+
That disables the default `/skill:<name>` command registration in the main menu and replaces it with a single `/skills` entry.
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
When you insert a skill from the menu, the extension adds a marker like this to the editor:
|
|
24
33
|
|
|
25
|
-
```
|
|
26
|
-
|
|
34
|
+
```text
|
|
35
|
+
[skill] my-skill
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
Before the message is sent, Pi expands that marker into the actual skill content.
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
## What the `/skills` menu shows
|
|
41
|
+
|
|
42
|
+
The menu includes all available installed skills:
|
|
32
43
|
|
|
33
|
-
- global skills
|
|
34
44
|
- project skills
|
|
35
|
-
- skills
|
|
45
|
+
- global skills
|
|
46
|
+
- skills provided by installed packages/libraries
|
|
36
47
|
|
|
37
|
-
The list is
|
|
48
|
+
The list is grouped into:
|
|
38
49
|
|
|
39
|
-
- **Your Skills** —
|
|
40
|
-
- **Library Skills** — skills coming from
|
|
50
|
+
- **Your Skills** — your own project and global skills
|
|
51
|
+
- **Library Skills** — skills coming from installed packages
|
|
41
52
|
|
|
42
53
|

|
|
43
54
|
|
|
44
|
-
## What
|
|
55
|
+
## What you can do in `/skills`
|
|
45
56
|
|
|
46
|
-
|
|
57
|
+
| Action | Shortcut | Notes |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Filter skills by name | type | Search works directly in the list |
|
|
60
|
+
| Open preview | `Tab` | Shows full metadata and content |
|
|
61
|
+
| Insert selected skill | `Enter` | Works only for enabled skills |
|
|
62
|
+
| Enable or disable a skill | `Ctrl+X` | Works for your skills and library skills |
|
|
63
|
+
| Create a new skill | `Enter` on **Create new skill** | Opens the creation flow |
|
|
64
|
+
| Delete your own skill | `Backspace` | Available only for project/global skills you own |
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
### Preview a skill
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
Press `Tab` on a selected skill to open preview mode.
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
The preview shows:
|
|
53
71
|
|
|
54
|
-
-
|
|
72
|
+
- the skill name and scope
|
|
73
|
+
- its source path or package
|
|
74
|
+
- whether it is enabled or disabled
|
|
75
|
+
- the full frontmatter
|
|
76
|
+
- the full skill content with scrolling support
|
|
55
77
|
|
|
56
78
|

|
|
57
79
|
|
|
58
80
|
### Insert a skill into the editor
|
|
59
81
|
|
|
60
|
-
Press
|
|
82
|
+
Press `Enter` on an enabled skill to insert it into the editor.
|
|
61
83
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
After that, the skill is inserted into the editor so it will be used by Pi when the message is sent.
|
|
84
|
+
This is useful when you want to explicitly attach one or more skills to the message you are writing, without manually copying skill content.
|
|
65
85
|
|
|
66
86
|
### Enable or disable a skill
|
|
67
87
|
|
|
68
|
-
Press
|
|
88
|
+
Press `Ctrl+X` to toggle the selected skill.
|
|
69
89
|
|
|
70
|
-
|
|
90
|
+
Disabled skills stay visible in the list and are marked with `[disabled]`, so you can still find them and re-enable them later.
|
|
71
91
|
|
|
72
|
-
This also works for skills that come from installed
|
|
92
|
+
This also works for skills that come from installed packages.
|
|
73
93
|
|
|
74
|
-
|
|
94
|
+

|
|
75
95
|
|
|
76
|
-
|
|
96
|
+
## Creating a new skill
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
The first row in the menu is **Create new skill**.
|
|
79
99
|
|
|
80
|
-
|
|
100
|
+
Creation is split into three short steps:
|
|
81
101
|
|
|
82
|
-
|
|
102
|
+
1. **Name** — the skill folder/name slug
|
|
103
|
+
2. **Description** — one clear sentence describing what the skill does and when Pi should use it
|
|
104
|
+
3. **Visibility** — whether the skill should be saved globally or only for the current project
|
|
83
105
|
|
|
84
|
-
1.
|
|
106
|
+
### 1. Name
|
|
85
107
|
|
|
86
108
|

|
|
87
109
|
|
|
88
|
-
2.
|
|
110
|
+
### 2. Description
|
|
89
111
|
|
|
90
112
|

|
|
91
113
|
|
|
92
|
-
3.
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
### 3. Visibility
|
|
115
|
+
|
|
116
|
+

|
|
95
117
|
|
|
96
|
-
After that, the extension generates
|
|
118
|
+
After that, the extension generates `SKILL.md` for you.
|
|
97
119
|
|
|
98
120
|
Generation uses:
|
|
99
121
|
|
|
100
|
-
- the
|
|
101
|
-
- the
|
|
122
|
+
- the model currently selected in the TUI
|
|
123
|
+
- the current thinking level selected in the TUI
|
|
102
124
|
|
|
103
|
-
So
|
|
125
|
+
So the draft follows the model configuration already active in your Pi session.
|
|
104
126
|
|
|
105
|
-

|
|
106
128
|
|
|
107
|
-
##
|
|
129
|
+
## Editing, renaming, and deleting your own skills
|
|
108
130
|
|
|
109
|
-
|
|
131
|
+
Your own project and global skills can be managed directly from preview mode.
|
|
110
132
|
|
|
111
|
-
|
|
112
|
-
- scrolling support for reading the content
|
|
133
|
+
Library skills can be previewed, inserted, and enabled/disabled, but they cannot be edited, renamed, or deleted from this menu.
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
### Edit skill content
|
|
115
136
|
|
|
116
|
-
|
|
137
|
+
In preview mode, press `e` to edit the skill content and metadata body.
|
|
138
|
+
|
|
139
|
+
Use `Ctrl+S` to save.
|
|
117
140
|
|
|
118
141
|

|
|
119
142
|
|
|
120
|
-
|
|
143
|
+
### Rename a skill
|
|
121
144
|
|
|
122
|
-
|
|
145
|
+
In preview mode, press `r` to rename the skill.
|
|
123
146
|
|
|
124
|
-
|
|
147
|
+
This updates both the directory name and the `name` field in frontmatter.
|
|
125
148
|
|
|
126
|
-
|
|
149
|
+

|
|
127
150
|
|
|
128
|
-
###
|
|
151
|
+
### Delete a skill
|
|
129
152
|
|
|
130
|
-
|
|
153
|
+
In browse mode or preview mode, press `Backspace` to delete your own skill.
|
|
131
154
|
|
|
132
|
-
|
|
133
|
-
.pi/skills/<skill-name>/SKILL.md
|
|
134
|
-
```
|
|
155
|
+
A confirmation dialog is shown before removal.
|
|
135
156
|
|
|
136
|
-
|
|
157
|
+

|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
159
|
+
## Where new skills are saved
|
|
160
|
+
|
|
161
|
+
New skills are stored in Pi's standard skill directories depending on the selected visibility.
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
| Visibility | Path |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| Project | `.pi/skills/<skill-name>/SKILL.md` |
|
|
166
|
+
| Global | `~/.pi/agent/skills/<skill-name>/SKILL.md` |
|
|
143
167
|
|
|
144
|
-
|
|
168
|
+
Example project skill:
|
|
145
169
|
|
|
146
170
|
```text
|
|
147
|
-
|
|
171
|
+
.pi/skills/react-review/SKILL.md
|
|
148
172
|
```
|
|
149
173
|
|
|
150
174
|
## Local development
|
|
@@ -167,31 +191,29 @@ Run the extension directly from a local checkout:
|
|
|
167
191
|
pi -e ./src/index.ts
|
|
168
192
|
```
|
|
169
193
|
|
|
170
|
-
##
|
|
194
|
+
## Contributing
|
|
171
195
|
|
|
172
|
-
|
|
196
|
+
Contributions are welcome, especially around:
|
|
173
197
|
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- suggest improvements to project structure and Pi package support
|
|
180
|
-
- etc
|
|
198
|
+
- menu UX and navigation
|
|
199
|
+
- skill search and filtering
|
|
200
|
+
- preview and editing flows
|
|
201
|
+
- skill generation quality
|
|
202
|
+
- Pi package compatibility
|
|
181
203
|
|
|
182
|
-
|
|
204
|
+
Typical workflow:
|
|
183
205
|
|
|
184
206
|
1. fork the repository
|
|
185
|
-
2. create a
|
|
207
|
+
2. create a branch
|
|
186
208
|
3. make your changes
|
|
187
|
-
4. verify
|
|
209
|
+
4. verify with:
|
|
188
210
|
|
|
189
211
|
```bash
|
|
190
212
|
npm install
|
|
191
213
|
npm run typecheck
|
|
192
214
|
```
|
|
193
215
|
|
|
194
|
-
5. test locally
|
|
216
|
+
5. test locally:
|
|
195
217
|
|
|
196
218
|
```bash
|
|
197
219
|
pi -e ./src/index.ts
|
package/package.json
CHANGED
package/src/create-skill.ts
CHANGED
|
@@ -546,6 +546,7 @@ export type SkillCreationThinkingLevel = ThinkingLevel | "off";
|
|
|
546
546
|
|
|
547
547
|
export interface SkillGenerationOptions {
|
|
548
548
|
thinkingLevel?: SkillCreationThinkingLevel;
|
|
549
|
+
signal?: AbortSignal;
|
|
549
550
|
}
|
|
550
551
|
|
|
551
552
|
class SingleLineText implements Component {
|
|
@@ -863,11 +864,21 @@ function getGenerationStatusLabel(
|
|
|
863
864
|
: `Generating skill draft using ${modelLabel}...`;
|
|
864
865
|
}
|
|
865
866
|
|
|
867
|
+
function isAbortError(error: unknown): boolean {
|
|
868
|
+
if (!error || typeof error !== "object") return false;
|
|
869
|
+
const name = "name" in error ? String((error as { name?: unknown }).name) : "";
|
|
870
|
+
const message = "message" in error ? String((error as { message?: unknown }).message) : "";
|
|
871
|
+
return name === "AbortError" || message.toLowerCase().includes("aborted");
|
|
872
|
+
}
|
|
873
|
+
|
|
866
874
|
async function generateSkillDraft(
|
|
867
875
|
ctx: ExtensionContext,
|
|
868
876
|
answers: SkillCreationAnswers,
|
|
869
877
|
options?: SkillGenerationOptions,
|
|
870
878
|
): Promise<string> {
|
|
879
|
+
if (options?.signal?.aborted) {
|
|
880
|
+
throw new Error("Generation aborted");
|
|
881
|
+
}
|
|
871
882
|
if (!ctx.model) {
|
|
872
883
|
return buildFallbackSkill(answers);
|
|
873
884
|
}
|
|
@@ -910,9 +921,13 @@ async function generateSkillDraft(
|
|
|
910
921
|
const response = await completeSimple(
|
|
911
922
|
ctx.model,
|
|
912
923
|
{ systemPrompt: GENERATE_SKILL_SYSTEM_PROMPT, messages: [userMessage] },
|
|
913
|
-
{ apiKey: auth.apiKey, headers: auth.headers, ...(reasoning ? { reasoning } : {}) },
|
|
924
|
+
{ apiKey: auth.apiKey, headers: auth.headers, ...(reasoning ? { reasoning } : {}), ...(options?.signal ? { signal: options.signal } : {}) },
|
|
914
925
|
);
|
|
915
926
|
|
|
927
|
+
if (options?.signal?.aborted) {
|
|
928
|
+
throw new Error("Generation aborted");
|
|
929
|
+
}
|
|
930
|
+
|
|
916
931
|
const generated = response.content
|
|
917
932
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
918
933
|
.map((c) => c.text)
|
|
@@ -942,12 +957,45 @@ async function runDraftGeneration(
|
|
|
942
957
|
|
|
943
958
|
generateSkillDraft(ctx, answers, options)
|
|
944
959
|
.then(done)
|
|
945
|
-
.catch(() => done(buildFallbackSkill(answers)));
|
|
960
|
+
.catch((error) => done(isAbortError(error) ? null : buildFallbackSkill(answers)));
|
|
946
961
|
|
|
947
962
|
return loader;
|
|
948
963
|
});
|
|
949
964
|
}
|
|
950
965
|
|
|
966
|
+
async function saveCreatedSkill(
|
|
967
|
+
ctx: ExtensionContext,
|
|
968
|
+
answers: SkillCreationAnswers,
|
|
969
|
+
draft: string,
|
|
970
|
+
): Promise<SkillEntry | null> {
|
|
971
|
+
let parsedSkill: ParsedSkillDraft;
|
|
972
|
+
try {
|
|
973
|
+
parsedSkill = parseSkillDraft(draft, answers.name);
|
|
974
|
+
} catch (error) {
|
|
975
|
+
ctx.ui.notify(error instanceof Error ? error.message : "Invalid generated SKILL.md", "error");
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const targetDir = getTargetDir(ctx, answers.location, answers.name);
|
|
980
|
+
const targetPath = join(targetDir, "SKILL.md");
|
|
981
|
+
await mkdir(targetDir, { recursive: true });
|
|
982
|
+
await writeFile(targetPath, parsedSkill.raw, "utf8");
|
|
983
|
+
|
|
984
|
+
ctx.ui.notify(`Created skill: ${targetPath}`, "info");
|
|
985
|
+
return {
|
|
986
|
+
name: parsedSkill.name,
|
|
987
|
+
description: parsedSkill.description,
|
|
988
|
+
path: targetPath,
|
|
989
|
+
content: parsedSkill.content,
|
|
990
|
+
frontmatter: parsedSkill.frontmatter,
|
|
991
|
+
scope: answers.location === "global" ? "user" : "project",
|
|
992
|
+
origin: "top-level",
|
|
993
|
+
source: "auto",
|
|
994
|
+
baseDir: targetDir,
|
|
995
|
+
enabled: true,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
951
999
|
async function collectAnswers(ctx: ExtensionContext): Promise<SkillCreationAnswers | null> {
|
|
952
1000
|
const answers = await ctx.ui.custom<SkillCreationAnswers | null>((tui, _theme, _kb, done) => {
|
|
953
1001
|
const component = new SkillCreationWizard(ctx.ui.theme, done);
|
|
@@ -992,30 +1040,36 @@ export async function createSkillFromAnswers(
|
|
|
992
1040
|
return null;
|
|
993
1041
|
}
|
|
994
1042
|
|
|
995
|
-
|
|
1043
|
+
return await saveCreatedSkill(ctx, answers, draft);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export async function createSkillFromAnswersWithoutUI(
|
|
1047
|
+
ctx: ExtensionContext,
|
|
1048
|
+
answers: SkillCreationAnswers,
|
|
1049
|
+
options?: SkillGenerationOptions,
|
|
1050
|
+
): Promise<SkillEntry | null> {
|
|
1051
|
+
const targetDir = getTargetDir(ctx, answers.location, answers.name);
|
|
1052
|
+
const targetPath = join(targetDir, "SKILL.md");
|
|
1053
|
+
if (existsSync(targetPath)) {
|
|
1054
|
+
ctx.ui.notify(`Skill already exists: ${targetPath}`, "error");
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
let draft: string;
|
|
996
1059
|
try {
|
|
997
|
-
|
|
1060
|
+
draft = await generateSkillDraft(ctx, answers, options);
|
|
998
1061
|
} catch (error) {
|
|
999
|
-
|
|
1000
|
-
|
|
1062
|
+
if (isAbortError(error) || options?.signal?.aborted) {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
draft = buildFallbackSkill(answers);
|
|
1001
1066
|
}
|
|
1002
1067
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1068
|
+
if (options?.signal?.aborted) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1005
1071
|
|
|
1006
|
-
ctx
|
|
1007
|
-
return {
|
|
1008
|
-
name: parsedSkill.name,
|
|
1009
|
-
description: parsedSkill.description,
|
|
1010
|
-
path: targetPath,
|
|
1011
|
-
content: parsedSkill.content,
|
|
1012
|
-
frontmatter: parsedSkill.frontmatter,
|
|
1013
|
-
scope: answers.location === "global" ? "user" : "project",
|
|
1014
|
-
origin: "top-level",
|
|
1015
|
-
source: "auto",
|
|
1016
|
-
baseDir: targetDir,
|
|
1017
|
-
enabled: true,
|
|
1018
|
-
};
|
|
1072
|
+
return await saveCreatedSkill(ctx, answers, draft);
|
|
1019
1073
|
}
|
|
1020
1074
|
|
|
1021
1075
|
export async function createNewSkill(
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, InputEventResult } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { createSkillFromAnswersWithoutUI } from "./create-skill.js";
|
|
3
3
|
import { deleteSkill } from "./delete-skill.js";
|
|
4
4
|
import { detectExtensionInstallScope } from "./extension-scope.js";
|
|
5
5
|
import { expandSkillMarkers, hasSkillMarker, insertSkillMarker, removeIncompleteSkillMarkerLines } from "./markers.js";
|
|
@@ -7,8 +7,7 @@ import { loadSkillRegistry } from "./skill-registry.js";
|
|
|
7
7
|
import { ensureSkillCommandsHidden } from "./settings-toggle.js";
|
|
8
8
|
import { setSkillEnabled } from "./skill-enabled-toggle.js";
|
|
9
9
|
import type { ExtensionInstallScope, SkillRegistry } from "./types.js";
|
|
10
|
-
import {
|
|
11
|
-
import { showSkillsSelector } from "./ui/skills-selector.js";
|
|
10
|
+
import { showSkillsManager } from "./ui/skills-manager.js";
|
|
12
11
|
|
|
13
12
|
const EMPTY_REGISTRY: SkillRegistry = {
|
|
14
13
|
skills: [],
|
|
@@ -107,81 +106,42 @@ export default function skillsMenuExtension(pi: ExtensionAPI) {
|
|
|
107
106
|
return;
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
selectorIndex = selection.selectedIndex;
|
|
126
|
-
selectorQuery = selection.query;
|
|
127
|
-
|
|
128
|
-
if (selection.type === "create") {
|
|
129
|
-
const createdSkill = await createSkillFromAnswers(ctx, selection.answers, {
|
|
130
|
-
thinkingLevel: pi.getThinkingLevel(),
|
|
131
|
-
});
|
|
132
|
-
if (!createdSkill) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
await refreshRegistry(ctx.cwd);
|
|
136
|
-
const createdSkillIndex = registry.allSkills.findIndex((skill) => skill.path === createdSkill.path);
|
|
137
|
-
selectorIndex = createdSkillIndex >= 0 ? createdSkillIndex + 1 : 0;
|
|
138
|
-
selectorQuery = "";
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (selection.type === "toggle") {
|
|
109
|
+
try {
|
|
110
|
+
await refreshRegistry(ctx.cwd);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("skills-menu: failed to refresh skills registry", error);
|
|
113
|
+
ctx.ui.notify("Failed to load skills list", "error");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const selection = await showSkillsManager(ctx, registry, {
|
|
118
|
+
onCreate: async (answers, signal) => await createSkillFromAnswersWithoutUI(ctx, answers, {
|
|
119
|
+
thinkingLevel: pi.getThinkingLevel(),
|
|
120
|
+
signal,
|
|
121
|
+
}),
|
|
122
|
+
onDelete: async (skill) => {
|
|
143
123
|
try {
|
|
144
|
-
await
|
|
145
|
-
await refreshRegistry(ctx.cwd);
|
|
146
|
-
ctx.ui.notify(
|
|
147
|
-
selection.skill.enabled
|
|
148
|
-
? `Disabled ${selection.skill.name}. Run /reload to fully apply the change.`
|
|
149
|
-
: `Enabled ${selection.skill.name}. Run /reload to fully apply the change.`,
|
|
150
|
-
"info",
|
|
151
|
-
);
|
|
124
|
+
return await deleteSkill(ctx, skill);
|
|
152
125
|
} catch (error) {
|
|
153
|
-
console.error("skills-menu: failed to
|
|
154
|
-
ctx.ui.notify(
|
|
155
|
-
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (selection.type === "preview") {
|
|
160
|
-
await showSkillPreview(ctx, selection.skill);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (selection.type === "delete") {
|
|
165
|
-
const confirmed = await ctx.ui.confirm(
|
|
166
|
-
"Delete skill",
|
|
167
|
-
`Delete ${selection.skill.name}? This removes the skill from disk and cannot be undone.`,
|
|
168
|
-
);
|
|
169
|
-
if (!confirmed) {
|
|
170
|
-
continue;
|
|
126
|
+
console.error("skills-menu: failed to delete skill", error);
|
|
127
|
+
ctx.ui.notify("Failed to delete skill", "error");
|
|
128
|
+
return false;
|
|
171
129
|
}
|
|
130
|
+
},
|
|
131
|
+
onToggle: async (skill, enabled) => {
|
|
172
132
|
try {
|
|
173
|
-
await
|
|
174
|
-
selectorIndex = Math.max(0, selectorIndex - 1);
|
|
133
|
+
await setSkillEnabled(ctx.cwd, skill, enabled);
|
|
175
134
|
} catch (error) {
|
|
176
|
-
console.error("skills-menu: failed to
|
|
177
|
-
|
|
135
|
+
console.error("skills-menu: failed to toggle skill", error);
|
|
136
|
+
throw error instanceof Error ? error : new Error("Failed to update skill visibility");
|
|
178
137
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
138
|
+
},
|
|
139
|
+
onRefresh: async () => await refreshRegistry(ctx.cwd),
|
|
140
|
+
});
|
|
141
|
+
if (!selection) {
|
|
183
142
|
return;
|
|
184
143
|
}
|
|
144
|
+
insertSkillMarker(ctx, selection);
|
|
185
145
|
},
|
|
186
146
|
});
|
|
187
147
|
|
|
@@ -207,7 +167,7 @@ export default function skillsMenuExtension(pi: ExtensionAPI) {
|
|
|
207
167
|
return { action: "continue" };
|
|
208
168
|
}
|
|
209
169
|
|
|
210
|
-
if (!currentCwd || currentCwd !== ctx.cwd || registry.
|
|
170
|
+
if (!currentCwd || currentCwd !== ctx.cwd || registry.allSkills.length === 0) {
|
|
211
171
|
try {
|
|
212
172
|
await refreshRegistry(ctx.cwd);
|
|
213
173
|
} catch (error) {
|
package/src/skill-registry.ts
CHANGED
|
@@ -3,10 +3,6 @@ import { parseSkillFile } from "./skill-parser.js";
|
|
|
3
3
|
import type { SkillEntry, SkillRegistry } from "./types.js";
|
|
4
4
|
|
|
5
5
|
function compareSkills(a: SkillEntry, b: SkillEntry): number {
|
|
6
|
-
if (a.enabled !== b.enabled) {
|
|
7
|
-
return a.enabled ? -1 : 1;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
const scopeRank = (scope: SkillEntry["scope"]) => {
|
|
11
7
|
switch (scope) {
|
|
12
8
|
case "project":
|
package/src/ui/skill-preview.ts
CHANGED
|
@@ -37,7 +37,7 @@ function getSkillLocation(skill: SkillEntry): string {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function getSkillLocationLabel(skill: SkillEntry): string {
|
|
40
|
-
return skill.origin === "package" ? "package" : "
|
|
40
|
+
return skill.origin === "package" ? "package • " : "";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function formatScalar(value: unknown): string {
|
|
@@ -271,7 +271,7 @@ class ScrollableSkillPreview implements Component {
|
|
|
271
271
|
private buildContentLines(innerWidth: number): string[] {
|
|
272
272
|
const content = new Container();
|
|
273
273
|
content.addChild(new Text(this.theme.fg("accent", this.theme.bold(this.skill.name)), 0, 0));
|
|
274
|
-
content.addChild(new Text(this.theme.fg("muted", `${getSkillLocationLabel(this.skill)}
|
|
274
|
+
content.addChild(new Text(this.theme.fg("muted", `${getSkillLocationLabel(this.skill)}${getSkillLocation(this.skill)}`), 0, 0));
|
|
275
275
|
content.addChild(new Spacer(1));
|
|
276
276
|
content.addChild(new Text(this.theme.fg("muted", this.theme.bold("Metadata")), 0, 0));
|
|
277
277
|
content.addChild(new Text(this.theme.fg("dim", buildFrontmatterBlock(this.skill)), 0, 0));
|
|
@@ -292,7 +292,7 @@ class ScrollableSkillPreview implements Component {
|
|
|
292
292
|
: "";
|
|
293
293
|
const editInfo = this.editable ? " • e edit • r rename" : "";
|
|
294
294
|
return truncateToWidth(
|
|
295
|
-
this.theme.fg("dim", `↑/↓ scroll
|
|
295
|
+
this.theme.fg("dim", `↑/↓ scroll${editInfo} • esc back${scrollInfo}`),
|
|
296
296
|
innerWidth,
|
|
297
297
|
this.theme.fg("dim", "..."),
|
|
298
298
|
);
|