@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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/@aileron/declare/function.d.ts +16 -0
- package/dist/@aileron/declare/index.d.ts +4 -0
- package/dist/@aileron/declare/object.d.ts +11 -0
- package/dist/@aileron/declare/unit.d.ts +19 -0
- package/dist/@aileron/declare/utility.d.ts +45 -0
- package/dist/cli/index.cjs +56 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.mjs +53 -0
- package/dist/core/filesystem.cjs +81 -0
- package/dist/core/filesystem.d.ts +73 -0
- package/dist/core/filesystem.mjs +70 -0
- package/dist/core/github.cjs +90 -0
- package/dist/core/github.d.ts +50 -0
- package/dist/core/github.mjs +83 -0
- package/dist/core/sync.cjs +150 -0
- package/dist/core/sync.d.ts +17 -0
- package/dist/core/sync.mjs +147 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +11 -0
- package/dist/utils/logger.cjs +67 -0
- package/dist/utils/logger.d.ts +57 -0
- package/dist/utils/logger.mjs +65 -0
- package/dist/utils/package.cjs +143 -0
- package/dist/utils/package.d.ts +66 -0
- package/dist/utils/package.mjs +133 -0
- package/dist/utils/types.d.ts +88 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vincent K. Kelvin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @slats/claude-assets-sync
|
|
2
|
+
|
|
3
|
+
CLI tool to sync Claude commands and skills from npm packages to your project's `.claude/` directory.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This tool allows npm package authors to distribute Claude Code commands and skills alongside their packages. When users install these packages, they can sync the Claude assets to their local `.claude/` directory for immediate use with Claude Code.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Using npx (recommended for one-time use)
|
|
13
|
+
npx @slats/claude-assets-sync -p @canard/schema-form
|
|
14
|
+
|
|
15
|
+
# Or install globally
|
|
16
|
+
npm install -g @slats/claude-assets-sync
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Basic Usage
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Sync a single package
|
|
25
|
+
npx @slats/claude-assets-sync -p @canard/schema-form
|
|
26
|
+
|
|
27
|
+
# Sync multiple packages
|
|
28
|
+
npx @slats/claude-assets-sync -p @canard/schema-form -p @lerx/promise-modal
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Options
|
|
32
|
+
|
|
33
|
+
| Option | Description |
|
|
34
|
+
|--------|-------------|
|
|
35
|
+
| `-p, --package <name>` | Package name to sync (can be specified multiple times) |
|
|
36
|
+
| `-f, --force` | Force sync even if version matches |
|
|
37
|
+
| `--dry-run` | Preview changes without writing files |
|
|
38
|
+
| `--help` | Show help |
|
|
39
|
+
| `--version` | Show version |
|
|
40
|
+
|
|
41
|
+
### Examples
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Preview what would be synced
|
|
45
|
+
npx @slats/claude-assets-sync -p @canard/schema-form --dry-run
|
|
46
|
+
|
|
47
|
+
# Force sync (ignore version check)
|
|
48
|
+
npx @slats/claude-assets-sync -p @canard/schema-form --force
|
|
49
|
+
|
|
50
|
+
# Sync with GitHub token (for higher rate limits)
|
|
51
|
+
GITHUB_TOKEN=ghp_xxx npx @slats/claude-assets-sync -p @canard/schema-form
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## For Package Authors
|
|
55
|
+
|
|
56
|
+
To make your package compatible with `claude-assets-sync`, add a `claude` field to your `package.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"name": "@your-scope/your-package",
|
|
61
|
+
"version": "1.0.0",
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "https://github.com/your-org/your-repo.git",
|
|
65
|
+
"directory": "packages/your-package"
|
|
66
|
+
},
|
|
67
|
+
"claude": {
|
|
68
|
+
"assetPath": "docs/claude"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Directory Structure
|
|
74
|
+
|
|
75
|
+
Your package should have the following structure:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
your-package/
|
|
79
|
+
├── docs/
|
|
80
|
+
│ └── claude/
|
|
81
|
+
│ ├── commands/
|
|
82
|
+
│ │ └── your-command.md
|
|
83
|
+
│ └── skills/
|
|
84
|
+
│ └── your-skill.md
|
|
85
|
+
└── package.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### File Format
|
|
89
|
+
|
|
90
|
+
- Commands and skills should be Markdown files (`.md`)
|
|
91
|
+
- Files are synced directly without modification
|
|
92
|
+
- Use the standard Claude Code command/skill format
|
|
93
|
+
|
|
94
|
+
## Destination Structure
|
|
95
|
+
|
|
96
|
+
Synced files are organized in your project's `.claude/` directory:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
your-project/
|
|
100
|
+
└── .claude/
|
|
101
|
+
├── commands/
|
|
102
|
+
│ └── @your-scope/
|
|
103
|
+
│ └── your-package/
|
|
104
|
+
│ ├── your-command.md
|
|
105
|
+
│ └── .sync-meta.json
|
|
106
|
+
└── skills/
|
|
107
|
+
└── @your-scope/
|
|
108
|
+
└── your-package/
|
|
109
|
+
├── your-skill.md
|
|
110
|
+
└── .sync-meta.json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Version Management
|
|
114
|
+
|
|
115
|
+
The tool creates `.sync-meta.json` files to track synced versions:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"version": "1.0.0",
|
|
120
|
+
"syncedAt": "2025-02-01T12:00:00.000Z",
|
|
121
|
+
"files": ["your-command.md"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- Sync is skipped if the local version matches the package version
|
|
126
|
+
- Use `--force` to override version checking
|
|
127
|
+
|
|
128
|
+
## Environment Variables
|
|
129
|
+
|
|
130
|
+
| Variable | Description |
|
|
131
|
+
|----------|-------------|
|
|
132
|
+
| `GITHUB_TOKEN` | GitHub personal access token for higher API rate limits |
|
|
133
|
+
| `VERBOSE` | Enable debug logging |
|
|
134
|
+
|
|
135
|
+
## Rate Limits
|
|
136
|
+
|
|
137
|
+
- **Without token**: 60 requests/hour (GitHub API limit)
|
|
138
|
+
- **With token**: 5,000 requests/hour
|
|
139
|
+
|
|
140
|
+
For most use cases, the unauthenticated limit is sufficient. If syncing many packages, set `GITHUB_TOKEN`.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT License - see [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Fn<Params extends Array<any> = [], Return = void> {
|
|
2
|
+
(...props: Params): Return;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface AsyncFn<Params extends Array<any> = [], Return = void> {
|
|
6
|
+
(...props: Params): Promise<Return>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type SetStateFn<S = unknown> = (
|
|
10
|
+
value: S | ((prevState: S) => S),
|
|
11
|
+
) => void;
|
|
12
|
+
|
|
13
|
+
export type Parameter<
|
|
14
|
+
F extends Fn<[any]> | SetStateFn<any> | undefined,
|
|
15
|
+
I extends number = 0,
|
|
16
|
+
> = Parameters<NonNullable<F>>[I];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type Dictionary<T = any> = Record<string, T>;
|
|
2
|
+
|
|
3
|
+
export type StringDictionary = Dictionary<string>;
|
|
4
|
+
|
|
5
|
+
export type DeepRequired<T> = {
|
|
6
|
+
[P in keyof T]-?: NonNullable<T[P]> extends object
|
|
7
|
+
? NonNullable<T[P]> extends any[]
|
|
8
|
+
? DeepRequired<NonNullable<T[P]>[number]>[]
|
|
9
|
+
: DeepRequired<NonNullable<T[P]>>
|
|
10
|
+
: T[P];
|
|
11
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type DomSize =
|
|
2
|
+
| number
|
|
3
|
+
| `${number}%`
|
|
4
|
+
| `${number}px`
|
|
5
|
+
| `${number}rem`
|
|
6
|
+
| `${number}em`
|
|
7
|
+
| `${number}vh`
|
|
8
|
+
| `${number}vw`
|
|
9
|
+
| `calc(${string})`;
|
|
10
|
+
|
|
11
|
+
export type Color =
|
|
12
|
+
| `#${string}`
|
|
13
|
+
| `rgb(${number}, ${number}, ${number})`
|
|
14
|
+
| `rgba(${number}, ${number}, ${number}, ${number})`
|
|
15
|
+
| `var(--${string})`;
|
|
16
|
+
|
|
17
|
+
export type Time = `${number}ms` | `${number}s`;
|
|
18
|
+
|
|
19
|
+
export type Duration = `${number}ms`;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Nullish = null | undefined;
|
|
2
|
+
|
|
3
|
+
export type Nullable<T> = T | null;
|
|
4
|
+
|
|
5
|
+
export type IsNullable<T> = [null] extends [T] ? true : false;
|
|
6
|
+
|
|
7
|
+
export type Optional<T> = T | undefined;
|
|
8
|
+
|
|
9
|
+
/** Extract keys K from T and make them required */
|
|
10
|
+
export type PickRequired<T, K extends keyof T> = Required<Pick<T, K>>;
|
|
11
|
+
|
|
12
|
+
/** Extract keys K from T and make them partial */
|
|
13
|
+
export type PickPartial<T, K extends keyof T> = Partial<Pick<T, K>>;
|
|
14
|
+
|
|
15
|
+
/** Omit keys K from T and make the rest required */
|
|
16
|
+
export type OmitRequired<T, K extends keyof T> = Required<Omit<T, K>>;
|
|
17
|
+
|
|
18
|
+
/** Omit keys K from T and make the rest partial */
|
|
19
|
+
export type OmitPartial<T, K extends keyof T> = Partial<Omit<T, K>>;
|
|
20
|
+
|
|
21
|
+
/** Make keys K required and the rest partial in T */
|
|
22
|
+
export type PickAndPartial<T, K extends keyof T> = PickRequired<T, K> &
|
|
23
|
+
OmitPartial<T, K>;
|
|
24
|
+
|
|
25
|
+
/** Make keys K required in T while keeping the rest unchanged */
|
|
26
|
+
export type RequiredBy<T, K extends keyof T> = PickRequired<T, K> & T;
|
|
27
|
+
|
|
28
|
+
/** Make keys K partial in T while keeping the rest unchanged */
|
|
29
|
+
export type PartialBy<T, K extends keyof T> = PickPartial<T, K> & Omit<T, K>;
|
|
30
|
+
|
|
31
|
+
export type Roll<T> = { [K in keyof T]: T[K] };
|
|
32
|
+
|
|
33
|
+
export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
34
|
+
|
|
35
|
+
export type ExpandRecursively<T> = T extends object
|
|
36
|
+
? T extends infer O
|
|
37
|
+
? { [K in keyof O]: ExpandRecursively<O[K]> }
|
|
38
|
+
: never
|
|
39
|
+
: T;
|
|
40
|
+
|
|
41
|
+
export type WithKey<T> = T & { key: string };
|
|
42
|
+
|
|
43
|
+
export type ElementOf<T extends any[]> = T[number];
|
|
44
|
+
|
|
45
|
+
export type Params<T extends Array<string>> = Record<T[number], string>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var commander = require('commander');
|
|
4
|
+
var sync = require('../core/sync.cjs');
|
|
5
|
+
var logger = require('../utils/logger.cjs');
|
|
6
|
+
|
|
7
|
+
const runSync = async (options) => {
|
|
8
|
+
if (options.package.length === 0) {
|
|
9
|
+
logger.logger.error('No packages specified. Use -p <package> to specify packages.');
|
|
10
|
+
logger.logger.info('Example: claude-assets-sync -p @canard/schema-form');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (options.dryRun)
|
|
14
|
+
logger.logger.dryRunNotice();
|
|
15
|
+
if (options.local)
|
|
16
|
+
logger.logger.info('[LOCAL MODE] Reading packages from workspace instead of node_modules\n');
|
|
17
|
+
const results = await sync.syncPackages(options.package, {
|
|
18
|
+
force: options.force,
|
|
19
|
+
dryRun: options.dryRun,
|
|
20
|
+
local: options.local,
|
|
21
|
+
ref: options.ref,
|
|
22
|
+
});
|
|
23
|
+
const hasFailures = results.some((r) => !r.success && !r.skipped);
|
|
24
|
+
if (hasFailures)
|
|
25
|
+
process.exit(1);
|
|
26
|
+
};
|
|
27
|
+
const createProgram = () => {
|
|
28
|
+
const program = new commander.Command();
|
|
29
|
+
program
|
|
30
|
+
.name('claude-assets-sync')
|
|
31
|
+
.description('Sync Claude commands and skills from npm packages to your project')
|
|
32
|
+
.version('0.1.0')
|
|
33
|
+
.option('-p, --package <name>', 'Package name to sync (can be specified multiple times)', (value, previous) => [...previous, value], [])
|
|
34
|
+
.option('-f, --force', 'Force sync even if version matches', false)
|
|
35
|
+
.option('--dry-run', 'Preview changes without writing files', false)
|
|
36
|
+
.option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
|
|
37
|
+
.option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from (overrides version tag)')
|
|
38
|
+
.action(async (opts) => {
|
|
39
|
+
const options = {
|
|
40
|
+
package: opts.package,
|
|
41
|
+
force: opts.force,
|
|
42
|
+
dryRun: opts.dryRun,
|
|
43
|
+
local: opts.local,
|
|
44
|
+
ref: opts.ref,
|
|
45
|
+
};
|
|
46
|
+
await runSync(options);
|
|
47
|
+
});
|
|
48
|
+
return program;
|
|
49
|
+
};
|
|
50
|
+
const run = async () => {
|
|
51
|
+
const program = createProgram();
|
|
52
|
+
await program.parseAsync(process.argv);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
exports.createProgram = createProgram;
|
|
56
|
+
exports.run = run;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { syncPackages } from '../core/sync.mjs';
|
|
3
|
+
import { logger } from '../utils/logger.mjs';
|
|
4
|
+
|
|
5
|
+
const runSync = async (options) => {
|
|
6
|
+
if (options.package.length === 0) {
|
|
7
|
+
logger.error('No packages specified. Use -p <package> to specify packages.');
|
|
8
|
+
logger.info('Example: claude-assets-sync -p @canard/schema-form');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
if (options.dryRun)
|
|
12
|
+
logger.dryRunNotice();
|
|
13
|
+
if (options.local)
|
|
14
|
+
logger.info('[LOCAL MODE] Reading packages from workspace instead of node_modules\n');
|
|
15
|
+
const results = await syncPackages(options.package, {
|
|
16
|
+
force: options.force,
|
|
17
|
+
dryRun: options.dryRun,
|
|
18
|
+
local: options.local,
|
|
19
|
+
ref: options.ref,
|
|
20
|
+
});
|
|
21
|
+
const hasFailures = results.some((r) => !r.success && !r.skipped);
|
|
22
|
+
if (hasFailures)
|
|
23
|
+
process.exit(1);
|
|
24
|
+
};
|
|
25
|
+
const createProgram = () => {
|
|
26
|
+
const program = new Command();
|
|
27
|
+
program
|
|
28
|
+
.name('claude-assets-sync')
|
|
29
|
+
.description('Sync Claude commands and skills from npm packages to your project')
|
|
30
|
+
.version('0.1.0')
|
|
31
|
+
.option('-p, --package <name>', 'Package name to sync (can be specified multiple times)', (value, previous) => [...previous, value], [])
|
|
32
|
+
.option('-f, --force', 'Force sync even if version matches', false)
|
|
33
|
+
.option('--dry-run', 'Preview changes without writing files', false)
|
|
34
|
+
.option('-l, --local', 'Read packages from local workspace instead of node_modules', false)
|
|
35
|
+
.option('-r, --ref <ref>', 'Git ref (branch, tag, or commit) to fetch from (overrides version tag)')
|
|
36
|
+
.action(async (opts) => {
|
|
37
|
+
const options = {
|
|
38
|
+
package: opts.package,
|
|
39
|
+
force: opts.force,
|
|
40
|
+
dryRun: opts.dryRun,
|
|
41
|
+
local: opts.local,
|
|
42
|
+
ref: opts.ref,
|
|
43
|
+
};
|
|
44
|
+
await runSync(options);
|
|
45
|
+
});
|
|
46
|
+
return program;
|
|
47
|
+
};
|
|
48
|
+
const run = async () => {
|
|
49
|
+
const program = createProgram();
|
|
50
|
+
await program.parseAsync(process.argv);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export { createProgram, run };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_fs = require('node:fs');
|
|
4
|
+
var node_path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const parsePackageName = (packageName) => {
|
|
7
|
+
if (packageName.startsWith('@')) {
|
|
8
|
+
const [scope, name] = packageName.split('/');
|
|
9
|
+
return [scope, name];
|
|
10
|
+
}
|
|
11
|
+
return ['', packageName];
|
|
12
|
+
};
|
|
13
|
+
const getDestinationDir = (cwd, packageName, assetType) => {
|
|
14
|
+
const [scope, name] = parsePackageName(packageName);
|
|
15
|
+
if (scope)
|
|
16
|
+
return node_path.join(cwd, '.claude', assetType, scope, name);
|
|
17
|
+
return node_path.join(cwd, '.claude', assetType, name);
|
|
18
|
+
};
|
|
19
|
+
const ensureDir = (dirPath) => {
|
|
20
|
+
if (!node_fs.existsSync(dirPath)) {
|
|
21
|
+
node_fs.mkdirSync(dirPath, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const writeFile = (filePath, content) => {
|
|
25
|
+
ensureDir(node_path.dirname(filePath));
|
|
26
|
+
node_fs.writeFileSync(filePath, content, 'utf-8');
|
|
27
|
+
};
|
|
28
|
+
const readSyncMeta = (cwd, packageName, assetType) => {
|
|
29
|
+
try {
|
|
30
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
31
|
+
const metaPath = node_path.join(destDir, '.sync-meta.json');
|
|
32
|
+
const content = node_fs.readFileSync(metaPath, 'utf-8');
|
|
33
|
+
return JSON.parse(content);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const writeSyncMeta = (cwd, packageName, assetType, meta) => {
|
|
40
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
41
|
+
const metaPath = node_path.join(destDir, '.sync-meta.json');
|
|
42
|
+
writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
43
|
+
};
|
|
44
|
+
const writeAssetFile = (cwd, packageName, assetType, fileName, content) => {
|
|
45
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
46
|
+
const filePath = node_path.join(destDir, fileName);
|
|
47
|
+
writeFile(filePath, content);
|
|
48
|
+
};
|
|
49
|
+
const cleanAssetDir = (cwd, packageName, assetType) => {
|
|
50
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
51
|
+
if (node_fs.existsSync(destDir)) {
|
|
52
|
+
node_fs.rmSync(destDir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const needsSync = (cwd, packageName, version) => {
|
|
56
|
+
const commandsMeta = readSyncMeta(cwd, packageName, 'commands');
|
|
57
|
+
const skillsMeta = readSyncMeta(cwd, packageName, 'skills');
|
|
58
|
+
if (!commandsMeta && !skillsMeta)
|
|
59
|
+
return true;
|
|
60
|
+
if (commandsMeta && commandsMeta.version !== version)
|
|
61
|
+
return true;
|
|
62
|
+
if (skillsMeta && skillsMeta.version !== version)
|
|
63
|
+
return true;
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
const createSyncMeta = (version, files) => ({
|
|
67
|
+
version,
|
|
68
|
+
syncedAt: new Date().toISOString(),
|
|
69
|
+
files,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
exports.cleanAssetDir = cleanAssetDir;
|
|
73
|
+
exports.createSyncMeta = createSyncMeta;
|
|
74
|
+
exports.ensureDir = ensureDir;
|
|
75
|
+
exports.getDestinationDir = getDestinationDir;
|
|
76
|
+
exports.needsSync = needsSync;
|
|
77
|
+
exports.parsePackageName = parsePackageName;
|
|
78
|
+
exports.readSyncMeta = readSyncMeta;
|
|
79
|
+
exports.writeAssetFile = writeAssetFile;
|
|
80
|
+
exports.writeFile = writeFile;
|
|
81
|
+
exports.writeSyncMeta = writeSyncMeta;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AssetType, SyncMeta } from '../utils/types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse scoped package name into scope and name
|
|
4
|
+
* @param packageName - Package name (e.g., "@canard/schema-form")
|
|
5
|
+
* @returns [scope, name] tuple (e.g., ["@canard", "schema-form"])
|
|
6
|
+
*/
|
|
7
|
+
export declare const parsePackageName: (packageName: string) => [string, string];
|
|
8
|
+
/**
|
|
9
|
+
* Get the destination directory for synced assets
|
|
10
|
+
* @param cwd - Current working directory
|
|
11
|
+
* @param packageName - Package name
|
|
12
|
+
* @param assetType - Asset type (commands or skills)
|
|
13
|
+
* @returns Full path to destination directory
|
|
14
|
+
*/
|
|
15
|
+
export declare const getDestinationDir: (cwd: string, packageName: string, assetType: AssetType) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Ensure directory exists (creates recursively if needed)
|
|
18
|
+
* @param dirPath - Directory path
|
|
19
|
+
*/
|
|
20
|
+
export declare const ensureDir: (dirPath: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Write file with directory creation
|
|
23
|
+
* @param filePath - Full file path
|
|
24
|
+
* @param content - File content
|
|
25
|
+
*/
|
|
26
|
+
export declare const writeFile: (filePath: string, content: string) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Read sync metadata file
|
|
29
|
+
* @param cwd - Current working directory
|
|
30
|
+
* @param packageName - Package name
|
|
31
|
+
* @param assetType - Asset type
|
|
32
|
+
* @returns SyncMeta or null if not found
|
|
33
|
+
*/
|
|
34
|
+
export declare const readSyncMeta: (cwd: string, packageName: string, assetType: AssetType) => SyncMeta | null;
|
|
35
|
+
/**
|
|
36
|
+
* Write sync metadata file
|
|
37
|
+
* @param cwd - Current working directory
|
|
38
|
+
* @param packageName - Package name
|
|
39
|
+
* @param assetType - Asset type
|
|
40
|
+
* @param meta - Sync metadata
|
|
41
|
+
*/
|
|
42
|
+
export declare const writeSyncMeta: (cwd: string, packageName: string, assetType: AssetType, meta: SyncMeta) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Write asset file to destination
|
|
45
|
+
* @param cwd - Current working directory
|
|
46
|
+
* @param packageName - Package name
|
|
47
|
+
* @param assetType - Asset type
|
|
48
|
+
* @param fileName - File name
|
|
49
|
+
* @param content - File content
|
|
50
|
+
*/
|
|
51
|
+
export declare const writeAssetFile: (cwd: string, packageName: string, assetType: AssetType, fileName: string, content: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Clean existing synced files for a package
|
|
54
|
+
* @param cwd - Current working directory
|
|
55
|
+
* @param packageName - Package name
|
|
56
|
+
* @param assetType - Asset type
|
|
57
|
+
*/
|
|
58
|
+
export declare const cleanAssetDir: (cwd: string, packageName: string, assetType: AssetType) => void;
|
|
59
|
+
/**
|
|
60
|
+
* Check if package assets need sync (version mismatch)
|
|
61
|
+
* @param cwd - Current working directory
|
|
62
|
+
* @param packageName - Package name
|
|
63
|
+
* @param version - Current package version
|
|
64
|
+
* @returns true if sync is needed
|
|
65
|
+
*/
|
|
66
|
+
export declare const needsSync: (cwd: string, packageName: string, version: string) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Create SyncMeta object for current sync operation
|
|
69
|
+
* @param version - Package version
|
|
70
|
+
* @param files - List of synced file names
|
|
71
|
+
* @returns SyncMeta object
|
|
72
|
+
*/
|
|
73
|
+
export declare const createSyncMeta: (version: string, files: string[]) => SyncMeta;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, rmSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const parsePackageName = (packageName) => {
|
|
5
|
+
if (packageName.startsWith('@')) {
|
|
6
|
+
const [scope, name] = packageName.split('/');
|
|
7
|
+
return [scope, name];
|
|
8
|
+
}
|
|
9
|
+
return ['', packageName];
|
|
10
|
+
};
|
|
11
|
+
const getDestinationDir = (cwd, packageName, assetType) => {
|
|
12
|
+
const [scope, name] = parsePackageName(packageName);
|
|
13
|
+
if (scope)
|
|
14
|
+
return join(cwd, '.claude', assetType, scope, name);
|
|
15
|
+
return join(cwd, '.claude', assetType, name);
|
|
16
|
+
};
|
|
17
|
+
const ensureDir = (dirPath) => {
|
|
18
|
+
if (!existsSync(dirPath)) {
|
|
19
|
+
mkdirSync(dirPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const writeFile = (filePath, content) => {
|
|
23
|
+
ensureDir(dirname(filePath));
|
|
24
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
25
|
+
};
|
|
26
|
+
const readSyncMeta = (cwd, packageName, assetType) => {
|
|
27
|
+
try {
|
|
28
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
29
|
+
const metaPath = join(destDir, '.sync-meta.json');
|
|
30
|
+
const content = readFileSync(metaPath, 'utf-8');
|
|
31
|
+
return JSON.parse(content);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const writeSyncMeta = (cwd, packageName, assetType, meta) => {
|
|
38
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
39
|
+
const metaPath = join(destDir, '.sync-meta.json');
|
|
40
|
+
writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
41
|
+
};
|
|
42
|
+
const writeAssetFile = (cwd, packageName, assetType, fileName, content) => {
|
|
43
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
44
|
+
const filePath = join(destDir, fileName);
|
|
45
|
+
writeFile(filePath, content);
|
|
46
|
+
};
|
|
47
|
+
const cleanAssetDir = (cwd, packageName, assetType) => {
|
|
48
|
+
const destDir = getDestinationDir(cwd, packageName, assetType);
|
|
49
|
+
if (existsSync(destDir)) {
|
|
50
|
+
rmSync(destDir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const needsSync = (cwd, packageName, version) => {
|
|
54
|
+
const commandsMeta = readSyncMeta(cwd, packageName, 'commands');
|
|
55
|
+
const skillsMeta = readSyncMeta(cwd, packageName, 'skills');
|
|
56
|
+
if (!commandsMeta && !skillsMeta)
|
|
57
|
+
return true;
|
|
58
|
+
if (commandsMeta && commandsMeta.version !== version)
|
|
59
|
+
return true;
|
|
60
|
+
if (skillsMeta && skillsMeta.version !== version)
|
|
61
|
+
return true;
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
64
|
+
const createSyncMeta = (version, files) => ({
|
|
65
|
+
version,
|
|
66
|
+
syncedAt: new Date().toISOString(),
|
|
67
|
+
files,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export { cleanAssetDir, createSyncMeta, ensureDir, getDestinationDir, needsSync, parsePackageName, readSyncMeta, writeAssetFile, writeFile, writeSyncMeta };
|