@slats/claude-assets-sync 0.0.3 → 0.0.5

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 (52) hide show
  1. package/dist/commands/add.cjs +2 -7
  2. package/dist/commands/add.mjs +2 -7
  3. package/dist/commands/index.d.ts +22 -0
  4. package/dist/commands/remove.cjs +13 -13
  5. package/dist/commands/remove.mjs +7 -7
  6. package/dist/commands/types.d.ts +2 -4
  7. package/dist/commands/update.cjs +178 -0
  8. package/dist/commands/update.d.ts +13 -0
  9. package/dist/commands/update.mjs +176 -0
  10. package/dist/components/add/AddCommand.cjs +11 -0
  11. package/dist/components/add/AddCommand.mjs +11 -0
  12. package/dist/components/list/ListCommand.cjs +65 -47
  13. package/dist/components/list/ListCommand.mjs +66 -48
  14. package/dist/components/status/StatusDisplay.cjs +4 -3
  15. package/dist/components/status/StatusDisplay.d.ts +2 -4
  16. package/dist/components/status/StatusDisplay.mjs +4 -3
  17. package/dist/components/tree/AssetTreeNode.cjs +21 -4
  18. package/dist/components/tree/AssetTreeNode.d.ts +1 -1
  19. package/dist/components/tree/AssetTreeNode.mjs +21 -4
  20. package/dist/components/tree/TreeSelect.cjs +14 -6
  21. package/dist/components/tree/TreeSelect.mjs +14 -6
  22. package/dist/core/assetStructure.d.ts +1 -1
  23. package/dist/core/cli.cjs +18 -0
  24. package/dist/core/cli.mjs +18 -0
  25. package/dist/core/constants.cjs +4 -1
  26. package/dist/core/constants.d.ts +8 -1
  27. package/dist/core/constants.mjs +4 -1
  28. package/dist/core/filesystem.cjs +18 -12
  29. package/dist/core/filesystem.d.ts +3 -2
  30. package/dist/core/filesystem.mjs +18 -12
  31. package/dist/core/github.cjs +31 -3
  32. package/dist/core/github.d.ts +14 -1
  33. package/dist/core/github.mjs +31 -4
  34. package/dist/core/migration.cjs +17 -15
  35. package/dist/core/migration.mjs +12 -10
  36. package/dist/core/packageScanner.cjs +110 -29
  37. package/dist/core/packageScanner.d.ts +6 -1
  38. package/dist/core/packageScanner.mjs +109 -30
  39. package/dist/core/sync.cjs +83 -59
  40. package/dist/core/sync.mjs +55 -31
  41. package/dist/core/syncMeta.cjs +153 -9
  42. package/dist/core/syncMeta.d.ts +22 -18
  43. package/dist/core/syncMeta.mjs +149 -9
  44. package/dist/utils/nameTransform.cjs +7 -6
  45. package/dist/utils/nameTransform.d.ts +22 -33
  46. package/dist/utils/nameTransform.mjs +8 -6
  47. package/dist/utils/types.d.ts +24 -7
  48. package/dist/version.cjs +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.mjs +1 -1
  51. package/package.json +2 -2
  52. package/CHANGELOG.md +0 -189
@@ -10,12 +10,14 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
10
10
  const [treeData, setTreeData] = React.useState(trees);
