claudeup 4.18.0 → 4.22.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.
Files changed (191) hide show
  1. package/bin/claudeup.js +43 -19
  2. package/package.json +14 -19
  3. package/scripts/build-binaries.ts +77 -0
  4. package/scripts/check-optional-deps.ts +49 -0
  5. package/scripts/test-isolated.ts +43 -0
  6. package/src/__tests__/alias-adopt.test.ts +364 -0
  7. package/src/__tests__/alias-parser.test.ts +92 -0
  8. package/src/__tests__/alias-shell-writer.test.ts +7 -0
  9. package/src/__tests__/alias-store.test.ts +77 -0
  10. package/src/__tests__/cli-router.test.ts +65 -0
  11. package/src/__tests__/conventions-integration.test.ts +745 -0
  12. package/src/__tests__/conventions-manager.test.ts +1172 -0
  13. package/src/__tests__/doctor-bins.test.ts +35 -0
  14. package/src/__tests__/doctor.test.ts +450 -0
  15. package/src/__tests__/dual-write-prevention.test.ts +14 -4
  16. package/src/__tests__/file-locking.test.ts +208 -0
  17. package/src/__tests__/gitignore-prerun.test.ts +1 -0
  18. package/src/__tests__/install-command.test.ts +81 -0
  19. package/src/__tests__/install-plan.test.ts +98 -0
  20. package/src/__tests__/manifest.test.ts +136 -0
  21. package/src/__tests__/marketplace-refresh.test.ts +267 -0
  22. package/src/__tests__/plugin-requires.test.ts +133 -0
  23. package/src/__tests__/plugin-setup.test.ts +118 -0
  24. package/src/__tests__/profile-command.test.ts +116 -0
  25. package/src/__tests__/profile-materializer.test.ts +82 -0
  26. package/src/__tests__/profile-sync.test.ts +136 -0
  27. package/src/__tests__/registry-version-resolution.test.ts +74 -0
  28. package/src/__tests__/resolve-executable.test.ts +42 -0
  29. package/src/__tests__/resolver.test.ts +218 -0
  30. package/src/__tests__/symlink-manager.test.ts +99 -0
  31. package/src/__tests__/toolchain.test.ts +56 -0
  32. package/src/cli/claude.ts +21 -0
  33. package/src/cli/doctor.ts +132 -0
  34. package/src/cli/install.ts +360 -0
  35. package/src/cli/profile.ts +166 -0
  36. package/src/cli/router.ts +107 -0
  37. package/src/cli/update.ts +85 -0
  38. package/src/data/alias-flags.ts +11 -1
  39. package/src/data/cli-tools.ts +7 -7
  40. package/src/data/gitignore-defaults.ts +4 -0
  41. package/src/data/marketplaces.ts +12 -0
  42. package/src/data/predefined-profiles.ts +3 -4
  43. package/src/main.tsx +11 -154
  44. package/src/prerunner/index.ts +62 -27
  45. package/src/services/alias-shell-writer.ts +382 -8
  46. package/src/services/alias-store.ts +60 -0
  47. package/src/services/claude-cli.ts +19 -22
  48. package/src/services/claude-settings.ts +99 -26
  49. package/src/services/conventions-manager.ts +865 -0
  50. package/src/services/doctor-bins.ts +49 -0
  51. package/src/services/doctor.ts +509 -0
  52. package/src/services/install-plan.ts +107 -0
  53. package/src/services/manifest.ts +166 -0
  54. package/src/services/marketplace-refresh.ts +173 -0
  55. package/src/services/plugin-manager.ts +7 -1
  56. package/src/services/plugin-requires.ts +215 -0
  57. package/src/services/plugin-setup.ts +61 -4
  58. package/src/services/plugin-version-check.ts +9 -4
  59. package/src/services/profile-materializer.ts +61 -0
  60. package/src/services/profile-sync.ts +150 -0
  61. package/src/services/resolver.ts +293 -0
  62. package/src/services/symlink-manager.ts +146 -0
  63. package/src/services/toolchain.ts +97 -0
  64. package/src/services/version-check.ts +10 -11
  65. package/src/types/index.ts +138 -31
  66. package/src/ui/App.tsx +16 -11
  67. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  68. package/src/ui/components/modals/ConfirmModal.tsx +1 -1
  69. package/src/ui/screens/AliasScreen.tsx +491 -359
  70. package/src/ui/screens/CliToolsScreen.tsx +11 -3
  71. package/src/ui/screens/PluginsScreen.tsx +3 -1
  72. package/src/ui/screens/index.ts +0 -1
  73. package/src/ui/state/reducer.ts +6 -102
  74. package/src/ui/state/types.ts +8 -35
  75. package/src/utils/command-utils.ts +23 -0
  76. package/src/utils/file-locking.ts +144 -0
  77. package/src/data/alias-flags.js +0 -196
  78. package/src/data/cli-tools.js +0 -123
  79. package/src/data/gitignore-defaults.js +0 -24
  80. package/src/data/gitignore-reasons.js +0 -97
  81. package/src/data/gitignore-templates.js +0 -21
  82. package/src/data/marketplaces.js +0 -138
  83. package/src/data/mcp-servers.js +0 -509
  84. package/src/data/predefined-profiles.js +0 -248
  85. package/src/data/settings-catalog.js +0 -625
  86. package/src/data/skill-repos.js +0 -160
  87. package/src/data/statuslines.js +0 -159
  88. package/src/data/statuslines.ts +0 -188
  89. package/src/index.js +0 -4
  90. package/src/index.ts +0 -5
  91. package/src/main.js +0 -170
  92. package/src/prerunner/index.js +0 -252
  93. package/src/services/alias-settings.js +0 -51
  94. package/src/services/alias-shell-writer.js +0 -764
  95. package/src/services/alias-store.js +0 -77
  96. package/src/services/claude-cli.js +0 -189
  97. package/src/services/claude-runner.js +0 -28
  98. package/src/services/claude-settings.js +0 -1223
  99. package/src/services/gitignore-detector.js +0 -155
  100. package/src/services/gitignore-fixer.js +0 -231
  101. package/src/services/gitignore-prerun.js +0 -46
  102. package/src/services/gitignore-resolver.js +0 -143
  103. package/src/services/gitignore-service.js +0 -117
  104. package/src/services/local-marketplace.js +0 -339
  105. package/src/services/marketplace-fetcher.js +0 -96
  106. package/src/services/marketplace-sync.js +0 -94
  107. package/src/services/mcp-registry.js +0 -87
  108. package/src/services/plugin-manager.js +0 -473
  109. package/src/services/plugin-mcp-config.js +0 -177
  110. package/src/services/plugin-setup.js +0 -444
  111. package/src/services/plugin-version-check.js +0 -262
  112. package/src/services/profiles.js +0 -161
  113. package/src/services/settings-manager.js +0 -243
  114. package/src/services/skills-manager.js +0 -393
  115. package/src/services/skillsmp-client.js +0 -87
  116. package/src/services/update-cache.js +0 -52
  117. package/src/services/version-check.js +0 -99
  118. package/src/types/gitignore.js +0 -6
  119. package/src/types/index.js +0 -1
  120. package/src/ui/App.js +0 -346
  121. package/src/ui/adapters/pluginsAdapter.js +0 -139
  122. package/src/ui/adapters/settingsAdapter.js +0 -111
  123. package/src/ui/adapters/skillsAdapter.js +0 -154
  124. package/src/ui/components/CategoryHeader.js +0 -9
  125. package/src/ui/components/EmptyFilterState.js +0 -4
  126. package/src/ui/components/ScopeIndicator.js +0 -30
  127. package/src/ui/components/ScrollableList.js +0 -36
  128. package/src/ui/components/SearchInput.js +0 -19
  129. package/src/ui/components/StyledText.js +0 -39
  130. package/src/ui/components/TabBar.js +0 -19
  131. package/src/ui/components/layout/FooterHints.js +0 -29
  132. package/src/ui/components/layout/Panel.js +0 -6
  133. package/src/ui/components/layout/ProgressBar.js +0 -14
  134. package/src/ui/components/layout/ScopeTabs.js +0 -6
  135. package/src/ui/components/layout/ScreenLayout.js +0 -16
  136. package/src/ui/components/layout/index.js +0 -5
  137. package/src/ui/components/modals/ConfirmModal.js +0 -14
  138. package/src/ui/components/modals/InputModal.js +0 -5
  139. package/src/ui/components/modals/LoadingModal.js +0 -17
  140. package/src/ui/components/modals/MessageModal.js +0 -16
  141. package/src/ui/components/modals/ModalContainer.js +0 -137
  142. package/src/ui/components/modals/SelectModal.js +0 -18
  143. package/src/ui/components/modals/VersionMismatchModal.js +0 -36
  144. package/src/ui/components/modals/index.js +0 -6
  145. package/src/ui/components/primitives/ActionHints.js +0 -13
  146. package/src/ui/components/primitives/DetailSection.js +0 -7
  147. package/src/ui/components/primitives/KeyValueLine.js +0 -8
  148. package/src/ui/components/primitives/ListCategoryRow.js +0 -8
  149. package/src/ui/components/primitives/MetaText.js +0 -8
  150. package/src/ui/components/primitives/ScopeDetail.js +0 -32
  151. package/src/ui/components/primitives/ScopeSquares.js +0 -11
  152. package/src/ui/components/primitives/SelectableRow.js +0 -5
  153. package/src/ui/components/primitives/index.js +0 -8
  154. package/src/ui/hooks/index.js +0 -4
  155. package/src/ui/hooks/useAsyncData.js +0 -77
  156. package/src/ui/hooks/useGitignoreModal.js +0 -74
  157. package/src/ui/hooks/useKeyboard.js +0 -13
  158. package/src/ui/hooks/useKeyboardHandler.js +0 -39
  159. package/src/ui/hooks/useMismatchModal.js +0 -77
  160. package/src/ui/registry.js +0 -1
  161. package/src/ui/renderers/cliToolRenderers.js +0 -54
  162. package/src/ui/renderers/gitignoreRenderers.js +0 -46
  163. package/src/ui/renderers/mcpRenderers.js +0 -26
  164. package/src/ui/renderers/pluginRenderers.js +0 -124
  165. package/src/ui/renderers/profileRenderers.js +0 -177
  166. package/src/ui/renderers/settingsRenderers.js +0 -73
  167. package/src/ui/renderers/skillRenderers.js +0 -138
  168. package/src/ui/screens/AliasScreen.js +0 -1008
  169. package/src/ui/screens/CliToolsScreen.js +0 -338
  170. package/src/ui/screens/EnvVarsScreen.js +0 -152
  171. package/src/ui/screens/GitignoreScreen.js +0 -328
  172. package/src/ui/screens/McpRegistryScreen.js +0 -238
  173. package/src/ui/screens/McpScreen.js +0 -176
  174. package/src/ui/screens/ModelSelectorScreen.js +0 -296
  175. package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
  176. package/src/ui/screens/PluginsScreen.js +0 -766
  177. package/src/ui/screens/ProfilesScreen.js +0 -298
  178. package/src/ui/screens/SkillsScreen.js +0 -549
  179. package/src/ui/screens/StatusLineScreen.js +0 -206
  180. package/src/ui/screens/StatusLineScreen.tsx +0 -416
  181. package/src/ui/screens/index.js +0 -10
  182. package/src/ui/state/AnimationContext.js +0 -34
  183. package/src/ui/state/AppContext.js +0 -162
  184. package/src/ui/state/DimensionsContext.js +0 -71
  185. package/src/ui/state/reducer.js +0 -543
  186. package/src/ui/state/types.js +0 -1
  187. package/src/ui/theme.js +0 -47
  188. package/src/utils/clipboard.js +0 -56
  189. package/src/utils/command-utils.js +0 -19
  190. package/src/utils/fuzzy-search.js +0 -101
  191. package/src/utils/string-utils.js +0 -62
