@matteoaliano/forest-ui 0.8.3 → 0.8.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.
- package/README.md +154 -154
- package/bin/sync.mjs +95 -95
- package/dist/{chunk-MIJBSV5Z.mjs → chunk-6LFUPGC2.mjs} +1 -1
- package/dist/chunk-6LFUPGC2.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -2
- package/dist/index.mjs.map +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +1 -1
- package/fonts/aeonik/aeonik.css +42 -42
- package/fonts/aeonik-mono/aeonik-mono.css +32 -32
- package/fonts/alkemy-beta/alkemy-beta.css +10 -10
- package/package.json +97 -97
- package/skills/forest-alkemy-plus/SKILL.md +141 -141
- package/skills/forest-alkemy-plus/references/components.md +202 -202
- package/skills/forest-alkemy-plus/references/patterns.md +405 -405
- package/skills/forest-alkemy-plus/references/upgrading.md +137 -137
- package/dist/chunk-MIJBSV5Z.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
# Forest UI
|
|
2
|
-
|
|
3
|
-
Forest Design System — A multi-tenant MUI v7 + Emotion component library.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
Install the package and its peer dependencies:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @matteoaliano/forest-ui \
|
|
11
|
-
@mui/material@^7 @mui/icons-material@^7 \
|
|
12
|
-
@mui/x-charts@^7 @mui/x-data-grid@^7 @mui/x-date-pickers@^8 \
|
|
13
|
-
@emotion/react @emotion/styled dayjs
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Forest UI re-exports MUI components with Forest defaults, so you should import everything from `@matteoaliano/forest-ui` rather than `@mui/material`. The only direct MUI import you need is `@mui/icons-material` for icons.
|
|
17
|
-
|
|
18
|
-
## Quick Start
|
|
19
|
-
|
|
20
|
-
```tsx
|
|
21
|
-
import { ForestProvider, Button, Typography } from '@matteoaliano/forest-ui';
|
|
22
|
-
import '@matteoaliano/forest-ui/fonts/aeonik/aeonik.css';
|
|
23
|
-
|
|
24
|
-
function App() {
|
|
25
|
-
return (
|
|
26
|
-
<ForestProvider>
|
|
27
|
-
<Typography variant="h1">Hello Forest</Typography>
|
|
28
|
-
<Button>Click me</Button>
|
|
29
|
-
</ForestProvider>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Next.js setup
|
|
35
|
-
|
|
36
|
-
Forest UI works with Next.js 14 / 15 / 16 (App Router and Pages Router). For best results with Turbopack and bundle size, add the following to `next.config.js`:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
/** @type {import('next').NextConfig} */
|
|
40
|
-
const nextConfig = {
|
|
41
|
-
transpilePackages: ['@matteoaliano/forest-ui'],
|
|
42
|
-
experimental: {
|
|
43
|
-
optimizePackageImports: [
|
|
44
|
-
'@matteoaliano/forest-ui',
|
|
45
|
-
'@mui/material',
|
|
46
|
-
'@mui/icons-material',
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
module.exports = nextConfig;
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Because `ForestProvider` is a client component, wrap your app in a client boundary (a `'use client'` component or a dedicated `providers.tsx`) before mounting it inside the App Router root layout.
|
|
55
|
-
|
|
56
|
-
> **Known issue:** some `@mui/x-*` packages have intermittent Turbopack issues upstream that are unrelated to Forest UI. If you hit one, falling back to webpack (`next dev` without `--turbo`) usually clears it.
|
|
57
|
-
|
|
58
|
-
## Theme Presets
|
|
59
|
-
|
|
60
|
-
Three presets are available:
|
|
61
|
-
|
|
62
|
-
| Preset | Use Case |
|
|
63
|
-
|--------|----------|
|
|
64
|
-
| `forest-agency` | Internal tools + partner agencies (default) |
|
|
65
|
-
| `forest-external` | Client-facing products |
|
|
66
|
-
| `forest-internal` | Internal tools |
|
|
67
|
-
|
|
68
|
-
### Using a Preset
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { ForestProvider } from '@matteoaliano/forest-ui';
|
|
72
|
-
|
|
73
|
-
<ForestProvider preset="forest-external">
|
|
74
|
-
{/* Your app */}
|
|
75
|
-
</ForestProvider>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Using a Custom Theme
|
|
79
|
-
|
|
80
|
-
```tsx
|
|
81
|
-
import { ForestProvider, buildTheme } from '@matteoaliano/forest-ui';
|
|
82
|
-
import { getPreset } from '@matteoaliano/forest-ui';
|
|
83
|
-
|
|
84
|
-
const preset = getPreset('forest-agency');
|
|
85
|
-
const theme = buildTheme(preset.variants.light.tokens);
|
|
86
|
-
|
|
87
|
-
<ForestProvider theme={theme}>
|
|
88
|
-
{/* Your app */}
|
|
89
|
-
</ForestProvider>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Components
|
|
93
|
-
|
|
94
|
-
40+ wrapped MUI components with Forest defaults. See [Storybook](https://storybook.url) for all components.
|
|
95
|
-
|
|
96
|
-
### Inputs
|
|
97
|
-
- Button, ButtonGroup
|
|
98
|
-
- TextField, Select, MultiSelect
|
|
99
|
-
- Checkbox, RadioGroup, Switch
|
|
100
|
-
- ToggleButton
|
|
101
|
-
- DatePicker
|
|
102
|
-
- Autocomplete
|
|
103
|
-
- Search
|
|
104
|
-
|
|
105
|
-
### Data Display
|
|
106
|
-
- Typography
|
|
107
|
-
- Table, DataGrid
|
|
108
|
-
- Chip, Badge
|
|
109
|
-
- Tooltip
|
|
110
|
-
- List
|
|
111
|
-
- Divider
|
|
112
|
-
- Skeleton
|
|
113
|
-
|
|
114
|
-
### Surfaces
|
|
115
|
-
- Card, Paper
|
|
116
|
-
- AppBar
|
|
117
|
-
- Accordion
|
|
118
|
-
|
|
119
|
-
### Feedback
|
|
120
|
-
- Alert, Snackbar
|
|
121
|
-
- Dialog, Modal, Popover
|
|
122
|
-
- Progress (Linear, Circular)
|
|
123
|
-
- Backdrop
|
|
124
|
-
|
|
125
|
-
### Navigation
|
|
126
|
-
- Tabs, Stepper
|
|
127
|
-
- Pagination
|
|
128
|
-
- Breadcrumbs
|
|
129
|
-
- Link
|
|
130
|
-
- Menu
|
|
131
|
-
- SidebarNav
|
|
132
|
-
|
|
133
|
-
### Charts
|
|
134
|
-
- LineChart, BarChart, PieChart
|
|
135
|
-
|
|
136
|
-
## Development
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
# Install dependencies
|
|
140
|
-
npm install
|
|
141
|
-
|
|
142
|
-
# Build
|
|
143
|
-
npm run build
|
|
144
|
-
|
|
145
|
-
# Run tests
|
|
146
|
-
npm test
|
|
147
|
-
|
|
148
|
-
# Storybook
|
|
149
|
-
npm run storybook
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## License
|
|
153
|
-
|
|
154
|
-
MIT
|
|
1
|
+
# Forest UI
|
|
2
|
+
|
|
3
|
+
Forest Design System — A multi-tenant MUI v7 + Emotion component library.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the package and its peer dependencies:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @matteoaliano/forest-ui \
|
|
11
|
+
@mui/material@^7 @mui/icons-material@^7 \
|
|
12
|
+
@mui/x-charts@^7 @mui/x-data-grid@^7 @mui/x-date-pickers@^8 \
|
|
13
|
+
@emotion/react @emotion/styled dayjs
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Forest UI re-exports MUI components with Forest defaults, so you should import everything from `@matteoaliano/forest-ui` rather than `@mui/material`. The only direct MUI import you need is `@mui/icons-material` for icons.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { ForestProvider, Button, Typography } from '@matteoaliano/forest-ui';
|
|
22
|
+
import '@matteoaliano/forest-ui/fonts/aeonik/aeonik.css';
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
return (
|
|
26
|
+
<ForestProvider>
|
|
27
|
+
<Typography variant="h1">Hello Forest</Typography>
|
|
28
|
+
<Button>Click me</Button>
|
|
29
|
+
</ForestProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Next.js setup
|
|
35
|
+
|
|
36
|
+
Forest UI works with Next.js 14 / 15 / 16 (App Router and Pages Router). For best results with Turbopack and bundle size, add the following to `next.config.js`:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
/** @type {import('next').NextConfig} */
|
|
40
|
+
const nextConfig = {
|
|
41
|
+
transpilePackages: ['@matteoaliano/forest-ui'],
|
|
42
|
+
experimental: {
|
|
43
|
+
optimizePackageImports: [
|
|
44
|
+
'@matteoaliano/forest-ui',
|
|
45
|
+
'@mui/material',
|
|
46
|
+
'@mui/icons-material',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = nextConfig;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Because `ForestProvider` is a client component, wrap your app in a client boundary (a `'use client'` component or a dedicated `providers.tsx`) before mounting it inside the App Router root layout.
|
|
55
|
+
|
|
56
|
+
> **Known issue:** some `@mui/x-*` packages have intermittent Turbopack issues upstream that are unrelated to Forest UI. If you hit one, falling back to webpack (`next dev` without `--turbo`) usually clears it.
|
|
57
|
+
|
|
58
|
+
## Theme Presets
|
|
59
|
+
|
|
60
|
+
Three presets are available:
|
|
61
|
+
|
|
62
|
+
| Preset | Use Case |
|
|
63
|
+
|--------|----------|
|
|
64
|
+
| `forest-agency` | Internal tools + partner agencies (default) |
|
|
65
|
+
| `forest-external` | Client-facing products |
|
|
66
|
+
| `forest-internal` | Internal tools |
|
|
67
|
+
|
|
68
|
+
### Using a Preset
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { ForestProvider } from '@matteoaliano/forest-ui';
|
|
72
|
+
|
|
73
|
+
<ForestProvider preset="forest-external">
|
|
74
|
+
{/* Your app */}
|
|
75
|
+
</ForestProvider>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Using a Custom Theme
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { ForestProvider, buildTheme } from '@matteoaliano/forest-ui';
|
|
82
|
+
import { getPreset } from '@matteoaliano/forest-ui';
|
|
83
|
+
|
|
84
|
+
const preset = getPreset('forest-agency');
|
|
85
|
+
const theme = buildTheme(preset.variants.light.tokens);
|
|
86
|
+
|
|
87
|
+
<ForestProvider theme={theme}>
|
|
88
|
+
{/* Your app */}
|
|
89
|
+
</ForestProvider>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Components
|
|
93
|
+
|
|
94
|
+
40+ wrapped MUI components with Forest defaults. See [Storybook](https://storybook.url) for all components.
|
|
95
|
+
|
|
96
|
+
### Inputs
|
|
97
|
+
- Button, ButtonGroup
|
|
98
|
+
- TextField, Select, MultiSelect
|
|
99
|
+
- Checkbox, RadioGroup, Switch
|
|
100
|
+
- ToggleButton
|
|
101
|
+
- DatePicker
|
|
102
|
+
- Autocomplete
|
|
103
|
+
- Search
|
|
104
|
+
|
|
105
|
+
### Data Display
|
|
106
|
+
- Typography
|
|
107
|
+
- Table, DataGrid
|
|
108
|
+
- Chip, Badge
|
|
109
|
+
- Tooltip
|
|
110
|
+
- List
|
|
111
|
+
- Divider
|
|
112
|
+
- Skeleton
|
|
113
|
+
|
|
114
|
+
### Surfaces
|
|
115
|
+
- Card, Paper
|
|
116
|
+
- AppBar
|
|
117
|
+
- Accordion
|
|
118
|
+
|
|
119
|
+
### Feedback
|
|
120
|
+
- Alert, Snackbar
|
|
121
|
+
- Dialog, Modal, Popover
|
|
122
|
+
- Progress (Linear, Circular)
|
|
123
|
+
- Backdrop
|
|
124
|
+
|
|
125
|
+
### Navigation
|
|
126
|
+
- Tabs, Stepper
|
|
127
|
+
- Pagination
|
|
128
|
+
- Breadcrumbs
|
|
129
|
+
- Link
|
|
130
|
+
- Menu
|
|
131
|
+
- SidebarNav
|
|
132
|
+
|
|
133
|
+
### Charts
|
|
134
|
+
- LineChart, BarChart, PieChart
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Install dependencies
|
|
140
|
+
npm install
|
|
141
|
+
|
|
142
|
+
# Build
|
|
143
|
+
npm run build
|
|
144
|
+
|
|
145
|
+
# Run tests
|
|
146
|
+
npm test
|
|
147
|
+
|
|
148
|
+
# Storybook
|
|
149
|
+
npm run storybook
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/bin/sync.mjs
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* forest-ui sync
|
|
5
|
-
*
|
|
6
|
-
* Copies skill folders into the consuming project's .claude/skills/
|
|
7
|
-
* directory so Claude Code automatically discovers Forest UI preset rules.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync, mkdirSync, existsSync, readdirSync, cpSync, rmSync } from "node:fs";
|
|
11
|
-
import { resolve, dirname } from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
|
|
14
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const skillsDir = resolve(__dirname, "..", "skills");
|
|
16
|
-
|
|
17
|
-
// Find the consuming project root (walk up until we find package.json)
|
|
18
|
-
function findProjectRoot(startDir) {
|
|
19
|
-
let dir = startDir;
|
|
20
|
-
while (dir !== dirname(dir)) {
|
|
21
|
-
if (existsSync(resolve(dir, "package.json"))) {
|
|
22
|
-
try {
|
|
23
|
-
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
|
|
24
|
-
if (pkg.name !== "@matteoaliano/forest-ui") return dir;
|
|
25
|
-
} catch {
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
dir = dirname(dir);
|
|
30
|
-
}
|
|
31
|
-
return process.cwd();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const projectRoot = findProjectRoot(process.cwd());
|
|
35
|
-
|
|
36
|
-
console.log(`\n🌲 Forest UI — Syncing skills\n`);
|
|
37
|
-
console.log(` Project root: ${projectRoot}\n`);
|
|
38
|
-
|
|
39
|
-
// Clean up legacy guideline files from pre-v0.4.1
|
|
40
|
-
const legacyPaths = [
|
|
41
|
-
resolve(projectRoot, ".claude", "forest-ui.md"),
|
|
42
|
-
resolve(projectRoot, ".cursor", "rules", "forest-ui.mdc"),
|
|
43
|
-
resolve(projectRoot, ".gemini", "forest-ui.md"),
|
|
44
|
-
resolve(projectRoot, ".antigravity", "rules.md"),
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
let cleaned = 0;
|
|
48
|
-
for (const legacyPath of legacyPaths) {
|
|
49
|
-
if (existsSync(legacyPath)) {
|
|
50
|
-
try {
|
|
51
|
-
rmSync(legacyPath);
|
|
52
|
-
const rel = legacyPath.replace(projectRoot + "/", "");
|
|
53
|
-
console.log(` 🧹 Removed legacy ${rel}`);
|
|
54
|
-
cleaned++;
|
|
55
|
-
} catch (err) {
|
|
56
|
-
const rel = legacyPath.replace(projectRoot + "/", "");
|
|
57
|
-
console.log(` ⚠️ Could not remove ${rel} (${err.message})`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (cleaned > 0) console.log();
|
|
62
|
-
|
|
63
|
-
let synced = 0;
|
|
64
|
-
let total = 0;
|
|
65
|
-
|
|
66
|
-
if (!existsSync(skillsDir)) {
|
|
67
|
-
console.log(` ⚠️ No skills directory found, nothing to sync.\n`);
|
|
68
|
-
process.exit(0);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const skillFolders = readdirSync(skillsDir, { withFileTypes: true })
|
|
72
|
-
.filter((d) => d.isDirectory())
|
|
73
|
-
.map((d) => d.name);
|
|
74
|
-
|
|
75
|
-
if (skillFolders.length === 0) {
|
|
76
|
-
console.log(` ⚠️ No skill folders found, nothing to sync.\n`);
|
|
77
|
-
process.exit(0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const claudeSkillsDir = resolve(projectRoot, ".claude", "skills");
|
|
81
|
-
|
|
82
|
-
for (const folder of skillFolders) {
|
|
83
|
-
const src = resolve(skillsDir, folder);
|
|
84
|
-
const dest = resolve(claudeSkillsDir, folder);
|
|
85
|
-
total++;
|
|
86
|
-
try {
|
|
87
|
-
mkdirSync(dest, { recursive: true });
|
|
88
|
-
cpSync(src, dest, { recursive: true });
|
|
89
|
-
console.log(` ✅ .claude/skills/${folder}/`);
|
|
90
|
-
synced++;
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.log(` ⚠️ .claude/skills/${folder}/ skipped (${err.message})`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
console.log(`\n Synced ${synced}/${total} skills.\n`);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* forest-ui sync
|
|
5
|
+
*
|
|
6
|
+
* Copies skill folders into the consuming project's .claude/skills/
|
|
7
|
+
* directory so Claude Code automatically discovers Forest UI preset rules.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, mkdirSync, existsSync, readdirSync, cpSync, rmSync } from "node:fs";
|
|
11
|
+
import { resolve, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const skillsDir = resolve(__dirname, "..", "skills");
|
|
16
|
+
|
|
17
|
+
// Find the consuming project root (walk up until we find package.json)
|
|
18
|
+
function findProjectRoot(startDir) {
|
|
19
|
+
let dir = startDir;
|
|
20
|
+
while (dir !== dirname(dir)) {
|
|
21
|
+
if (existsSync(resolve(dir, "package.json"))) {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
|
|
24
|
+
if (pkg.name !== "@matteoaliano/forest-ui") return dir;
|
|
25
|
+
} catch {
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
dir = dirname(dir);
|
|
30
|
+
}
|
|
31
|
+
return process.cwd();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
35
|
+
|
|
36
|
+
console.log(`\n🌲 Forest UI — Syncing skills\n`);
|
|
37
|
+
console.log(` Project root: ${projectRoot}\n`);
|
|
38
|
+
|
|
39
|
+
// Clean up legacy guideline files from pre-v0.4.1
|
|
40
|
+
const legacyPaths = [
|
|
41
|
+
resolve(projectRoot, ".claude", "forest-ui.md"),
|
|
42
|
+
resolve(projectRoot, ".cursor", "rules", "forest-ui.mdc"),
|
|
43
|
+
resolve(projectRoot, ".gemini", "forest-ui.md"),
|
|
44
|
+
resolve(projectRoot, ".antigravity", "rules.md"),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
let cleaned = 0;
|
|
48
|
+
for (const legacyPath of legacyPaths) {
|
|
49
|
+
if (existsSync(legacyPath)) {
|
|
50
|
+
try {
|
|
51
|
+
rmSync(legacyPath);
|
|
52
|
+
const rel = legacyPath.replace(projectRoot + "/", "");
|
|
53
|
+
console.log(` 🧹 Removed legacy ${rel}`);
|
|
54
|
+
cleaned++;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const rel = legacyPath.replace(projectRoot + "/", "");
|
|
57
|
+
console.log(` ⚠️ Could not remove ${rel} (${err.message})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (cleaned > 0) console.log();
|
|
62
|
+
|
|
63
|
+
let synced = 0;
|
|
64
|
+
let total = 0;
|
|
65
|
+
|
|
66
|
+
if (!existsSync(skillsDir)) {
|
|
67
|
+
console.log(` ⚠️ No skills directory found, nothing to sync.\n`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skillFolders = readdirSync(skillsDir, { withFileTypes: true })
|
|
72
|
+
.filter((d) => d.isDirectory())
|
|
73
|
+
.map((d) => d.name);
|
|
74
|
+
|
|
75
|
+
if (skillFolders.length === 0) {
|
|
76
|
+
console.log(` ⚠️ No skill folders found, nothing to sync.\n`);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const claudeSkillsDir = resolve(projectRoot, ".claude", "skills");
|
|
81
|
+
|
|
82
|
+
for (const folder of skillFolders) {
|
|
83
|
+
const src = resolve(skillsDir, folder);
|
|
84
|
+
const dest = resolve(claudeSkillsDir, folder);
|
|
85
|
+
total++;
|
|
86
|
+
try {
|
|
87
|
+
mkdirSync(dest, { recursive: true });
|
|
88
|
+
cpSync(src, dest, { recursive: true });
|
|
89
|
+
console.log(` ✅ .claude/skills/${folder}/`);
|
|
90
|
+
synced++;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.log(` ⚠️ .claude/skills/${folder}/ skipped (${err.message})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`\n Synced ${synced}/${total} skills.\n`);
|