@alexgorbatchev/pi-skill-library 1.0.1 → 1.0.2

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. Home-directory paths are rendered with the `~/` convention.
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.1",
3
+ "version": "1.0.2",
4
4
  "description": "Pi extension that exposes skills-library roots through /library:<skill-name> commands.",
5
5
  "type": "module",
6
6
  "author": "Alex Gorbatchev",
@@ -1,16 +1,9 @@
1
1
  import { groupLibrarySummariesByScope } from "./groupLibrarySummariesByScope.js";
2
2
  import { replaceHomeDirectoryWithTilde } from "./replaceHomeDirectoryWithTilde.js";
3
- import type { ILibraryReportDetails, ILibrarySkillDiscovery, ILibrarySummary } from "./types.js";
3
+ import type { ILibraryReportDetails, ILibrarySummary } from "./types.js";
4
4
 
5
- export function createLibraryReport(librarySkillDiscovery: ILibrarySkillDiscovery): string {
6
- return createLibraryReportFromDetails({
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", "user", "path"];
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
- if (scope === "temporary") {
33
- return "path";
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 { ExtensionAPI, ExtensionContext, InputEventResult } from "@mariozechner/pi-coding-agent";
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, MessageContent } from "./types.js";
11
+ import type { ILibraryReportDetails, ILibrarySkillDiscovery } from "./types.js";
11
12
 
12
13
  const INFO_COMMAND_NAME = "pi-skill-library";
13
- const STARTUP_REPORT_MESSAGE_TYPE = "pi-skill-library.startup-report";
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
- ctx.ui.notify(
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
- pi.sendMessage({
85
- customType: STARTUP_REPORT_MESSAGE_TYPE,
86
- content: createLibraryReport(librarySkillDiscovery),
87
- display: true,
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
- pi.sendMessage({
100
- customType: STARTUP_REPORT_MESSAGE_TYPE,
101
- content: createLibraryReport(librarySkillDiscovery),
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
- ctx.ui.notify(`Unknown library skill: ${libraryCommand.skillName}. ${availableSkillSummary}`, "error");
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 readLibraryReportDetails(details: unknown): ILibraryReportDetails | null {
152
- if (!isLibraryReportDetails(details)) {
153
- return null;
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
- const diagnostics = Reflect.get(value, "diagnostics");
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: MessageContent): string {
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", "[Skills Library]")];
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[];