@live-change/frontend-template 0.9.198 → 0.9.199
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/.claude/rules/live-change-backend-actions-views-triggers.md +184 -0
- package/.claude/rules/live-change-backend-architecture.md +126 -0
- package/.claude/rules/live-change-backend-models-and-relations.md +188 -0
- package/.claude/rules/live-change-frontend-vue-primevue.md +291 -0
- package/.claude/rules/live-change-service-structure.md +89 -0
- package/.claude/skills/create-skills-and-rules.md +196 -0
- package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
- package/.claude/skills/live-change-design-models-relations.md +173 -0
- package/.claude/skills/live-change-design-service.md +132 -0
- package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
- package/.claude/skills/live-change-frontend-action-form.md +143 -0
- package/.claude/skills/live-change-frontend-analytics.md +146 -0
- package/.claude/skills/live-change-frontend-command-forms.md +215 -0
- package/.claude/skills/live-change-frontend-data-views.md +182 -0
- package/.claude/skills/live-change-frontend-editor-form.md +177 -0
- package/.claude/skills/live-change-frontend-locale-time.md +171 -0
- package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
- package/.claude/skills/live-change-frontend-range-list.md +128 -0
- package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +202 -0
- package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +194 -0
- package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
- package/.cursor/rules/live-change-service-structure.mdc +107 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +197 -0
- package/.cursor/skills/live-change-design-models-relations.md +168 -0
- package/.cursor/skills/live-change-design-service.md +75 -0
- package/.cursor/skills/live-change-frontend-action-buttons.md +128 -0
- package/.cursor/skills/live-change-frontend-action-form.md +143 -0
- package/.cursor/skills/live-change-frontend-analytics.md +146 -0
- package/.cursor/skills/live-change-frontend-command-forms.md +215 -0
- package/.cursor/skills/live-change-frontend-data-views.md +182 -0
- package/.cursor/skills/live-change-frontend-editor-form.md +177 -0
- package/.cursor/skills/live-change-frontend-locale-time.md +171 -0
- package/.cursor/skills/live-change-frontend-page-list-detail.md +200 -0
- package/.cursor/skills/live-change-frontend-range-list.md +128 -0
- package/.cursor/skills/live-change-frontend-ssr-setup.md +119 -0
- package/README.md +71 -0
- package/package.json +50 -50
- package/server/app.config.js +35 -0
- package/server/services.list.js +2 -0
- package/.nx/workspace-data/file-map.json +0 -195
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/project-graph.json +0 -8
- package/.nx/workspace-data/project-graph.lock +0 -0
- package/.nx/workspace-data/source-maps.json +0 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for Vue 3, PrimeVue, Tailwind frontend development on LiveChange
|
|
3
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Frontend on live-change-stack – Vue 3 + PrimeVue + Tailwind (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use these rules when working on frontends that talk to LiveChange backends.
|
|
9
|
+
|
|
10
|
+
## Stack
|
|
11
|
+
|
|
12
|
+
- Vue 3 + TypeScript
|
|
13
|
+
- PrimeVue 4 for UI components
|
|
14
|
+
- Tailwind CSS for styling (prefer utility classes, avoid unnecessary custom CSS)
|
|
15
|
+
- vite-plugin-pages for file-based routing in `src/pages/`
|
|
16
|
+
- `@live-change/vue3-ssr` for integration with the backend
|
|
17
|
+
|
|
18
|
+
## Data loading – `live` + `Promise.all` (Suspense)
|
|
19
|
+
|
|
20
|
+
- **Do not** use `ref(null)` + `onMounted` to fetch data.
|
|
21
|
+
- Always fetch data using `await Promise.all([...])` and `live(path()...)` in `script setup`.
|
|
22
|
+
- The root app should wrap pages with `<Suspense>` (usually handled by `ViewRoot` in live-change frontends).
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { path, live, api as useApi } from '@live-change/vue3-ssr'
|
|
28
|
+
|
|
29
|
+
const api = useApi()
|
|
30
|
+
|
|
31
|
+
const [devices] = await Promise.all([
|
|
32
|
+
live(path().deviceManager.myUserDevices({}))
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
const [device, connections] = await Promise.all([
|
|
36
|
+
live(path().deviceManager.myUserDevice({ device: deviceId })),
|
|
37
|
+
live(path().deviceManager.deviceOwnedDeviceConnections({ device: deviceId }))
|
|
38
|
+
])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
In templates, use the `.value` of these refs:
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<template>
|
|
45
|
+
<div v-if="device.value">
|
|
46
|
+
{{ device.value.name }}
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Commands and forms – choosing the right pattern
|
|
52
|
+
|
|
53
|
+
There are 4 ways to execute backend actions. Use the right one:
|
|
54
|
+
|
|
55
|
+
| Pattern | When to use |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `editorData` | **Editing model records** (create/update). Drafts, validation, `AutoField`. Use for settings, editors, profiles. |
|
|
58
|
+
| `actionData` | **One-shot action forms** (not CRUD). Submit once → done. Use for publish, invite, import. |
|
|
59
|
+
| `api.command` | **Single button or programmatic calls** (no form fields). Use for delete, toggle, code-triggered actions. |
|
|
60
|
+
| `<command-form>` | **Avoid.** Legacy, only for trivial prototypes. Prefer `editorData` or `actionData`. |
|
|
61
|
+
|
|
62
|
+
Decision flow:
|
|
63
|
+
|
|
64
|
+
1. Does the user fill in form fields? → **No**: use `api.command` (wrap in `workingZone.addPromise` for buttons).
|
|
65
|
+
2. Is it editing a model record (create/update)? → **Yes**: use `editorData`. **No**: use `actionData`.
|
|
66
|
+
3. Only use `<command-form>` for the simplest throwaway cases.
|
|
67
|
+
|
|
68
|
+
### `api.command`
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
await api.command(['deviceManager', 'createMyUserDevice'], {
|
|
72
|
+
name: 'My device'
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
await api.command(['deviceManager', 'deleteMyUserDevice'], {
|
|
76
|
+
device: id
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Format:
|
|
81
|
+
|
|
82
|
+
- `['serviceName', 'actionName']` as the first argument,
|
|
83
|
+
- payload object as the second argument.
|
|
84
|
+
|
|
85
|
+
## Routing – `<route>` block and `meta.signedIn`
|
|
86
|
+
|
|
87
|
+
- Each page in `src/pages/` can declare its route meta in a `<route>` block.
|
|
88
|
+
- Use `meta.signedIn` for pages that require authentication.
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<route>
|
|
92
|
+
{ "name": "devices", "meta": { "signedIn": true } }
|
|
93
|
+
</route>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Dynamic routes:
|
|
97
|
+
|
|
98
|
+
- `[id].vue` corresponds to `/devices/:id`.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { useRoute } from 'vue-router'
|
|
102
|
+
|
|
103
|
+
const route = useRoute()
|
|
104
|
+
const deviceId = route.params.id
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Confirm + Toast for destructive actions
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
111
|
+
import { useToast } from 'primevue/usetoast'
|
|
112
|
+
|
|
113
|
+
const confirm = useConfirm()
|
|
114
|
+
const toast = useToast()
|
|
115
|
+
|
|
116
|
+
function deleteDevice(id) {
|
|
117
|
+
confirm.require({
|
|
118
|
+
message: 'Are you sure you want to delete this device?',
|
|
119
|
+
header: 'Confirmation',
|
|
120
|
+
icon: 'pi pi-exclamation-triangle',
|
|
121
|
+
accept: async () => {
|
|
122
|
+
await api.command(['deviceManager', 'deleteMyUserDevice'], { device: id })
|
|
123
|
+
toast.add({
|
|
124
|
+
severity: 'success',
|
|
125
|
+
summary: 'Deleted',
|
|
126
|
+
life: 2000
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Common UI patterns – list and detail
|
|
134
|
+
|
|
135
|
+
### List page
|
|
136
|
+
|
|
137
|
+
```vue
|
|
138
|
+
<template>
|
|
139
|
+
<div class="container mx-auto p-4">
|
|
140
|
+
<div class="flex items-center justify-between mb-6">
|
|
141
|
+
<h1 class="text-2xl font-bold">Devices</h1>
|
|
142
|
+
<Button label="Add" icon="pi pi-plus" @click="openDialog" />
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<Card v-if="devices.value?.length === 0">
|
|
146
|
+
<template #content>
|
|
147
|
+
<p class="text-center text-gray-500">
|
|
148
|
+
No devices yet
|
|
149
|
+
</p>
|
|
150
|
+
</template>
|
|
151
|
+
</Card>
|
|
152
|
+
|
|
153
|
+
<div class="grid gap-4">
|
|
154
|
+
<Card v-for="device in devices.value" :key="device.id">
|
|
155
|
+
<template #content>
|
|
156
|
+
<!-- content -->
|
|
157
|
+
</template>
|
|
158
|
+
</Card>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Status tag
|
|
165
|
+
|
|
166
|
+
```vue
|
|
167
|
+
<Tag
|
|
168
|
+
:value="conn.status"
|
|
169
|
+
:severity="conn.status === 'online' ? 'success' : 'secondary'"
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Computed paths – reactive parameters
|
|
174
|
+
|
|
175
|
+
When paths depend on reactive values (route params, props), wrap them in `computed()`:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import { computed, unref } from 'vue'
|
|
179
|
+
import { usePath, live } from '@live-change/vue3-ssr'
|
|
180
|
+
|
|
181
|
+
const path = usePath()
|
|
182
|
+
|
|
183
|
+
const articlePath = computed(() => path.blog.article({ article: unref(articleId) }))
|
|
184
|
+
const [article] = await Promise.all([live(articlePath)])
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
For conditional loading (e.g. only when logged in), return a falsy value:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
import { useClient } from '@live-change/vue3-ssr'
|
|
191
|
+
const client = useClient()
|
|
192
|
+
|
|
193
|
+
const myDataPath = computed(() => client.value.user && path.blog.myArticles({}))
|
|
194
|
+
const [myData] = await Promise.all([live(myDataPath)])
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Related data – `.with()`
|
|
198
|
+
|
|
199
|
+
Attach related objects to items in a single reactive query:
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
path.blog.articles({})
|
|
203
|
+
.with(article => path.userIdentification.identification({
|
|
204
|
+
sessionOrUserType: article.authorType,
|
|
205
|
+
sessionOrUser: article.author
|
|
206
|
+
}).bind('authorProfile'))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Access: `article.authorProfile?.firstName`. Works with both `live()` and `RangeViewer`.
|
|
210
|
+
|
|
211
|
+
## WorkingZone for async actions
|
|
212
|
+
|
|
213
|
+
`ViewRoot` wraps every page in `<WorkingZone>`. Use `inject('workingZone')` for non-form button actions:
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
import { inject } from 'vue'
|
|
217
|
+
const workingZone = inject('workingZone')
|
|
218
|
+
|
|
219
|
+
function doAction() {
|
|
220
|
+
workingZone.addPromise('actionName', (async () => {
|
|
221
|
+
await actions.blog.publishArticle({ article: id })
|
|
222
|
+
toast.add({ severity: 'success', summary: 'Published', life: 2000 })
|
|
223
|
+
})())
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This activates the global loading spinner/blur while the promise is pending.
|
|
228
|
+
|
|
229
|
+
## Auth guards with `useClient`
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
import { useClient } from '@live-change/vue3-ssr'
|
|
233
|
+
const client = useClient()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- `client.value.user` – truthy when logged in
|
|
237
|
+
- `client.value.roles` – array of roles (e.g. `['admin', 'owner']`)
|
|
238
|
+
|
|
239
|
+
Use in templates:
|
|
240
|
+
|
|
241
|
+
```vue
|
|
242
|
+
<Button v-if="client.roles.includes('admin')" label="Admin" />
|
|
243
|
+
<div v-if="!client.user">Please sign in</div>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Locale and time
|
|
247
|
+
|
|
248
|
+
- Call `useLocale().captureLocale()` in `App.vue` to save browser locale to backend.
|
|
249
|
+
- Use `locale.localTime(date)` with vue-i18n's `d()` for SSR-safe date display.
|
|
250
|
+
- `currentTime` from `@live-change/frontend-base` is a reactive ref that ticks every 500ms.
|
|
251
|
+
- `useTimeSynchronization()` from `@live-change/vue3-ssr` corrects clock skew – use when timing is critical (countdowns, real-time events).
|
|
252
|
+
|
|
253
|
+
## Analytics
|
|
254
|
+
|
|
255
|
+
Use `analytics` from `@live-change/vue3-components`:
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
import { analytics } from '@live-change/vue3-components'
|
|
259
|
+
analytics.emit('article:published', { articleId: id })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Wire providers (PostHog, GA4) in a separate file imported from `App.vue`.
|
|
263
|
+
|
|
264
|
+
## SSR and frontend configuration
|
|
265
|
+
|
|
266
|
+
- Keep separate entry points for client and server:
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
// entry-client.js
|
|
270
|
+
import { clientEntry } from '@live-change/frontend-base/client-entry.js'
|
|
271
|
+
export default clientEntry(App, createRouter, config)
|
|
272
|
+
|
|
273
|
+
// entry-server.js
|
|
274
|
+
import { serverEntry, sitemapEntry } from '@live-change/frontend-base/server-entry.js'
|
|
275
|
+
export const render = serverEntry(App, createRouter, config)
|
|
276
|
+
export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- Configure PrimeVue theme in one place (e.g. `config.js`) using `definePreset` and keep dark mode options consistent.
|
|
280
|
+
|
|
281
|
+
## Discovering views and actions with `describe`
|
|
282
|
+
|
|
283
|
+
Use the CLI `describe` command to find available views (for `live()`) and actions (for `api.command` / `editorData` / `actionData`):
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
node server/start.js describe --service blog
|
|
287
|
+
node server/start.js describe --service blog --view articlesByCreatedAt --output yaml
|
|
288
|
+
node server/start.js describe --service blog --action createMyUserArticle --output yaml
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
This is the fastest way to discover what paths and actions are available, including those auto-generated by relations.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for LiveChange service directory structure and file organization
|
|
3
|
+
globs: **/services/**/*.js
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LiveChange Service Structure
|
|
7
|
+
|
|
8
|
+
Every LiveChange service **must** be a directory, not a single file.
|
|
9
|
+
|
|
10
|
+
## Required structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
server/services/<serviceName>/
|
|
14
|
+
definition.js # creates app.createServiceDefinition({ name }) – nothing else
|
|
15
|
+
index.js # imports definition, imports all domain files, exports definition
|
|
16
|
+
config.js # optional – reads definition.config, exports resolved config object
|
|
17
|
+
<domain>.js # one file per domain area (models, views, actions, triggers)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## definition.js
|
|
21
|
+
|
|
22
|
+
Only creates and exports the definition. No models, no actions here.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import App from '@live-change/framework'
|
|
26
|
+
const app = App.app()
|
|
27
|
+
|
|
28
|
+
const definition = app.createServiceDefinition({ name: 'myService' })
|
|
29
|
+
|
|
30
|
+
export default definition
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## index.js
|
|
34
|
+
|
|
35
|
+
Imports definition and all domain files (side-effect imports), then re-exports definition.
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
import App from '@live-change/framework'
|
|
39
|
+
const app = App.app()
|
|
40
|
+
|
|
41
|
+
import definition from './definition.js'
|
|
42
|
+
|
|
43
|
+
import './authenticator.js'
|
|
44
|
+
import './myModel.js'
|
|
45
|
+
import './otherModel.js'
|
|
46
|
+
|
|
47
|
+
export default definition
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## config.js (if needed)
|
|
51
|
+
|
|
52
|
+
Reads `definition.config` set from `app.config.js`, resolves defaults, exports plain object.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import definition from './definition.js'
|
|
56
|
+
|
|
57
|
+
const { someOption = 'default' } = definition.config
|
|
58
|
+
|
|
59
|
+
export default { someOption }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Domain files (e.g. myModel.js)
|
|
63
|
+
|
|
64
|
+
Each file imports `definition` (and `config` if needed) and registers models/views/actions/triggers.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import App from '@live-change/framework'
|
|
68
|
+
const app = App.app()
|
|
69
|
+
|
|
70
|
+
import definition from './definition.js'
|
|
71
|
+
|
|
72
|
+
export const MyModel = definition.model({ name: 'MyModel', ... })
|
|
73
|
+
|
|
74
|
+
definition.view({ name: 'myList', ... })
|
|
75
|
+
definition.action({ name: 'doThing', ... })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## services.list.js import
|
|
79
|
+
|
|
80
|
+
Always import from the directory index, not a flat file:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import myService from './services/myService/index.js' // correct
|
|
84
|
+
import myService from './services/myService.js' // wrong
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Reference implementation
|
|
88
|
+
|
|
89
|
+
See `/home/m8/IdeaProjects/live-change/live-change-stack/services/stripe-service/` as the canonical example.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create or update Claude Code skills and Cursor rules/skills with proper format and frontmatter
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Skill: create-skills-and-rules
|
|
6
|
+
|
|
7
|
+
Use this skill when you need to **create or update skills and rules** for Claude Code (`.claude/`) and Cursor (`.cursor/`).
|
|
8
|
+
|
|
9
|
+
## Directory structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
.claude/
|
|
13
|
+
rules/ *.md – always-loaded project instructions
|
|
14
|
+
skills/ *.md – step-by-step guides, invocable or auto-matched by description
|
|
15
|
+
.cursor/
|
|
16
|
+
rules/ *.mdc – Cursor rules with description/globs/alwaysApply frontmatter
|
|
17
|
+
skills/ *.md – same content as .claude/skills/ (Cursor reads them as reference)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Step 1 – Decide: rule or skill
|
|
21
|
+
|
|
22
|
+
| Type | Purpose | When loaded |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **Rule** | Constraints, conventions, best practices | Automatically, based on `globs` or `alwaysApply` |
|
|
25
|
+
| **Skill** | Step-by-step implementation guide | When description matches task, or invoked via `/skill-name` |
|
|
26
|
+
|
|
27
|
+
Rules say **what to do / not do**. Skills say **how to do it step by step**.
|
|
28
|
+
|
|
29
|
+
## Step 2 – Create a Claude Code skill (`.claude/skills/<name>.md`)
|
|
30
|
+
|
|
31
|
+
### Frontmatter
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
---
|
|
35
|
+
description: Short description of what this skill does and when to use it
|
|
36
|
+
---
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `description` field is used by Claude to decide when to automatically apply the skill. Write it as a concise action phrase.
|
|
40
|
+
|
|
41
|
+
### Optional frontmatter fields (for directory-based skills)
|
|
42
|
+
|
|
43
|
+
If you use the directory format `.claude/skills/<name>/SKILL.md`:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
---
|
|
47
|
+
name: my-skill
|
|
48
|
+
description: What this skill does
|
|
49
|
+
user-invocable: true
|
|
50
|
+
disable-model-invocation: false
|
|
51
|
+
allowed-tools: Read, Grep, Bash
|
|
52
|
+
context: fork
|
|
53
|
+
agent: Explore
|
|
54
|
+
argument-hint: [filename]
|
|
55
|
+
model: sonnet
|
|
56
|
+
---
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Field | Default | Description |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `name` | directory name | Slash command name (lowercase, hyphens, max 64 chars) |
|
|
62
|
+
| `description` | – | When to use (Claude matches this to decide auto-invocation) |
|
|
63
|
+
| `user-invocable` | `true` | Show in `/` menu |
|
|
64
|
+
| `disable-model-invocation` | `false` | Prevent auto-loading by Claude |
|
|
65
|
+
| `allowed-tools` | – | Tools allowed without asking (e.g. `Read, Grep, Bash`) |
|
|
66
|
+
| `context` | – | Set to `fork` to run in isolated subagent |
|
|
67
|
+
| `agent` | – | Subagent type when `context: fork` (`Explore`, `Plan`, etc.) |
|
|
68
|
+
| `argument-hint` | – | Hint for autocomplete (e.g. `[issue-number]`) |
|
|
69
|
+
| `model` | inherited | Model override (`sonnet`, `opus`, `haiku`) |
|
|
70
|
+
|
|
71
|
+
### Dynamic substitutions in skill content
|
|
72
|
+
|
|
73
|
+
- `$ARGUMENTS` – all arguments passed when invoking
|
|
74
|
+
- `$ARGUMENTS[0]`, `$1` – specific argument by index
|
|
75
|
+
- `${CLAUDE_SESSION_ID}` – current session ID
|
|
76
|
+
- `${CLAUDE_SKILL_DIR}` – directory containing SKILL.md
|
|
77
|
+
|
|
78
|
+
### Body structure
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
---
|
|
82
|
+
description: Build X with Y and Z
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
# Skill: my-skill-name (Claude Code)
|
|
86
|
+
|
|
87
|
+
Use this skill when you build **X** using Y and Z.
|
|
88
|
+
|
|
89
|
+
## When to use
|
|
90
|
+
|
|
91
|
+
- Bullet list of scenarios
|
|
92
|
+
|
|
93
|
+
## Step 1 – First step
|
|
94
|
+
|
|
95
|
+
Explanation + code example.
|
|
96
|
+
|
|
97
|
+
## Step 2 – Second step
|
|
98
|
+
|
|
99
|
+
Explanation + code example.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Keep skills under 500 lines. Use clear step numbering. Include real code examples.
|
|
103
|
+
|
|
104
|
+
## Step 3 – Create a Claude Code rule (`.claude/rules/<name>.md`)
|
|
105
|
+
|
|
106
|
+
### Frontmatter
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
---
|
|
110
|
+
description: Short description of this rule's purpose
|
|
111
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
112
|
+
---
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Field | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `description` | What this rule covers (used for matching) |
|
|
118
|
+
| `globs` | File patterns that trigger this rule (e.g. `**/*.js`, `**/services/**/*.js`) |
|
|
119
|
+
|
|
120
|
+
### Body structure
|
|
121
|
+
|
|
122
|
+
```markdown
|
|
123
|
+
---
|
|
124
|
+
description: Rules for X development
|
|
125
|
+
globs: **/*.js
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
# Title
|
|
129
|
+
|
|
130
|
+
## Section 1
|
|
131
|
+
|
|
132
|
+
- Rule bullet points
|
|
133
|
+
- Code examples
|
|
134
|
+
|
|
135
|
+
## Section 2
|
|
136
|
+
|
|
137
|
+
- More rules
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Rules are concise. Lead with the constraint, then show a code example.
|
|
141
|
+
|
|
142
|
+
## Step 4 – Create Cursor rule (`.cursor/rules/<name>.mdc`)
|
|
143
|
+
|
|
144
|
+
Same content as the Claude rule, but with Cursor-specific frontmatter:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
---
|
|
148
|
+
description: Short description of this rule's purpose
|
|
149
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
150
|
+
alwaysApply: false
|
|
151
|
+
---
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Field | Default | Description |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `description` | – | What the rule does; Cursor uses this to decide when to apply |
|
|
157
|
+
| `globs` | – | Comma-separated file patterns (e.g. `**/*.tsx, src/**/*.js`) |
|
|
158
|
+
| `alwaysApply` | `false` | If `true`, applies to every session regardless of context |
|
|
159
|
+
|
|
160
|
+
**Notes:**
|
|
161
|
+
- Do NOT quote glob patterns in frontmatter
|
|
162
|
+
- Keep rules short (target 25 lines, max 50 lines for best Cursor performance)
|
|
163
|
+
- The `.mdc` extension is required for Cursor
|
|
164
|
+
|
|
165
|
+
## Step 5 – Mirror Cursor skills (`.cursor/skills/<name>.md`)
|
|
166
|
+
|
|
167
|
+
Copy the `.claude/skills/*.md` file directly to `.cursor/skills/`. Same content, same filename. Cursor reads these as reference documents.
|
|
168
|
+
|
|
169
|
+
## Step 6 – Mirror to sub-projects
|
|
170
|
+
|
|
171
|
+
If the project has sub-projects with their own `.claude/` and `.cursor/` directories, copy the files there too:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
for dir in automation auto-firma; do
|
|
175
|
+
cp .claude/skills/my-skill.md "$dir/.claude/skills/"
|
|
176
|
+
cp .claude/skills/my-skill.md "$dir/.cursor/skills/"
|
|
177
|
+
cp .claude/rules/my-rule.md "$dir/.claude/rules/"
|
|
178
|
+
cp .cursor/rules/my-rule.mdc "$dir/.cursor/rules/"
|
|
179
|
+
done
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Naming conventions
|
|
183
|
+
|
|
184
|
+
- Use lowercase with hyphens: `live-change-frontend-editor-form.md`
|
|
185
|
+
- Prefix with domain: `live-change-frontend-*`, `live-change-backend-*`
|
|
186
|
+
- Skills describe actions: `*-editor-form`, `*-range-list`, `*-action-buttons`
|
|
187
|
+
- Rules describe scope: `*-vue-primevue`, `*-models-and-relations`
|
|
188
|
+
|
|
189
|
+
## Checklist
|
|
190
|
+
|
|
191
|
+
- [ ] Frontmatter with `description` in every file
|
|
192
|
+
- [ ] `.claude/skills/*.md` created
|
|
193
|
+
- [ ] `.cursor/skills/*.md` mirrored (same content)
|
|
194
|
+
- [ ] `.claude/rules/*.md` created (if rule)
|
|
195
|
+
- [ ] `.cursor/rules/*.mdc` created with `globs` + `alwaysApply` (if rule)
|
|
196
|
+
- [ ] Sub-projects updated (automation, auto-firma)
|