claudeup 3.12.0 → 3.13.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/package.json +1 -1
- package/src/services/skills-manager.js +17 -6
- package/src/services/skills-manager.ts +21 -6
- package/src/ui/components/ScopeIndicator.js +30 -0
- package/src/ui/components/ScopeIndicator.tsx +57 -0
- package/src/ui/screens/PluginsScreen.js +17 -26
- package/src/ui/screens/PluginsScreen.tsx +25 -29
- package/src/ui/screens/SkillsScreen.js +12 -23
- package/src/ui/screens/SkillsScreen.tsx +17 -27
package/package.json
CHANGED
|
@@ -164,9 +164,11 @@ export async function fetchPopularSkills(limit = 30) {
|
|
|
164
164
|
export async function fetchAvailableSkills(_repos, projectPath) {
|
|
165
165
|
const userInstalled = await getInstalledSkillNames("user");
|
|
166
166
|
const projectInstalled = await getInstalledSkillNames("project", projectPath);
|
|
167
|
+
const slugify = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
167
168
|
const markInstalled = (skill) => {
|
|
168
|
-
const
|
|
169
|
-
const
|
|
169
|
+
const slug = slugify(skill.name);
|
|
170
|
+
const isUserInstalled = userInstalled.has(slug) || userInstalled.has(skill.name);
|
|
171
|
+
const isProjInstalled = projectInstalled.has(slug) || projectInstalled.has(skill.name);
|
|
170
172
|
const installed = isUserInstalled || isProjInstalled;
|
|
171
173
|
const installedScope = isProjInstalled
|
|
172
174
|
? "project"
|
|
@@ -256,16 +258,25 @@ export async function installSkill(skill, scope, projectPath) {
|
|
|
256
258
|
if (!content) {
|
|
257
259
|
throw new Error(`Failed to fetch skill: SKILL.md not found in ${repo}/${repoPath}`);
|
|
258
260
|
}
|
|
261
|
+
// Use slug for directory name — display names can have slashes/spaces
|
|
262
|
+
const dirName = skill.name
|
|
263
|
+
.toLowerCase()
|
|
264
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
265
|
+
.replace(/^-|-$/g, "");
|
|
259
266
|
const installDir = scope === "user"
|
|
260
|
-
? path.join(getUserSkillsDir(),
|
|
261
|
-
: path.join(getProjectSkillsDir(projectPath),
|
|
267
|
+
? path.join(getUserSkillsDir(), dirName)
|
|
268
|
+
: path.join(getProjectSkillsDir(projectPath), dirName);
|
|
262
269
|
await fs.ensureDir(installDir);
|
|
263
270
|
await fs.writeFile(path.join(installDir, "SKILL.md"), content, "utf8");
|
|
264
271
|
}
|
|
265
272
|
export async function uninstallSkill(skillName, scope, projectPath) {
|
|
273
|
+
const dirName = skillName
|
|
274
|
+
.toLowerCase()
|
|
275
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
276
|
+
.replace(/^-|-$/g, "");
|
|
266
277
|
const installDir = scope === "user"
|
|
267
|
-
? path.join(getUserSkillsDir(),
|
|
268
|
-
: path.join(getProjectSkillsDir(projectPath),
|
|
278
|
+
? path.join(getUserSkillsDir(), dirName)
|
|
279
|
+
: path.join(getProjectSkillsDir(projectPath), dirName);
|
|
269
280
|
const skillMdPath = path.join(installDir, "SKILL.md");
|
|
270
281
|
if (await fs.pathExists(skillMdPath)) {
|
|
271
282
|
await fs.remove(skillMdPath);
|
|
@@ -228,9 +228,13 @@ export async function fetchAvailableSkills(
|
|
|
228
228
|
const userInstalled = await getInstalledSkillNames("user");
|
|
229
229
|
const projectInstalled = await getInstalledSkillNames("project", projectPath);
|
|
230
230
|
|
|
231
|
+
const slugify = (name: string) =>
|
|
232
|
+
name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
233
|
+
|
|
231
234
|
const markInstalled = (skill: SkillInfo): SkillInfo => {
|
|
232
|
-
const
|
|
233
|
-
const
|
|
235
|
+
const slug = slugify(skill.name);
|
|
236
|
+
const isUserInstalled = userInstalled.has(slug) || userInstalled.has(skill.name);
|
|
237
|
+
const isProjInstalled = projectInstalled.has(slug) || projectInstalled.has(skill.name);
|
|
234
238
|
const installed = isUserInstalled || isProjInstalled;
|
|
235
239
|
const installedScope: "user" | "project" | null = isProjInstalled
|
|
236
240
|
? "project"
|
|
@@ -334,10 +338,16 @@ export async function installSkill(
|
|
|
334
338
|
);
|
|
335
339
|
}
|
|
336
340
|
|
|
341
|
+
// Use slug for directory name — display names can have slashes/spaces
|
|
342
|
+
const dirName = skill.name
|
|
343
|
+
.toLowerCase()
|
|
344
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
345
|
+
.replace(/^-|-$/g, "");
|
|
346
|
+
|
|
337
347
|
const installDir =
|
|
338
348
|
scope === "user"
|
|
339
|
-
? path.join(getUserSkillsDir(),
|
|
340
|
-
: path.join(getProjectSkillsDir(projectPath),
|
|
349
|
+
? path.join(getUserSkillsDir(), dirName)
|
|
350
|
+
: path.join(getProjectSkillsDir(projectPath), dirName);
|
|
341
351
|
|
|
342
352
|
await fs.ensureDir(installDir);
|
|
343
353
|
await fs.writeFile(path.join(installDir, "SKILL.md"), content, "utf8");
|
|
@@ -348,10 +358,15 @@ export async function uninstallSkill(
|
|
|
348
358
|
scope: "user" | "project",
|
|
349
359
|
projectPath?: string,
|
|
350
360
|
): Promise<void> {
|
|
361
|
+
const dirName = skillName
|
|
362
|
+
.toLowerCase()
|
|
363
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
364
|
+
.replace(/^-|-$/g, "");
|
|
365
|
+
|
|
351
366
|
const installDir =
|
|
352
367
|
scope === "user"
|
|
353
|
-
? path.join(getUserSkillsDir(),
|
|
354
|
-
: path.join(getProjectSkillsDir(projectPath),
|
|
368
|
+
? path.join(getUserSkillsDir(), dirName)
|
|
369
|
+
: path.join(getProjectSkillsDir(projectPath), dirName);
|
|
355
370
|
|
|
356
371
|
const skillMdPath = path.join(installDir, "SKILL.md");
|
|
357
372
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@opentui/react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Scope indicator for list items.
|
|
4
|
+
* Shows colored letter badges when installed, empty space when not.
|
|
5
|
+
*
|
|
6
|
+
* Installed in user+project: u p
|
|
7
|
+
* Installed in project only: p
|
|
8
|
+
* Not installed: (3 spaces)
|
|
9
|
+
*
|
|
10
|
+
* Colors match the detail panel: cyan=user, green=project, yellow=local
|
|
11
|
+
*/
|
|
12
|
+
export function scopeIndicatorText(user, project, local) {
|
|
13
|
+
const segments = [
|
|
14
|
+
user
|
|
15
|
+
? { char: "u", bg: "cyan", fg: "black" }
|
|
16
|
+
: { char: " ", bg: "", fg: "" },
|
|
17
|
+
project
|
|
18
|
+
? { char: "p", bg: "green", fg: "black" }
|
|
19
|
+
: { char: " ", bg: "", fg: "" },
|
|
20
|
+
local
|
|
21
|
+
? { char: "l", bg: "yellow", fg: "black" }
|
|
22
|
+
: { char: " ", bg: "", fg: "" },
|
|
23
|
+
];
|
|
24
|
+
const text = segments.map((s) => s.char).join("");
|
|
25
|
+
return { text, segments };
|
|
26
|
+
}
|
|
27
|
+
export function ScopeIndicator({ user, project, local }) {
|
|
28
|
+
const { segments } = scopeIndicatorText(user, project, local);
|
|
29
|
+
return (_jsx("text", { children: segments.map((s, i) => s.bg ? (_jsx("span", { bg: s.bg, fg: s.fg, children: s.char }, i)) : (_jsx("span", { children: " " }, i))) }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scope indicator for list items.
|
|
5
|
+
* Shows colored letter badges when installed, empty space when not.
|
|
6
|
+
*
|
|
7
|
+
* Installed in user+project: u p
|
|
8
|
+
* Installed in project only: p
|
|
9
|
+
* Not installed: (3 spaces)
|
|
10
|
+
*
|
|
11
|
+
* Colors match the detail panel: cyan=user, green=project, yellow=local
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export function scopeIndicatorText(
|
|
15
|
+
user?: boolean,
|
|
16
|
+
project?: boolean,
|
|
17
|
+
local?: boolean,
|
|
18
|
+
): { text: string; segments: Array<{ char: string; bg: string; fg: string }> } {
|
|
19
|
+
const segments: Array<{ char: string; bg: string; fg: string }> = [
|
|
20
|
+
user
|
|
21
|
+
? { char: "u", bg: "cyan", fg: "black" }
|
|
22
|
+
: { char: " ", bg: "", fg: "" },
|
|
23
|
+
project
|
|
24
|
+
? { char: "p", bg: "green", fg: "black" }
|
|
25
|
+
: { char: " ", bg: "", fg: "" },
|
|
26
|
+
local
|
|
27
|
+
? { char: "l", bg: "yellow", fg: "black" }
|
|
28
|
+
: { char: " ", bg: "", fg: "" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const text = segments.map((s) => s.char).join("");
|
|
32
|
+
return { text, segments };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ScopeIndicatorProps {
|
|
36
|
+
user?: boolean;
|
|
37
|
+
project?: boolean;
|
|
38
|
+
local?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function ScopeIndicator({ user, project, local }: ScopeIndicatorProps) {
|
|
42
|
+
const { segments } = scopeIndicatorText(user, project, local);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<text>
|
|
46
|
+
{segments.map((s, i) =>
|
|
47
|
+
s.bg ? (
|
|
48
|
+
<span key={i} bg={s.bg} fg={s.fg}>
|
|
49
|
+
{s.char}
|
|
50
|
+
</span>
|
|
51
|
+
) : (
|
|
52
|
+
<span key={i}> </span>
|
|
53
|
+
),
|
|
54
|
+
)}
|
|
55
|
+
</text>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -111,15 +111,15 @@ export function PluginsScreen() {
|
|
|
111
111
|
if (communityPlugins.length > 0) {
|
|
112
112
|
const communityVirtualMp = {
|
|
113
113
|
name: COMMUNITY_VIRTUAL_MARKETPLACE,
|
|
114
|
-
displayName: "
|
|
114
|
+
displayName: "Anthropic Official — 3rd Party",
|
|
115
115
|
source: marketplace.source,
|
|
116
|
-
description: "Third-party plugins
|
|
116
|
+
description: "Third-party plugins in the Anthropic Official marketplace",
|
|
117
117
|
};
|
|
118
118
|
const communityCollapsed = collapsed.has(COMMUNITY_VIRTUAL_MARKETPLACE);
|
|
119
119
|
items.push({
|
|
120
120
|
id: `mp:${COMMUNITY_VIRTUAL_MARKETPLACE}`,
|
|
121
121
|
type: "category",
|
|
122
|
-
label: "
|
|
122
|
+
label: "Anthropic Official — 3rd Party",
|
|
123
123
|
marketplace: communityVirtualMp,
|
|
124
124
|
marketplaceEnabled: true,
|
|
125
125
|
pluginCount: communityPlugins.length,
|
|
@@ -708,9 +708,10 @@ export function PluginsScreen() {
|
|
|
708
708
|
installedVersion &&
|
|
709
709
|
latestVersion !== "0.0.0" &&
|
|
710
710
|
installedVersion !== latestVersion;
|
|
711
|
-
// Determine action
|
|
712
|
-
//
|
|
713
|
-
//
|
|
711
|
+
// Determine action for THIS scope:
|
|
712
|
+
// - installed in this scope + has update → update
|
|
713
|
+
// - installed in this scope → uninstall from this scope
|
|
714
|
+
// - not installed in this scope → install to this scope
|
|
714
715
|
let action;
|
|
715
716
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
716
717
|
action = "update";
|
|
@@ -718,12 +719,8 @@ export function PluginsScreen() {
|
|
|
718
719
|
else if (isInstalledInScope) {
|
|
719
720
|
action = "uninstall";
|
|
720
721
|
}
|
|
721
|
-
else if (!isInstalledAnywhere) {
|
|
722
|
-
action = "install";
|
|
723
|
-
}
|
|
724
722
|
else {
|
|
725
|
-
|
|
726
|
-
action = "uninstall";
|
|
723
|
+
action = "install";
|
|
727
724
|
}
|
|
728
725
|
const actionLabel = action === "update"
|
|
729
726
|
? `Updating ${scopeLabel}`
|
|
@@ -883,17 +880,12 @@ export function PluginsScreen() {
|
|
|
883
880
|
}
|
|
884
881
|
if (item.type === "plugin" && item.plugin) {
|
|
885
882
|
const plugin = item.plugin;
|
|
886
|
-
let statusIcon = "○";
|
|
887
|
-
let statusColor = "gray";
|
|
888
883
|
const isAnyScope = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
statusIcon = "●";
|
|
895
|
-
statusColor = "green";
|
|
896
|
-
}
|
|
884
|
+
// Build scope parts for colored rendering
|
|
885
|
+
const hasUser = plugin.userScope?.enabled;
|
|
886
|
+
const hasProject = plugin.projectScope?.enabled;
|
|
887
|
+
const hasLocal = plugin.localScope?.enabled;
|
|
888
|
+
const hasAnyScope = hasUser || hasProject || hasLocal;
|
|
897
889
|
// Build version string
|
|
898
890
|
let versionStr = "";
|
|
899
891
|
if (plugin.isOrphaned) {
|
|
@@ -909,19 +901,18 @@ export function PluginsScreen() {
|
|
|
909
901
|
const matches = item._matches;
|
|
910
902
|
const segments = matches ? highlightMatches(plugin.name, matches) : null;
|
|
911
903
|
if (isSelected) {
|
|
912
|
-
const
|
|
913
|
-
return (
|
|
904
|
+
const scopeStr = `[${hasUser ? "u" : "."}${hasProject ? "p" : "."}${hasLocal ? "l" : "."}]`;
|
|
905
|
+
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", scopeStr, " ", plugin.name, versionStr, " "] }));
|
|
914
906
|
}
|
|
915
|
-
// For non-selected, render with colors
|
|
916
907
|
const displayName = segments
|
|
917
908
|
? segments.map((seg) => seg.text).join("")
|
|
918
909
|
: plugin.name;
|
|
919
910
|
if (plugin.isOrphaned) {
|
|
920
911
|
const ver = plugin.installedVersion && plugin.installedVersion !== "0.0.0"
|
|
921
912
|
? ` v${plugin.installedVersion}` : "";
|
|
922
|
-
return (_jsxs("text", { children: [
|
|
913
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: "red", children: " [x..] " }), _jsx("span", { fg: "gray", children: displayName }), ver && _jsx("span", { fg: "yellow", children: ver }), _jsx("span", { fg: "red", children: " deprecated" })] }));
|
|
923
914
|
}
|
|
924
|
-
return (_jsxs("text", { children: [
|
|
915
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: "#555555", children: " [" }), _jsx("span", { fg: hasUser ? "cyan" : "#555555", children: hasUser ? "u" : "." }), _jsx("span", { fg: hasProject ? "green" : "#555555", children: hasProject ? "p" : "." }), _jsx("span", { fg: hasLocal ? "yellow" : "#555555", children: hasLocal ? "l" : "." }), _jsx("span", { fg: "#555555", children: "]" }), _jsx("span", { children: " " }), _jsx("span", { fg: hasAnyScope ? "white" : "gray", children: displayName }), _jsx("span", { fg: plugin.hasUpdate ? "yellow" : "gray", children: versionStr })] }));
|
|
925
916
|
}
|
|
926
917
|
return _jsx("text", { fg: "gray", children: item.label });
|
|
927
918
|
};
|
|
@@ -6,6 +6,7 @@ import { ScreenLayout } from "../components/layout/index.js";
|
|
|
6
6
|
import { CategoryHeader } from "../components/CategoryHeader.js";
|
|
7
7
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
8
8
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
9
|
+
import { scopeIndicatorText } from "../components/ScopeIndicator.js";
|
|
9
10
|
import { fuzzyFilter, highlightMatches } from "../../utils/fuzzy-search.js";
|
|
10
11
|
import { getAllMarketplaces } from "../../data/marketplaces.js";
|
|
11
12
|
import {
|
|
@@ -171,15 +172,15 @@ export function PluginsScreen() {
|
|
|
171
172
|
if (communityPlugins.length > 0) {
|
|
172
173
|
const communityVirtualMp: Marketplace = {
|
|
173
174
|
name: COMMUNITY_VIRTUAL_MARKETPLACE,
|
|
174
|
-
displayName: "
|
|
175
|
+
displayName: "Anthropic Official — 3rd Party",
|
|
175
176
|
source: marketplace.source,
|
|
176
|
-
description: "Third-party plugins
|
|
177
|
+
description: "Third-party plugins in the Anthropic Official marketplace",
|
|
177
178
|
};
|
|
178
179
|
const communityCollapsed = collapsed.has(COMMUNITY_VIRTUAL_MARKETPLACE);
|
|
179
180
|
items.push({
|
|
180
181
|
id: `mp:${COMMUNITY_VIRTUAL_MARKETPLACE}`,
|
|
181
182
|
type: "category",
|
|
182
|
-
label: "
|
|
183
|
+
label: "Anthropic Official — 3rd Party",
|
|
183
184
|
marketplace: communityVirtualMp,
|
|
184
185
|
marketplaceEnabled: true,
|
|
185
186
|
pluginCount: communityPlugins.length,
|
|
@@ -897,19 +898,17 @@ export function PluginsScreen() {
|
|
|
897
898
|
latestVersion !== "0.0.0" &&
|
|
898
899
|
installedVersion !== latestVersion;
|
|
899
900
|
|
|
900
|
-
// Determine action
|
|
901
|
-
//
|
|
902
|
-
//
|
|
901
|
+
// Determine action for THIS scope:
|
|
902
|
+
// - installed in this scope + has update → update
|
|
903
|
+
// - installed in this scope → uninstall from this scope
|
|
904
|
+
// - not installed in this scope → install to this scope
|
|
903
905
|
let action: "update" | "install" | "uninstall";
|
|
904
906
|
if (isInstalledInScope && hasUpdateInScope) {
|
|
905
907
|
action = "update";
|
|
906
908
|
} else if (isInstalledInScope) {
|
|
907
909
|
action = "uninstall";
|
|
908
|
-
} else if (!isInstalledAnywhere) {
|
|
909
|
-
action = "install";
|
|
910
910
|
} else {
|
|
911
|
-
|
|
912
|
-
action = "uninstall";
|
|
911
|
+
action = "install";
|
|
913
912
|
}
|
|
914
913
|
|
|
915
914
|
const actionLabel =
|
|
@@ -1139,17 +1138,13 @@ export function PluginsScreen() {
|
|
|
1139
1138
|
|
|
1140
1139
|
if (item.type === "plugin" && item.plugin) {
|
|
1141
1140
|
const plugin = item.plugin;
|
|
1142
|
-
let statusIcon = "○";
|
|
1143
|
-
let statusColor = "gray";
|
|
1144
|
-
|
|
1145
1141
|
const isAnyScope = plugin.userScope?.enabled || plugin.projectScope?.enabled || plugin.localScope?.enabled;
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1142
|
+
|
|
1143
|
+
// Build scope parts for colored rendering
|
|
1144
|
+
const hasUser = plugin.userScope?.enabled;
|
|
1145
|
+
const hasProject = plugin.projectScope?.enabled;
|
|
1146
|
+
const hasLocal = plugin.localScope?.enabled;
|
|
1147
|
+
const hasAnyScope = hasUser || hasProject || hasLocal;
|
|
1153
1148
|
|
|
1154
1149
|
// Build version string
|
|
1155
1150
|
let versionStr = "";
|
|
@@ -1167,15 +1162,14 @@ export function PluginsScreen() {
|
|
|
1167
1162
|
const segments = matches ? highlightMatches(plugin.name, matches) : null;
|
|
1168
1163
|
|
|
1169
1164
|
if (isSelected) {
|
|
1170
|
-
const
|
|
1165
|
+
const scopeStr = `[${hasUser ? "u" : "."}${hasProject ? "p" : "."}${hasLocal ? "l" : "."}]`;
|
|
1171
1166
|
return (
|
|
1172
1167
|
<text bg="magenta" fg="white">
|
|
1173
|
-
{
|
|
1168
|
+
{" "}{scopeStr} {plugin.name}{versionStr}{" "}
|
|
1174
1169
|
</text>
|
|
1175
1170
|
);
|
|
1176
1171
|
}
|
|
1177
1172
|
|
|
1178
|
-
// For non-selected, render with colors
|
|
1179
1173
|
const displayName = segments
|
|
1180
1174
|
? segments.map((seg) => seg.text).join("")
|
|
1181
1175
|
: plugin.name;
|
|
@@ -1185,7 +1179,7 @@ export function PluginsScreen() {
|
|
|
1185
1179
|
? ` v${plugin.installedVersion}` : "";
|
|
1186
1180
|
return (
|
|
1187
1181
|
<text>
|
|
1188
|
-
<span fg="red">
|
|
1182
|
+
<span fg="red"> [x..] </span>
|
|
1189
1183
|
<span fg="gray">{displayName}</span>
|
|
1190
1184
|
{ver && <span fg="yellow">{ver}</span>}
|
|
1191
1185
|
<span fg="red"> deprecated</span>
|
|
@@ -1195,11 +1189,13 @@ export function PluginsScreen() {
|
|
|
1195
1189
|
|
|
1196
1190
|
return (
|
|
1197
1191
|
<text>
|
|
1198
|
-
<span fg=
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
</span>
|
|
1202
|
-
<span>
|
|
1192
|
+
<span fg="#555555"> [</span>
|
|
1193
|
+
<span fg={hasUser ? "cyan" : "#555555"}>{hasUser ? "u" : "."}</span>
|
|
1194
|
+
<span fg={hasProject ? "green" : "#555555"}>{hasProject ? "p" : "."}</span>
|
|
1195
|
+
<span fg={hasLocal ? "yellow" : "#555555"}>{hasLocal ? "l" : "."}</span>
|
|
1196
|
+
<span fg="#555555">]</span>
|
|
1197
|
+
<span> </span>
|
|
1198
|
+
<span fg={hasAnyScope ? "white" : "gray"}>{displayName}</span>
|
|
1203
1199
|
<span fg={plugin.hasUpdate ? "yellow" : "gray"}>{versionStr}</span>
|
|
1204
1200
|
</text>
|
|
1205
1201
|
);
|
|
@@ -229,21 +229,15 @@ export function SkillsScreen() {
|
|
|
229
229
|
try {
|
|
230
230
|
await installSkill(selectedSkill, scope, state.projectPath);
|
|
231
231
|
modal.hideModal();
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
name: selectedSkill.name,
|
|
235
|
-
updates: {
|
|
236
|
-
installed: true,
|
|
237
|
-
installedScope: scope,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
232
|
+
// Refetch to pick up install status for all skills including recommended
|
|
233
|
+
await fetchData();
|
|
240
234
|
await modal.message("Installed", `${selectedSkill.name} installed to ${scope === "user" ? "~/.claude/skills/" : ".claude/skills/"}`, "success");
|
|
241
235
|
}
|
|
242
236
|
catch (error) {
|
|
243
237
|
modal.hideModal();
|
|
244
238
|
await modal.message("Error", `Failed to install: ${error}`, "error");
|
|
245
239
|
}
|
|
246
|
-
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
240
|
+
}, [selectedSkill, state.projectPath, dispatch, modal, fetchData]);
|
|
247
241
|
// Uninstall handler
|
|
248
242
|
const handleUninstall = useCallback(async () => {
|
|
249
243
|
if (!selectedSkill || !selectedSkill.installed)
|
|
@@ -258,21 +252,15 @@ export function SkillsScreen() {
|
|
|
258
252
|
try {
|
|
259
253
|
await uninstallSkill(selectedSkill.name, scope, state.projectPath);
|
|
260
254
|
modal.hideModal();
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
name: selectedSkill.name,
|
|
264
|
-
updates: {
|
|
265
|
-
installed: false,
|
|
266
|
-
installedScope: null,
|
|
267
|
-
},
|
|
268
|
-
});
|
|
255
|
+
// Refetch to pick up uninstall status
|
|
256
|
+
await fetchData();
|
|
269
257
|
await modal.message("Uninstalled", `${selectedSkill.name} removed.`, "success");
|
|
270
258
|
}
|
|
271
259
|
catch (error) {
|
|
272
260
|
modal.hideModal();
|
|
273
261
|
await modal.message("Error", `Failed to uninstall: ${error}`, "error");
|
|
274
262
|
}
|
|
275
|
-
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
263
|
+
}, [selectedSkill, state.projectPath, dispatch, modal, fetchData]);
|
|
276
264
|
// Keyboard handling — same pattern as PluginsScreen
|
|
277
265
|
useKeyboard((event) => {
|
|
278
266
|
if (state.modal)
|
|
@@ -374,14 +362,15 @@ export function SkillsScreen() {
|
|
|
374
362
|
}
|
|
375
363
|
if (item.type === "skill" && item.skill) {
|
|
376
364
|
const skill = item.skill;
|
|
377
|
-
const indicator = skill.installed ? "●" : "○";
|
|
378
|
-
const indicatorColor = skill.installed ? "cyan" : "gray";
|
|
379
|
-
const scopeTag = skill.installedScope === "user" ? "u" : skill.installedScope === "project" ? "p" : "";
|
|
380
365
|
const starsStr = formatStars(skill.stars);
|
|
366
|
+
const hasUser = skill.installedScope === "user";
|
|
367
|
+
const hasProject = skill.installedScope === "project";
|
|
368
|
+
const nameColor = skill.installed ? "white" : "gray";
|
|
381
369
|
if (isSelected) {
|
|
382
|
-
|
|
370
|
+
const scopeStr = `[${hasUser ? "u" : "."}${hasProject ? "p" : "."}]`;
|
|
371
|
+
return (_jsxs("text", { bg: "magenta", fg: "white", children: [" ", scopeStr, " ", skill.name, skill.hasUpdate ? " ⬆" : "", starsStr ? ` ${starsStr}` : "", " "] }));
|
|
383
372
|
}
|
|
384
|
-
return (_jsxs("text", { children: [
|
|
373
|
+
return (_jsxs("text", { children: [_jsx("span", { fg: "#555555", children: " [" }), _jsx("span", { fg: hasUser ? "cyan" : "#555555", children: hasUser ? "u" : "." }), _jsx("span", { fg: hasProject ? "green" : "#555555", children: hasProject ? "p" : "." }), _jsx("span", { fg: "#555555", children: "] " }), _jsx("span", { fg: nameColor, children: skill.name }), skill.hasUpdate && _jsx("span", { fg: "yellow", children: " \u2B06" }), starsStr && (_jsxs("span", { fg: "yellow", children: [" ", starsStr] }))] }));
|
|
385
374
|
}
|
|
386
375
|
return _jsx("text", { fg: "gray", children: item.label });
|
|
387
376
|
};
|
|
@@ -5,6 +5,7 @@ import { useKeyboard } from "../hooks/useKeyboard.js";
|
|
|
5
5
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
6
6
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
7
7
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
8
|
+
import { scopeIndicatorText } from "../components/ScopeIndicator.js";
|
|
8
9
|
import {
|
|
9
10
|
fetchAvailableSkills,
|
|
10
11
|
fetchSkillFrontmatter,
|
|
@@ -271,14 +272,8 @@ export function SkillsScreen() {
|
|
|
271
272
|
try {
|
|
272
273
|
await installSkill(selectedSkill, scope, state.projectPath);
|
|
273
274
|
modal.hideModal();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
name: selectedSkill.name,
|
|
277
|
-
updates: {
|
|
278
|
-
installed: true,
|
|
279
|
-
installedScope: scope,
|
|
280
|
-
},
|
|
281
|
-
});
|
|
275
|
+
// Refetch to pick up install status for all skills including recommended
|
|
276
|
+
await fetchData();
|
|
282
277
|
await modal.message(
|
|
283
278
|
"Installed",
|
|
284
279
|
`${selectedSkill.name} installed to ${scope === "user" ? "~/.claude/skills/" : ".claude/skills/"}`,
|
|
@@ -288,7 +283,7 @@ export function SkillsScreen() {
|
|
|
288
283
|
modal.hideModal();
|
|
289
284
|
await modal.message("Error", `Failed to install: ${error}`, "error");
|
|
290
285
|
}
|
|
291
|
-
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
286
|
+
}, [selectedSkill, state.projectPath, dispatch, modal, fetchData]);
|
|
292
287
|
|
|
293
288
|
// Uninstall handler
|
|
294
289
|
const handleUninstall = useCallback(async () => {
|
|
@@ -307,20 +302,14 @@ export function SkillsScreen() {
|
|
|
307
302
|
try {
|
|
308
303
|
await uninstallSkill(selectedSkill.name, scope, state.projectPath);
|
|
309
304
|
modal.hideModal();
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
name: selectedSkill.name,
|
|
313
|
-
updates: {
|
|
314
|
-
installed: false,
|
|
315
|
-
installedScope: null,
|
|
316
|
-
},
|
|
317
|
-
});
|
|
305
|
+
// Refetch to pick up uninstall status
|
|
306
|
+
await fetchData();
|
|
318
307
|
await modal.message("Uninstalled", `${selectedSkill.name} removed.`, "success");
|
|
319
308
|
} catch (error) {
|
|
320
309
|
modal.hideModal();
|
|
321
310
|
await modal.message("Error", `Failed to uninstall: ${error}`, "error");
|
|
322
311
|
}
|
|
323
|
-
}, [selectedSkill, state.projectPath, dispatch, modal]);
|
|
312
|
+
}, [selectedSkill, state.projectPath, dispatch, modal, fetchData]);
|
|
324
313
|
|
|
325
314
|
// Keyboard handling — same pattern as PluginsScreen
|
|
326
315
|
useKeyboard((event) => {
|
|
@@ -437,27 +426,28 @@ export function SkillsScreen() {
|
|
|
437
426
|
|
|
438
427
|
if (item.type === "skill" && item.skill) {
|
|
439
428
|
const skill = item.skill;
|
|
440
|
-
const indicator = skill.installed ? "●" : "○";
|
|
441
|
-
const indicatorColor = skill.installed ? "cyan" : "gray";
|
|
442
|
-
const scopeTag = skill.installedScope === "user" ? "u" : skill.installedScope === "project" ? "p" : "";
|
|
443
429
|
const starsStr = formatStars(skill.stars);
|
|
430
|
+
const hasUser = skill.installedScope === "user";
|
|
431
|
+
const hasProject = skill.installedScope === "project";
|
|
432
|
+
const nameColor = skill.installed ? "white" : "gray";
|
|
444
433
|
|
|
445
434
|
if (isSelected) {
|
|
435
|
+
const scopeStr = `[${hasUser ? "u" : "."}${hasProject ? "p" : "."}]`;
|
|
446
436
|
return (
|
|
447
437
|
<text bg="magenta" fg="white">
|
|
448
|
-
{" "}{
|
|
438
|
+
{" "}{scopeStr} {skill.name}{skill.hasUpdate ? " ⬆" : ""}{starsStr ? ` ${starsStr}` : ""}{" "}
|
|
449
439
|
</text>
|
|
450
440
|
);
|
|
451
441
|
}
|
|
452
442
|
|
|
453
443
|
return (
|
|
454
444
|
<text>
|
|
455
|
-
<span fg=
|
|
456
|
-
<span fg="
|
|
445
|
+
<span fg="#555555"> [</span>
|
|
446
|
+
<span fg={hasUser ? "cyan" : "#555555"}>{hasUser ? "u" : "."}</span>
|
|
447
|
+
<span fg={hasProject ? "green" : "#555555"}>{hasProject ? "p" : "."}</span>
|
|
448
|
+
<span fg="#555555">] </span>
|
|
449
|
+
<span fg={nameColor}>{skill.name}</span>
|
|
457
450
|
{skill.hasUpdate && <span fg="yellow"> ⬆</span>}
|
|
458
|
-
{scopeTag && (
|
|
459
|
-
<span fg={scopeTag === "u" ? "cyan" : "green"}> [{scopeTag}]</span>
|
|
460
|
-
)}
|
|
461
451
|
{starsStr && (
|
|
462
452
|
<span fg="yellow">{" "}{starsStr}</span>
|
|
463
453
|
)}
|