@slats/claude-assets-sync 0.0.5 → 0.0.6

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.
@@ -22,7 +22,7 @@ export declare const META_FILES: {
22
22
  * Schema versions for metadata files
23
23
  */
24
24
  export declare const SCHEMA_VERSIONS: {
25
- readonly UNIFIED_SYNC_META: "0.0.5";
25
+ readonly UNIFIED_SYNC_META: "0.0.6";
26
26
  readonly LEGACY_SYNC_META: "1.0.0";
27
27
  readonly SKILL_UNIT_FORMAT: "2";
28
28
  };
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var fs = require('node:fs');
4
+ var path = require('node:path');
5
+
6
+ const canUseLocalSource = (packageName, requestedVersion, assetPath, cwd) => {
7
+ const docsPath = path.join(cwd, 'node_modules', packageName, assetPath);
8
+ if (!fs.existsSync(docsPath)) {
9
+ return { available: false, reason: `Local docs path not found: ${docsPath}` };
10
+ }
11
+ try {
12
+ const pkgJsonPath = path.join(cwd, 'node_modules', packageName, 'package.json');
13
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
14
+ if (pkgJson.version !== requestedVersion) {
15
+ return {
16
+ available: false,
17
+ reason: `Version mismatch: installed=${pkgJson.version}, requested=${requestedVersion}`,
18
+ };
19
+ }
20
+ }
21
+ catch {
22
+ return { available: false, reason: 'Failed to read package.json from node_modules' };
23
+ }
24
+ return { available: true, docsPath };
25
+ };
26
+ const fetchLocalDirectoryContents = (dirPath) => {
27
+ if (!fs.existsSync(dirPath))
28
+ return null;
29
+ try {
30
+ const entries = fs.readdirSync(dirPath);
31
+ const result = [];
32
+ for (const name of entries) {
33
+ const fullPath = path.join(dirPath, name);
34
+ const stat = fs.statSync(fullPath);
35
+ if (stat.isDirectory()) {
36
+ result.push({
37
+ name,
38
+ path: fullPath,
39
+ type: 'dir',
40
+ download_url: null,
41
+ sha: '',
42
+ });
43
+ }
44
+ else if (stat.isFile() && name.endsWith('.md')) {
45
+ result.push({
46
+ name,
47
+ path: fullPath,
48
+ type: 'file',
49
+ download_url: null,
50
+ sha: '',
51
+ });
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ };
60
+ const expandLocalDirectoryEntries = (dirPath, entries, prefix = '') => {
61
+ const result = [];
62
+ for (const entry of entries) {
63
+ const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
64
+ if (entry.type === 'file') {
65
+ result.push({
66
+ ...entry,
67
+ name: prefix ? entryPrefix : entry.name,
68
+ });
69
+ }
70
+ else if (entry.type === 'dir') {
71
+ const subDirPath = path.join(dirPath, entry.name);
72
+ const subEntries = fetchLocalDirectoryContents(subDirPath);
73
+ if (subEntries) {
74
+ const expanded = expandLocalDirectoryEntries(subDirPath, subEntries, entryPrefix);
75
+ result.push(...expanded);
76
+ }
77
+ }
78
+ }
79
+ return result;
80
+ };
81
+ const fetchLocalAssetFiles = async (docsBasePath, assetTypes) => {
82
+ const assetFiles = {};
83
+ for (const assetType of assetTypes) {
84
+ const assetDirPath = path.join(docsBasePath, assetType);
85
+ const entries = fetchLocalDirectoryContents(assetDirPath);
86
+ if (!entries) {
87
+ assetFiles[assetType] = [];
88
+ continue;
89
+ }
90
+ assetFiles[assetType] = expandLocalDirectoryEntries(assetDirPath, entries);
91
+ }
92
+ return assetFiles;
93
+ };
94
+ const downloadLocalAssetFiles = async (docsBasePath, assetType, entries) => {
95
+ const results = new Map();
96
+ for (const entry of entries) {
97
+ const filePath = path.join(docsBasePath, assetType, entry.name);
98
+ try {
99
+ const content = fs.readFileSync(filePath, 'utf-8');
100
+ results.set(entry.name, content);
101
+ }
102
+ catch {
103
+ }
104
+ }
105
+ return results;
106
+ };
107
+
108
+ exports.canUseLocalSource = canUseLocalSource;
109
+ exports.downloadLocalAssetFiles = downloadLocalAssetFiles;
110
+ exports.expandLocalDirectoryEntries = expandLocalDirectoryEntries;
111
+ exports.fetchLocalAssetFiles = fetchLocalAssetFiles;
112
+ exports.fetchLocalDirectoryContents = fetchLocalDirectoryContents;
@@ -0,0 +1,33 @@
1
+ import type { AssetType, GitHubEntry } from '../utils/types.js';
2
+ export interface LocalSourceResult {
3
+ available: boolean;
4
+ docsPath?: string;
5
+ reason?: string;
6
+ }
7
+ /**
8
+ * Check if local docs source is available in node_modules.
9
+ * Returns available=true if:
10
+ * 1. node_modules/<packageName>/<assetPath> directory exists
11
+ * 2. Installed version matches requestedVersion
12
+ */
13
+ export declare const canUseLocalSource: (packageName: string, requestedVersion: string, assetPath: string, cwd: string) => LocalSourceResult;
14
+ /**
15
+ * Read directory contents and return .md files and subdirs in GitHubEntry format.
16
+ * Returns null if the directory doesn't exist.
17
+ */
18
+ export declare const fetchLocalDirectoryContents: (dirPath: string) => GitHubEntry[] | null;
19
+ /**
20
+ * Recursively expand directory entries into flat file entries with path prefixes.
21
+ * Mirrors github.ts expandDirectoryEntries behaviour but reads from local filesystem.
22
+ */
23
+ export declare const expandLocalDirectoryEntries: (dirPath: string, entries: GitHubEntry[], prefix?: string) => GitHubEntry[];
24
+ /**
25
+ * Fetch asset files from local filesystem.
26
+ * Mirrors github.ts fetchAssetFiles but reads from node_modules instead of GitHub API.
27
+ */
28
+ export declare const fetchLocalAssetFiles: (docsBasePath: string, assetTypes: string[]) => Promise<Record<string, GitHubEntry[]>>;
29
+ /**
30
+ * Read file contents from local filesystem for the given entries.
31
+ * Mirrors github.ts downloadAssetFiles but reads local files instead of HTTP requests.
32
+ */
33
+ export declare const downloadLocalAssetFiles: (docsBasePath: string, assetType: AssetType, entries: GitHubEntry[]) => Promise<Map<string, string>>;
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const canUseLocalSource = (packageName, requestedVersion, assetPath, cwd) => {
5
+ const docsPath = join(cwd, 'node_modules', packageName, assetPath);
6
+ if (!existsSync(docsPath)) {
7
+ return { available: false, reason: `Local docs path not found: ${docsPath}` };
8
+ }
9
+ try {
10
+ const pkgJsonPath = join(cwd, 'node_modules', packageName, 'package.json');
11
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
12
+ if (pkgJson.version !== requestedVersion) {
13
+ return {
14
+ available: false,
15
+ reason: `Version mismatch: installed=${pkgJson.version}, requested=${requestedVersion}`,
16
+ };
17
+ }
18
+ }
19
+ catch {
20
+ return { available: false, reason: 'Failed to read package.json from node_modules' };
21
+ }
22
+ return { available: true, docsPath };
23
+ };
24
+ const fetchLocalDirectoryContents = (dirPath) => {
25
+ if (!existsSync(dirPath))
26
+ return null;
27
+ try {
28
+ const entries = readdirSync(dirPath);
29
+ const result = [];
30
+ for (const name of entries) {
31
+ const fullPath = join(dirPath, name);
32
+ const stat = statSync(fullPath);
33
+ if (stat.isDirectory()) {
34
+ result.push({
35
+ name,
36
+ path: fullPath,
37
+ type: 'dir',
38
+ download_url: null,
39
+ sha: '',
40
+ });
41
+ }
42
+ else if (stat.isFile() && name.endsWith('.md')) {
43
+ result.push({
44
+ name,
45
+ path: fullPath,
46
+ type: 'file',
47
+ download_url: null,
48
+ sha: '',
49
+ });
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ };
58
+ const expandLocalDirectoryEntries = (dirPath, entries, prefix = '') => {
59
+ const result = [];
60
+ for (const entry of entries) {
61
+ const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
62
+ if (entry.type === 'file') {
63
+ result.push({
64
+ ...entry,
65
+ name: prefix ? entryPrefix : entry.name,
66
+ });
67
+ }
68
+ else if (entry.type === 'dir') {
69
+ const subDirPath = join(dirPath, entry.name);
70
+ const subEntries = fetchLocalDirectoryContents(subDirPath);
71
+ if (subEntries) {
72
+ const expanded = expandLocalDirectoryEntries(subDirPath, subEntries, entryPrefix);
73
+ result.push(...expanded);
74
+ }
75
+ }
76
+ }
77
+ return result;
78
+ };
79
+ const fetchLocalAssetFiles = async (docsBasePath, assetTypes) => {
80
+ const assetFiles = {};
81
+ for (const assetType of assetTypes) {
82
+ const assetDirPath = join(docsBasePath, assetType);
83
+ const entries = fetchLocalDirectoryContents(assetDirPath);
84
+ if (!entries) {
85
+ assetFiles[assetType] = [];
86
+ continue;
87
+ }
88
+ assetFiles[assetType] = expandLocalDirectoryEntries(assetDirPath, entries);
89
+ }
90
+ return assetFiles;
91
+ };
92
+ const downloadLocalAssetFiles = async (docsBasePath, assetType, entries) => {
93
+ const results = new Map();
94
+ for (const entry of entries) {
95
+ const filePath = join(docsBasePath, assetType, entry.name);
96
+ try {
97
+ const content = readFileSync(filePath, 'utf-8');
98
+ results.set(entry.name, content);
99
+ }
100
+ catch {
101
+ }
102
+ }
103
+ return results;
104
+ };
105
+
106
+ export { canUseLocalSource, downloadLocalAssetFiles, expandLocalDirectoryEntries, fetchLocalAssetFiles, fetchLocalDirectoryContents };
@@ -65,6 +65,24 @@ async function scanRemoteAssets(packageName, ref) {
65
65
  if (!pkgInfo || !pkgInfo.claude?.assetPath) {
66
66
  throw new Error(`Package ${packageName} has no claude.assetPath configured`);
67
67
  }
68
+ const assetBasePath = pkgInfo.claude.assetPath;
69
+ if (!ref) {
70
+ const localDocsPath = path.join(nodeModulesPath, assetBasePath);
71
+ if (fs.existsSync(localDocsPath)) {
72
+ const trees = [];
73
+ for (const assetType of constants.DEFAULT_ASSET_TYPES) {
74
+ const assetDir = path.join(localDocsPath, assetType);
75
+ if (!fs.existsSync(assetDir))
76
+ continue;
77
+ const tree = buildTreeFromLocalDir(assetType, assetDir, assetType);
78
+ if (tree.children && tree.children.length > 0) {
79
+ trees.push(tree);
80
+ }
81
+ }
82
+ if (trees.length > 0)
83
+ return trees;
84
+ }
85
+ }
68
86
  if (!pkgInfo.repository) {
69
87
  throw new Error(`Package ${packageName} has no repository field`);
70
88
  }
@@ -72,7 +90,6 @@ async function scanRemoteAssets(packageName, ref) {
72
90
  if (!repoInfo) {
73
91
  throw new Error(`Invalid GitHub repository URL in package ${packageName}`);
74
92
  }
75
- const assetBasePath = pkgInfo.claude.assetPath;
76
93
  const tag = ref ?? 'HEAD';
77
94
  const trees = [];
78
95
  for (const assetType of constants.DEFAULT_ASSET_TYPES) {
@@ -63,6 +63,24 @@ async function scanRemoteAssets(packageName, ref) {
63
63
  if (!pkgInfo || !pkgInfo.claude?.assetPath) {
64
64
  throw new Error(`Package ${packageName} has no claude.assetPath configured`);
65
65
  }
66
+ const assetBasePath = pkgInfo.claude.assetPath;
67
+ if (!ref) {
68
+ const localDocsPath = join(nodeModulesPath, assetBasePath);
69
+ if (existsSync(localDocsPath)) {
70
+ const trees = [];
71
+ for (const assetType of DEFAULT_ASSET_TYPES) {
72
+ const assetDir = join(localDocsPath, assetType);
73
+ if (!existsSync(assetDir))
74
+ continue;
75
+ const tree = buildTreeFromLocalDir(assetType, assetDir, assetType);
76
+ if (tree.children && tree.children.length > 0) {
77
+ trees.push(tree);
78
+ }
79
+ }
80
+ if (trees.length > 0)
81
+ return trees;
82
+ }
83
+ }
66
84
  if (!pkgInfo.repository) {
67
85
  throw new Error(`Package ${packageName} has no repository field`);
68
86
  }
@@ -70,7 +88,6 @@ async function scanRemoteAssets(packageName, ref) {
70
88
  if (!repoInfo) {
71
89
  throw new Error(`Invalid GitHub repository URL in package ${packageName}`);
72
90
  }
73
- const assetBasePath = pkgInfo.claude.assetPath;
74
91
  const tag = ref ?? 'HEAD';
75
92
  const trees = [];
76
93
  for (const assetType of DEFAULT_ASSET_TYPES) {
@@ -7,6 +7,7 @@ var packageName = require('../utils/packageName.cjs');
7
7
  var paths = require('../utils/paths.cjs');
8
8
  var filesystem = require('./filesystem.cjs');
9
9
  var github = require('./github.cjs');
10
+ var localSource = require('./localSource.cjs');
10
11
  var syncMeta = require('./syncMeta.cjs');
11
12
  var constants = require('./constants.cjs');
12
13
  var assetStructure = require('./assetStructure.cjs');
@@ -67,14 +68,6 @@ const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusio
67
68
  reason: 'Package does not have claude.assetPath in package.json',
68
69
  };
69
70
  const repoInfo = _package.parseGitHubRepo(packageInfo.repository);
70
- if (!repoInfo) {
71
- return {
72
- packageName: packageName$1,
73
- success: false,
74
- skipped: true,
75
- reason: 'Unable to parse GitHub repository URL',
76
- };
77
- }
78
71
  const useFlat = options.flat !== false;
79
72
  if (useFlat) {
80
73
  const prefix = packageName.packageNameToPrefix(packageName$1);
@@ -88,11 +81,32 @@ const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusio
88
81
  reason: `Already synced at version ${packageInfo.version}`,
89
82
  };
90
83
  }
91
- const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
92
84
  const assetPath = _package.buildAssetPath(packageInfo.claude.assetPath);
93
- logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
85
+ const useLocalSource = options.ref
86
+ ? { available: false }
87
+ : localSource.canUseLocalSource(packageName$1, packageInfo.version, assetPath, cwd);
94
88
  const assetTypes = _package.getAssetTypes(packageInfo.claude);
95
- const assetFiles = await github.fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
89
+ let assetFiles;
90
+ let isLocalSource;
91
+ if (useLocalSource.available && useLocalSource.docsPath) {
92
+ isLocalSource = true;
93
+ logger.logger.step('Using', 'local docs from node_modules');
94
+ assetFiles = await localSource.fetchLocalAssetFiles(useLocalSource.docsPath, assetTypes);
95
+ }
96
+ else {
97
+ if (!repoInfo) {
98
+ return {
99
+ packageName: packageName$1,
100
+ success: false,
101
+ skipped: true,
102
+ reason: `Package ${packageName$1} has no valid GitHub repository URL for remote fetch`,
103
+ };
104
+ }
105
+ isLocalSource = false;
106
+ const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
107
+ logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
108
+ assetFiles = await github.fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
109
+ }
96
110
  let totalFiles = 0;
97
111
  for (const assetType of assetTypes) {
98
112
  totalFiles += (assetFiles[assetType] || []).length;
@@ -189,7 +203,14 @@ const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusio
189
203
  continue;
190
204
  const structure = assetStructure.getAssetStructure(assetType, packageInfo.claude);
191
205
  logger.logger.step('Downloading', assetType);
192
- const downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
206
+ let downloadedFiles;
207
+ if (isLocalSource && useLocalSource.docsPath) {
208
+ downloadedFiles = await localSource.downloadLocalAssetFiles(useLocalSource.docsPath, assetType, filteredEntries);
209
+ }
210
+ else {
211
+ const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
212
+ downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
213
+ }
193
214
  if (structure === 'nested') {
194
215
  for (const [fileName, content] of downloadedFiles) {
195
216
  filesystem.writeAssetFile(destDir, packageName$1, assetType, fileName, content);
@@ -231,11 +252,32 @@ const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusio
231
252
  };
232
253
  }
233
254
  else {
234
- const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
235
255
  const assetPath = _package.buildAssetPath(packageInfo.claude.assetPath);
236
- logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
256
+ const useLocalSource = options.ref
257
+ ? { available: false }
258
+ : localSource.canUseLocalSource(packageName$1, packageInfo.version, assetPath, cwd);
237
259
  const assetTypes = _package.getAssetTypes(packageInfo.claude);
238
- const assetFiles = await github.fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
260
+ let assetFiles;
261
+ let isLocalSource;
262
+ if (useLocalSource.available && useLocalSource.docsPath) {
263
+ isLocalSource = true;
264
+ logger.logger.step('Using', 'local docs from node_modules');
265
+ assetFiles = await localSource.fetchLocalAssetFiles(useLocalSource.docsPath, assetTypes);
266
+ }
267
+ else {
268
+ if (!repoInfo) {
269
+ return {
270
+ packageName: packageName$1,
271
+ success: false,
272
+ skipped: true,
273
+ reason: `Package ${packageName$1} has no valid GitHub repository URL for remote fetch`,
274
+ };
275
+ }
276
+ isLocalSource = false;
277
+ const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
278
+ logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
279
+ assetFiles = await github.fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
280
+ }
239
281
  let totalFiles = 0;
240
282
  for (const assetType of assetTypes) {
241
283
  totalFiles += (assetFiles[assetType] || []).length;
@@ -299,7 +341,14 @@ const syncPackage = async (packageName$1, options, cwd = process.cwd(), exclusio
299
341
  if (filteredEntries.length === 0)
300
342
  continue;
301
343
  logger.logger.step('Downloading', assetType);
302
- const downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
344
+ let downloadedFiles;
345
+ if (isLocalSource && useLocalSource.docsPath) {
346
+ downloadedFiles = await localSource.downloadLocalAssetFiles(useLocalSource.docsPath, assetType, filteredEntries);
347
+ }
348
+ else {
349
+ const tag = options.ref ?? _package.buildVersionTag(packageName$1, packageInfo.version);
350
+ downloadedFiles = await github.downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
351
+ }
303
352
  filesystem.cleanAssetDir(destDir, packageName$1, assetType);
304
353
  syncedFiles[assetType] = [];
305
354
  for (const [fileName, content] of downloadedFiles) {
@@ -1,10 +1,11 @@
1
1
  import { logger } from '../utils/logger.mjs';
2
2
  import { toFlatFileName } from '../utils/nameTransform.mjs';
3
- import { findGitRoot, readLocalPackageJson, readPackageJson, parseGitHubRepo, buildVersionTag, buildAssetPath, getAssetTypes } from '../utils/package.mjs';
3
+ import { findGitRoot, readLocalPackageJson, readPackageJson, parseGitHubRepo, buildAssetPath, getAssetTypes, buildVersionTag } from '../utils/package.mjs';
4
4
  import { packageNameToPrefix } from '../utils/packageName.mjs';
5
5
  import { getDestinationDir, getFlatDestinationDir } from '../utils/paths.mjs';
6
6
  import { cleanAssetDir, cleanFlatAssetFiles, writeAssetFile, writeFlatAssetFile, needsSync, writeSyncMeta, createSyncMeta } from './filesystem.mjs';
7
7
  import { fetchAssetFiles, downloadAssetFiles, RateLimitError } from './github.mjs';
8
+ import { canUseLocalSource, fetchLocalAssetFiles, downloadLocalAssetFiles } from './localSource.mjs';
8
9
  import { readUnifiedSyncMeta, createEmptyUnifiedMeta, needsSyncUnified, updatePackageInMeta, writeUnifiedSyncMeta } from './syncMeta.mjs';
9
10
  import { SCHEMA_VERSIONS } from './constants.mjs';
10
11
  import { getAssetStructure } from './assetStructure.mjs';
@@ -65,14 +66,6 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
65
66
  reason: 'Package does not have claude.assetPath in package.json',
66
67
  };
67
68
  const repoInfo = parseGitHubRepo(packageInfo.repository);
68
- if (!repoInfo) {
69
- return {
70
- packageName,
71
- success: false,
72
- skipped: true,
73
- reason: 'Unable to parse GitHub repository URL',
74
- };
75
- }
76
69
  const useFlat = options.flat !== false;
77
70
  if (useFlat) {
78
71
  const prefix = packageNameToPrefix(packageName);
@@ -86,11 +79,32 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
86
79
  reason: `Already synced at version ${packageInfo.version}`,
87
80
  };
88
81
  }
89
- const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
90
82
  const assetPath = buildAssetPath(packageInfo.claude.assetPath);
91
- logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
83
+ const useLocalSource = options.ref
84
+ ? { available: false }
85
+ : canUseLocalSource(packageName, packageInfo.version, assetPath, cwd);
92
86
  const assetTypes = getAssetTypes(packageInfo.claude);
93
- const assetFiles = await fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
87
+ let assetFiles;
88
+ let isLocalSource;
89
+ if (useLocalSource.available && useLocalSource.docsPath) {
90
+ isLocalSource = true;
91
+ logger.step('Using', 'local docs from node_modules');
92
+ assetFiles = await fetchLocalAssetFiles(useLocalSource.docsPath, assetTypes);
93
+ }
94
+ else {
95
+ if (!repoInfo) {
96
+ return {
97
+ packageName,
98
+ success: false,
99
+ skipped: true,
100
+ reason: `Package ${packageName} has no valid GitHub repository URL for remote fetch`,
101
+ };
102
+ }
103
+ isLocalSource = false;
104
+ const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
105
+ logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
106
+ assetFiles = await fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
107
+ }
94
108
  let totalFiles = 0;
95
109
  for (const assetType of assetTypes) {
96
110
  totalFiles += (assetFiles[assetType] || []).length;
@@ -187,7 +201,14 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
187
201
  continue;
188
202
  const structure = getAssetStructure(assetType, packageInfo.claude);
189
203
  logger.step('Downloading', assetType);
190
- const downloadedFiles = await downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
204
+ let downloadedFiles;
205
+ if (isLocalSource && useLocalSource.docsPath) {
206
+ downloadedFiles = await downloadLocalAssetFiles(useLocalSource.docsPath, assetType, filteredEntries);
207
+ }
208
+ else {
209
+ const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
210
+ downloadedFiles = await downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
211
+ }
191
212
  if (structure === 'nested') {
192
213
  for (const [fileName, content] of downloadedFiles) {
193
214
  writeAssetFile(destDir, packageName, assetType, fileName, content);
@@ -229,11 +250,32 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
229
250
  };
230
251
  }
231
252
  else {
232
- const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
233
253
  const assetPath = buildAssetPath(packageInfo.claude.assetPath);
234
- logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
254
+ const useLocalSource = options.ref
255
+ ? { available: false }
256
+ : canUseLocalSource(packageName, packageInfo.version, assetPath, cwd);
235
257
  const assetTypes = getAssetTypes(packageInfo.claude);
236
- const assetFiles = await fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
258
+ let assetFiles;
259
+ let isLocalSource;
260
+ if (useLocalSource.available && useLocalSource.docsPath) {
261
+ isLocalSource = true;
262
+ logger.step('Using', 'local docs from node_modules');
263
+ assetFiles = await fetchLocalAssetFiles(useLocalSource.docsPath, assetTypes);
264
+ }
265
+ else {
266
+ if (!repoInfo) {
267
+ return {
268
+ packageName,
269
+ success: false,
270
+ skipped: true,
271
+ reason: `Package ${packageName} has no valid GitHub repository URL for remote fetch`,
272
+ };
273
+ }
274
+ isLocalSource = false;
275
+ const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
276
+ logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
277
+ assetFiles = await fetchAssetFiles(repoInfo, assetPath, tag, assetTypes);
278
+ }
237
279
  let totalFiles = 0;
238
280
  for (const assetType of assetTypes) {
239
281
  totalFiles += (assetFiles[assetType] || []).length;
@@ -297,7 +339,14 @@ const syncPackage = async (packageName, options, cwd = process.cwd(), exclusions
297
339
  if (filteredEntries.length === 0)
298
340
  continue;
299
341
  logger.step('Downloading', assetType);
300
- const downloadedFiles = await downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
342
+ let downloadedFiles;
343
+ if (isLocalSource && useLocalSource.docsPath) {
344
+ downloadedFiles = await downloadLocalAssetFiles(useLocalSource.docsPath, assetType, filteredEntries);
345
+ }
346
+ else {
347
+ const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
348
+ downloadedFiles = await downloadAssetFiles(repoInfo, assetPath, assetType, filteredEntries, tag);
349
+ }
301
350
  cleanAssetDir(destDir, packageName, assetType);
302
351
  syncedFiles[assetType] = [];
303
352
  for (const [fileName, content] of downloadedFiles) {
@@ -2,7 +2,7 @@ import type { PackageSyncInfo, SkillUnit, UnifiedSyncMeta } from '../utils/types
2
2
  /**
3
3
  * Schema version for the unified metadata format
4
4
  */
5
- export declare const SCHEMA_VERSION: "0.0.5";
5
+ export declare const SCHEMA_VERSION: "0.0.6";
6
6
  /**
7
7
  * Read unified sync metadata from .claude/.sync-meta.json
8
8
  *
package/dist/version.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const VERSION = '0.0.5';
3
+ const VERSION = '0.0.6';
4
4
 
5
5
  exports.VERSION = VERSION;
package/dist/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current package version from package.json
3
3
  * Automatically synchronized during build process
4
4
  */
5
- export declare const VERSION = "0.0.5";
5
+ export declare const VERSION = "0.0.6";
package/dist/version.mjs CHANGED
@@ -1,3 +1,3 @@
1
- const VERSION = '0.0.5';
1
+ const VERSION = '0.0.6';
2
2
 
3
3
  export { VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slats/claude-assets-sync",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "CLI tool to sync Claude commands and skills from npm packages to your project's .claude directory",
5
5
  "keywords": [
6
6
  "claude",