motion-presets-kit 0.1.0
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 +174 -0
- package/bin/cli.mjs +115 -0
- package/bin/skill.mjs +94 -0
- package/dist/chunk-GU3ZCMLK.js +537 -0
- package/dist/chunk-GU3ZCMLK.js.map +1 -0
- package/dist/chunk-ONXBTJWT.js +167 -0
- package/dist/chunk-ONXBTJWT.js.map +1 -0
- package/dist/chunk-PFZKSPJW.js +164 -0
- package/dist/chunk-PFZKSPJW.js.map +1 -0
- package/dist/components.cjs +719 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.d.cts +199 -0
- package/dist/components.d.ts +199 -0
- package/dist/components.js +4 -0
- package/dist/components.js.map +1 -0
- package/dist/index-DtNOkqyg.d.cts +112 -0
- package/dist/index-DtNOkqyg.d.ts +112 -0
- package/dist/index.cjs +890 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/presets.cjs +334 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +3 -0
- package/dist/presets.d.ts +3 -0
- package/dist/presets.js +4 -0
- package/dist/presets.js.map +1 -0
- package/package.json +117 -0
- package/scripts/postinstall.mjs +75 -0
- package/skill/motion-presets-kit/SKILL.md +221 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Orlando Lopez
|
|
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,174 @@
|
|
|
1
|
+
# motion-presets-kit
|
|
2
|
+
|
|
3
|
+
Set tipado y validado en runtime de helpers para [`motion/react`](https://motion.dev) que facilita crear animaciones de entrada, salida y transiciones con valores por defecto sensatos.
|
|
4
|
+
|
|
5
|
+
- **API variant-returning**: funciones puras que retornan `Variants` de motion.
|
|
6
|
+
- **Validación con Zod** de todas las opciones (los tipos se derivan de los schemas).
|
|
7
|
+
- **Tree-shakeable**: 3 entry points (`.`, `./presets`, `./components`).
|
|
8
|
+
- **SSR-safe** y respeta `prefers-reduced-motion` por defecto.
|
|
9
|
+
|
|
10
|
+
## Instalación
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add motion-presets-kit motion zod react react-dom
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`react`, `react-dom`, `motion` y `zod` son peer dependencies.
|
|
17
|
+
|
|
18
|
+
## Skill para agentes de IA
|
|
19
|
+
|
|
20
|
+
El paquete incluye una skill (`skill/motion-presets-kit/SKILL.md`) para que
|
|
21
|
+
asistentes como opencode, Claude o agentes compatibles conozcan la API completa.
|
|
22
|
+
Al terminar la instalación se pregunta si deseas instalarla (solo en terminal
|
|
23
|
+
interactiva; en CI se omite). También puedes instalarla en cualquier momento:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx motion-presets-kit add-skill # auto-detecta .opencode, .claude, .agents
|
|
27
|
+
npx motion-presets-kit add-skill --global # instala en tu HOME
|
|
28
|
+
npx motion-presets-kit add-skill --tool opencode --project
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Para omitir la pregunta usa `MOTION_PRESETS_KIT_SKIP_SKILL=1`.
|
|
32
|
+
|
|
33
|
+
## Uso rápido
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { motion } from "motion/react";
|
|
37
|
+
import { fade, parentVariants } from "motion-presets-kit/presets";
|
|
38
|
+
|
|
39
|
+
export function Hero() {
|
|
40
|
+
return (
|
|
41
|
+
<motion.div variants={parentVariants({ delayChildren: 0.12 })}>
|
|
42
|
+
<motion.h1 variants={fade({ direction: "up" })}>Hola</motion.h1>
|
|
43
|
+
<motion.p variants={fade({ direction: "up", excludeDelay: true })}>
|
|
44
|
+
Motion presets kit
|
|
45
|
+
</motion.p>
|
|
46
|
+
</motion.div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { TextAnimate, Marquee, useCounter, Draggable } from "motion-presets-kit/components";
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Entry points
|
|
56
|
+
|
|
57
|
+
| Import | Contenido |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `motion-presets-kit` | schemas + presets + componentes |
|
|
60
|
+
| `motion-presets-kit/presets` | `fade`, `slide`, `scale`, `clipReveal`, `parentVariants` |
|
|
61
|
+
| `motion-presets-kit/components` | `TextAnimate`, `Marquee`, `Draggable`, `useCounter` |
|
|
62
|
+
|
|
63
|
+
## Presets
|
|
64
|
+
|
|
65
|
+
Todos los presets aceptan: `duration` (0.5), `delay` (0), `ease` (`[0.16, 1, 0.3, 1]`), `spring`, `excludeDelay` (false) y `reducedMotion` (true).
|
|
66
|
+
|
|
67
|
+
### `fade(options?)`
|
|
68
|
+
|
|
69
|
+
`direction` (`"up" | "down" | "left" | "right" | "none"`, default `"up"`), `distance` (60), `blur` (0), `scale` (1).
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<motion.div variants={fade({ direction: "left", distance: 40, blur: 8 })} />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `slide(options?)`
|
|
76
|
+
|
|
77
|
+
Desplazamiento puro, **sin `opacity`**. `direction` (default `"right"`), `distance` (100).
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<motion.div variants={slide({ direction: "right" })} />
|
|
81
|
+
// initial: { x: -100 } → animate: { x: 0 }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `scale(options?)`
|
|
85
|
+
|
|
86
|
+
`from` (0.8) → `1` junto con `opacity: 0 → 1`.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<motion.div variants={scale({ from: 0.5 })} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `clipReveal(options?)`
|
|
93
|
+
|
|
94
|
+
Anima `clipPath` con insets por dirección y `scale` opcional.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<motion.div variants={clipReveal({ direction: "up", scale: 1.1 })} />
|
|
98
|
+
// initial: { clipPath: "inset(100% 0% 0% 0%)" } → animate: { clipPath: "inset(0% 0% 0% 0%)" }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `parentVariants(options?)`
|
|
102
|
+
|
|
103
|
+
Contenedor para stagger: `delayChildren` (0) y `startDelay` (0).
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<motion.div variants={parentVariants({ delayChildren: 0.1 })} initial="initial" whileInView="animate">
|
|
107
|
+
<motion.span variants={fade({ excludeDelay: true })} />
|
|
108
|
+
<motion.span variants={fade({ excludeDelay: true })} />
|
|
109
|
+
</motion.div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Componentes y hooks
|
|
113
|
+
|
|
114
|
+
### `<TextAnimate>`
|
|
115
|
+
|
|
116
|
+
`text` (requerido), `as` (`"p"`), `by` (`"word" | "letter"`), `type` (`blurIn | slideUp | slideDown | slideLeft | slideRight | typeWriter`), `duration`, `startDelay`, `highlight: string[]`, `highlightClassName`.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<TextAnimate text="Hola <strong>mundo</strong>" by="letter" type="blurIn" highlight={["mundo"]} />
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
El HTML inline se inyecta con `dangerouslySetInnerHTML`; sanitiza `text` si viene de input no confiable.
|
|
123
|
+
|
|
124
|
+
### `<Marquee>`
|
|
125
|
+
|
|
126
|
+
CSS puro con `@keyframes` dinámicos. `children` (requerido), `speed` (20 s/vuelta), `direction` (`"left"`), `pauseOnHover` (true), `gap` (`"2rem"`).
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<Marquee speed={30} direction="right" gap="1.5rem">
|
|
130
|
+
<span>Logo 1</span>
|
|
131
|
+
<span>Logo 2</span>
|
|
132
|
+
</Marquee>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `<Draggable>`
|
|
136
|
+
|
|
137
|
+
Wrapper de `motion.div`. `axis` (`"both"`), `bounds` (`"parent" | "document" | {top,right,bottom,left}`), `snapToGrid: [number, number]`, `dragElastic`, `dragMomentum`, `dragControls`, `onDragStart`/`onDrag`/`onDragEnd`.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<Draggable axis="x" bounds="parent" snapToGrid={[20, 20]}>
|
|
141
|
+
<div>Arrastrame</div>
|
|
142
|
+
</Draggable>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Con `bounds="parent"` el elemento queda restringido a su contenedor; el contenedor debería poder medirse (`position: relative`).
|
|
146
|
+
|
|
147
|
+
### `useCounter(ref, options?)`
|
|
148
|
+
|
|
149
|
+
`from` (0), `to` (100), `decimals` (0), `prefix`, `suffix`, `separator` (","), `format`, `duration` (1). Escribe el valor formateado en `ref.current.textContent` y retorna `{ value, start, reset }`.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
153
|
+
const { start, reset } = useCounter(ref, { to: 1250, separator: ",", suffix: " px" });
|
|
154
|
+
return <span ref={ref} />;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Casos límite documentados
|
|
158
|
+
|
|
159
|
+
- **CL-001 — `excludeDelay` con stagger**: un hijo con `delay` propio sobrescribe el stagger del padre. Usa siempre `excludeDelay: true` en los hijos dentro de `parentVariants()`.
|
|
160
|
+
- **CL-003 — ClipReveal con `border-radius`**: el `clip-path` se superpone al radio del borde; es una limitación conocida del `clip-path`.
|
|
161
|
+
- **CL-005 — Counter con negativos**: el formateo preserva el signo (`-1,250`).
|
|
162
|
+
- **CL-007 — Draggable con bounds inválidos**: si `top > bottom` o `left > right`, se emite un `warn()` y se ignoran los límites.
|
|
163
|
+
- **CL-008 — SSR**: los presets retornan variants estáticas (sin `window`). Los componentes usan `whileInView`, que se activa solo en el cliente.
|
|
164
|
+
- **CL-009 — Reduced motion**: con `prefers-reduced-motion: reduce` (y `reducedMotion: true`, por defecto), los presets saltan al estado final, `TextAnimate` muestra el texto sin animar y `Marquee` no reproduce el loop.
|
|
165
|
+
|
|
166
|
+
## Desarrollo
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm playground # demo Vite de todos los presets/componentes
|
|
170
|
+
pnpm test # build + tests con node:test
|
|
171
|
+
pnpm typecheck
|
|
172
|
+
pnpm lint
|
|
173
|
+
pnpm build
|
|
174
|
+
```
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectTargets,
|
|
4
|
+
installSkill,
|
|
5
|
+
readPackageVersion,
|
|
6
|
+
SKILL_NAME,
|
|
7
|
+
TOOLS,
|
|
8
|
+
} from "./skill.mjs";
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
motion-presets-kit ${readPackageVersion()}
|
|
12
|
+
|
|
13
|
+
Uso:
|
|
14
|
+
motion-presets-kit <comando> [opciones]
|
|
15
|
+
|
|
16
|
+
Comandos:
|
|
17
|
+
add-skill Instala la skill de ${SKILL_NAME} para agentes de IA.
|
|
18
|
+
help Muestra esta ayuda.
|
|
19
|
+
|
|
20
|
+
Opciones:
|
|
21
|
+
--global Instala en el directorio de usuario (home).
|
|
22
|
+
--project Instala en el proyecto actual (por defecto).
|
|
23
|
+
--tool <nombre> ${Object.keys(TOOLS).join(" | ")} | all (por defecto: all).
|
|
24
|
+
--dir <ruta> Directorio de skills explícito.
|
|
25
|
+
--force Sobrescribe una skill ya instalada.
|
|
26
|
+
|
|
27
|
+
Ejemplos:
|
|
28
|
+
npx motion-presets-kit add-skill
|
|
29
|
+
npx motion-presets-kit add-skill --global
|
|
30
|
+
npx motion-presets-kit add-skill --tool opencode --project
|
|
31
|
+
`.trim();
|
|
32
|
+
|
|
33
|
+
function parseArgs(argv) {
|
|
34
|
+
const options = {
|
|
35
|
+
command: null,
|
|
36
|
+
global: false,
|
|
37
|
+
project: false,
|
|
38
|
+
tool: "all",
|
|
39
|
+
dir: undefined,
|
|
40
|
+
force: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
44
|
+
const arg = argv[index];
|
|
45
|
+
if (arg === "add-skill" || arg === "skill" || arg === "init") {
|
|
46
|
+
options.command = "add-skill";
|
|
47
|
+
} else if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
48
|
+
options.command = "help";
|
|
49
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
50
|
+
options.command = "version";
|
|
51
|
+
} else if (arg === "--global" || arg === "-g") {
|
|
52
|
+
options.global = true;
|
|
53
|
+
} else if (arg === "--project") {
|
|
54
|
+
options.project = true;
|
|
55
|
+
} else if (arg === "--force" || arg === "-f") {
|
|
56
|
+
options.force = true;
|
|
57
|
+
} else if (arg === "--tool") {
|
|
58
|
+
options.tool = argv[index + 1] ?? "all";
|
|
59
|
+
index += 1;
|
|
60
|
+
} else if (arg === "--dir") {
|
|
61
|
+
options.dir = argv[index + 1];
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function run() {
|
|
69
|
+
const options = parseArgs(process.argv.slice(2));
|
|
70
|
+
|
|
71
|
+
if (options.command === "version") {
|
|
72
|
+
console.log(readPackageVersion());
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (options.command === "help" || options.command === null) {
|
|
76
|
+
console.log(HELP);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (options.tool !== "all" && !(options.tool in TOOLS)) {
|
|
81
|
+
console.error(
|
|
82
|
+
`[${SKILL_NAME}] Herramienta desconocida: ${options.tool}. Usa: ${Object.keys(
|
|
83
|
+
TOOLS,
|
|
84
|
+
).join(", ")} o all.`,
|
|
85
|
+
);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const targets = detectTargets({
|
|
91
|
+
global: options.global && !options.project,
|
|
92
|
+
tool: options.tool,
|
|
93
|
+
dir: options.dir,
|
|
94
|
+
});
|
|
95
|
+
const results = installSkill(targets, { force: options.force });
|
|
96
|
+
|
|
97
|
+
for (const result of results) {
|
|
98
|
+
if (result.status === "installed") {
|
|
99
|
+
console.log(
|
|
100
|
+
`[${SKILL_NAME}] Skill instalada (${result.tool}): ${result.destination}`,
|
|
101
|
+
);
|
|
102
|
+
} else if (result.status === "exists") {
|
|
103
|
+
console.log(
|
|
104
|
+
`[${SKILL_NAME}] Ya existía (${result.tool}): ${result.destination}. Usa --force para sobrescribir.`,
|
|
105
|
+
);
|
|
106
|
+
} else {
|
|
107
|
+
console.error(
|
|
108
|
+
`[${SKILL_NAME}] Error al instalar (${result.tool}): ${result.error}`,
|
|
109
|
+
);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
run();
|
package/bin/skill.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
export const PACKAGE_ROOT = resolve(HERE, "..");
|
|
13
|
+
export const SKILL_NAME = "motion-presets-kit";
|
|
14
|
+
export const SKILL_SOURCE = join(
|
|
15
|
+
PACKAGE_ROOT,
|
|
16
|
+
"skill",
|
|
17
|
+
SKILL_NAME,
|
|
18
|
+
"SKILL.md",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/** Raíces por herramienta: proyecto y global. */
|
|
22
|
+
export const TOOLS = {
|
|
23
|
+
opencode: { projectRoot: ".opencode", globalRoot: join(".config", "opencode") },
|
|
24
|
+
claude: { projectRoot: ".claude", globalRoot: ".claude" },
|
|
25
|
+
agents: { projectRoot: ".agents", globalRoot: ".agents" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function readPackageVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(
|
|
31
|
+
readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"),
|
|
32
|
+
);
|
|
33
|
+
return pkg.version ?? "0.0.0";
|
|
34
|
+
} catch {
|
|
35
|
+
return "0.0.0";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resuelve los directorios de skills donde instalar.
|
|
41
|
+
* Con `dir` se usa ese directorio; si no, auto-detecta las herramientas
|
|
42
|
+
* existentes (opencode, claude, agents) y cae en opencode si no hay ninguna.
|
|
43
|
+
*/
|
|
44
|
+
export function detectTargets({ global = false, tool = "all", dir } = {}) {
|
|
45
|
+
if (dir) return [{ tool: "custom", dir: resolve(dir) }];
|
|
46
|
+
|
|
47
|
+
const names = tool === "all" ? Object.keys(TOOLS) : [tool];
|
|
48
|
+
const all = names.map((name) => {
|
|
49
|
+
const root = global
|
|
50
|
+
? join(homedir(), TOOLS[name].globalRoot)
|
|
51
|
+
: resolve(process.cwd(), TOOLS[name].projectRoot);
|
|
52
|
+
return { tool: name, root, dir: join(root, "skills") };
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const existing = all.filter((target) => existsSync(target.root));
|
|
56
|
+
if (existing.length > 0) {
|
|
57
|
+
return existing.map(({ tool: name, dir: skillsDir }) => ({
|
|
58
|
+
tool: name,
|
|
59
|
+
dir: skillsDir,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
if (tool !== "all") {
|
|
63
|
+
return all.map(({ tool: name, dir: skillsDir }) => ({
|
|
64
|
+
tool: name,
|
|
65
|
+
dir: skillsDir,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
return [{ tool: names[0], dir: all[0].dir }];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Copia la skill a cada directorio destino.
|
|
73
|
+
* Devuelve un resultado por destino con el estado de la operación.
|
|
74
|
+
*/
|
|
75
|
+
export function installSkill(targets, { force = false } = {}) {
|
|
76
|
+
return targets.map((target) => {
|
|
77
|
+
const destination = join(target.dir, SKILL_NAME, "SKILL.md");
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(destination) && !force) {
|
|
80
|
+
return { ...target, destination, status: "exists" };
|
|
81
|
+
}
|
|
82
|
+
mkdirSync(join(target.dir, SKILL_NAME), { recursive: true });
|
|
83
|
+
copyFileSync(SKILL_SOURCE, destination);
|
|
84
|
+
return { ...target, destination, status: "installed" };
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
...target,
|
|
88
|
+
destination,
|
|
89
|
+
status: "error",
|
|
90
|
+
error: error instanceof Error ? error.message : String(error),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|