@kmiyh/pi-skills-menu 1.0.5 → 1.2.0
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 +13 -1
- package/package.json +1 -1
- package/src/create-skill.ts +75 -20
- package/src/index.ts +34 -55
- package/src/skill-enabled-toggle.ts +69 -0
- package/src/skill-registry.ts +16 -5
- package/src/types.ts +2 -0
- package/src/ui/skill-preview.ts +3 -3
- package/src/ui/skills-manager.ts +1357 -0
- package/src/ui/skills-selector.ts +27 -8
package/README.md
CHANGED
|
@@ -59,10 +59,22 @@ Press:
|
|
|
59
59
|
|
|
60
60
|
Press:
|
|
61
61
|
|
|
62
|
-
- `Enter` — select
|
|
62
|
+
- `Enter` — select an enabled skill and insert it into the editor
|
|
63
63
|
|
|
64
64
|
After that, the skill is inserted into the editor so it will be used by Pi when the message is sent.
|
|
65
65
|
|
|
66
|
+
### Enable or disable a skill
|
|
67
|
+
|
|
68
|
+
Press:
|
|
69
|
+
|
|
70
|
+
- `Ctrl+X` — toggle the selected skill between enabled and disabled
|
|
71
|
+
|
|
72
|
+
This also works for skills that come from installed libraries/packages.
|
|
73
|
+
|
|
74
|
+
Disabled skills stay visible in the list and are marked with:
|
|
75
|
+
|
|
76
|
+
- `[disabled]`
|
|
77
|
+
|
|
66
78
|
### Create a new skill
|
|
67
79
|
|
|
68
80
|
The list also contains a dedicated entry for creating a new skill.
|
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,29 +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
|
-
};
|
|
1072
|
+
return await saveCreatedSkill(ctx, answers, draft);
|
|
1018
1073
|
}
|
|
1019
1074
|
|
|
1020
1075
|
export async function createNewSkill(
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
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";
|
|
6
6
|
import { loadSkillRegistry } from "./skill-registry.js";
|
|
7
7
|
import { ensureSkillCommandsHidden } from "./settings-toggle.js";
|
|
8
|
+
import { setSkillEnabled } from "./skill-enabled-toggle.js";
|
|
8
9
|
import type { ExtensionInstallScope, SkillRegistry } from "./types.js";
|
|
9
|
-
import {
|
|
10
|
-
import { showSkillsSelector } from "./ui/skills-selector.js";
|
|
10
|
+
import { showSkillsManager } from "./ui/skills-manager.js";
|
|
11
11
|
|
|
12
12
|
const EMPTY_REGISTRY: SkillRegistry = {
|
|
13
13
|
skills: [],
|
|
14
|
+
allSkills: [],
|
|
14
15
|
byName: new Map(),
|
|
15
16
|
};
|
|
16
17
|
|
|
@@ -105,64 +106,42 @@ export default function skillsMenuExtension(pi: ExtensionAPI) {
|
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
selectorIndex = selection.selectedIndex;
|
|
124
|
-
selectorQuery = selection.query;
|
|
125
|
-
|
|
126
|
-
if (selection.type === "create") {
|
|
127
|
-
const createdSkill = await createSkillFromAnswers(ctx, selection.answers, {
|
|
128
|
-
thinkingLevel: pi.getThinkingLevel(),
|
|
129
|
-
});
|
|
130
|
-
if (!createdSkill) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
await refreshRegistry(ctx.cwd);
|
|
134
|
-
const createdSkillIndex = registry.skills.findIndex((skill) => skill.path === createdSkill.path);
|
|
135
|
-
selectorIndex = createdSkillIndex >= 0 ? createdSkillIndex + 1 : 0;
|
|
136
|
-
selectorQuery = "";
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (selection.type === "preview") {
|
|
141
|
-
await showSkillPreview(ctx, selection.skill);
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (selection.type === "delete") {
|
|
146
|
-
const confirmed = await ctx.ui.confirm(
|
|
147
|
-
"Delete skill",
|
|
148
|
-
`Delete ${selection.skill.name}? This removes the skill from disk and cannot be undone.`,
|
|
149
|
-
);
|
|
150
|
-
if (!confirmed) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
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) => {
|
|
153
123
|
try {
|
|
154
|
-
await deleteSkill(ctx,
|
|
155
|
-
selectorIndex = Math.max(0, selectorIndex - 1);
|
|
124
|
+
return await deleteSkill(ctx, skill);
|
|
156
125
|
} catch (error) {
|
|
157
126
|
console.error("skills-menu: failed to delete skill", error);
|
|
158
127
|
ctx.ui.notify("Failed to delete skill", "error");
|
|
128
|
+
return false;
|
|
159
129
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
130
|
+
},
|
|
131
|
+
onToggle: async (skill, enabled) => {
|
|
132
|
+
try {
|
|
133
|
+
await setSkillEnabled(ctx.cwd, skill, enabled);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("skills-menu: failed to toggle skill", error);
|
|
136
|
+
throw error instanceof Error ? error : new Error("Failed to update skill visibility");
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
onRefresh: async () => await refreshRegistry(ctx.cwd),
|
|
140
|
+
});
|
|
141
|
+
if (!selection) {
|
|
164
142
|
return;
|
|
165
143
|
}
|
|
144
|
+
insertSkillMarker(ctx, selection);
|
|
166
145
|
},
|
|
167
146
|
});
|
|
168
147
|
|
|
@@ -188,7 +167,7 @@ export default function skillsMenuExtension(pi: ExtensionAPI) {
|
|
|
188
167
|
return { action: "continue" };
|
|
189
168
|
}
|
|
190
169
|
|
|
191
|
-
if (!currentCwd || currentCwd !== ctx.cwd || registry.
|
|
170
|
+
if (!currentCwd || currentCwd !== ctx.cwd || registry.allSkills.length === 0) {
|
|
192
171
|
try {
|
|
193
172
|
await refreshRegistry(ctx.cwd);
|
|
194
173
|
} catch (error) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { dirname, join, relative } from "node:path";
|
|
2
|
+
import { getAgentDir, SettingsManager, type PackageSource } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import type { SkillEntry } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function updatePatterns(current: string[], pattern: string, enabled: boolean): string[] {
|
|
6
|
+
const updated = current.filter((entry) => {
|
|
7
|
+
const stripped = entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-") ? entry.slice(1) : entry;
|
|
8
|
+
return stripped !== pattern;
|
|
9
|
+
});
|
|
10
|
+
updated.push(`${enabled ? "+" : "-"}${pattern}`);
|
|
11
|
+
return updated;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getTopLevelPattern(skill: SkillEntry, cwd: string): string {
|
|
15
|
+
const baseDir = skill.scope === "project" ? join(cwd, ".pi") : getAgentDir();
|
|
16
|
+
return relative(baseDir, skill.path);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getPackagePattern(skill: SkillEntry): string {
|
|
20
|
+
const baseDir = skill.baseDir ?? dirname(skill.path);
|
|
21
|
+
return relative(baseDir, skill.path);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasPackageFilters(pkg: Exclude<PackageSource, string>): boolean {
|
|
25
|
+
return pkg.extensions !== undefined || pkg.skills !== undefined || pkg.prompts !== undefined || pkg.themes !== undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function setSkillEnabled(cwd: string, skill: SkillEntry, enabled: boolean): Promise<void> {
|
|
29
|
+
if (skill.scope === "temporary") {
|
|
30
|
+
throw new Error("Temporary skills cannot be toggled.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const settingsManager = SettingsManager.create(cwd, getAgentDir());
|
|
34
|
+
|
|
35
|
+
if (skill.origin === "top-level") {
|
|
36
|
+
const settings = skill.scope === "project" ? settingsManager.getProjectSettings() : settingsManager.getGlobalSettings();
|
|
37
|
+
const current = [...(settings.skills ?? [])];
|
|
38
|
+
const updated = updatePatterns(current, getTopLevelPattern(skill, cwd), enabled);
|
|
39
|
+
|
|
40
|
+
if (skill.scope === "project") {
|
|
41
|
+
settingsManager.setProjectSkillPaths(updated);
|
|
42
|
+
} else {
|
|
43
|
+
settingsManager.setSkillPaths(updated);
|
|
44
|
+
}
|
|
45
|
+
await settingsManager.flush();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const settings = skill.scope === "project" ? settingsManager.getProjectSettings() : settingsManager.getGlobalSettings();
|
|
50
|
+
const packages = [...(settings.packages ?? [])];
|
|
51
|
+
const packageIndex = packages.findIndex((pkg) => (typeof pkg === "string" ? pkg : pkg.source) === skill.source);
|
|
52
|
+
if (packageIndex === -1) {
|
|
53
|
+
throw new Error("Could not find the package settings entry for this skill.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const packageEntry = packages[packageIndex];
|
|
57
|
+
const packageConfig = typeof packageEntry === "string" ? { source: packageEntry } : { ...packageEntry };
|
|
58
|
+
const current = [...(packageConfig.skills ?? [])];
|
|
59
|
+
const updated = updatePatterns(current, getPackagePattern(skill), enabled);
|
|
60
|
+
packageConfig.skills = updated.length > 0 ? updated : undefined;
|
|
61
|
+
packages[packageIndex] = hasPackageFilters(packageConfig) ? packageConfig : packageConfig.source;
|
|
62
|
+
|
|
63
|
+
if (skill.scope === "project") {
|
|
64
|
+
settingsManager.setProjectPackages(packages);
|
|
65
|
+
} else {
|
|
66
|
+
settingsManager.setPackages(packages);
|
|
67
|
+
}
|
|
68
|
+
await settingsManager.flush();
|
|
69
|
+
}
|
package/src/skill-registry.ts
CHANGED
|
@@ -25,8 +25,6 @@ function compareSkills(a: SkillEntry, b: SkillEntry): number {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function toSkillEntry(resource: ResolvedResource): SkillEntry | null {
|
|
28
|
-
if (!resource.enabled) return null;
|
|
29
|
-
|
|
30
28
|
const parsed = parseSkillFile(resource.path);
|
|
31
29
|
if (!parsed) return null;
|
|
32
30
|
|
|
@@ -40,9 +38,21 @@ function toSkillEntry(resource: ResolvedResource): SkillEntry | null {
|
|
|
40
38
|
origin: resource.metadata.origin,
|
|
41
39
|
source: resource.metadata.source,
|
|
42
40
|
baseDir: resource.metadata.baseDir,
|
|
41
|
+
enabled: resource.enabled,
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
function dedupeByPath(skills: SkillEntry[]): SkillEntry[] {
|
|
46
|
+
const seen = new Set<string>();
|
|
47
|
+
const deduped: SkillEntry[] = [];
|
|
48
|
+
for (const skill of skills) {
|
|
49
|
+
if (seen.has(skill.path)) continue;
|
|
50
|
+
seen.add(skill.path);
|
|
51
|
+
deduped.push(skill);
|
|
52
|
+
}
|
|
53
|
+
return deduped;
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
export async function loadSkillRegistry(cwd: string): Promise<SkillRegistry> {
|
|
47
57
|
const settingsManager = SettingsManager.create(cwd, getAgentDir());
|
|
48
58
|
const packageManager = new DefaultPackageManager({
|
|
@@ -52,10 +62,10 @@ export async function loadSkillRegistry(cwd: string): Promise<SkillRegistry> {
|
|
|
52
62
|
});
|
|
53
63
|
const resolved = await packageManager.resolve();
|
|
54
64
|
|
|
65
|
+
const allSkills = dedupeByPath(resolved.skills.map(toSkillEntry).filter((entry): entry is SkillEntry => entry !== null)).sort(compareSkills);
|
|
55
66
|
const byName = new Map<string, SkillEntry>();
|
|
56
|
-
for (const
|
|
57
|
-
|
|
58
|
-
if (!entry) continue;
|
|
67
|
+
for (const entry of allSkills) {
|
|
68
|
+
if (!entry.enabled) continue;
|
|
59
69
|
if (!byName.has(entry.name)) {
|
|
60
70
|
byName.set(entry.name, entry);
|
|
61
71
|
}
|
|
@@ -64,6 +74,7 @@ export async function loadSkillRegistry(cwd: string): Promise<SkillRegistry> {
|
|
|
64
74
|
const skills = Array.from(byName.values()).sort(compareSkills);
|
|
65
75
|
return {
|
|
66
76
|
skills,
|
|
77
|
+
allSkills,
|
|
67
78
|
byName: new Map(skills.map((skill) => [skill.name, skill])),
|
|
68
79
|
};
|
|
69
80
|
}
|
package/src/types.ts
CHANGED
|
@@ -10,9 +10,11 @@ export interface SkillEntry {
|
|
|
10
10
|
origin: "package" | "top-level";
|
|
11
11
|
source: string;
|
|
12
12
|
baseDir?: string;
|
|
13
|
+
enabled: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface SkillRegistry {
|
|
16
17
|
skills: SkillEntry[];
|
|
18
|
+
allSkills: SkillEntry[];
|
|
17
19
|
byName: Map<string, SkillEntry>;
|
|
18
20
|
}
|
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
|
);
|