@salesforce/b2c-tooling-sdk 1.5.0 → 1.6.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.
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/skills/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/skills/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SkillSet, SkillSourceConfig } from './types.js';
2
+ export declare const SKILL_SOURCES: Record<SkillSet, SkillSourceConfig>;
3
+ export declare function getSkillSource(skillSet: SkillSet): SkillSourceConfig;
4
+ export declare const ALL_SKILL_SETS: SkillSet[];
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright (c) 2025, Salesforce, Inc.
3
+ * SPDX-License-Identifier: Apache-2
4
+ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
+ */
6
+ function pluginsTag(version) {
7
+ const bare = version.replace(/^v/, '');
8
+ return `b2c-agent-plugins@${bare}`;
9
+ }
10
+ export const SKILL_SOURCES = {
11
+ b2c: {
12
+ id: 'b2c',
13
+ displayName: 'B2C Commerce development patterns',
14
+ type: 'release-artifact',
15
+ repo: 'SalesforceCommerceCloud/b2c-developer-tooling',
16
+ assetName: 'b2c-skills.zip',
17
+ tagPattern: pluginsTag,
18
+ },
19
+ 'b2c-cli': {
20
+ id: 'b2c-cli',
21
+ displayName: 'B2C CLI commands and operations',
22
+ type: 'release-artifact',
23
+ repo: 'SalesforceCommerceCloud/b2c-developer-tooling',
24
+ assetName: 'b2c-cli-skills.zip',
25
+ tagPattern: pluginsTag,
26
+ },
27
+ 'cap-dev': {
28
+ id: 'cap-dev',
29
+ displayName: 'Commerce Apps development skills',
30
+ type: 'repo-contents',
31
+ repo: 'SalesforceCommerceCloud/commerce-apps',
32
+ ref: 'main',
33
+ skillsPath: '.claude/skills',
34
+ },
35
+ };
36
+ export function getSkillSource(skillSet) {
37
+ const source = SKILL_SOURCES[skillSet];
38
+ if (!source) {
39
+ throw new Error(`Unknown skill set: ${skillSet}`);
40
+ }
41
+ return source;
42
+ }
43
+ export const ALL_SKILL_SETS = Object.keys(SKILL_SOURCES);
44
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../src/skills/sources.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,SAAS,UAAU,CAAC,OAAe;IACjC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO,qBAAqB,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAwC;IAChE,GAAG,EAAE;QACH,EAAE,EAAE,KAAK;QACT,WAAW,EAAE,mCAAmC;QAChD,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,+CAA+C;QACrD,SAAS,EAAE,gBAAgB;QAC3B,UAAU,EAAE,UAAU;KACvB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,SAAS;QACb,WAAW,EAAE,iCAAiC;QAC9C,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,+CAA+C;QACrD,SAAS,EAAE,oBAAoB;QAC/B,UAAU,EAAE,UAAU;KACvB;IACD,SAAS,EAAE;QACT,EAAE,EAAE,SAAS;QACb,WAAW,EAAE,kCAAkC;QAC/C,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,uCAAuC;QAC7C,GAAG,EAAE,MAAM;QACX,UAAU,EAAE,gBAAgB;KAC7B;CACF,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAe,MAAM,CAAC,IAAI,CAAC,aAAa,CAAe,CAAC"}
@@ -5,7 +5,20 @@ export type IdeType = 'claude-code' | 'cursor' | 'windsurf' | 'vscode' | 'codex'
5
5
  /**
6
6
  * Skill set categories matching the plugins directory structure.
7
7
  */
8
- export type SkillSet = 'b2c' | 'b2c-cli';
8
+ export type SkillSet = 'b2c' | 'b2c-cli' | 'cap-dev';
9
+ /**
10
+ * Configuration for a skill source — defines how to fetch skills from a particular repository.
11
+ */
12
+ export interface SkillSourceConfig {
13
+ id: SkillSet;
14
+ displayName: string;
15
+ type: 'release-artifact' | 'repo-contents';
16
+ repo: string;
17
+ assetName?: string;
18
+ tagPattern?: (version: string) => string;
19
+ ref?: string;
20
+ skillsPath?: string;
21
+ }
9
22
  /**
10
23
  * IDE path configuration for skill installation.
11
24
  */
