@rokkit/cli 1.1.12 → 1.1.14
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 +18 -0
- package/dist/src/init.d.ts +11 -3
- package/dist/src/skills.d.ts +63 -0
- package/package.json +2 -1
- package/skills/command-system-rokkit/SKILL.md +256 -0
- package/skills/rokkit-components/SKILL.md +415 -0
- package/skills/semantic-styles-rokkit/SKILL.md +390 -0
- package/src/index.js +18 -0
- package/src/init.js +24 -4
- package/src/skills.js +141 -0
package/README.md
CHANGED
|
@@ -135,3 +135,21 @@ lib/
|
|
|
135
135
|
---
|
|
136
136
|
|
|
137
137
|
Part of [Rokkit](https://github.com/jerrythomas/rokkit) — a Svelte 5 component library and design system.
|
|
138
|
+
|
|
139
|
+
## AI skills
|
|
140
|
+
|
|
141
|
+
Rokkit ships curated AI skills (SKILL.md guides) that teach coding agents how to
|
|
142
|
+
use the library well — theming with the named-token system and building UI with
|
|
143
|
+
the components.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
rokkit skills list # see the catalog
|
|
147
|
+
rokkit skills add semantic-styles-rokkit # install one
|
|
148
|
+
rokkit skills add # interactive multi-select
|
|
149
|
+
rokkit skills add --all # install everything
|
|
150
|
+
rokkit skills add <name> --force # overwrite an existing install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Skills install into your project's `.claude/skills/<name>/`, so they're shared
|
|
154
|
+
with your team via version control. Browse the source under
|
|
155
|
+
[`packages/cli/skills/`](./skills).
|
package/dist/src/init.d.ts
CHANGED
|
@@ -77,12 +77,20 @@ export function generateUnoConfig(): string;
|
|
|
77
77
|
export function generateAppCssImports(themes: string[]): string[];
|
|
78
78
|
/**
|
|
79
79
|
* Generate the flash-prevention script for app.html.
|
|
80
|
-
*
|
|
80
|
+
*
|
|
81
|
+
* Themes flip on `[data-mode="dark"]` only — there is no `@media
|
|
82
|
+
* (prefers-color-scheme)` fallback — so every switcher needs a script that
|
|
83
|
+
* resolves the mode pre-paint (mirrors `@rokkit/unocss`'s `themeHook`):
|
|
84
|
+
* - `system` tracks the OS preference directly (no persisted toggle).
|
|
85
|
+
* - `manual` / `full` read the persisted mode and resolve a stored-or-default
|
|
86
|
+
* `'system'`/`'auto'` against the OS preference (default: follow the OS).
|
|
87
|
+
*
|
|
81
88
|
* @param {string} switcher — 'system' | 'manual' | 'full'
|
|
82
89
|
* @param {string} [storageKey='rokkit-theme']
|
|
83
|
-
* @
|
|
90
|
+
* @param {string} [defaultStyle='rokkit']
|
|
91
|
+
* @returns {string} the `<script>` tag to inject after `<body>`
|
|
84
92
|
*/
|
|
85
|
-
export function generateInitScript(switcher: string, storageKey?: string, defaultStyle?: string): string
|
|
93
|
+
export function generateInitScript(switcher: string, storageKey?: string, defaultStyle?: string): string;
|
|
86
94
|
/**
|
|
87
95
|
* Interactive init command — prompts the user, writes config files.
|
|
88
96
|
* @param {Record<string, unknown>} [_opts]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `name` and `description` from a SKILL.md YAML frontmatter block.
|
|
3
|
+
* @param {string} md
|
|
4
|
+
* @returns {{ name: string, description: string }}
|
|
5
|
+
*/
|
|
6
|
+
export function parseFrontmatter(md: string): {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Read the bundled skill catalog (derived from each SKILL.md's frontmatter).
|
|
12
|
+
* @param {{ skillsDir?: string }} [opts]
|
|
13
|
+
* @returns {Array<{ name: string, description: string }>}
|
|
14
|
+
*/
|
|
15
|
+
export function listSkills({ skillsDir }?: {
|
|
16
|
+
skillsDir?: string;
|
|
17
|
+
}): Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Copy named skills into <cwd>/.claude/skills/<name>/ (idempotent).
|
|
23
|
+
* @param {string[]} names
|
|
24
|
+
* @param {{ cwd?: string, force?: boolean, skillsDir?: string }} [opts]
|
|
25
|
+
* @returns {Array<{ name: string, status: 'added' | 'skipped' | 'unknown' }>}
|
|
26
|
+
*/
|
|
27
|
+
export function installSkills(names: string[], { cwd, force, skillsDir }?: {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
force?: boolean;
|
|
30
|
+
skillsDir?: string;
|
|
31
|
+
}): Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
status: "added" | "skipped" | "unknown";
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Print the catalog, marking already-installed skills.
|
|
37
|
+
* @param {{ skillsDir?: string, cwd?: string }} [opts]
|
|
38
|
+
*/
|
|
39
|
+
export function runSkillsList({ skillsDir, cwd }?: {
|
|
40
|
+
skillsDir?: string;
|
|
41
|
+
cwd?: string;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve which skills to install, then copy and report.
|
|
45
|
+
* @param {string[]} names
|
|
46
|
+
* @param {{ all?: boolean, force?: boolean, skillsDir?: string, cwd?: string }} [opts]
|
|
47
|
+
*/
|
|
48
|
+
export function runSkillsAdd(names: string[], { all, force, skillsDir, cwd }?: {
|
|
49
|
+
all?: boolean;
|
|
50
|
+
force?: boolean;
|
|
51
|
+
skillsDir?: string;
|
|
52
|
+
cwd?: string;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* CLI entry for `rokkit skills` subcommands.
|
|
56
|
+
* @param {'list' | 'add'} sub
|
|
57
|
+
* @param {{ _?: string[], all?: boolean, force?: boolean }} [opts]
|
|
58
|
+
*/
|
|
59
|
+
export function skillsCommand(sub: "list" | "add", opts?: {
|
|
60
|
+
_?: string[];
|
|
61
|
+
all?: boolean;
|
|
62
|
+
force?: boolean;
|
|
63
|
+
}): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
4
4
|
"description": "Command line utilities for icon bundling and conversion.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"files": [
|
|
37
37
|
"lib/**/*",
|
|
38
38
|
"src/**/*.js",
|
|
39
|
+
"skills/**",
|
|
39
40
|
"dist/**/*.d.ts",
|
|
40
41
|
"README.md",
|
|
41
42
|
"package.json",
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: command-system-rokkit
|
|
3
|
+
description: Use when adding keyboard shortcuts, a command palette, or app-level commands to a Rokkit (Svelte 5) app — the commands registry (@rokkit/states), the shortcuts action (@rokkit/actions), the CommandPalette component (@rokkit/ui), the 'mod+k' shortcut grammar, command scope/lifecycle, conflict detection, and i18n.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rokkit Command System
|
|
7
|
+
|
|
8
|
+
Three packages, one coherent system — commands live in a registry, shortcuts fire them from the keyboard,
|
|
9
|
+
the palette lets users search and run them by name.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
@rokkit/states → commands registry (register, execute, resolve, commands.all)
|
|
13
|
+
@rokkit/actions → shortcuts action (use:shortcuts={commands})
|
|
14
|
+
@rokkit/ui → CommandPalette component (<CommandPalette bind:open />)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Parallel to the theme system: `vibe` (state) + `themable` (action) + `ThemeSwitcherToggle` (component).
|
|
18
|
+
Commands are the same shape — registry singleton + action wiring + a ready-made UI.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## The three pieces
|
|
23
|
+
|
|
24
|
+
| Package | Export | Role |
|
|
25
|
+
|---------|--------|------|
|
|
26
|
+
| `@rokkit/states` | `commands` | Singleton registry — register, resolve, execute |
|
|
27
|
+
| `@rokkit/actions` | `shortcuts` | Svelte action — intercepts `keydown`, delegates to `commands` |
|
|
28
|
+
| `@rokkit/ui` | `CommandPalette` | Searchable dialog — reads `commands.all`, opens on `mod+k` |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Defining commands
|
|
33
|
+
|
|
34
|
+
A `Command` object has one required field (`run`) plus metadata:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
type Command = {
|
|
38
|
+
id: string // unique — re-registering the same id replaces and warns
|
|
39
|
+
label: string // display text in the palette (consumer-localized)
|
|
40
|
+
run: () => void // executed on shortcut match or palette selection
|
|
41
|
+
shortcut?: string // e.g. 'mod+k', 'mod+shift+l', '?'
|
|
42
|
+
group?: string // palette category header
|
|
43
|
+
global?: boolean // fire even while a text input is focused (default false)
|
|
44
|
+
enabled?: () => boolean // dynamic gate — command is skipped when returns false
|
|
45
|
+
keywords?: string[] // extra palette search terms
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Register a batch with `registerMany` (returns a single unregister callback):
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { commands } from '@rokkit/states'
|
|
53
|
+
|
|
54
|
+
const unregister = commands.registerMany([
|
|
55
|
+
{
|
|
56
|
+
id: 'palette.open',
|
|
57
|
+
label: 'Open command palette',
|
|
58
|
+
shortcut: 'mod+k',
|
|
59
|
+
global: true, // fires while typing — required for Cmd/Ctrl+K
|
|
60
|
+
group: 'general',
|
|
61
|
+
run: () => { paletteOpen = true }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'theme.toggle',
|
|
65
|
+
label: 'Toggle light / dark',
|
|
66
|
+
shortcut: 'mod+shift+l',
|
|
67
|
+
group: 'theme',
|
|
68
|
+
run: () => { vibe.mode = vibe.mode === 'dark' ? 'light' : 'dark' }
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'conversation.new',
|
|
72
|
+
label: 'New conversation',
|
|
73
|
+
shortcut: 'mod+shift+n',
|
|
74
|
+
group: 'general',
|
|
75
|
+
run: startNewConversation
|
|
76
|
+
}
|
|
77
|
+
])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Register one command with `register` (also returns an unregister callback):
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const unregister = commands.register({
|
|
84
|
+
id: 'search.focus',
|
|
85
|
+
label: 'Focus search',
|
|
86
|
+
shortcut: '/',
|
|
87
|
+
run: () => searchInput?.focus()
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Wiring it up
|
|
94
|
+
|
|
95
|
+
Wire the root element once (e.g. `+layout.svelte`). Register commands in `onMount`
|
|
96
|
+
and return the unregister fn — Svelte calls it on destroy to clean up:
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<script>
|
|
100
|
+
import { onMount } from 'svelte'
|
|
101
|
+
import { commands, vibe } from '@rokkit/states'
|
|
102
|
+
import { shortcuts } from '@rokkit/actions'
|
|
103
|
+
import { CommandPalette } from '@rokkit/ui'
|
|
104
|
+
|
|
105
|
+
let paletteOpen = $state(false)
|
|
106
|
+
|
|
107
|
+
onMount(() => {
|
|
108
|
+
return commands.registerMany([
|
|
109
|
+
{
|
|
110
|
+
id: 'palette.open',
|
|
111
|
+
label: 'Open command palette',
|
|
112
|
+
shortcut: 'mod+k',
|
|
113
|
+
global: true,
|
|
114
|
+
group: 'general',
|
|
115
|
+
run: () => { paletteOpen = true }
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'theme.toggle',
|
|
119
|
+
label: 'Toggle light / dark',
|
|
120
|
+
shortcut: 'mod+shift+l',
|
|
121
|
+
group: 'theme',
|
|
122
|
+
run: () => { vibe.mode = vibe.mode === 'dark' ? 'light' : 'dark' }
|
|
123
|
+
}
|
|
124
|
+
])
|
|
125
|
+
})
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<!-- shortcuts action on the root element intercepts keydown globally -->
|
|
129
|
+
<div use:shortcuts={commands}>
|
|
130
|
+
{@render children()}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- palette renders as an overlay; it reads commands.all directly -->
|
|
134
|
+
<CommandPalette bind:open={paletteOpen} />
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The `shortcuts` action listens on `window` for `keydown` (via Svelte's `on()`), so
|
|
138
|
+
placing it on any element in the tree works — root element or `<svelte:body>` are both fine.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Scope & lifecycle
|
|
143
|
+
|
|
144
|
+
**App-root commands** — register once in the root layout's `onMount`. They live for
|
|
145
|
+
the entire session. The `palette.open` command belongs here; it must always be reachable.
|
|
146
|
+
|
|
147
|
+
**Route or component-scoped commands** — register in the component's `onMount` and
|
|
148
|
+
return the unregister fn. They are removed when the component unmounts:
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<script>
|
|
152
|
+
import { onMount } from 'svelte'
|
|
153
|
+
import { commands } from '@rokkit/states'
|
|
154
|
+
|
|
155
|
+
onMount(() => {
|
|
156
|
+
// These commands are only active while this component is mounted
|
|
157
|
+
return commands.registerMany([
|
|
158
|
+
{
|
|
159
|
+
id: 'editor.save',
|
|
160
|
+
label: 'Save document',
|
|
161
|
+
shortcut: 'mod+s',
|
|
162
|
+
group: 'editor',
|
|
163
|
+
run: saveDocument
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'editor.format',
|
|
167
|
+
label: 'Format code',
|
|
168
|
+
shortcut: 'mod+shift+f',
|
|
169
|
+
group: 'editor',
|
|
170
|
+
run: formatCode
|
|
171
|
+
}
|
|
172
|
+
])
|
|
173
|
+
})
|
|
174
|
+
</script>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`commands.all` is reactive (`$state` inside the registry), so `CommandPalette` and
|
|
178
|
+
any list reading `commands.all` automatically re-renders when commands are added or removed.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Shortcut grammar
|
|
183
|
+
|
|
184
|
+
`mod` resolves to `Cmd` on macOS, `Ctrl` on Linux/Windows — use it for all
|
|
185
|
+
primary shortcuts so they are cross-platform by default.
|
|
186
|
+
|
|
187
|
+
| Shortcut string | Fires on |
|
|
188
|
+
|-----------------|----------|
|
|
189
|
+
| `'mod+k'` | Cmd+K (macOS) / Ctrl+K (other) |
|
|
190
|
+
| `'mod+shift+l'` | Cmd+Shift+L / Ctrl+Shift+L |
|
|
191
|
+
| `'mod+shift+n'` | Cmd+Shift+N / Ctrl+Shift+N |
|
|
192
|
+
| `'?'` | `?` key (no modifier) |
|
|
193
|
+
| `'alt+t'` | Option+T (macOS) / Alt+T (other) |
|
|
194
|
+
|
|
195
|
+
**`global: true` is required for any shortcut that must fire while a text input is focused.**
|
|
196
|
+
Without it the `shortcuts` action skips the command when `document.activeElement` is an
|
|
197
|
+
`<input>`, `<textarea>`, `<select>`, or `contenteditable`. The `palette.open` command
|
|
198
|
+
(`mod+k`) must always be `global: true` — the palette is most useful when the composer
|
|
199
|
+
or a form field is focused.
|
|
200
|
+
|
|
201
|
+
**Conflict detection:** the registry normalizes shortcuts on registration (modifiers
|
|
202
|
+
sorted by canonical order, key lowercased, `mod` resolved). If a second command registers
|
|
203
|
+
the same normalized shortcut it is warned and skipped — the first registrant wins.
|
|
204
|
+
|
|
205
|
+
**Single chords only:** the grammar is one key + modifiers. Multi-chord sequences
|
|
206
|
+
(e.g. Vim-style `g g`) are not supported.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## i18n
|
|
211
|
+
|
|
212
|
+
The palette chrome comes from `messages.command.*` — override once to localize globally:
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
import { messages } from '@rokkit/states'
|
|
216
|
+
|
|
217
|
+
messages.set({
|
|
218
|
+
command: {
|
|
219
|
+
placeholder: 'Befehl ausführen…',
|
|
220
|
+
noResults: 'Keine Befehle gefunden',
|
|
221
|
+
label: 'Befehlspalette'
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Or register a named locale and switch:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
messages.register('de', {
|
|
230
|
+
command: {
|
|
231
|
+
placeholder: 'Befehl ausführen…',
|
|
232
|
+
noResults: 'Keine Befehle gefunden',
|
|
233
|
+
label: 'Befehlspalette'
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
messages.setLocale('de')
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Command `label` and `keywords` are consumer-supplied strings — localize them
|
|
240
|
+
directly in the object passed to `register` / `registerMany`.
|
|
241
|
+
|
|
242
|
+
Default English values: `placeholder: 'Run a command…'`, `noResults: 'No commands found'`,
|
|
243
|
+
`label: 'Command palette'`.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Common mistakes
|
|
248
|
+
|
|
249
|
+
| Mistake | Why it fails | Fix |
|
|
250
|
+
|---------|-------------|-----|
|
|
251
|
+
| Registering at module top-level (not in `onMount`) | The registry is a singleton; commands pile up across hot-reloads in dev and are never cleaned up on unmount | Always register in `onMount`, return the unregister fn |
|
|
252
|
+
| Forgetting `global: true` on `mod+k` (or any Cmd-shortcut) | `shortcuts` skips non-global commands while a text input is focused — Cmd+K fired in the composer silently no-ops | Add `global: true` to any command that must fire during text input |
|
|
253
|
+
| Hand-rolling `window.addEventListener('keydown', ...)` | No conflict detection, no `enabled()` gate, no `commands.all` integration, no cleanup | Use `use:shortcuts={commands}` — it handles all of these |
|
|
254
|
+
| Using `<svelte:body use:shortcuts />` without noting that `shortcuts` uses `window` | The action attaches a `window` listener regardless of which element it's placed on — placing it on body is fine but redundant | Place on any element in the tree; root element or body both work |
|
|
255
|
+
| Putting `run: () => paletteOpen = true` at module scope | `paletteOpen` is `$state` in the component — the closure captures the right reference only when created inside the component function | Define `run` functions after `$state` declarations, inside the component |
|
|
256
|
+
| Expecting `commands.all` to include disabled commands without `enabled` | `enabled` is a dynamic gate called at dispatch time, not a filter on the list | `commands.all` always contains all registered commands; `enabled` only gates execution |
|