@@ -1,393 +0,0 @@
1
- import fs from "fs-extra";
2
- import path from "node:path";
3
- import os from "node:os";
4
- import { RECOMMENDED_SKILLS, classifyStarReliability } from "../data/skill-repos.js";
5
- const SKILLS_API_BASE = "https://us-central1-claudish-6da10.cloudfunctions.net/skills";
6
- const treeCache = new Map();
7
- // ─── Path helpers ──────────────────────────────────────────────────────────────
8
- export function getUserSkillsDir() {
9
- return path.join(os.homedir(), ".claude", "skills");
10
- }
11
- export function getProjectSkillsDir(projectPath) {
12
- return path.join(projectPath ?? process.cwd(), ".claude", "skills");
13
- }
14
- // ─── GitHub Tree API ──────────────────────────────────────────────────────────
15
- async function fetchGitTree(repo) {
16
- const cached = treeCache.get(repo);
17
- const url = `https://api.github.com/repos/${repo}/git/trees/HEAD?recursive=1`;
18
- const headers = {
19
- Accept: "application/vnd.github+json",
20
- "X-GitHub-Api-Version": "2022-11-28",
21
- };
22
- if (cached?.etag) {
23
- headers["If-None-Match"] = cached.etag;
24
- }
25
- const token = process.env.GITHUB_TOKEN || process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
26
- if (token) {
27
- headers.Authorization = `Bearer ${token}`;
28
- }
29
- const response = await fetch(url, {
30
- headers,
31
- signal: AbortSignal.timeout(10000),
32
- });
33
- if (response.status === 304 && cached) {
34
- return cached.tree;
35
- }
36
- if (response.status === 403 || response.status === 429) {
37
- const resetHeader = response.headers.get("X-RateLimit-Reset");
38
- const resetTime = resetHeader
39
- ? new Date(Number(resetHeader) * 1000).toLocaleTimeString()
40
- : "unknown";
41
- throw new Error(`GitHub API rate limit exceeded. Resets at ${resetTime}. Set GITHUB_TOKEN to increase limits.`);
42
- }
43
- if (!response.ok) {
44
- throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
45
- }
46
- const etag = response.headers.get("ETag") || "";
47
- const tree = (await response.json());
48
- treeCache.set(repo, { etag, tree, fetchedAt: Date.now() });
49
- return tree;
50
- }
51
- // ─── Skill Set fetching ──────────────────────────────────────────────────────
52
- /**
53
- * Fetch all skills from a skill set repo using the GitHub Tree API.
54
- * Returns SkillInfo[] with installed status marked via disk scan.
55
- */
56
- export async function fetchSkillSetSkills(repo, projectPath) {
57
- const tree = await fetchGitTree(repo);
58
- // Find all SKILL.md blobs
59
- const skillBlobs = tree.tree.filter((entry) => entry.type === "blob" && entry.path.endsWith("/SKILL.md"));
60
- const userInstalled = await getInstalledSkillNames("user");
61
- const projectInstalled = await getInstalledSkillNames("project", projectPath);
62
- const slugify = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
63
- const source = {
64
- label: repo,
65
- repo,
66
- skillsPath: "",
67
- };
68
- return skillBlobs.map((blob) => {
69
- // Derive skill name from parent directory of SKILL.md
70
- const parts = blob.path.split("/");
71
- const name = parts[parts.length - 2]; // e.g. "huggingface-datasets"
72
- const repoPath = blob.path; // e.g. "skills/huggingface-datasets/SKILL.md"
73
- const slug = slugify(name);
74
- const isUser = userInstalled.has(slug) || userInstalled.has(name);
75
- const isProj = projectInstalled.has(slug) || projectInstalled.has(name);
76
- const installed = isUser || isProj;
77
- const installedScope = isProj
78
- ? "project"
79
- : isUser
80
- ? "user"
81
- : null;
82
- return {
83
- id: `${repo}/${blob.path.replace("/SKILL.md", "")}`,
84
- name,
85
- source,
86
- repoPath,
87
- gitBlobSha: blob.sha,
88
- frontmatter: null,
89
- installed,
90
- installedScope,
91
- hasUpdate: false,
92
- description: "",
93
- };
94
- });
95
- }
96
- // ─── Frontmatter parser ───────────────────────────────────────────────────────
97
- function parseYamlFrontmatter(content) {
98
- const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
99
- if (!frontmatterMatch)
100
- return {};
101
- const yaml = frontmatterMatch[1];
102
- const result = {};
103
- for (const line of yaml.split("\n")) {
104
- const colonIdx = line.indexOf(":");
105
- if (colonIdx === -1)
106
- continue;
107
- const key = line.slice(0, colonIdx).trim();
108
- const rawValue = line.slice(colonIdx + 1).trim();
109
- if (!key)
110
- continue;
111
- // Handle arrays (simple inline: [a, b, c])
112
- if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
113
- const items = rawValue
114
- .slice(1, -1)
115
- .split(",")
116
- .map((s) => s.trim().replace(/^["']|["']$/g, ""))
117
- .filter(Boolean);
118
- result[key] = items;
119
- }
120
- else {
121
- // Strip quotes
122
- result[key] = rawValue.replace(/^["']|["']$/g, "");
123
- }
124
- }
125
- return result;
126
- }
127
- export async function fetchSkillFrontmatter(skill) {
128
- const url = `https://raw.githubusercontent.com/${skill.source.repo}/HEAD/${skill.repoPath}`;
129
- const response = await fetch(url, {
130
- signal: AbortSignal.timeout(10000),
131
- });
132
- if (!response.ok) {
133
- return {
134
- name: skill.name,
135
- description: "(no description)",
136
- category: "general",
137
- };
138
- }
139
- const content = await response.text();
140
- const parsed = parseYamlFrontmatter(content);
141
- return {
142
- name: parsed.name || skill.name,
143
- description: parsed.description || "(no description)",
144
- category: parsed.category,
145
- author: parsed.author,
146
- version: parsed.version,
147
- tags: parsed.tags,
148
- };
149
- }
150
- // ─── Check installation ───────────────────────────────────────────────────────
151
- export async function getInstalledSkillNames(scope, projectPath) {
152
- const dir = scope === "user"
153
- ? getUserSkillsDir()
154
- : getProjectSkillsDir(projectPath);
155
- const installed = new Set();
156
- try {
157
- if (!(await fs.pathExists(dir)))
158
- return installed;
159
- const entries = await fs.readdir(dir);
160
- for (const entry of entries) {
161
- const skillMd = path.join(dir, entry, "SKILL.md");
162
- if (await fs.pathExists(skillMd)) {
163
- installed.add(entry);
164
- }
165
- }
166
- }
167
- catch {
168
- // ignore
169
- }
170
- return installed;
171
- }
172
- // ─── Firebase Skills API ──────────────────────────────────────────────────────
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
- function mapApiSkillToSkillInfo(raw) {
175
- const repo = raw.repo || "unknown";
176
- const skillPath = raw.skillPath || raw.name || "";
177
- const source = {
178
- label: repo,
179
- repo,
180
- skillsPath: "",
181
- };
182
- const stars = typeof raw.stars === "number" ? raw.stars : undefined;
183
- return {
184
- id: `${repo}/${skillPath}`,
185
- name: raw.name || skillPath,
186
- description: raw.description || "",
187
- source,
188
- repoPath: skillPath ? `${skillPath}/SKILL.md` : "SKILL.md",
189
- gitBlobSha: "",
190
- frontmatter: null,
191
- installed: false,
192
- installedScope: null,
193
- hasUpdate: false,
194
- stars,
195
- starReliability: classifyStarReliability(repo, stars),
196
- };
197
- }
198
- export async function fetchPopularSkills(limit = 30) {
199
- try {
200
- const res = await fetch(`${SKILLS_API_BASE}/search?q=development&limit=${limit}&sortBy=stars`, { signal: AbortSignal.timeout(10000) });
201
- if (!res.ok)
202
- return [];
203
- const data = (await res.json());
204
- return (data.skills || []).map(mapApiSkillToSkillInfo);
205
- }
206
- catch {
207
- return [];
208
- }
209
- }
210
- // ─── Fetch available skills ───────────────────────────────────────────────────
211
- export async function fetchAvailableSkills(_repos, projectPath) {
212
- const userInstalled = await getInstalledSkillNames("user");
213
- const projectInstalled = await getInstalledSkillNames("project", projectPath);
214
- const slugify = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
215
- const markInstalled = (skill) => {
216
- const slug = slugify(skill.name);
217
- const isUserInstalled = userInstalled.has(slug) || userInstalled.has(skill.name);
218
- const isProjInstalled = projectInstalled.has(slug) || projectInstalled.has(skill.name);
219
- const installed = isUserInstalled || isProjInstalled;
220
- const installedScope = isProjInstalled
221
- ? "project"
222
- : isUserInstalled
223
- ? "user"
224
- : null;
225
- return { ...skill, installed, installedScope };
226
- };
227
- // 1. Recommended skills from RECOMMENDED_SKILLS constant (no API call)
228
- const recommendedSkills = RECOMMENDED_SKILLS.map((rec) => {
229
- const source = {
230
- label: rec.repo,
231
- repo: rec.repo,
232
- skillsPath: "",
233
- };
234
- const skill = {
235
- id: `${rec.repo}/${rec.skillPath}`,
236
- name: rec.name,
237
- description: rec.description,
238
- source,
239
- repoPath: `${rec.skillPath}/SKILL.md`,
240
- gitBlobSha: "",
241
- frontmatter: null,
242
- installed: false,
243
- installedScope: null,
244
- hasUpdate: false,
245
- isRecommended: true,
246
- starReliability: classifyStarReliability(rec.repo, rec.stars),
247
- };
248
- return markInstalled(skill);
249
- });
250
- // 2. Fetch popular skills from Firebase API
251
- const popular = await fetchPopularSkills(30);
252
- const popularSkills = popular.map((s) => markInstalled({ ...s, isRecommended: false }));
253
- // 3. Enrich recommended skills with GitHub repo stars (cached to disk)
254
- const starsCachePath = path.join(os.homedir(), ".claude", "skill-stars-cache.json");
255
- let starsCache = {};
256
- try {
257
- starsCache = await fs.readJson(starsCachePath);
258
- }
259
- catch { /* no cache yet */ }
260
- const uniqueRepos = [...new Set(recommendedSkills.map((s) => s.source.repo))];
261
- const repoStars = new Map();
262
- const cacheMaxAge = 24 * 60 * 60 * 1000; // 24 hours
263
- let cacheUpdated = false;
264
- for (const repo of uniqueRepos) {
265
- const cached = starsCache[repo];
266
- if (cached && Date.now() - new Date(cached.fetchedAt).getTime() < cacheMaxAge) {
267
- repoStars.set(repo, cached.stars);
268
- continue;
269
- }
270
- // Try fetching from GitHub (may be rate limited)
271
- try {
272
- const res = await fetch(`https://api.github.com/repos/${repo}`, {
273
- headers: { Accept: "application/vnd.github+json" },
274
- signal: AbortSignal.timeout(5000),
275
- });
276
- if (res.ok) {
277
- const data = (await res.json());
278
- if (data.stargazers_count) {
279
- repoStars.set(repo, data.stargazers_count);
280
- starsCache[repo] = { stars: data.stargazers_count, fetchedAt: new Date().toISOString() };
281
- cacheUpdated = true;
282
- }
283
- }
284
- else if (cached) {
285
- // Rate limited but have stale cache — use it
286
- repoStars.set(repo, cached.stars);
287
- }
288
- }
289
- catch {
290
- if (cached)
291
- repoStars.set(repo, cached.stars);
292
- }
293
- }
294
- if (cacheUpdated) {
295
- try {
296
- await fs.writeJson(starsCachePath, starsCache);
297
- }
298
- catch { /* ignore */ }
299
- }
300
- for (const rec of recommendedSkills) {
301
- rec.stars = repoStars.get(rec.source.repo) || rec.stars || undefined;
302
- }
303
- // 4. Combine: recommended first, then popular (dedup by name)
304
- const seen = new Set(recommendedSkills.map((s) => s.name));
305
- const deduped = popularSkills.filter((s) => !seen.has(s.name));
306
- return [...recommendedSkills, ...deduped];
307
- }
308
- // ─── Install / Uninstall ──────────────────────────────────────────────────────
309
- export async function installSkill(skill, scope, projectPath) {
310
- // Try multiple URL patterns — repos structure SKILL.md differently
311
- const repo = skill.source.repo;
312
- const repoPath = skill.repoPath.replace(/\/SKILL\.md$/, "");
313
- const candidates = [
314
- `https://raw.githubusercontent.com/${repo}/HEAD/${repoPath}/SKILL.md`,
315
- `https://raw.githubusercontent.com/${repo}/main/${repoPath}/SKILL.md`,
316
- `https://raw.githubusercontent.com/${repo}/master/${repoPath}/SKILL.md`,
317
- `https://raw.githubusercontent.com/${repo}/HEAD/SKILL.md`,
318
- `https://raw.githubusercontent.com/${repo}/main/SKILL.md`,
319
- ];
320
- let content = null;
321
- for (const url of candidates) {
322
- try {
323
- const response = await fetch(url, { signal: AbortSignal.timeout(8000) });
324
- if (response.ok) {
325
- content = await response.text();
326
- break;
327
- }
328
- }
329
- catch {
330
- continue;
331
- }
332
- }
333
- if (!content) {
334
- throw new Error(`Failed to fetch skill: SKILL.md not found in ${repo}/${repoPath}`);
335
- }
336
- // Use slug for directory name — display names can have slashes/spaces
337
- const dirName = skill.name
338
- .toLowerCase()
339
- .replace(/[^a-z0-9]+/g, "-")
340
- .replace(/^-|-$/g, "");
341
- const installDir = scope === "user"
342
- ? path.join(getUserSkillsDir(), dirName)
343
- : path.join(getProjectSkillsDir(projectPath), dirName);
344
- await fs.ensureDir(installDir);
345
- await fs.writeFile(path.join(installDir, "SKILL.md"), content, "utf8");
346
- }
347
- export async function uninstallSkill(skillName, scope, projectPath) {
348
- const dirName = skillName
349
- .toLowerCase()
350
- .replace(/[^a-z0-9]+/g, "-")
351
- .replace(/^-|-$/g, "");
352
- const installDir = scope === "user"
353
- ? path.join(getUserSkillsDir(), dirName)
354
- : path.join(getProjectSkillsDir(projectPath), dirName);
355
- const skillMdPath = path.join(installDir, "SKILL.md");
356
- if (await fs.pathExists(skillMdPath)) {
357
- await fs.remove(skillMdPath);
358
- }
359
- // Try to remove directory if empty
360
- try {
361
- await fs.rmdir(installDir);
362
- }
363
- catch {
364
- // Ignore if not empty or doesn't exist
365
- }
366
- }
367
- // ─── Check installed skills from file system ──────────────────────────────────
368
- export async function getInstalledSkillsFromFs(projectPath) {
369
- const result = [];
370
- const userDir = getUserSkillsDir();
371
- const projDir = getProjectSkillsDir(projectPath);
372
- const scopedDirs = [
373
- [userDir, "user"],
374
- [projDir, "project"],
375
- ];
376
- for (const [dir, scope] of scopedDirs) {
377
- try {
378
- if (!(await fs.pathExists(dir)))
379
- continue;
380
- const entries = await fs.readdir(dir);
381
- for (const entry of entries) {
382
- const skillMd = path.join(dir, entry, "SKILL.md");
383
- if (await fs.pathExists(skillMd)) {
384
- result.push({ name: entry, scope });
385
- }
386
- }
387
- }
388
- catch {
389
- // ignore
390
- }
391
- }
392
- return result;
393
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * Skills API client — calls our Firebase aggregator function
3
- * instead of hitting SkillsMP/GitHub APIs directly.
4
- *
5
- * The Firebase function handles:
6
- * - SkillsMP keyword + AI search (with server-side API key)
7
- * - GitHub Tree API with ETag caching
8
- * - Recommended skills list
9
- */
10
- // Deployed `skills` Gen2 Cloud Function (project claudish-6da10, us-central1).
11
- // Firebase keeps this cloudfunctions.net alias stable for the function.
12
- const SKILLS_API_BASE = process.env.SKILLS_API_URL || "https://us-central1-claudish-6da10.cloudfunctions.net/skills";
13
- /**
14
- * Thrown when the skills API is unreachable or returns a non-OK status.
15
- * Lets callers distinguish "search service is down" from "no skills matched",
16
- * which a bare `[]` return cannot express.
17
- */
18
- export class SkillsApiError extends Error {
19
- status;
20
- constructor(message, status) {
21
- super(message);
22
- this.status = status;
23
- this.name = "SkillsApiError";
24
- }
25
- }
26
- /**
27
- * Search skills across all sources (SkillsMP keyword + AI search).
28
- *
29
- * Throws {@link SkillsApiError} on transport/HTTP failure so the caller can
30
- * surface a distinct "search unavailable" state. An empty array means the
31
- * query genuinely matched nothing.
32
- */
33
- export async function searchSkills(query, options) {
34
- const params = new URLSearchParams({ q: query });
35
- if (options?.page)
36
- params.set("page", String(options.page));
37
- if (options?.limit)
38
- params.set("limit", String(options.limit));
39
- let res;
40
- try {
41
- res = await fetch(`${SKILLS_API_BASE}/search?${params}`, {
42
- signal: AbortSignal.timeout(12000),
43
- });
44
- }
45
- catch (cause) {
46
- throw new SkillsApiError(`Skills search request failed: ${cause instanceof Error ? cause.message : String(cause)}`);
47
- }
48
- if (!res.ok) {
49
- throw new SkillsApiError(`Skills search returned HTTP ${res.status}`, res.status);
50
- }
51
- const data = (await res.json());
52
- return data.skills || [];
53
- }
54
- /**
55
- * Fetch skills from a specific GitHub repo (via our proxy)
56
- */
57
- export async function fetchRepoSkills(owner, repo) {
58
- try {
59
- const res = await fetch(`${SKILLS_API_BASE}/repo/${owner}/${repo}`, {
60
- signal: AbortSignal.timeout(10000),
61
- });
62
- if (!res.ok)
63
- return [];
64
- const data = (await res.json());
65
- return data.skills || [];
66
- }
67
- catch {
68
- return [];
69
- }
70
- }
71
- /**
72
- * Get recommended skills list (curated, server-side)
73
- */
74
- export async function fetchRecommendedSkills() {
75
- try {
76
- const res = await fetch(`${SKILLS_API_BASE}/recommended`, {
77
- signal: AbortSignal.timeout(5000),
78
- });
79
- if (!res.ok)
80
- return [];
81
- const data = (await res.json());
82
- return data.skills || [];
83
- }
84
- catch {
85
- return [];
86
- }
87
- }
@@ -1,52 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import path from "node:path";
3
- import os from "node:os";
4
- const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
5
- export class UpdateCache {
6
- cachePath;
7
- constructor() {
8
- // Cache at ~/.claude/claudeup-cache.json
9
- this.cachePath = path.join(os.homedir(), ".claude", "claudeup-cache.json");
10
- }
11
- async shouldCheckForUpdates() {
12
- const data = await this.getLastCheck();
13
- if (!data)
14
- return true; // No cache
15
- const lastCheck = new Date(data.lastUpdateCheck);
16
- const now = new Date();
17
- const elapsed = now.getTime() - lastCheck.getTime();
18
- return elapsed >= CACHE_TTL_MS;
19
- }
20
- async getLastCheck() {
21
- try {
22
- const content = await fs.readFile(this.cachePath, "utf-8");
23
- const data = JSON.parse(content);
24
- return data;
25
- }
26
- catch (error) {
27
- // Cache doesn't exist or is corrupted
28
- return null;
29
- }
30
- }
31
- async saveCheck(result) {
32
- try {
33
- // Ensure .claude directory exists
34
- const cacheDir = path.dirname(this.cachePath);
35
- await fs.mkdir(cacheDir, { recursive: true });
36
- // Write cache file
37
- await fs.writeFile(this.cachePath, JSON.stringify(result, null, 2), "utf-8");
38
- }
39
- catch (error) {
40
- // Non-fatal - just log and continue
41
- console.warn("Warning: Failed to save update cache:", error instanceof Error ? error.message : "Unknown error");
42
- }
43
- }
44
- async clear() {
45
- try {
46
- await fs.unlink(this.cachePath);
47
- }
48
- catch (error) {
49
- // Ignore errors (file might not exist)
50
- }
51
- }
52
- }
@@ -1,99 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import semver from "semver";
3
- const require = createRequire(import.meta.url);
4
- // Cache the result for the session
5
- let cachedResult = null;
6
- /**
7
- * Get the current version from package.json
8
- */
9
- export function getCurrentVersion() {
10
- try {
11
- const pkg = require("../../package.json");
12
- return pkg.version;
13
- }
14
- catch {
15
- return "0.0.0";
16
- }
17
- }
18
- /**
19
- * Fetch the latest version from npm registry
20
- * Uses a short timeout to avoid blocking startup
21
- */
22
- async function fetchLatestVersion() {
23
- try {
24
- const controller = new AbortController();
25
- const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout
26
- const response = await fetch("https://registry.npmjs.org/claudeup/latest", {
27
- signal: controller.signal,
28
- headers: {
29
- Accept: "application/json",
30
- },
31
- });
32
- clearTimeout(timeout);
33
- if (!response.ok) {
34
- return null;
35
- }
36
- const data = (await response.json());
37
- return data.version || null;
38
- }
39
- catch {
40
- // Network error, timeout, or parse error - silently fail
41
- return null;
42
- }
43
- }
44
- /**
45
- * Determine the type of update (major, minor, patch)
46
- */
47
- function getUpdateType(current, latest) {
48
- const currentParsed = semver.parse(current);
49
- const latestParsed = semver.parse(latest);
50
- if (!currentParsed || !latestParsed)
51
- return undefined;
52
- if (latestParsed.major > currentParsed.major)
53
- return "major";
54
- if (latestParsed.minor > currentParsed.minor)
55
- return "minor";
56
- if (latestParsed.patch > currentParsed.patch)
57
- return "patch";
58
- return undefined;
59
- }
60
- /**
61
- * Check if a new version is available
62
- * Returns cached result if already checked this session
63
- */
64
- export async function checkForUpdates() {
65
- // Return cached result if available
66
- if (cachedResult) {
67
- return cachedResult;
68
- }
69
- const currentVersion = getCurrentVersion();
70
- const latestVersion = await fetchLatestVersion();
71
- if (!latestVersion) {
72
- // Couldn't fetch, assume no update
73
- cachedResult = {
74
- currentVersion,
75
- latestVersion: currentVersion,
76
- updateAvailable: false,
77
- };
78
- return cachedResult;
79
- }
80
- const updateAvailable = semver.gt(latestVersion, currentVersion);
81
- cachedResult = {
82
- currentVersion,
83
- latestVersion,
84
- updateAvailable,
85
- updateType: updateAvailable
86
- ? getUpdateType(currentVersion, latestVersion)
87
- : undefined,
88
- };
89
- return cachedResult;
90
- }
91
- /**
92
- * Format update message for display
93
- */
94
- export function formatUpdateMessage(result) {
95
- if (!result.updateAvailable)
96
- return "";
97
- const typeLabel = result.updateType ? ` (${result.updateType})` : "";
98
- return `Update available: v${result.currentVersion} → v${result.latestVersion}${typeLabel}. Run: npm i -g claudeup`;
99
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Types for the gitignore manager: a three-tier (built-in → global → project)
3
- * manifest system that declares which paths must be ignored and which must
4
- * stay tracked. Last-wins on conflict across tiers; intra-tier conflicts throw.
5
- */
6
- export {};
@@ -1 +0,0 @@
1
- export {};