@@ -133,4 +146,6 @@ export interface CachedArtifact {
133
146
  path: string;
134
147
  /** ISO date string when artifact was downloaded */
135
148
  downloadedAt: string;
149
+ /** For repo-contents sources: the resolved commit SHA */
150
+ commitSha?: string;
136
151
  }
@@ -2,46 +2,30 @@ import type { CachedArtifact, DownloadSkillsOptions, ReleaseInfo, SkillSet } fro
2
2
  /**
3
3
  * Get the cache directory for skills.
4
4
  * Uses XDG_CACHE_HOME on Linux, ~/.cache otherwise.
5
- *
6
- * @returns Absolute path to cache directory
7
5
  */
8
6
  export declare function getCacheDir(): string;
9
7
  /**
10
8
  * Fetch release information from GitHub API.
11
- *
12
- * @param version - 'latest' or specific version (e.g., 'v0.1.0')
13
- * @returns Release information
14
- * @throws Error if release not found or API request fails
15
9
  */
16
10
  export declare function getRelease(version?: string): Promise<ReleaseInfo>;
17
11
  /**
18
12
  * List available releases with skills artifacts.
19
- *
20
- * @param limit - Maximum number of releases to return (default: 10)
21
- * @returns Array of release information
22
13
  */
23
14
  export declare function listReleases(limit?: number): Promise<ReleaseInfo[]>;
24
15
  /**
25
16
  * Get cached artifact metadata if available.
26
- *
27
- * @param version - Release version
28
- * @param skillSet - Skill set to check
29
- * @returns Cached artifact info or null if not cached
30
17
  */
31
18
  export declare function getCachedArtifact(version: string, skillSet: SkillSet): CachedArtifact | null;
32
19
  /**
33
20
  * Download and extract skills artifact.
34
- * Uses direct download URLs to avoid GitHub API rate limits.
21
+ * Dispatches to the appropriate download strategy based on the skill source type.
35
22
  *
36
- * @param skillSet - Which skill set to download ('b2c' or 'b2c-cli')
23
+ * @param skillSet - Which skill set to download
37
24
  * @param options - Download options
38
25
  * @returns Path to extracted skills directory
39
- * @throws Error if download fails or artifact not available
40
26
  */
41
27
  export declare function downloadSkillsArtifact(skillSet: SkillSet, options?: DownloadSkillsOptions): Promise<string>;
42
28
  /**
43
29
  * Clear the skills cache.
44
- *
45
- * @param version - Optional specific version to clear (default: all)
46
30
  */
47
31
  export declare function clearCache(version?: string): Promise<void>;
@@ -3,46 +3,25 @@
3
3
  * SPDX-License-Identifier: Apache-2
4
4
  * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
5
5
  */
6
+ import { execFile } from 'node:child_process';
6
7
  import * as fs from 'node:fs';
7
8
  import * as os from 'node:os';
8
9
  import * as path from 'node:path';
10
+ import { promisify } from 'node:util';
9
11
  import JSZip from 'jszip';
12
+ import { getSkillSource } from './sources.js';
10
13
  import { getLogger } from '../logging/logger.js';
11
- const GITHUB_REPO = 'SalesforceCommerceCloud/b2c-developer-tooling';
14
+ const execFileAsync = promisify(execFile);
12
15
  const GITHUB_API_BASE = 'https://api.github.com';
13
16
  const GITHUB_DOWNLOAD_BASE = 'https://github.com';
14
17
  const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com';
15
18
  const LATEST_VERSION_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