11
11
  const flattenTree = (nodes, depth = 0) => {
12
12
  const result = [];
13
- const traverse = (items, currentDepth, parentPath = []) => {
13
+ const traverse = (items, currentDepth, parentPath = [], ancestorIsLast = []) => {
14
14
  items.forEach((item, index) => {
15
+ const isLast = index === items.length - 1;
16
+ const currentIsLast = [...ancestorIsLast, isLast];
15
17
  const currentPath = [...parentPath, index];
16
- result.push({ node: item, depth: currentDepth, path: currentPath });
18
+ result.push({ node: item, depth: currentDepth, path: currentPath, isLastAtDepth: currentIsLast });
17
19
  if (item.expanded && item.children) {
18
- traverse(item.children, currentDepth + 1, currentPath);
20
+ traverse(item.children, currentDepth + 1, currentPath, currentIsLast);
19
21
  }
20
22
  });
21
23
  };
@@ -32,7 +34,9 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
32
34
  }
33
35
  else if (key.rightArrow) {
34
36
  const current = flatItems[selectedIndex];
35
- if (current.node.type === 'directory' && !current.node.expanded) {
37
+ if ((current.node.type === 'directory' || current.node.type === 'skill-directory') &&
38
+ !current.node.expanded &&
39
+ current.node.children) {
36
40
  const newTrees = updateNodeAtPath(treeData, current.path, (node) => ({
37
41
  ...node,
38
42
  expanded: true,
@@ -42,7 +46,8 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
42
46
  }
43
47
  else if (key.leftArrow) {
44
48
  const current = flatItems[selectedIndex];
45
- if (current.node.type === 'directory' && current.node.expanded) {
49
+ if ((current.node.type === 'directory' || current.node.type === 'skill-directory') &&
50
+ current.node.expanded) {
46
51
  const newTrees = updateNodeAtPath(treeData, current.path, (node) => ({
47
52
  ...node,
48
53
  expanded: false,
@@ -52,6 +57,9 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
52
57
  }
53
58
  else if (input === ' ') {
54
59
  const current = flatItems[selectedIndex];
60
+ if (current.node.disabled) {
61
+ return;
62
+ }
55
63
  const newTrees = toggleNodeSelection(treeData, current.path);
56
64
  setTreeData(newTrees);
57
65
  }
@@ -65,7 +73,7 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
65
73
  onCancel();
66
74
  }
67
75
  });
68
- return (jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: flatItems.map((item, index) => (jsxRuntime.jsx(AssetTreeNode.AssetTreeNode, { node: item.node, depth: item.depth, isSelected: index === selectedIndex, onToggle: () => {
76
+ return (jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: flatItems.map((item, index) => (jsxRuntime.jsx(AssetTreeNode.AssetTreeNode, { node: item.node, isLastAtDepth: item.isLastAtDepth, isSelected: index === selectedIndex, onToggle: () => {
69
77
  const newTrees = toggleNodeSelection(treeData, item.path);
70
78
  setTreeData(newTrees);
71
79
  } }, item.path.join('-')))) }));
@@ -8,12 +8,14 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
8
8
  const [treeData, setTreeData] = useState(trees);
9
9
  const flattenTree = (nodes, depth = 0) => {
10
10
  const result = [];
11
- const traverse = (items, currentDepth, parentPath = []) => {
11
+ const traverse = (items, currentDepth, parentPath = [], ancestorIsLast = []) => {
12
12
  items.forEach((item, index) => {
13
+ const isLast = index === items.length - 1;
14
+ const currentIsLast = [...ancestorIsLast, isLast];
13
15
  const currentPath = [...parentPath, index];
14
- result.push({ node: item, depth: currentDepth, path: currentPath });
16
+ result.push({ node: item, depth: currentDepth, path: currentPath, isLastAtDepth: currentIsLast });
15
17
  if (item.expanded && item.children) {
16
- traverse(item.children, currentDepth + 1, currentPath);
18
+ traverse(item.children, currentDepth + 1, currentPath, currentIsLast);
17
19
  }
18
20
  });
19
21
  };
@@ -30,7 +32,9 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
30
32
  }
31
33
  else if (key.rightArrow) {
32
34
  const current = flatItems[selectedIndex];
33
- if (current.node.type === 'directory' && !current.node.expanded) {
35
+ if ((current.node.type === 'directory' || current.node.type === 'skill-directory') &&
36
+ !current.node.expanded &&
37
+ current.node.children) {
34
38
  const newTrees = updateNodeAtPath(treeData, current.path, (node) => ({
35
39
  ...node,
36
40
  expanded: true,
@@ -40,7 +44,8 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
40
44
  }
41
45
  else if (key.leftArrow) {
42
46
  const current = flatItems[selectedIndex];
43
- if (current.node.type === 'directory' && current.node.expanded) {
47
+ if ((current.node.type === 'directory' || current.node.type === 'skill-directory') &&
48
+ current.node.expanded) {
44
49
  const newTrees = updateNodeAtPath(treeData, current.path, (node) => ({
45
50
  ...node,
46
51
  expanded: false,
@@ -50,6 +55,9 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
50
55
  }
51
56
  else if (input === ' ') {
52
57
  const current = flatItems[selectedIndex];
58
+ if (current.node.disabled) {
59
+ return;
60
+ }
53
61
  const newTrees = toggleNodeSelection(treeData, current.path);
54
62
  setTreeData(newTrees);
55
63
  }
@@ -63,7 +71,7 @@ const TreeSelect = ({ trees, onSubmit, onCancel, onRefresh, }) => {
63
71
  onCancel();
64
72
  }
65
73
  });
66
- return (jsx(Box, { flexDirection: "column", children: flatItems.map((item, index) => (jsx(AssetTreeNode, { node: item.node, depth: item.depth, isSelected: index === selectedIndex, onToggle: () => {
74
+ return (jsx(Box, { flexDirection: "column", children: flatItems.map((item, index) => (jsx(AssetTreeNode, { node: item.node, isLastAtDepth: item.isLastAtDepth, isSelected: index === selectedIndex, onToggle: () => {
67
75
  const newTrees = toggleNodeSelection(treeData, item.path);
68
76
  setTreeData(newTrees);
69
77
  } }, item.path.join('-')))) }));
@@ -1,4 +1,4 @@
1
- import type { AssetStructure, AssetsConfig } from '../utils/types';
1
+ import type { AssetStructure, AssetsConfig } from '../utils/types.js';
2
2
  import { DEFAULT_ASSET_TYPES } from './constants';
3
3
  export { DEFAULT_ASSET_TYPES };
4
4
  /**
package/dist/core/cli.cjs CHANGED
@@ -7,6 +7,7 @@ var remove = require('../commands/remove.cjs');
7
7
  var status = require('../commands/status.cjs');
8
8
  var migrate = require('../commands/migrate.cjs');
9
9
  var add = require('../commands/add.cjs');
10
+ var update = require('../commands/update.cjs');
10
11
  var version = require('../version.cjs');
11
12
 
12
13
  const createProgram = () => {
@@ -81,6 +82,23 @@ const createProgram = () => {
81
82
  .action(async (opts) => {
82
83
  await migrate.runMigrateCommand({ dryRun: opts.dryRun });
83
84
  });
85
+ program
86
+ .command('update')
87
+ .description('Update package metadata in .sync-meta.json')
88
+ .option('-p, --package <name>', 'Package name to update (default: all)')
89
+ .option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
90
+ .option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from')
91
+ .option('--dry-run', 'Preview changes without writing files', false)
92
+ .option('--sync', 'Re-sync files after updating metadata', false)
93
+ .action(async (opts) => {
94
+ await update.runUpdateCommand({
95
+ package: opts.package,
96
+ local: opts.local,
97
+ ref: opts.ref,
98
+ dryRun: opts.dryRun,
99
+ sync: opts.sync,
100
+ });
101
+ });
84
102
  return program;
85
103
  };
86
104
  const run = async () => {
package/dist/core/cli.mjs CHANGED
@@ -5,6 +5,7 @@ import { runRemoveCommand } from '../commands/remove.mjs';
5
5
  import { runStatusCommand } from '../commands/status.mjs';
6
6
  import { runMigrateCommand } from '../commands/migrate.mjs';
7
7
  import { runAddCommand } from '../commands/add.mjs';
8
+ import { runUpdateCommand } from '../commands/update.mjs';
8
9
  import { VERSION } from '../version.mjs';
9
10
 
10
11
  const createProgram = () => {
@@ -79,6 +80,23 @@ const createProgram = () => {
79
80
  .action(async (opts) => {
80
81
  await runMigrateCommand({ dryRun: opts.dryRun });
81
82
  });
83
+ program
84
+ .command('update')
85
+ .description('Update package metadata in .sync-meta.json')
86
+ .option('-p, --package <name>', 'Package name to update (default: all)')
87
+ .option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
88
+ .option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from')
89
+ .option('--dry-run', 'Preview changes without writing files', false)
90
+ .option('--sync', 'Re-sync files after updating metadata', false)
91
+ .action(async (opts) => {
92
+ await runUpdateCommand({
93
+ package: opts.package,
94
+ local: opts.local,
95
+ ref: opts.ref,
96
+ dryRun: opts.dryRun,
97
+ sync: opts.sync,
98
+ });
99
+ });
82
100
  return program;
83
101
  };
84
102
  const run = async () => {
@@ -8,7 +8,10 @@ const META_FILES = {
8
8
  UNIFIED_SYNC_META: '.claude/.sync-meta.json',
9
9
  };
10
10
  const SCHEMA_VERSIONS = {
11
- UNIFIED_SYNC_META: version.VERSION};
11
+ UNIFIED_SYNC_META: version.VERSION,
12
+ LEGACY_SYNC_META: '1.0.0',
13
+ SKILL_UNIT_FORMAT: '2',
14
+ };
12
15
  const DEFAULT_ASSET_TYPES = ['commands', 'skills', 'agents'];
13
16
  const FS_PATTERNS = {
14
17
  GITHUB_HTTPS_URL: /https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
@@ -22,9 +22,16 @@ 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.3";
25
+ readonly UNIFIED_SYNC_META: "0.0.5";
26
26
  readonly LEGACY_SYNC_META: "1.0.0";
27
+ readonly SKILL_UNIT_FORMAT: "2";
27
28
  };
29
+ /**
30
+ * Schema version for SkillUnit-based metadata format.
31
+ * Separate from package VERSION to allow independent format evolution.
32
+ * Used to detect whether migration from old format is needed.
33
+ */
34
+ export declare const SKILL_UNIT_SCHEMA_VERSION: "2";
28
35
  /**
29
36
  * Default asset types (exported for backward compatibility)
30
37
  */
@@ -6,7 +6,10 @@ const META_FILES = {
6
6
  UNIFIED_SYNC_META: '.claude/.sync-meta.json',
7
7
  };
8
8
  const SCHEMA_VERSIONS = {
9
- UNIFIED_SYNC_META: VERSION};
9
+ UNIFIED_SYNC_META: VERSION,
10
+ LEGACY_SYNC_META: '1.0.0',
11
+ SKILL_UNIT_FORMAT: '2',
12
+ };
10
13
  const DEFAULT_ASSET_TYPES = ['commands', 'skills', 'agents'];
11
14
  const FS_PATTERNS = {
12
15
  GITHUB_HTTPS_URL: /https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
@@ -58,24 +58,30 @@ const cleanFlatAssetFiles = (cwd, assetType, prefix, existingMeta) => {
58
58
  const packageInfo = existingMeta.packages[prefix];
59
59
  const filesToRemove = packageInfo.files[assetType];
60
60
  if (Array.isArray(filesToRemove)) {
61
- for (const fileMapping of filesToRemove) {
62
- const fileName = typeof fileMapping === 'string'
63
- ? fileMapping
64
- : fileMapping.transformed;
65
- const filePath = path.join(destDir, fileName);
66
- if (io.fileExists(filePath)) {
67
- fs.rmSync(filePath, { force: true });
61
+ for (const unit of filesToRemove) {
62
+ const fileName = unit.transformed ?? unit.name;
63
+ if (unit.isDirectory) {
64
+ const dirPath = path.join(destDir, fileName);
65
+ if (io.fileExists(dirPath)) {
66
+ fs.rmSync(dirPath, { recursive: true, force: true });
67
+ }
68
+ }
69
+ else {
70
+ const filePath = path.join(destDir, fileName);
71
+ if (io.fileExists(filePath)) {
72
+ fs.rmSync(filePath, { force: true });
73
+ }
68
74
  }
69
75
  }
70
76
  }
71
77
  }
72
78
  else {
73
79
  const pattern = `${prefix}_`;
74
- const files = io.listDirectory(destDir);
75
- for (const file of files) {
76
- if (file.startsWith(pattern) && file.endsWith('.md')) {
77
- const filePath = path.join(destDir, file);
78
- fs.rmSync(filePath, { force: true });
80
+ const entries = io.listDirectory(destDir);
81
+ for (const entry of entries) {
82
+ if (entry.startsWith(pattern)) {
83
+ const entryPath = path.join(destDir, entry);
84
+ fs.rmSync(entryPath, { recursive: true, force: true });
79
85
  }
80
86
  }
81
87
  }
@@ -1,4 +1,4 @@
1
- import type { AssetType, SyncMeta, UnifiedSyncMeta } from '../utils/types';
1
+ import type { AssetType, SyncMeta, UnifiedSyncMeta } from '../utils/types.js';
2
2
  import { ensureDirectory, writeTextFile } from './io';
3
3
  /**
4
4
  * Ensure directory exists (creates recursively if needed)
@@ -71,7 +71,8 @@ export declare const createSyncMeta: (version: string, files: string[]) => SyncM
71
71
  export declare const writeFlatAssetFile: (cwd: string, assetType: AssetType, flatFileName: string, content: string) => void;
72
72
  /**
73
73
  * Clean flat asset files with specific prefix
74
- * Removes only files belonging to the specified package, preserving others
74
+ * Removes only files belonging to the specified package, preserving others.
75
+ * Handles both single flat files (prefix_file.md) and directory-based skills (prefix_dir/).
75
76
  * @param cwd - Current working directory
76
77
  * @param assetType - Asset type (commands, skills, agents, or any custom string)
77
78
  * @param prefix - Package prefix (e.g., "canard-schemaForm")
@@ -56,24 +56,30 @@ const cleanFlatAssetFiles = (cwd, assetType, prefix, existingMeta) => {
56
56
  const packageInfo = existingMeta.packages[prefix];
57
57
  const filesToRemove = packageInfo.files[assetType];
58
58
  if (Array.isArray(filesToRemove)) {
59
- for (const fileMapping of filesToRemove) {
60
- const fileName = typeof fileMapping === 'string'
61
- ? fileMapping
62
- : fileMapping.transformed;
63
- const filePath = join(destDir, fileName);
64
- if (fileExists(filePath)) {
65
- rmSync(filePath, { force: true });
59
+ for (const unit of filesToRemove) {
60
+ const fileName = unit.transformed ?? unit.name;
61
+ if (unit.isDirectory) {
62
+ const dirPath = join(destDir, fileName);
63
+ if (fileExists(dirPath)) {
64
+ rmSync(dirPath, { recursive: true, force: true });
65
+ }
66
+ }
67
+ else {
68
+ const filePath = join(destDir, fileName);
69
+ if (fileExists(filePath)) {
70
+ rmSync(filePath, { force: true });
71
+ }
66
72
  }
67
73
  }
68
74
  }
69
75
  }
70
76
  else {
71
77
  const pattern = `${prefix}_`;
72
- const files = listDirectory(destDir);
73
- for (const file of files) {
74
- if (file.startsWith(pattern) && file.endsWith('.md')) {
75
- const filePath = join(destDir, file);
76
- rmSync(filePath, { force: true });
78
+ const entries = listDirectory(destDir);
79
+ for (const entry of entries) {
80
+ if (entry.startsWith(pattern)) {
81
+ const entryPath = join(destDir, entry);
82
+ rmSync(entryPath, { recursive: true, force: true });
77
83
  }
78
84
  }
79
85
  }
@@ -37,17 +37,44 @@ const fetchDirectoryContents = async (repoInfo, path, tag) => {
37
37
  if (!response.ok)
38
38
  throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
39
39
  const data = await response.json();
40
- return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'));
40
+ return data.filter((entry) => (entry.type === 'file' && entry.name.endsWith('.md')) ||
41
+ entry.type === 'dir');
42
+ };
43
+ const expandDirectoryEntries = async (repoInfo, parentPath, entries, tag, prefix = '') => {
44
+ const result = [];
45
+ for (const entry of entries) {
46
+ const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
47
+ if (entry.type === 'file') {
48
+ result.push({
49
+ ...entry,
50
+ name: prefix ? entryPrefix : entry.name,
51
+ });
52
+ }
53
+ else if (entry.type === 'dir') {
54
+ const subEntries = await fetchDirectoryContents(repoInfo, `${parentPath}/${entry.name}`, tag);
55
+ if (subEntries) {
56
+ const expanded = await expandDirectoryEntries(repoInfo, `${parentPath}/${entry.name}`, subEntries, tag, entryPrefix);
57
+ result.push(...expanded);
58
+ }
59
+ }
60
+ }
61
+ return result;
41
62
  };
42
63
  const fetchAssetFiles = async (repoInfo, assetPath, tag, assetTypes) => {
43
64
  const basePath = repoInfo.directory
44
65
  ? `${repoInfo.directory}/${assetPath}`
45
66
  : assetPath;
46
67
  const fetchPromises = assetTypes.map((assetType) => fetchDirectoryContents(repoInfo, `${basePath}/${assetType}`, tag));
47
- const results = await Promise.all(fetchPromises);
68
+ const rawResults = await Promise.all(fetchPromises);
69
+ const expandedResults = await Promise.all(rawResults.map((entries, index) => {
70
+ if (!entries)
71
+ return Promise.resolve([]);
72
+ const assetDirPath = `${basePath}/${assetTypes[index]}`;
73
+ return expandDirectoryEntries(repoInfo, assetDirPath, entries, tag);
74
+ }));
48
75
  const assetFiles = {};
49
76
  assetTypes.forEach((assetType, index) => {
50
- assetFiles[assetType] = results[index] || [];
77
+ assetFiles[assetType] = expandedResults[index] || [];
51
78
  });
52
79
  return assetFiles;
53
80
  };
@@ -83,5 +110,6 @@ exports.NotFoundError = NotFoundError;
83
110
  exports.RateLimitError = RateLimitError;
84
111
  exports.downloadAssetFiles = downloadAssetFiles;
85
112
  exports.downloadFile = downloadFile;
113
+ exports.expandDirectoryEntries = expandDirectoryEntries;
86
114
  exports.fetchAssetFiles = fetchAssetFiles;
87
115
  exports.fetchDirectoryContents = fetchDirectoryContents;
@@ -1,4 +1,4 @@
1
- import type { AssetType, GitHubEntry, GitHubRepoInfo } from '../utils/types';
1
+ import type { AssetType, GitHubEntry, GitHubRepoInfo } from '../utils/types.js';
2
2
  /**
3
3
  * Error thrown when GitHub API rate limit is exceeded
4
4
  */
@@ -19,6 +19,19 @@ export declare class NotFoundError extends Error {
19
19
  * @returns Array of GitHubEntry or null if directory doesn't exist
20
20
  */
21
21
  export declare const fetchDirectoryContents: (repoInfo: GitHubRepoInfo, path: string, tag: string) => Promise<GitHubEntry[] | null>;
22
+ /**
23
+ * Expand directory entries into flat file entries with recursive traversal.
24
+ * Fetches contents of each directory and prefixes file names with the directory path.
25
+ * Recursively traverses subdirectories to collect all nested files.
26
+ *
27
+ * @param repoInfo - GitHub repository information
28
+ * @param parentPath - Parent directory path in the repository
29
+ * @param entries - Array of GitHubEntry (may contain both file and dir types)
30
+ * @param tag - Git tag or ref to fetch from
31
+ * @param prefix - Accumulated path prefix for nested entries
32
+ * @returns Flat array of file GitHubEntry with dir-prefixed names
33
+ */
34
+ export declare const expandDirectoryEntries: (repoInfo: GitHubRepoInfo, parentPath: string, entries: GitHubEntry[], tag: string, prefix?: string) => Promise<GitHubEntry[]>;
22
35
  /**
23
36
  * Fetch asset files dynamically from GitHub
24
37
  * @param repoInfo - GitHub repository information
@@ -35,17 +35,44 @@ const fetchDirectoryContents = async (repoInfo, path, tag) => {
35
35
  if (!response.ok)
36
36
  throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
37
37
  const data = await response.json();
38
- return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'));
38
+ return data.filter((entry) => (entry.type === 'file' && entry.name.endsWith('.md')) ||
39
+ entry.type === 'dir');
40
+ };
41
+ const expandDirectoryEntries = async (repoInfo, parentPath, entries, tag, prefix = '') => {
42
+ const result = [];
43
+ for (const entry of entries) {
44
+ const entryPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
45
+ if (entry.type === 'file') {
46
+ result.push({
47
+ ...entry,
48
+ name: prefix ? entryPrefix : entry.name,
49
+ });
50
+ }
51
+ else if (entry.type === 'dir') {
52
+ const subEntries = await fetchDirectoryContents(repoInfo, `${parentPath}/${entry.name}`, tag);
53
+ if (subEntries) {
54
+ const expanded = await expandDirectoryEntries(repoInfo, `${parentPath}/${entry.name}`, subEntries, tag, entryPrefix);
55
+ result.push(...expanded);
56
+ }
57
+ }
58
+ }
59
+ return result;
39
60
  };
40
61
  const fetchAssetFiles = async (repoInfo, assetPath, tag, assetTypes) => {
41
62
  const basePath = repoInfo.directory
42
63
  ? `${repoInfo.directory}/${assetPath}`
43
64
  : assetPath;
44
65
  const fetchPromises = assetTypes.map((assetType) => fetchDirectoryContents(repoInfo, `${basePath}/${assetType}`, tag));
45
- const results = await Promise.all(fetchPromises);
66
+ const rawResults = await Promise.all(fetchPromises);
67
+ const expandedResults = await Promise.all(rawResults.map((entries, index) => {
68
+ if (!entries)
69
+ return Promise.resolve([]);
70
+ const assetDirPath = `${basePath}/${assetTypes[index]}`;
71
+ return expandDirectoryEntries(repoInfo, assetDirPath, entries, tag);
72
+ }));
46
73
  const assetFiles = {};
47
74
  assetTypes.forEach((assetType, index) => {
48
- assetFiles[assetType] = results[index] || [];
75
+ assetFiles[assetType] = expandedResults[index] || [];
49
76
  });
50
77
  return assetFiles;
51
78
  };
@@ -77,4 +104,4 @@ const downloadAssetFiles = async (repoInfo, assetPath, assetType, entries, tag)
77
104
  return results;
78
105
  };
79
106
 
80
- export { NotFoundError, RateLimitError, downloadAssetFiles, downloadFile, fetchAssetFiles, fetchDirectoryContents };
107
+ export { NotFoundError, RateLimitError, downloadAssetFiles, downloadFile, expandDirectoryEntries, fetchAssetFiles, fetchDirectoryContents };
@@ -3,6 +3,7 @@
3
3
  var fs = require('node:fs');
4
4
  var path = require('node:path');
5
5
  var nameTransform = require('../utils/nameTransform.cjs');
6
+ var packageName = require('../utils/packageName.cjs');
6
7
  var filesystem = require('./filesystem.cjs');
7
8
  var syncMeta = require('./syncMeta.cjs');
8
9
 
@@ -62,8 +63,8 @@ async function migrateToFlat(cwd, options = {}) {
62
63
  });
63
64
  for (const packageDir of packageDirs) {
64
65
  const packagePath = path.join(scopePath, packageDir);
65
- const packageName = `${scopeDir}/${packageDir}`;
66
- console.log(` 📦 Processing ${packageName}...`);
66
+ const packageName$1 = `${scopeDir}/${packageDir}`;
67
+ console.log(` 📦 Processing ${packageName$1}...`);
67
68
  try {
68
69
  const metaPath = path.join(packagePath, '.sync-meta.json');
69
70
  let legacyMeta = null;
@@ -75,9 +76,9 @@ async function migrateToFlat(cwd, options = {}) {
75
76
  console.log(` ⚠️ No .sync-meta.json found, skipping`);
76
77
  continue;
77
78
  }
78
- const prefix = nameTransform.packageNameToPrefix(packageName);
79
- const commandFiles = [];
80
- const fileMappings = [];
79
+ const prefix = packageName.packageNameToPrefix(packageName$1);
80
+ const commandUnits = [];
81
+ const skillUnits = [];
81
82
  for (const fileName of legacyMeta.files) {
82
83
  const sourcePath = path.join(packagePath, fileName);
83
84
  if (!fs.existsSync(sourcePath)) {
@@ -93,14 +94,10 @@ async function migrateToFlat(cwd, options = {}) {
93
94
  else {
94
95
  console.log(` 🔍 Would migrate: ${fileName}`);
95
96
  }
96
- commandFiles.push(fileName);
97
+ commandUnits.push({ name: fileName, isDirectory: false });
97
98
  }
98
99
  else {
99
100
  const flatFileName = nameTransform.toFlatFileName(prefix, fileName);
100
- fileMappings.push({
101
- original: fileName,
102
- transformed: flatFileName,
103
- });
104
101
  if (!dryRun) {
105
102
  const content = fs.readFileSync(sourcePath, 'utf-8');
106
103
  filesystem.writeFlatAssetFile(cwd, assetType, flatFileName, content);
@@ -109,14 +106,19 @@ async function migrateToFlat(cwd, options = {}) {
109
106
  else {
110
107
  console.log(` 🔍 Would migrate: ${fileName} → ${flatFileName}`);
111
108
  }
109
+ skillUnits.push({
110
+ name: fileName,
111
+ isDirectory: false,
112
+ transformed: flatFileName,
113
+ });
112
114
  }
113
115
  }
114
116
  const packageInfo = {
115
- originalName: packageName,
117
+ originalName: packageName$1,
116
118
  version: legacyMeta.version,
117
119
  files: {
118
- commands: assetType === 'commands' ? commandFiles : [],
119
- skills: assetType === 'skills' ? fileMappings : [],
120
+ commands: assetType === 'commands' ? commandUnits : [],
121
+ skills: assetType === 'skills' ? skillUnits : [],
120
122
  agents: [],
121
123
  },
122
124
  };
@@ -136,11 +138,11 @@ async function migrateToFlat(cwd, options = {}) {
136
138
  }
137
139
  }
138
140
  unifiedMeta = syncMeta.updatePackageInMeta(unifiedMeta, prefix, packageInfo);
139
- migratedPackages.push(packageName);
141
+ migratedPackages.push(packageName$1);
140
142
  legacyDirs.push(packagePath);
141
143
  }
142
144
  catch (error) {
143
- const errorMsg = `Failed to migrate ${packageName}: ${error}`;
145
+ const errorMsg = `Failed to migrate ${packageName$1}: ${error}`;
144
146
  console.error(` ❌ ${errorMsg}`);
145
147
  errors.push(errorMsg);
146
148
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, statSync, readFileSync, rmSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { packageNameToPrefix, toFlatFileName } from '../utils/nameTransform.mjs';
3
+ import { toFlatFileName } from '../utils/nameTransform.mjs';
4
+ import { packageNameToPrefix } from '../utils/packageName.mjs';
4
5
  import { writeFlatAssetFile } from './filesystem.mjs';
5
6
  import { readUnifiedSyncMeta, createEmptyUnifiedMeta, updatePackageInMeta, writeUnifiedSyncMeta } from './syncMeta.mjs';
6
7
 
@@ -74,8 +75,8 @@ async function migrateToFlat(cwd, options = {}) {
74
75
  continue;
75
76
  }
76
77
  const prefix = packageNameToPrefix(packageName);
77
- const commandFiles = [];
78
- const fileMappings = [];
78
+ const commandUnits = [];
79
+ const skillUnits = [];
79
80
  for (const fileName of legacyMeta.files) {
80
81
  const sourcePath = join(packagePath, fileName);
81
82
  if (!existsSync(sourcePath)) {
@@ -91,14 +92,10 @@ async function migrateToFlat(cwd, options = {}) {
91
92
  else {
92
93
  console.log(` 🔍 Would migrate: ${fileName}`);
93
94
  }
94
- commandFiles.push(fileName);
95
+ commandUnits.push({ name: fileName, isDirectory: false });
95
96
  }
96
97
  else {
97
98
  const flatFileName = toFlatFileName(prefix, fileName);
98
- fileMappings.push({
99
- original: fileName,
100
- transformed: flatFileName,
101
- });
102
99
  if (!dryRun) {
103
100
  const content = readFileSync(sourcePath, 'utf-8');
104
101
  writeFlatAssetFile(cwd, assetType, flatFileName, content);
@@ -107,14 +104,19 @@ async function migrateToFlat(cwd, options = {}) {
107
104
  else {
108
105
  console.log(` 🔍 Would migrate: ${fileName} → ${flatFileName}`);
109
106
  }
107
+ skillUnits.push({
108
+ name: fileName,
109
+ isDirectory: false,
110
+ transformed: flatFileName,
111
+ });
110
112
  }
111
113
  }
112
114
  const packageInfo = {
113
115
  originalName: packageName,
114
116
  version: legacyMeta.version,
115
117
  files: {
116
- commands: assetType === 'commands' ? commandFiles : [],
117
- skills: assetType === 'skills' ? fileMappings : [],
118
+ commands: assetType === 'commands' ? commandUnits : [],
119
+ skills: assetType === 'skills' ? skillUnits : [],
118
120
  agents: [],
119
121
  },
120
122
  };