@alexgorbatchev/pi-skill-library 1.0.1 → 1.0.3
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
|
@@ -75,7 +75,7 @@ Invoke a library skill directly:
|
|
|
75
75
|
|
|
76
76
|
Discovered library skills are registered as real extension slash commands at startup and reload, so they show up in slash-command autocomplete like other commands.
|
|
77
77
|
|
|
78
|
-
On startup, the extension prints a library-discovery message into the transcript listing each discovered library root and its skills.
|
|
78
|
+
On startup, the extension prints a library-discovery message into the transcript listing each discovered library root and its skills.
|
|
79
79
|
|
|
80
80
|
Use the package info command to print the same report again:
|
|
81
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexgorbatchev/pi-skill-library",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Pi extension that exposes skills-library roots through /library:<skill-name> commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Alex Gorbatchev",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"typescript": "6.0.2"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"
|
|
49
|
+
"fix": "bun --bun oxfmt --write .",
|
|
50
|
+
"check": "bun --bun oxfmt --check . && bun --bun oxlint . && tsgo --noEmit",
|
|
50
51
|
"test": "PI_OFFLINE=1 bun x pi --no-extensions -e ./src/index.ts --list-models > /dev/null 2>&1",
|
|
51
52
|
"test:local": "bun x pi -e ./src/index.ts"
|
|
52
53
|
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { groupLibrarySummariesByScope } from "./groupLibrarySummariesByScope.js";
|
|
2
2
|
import { replaceHomeDirectoryWithTilde } from "./replaceHomeDirectoryWithTilde.js";
|
|
3
|
-
import type { ILibraryReportDetails,
|
|
3
|
+
import type { ILibraryReportDetails, ILibrarySummary } from "./types.js";
|
|
4
4
|
|
|
5
|
-
export function createLibraryReport(
|
|
6
|
-
|
|
7
|
-
diagnostics: librarySkillDiscovery.diagnostics,
|
|
8
|
-
librarySummaries: librarySkillDiscovery.librarySummaries,
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function createLibraryReportFromDetails(details: ILibraryReportDetails): string {
|
|
13
|
-
const lines = ["[Skills Library]"];
|
|
5
|
+
export function createLibraryReport(details: ILibraryReportDetails): string {
|
|
6
|
+
const lines = ["[@alexgorbatchev/pi-skill-library]"];
|
|
14
7
|
if (details.librarySummaries.length === 0) {
|
|
15
8
|
lines.push(" No library skills were discovered.");
|
|
16
9
|
return lines.join("\n");
|
|
@@ -9,7 +9,7 @@ export function groupLibrarySummariesByScope(librarySummaries: ILibrarySummary[]
|
|
|
9
9
|
librarySummariesByScope.set(displayScope, existingSummaries);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const orderedScopes = ["project", "
|
|
12
|
+
const orderedScopes = ["project settings", "global settings", "path"];
|
|
13
13
|
const orderedLibrarySummariesByScope = new Map<string, ILibrarySummary[]>();
|
|
14
14
|
for (const orderedScope of orderedScopes) {
|
|
15
15
|
const scopedSummaries = librarySummariesByScope.get(orderedScope);
|
|
@@ -29,9 +29,12 @@ export function groupLibrarySummariesByScope(librarySummaries: ILibrarySummary[]
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function toDisplayScope(scope: ILibrarySummary["scope"]): string {
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
switch (scope) {
|
|
33
|
+
case "project":
|
|
34
|
+
return "project settings";
|
|
35
|
+
case "user":
|
|
36
|
+
return "global settings";
|
|
37
|
+
case "temporary":
|
|
38
|
+
return "path";
|
|
34
39
|
}
|
|
35
|
-
|
|
36
|
-
return scope;
|
|
37
40
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, InputEventResult, Theme } from "@mariozechner/pi-coding-agent";
|
|
2
3
|
import { Box, Text } from "@mariozechner/pi-tui";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -7,17 +8,53 @@ import { discoverLibrarySkills } from "./discoverLibrarySkills.js";
|
|
|
7
8
|
import { expandLibrarySkill } from "./expandLibrarySkill.js";
|
|
8
9
|
import { parseLibraryCommand } from "./parseLibraryCommand.js";
|
|
9
10
|
import { renderLibraryReport } from "./renderLibraryReport.js";
|
|
10
|
-
import type { ILibraryReportDetails, ILibrarySkillDiscovery
|
|
11
|
+
import type { ILibraryReportDetails, ILibrarySkillDiscovery } from "./types.js";
|
|
11
12
|
|
|
12
13
|
const INFO_COMMAND_NAME = "pi-skill-library";
|
|
13
|
-
const
|
|
14
|
+
const LIBRARY_MESSAGE_TYPE = "pi-skill-library.message";
|
|
14
15
|
const extensionPackageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
15
16
|
const handledInputEventResult: InputEventResult = { action: "handled" };
|
|
16
17
|
|
|
18
|
+
type LibraryMessageContent = string | (TextContent | ImageContent)[];
|
|
19
|
+
type LibraryMessageLevel = "info" | "error";
|
|
20
|
+
|
|
21
|
+
interface ILibraryMessageDetails {
|
|
22
|
+
level: LibraryMessageLevel;
|
|
23
|
+
reportDetails?: ILibraryReportDetails;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export default function piSkillLibraryExtension(pi: ExtensionAPI): void {
|
|
18
27
|
let cachedLibrarySkillDiscovery: ILibrarySkillDiscovery | null = null;
|
|
19
28
|
let cachedCwd = "";
|
|
20
29
|
|
|
30
|
+
pi.registerMessageRenderer<ILibraryMessageDetails>(LIBRARY_MESSAGE_TYPE, (message, _options, theme) => {
|
|
31
|
+
const details = message.details;
|
|
32
|
+
const content = getMessageTextContent(message.content);
|
|
33
|
+
const text =
|
|
34
|
+
details?.reportDetails === undefined
|
|
35
|
+
? renderLibraryMessage(theme, content, details?.level ?? "info")
|
|
36
|
+
: renderLibraryReport(theme, details.reportDetails);
|
|
37
|
+
const box = new Box(0, 0);
|
|
38
|
+
box.addChild(new Text(text, 0, 0));
|
|
39
|
+
return box;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const sendLibraryMessage = (content: string, details: ILibraryMessageDetails): void => {
|
|
43
|
+
pi.sendMessage<ILibraryMessageDetails>({
|
|
44
|
+
customType: LIBRARY_MESSAGE_TYPE,
|
|
45
|
+
content,
|
|
46
|
+
display: true,
|
|
47
|
+
details,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const sendLibraryReport = (reportDetails: ILibraryReportDetails): void => {
|
|
52
|
+
sendLibraryMessage(createLibraryReport(reportDetails), {
|
|
53
|
+
level: "info",
|
|
54
|
+
reportDetails,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
21
58
|
const ensureLibrarySkillCommandsRegistered = (librarySkillDiscovery: ILibrarySkillDiscovery): void => {
|
|
22
59
|
for (const librarySkill of librarySkillDiscovery.skills) {
|
|
23
60
|
const commandName = createLibraryCommandName(librarySkill.name);
|
|
@@ -28,9 +65,9 @@ export default function piSkillLibraryExtension(pi: ExtensionAPI): void {
|
|
|
28
65
|
const requestedSkill = refreshedLibrarySkillDiscovery.skillByName.get(librarySkill.name);
|
|
29
66
|
if (requestedSkill === undefined) {
|
|
30
67
|
if (ctx.hasUI) {
|
|
31
|
-
|
|
68
|
+
sendLibraryMessage(
|
|
32
69
|
`Library skill is no longer available: ${librarySkill.name}. Run /reload if discovery changed.`,
|
|
33
|
-
"error",
|
|
70
|
+
{ level: "error" },
|
|
34
71
|
);
|
|
35
72
|
}
|
|
36
73
|
return;
|
|
@@ -69,24 +106,13 @@ export default function piSkillLibraryExtension(pi: ExtensionAPI): void {
|
|
|
69
106
|
await refreshLibrarySkillDiscovery(ctx.cwd);
|
|
70
107
|
};
|
|
71
108
|
|
|
72
|
-
pi.registerMessageRenderer(STARTUP_REPORT_MESSAGE_TYPE, (message, _options, theme) => {
|
|
73
|
-
const reportDetails = readLibraryReportDetails(message.details);
|
|
74
|
-
const reportText =
|
|
75
|
-
reportDetails === null ? getMessageTextContent(message.content) : renderLibraryReport(theme, reportDetails);
|
|
76
|
-
const box = new Box(0, 0);
|
|
77
|
-
box.addChild(new Text(reportText, 0, 0));
|
|
78
|
-
return box;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
109
|
pi.on("session_start", async (_event, ctx) => {
|
|
82
110
|
invalidateLibrarySkillDiscovery();
|
|
83
111
|
const librarySkillDiscovery = await refreshLibrarySkillDiscovery(ctx.cwd);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
details: createLibraryReportDetails(librarySkillDiscovery),
|
|
89
|
-
});
|
|
112
|
+
|
|
113
|
+
if (ctx.hasUI) {
|
|
114
|
+
sendLibraryReport(createLibraryReportDetails(librarySkillDiscovery));
|
|
115
|
+
}
|
|
90
116
|
});
|
|
91
117
|
pi.on("session_before_switch", onSessionChanged);
|
|
92
118
|
pi.on("session_before_fork", onSessionChanged);
|
|
@@ -96,24 +122,12 @@ export default function piSkillLibraryExtension(pi: ExtensionAPI): void {
|
|
|
96
122
|
description: "Print the discovered skills-library roots and skills",
|
|
97
123
|
handler: async (_args, ctx) => {
|
|
98
124
|
const librarySkillDiscovery = await refreshLibrarySkillDiscovery(ctx.cwd);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
display: true,
|
|
103
|
-
details: createLibraryReportDetails(librarySkillDiscovery),
|
|
104
|
-
});
|
|
125
|
+
if (ctx.hasUI) {
|
|
126
|
+
sendLibraryReport(createLibraryReportDetails(librarySkillDiscovery));
|
|
127
|
+
}
|
|
105
128
|
},
|
|
106
129
|
});
|
|
107
130
|
|
|
108
|
-
pi.on("context", async (event) => {
|
|
109
|
-
return {
|
|
110
|
-
messages: event.messages.filter((message) => {
|
|
111
|
-
const messageCustomType = "customType" in message ? message.customType : undefined;
|
|
112
|
-
return messageCustomType !== STARTUP_REPORT_MESSAGE_TYPE;
|
|
113
|
-
}),
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
|
|
117
131
|
pi.on("input", async (event, ctx): Promise<InputEventResult | undefined> => {
|
|
118
132
|
const libraryCommand = parseLibraryCommand(event.text);
|
|
119
133
|
if (libraryCommand === null) {
|
|
@@ -130,7 +144,9 @@ export default function piSkillLibraryExtension(pi: ExtensionAPI): void {
|
|
|
130
144
|
: `Available library skills: ${availableSkillNames.join(", ")}`;
|
|
131
145
|
|
|
132
146
|
if (ctx.hasUI) {
|
|
133
|
-
|
|
147
|
+
sendLibraryMessage(`Unknown library skill: ${libraryCommand.skillName}. ${availableSkillSummary}`, {
|
|
148
|
+
level: "error",
|
|
149
|
+
});
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
return handledInputEventResult;
|
|
@@ -148,50 +164,15 @@ function createLibraryReportDetails(librarySkillDiscovery: ILibrarySkillDiscover
|
|
|
148
164
|
};
|
|
149
165
|
}
|
|
150
166
|
|
|
151
|
-
function
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return details;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function isLibraryReportDetails(value: unknown): value is ILibraryReportDetails {
|
|
160
|
-
if (typeof value !== "object" || value === null) {
|
|
161
|
-
return false;
|
|
167
|
+
function renderLibraryMessage(theme: Theme, content: string, level: LibraryMessageLevel): string {
|
|
168
|
+
if (level === "error") {
|
|
169
|
+
return theme.fg("error", content);
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
const librarySummaries = Reflect.get(value, "librarySummaries");
|
|
166
|
-
return (
|
|
167
|
-
Array.isArray(diagnostics) &&
|
|
168
|
-
diagnostics.every((diagnostic) => typeof diagnostic === "string") &&
|
|
169
|
-
Array.isArray(librarySummaries) &&
|
|
170
|
-
librarySummaries.every(isLibrarySummary)
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function isLibrarySummary(value: unknown): value is ILibraryReportDetails["librarySummaries"][number] {
|
|
175
|
-
if (typeof value !== "object" || value === null) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const libraryPath = Reflect.get(value, "libraryPath");
|
|
180
|
-
const scope = Reflect.get(value, "scope");
|
|
181
|
-
const skillNames = Reflect.get(value, "skillNames");
|
|
182
|
-
return (
|
|
183
|
-
typeof libraryPath === "string" &&
|
|
184
|
-
isLibrarySummaryScope(scope) &&
|
|
185
|
-
Array.isArray(skillNames) &&
|
|
186
|
-
skillNames.every((skillName) => typeof skillName === "string")
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function isLibrarySummaryScope(value: unknown): value is ILibraryReportDetails["librarySummaries"][number]["scope"] {
|
|
191
|
-
return value === "project" || value === "user" || value === "temporary";
|
|
172
|
+
return content;
|
|
192
173
|
}
|
|
193
174
|
|
|
194
|
-
function getMessageTextContent(content:
|
|
175
|
+
function getMessageTextContent(content: LibraryMessageContent): string {
|
|
195
176
|
if (typeof content === "string") {
|
|
196
177
|
return content;
|
|
197
178
|
}
|
|
@@ -4,7 +4,7 @@ import { replaceHomeDirectoryWithTilde } from "./replaceHomeDirectoryWithTilde.j
|
|
|
4
4
|
import type { ILibraryReportDetails } from "./types.js";
|
|
5
5
|
|
|
6
6
|
export function renderLibraryReport(theme: Theme, details: ILibraryReportDetails): string {
|
|
7
|
-
const lines: string[] = [theme.fg("mdHeading", "[
|
|
7
|
+
const lines: string[] = [theme.fg("mdHeading", "[@alexgorbatchev/pi-skill-library]")];
|
|
8
8
|
if (details.librarySummaries.length === 0) {
|
|
9
9
|
lines.push(theme.fg("dim", " No library skills were discovered."));
|
|
10
10
|
return lines.join("\n");
|
package/src/types.ts
CHANGED
|
@@ -23,10 +23,3 @@ export interface ILibrarySkillDiscovery {
|
|
|
23
23
|
libraryPaths: string[];
|
|
24
24
|
librarySummaries: ILibrarySummary[];
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
export interface IMessageContentPart {
|
|
28
|
-
type: string;
|
|
29
|
-
text?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type MessageContent = string | IMessageContentPart[];
|