@andersseen/skills 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 +72 -0
- package/bin/cli.mjs +264 -0
- package/package.json +38 -0
- package/skills/andersseen/SKILL.md +93 -0
- package/skills/andersseen-headless-core/SKILL.md +178 -0
- package/skills/andersseen-icon/SKILL.md +124 -0
- package/skills/andersseen-layout/SKILL.md +198 -0
- package/skills/andersseen-motion/SKILL.md +166 -0
- package/skills/andersseen-web-components/SKILL.md +478 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Andersseen
|
|
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,72 @@
|
|
|
1
|
+
# @andersseen/skills
|
|
2
|
+
|
|
3
|
+
Installable **AI agent skills** for the Andersseen ecosystem — one focused skill
|
|
4
|
+
per library, plus an orchestrator that routes between them. Works with any agent
|
|
5
|
+
that reads `.claude/skills/` (Claude Code, and compatible tools).
|
|
6
|
+
|
|
7
|
+
Each skill teaches an agent how to use **one** library correctly (real package
|
|
8
|
+
names, verified APIs, no invented props), so you can install only what your
|
|
9
|
+
project needs.
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Interactive: pick skills, then choose install or download
|
|
15
|
+
npx @andersseen/skills
|
|
16
|
+
|
|
17
|
+
# Install everything (orchestrator + all libraries)
|
|
18
|
+
npx @andersseen/skills add --all
|
|
19
|
+
|
|
20
|
+
# Add just what you need
|
|
21
|
+
npx @andersseen/skills add andersseen web-components icon
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`pnpm dlx @andersseen/skills …` and `yarn dlx @andersseen/skills …` work too.
|
|
25
|
+
|
|
26
|
+
## The two modes
|
|
27
|
+
|
|
28
|
+
| Mode | Command flag | Destination | Use when |
|
|
29
|
+
| ------------ | ------------------- | ---------------------- | ------------------------------------- |
|
|
30
|
+
| **Install** | _(default)_ | `./.claude/skills/` | Activate immediately for your agent |
|
|
31
|
+
| **Download** | `--download` / `-d` | `./andersseen-skills/` | Review the SKILL.md before activating |
|
|
32
|
+
|
|
33
|
+
## Available skills
|
|
34
|
+
|
|
35
|
+
| Skill | Alias | Library |
|
|
36
|
+
| --------------------------- | ---------------- | --------------------------------- |
|
|
37
|
+
| `andersseen` | `orchestrator` | Ecosystem router (start here) |
|
|
38
|
+
| `andersseen-web-components` | `web-components` | `@andersseen/web-components` |
|
|
39
|
+
| `andersseen-headless-core` | `headless-core` | `@andersseen/headless-components` |
|
|
40
|
+
| `andersseen-icon` | `icon` | `@andersseen/icon` |
|
|
41
|
+
| `andersseen-motion` | `motion` | `@andersseen/motion` |
|
|
42
|
+
| `andersseen-layout` | `layout` | `@andersseen/layout` |
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx @andersseen/skills list # print the same table with descriptions
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
npx @andersseen/skills Interactive picker
|
|
52
|
+
npx @andersseen/skills list List available skills
|
|
53
|
+
npx @andersseen/skills add <name...> Install into ./.claude/skills/
|
|
54
|
+
npx @andersseen/skills add --all Install every skill
|
|
55
|
+
npx @andersseen/skills add <name> --download Download into ./andersseen-skills/
|
|
56
|
+
|
|
57
|
+
Options: -d/--download -a/--all -f/--force -h/--help
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Names accept the full folder name (`andersseen-web-components`) or the short
|
|
61
|
+
alias (`web-components`). `all` selects everything.
|
|
62
|
+
|
|
63
|
+
## Recommended: start with the orchestrator
|
|
64
|
+
|
|
65
|
+
Install `andersseen` alongside the per-library skills. It decides **which**
|
|
66
|
+
library fits a task and defers to the focused skill — so the agent reaches for
|
|
67
|
+
the right package instead of guessing.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx @andersseen/skills add andersseen web-components headless-core icon motion layout
|
|
71
|
+
# ≡ npx @andersseen/skills add --all
|
|
72
|
+
```
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @andersseen/skills — install focused AI agent skills into a consumer project.
|
|
3
|
+
//
|
|
4
|
+
// Two modes, mirroring the two things a dev actually wants:
|
|
5
|
+
// install → copies the skill into ./.claude/skills/<name>/ (agent picks it up)
|
|
6
|
+
// download → copies the skill into ./andersseen-skills/<name>/ (review first)
|
|
7
|
+
//
|
|
8
|
+
// Zero runtime dependencies: Node built-ins only, so `npx @andersseen/skills`
|
|
9
|
+
// works offline once the package is fetched.
|
|
10
|
+
|
|
11
|
+
import { readdir, readFile, mkdir, cp } from 'node:fs/promises';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
|
|
17
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const SKILLS_DIR = resolve(HERE, '..', 'skills');
|
|
19
|
+
const INSTALL_DIR = resolve(process.cwd(), '.claude', 'skills');
|
|
20
|
+
const DOWNLOAD_DIR = resolve(process.cwd(), 'andersseen-skills');
|
|
21
|
+
|
|
22
|
+
const c = {
|
|
23
|
+
reset: '\x1b[0m',
|
|
24
|
+
bold: '\x1b[1m',
|
|
25
|
+
dim: '\x1b[2m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
green: '\x1b[32m',
|
|
28
|
+
yellow: '\x1b[33m',
|
|
29
|
+
red: '\x1b[31m',
|
|
30
|
+
};
|
|
31
|
+
const paint = (color, s) => (process.stdout.isTTY ? `${c[color]}${s}${c.reset}` : s);
|
|
32
|
+
|
|
33
|
+
/** Read every skill folder that has a SKILL.md, with its description. */
|
|
34
|
+
async function discoverSkills() {
|
|
35
|
+
if (!existsSync(SKILLS_DIR)) return [];
|
|
36
|
+
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
37
|
+
const skills = [];
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (!entry.isDirectory()) continue;
|
|
40
|
+
const skillMd = join(SKILLS_DIR, entry.name, 'SKILL.md');
|
|
41
|
+
if (!existsSync(skillMd)) continue;
|
|
42
|
+
const raw = await readFile(skillMd, 'utf8');
|
|
43
|
+
skills.push({
|
|
44
|
+
name: entry.name,
|
|
45
|
+
alias: entry.name.replace(/^andersseen-/, ''),
|
|
46
|
+
description: frontmatterDescription(raw),
|
|
47
|
+
dir: join(SKILLS_DIR, entry.name),
|
|
48
|
+
orchestrator: entry.name === 'andersseen',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Orchestrator first, then alphabetical.
|
|
52
|
+
return skills.sort((a, b) => Number(b.orchestrator) - Number(a.orchestrator) || a.name.localeCompare(b.name));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function frontmatterDescription(raw) {
|
|
56
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
|
|
57
|
+
if (!match) return '';
|
|
58
|
+
const body = match[1];
|
|
59
|
+
const line = /description:\s*(?:'([^']*)'|"([^"]*)"|(.*))/.exec(body);
|
|
60
|
+
if (!line) return '';
|
|
61
|
+
return (line[1] ?? line[2] ?? line[3] ?? '').replace(/\s+/g, ' ').trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Resolve user tokens (folder name, alias, or "all") to skill objects. */
|
|
65
|
+
function resolveSelection(tokens, skills) {
|
|
66
|
+
const wanted = new Set();
|
|
67
|
+
const unknown = [];
|
|
68
|
+
for (const tokenRaw of tokens) {
|
|
69
|
+
const token = tokenRaw.toLowerCase();
|
|
70
|
+
if (token === 'all' || token === '*') {
|
|
71
|
+
skills.forEach(s => wanted.add(s.name));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const hit = skills.find(s => s.name === token || s.alias === token);
|
|
75
|
+
if (hit) wanted.add(hit.name);
|
|
76
|
+
else unknown.push(tokenRaw);
|
|
77
|
+
}
|
|
78
|
+
return { selected: skills.filter(s => wanted.has(s.name)), unknown };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function place(skill, mode, force) {
|
|
82
|
+
const root = mode === 'download' ? DOWNLOAD_DIR : INSTALL_DIR;
|
|
83
|
+
const target = join(root, skill.name);
|
|
84
|
+
if (existsSync(target) && !force) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ${paint('yellow', '•')} ${skill.name} ${paint('dim', 'already exists (skipped — pass --force to overwrite)')}`,
|
|
87
|
+
);
|
|
88
|
+
return 'skipped';
|
|
89
|
+
}
|
|
90
|
+
await mkdir(root, { recursive: true });
|
|
91
|
+
await cp(skill.dir, target, { recursive: true, force: true });
|
|
92
|
+
const shown = mode === 'download' ? join('andersseen-skills', skill.name) : join('.claude', 'skills', skill.name);
|
|
93
|
+
console.log(` ${paint('green', '✓')} ${skill.name} ${paint('dim', '→ ' + shown)}`);
|
|
94
|
+
return 'written';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function printSkillList(skills) {
|
|
98
|
+
const pad = Math.max(...skills.map(s => s.name.length));
|
|
99
|
+
for (const s of skills) {
|
|
100
|
+
const tag = s.orchestrator ? paint('cyan', ' [orchestrator]') : '';
|
|
101
|
+
console.log(` ${paint('bold', s.name.padEnd(pad))}${tag}`);
|
|
102
|
+
if (s.description) {
|
|
103
|
+
const short = s.description.length > 96 ? s.description.slice(0, 93) + '…' : s.description;
|
|
104
|
+
console.log(` ${paint('dim', ' '.repeat(pad) + ' ' + short)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function help() {
|
|
110
|
+
console.log(`
|
|
111
|
+
${paint('bold', '@andersseen/skills')} — install AI agent skills for the Andersseen ecosystem
|
|
112
|
+
|
|
113
|
+
${paint('bold', 'Usage')}
|
|
114
|
+
npx @andersseen/skills ${paint('dim', 'interactive picker (choose skills + install/download)')}
|
|
115
|
+
npx @andersseen/skills list ${paint('dim', 'list available skills')}
|
|
116
|
+
npx @andersseen/skills add <name...> ${paint('dim', 'install into ./.claude/skills/')}
|
|
117
|
+
npx @andersseen/skills add --all ${paint('dim', 'install every skill')}
|
|
118
|
+
npx @andersseen/skills add <name> --download ${paint('dim', 'download into ./andersseen-skills/ (review first)')}
|
|
119
|
+
|
|
120
|
+
${paint('bold', 'Options')}
|
|
121
|
+
-d, --download Download only — do not activate in .claude/skills
|
|
122
|
+
-a, --all Select every skill
|
|
123
|
+
-f, --force Overwrite an existing skill folder
|
|
124
|
+
-h, --help Show this help
|
|
125
|
+
|
|
126
|
+
${paint('bold', 'Names')} accept the folder name or its short alias, e.g.
|
|
127
|
+
andersseen-web-components ${paint('dim', '≡')} web-components
|
|
128
|
+
andersseen ${paint('dim', '≡')} orchestrator ("andersseen" routes to the right skill)
|
|
129
|
+
|
|
130
|
+
${paint('bold', 'Examples')}
|
|
131
|
+
npx @andersseen/skills add andersseen web-components icon
|
|
132
|
+
pnpm dlx @andersseen/skills add --all
|
|
133
|
+
yarn dlx @andersseen/skills add layout --download
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ask(rl, question) {
|
|
138
|
+
return new Promise(res => rl.question(question, a => res(a.trim())));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function interactive(skills) {
|
|
142
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
143
|
+
try {
|
|
144
|
+
console.log(paint('bold', '\nAndersseen agent skills\n'));
|
|
145
|
+
skills.forEach((s, i) => {
|
|
146
|
+
const tag = s.orchestrator ? paint('cyan', ' [orchestrator]') : '';
|
|
147
|
+
console.log(` ${paint('bold', String(i + 1).padStart(2))}. ${s.name}${tag}`);
|
|
148
|
+
if (s.description) {
|
|
149
|
+
const short = s.description.length > 88 ? s.description.slice(0, 85) + '…' : s.description;
|
|
150
|
+
console.log(` ${paint('dim', short)}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
console.log(`\n ${paint('dim', "Enter numbers (e.g. 1,3), a name, or 'all'.")}`);
|
|
154
|
+
const pick = await ask(rl, paint('cyan', '\nSkills to add: '));
|
|
155
|
+
let selected;
|
|
156
|
+
if (/^(all|\*)$/i.test(pick.trim())) {
|
|
157
|
+
selected = skills.slice();
|
|
158
|
+
} else {
|
|
159
|
+
const byIndex = pick
|
|
160
|
+
.split(/[,\s]+/)
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.map(t => (/^\d+$/.test(t) ? skills[Number(t) - 1]?.name : t))
|
|
163
|
+
.filter(Boolean);
|
|
164
|
+
selected = resolveSelection(byIndex, skills).selected;
|
|
165
|
+
}
|
|
166
|
+
if (selected.length === 0) {
|
|
167
|
+
console.log(paint('yellow', '\nNothing selected. Bye.'));
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(
|
|
172
|
+
`\n ${paint('bold', '1')}. Install ${paint('dim', '→ ./.claude/skills/ (your agent uses it immediately)')}`,
|
|
173
|
+
);
|
|
174
|
+
console.log(
|
|
175
|
+
` ${paint('bold', '2')}. Download ${paint('dim', '→ ./andersseen-skills/ (review before activating)')}`,
|
|
176
|
+
);
|
|
177
|
+
const action = await ask(rl, paint('cyan', '\nChoose 1 or 2 [1]: '));
|
|
178
|
+
const mode = action.trim() === '2' || /^d/i.test(action.trim()) ? 'download' : 'install';
|
|
179
|
+
|
|
180
|
+
console.log('');
|
|
181
|
+
for (const skill of selected) await place(skill, mode, false);
|
|
182
|
+
printDone(mode, selected);
|
|
183
|
+
return 0;
|
|
184
|
+
} finally {
|
|
185
|
+
rl.close();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printDone(mode, selected) {
|
|
190
|
+
console.log('');
|
|
191
|
+
if (mode === 'install') {
|
|
192
|
+
console.log(paint('green', `Installed ${selected.length} skill(s) into .claude/skills/.`));
|
|
193
|
+
console.log(paint('dim', 'Restart your agent (or reload skills) to pick them up.'));
|
|
194
|
+
} else {
|
|
195
|
+
console.log(paint('green', `Downloaded ${selected.length} skill(s) into andersseen-skills/.`));
|
|
196
|
+
console.log(paint('dim', 'Move the folders into .claude/skills/ when you are ready to activate them.'));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function main() {
|
|
201
|
+
const argv = process.argv.slice(2);
|
|
202
|
+
if (argv.includes('-h') || argv.includes('--help')) return help();
|
|
203
|
+
|
|
204
|
+
const skills = await discoverSkills();
|
|
205
|
+
if (skills.length === 0) {
|
|
206
|
+
console.error(paint('red', 'No skills bundled with this package.'));
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const command = argv[0];
|
|
212
|
+
|
|
213
|
+
if (command === 'list' || command === 'ls') {
|
|
214
|
+
console.log(paint('bold', '\nAvailable skills\n'));
|
|
215
|
+
printSkillList(skills);
|
|
216
|
+
console.log('');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (command === undefined) return interactive(skills);
|
|
221
|
+
|
|
222
|
+
if (command === 'add' || command === 'install' || command === 'i') {
|
|
223
|
+
const rest = argv.slice(1);
|
|
224
|
+
const download = rest.includes('-d') || rest.includes('--download');
|
|
225
|
+
const all = rest.includes('-a') || rest.includes('--all');
|
|
226
|
+
const force = rest.includes('-f') || rest.includes('--force');
|
|
227
|
+
const names = rest.filter(a => !a.startsWith('-'));
|
|
228
|
+
const mode = download ? 'download' : 'install';
|
|
229
|
+
|
|
230
|
+
let selected;
|
|
231
|
+
if (all) {
|
|
232
|
+
selected = skills.slice();
|
|
233
|
+
} else {
|
|
234
|
+
if (names.length === 0) {
|
|
235
|
+
console.error(paint('red', 'Specify at least one skill name, or use --all.'));
|
|
236
|
+
console.error(paint('dim', 'Run `npx @andersseen/skills list` to see the options.'));
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const { selected: sel, unknown } = resolveSelection(names, skills);
|
|
241
|
+
if (unknown.length) {
|
|
242
|
+
console.error(paint('red', `Unknown skill(s): ${unknown.join(', ')}`));
|
|
243
|
+
console.error(paint('dim', 'Run `npx @andersseen/skills list` to see the options.'));
|
|
244
|
+
process.exitCode = 1;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
selected = sel;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log('');
|
|
251
|
+
for (const skill of selected) await place(skill, mode, force);
|
|
252
|
+
printDone(mode, selected);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.error(paint('red', `Unknown command: ${command}`));
|
|
257
|
+
help();
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
main().catch(err => {
|
|
262
|
+
console.error(paint('red', 'Error: ' + (err?.message ?? String(err))));
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@andersseen/skills",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Installable AI agent skills (Claude Code, Cursor, ...) for the Andersseen ecosystem. One focused skill per library, plus an orchestrator.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"andersseen-skills": "bin/cli.mjs",
|
|
8
|
+
"and-skills": "bin/cli.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"skills",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude-code",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"cursor",
|
|
19
|
+
"web-components",
|
|
20
|
+
"andersseen",
|
|
21
|
+
"ai"
|
|
22
|
+
],
|
|
23
|
+
"author": "Andersseen",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/Andersseen/and-web-components.git",
|
|
31
|
+
"directory": "packages/skills"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"lint": "node bin/cli.mjs list >/dev/null",
|
|
35
|
+
"format": "prettier --write \"**/*.{md,mjs,json}\"",
|
|
36
|
+
"format:check": "prettier --check \"**/*.{md,mjs,json}\""
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: andersseen
|
|
3
|
+
description:
|
|
4
|
+
'Orchestrator for the Andersseen front-end ecosystem. Use this to decide WHICH
|
|
5
|
+
Andersseen library to reach for and route to its focused skill before writing
|
|
6
|
+
code. Trigger phrases: andersseen, @andersseen, and-* component, andersseen
|
|
7
|
+
design system, which andersseen package, build a UI with andersseen, headless
|
|
8
|
+
logic, icon, motion animation, layout attributes. Routes to:
|
|
9
|
+
andersseen-web-components, andersseen-headless-core, andersseen-icon,
|
|
10
|
+
andersseen-motion, andersseen-layout.'
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Andersseen — ecosystem orchestrator
|
|
14
|
+
|
|
15
|
+
You are working in a project that uses the **Andersseen** front-end ecosystem: a
|
|
16
|
+
set of small, framework-agnostic libraries that compose. Your job in this skill
|
|
17
|
+
is to **pick the right library for the task and defer to its focused skill**
|
|
18
|
+
instead of guessing an API.
|
|
19
|
+
|
|
20
|
+
Each library is documented by a dedicated skill (one library, one skill). Load
|
|
21
|
+
the matching skill's guidance before generating code for that library. If a
|
|
22
|
+
sibling skill is not installed, tell the user how to add it (see _Install_).
|
|
23
|
+
|
|
24
|
+
## Routing table — task → library → skill
|
|
25
|
+
|
|
26
|
+
| The user wants to… | Library | Skill |
|
|
27
|
+
| ------------------------------------------------------------- | --------------------------------- | --------------------------- |
|
|
28
|
+
| Drop styled, ready-made UI (`<and-button>`, `<and-modal>`, …) | `@andersseen/web-components` | `andersseen-web-components` |
|
|
29
|
+
| Build a **custom** UI but reuse state/a11y/keyboard logic | `@andersseen/headless-components` | `andersseen-headless-core` |
|
|
30
|
+
| Render or register SVG icons (`<and-icon>`, `getIcon`) | `@andersseen/icon` | `andersseen-icon` |
|
|
31
|
+
| Animate on scroll/hover/tap via HTML attributes | `@andersseen/motion` | `andersseen-motion` |
|
|
32
|
+
| Compose layout & typography with `and-layout` / `and-text` | `@andersseen/layout` | `andersseen-layout` |
|
|
33
|
+
|
|
34
|
+
## How the layers relate
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
headless-core → pure TS state machines + ARIA + keyboard (no DOM, no CSS)
|
|
38
|
+
↓
|
|
39
|
+
web-components → Stencil custom elements (Shadow DOM) built ON headless-core + icon
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`icon`, `motion`, and `layout` are independent satellites usable from any
|
|
43
|
+
framework or from plain HTML.
|
|
44
|
+
|
|
45
|
+
## Decision guide
|
|
46
|
+
|
|
47
|
+
1. **Need a component that already exists?** (button, card, modal, tabs, drawer,
|
|
48
|
+
navbar, select, input…) → use `web-components`. Do **not** re-implement it
|
|
49
|
+
with headless-core.
|
|
50
|
+
2. **Building something the library doesn't ship, but that needs correct
|
|
51
|
+
open/close, focus, keyboard, or ARIA?** → use `headless-core` and provide
|
|
52
|
+
your own markup/styles.
|
|
53
|
+
3. **Just icons?** → `icon`. Remember `registerIcons(COMPONENT_ICONS)` is
|
|
54
|
+
required whenever `web-components` is used.
|
|
55
|
+
4. **Just motion/entrance animation?** → `motion` (attribute-driven, respects
|
|
56
|
+
`prefers-reduced-motion`).
|
|
57
|
+
5. **Just layout/spacing/typography without a utility framework?** → `layout`.
|
|
58
|
+
|
|
59
|
+
You can combine several — e.g. `web-components` + `icon` + `motion`. When you
|
|
60
|
+
do, follow each library's own skill rules; never invent props, classes, or
|
|
61
|
+
attributes that a skill does not document.
|
|
62
|
+
|
|
63
|
+
## Global rules for every Andersseen library
|
|
64
|
+
|
|
65
|
+
- Use the **real package names** exactly: `@andersseen/web-components`,
|
|
66
|
+
`@andersseen/headless-components`, `@andersseen/icon`, `@andersseen/motion`,
|
|
67
|
+
`@andersseen/layout`.
|
|
68
|
+
- Only use public props, slots, events, attributes, and exported functions that
|
|
69
|
+
the relevant skill documents. Never target internal Shadow DOM parts.
|
|
70
|
+
- Prefer design-token CSS variables over hardcoded colors.
|
|
71
|
+
- Clean up: call the `cleanup()`/`destroy()` handles that a library returns when
|
|
72
|
+
unmounting SPA views.
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
Skills are distributed via the `@andersseen/skills` CLI:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# install the orchestrator + everything
|
|
80
|
+
npx @andersseen/skills add --all
|
|
81
|
+
|
|
82
|
+
# or add just the ones you need
|
|
83
|
+
npx @andersseen/skills add andersseen web-components icon
|
|
84
|
+
|
|
85
|
+
# download to ./andersseen-skills/ to review before activating
|
|
86
|
+
npx @andersseen/skills add layout --download
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Runtime packages install normally, e.g.:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm i @andersseen/web-components @andersseen/icon
|
|
93
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: andersseen-headless-core
|
|
3
|
+
description:
|
|
4
|
+
'Reuse Andersseen headless behavior primitives
|
|
5
|
+
(@andersseen/headless-components) for state, accessibility, and keyboard logic
|
|
6
|
+
when building a CUSTOM UI. Load when you need correct open/close, focus, ARIA,
|
|
7
|
+
or keyboard handling without a pre-styled component: createTabs,
|
|
8
|
+
createDropdown, createModal, createTooltip, createToastManager,
|
|
9
|
+
createAccordion, createDrawer, createNavbar, createSidebar. Trigger phrases:
|
|
10
|
+
headless, state machine, a11y logic, keyboard navigation, ARIA props, custom
|
|
11
|
+
component behavior.'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# @andersseen/headless-components — headless behavior primitives
|
|
15
|
+
|
|
16
|
+
Framework-agnostic headless primitives for behavior, a11y, and keyboard
|
|
17
|
+
navigation. **No DOM rendering, no CSS, no framework lock-in.** You provide
|
|
18
|
+
markup/styles; the library provides state + ARIA + interaction logic. Reach for
|
|
19
|
+
this when the pre-built `@andersseen/web-components` element does not fit and
|
|
20
|
+
you must build custom markup that still behaves correctly.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm i @andersseen/headless-components
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Verified module exports
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
@andersseen/headless-components
|
|
32
|
+
@andersseen/headless-components/button
|
|
33
|
+
@andersseen/headless-components/accordion
|
|
34
|
+
@andersseen/headless-components/tabs
|
|
35
|
+
@andersseen/headless-components/dropdown
|
|
36
|
+
@andersseen/headless-components/modal
|
|
37
|
+
@andersseen/headless-components/tooltip
|
|
38
|
+
@andersseen/headless-components/toast
|
|
39
|
+
@andersseen/headless-components/drawer
|
|
40
|
+
@andersseen/headless-components/alert
|
|
41
|
+
@andersseen/headless-components/navbar
|
|
42
|
+
@andersseen/headless-components/sidebar
|
|
43
|
+
@andersseen/headless-components/breadcrumb
|
|
44
|
+
@andersseen/headless-components/menu-list
|
|
45
|
+
@andersseen/headless-components/context-menu
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Canonical API contract
|
|
49
|
+
|
|
50
|
+
Most modules follow:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const logic = createX(config);
|
|
54
|
+
|
|
55
|
+
logic.state; // read-only state snapshot
|
|
56
|
+
logic.actions; // imperative updates
|
|
57
|
+
logic.queries; // derived state (where available)
|
|
58
|
+
logic.get*Props(...); // ARIA/data attributes for semantic elements
|
|
59
|
+
logic.handle*KeyDown(...); // keyboard support
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Verified factories
|
|
63
|
+
|
|
64
|
+
`createButton`, `createAccordion`, `createTabs`, `createDropdown`,
|
|
65
|
+
`createModal`, `createTooltip`, `createDrawer`, `createAlert`, `createNavbar`,
|
|
66
|
+
`createSidebar`, `createBreadcrumb`, `createMenuList`, `createContextMenu`, and
|
|
67
|
+
`createToastManager` (**not** `createToast`).
|
|
68
|
+
|
|
69
|
+
## Core usage examples
|
|
70
|
+
|
|
71
|
+
### Tabs
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createTabs } from '@andersseen/headless-components/tabs';
|
|
75
|
+
|
|
76
|
+
const tabs = createTabs({
|
|
77
|
+
defaultValue: 'overview',
|
|
78
|
+
orientation: 'horizontal',
|
|
79
|
+
activationMode: 'automatic',
|
|
80
|
+
onValueChange: tabId => console.log(tabId),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const listProps = tabs.getTabListProps();
|
|
84
|
+
const triggerProps = tabs.getTabTriggerProps('overview');
|
|
85
|
+
const panelProps = tabs.getTabContentProps('overview');
|
|
86
|
+
|
|
87
|
+
tabs.handleTabKeyDown(event, 'overview', ['overview', 'settings']);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Dropdown
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { createDropdown } from '@andersseen/headless-components/dropdown';
|
|
94
|
+
|
|
95
|
+
const dropdown = createDropdown({
|
|
96
|
+
defaultOpen: false,
|
|
97
|
+
closeOnSelect: true,
|
|
98
|
+
placement: 'bottom',
|
|
99
|
+
onOpenChange: open => console.log(open),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const triggerProps = dropdown.getTriggerProps();
|
|
103
|
+
const menuProps = dropdown.getMenuProps();
|
|
104
|
+
const itemProps = dropdown.getItemProps('theme-light');
|
|
105
|
+
|
|
106
|
+
dropdown.handleTriggerKeyDown(event);
|
|
107
|
+
dropdown.handleMenuKeyDown(event, ['theme-light', 'theme-dark']);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Modal
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { createModal } from '@andersseen/headless-components/modal';
|
|
114
|
+
|
|
115
|
+
const modal = createModal({
|
|
116
|
+
closeOnEscape: true,
|
|
117
|
+
closeOnOverlayClick: true,
|
|
118
|
+
onOpenChange: open => console.log(open),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
modal.actions.open();
|
|
122
|
+
const overlayProps = modal.getOverlayProps();
|
|
123
|
+
const contentProps = modal.getContentProps();
|
|
124
|
+
const closeBtnProps = modal.getCloseButtonProps();
|
|
125
|
+
|
|
126
|
+
window.addEventListener('keydown', modal.handleKeyDown);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Tooltip
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { createTooltip } from '@andersseen/headless-components/tooltip';
|
|
133
|
+
|
|
134
|
+
const tooltip = createTooltip({
|
|
135
|
+
placement: 'top',
|
|
136
|
+
openDelay: 120,
|
|
137
|
+
closeDelay: 80,
|
|
138
|
+
onVisibilityChange: visible => console.log(visible),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
trigger.addEventListener('mouseenter', tooltip.handleMouseEnter);
|
|
142
|
+
trigger.addEventListener('mouseleave', tooltip.handleMouseLeave);
|
|
143
|
+
trigger.addEventListener('focusin', tooltip.handleFocusIn);
|
|
144
|
+
trigger.addEventListener('focusout', tooltip.handleFocusOut);
|
|
145
|
+
|
|
146
|
+
const triggerProps = tooltip.getTriggerProps();
|
|
147
|
+
const tooltipProps = tooltip.getTooltipProps();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Toast manager
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { createToastManager } from '@andersseen/headless-components/toast';
|
|
154
|
+
|
|
155
|
+
const toasts = createToastManager({
|
|
156
|
+
defaultDuration: 3000,
|
|
157
|
+
maxToasts: 5,
|
|
158
|
+
position: 'bottom-right',
|
|
159
|
+
onToastsChange: list => console.log(list),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const id = toasts.actions.present('Saved', 'success');
|
|
163
|
+
toasts.actions.dismiss(id);
|
|
164
|
+
toasts.actions.dismissAll();
|
|
165
|
+
|
|
166
|
+
const containerProps = toasts.getContainerProps();
|
|
167
|
+
const dismissProps = toasts.getDismissProps();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Rules
|
|
171
|
+
|
|
172
|
+
- Use the real package name: `@andersseen/headless-components`.
|
|
173
|
+
- Do not invent rendering helpers; compose with semantic HTML and spread the
|
|
174
|
+
returned props objects onto your elements.
|
|
175
|
+
- Preserve the provided keyboard handlers and ARIA attributes — do not
|
|
176
|
+
re-implement focus/state logic already provided by `actions`/`queries`.
|
|
177
|
+
- Call `destroy()` where provided (tooltip / toast manager) during unmount
|
|
178
|
+
cleanup.
|