16
- /**
17
- * Asset filename patterns for skill archives.
18
- */
19
- const ASSET_NAMES = {
20
- b2c: 'b2c-skills.zip',
21
- 'b2c-cli': 'b2c-cli-skills.zip',
22
- };
23
- /**
24
- * Build a direct CDN download URL for a release asset.
25
- * Assumes a concrete tag — call `resolveLatestVersion()` first if you have 'latest'.
26
- */
27
- function buildDownloadUrl(tag, assetName) {
28
- return `${GITHUB_DOWNLOAD_BASE}/${GITHUB_REPO}/releases/download/${tag}/${assetName}`;
29
- }
30
- /**
31
- * Tag name used for skill-only releases (matches publish.yml).
32
- */
33
- function pluginsTag(version) {
34
- const bare = version.replace(/^v/, '');
35
- return `b2c-agent-plugins@${bare}`;
19
+ function buildDownloadUrl(repo, tag, assetName) {
20
+ return `${GITHUB_DOWNLOAD_BASE}/${repo}/releases/download/${tag}/${assetName}`;
36
21
  }
37
- /**
38
- * File path for the resolved-latest-version cache.
39
- */
40
22
  function getLatestVersionCachePath() {
41
23
  return path.join(getCacheDir(), '.latest-version.json');
42
24
  }
43
- /**
44
- * Read a cached "latest" version if still fresh (1-hour TTL).
45
- */
46
25
  function readLatestVersionCache() {
47
26
  try {
48
27
  const p = getLatestVersionCachePath();
@@ -68,9 +47,6 @@ function writeLatestVersionCache(version) {
68
47
  // Caching is best-effort
69
48
  }
70
49
  }
71
- /**
72
- * Detect whether a fetch response is an unauthenticated GitHub rate-limit.
73
- */
74
50
  function isRateLimited(response) {
75
51
  if (response.status !== 403 && response.status !== 429)
76
52
  return false;
@@ -78,27 +54,15 @@ function isRateLimited(response) {
78
54
  return remaining === '0' || response.status === 429;
79
55
  }
80
56
  /**
81
- * Resolve the version of the most recent skills release.
82
- *
83
- * Strategy:
84
- * 1. API primary — paginate /releases and return the first entry that has
85
- * a skills asset attached.
86
- * 2. Rate-limit fallback — fetch skills/package.json from main via
87
- * raw.githubusercontent.com. This is the version that will be tagged
88
- * b2c-agent-plugins@<version> at release time.
89
- *
90
- * Caches the resolved version for 1 hour to avoid re-resolving on consecutive
91
- * calls within the same session. Always returns a bare version string like
92
- * "1.2.3" (no 'v' prefix, no tag format).
57
+ * Resolve the version of the most recent skills release from the b2c-developer-tooling repo.
93
58
  */
94
- async function resolveLatestVersion() {
59
+ async function resolveLatestVersion(source) {
95
60
  const logger = getLogger();
96
61
  const cached = readLatestVersionCache();
97
62
  if (cached) {
98
63
  logger.debug({ version: cached }, 'Using cached latest version');
99
64
  return cached;
100
65
  }
101
- // Primary — GitHub REST API with asset filtering
102
66
  try {
103
67
  const releases = await listReleases(30);
104
68
  if (releases.length > 0) {
@@ -117,8 +81,7 @@ async function resolveLatestVersion() {
117
81
  logger.warn({ err: err.message }, 'GitHub API unavailable; falling back to main branch for version resolution');
118
82
  }
119
83
  }
120
- // Fallback read skills/package.json from main via raw.githubusercontent.com
121
- const rawUrl = `${GITHUB_RAW_BASE}/${GITHUB_REPO}/main/skills/package.json`;
84
+ const rawUrl = `${GITHUB_RAW_BASE}/${source.repo}/main/skills/package.json`;
122
85
  logger.debug({ url: rawUrl }, 'Fetching version from raw.githubusercontent.com');
123
86
  const response = await fetch(rawUrl, { headers: { 'User-Agent': 'b2c-cli' } });
124
87
  if (!response.ok) {
@@ -134,20 +97,17 @@ async function resolveLatestVersion() {
134
97
  /**
135
98
  * Get the cache directory for skills.
136
99
  * Uses XDG_CACHE_HOME on Linux, ~/.cache otherwise.
137
- *
138
- * @returns Absolute path to cache directory
139
100
  */
140
101
  export function getCacheDir() {
141
102
  const xdgCache = process.env.XDG_CACHE_HOME;
142
103
  const baseCache = xdgCache || path.join(os.homedir(), '.cache');
143
104
  return path.join(baseCache, 'b2c-cli', 'skills');
144
105
  }
145
- /**
146
- * Parse GitHub API release response into ReleaseInfo.
147
- */
148
106
  function parseRelease(release) {
149
- const b2cAsset = release.assets.find((a) => a.name === ASSET_NAMES['b2c']);
150
- const b2cCliAsset = release.assets.find((a) => a.name === ASSET_NAMES['b2c-cli']);
107
+ const b2cSource = getSkillSource('b2c');
108
+ const b2cCliSource = getSkillSource('b2c-cli');
109
+ const b2cAsset = release.assets.find((a) => a.name === b2cSource.assetName);
110
+ const b2cCliAsset = release.assets.find((a) => a.name === b2cCliSource.assetName);
151
111
  return {
152
112
  tagName: release.tag_name,
153
113
  version: release.tag_name.replace(/^v/, ''),
@@ -158,29 +118,22 @@ function parseRelease(release) {
158
118
  }
159
119
  /**
160
120
  * Fetch release information from GitHub API.
161
- *
162
- * @param version - 'latest' or specific version (e.g., 'v0.1.0')
163
- * @returns Release information
164
- * @throws Error if release not found or API request fails
165
121
  */
166
122
  export async function getRelease(version = 'latest') {
167
123
  const logger = getLogger();
168
- // For 'latest', resolve to a concrete tag via the skills-aware resolver.
169
- // This avoids GitHub's /releases/latest endpoint, which returns whichever
170
- // release GitHub flags as latest regardless of whether it contains skills.
124
+ const source = getSkillSource('b2c');
171
125
  let tag;
172
126
  if (version === 'latest') {
173
- const resolved = await resolveLatestVersion();
174
- tag = pluginsTag(resolved);
127
+ const resolved = await resolveLatestVersion(source);
128
+ tag = source.tagPattern(resolved);
175
129
  }
176
130
  else if (/^b2c-agent-plugins@/.test(version) || version.includes('@')) {
177
- // Caller passed an explicit tag (including other packages' tags, for tests/internal use).
178
131
  tag = version;
179
132
  }
180
133
  else {
181
- tag = pluginsTag(version);
134
+ tag = source.tagPattern(version);
182
135
  }
183
- const endpoint = `${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/tags/${encodeURIComponent(tag)}`;
136
+ const endpoint = `${GITHUB_API_BASE}/repos/${source.repo}/releases/tags/${encodeURIComponent(tag)}`;
184
137
  logger.debug({ endpoint }, 'Fetching release info');
185
138
  const response = await fetch(endpoint, {
186
139
  headers: {
@@ -202,13 +155,11 @@ export async function getRelease(version = 'latest') {
202
155
  }
203
156
  /**
204
157
  * List available releases with skills artifacts.
205
- *
206
- * @param limit - Maximum number of releases to return (default: 10)
207
- * @returns Array of release information
208
158
  */
209
159
  export async function listReleases(limit = 10) {
210
160
  const logger = getLogger();
211
- const endpoint = `${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases?per_page=${limit}`;
161
+ const source = getSkillSource('b2c');
162
+ const endpoint = `${GITHUB_API_BASE}/repos/${source.repo}/releases?per_page=${limit}`;
212
163
  logger.debug({ endpoint }, 'Listing releases');
213
164
  const response = await fetch(endpoint, {
214
165
  headers: {
@@ -220,15 +171,10 @@ export async function listReleases(limit = 10) {
220
171
  throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
221
172
  }
222
173
  const data = (await response.json());
223
- // Only return releases that have at least one skills artifact
224
174
  return data.map(parseRelease).filter((r) => r.b2cSkillsAssetUrl || r.b2cCliSkillsAssetUrl);
225
175
  }
226
176
  /**
227
177
  * Get cached artifact metadata if available.
228
- *
229
- * @param version - Release version
230
- * @param skillSet - Skill set to check
231
- * @returns Cached artifact info or null if not cached
232
178
  */
233
179
  export function getCachedArtifact(version, skillSet) {
234
180
  const cacheDir = getCacheDir();
@@ -245,58 +191,40 @@ export function getCachedArtifact(version, skillSet) {
245
191
  }
246
192
  }
247
193
  /**
248
- * Download and extract skills artifact.
249
- * Uses direct download URLs to avoid GitHub API rate limits.
250
- *
251
- * @param skillSet - Which skill set to download ('b2c' or 'b2c-cli')
252
- * @param options - Download options
253
- * @returns Path to extracted skills directory
254
- * @throws Error if download fails or artifact not available
194
+ * Download and extract a release-artifact skill set (b2c or b2c-cli).
255
195
  */
256
- export async function downloadSkillsArtifact(skillSet, options = {}) {
196
+ async function downloadReleaseArtifact(source, options = {}) {
257
197
  const logger = getLogger();
258
198
  const { version = 'latest', forceDownload = false } = options;
259
- const assetName = ASSET_NAMES[skillSet];
260
- // Resolve 'latest' to a concrete version before any download or cache lookup.
261
- // This keeps the cache keyed to real version tags and avoids GitHub's opinionated
262
- // /releases/latest endpoint, which may point at a release that has no skills zips.
263
- const resolvedVersion = version === 'latest' ? await resolveLatestVersion() : version.replace(/^v/, '');
264
- const tag = pluginsTag(resolvedVersion);
265
- // Check cache for the resolved version
199
+ const resolvedVersion = version === 'latest' ? await resolveLatestVersion(source) : version.replace(/^v/, '');
200
+ const tag = source.tagPattern(resolvedVersion);
266
201
  if (!forceDownload) {
267
- const cached = getCachedArtifact(resolvedVersion, skillSet);
202
+ const cached = getCachedArtifact(resolvedVersion, source.id);
268
203
  if (cached && fs.existsSync(cached.path)) {
269
- logger.debug({ version: resolvedVersion, skillSet, path: cached.path }, 'Using cached skills');
204
+ logger.debug({ version: resolvedVersion, skillSet: source.id, path: cached.path }, 'Using cached skills');
270
205
  return cached.path;
271
206
  }
272
207
  }
273
- // Download from the CDN — no API rate limit on this path.
274
- const downloadUrl = buildDownloadUrl(tag, assetName);
275
- logger.debug({ url: downloadUrl, skillSet }, 'Downloading skills artifact');
276
- // Download artifact - GitHub will redirect to the actual file
208
+ const downloadUrl = buildDownloadUrl(source.repo, tag, source.assetName);
209
+ logger.debug({ url: downloadUrl, skillSet: source.id }, 'Downloading skills artifact');
277
210
  const response = await fetch(downloadUrl, {
278
- headers: {
279
- 'User-Agent': 'b2c-cli',
280
- },
211
+ headers: { 'User-Agent': 'b2c-cli' },
281
212
  redirect: 'follow',
282
213
  });
283
214
  if (!response.ok) {
284
215
  if (response.status === 404) {
285
- throw new Error(`Skills artifact '${assetName}' not found for release ${tag}`);
216
+ throw new Error(`Skills artifact '${source.assetName}' not found for release ${tag}`);
286
217
  }
287
218
  throw new Error(`Failed to download skills: ${response.status} ${response.statusText}`);
288
219
  }
289
220
  const zipBuffer = Buffer.from(await response.arrayBuffer());
290
221
  logger.debug({ size: zipBuffer.length, version: resolvedVersion }, 'Downloaded skills archive');
291
- // Extract to cache directory
292
222
  const cacheDir = options.cacheDir || getCacheDir();
293
- const extractDir = path.join(cacheDir, resolvedVersion, skillSet);
294
- // Clean existing extraction if present
223
+ const extractDir = path.join(cacheDir, resolvedVersion, source.id);
295
224
  if (fs.existsSync(extractDir)) {
296
225
  await fs.promises.rm(extractDir, { recursive: true });
297
226
  }
298
227
  await fs.promises.mkdir(extractDir, { recursive: true });
299
- // Extract zip contents
300
228
  const zip = await JSZip.loadAsync(zipBuffer);
301
229
  let extractedCount = 0;
302
230
  for (const [relativePath, zipEntry] of Object.entries(zip.files)) {
@@ -306,15 +234,12 @@ export async function downloadSkillsArtifact(skillSet, options = {}) {
306
234
  }
307
235
  const targetPath = path.join(extractDir, relativePath);
308
236
  const targetDir = path.dirname(targetPath);
309
- // Ensure parent directory exists
310
237
  await fs.promises.mkdir(targetDir, { recursive: true });
311
- // Write file
312
238
  const content = await zipEntry.async('nodebuffer');
313
239
  await fs.promises.writeFile(targetPath, content);
314
240
  extractedCount++;
315
241
  }
316
242
  logger.debug({ extractDir, fileCount: extractedCount }, 'Extracted skills');
317
- // Write cache manifest
318
243
  const manifest = {
319
244
  version: resolvedVersion,
320
245
  path: extractDir,
@@ -324,9 +249,171 @@ export async function downloadSkillsArtifact(skillSet, options = {}) {
324
249
  return extractDir;
325
250
  }
326
251
  /**
327
- * Clear the skills cache.
252
+ * Resolve a git ref to a commit SHA via the GitHub API.
253
+ */
254
+ async function resolveCommitSha(repo, ref) {
255
+ const response = await fetch(`${GITHUB_API_BASE}/repos/${repo}/commits/${encodeURIComponent(ref)}`, {
256
+ headers: { 'User-Agent': 'b2c-cli', Accept: 'application/vnd.github.v3.sha' },
257
+ });
258
+ if (!response.ok) {
259
+ throw new Error(`Failed to resolve ref '${ref}' for ${repo}: ${response.status} ${response.statusText}`);
260
+ }
261
+ return (await response.text()).trim();
262
+ }
263
+ /**
264
+ * Recursively fetch directory contents from the GitHub Contents API and write to disk.
265
+ */
266
+ async function fetchContentsRecursive(repo, repoPath, ref, destDir) {
267
+ const logger = getLogger();
268
+ const endpoint = `${GITHUB_API_BASE}/repos/${repo}/contents/${encodeURIComponent(repoPath)}?ref=${encodeURIComponent(ref)}`;
269
+ const response = await fetch(endpoint, {
270
+ headers: { Accept: 'application/vnd.github.v3+json', 'User-Agent': 'b2c-cli' },
271
+ });
272
+ if (!response.ok) {
273
+ if (isRateLimited(response)) {
274
+ throw Object.assign(new Error('GitHub API rate-limited'), { rateLimited: true });
275
+ }
276
+ throw new Error(`GitHub Contents API error for ${repoPath}: ${response.status} ${response.statusText}`);
277
+ }
278
+ const entries = (await response.json());
279
+ let fileCount = 0;
280
+ await fs.promises.mkdir(destDir, { recursive: true });
281
+ for (const entry of entries) {
282
+ const targetPath = path.join(destDir, entry.name);
283
+ if (entry.type === 'dir') {
284
+ fileCount += await fetchContentsRecursive(repo, entry.path, ref, targetPath);
285
+ }
286
+ else if (entry.type === 'file' && entry.download_url) {
287
+ const fileResponse = await fetch(entry.download_url, { headers: { 'User-Agent': 'b2c-cli' } });
288
+ if (!fileResponse.ok) {
289
+ logger.warn({ path: entry.path }, 'Failed to download file, skipping');
290
+ continue;
291
+ }
292
+ const content = Buffer.from(await fileResponse.arrayBuffer());
293
+ await fs.promises.writeFile(targetPath, content);
294
+ fileCount++;
295
+ }
296
+ }
297
+ return fileCount;
298
+ }
299
+ /**
300
+ * Recursively copy a directory tree.
301
+ */
302
+ async function copyDirRecursive(src, dest) {
303
+ await fs.promises.mkdir(dest, { recursive: true });
304
+ const entries = await fs.promises.readdir(src, { withFileTypes: true });
305
+ for (const entry of entries) {
306
+ const srcPath = path.join(src, entry.name);
307
+ const destPath = path.join(dest, entry.name);
308
+ if (entry.isDirectory()) {
309
+ await copyDirRecursive(srcPath, destPath);
310
+ }
311
+ else if (entry.isFile()) {
312
+ await fs.promises.copyFile(srcPath, destPath);
313
+ }
314
+ }
315
+ }
316
+ /**
317
+ * Fallback: use sparse git checkout to fetch only the skills subtree.
318
+ */
319
+ async function fetchViaSparseCheckout(repo, ref, skillsPath, destDir) {
320
+ const logger = getLogger();
321
+ const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'b2c-skills-'));
322
+ try {
323
+ const repoUrl = `${GITHUB_DOWNLOAD_BASE}/${repo}.git`;
324
+ logger.debug({ repoUrl, skillsPath }, 'Falling back to sparse git checkout');
325
+ await execFileAsync('git', [
326
+ 'clone',
327
+ '--no-checkout',
328
+ '--depth',
329
+ '1',
330
+ '--filter=blob:none',
331
+ '--sparse',
332
+ repoUrl,
333
+ tmpDir,
334
+ ]);
335
+ await execFileAsync('git', ['-C', tmpDir, 'sparse-checkout', 'set', skillsPath]);
336
+ await execFileAsync('git', ['-C', tmpDir, 'checkout']);
337
+ const skillsSrc = path.join(tmpDir, skillsPath);
338
+ if (!fs.existsSync(skillsSrc)) {
339
+ throw new Error(`Skills path '${skillsPath}' not found in repo ${repo}`);
340
+ }
341
+ await copyDirRecursive(skillsSrc, destDir);
342
+ }
343
+ finally {
344
+ await fs.promises.rm(tmpDir, { recursive: true, force: true });
345
+ }
346
+ }
347
+ /**
348
+ * Download skills from a repo-contents source (e.g., commerce-apps).
349
+ * Primary: GitHub Contents API. Fallback: sparse git checkout.
350
+ */
351
+ async function downloadRepoContents(source, options = {}) {
352
+ const logger = getLogger();
353
+ const ref = options.version || source.ref || 'main';
354
+ const forceDownload = options.forceDownload ?? false;
355
+ const skillsPath = source.skillsPath || '.claude/skills';
356
+ const commitSha = await resolveCommitSha(source.repo, ref);
357
+ const cacheKey = `${ref}-${commitSha.slice(0, 8)}`;
358
+ if (!forceDownload) {
359
+ const cached = getCachedArtifact(cacheKey, source.id);
360
+ if (cached && fs.existsSync(cached.path)) {
361
+ logger.debug({ cacheKey, skillSet: source.id, path: cached.path }, 'Using cached skills');
362
+ return cached.path;
363
+ }
364
+ }
365
+ const cacheDir = options.cacheDir || getCacheDir();
366
+ const extractDir = path.join(cacheDir, cacheKey, source.id);
367
+ const skillsDestDir = path.join(extractDir, 'skills');
368
+ if (fs.existsSync(extractDir)) {
369
+ await fs.promises.rm(extractDir, { recursive: true });
370
+ }
371
+ await fs.promises.mkdir(skillsDestDir, { recursive: true });
372
+ try {
373
+ logger.debug({ repo: source.repo, skillsPath, ref }, 'Fetching skills via Contents API');
374
+ const fileCount = await fetchContentsRecursive(source.repo, skillsPath, ref, skillsDestDir);
375
+ logger.debug({ extractDir, fileCount }, 'Fetched skills via Contents API');
376
+ }
377
+ catch (err) {
378
+ const isRateLimit = err instanceof Error && 'rateLimited' in err;
379
+ if (isRateLimit) {
380
+ logger.warn('Contents API rate-limited; falling back to sparse git checkout');
381
+ }
382
+ else {
383
+ logger.warn({ err: err.message }, 'Contents API failed; falling back to sparse git checkout');
384
+ }
385
+ if (fs.existsSync(skillsDestDir)) {
386
+ await fs.promises.rm(skillsDestDir, { recursive: true });
387
+ }
388
+ await fs.promises.mkdir(skillsDestDir, { recursive: true });
389
+ await fetchViaSparseCheckout(source.repo, ref, skillsPath, skillsDestDir);
390
+ }
391
+ const manifest = {
392
+ version: cacheKey,
393
+ path: extractDir,
394
+ downloadedAt: new Date().toISOString(),
395
+ commitSha,
396
+ };
397
+ await fs.promises.writeFile(path.join(extractDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
398
+ return extractDir;
399
+ }
400
+ /**
401
+ * Download and extract skills artifact.
402
+ * Dispatches to the appropriate download strategy based on the skill source type.
328
403
  *
329
- * @param version - Optional specific version to clear (default: all)
404
+ * @param skillSet - Which skill set to download
405
+ * @param options - Download options
406
+ * @returns Path to extracted skills directory
407
+ */
408
+ export async function downloadSkillsArtifact(skillSet, options = {}) {
409
+ const source = getSkillSource(skillSet);
410
+ if (source.type === 'release-artifact') {
411
+ return downloadReleaseArtifact(source, options);
412
+ }
413
+ return downloadRepoContents(source, options);
414
+ }
415
+ /**
416
+ * Clear the skills cache.
330
417
  */
331
418
  export async function clearCache(version) {
332
419
  const cacheDir = getCacheDir();