@slats/claude-assets-sync 0.0.1

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.
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ class RateLimitError extends Error {
4
+ constructor() {
5
+ super('GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable to increase the limit.');
6
+ this.name = 'RateLimitError';
7
+ }
8
+ }
9
+ class NotFoundError extends Error {
10
+ constructor(resource) {
11
+ super(`GitHub resource not found: ${resource}`);
12
+ this.name = 'NotFoundError';
13
+ }
14
+ }
15
+ const getHeaders = () => {
16
+ const headers = {
17
+ Accept: 'application/vnd.github.v3+json',
18
+ 'User-Agent': 'claude-assets-sync',
19
+ };
20
+ const token = process.env.GITHUB_TOKEN;
21
+ if (token)
22
+ headers.Authorization = `Bearer ${token}`;
23
+ return headers;
24
+ };
25
+ const fetchDirectoryContents = async (repoInfo, path, tag) => {
26
+ const url = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents/${path}?ref=${encodeURIComponent(tag)}`;
27
+ const response = await fetch(url, {
28
+ headers: getHeaders(),
29
+ });
30
+ if (response.status === 403) {
31
+ const remaining = response.headers.get('x-ratelimit-remaining');
32
+ if (remaining === '0')
33
+ throw new RateLimitError();
34
+ }
35
+ if (response.status === 404)
36
+ return null;
37
+ if (!response.ok)
38
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
39
+ const data = await response.json();
40
+ return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'));
41
+ };
42
+ const fetchAssetFiles = async (repoInfo, assetPath, tag) => {
43
+ const basePath = repoInfo.directory
44
+ ? `${repoInfo.directory}/${assetPath}`
45
+ : assetPath;
46
+ const commandsPath = `${basePath}/commands`;
47
+ const skillsPath = `${basePath}/skills`;
48
+ const [commandsEntries, skillsEntries] = await Promise.all([
49
+ fetchDirectoryContents(repoInfo, commandsPath, tag),
50
+ fetchDirectoryContents(repoInfo, skillsPath, tag),
51
+ ]);
52
+ return {
53
+ commands: commandsEntries || [],
54
+ skills: skillsEntries || [],
55
+ };
56
+ };
57
+ const downloadFile = async (repoInfo, filePath, tag) => {
58
+ const url = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/${encodeURIComponent(tag)}/${filePath}`;
59
+ const response = await fetch(url, {
60
+ headers: {
61
+ 'User-Agent': 'claude-assets-sync',
62
+ },
63
+ });
64
+ if (response.status === 404) {
65
+ throw new NotFoundError(filePath);
66
+ }
67
+ if (!response.ok) {
68
+ throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
69
+ }
70
+ return response.text();
71
+ };
72
+ const downloadAssetFiles = async (repoInfo, assetPath, assetType, entries, tag) => {
73
+ const basePath = repoInfo.directory
74
+ ? `${repoInfo.directory}/${assetPath}/${assetType}`
75
+ : `${assetPath}/${assetType}`;
76
+ const results = new Map();
77
+ for (const entry of entries) {
78
+ const filePath = `${basePath}/${entry.name}`;
79
+ const content = await downloadFile(repoInfo, filePath, tag);
80
+ results.set(entry.name, content);
81
+ }
82
+ return results;
83
+ };
84
+
85
+ exports.NotFoundError = NotFoundError;
86
+ exports.RateLimitError = RateLimitError;
87
+ exports.downloadAssetFiles = downloadAssetFiles;
88
+ exports.downloadFile = downloadFile;
89
+ exports.fetchAssetFiles = fetchAssetFiles;
90
+ exports.fetchDirectoryContents = fetchDirectoryContents;
@@ -0,0 +1,50 @@
1
+ import type { AssetType, GitHubEntry, GitHubRepoInfo } from '../utils/types';
2
+ /**
3
+ * Error thrown when GitHub API rate limit is exceeded
4
+ */
5
+ export declare class RateLimitError extends Error {
6
+ constructor();
7
+ }
8
+ /**
9
+ * Error thrown when GitHub resource is not found
10
+ */
11
+ export declare class NotFoundError extends Error {
12
+ constructor(resource: string);
13
+ }
14
+ /**
15
+ * Fetch directory contents from GitHub API
16
+ * @param repoInfo - GitHub repository information
17
+ * @param path - Path to the directory
18
+ * @param tag - Git tag or ref to fetch from
19
+ * @returns Array of GitHubEntry or null if directory doesn't exist
20
+ */
21
+ export declare const fetchDirectoryContents: (repoInfo: GitHubRepoInfo, path: string, tag: string) => Promise<GitHubEntry[] | null>;
22
+ /**
23
+ * Fetch asset files (commands and skills) from GitHub
24
+ * @param repoInfo - GitHub repository information
25
+ * @param assetPath - Base path to Claude assets (e.g., "docs/claude")
26
+ * @param tag - Git tag or ref to fetch from
27
+ * @returns Object with commands and skills file lists
28
+ */
29
+ export declare const fetchAssetFiles: (repoInfo: GitHubRepoInfo, assetPath: string, tag: string) => Promise<{
30
+ commands: GitHubEntry[];
31
+ skills: GitHubEntry[];
32
+ }>;
33
+ /**
34
+ * Download file content from raw.githubusercontent.com
35
+ * @param repoInfo - GitHub repository information
36
+ * @param filePath - Full path to the file
37
+ * @param tag - Git tag or ref
38
+ * @returns File content as string
39
+ */
40
+ export declare const downloadFile: (repoInfo: GitHubRepoInfo, filePath: string, tag: string) => Promise<string>;
41
+ /**
42
+ * Download multiple files from a specific asset type
43
+ * @param repoInfo - GitHub repository information
44
+ * @param assetPath - Base path to Claude assets
45
+ * @param assetType - Type of asset (commands or skills)
46
+ * @param entries - Array of GitHubEntry to download
47
+ * @param tag - Git tag or ref
48
+ * @returns Map of filename to content
49
+ */
50
+ export declare const downloadAssetFiles: (repoInfo: GitHubRepoInfo, assetPath: string, assetType: AssetType, entries: GitHubEntry[], tag: string) => Promise<Map<string, string>>;
@@ -0,0 +1,83 @@
1
+ class RateLimitError extends Error {
2
+ constructor() {
3
+ super('GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable to increase the limit.');
4
+ this.name = 'RateLimitError';
5
+ }
6
+ }
7
+ class NotFoundError extends Error {
8
+ constructor(resource) {
9
+ super(`GitHub resource not found: ${resource}`);
10
+ this.name = 'NotFoundError';
11
+ }
12
+ }
13
+ const getHeaders = () => {
14
+ const headers = {
15
+ Accept: 'application/vnd.github.v3+json',
16
+ 'User-Agent': 'claude-assets-sync',
17
+ };
18
+ const token = process.env.GITHUB_TOKEN;
19
+ if (token)
20
+ headers.Authorization = `Bearer ${token}`;
21
+ return headers;
22
+ };
23
+ const fetchDirectoryContents = async (repoInfo, path, tag) => {
24
+ const url = `https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/contents/${path}?ref=${encodeURIComponent(tag)}`;
25
+ const response = await fetch(url, {
26
+ headers: getHeaders(),
27
+ });
28
+ if (response.status === 403) {
29
+ const remaining = response.headers.get('x-ratelimit-remaining');
30
+ if (remaining === '0')
31
+ throw new RateLimitError();
32
+ }
33
+ if (response.status === 404)
34
+ return null;
35
+ if (!response.ok)
36
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
37
+ const data = await response.json();
38
+ return data.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'));
39
+ };
40
+ const fetchAssetFiles = async (repoInfo, assetPath, tag) => {
41
+ const basePath = repoInfo.directory
42
+ ? `${repoInfo.directory}/${assetPath}`
43
+ : assetPath;
44
+ const commandsPath = `${basePath}/commands`;
45
+ const skillsPath = `${basePath}/skills`;
46
+ const [commandsEntries, skillsEntries] = await Promise.all([
47
+ fetchDirectoryContents(repoInfo, commandsPath, tag),
48
+ fetchDirectoryContents(repoInfo, skillsPath, tag),
49
+ ]);
50
+ return {
51
+ commands: commandsEntries || [],
52
+ skills: skillsEntries || [],
53
+ };
54
+ };
55
+ const downloadFile = async (repoInfo, filePath, tag) => {
56
+ const url = `https://raw.githubusercontent.com/${repoInfo.owner}/${repoInfo.repo}/${encodeURIComponent(tag)}/${filePath}`;
57
+ const response = await fetch(url, {
58
+ headers: {
59
+ 'User-Agent': 'claude-assets-sync',
60
+ },
61
+ });
62
+ if (response.status === 404) {
63
+ throw new NotFoundError(filePath);
64
+ }
65
+ if (!response.ok) {
66
+ throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
67
+ }
68
+ return response.text();
69
+ };
70
+ const downloadAssetFiles = async (repoInfo, assetPath, assetType, entries, tag) => {
71
+ const basePath = repoInfo.directory
72
+ ? `${repoInfo.directory}/${assetPath}/${assetType}`
73
+ : `${assetPath}/${assetType}`;
74
+ const results = new Map();
75
+ for (const entry of entries) {
76
+ const filePath = `${basePath}/${entry.name}`;
77
+ const content = await downloadFile(repoInfo, filePath, tag);
78
+ results.set(entry.name, content);
79
+ }
80
+ return results;
81
+ };
82
+
83
+ export { NotFoundError, RateLimitError, downloadAssetFiles, downloadFile, fetchAssetFiles, fetchDirectoryContents };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ var logger = require('../utils/logger.cjs');
4
+ var _package = require('../utils/package.cjs');
5
+ var filesystem = require('./filesystem.cjs');
6
+ var github = require('./github.cjs');
7
+
8
+ const syncPackage = async (packageName, options, cwd = process.cwd(), outputDir) => {
9
+ logger.logger.packageStart(packageName);
10
+ try {
11
+ const destDir = outputDir ?? _package.findGitRoot(cwd) ?? cwd;
12
+ const packageInfo = options.local
13
+ ? _package.readLocalPackageJson(packageName, cwd)
14
+ : _package.readPackageJson(packageName, cwd);
15
+ if (!packageInfo) {
16
+ const location = options.local ? 'workspace' : 'node_modules';
17
+ return {
18
+ packageName,
19
+ success: false,
20
+ skipped: true,
21
+ reason: `Package not found in ${location}`,
22
+ };
23
+ }
24
+ if (!packageInfo.claude?.assetPath)
25
+ return {
26
+ packageName,
27
+ success: false,
28
+ skipped: true,
29
+ reason: 'Package does not have claude.assetPath in package.json',
30
+ };
31
+ const repoInfo = _package.parseGitHubRepo(packageInfo.repository);
32
+ if (!repoInfo) {
33
+ return {
34
+ packageName,
35
+ success: false,
36
+ skipped: true,
37
+ reason: 'Unable to parse GitHub repository URL',
38
+ };
39
+ }
40
+ if (!options.force &&
41
+ !filesystem.needsSync(destDir, packageName, packageInfo.version)) {
42
+ return {
43
+ packageName,
44
+ success: true,
45
+ skipped: true,
46
+ reason: `Already synced at version ${packageInfo.version}`,
47
+ };
48
+ }
49
+ const tag = options.ref ?? _package.buildVersionTag(packageName, packageInfo.version);
50
+ const assetPath = _package.buildAssetPath(packageInfo.claude.assetPath);
51
+ logger.logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
52
+ const { commands, skills } = await github.fetchAssetFiles(repoInfo, assetPath, tag);
53
+ if (commands.length === 0 && skills.length === 0)
54
+ return {
55
+ packageName,
56
+ success: false,
57
+ skipped: true,
58
+ reason: 'No commands or skills found in package',
59
+ };
60
+ logger.logger.step('Found', `${commands.length} commands, ${skills.length} skills`);
61
+ if (options.dryRun) {
62
+ if (commands.length > 0) {
63
+ logger.logger.step('Would sync commands to', filesystem.getDestinationDir(destDir, packageName, 'commands'));
64
+ commands.forEach((entry) => logger.logger.file('create', entry.name));
65
+ }
66
+ if (skills.length > 0) {
67
+ logger.logger.step('Would sync skills to', filesystem.getDestinationDir(destDir, packageName, 'skills'));
68
+ skills.forEach((entry) => logger.logger.file('create', entry.name));
69
+ }
70
+ return {
71
+ packageName,
72
+ success: true,
73
+ skipped: false,
74
+ syncedFiles: {
75
+ commands: commands.map((e) => e.name),
76
+ skills: skills.map((e) => e.name),
77
+ },
78
+ };
79
+ }
80
+ const syncedFiles = {
81
+ commands: [],
82
+ skills: [],
83
+ };
84
+ if (commands.length > 0) {
85
+ logger.logger.step('Downloading', 'commands');
86
+ const commandFiles = await github.downloadAssetFiles(repoInfo, assetPath, 'commands', commands, tag);
87
+ filesystem.cleanAssetDir(destDir, packageName, 'commands');
88
+ for (const [fileName, content] of commandFiles) {
89
+ filesystem.writeAssetFile(destDir, packageName, 'commands', fileName, content);
90
+ logger.logger.file('create', fileName);
91
+ syncedFiles.commands.push(fileName);
92
+ }
93
+ filesystem.writeSyncMeta(destDir, packageName, 'commands', filesystem.createSyncMeta(packageInfo.version, syncedFiles.commands));
94
+ }
95
+ if (skills.length > 0) {
96
+ logger.logger.step('Downloading', 'skills');
97
+ const skillFiles = await github.downloadAssetFiles(repoInfo, assetPath, 'skills', skills, tag);
98
+ filesystem.cleanAssetDir(destDir, packageName, 'skills');
99
+ for (const [fileName, content] of skillFiles) {
100
+ filesystem.writeAssetFile(destDir, packageName, 'skills', fileName, content);
101
+ logger.logger.file('create', fileName);
102
+ syncedFiles.skills.push(fileName);
103
+ }
104
+ filesystem.writeSyncMeta(destDir, packageName, 'skills', filesystem.createSyncMeta(packageInfo.version, syncedFiles.skills));
105
+ }
106
+ return {
107
+ packageName,
108
+ success: true,
109
+ skipped: false,
110
+ syncedFiles,
111
+ };
112
+ }
113
+ catch (error) {
114
+ if (error instanceof github.RateLimitError)
115
+ return {
116
+ packageName,
117
+ success: false,
118
+ skipped: false,
119
+ reason: error.message,
120
+ };
121
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
122
+ return {
123
+ packageName,
124
+ success: false,
125
+ skipped: false,
126
+ reason: message,
127
+ };
128
+ }
129
+ };
130
+ const syncPackages = async (packages, options, cwd = process.cwd()) => {
131
+ const results = [];
132
+ const gitRoot = _package.findGitRoot(cwd);
133
+ if (gitRoot)
134
+ logger.logger.info(`[Output] ${gitRoot}/.claude\n`);
135
+ for (const packageName of packages) {
136
+ const result = await syncPackage(packageName, options, cwd, gitRoot ?? undefined);
137
+ logger.logger.packageEnd(packageName, result);
138
+ results.push(result);
139
+ }
140
+ const summary = {
141
+ success: results.filter((r) => r.success && !r.skipped).length,
142
+ skipped: results.filter((r) => r.skipped).length,
143
+ failed: results.filter((r) => !r.success && !r.skipped).length,
144
+ };
145
+ logger.logger.summary(summary);
146
+ return results;
147
+ };
148
+
149
+ exports.syncPackage = syncPackage;
150
+ exports.syncPackages = syncPackages;
@@ -0,0 +1,17 @@
1
+ import type { CliOptions, SyncResult } from '../utils/types';
2
+ /**
3
+ * Sync Claude assets for a single package
4
+ * @param packageName - Package name to sync
5
+ * @param options - CLI options
6
+ * @param cwd - Current working directory
7
+ * @returns Sync result
8
+ */
9
+ export declare const syncPackage: (packageName: string, options: Pick<CliOptions, "force" | "dryRun" | "local" | "ref">, cwd?: string, outputDir?: string) => Promise<SyncResult>;
10
+ /**
11
+ * Sync Claude assets for multiple packages
12
+ * @param packages - List of package names to sync
13
+ * @param options - CLI options
14
+ * @param cwd - Current working directory
15
+ * @returns Array of sync results
16
+ */
17
+ export declare const syncPackages: (packages: string[], options: Pick<CliOptions, "force" | "dryRun" | "local" | "ref">, cwd?: string) => Promise<SyncResult[]>;
@@ -0,0 +1,147 @@
1
+ import { logger } from '../utils/logger.mjs';
2
+ import { findGitRoot, readLocalPackageJson, readPackageJson, parseGitHubRepo, buildVersionTag, buildAssetPath } from '../utils/package.mjs';
3
+ import { needsSync, getDestinationDir, cleanAssetDir, writeAssetFile, writeSyncMeta, createSyncMeta } from './filesystem.mjs';
4
+ import { fetchAssetFiles, downloadAssetFiles, RateLimitError } from './github.mjs';
5
+
6
+ const syncPackage = async (packageName, options, cwd = process.cwd(), outputDir) => {
7
+ logger.packageStart(packageName);
8
+ try {
9
+ const destDir = outputDir ?? findGitRoot(cwd) ?? cwd;
10
+ const packageInfo = options.local
11
+ ? readLocalPackageJson(packageName, cwd)
12
+ : readPackageJson(packageName, cwd);
13
+ if (!packageInfo) {
14
+ const location = options.local ? 'workspace' : 'node_modules';
15
+ return {
16
+ packageName,
17
+ success: false,
18
+ skipped: true,
19
+ reason: `Package not found in ${location}`,
20
+ };
21
+ }
22
+ if (!packageInfo.claude?.assetPath)
23
+ return {
24
+ packageName,
25
+ success: false,
26
+ skipped: true,
27
+ reason: 'Package does not have claude.assetPath in package.json',
28
+ };
29
+ const repoInfo = parseGitHubRepo(packageInfo.repository);
30
+ if (!repoInfo) {
31
+ return {
32
+ packageName,
33
+ success: false,
34
+ skipped: true,
35
+ reason: 'Unable to parse GitHub repository URL',
36
+ };
37
+ }
38
+ if (!options.force &&
39
+ !needsSync(destDir, packageName, packageInfo.version)) {
40
+ return {
41
+ packageName,
42
+ success: true,
43
+ skipped: true,
44
+ reason: `Already synced at version ${packageInfo.version}`,
45
+ };
46
+ }
47
+ const tag = options.ref ?? buildVersionTag(packageName, packageInfo.version);
48
+ const assetPath = buildAssetPath(packageInfo.claude.assetPath);
49
+ logger.step('Fetching', `asset list from GitHub (ref: ${tag})`);
50
+ const { commands, skills } = await fetchAssetFiles(repoInfo, assetPath, tag);
51
+ if (commands.length === 0 && skills.length === 0)
52
+ return {
53
+ packageName,
54
+ success: false,
55
+ skipped: true,
56
+ reason: 'No commands or skills found in package',
57
+ };
58
+ logger.step('Found', `${commands.length} commands, ${skills.length} skills`);
59
+ if (options.dryRun) {
60
+ if (commands.length > 0) {
61
+ logger.step('Would sync commands to', getDestinationDir(destDir, packageName, 'commands'));
62
+ commands.forEach((entry) => logger.file('create', entry.name));
63
+ }
64
+ if (skills.length > 0) {
65
+ logger.step('Would sync skills to', getDestinationDir(destDir, packageName, 'skills'));
66
+ skills.forEach((entry) => logger.file('create', entry.name));
67
+ }
68
+ return {
69
+ packageName,
70
+ success: true,
71
+ skipped: false,
72
+ syncedFiles: {
73
+ commands: commands.map((e) => e.name),
74
+ skills: skills.map((e) => e.name),
75
+ },
76
+ };
77
+ }
78
+ const syncedFiles = {
79
+ commands: [],
80
+ skills: [],
81
+ };
82
+ if (commands.length > 0) {
83
+ logger.step('Downloading', 'commands');
84
+ const commandFiles = await downloadAssetFiles(repoInfo, assetPath, 'commands', commands, tag);
85
+ cleanAssetDir(destDir, packageName, 'commands');
86
+ for (const [fileName, content] of commandFiles) {
87
+ writeAssetFile(destDir, packageName, 'commands', fileName, content);
88
+ logger.file('create', fileName);
89
+ syncedFiles.commands.push(fileName);
90
+ }
91
+ writeSyncMeta(destDir, packageName, 'commands', createSyncMeta(packageInfo.version, syncedFiles.commands));
92
+ }
93
+ if (skills.length > 0) {
94
+ logger.step('Downloading', 'skills');
95
+ const skillFiles = await downloadAssetFiles(repoInfo, assetPath, 'skills', skills, tag);
96
+ cleanAssetDir(destDir, packageName, 'skills');
97
+ for (const [fileName, content] of skillFiles) {
98
+ writeAssetFile(destDir, packageName, 'skills', fileName, content);
99
+ logger.file('create', fileName);
100
+ syncedFiles.skills.push(fileName);
101
+ }
102
+ writeSyncMeta(destDir, packageName, 'skills', createSyncMeta(packageInfo.version, syncedFiles.skills));
103
+ }
104
+ return {
105
+ packageName,
106
+ success: true,
107
+ skipped: false,
108
+ syncedFiles,
109
+ };
110
+ }
111
+ catch (error) {
112
+ if (error instanceof RateLimitError)
113
+ return {
114
+ packageName,
115
+ success: false,
116
+ skipped: false,
117
+ reason: error.message,
118
+ };
119
+ const message = error instanceof Error ? error.message : 'Unknown error occurred';
120
+ return {
121
+ packageName,
122
+ success: false,
123
+ skipped: false,
124
+ reason: message,
125
+ };
126
+ }
127
+ };
128
+ const syncPackages = async (packages, options, cwd = process.cwd()) => {
129
+ const results = [];
130
+ const gitRoot = findGitRoot(cwd);
131
+ if (gitRoot)
132
+ logger.info(`[Output] ${gitRoot}/.claude\n`);
133
+ for (const packageName of packages) {
134
+ const result = await syncPackage(packageName, options, cwd, gitRoot ?? undefined);
135
+ logger.packageEnd(packageName, result);
136
+ results.push(result);
137
+ }
138
+ const summary = {
139
+ success: results.filter((r) => r.success && !r.skipped).length,
140
+ skipped: results.filter((r) => r.skipped).length,
141
+ failed: results.filter((r) => !r.success && !r.skipped).length,
142
+ };
143
+ logger.summary(summary);
144
+ return results;
145
+ };
146
+
147
+ export { syncPackage, syncPackages };
package/dist/index.cjs ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var index = require('./cli/index.cjs');
5
+ var sync = require('./core/sync.cjs');
6
+
7
+ index.run().catch((error) => {
8
+ console.error('Fatal error:', error.message);
9
+ process.exit(1);
10
+ });
11
+
12
+ exports.createProgram = index.createProgram;
13
+ exports.run = index.run;
14
+ exports.syncPackage = sync.syncPackage;
15
+ exports.syncPackages = sync.syncPackages;
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export { syncPackage, syncPackages } from './core/sync';
3
+ export { createProgram, run } from './cli';
4
+ export type { AssetType, CliOptions, ClaudeConfig, GitHubRepoInfo, PackageInfo, SyncMeta, SyncResult, } from './utils/types';
package/dist/index.mjs ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './cli/index.mjs';
3
+ export { createProgram } from './cli/index.mjs';
4
+ export { syncPackage, syncPackages } from './core/sync.mjs';
5
+
6
+ run().catch((error) => {
7
+ console.error('Fatal error:', error.message);
8
+ process.exit(1);
9
+ });
10
+
11
+ export { run };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ var pc = require('picocolors');
4
+
5
+ const logger = {
6
+ info(message) {
7
+ console.log(pc.blue('info'), message);
8
+ },
9
+ success(message) {
10
+ console.log(pc.green('success'), message);
11
+ },
12
+ warn(message) {
13
+ console.log(pc.yellow('warn'), message);
14
+ },
15
+ error(message) {
16
+ console.log(pc.red('error'), message);
17
+ },
18
+ debug(message) {
19
+ if (process.env.VERBOSE) {
20
+ console.log(pc.gray('debug'), message);
21
+ }
22
+ },
23
+ step(step, detail) {
24
+ const stepText = pc.cyan(`[${step}]`);
25
+ console.log(stepText, detail || '');
26
+ },
27
+ file(operation, path) {
28
+ const colors = {
29
+ create: pc.green,
30
+ update: pc.yellow,
31
+ skip: pc.gray,
32
+ };
33
+ const symbols = {
34
+ create: '+',
35
+ update: '~',
36
+ skip: '-',
37
+ };
38
+ console.log(` ${colors[operation](symbols[operation])} ${path}`);
39
+ },
40
+ packageStart(packageName) {
41
+ console.log();
42
+ console.log(pc.bold(pc.cyan(`Syncing ${packageName}...`)));
43
+ },
44
+ packageEnd(_packageName, result) {
45
+ if (result.skipped)
46
+ console.log(pc.gray(` Skipped: ${result.reason || 'Unknown reason'}`));
47
+ else if (result.success)
48
+ console.log(pc.green(` Completed successfully`));
49
+ else
50
+ console.log(pc.red(` Failed: ${result.reason || 'Unknown error'}`));
51
+ },
52
+ summary(results) {
53
+ console.log();
54
+ console.log(pc.bold('Summary:'));
55
+ console.log(` ${pc.green('Success:')} ${results.success}`);
56
+ console.log(` ${pc.gray('Skipped:')} ${results.skipped}`);
57
+ if (results.failed > 0)
58
+ console.log(` ${pc.red('Failed:')} ${results.failed}`);
59
+ },
60
+ dryRunNotice() {
61
+ console.log();
62
+ console.log(pc.yellow(pc.bold('[DRY RUN] No files will be created or modified.')));
63
+ console.log();
64
+ },
65
+ };
66
+
67
+ exports.logger = logger;