sliderpro-agentic-skills-etch 0.1.2 → 0.2.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/bin/cli.js +151 -41
- package/lib/skills-source.js +65 -0
- package/package.json +2 -1
- package/skills-package/components/dwc-slider-pagination.md +38 -0
- package/skills-package/manifest.json +47 -0
- package/skills-package/slider-skills/slider-pro-skills-build.md +438 -0
- package/skills-package/slider-skills/slider-pro-skills-reference.md +8 -3
- package/skills-package/slider-skills/slider-pro-skills.md +302 -308
package/bin/cli.js
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Installs the Slider Pro AI Connector skills into a project.
|
|
4
|
+
//
|
|
5
|
+
// The skills live in the docs repo and change more often than this package is
|
|
6
|
+
// published, so the current files are fetched at install time and the bundled
|
|
7
|
+
// copy is only a fallback for when the network is unavailable. Fetching is
|
|
8
|
+
// all-or-nothing: a partial fetch would mix a new skills file with stale
|
|
9
|
+
// component docs, which is worse than either source on its own.
|
|
10
|
+
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
4
12
|
import { dirname, join, resolve } from "node:path";
|
|
5
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
+
import {
|
|
15
|
+
applyRewrites,
|
|
16
|
+
DEFAULT_REF,
|
|
17
|
+
looksLikeMarkdown,
|
|
18
|
+
OUT_COMPONENTS_DIR,
|
|
19
|
+
OUT_SKILLS_DIR,
|
|
20
|
+
rawUrl,
|
|
21
|
+
REPO,
|
|
22
|
+
REPO_MANIFEST,
|
|
23
|
+
} from "../lib/skills-source.js";
|
|
6
24
|
|
|
7
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
26
|
const packageRoot = join(__dirname, "..");
|
|
9
|
-
const
|
|
27
|
+
const bundleRoot = join(packageRoot, "skills-package");
|
|
10
28
|
|
|
11
|
-
const DIRS = [
|
|
29
|
+
const DIRS = [OUT_SKILLS_DIR, OUT_COMPONENTS_DIR];
|
|
30
|
+
const TIMEOUT_MS = 10000;
|
|
12
31
|
|
|
13
32
|
function printHelp() {
|
|
14
33
|
console.log(`
|
|
@@ -18,27 +37,37 @@ Usage:
|
|
|
18
37
|
npx sliderpro-agentic-skills-etch [directory] [options]
|
|
19
38
|
|
|
20
39
|
Options:
|
|
21
|
-
--force, -f
|
|
22
|
-
--
|
|
40
|
+
--force, -f Overwrite existing slider-skills/ or components/
|
|
41
|
+
--offline Skip the network and install the copy bundled with this package
|
|
42
|
+
--ref <ref> Install from a branch or tag (default: ${DEFAULT_REF})
|
|
43
|
+
--help, -h Show this help message
|
|
44
|
+
|
|
45
|
+
By default the current skills are fetched from ${REPO} so they are never
|
|
46
|
+
older than this package. Without a network the bundled copy is used instead.
|
|
23
47
|
|
|
24
48
|
Examples:
|
|
25
49
|
npx sliderpro-agentic-skills-etch
|
|
26
50
|
npx sliderpro-agentic-skills-etch ./my-site-project
|
|
27
|
-
npx sliderpro-agentic-skills-etch --force
|
|
51
|
+
npx sliderpro-agentic-skills-etch --offline --force
|
|
28
52
|
`);
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
function parseArgs(argv) {
|
|
32
|
-
const options = { force: false, help: false, target: process.cwd() };
|
|
33
|
-
|
|
34
|
-
for (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
options.
|
|
41
|
-
|
|
56
|
+
const options = { force: false, help: false, offline: false, ref: DEFAULT_REF, target: process.cwd() };
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < argv.length; i++) {
|
|
59
|
+
const arg = argv[i];
|
|
60
|
+
if (arg === "--help" || arg === "-h") options.help = true;
|
|
61
|
+
else if (arg === "--force" || arg === "-f") options.force = true;
|
|
62
|
+
else if (arg === "--offline") options.offline = true;
|
|
63
|
+
else if (arg === "--ref") {
|
|
64
|
+
options.ref = argv[++i];
|
|
65
|
+
if (!options.ref) {
|
|
66
|
+
console.error("--ref needs a branch or tag name");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
} else if (!arg.startsWith("-")) options.target = resolve(arg);
|
|
70
|
+
else {
|
|
42
71
|
console.error(`Unknown option: ${arg}`);
|
|
43
72
|
printHelp();
|
|
44
73
|
process.exit(1);
|
|
@@ -48,60 +77,141 @@ function parseArgs(argv) {
|
|
|
48
77
|
return options;
|
|
49
78
|
}
|
|
50
79
|
|
|
51
|
-
function
|
|
52
|
-
|
|
80
|
+
async function get(url) {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(url, { signal: controller.signal, headers: { "user-agent": "sliderpro-agentic-skills-etch" } });
|
|
85
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
86
|
+
return await res.text();
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
53
90
|
}
|
|
54
91
|
|
|
55
|
-
function
|
|
56
|
-
|
|
92
|
+
function bundledManifest() {
|
|
93
|
+
return JSON.parse(readFileSync(join(bundleRoot, "manifest.json"), "utf8"));
|
|
94
|
+
}
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
/** Returns { files: [{out, content}] } or null if anything at all went wrong. */
|
|
97
|
+
async function fetchLive(ref) {
|
|
98
|
+
let manifest;
|
|
99
|
+
try {
|
|
100
|
+
manifest = JSON.parse(await get(rawUrl(ref, REPO_MANIFEST)));
|
|
101
|
+
} catch {
|
|
102
|
+
// The manifest may predate this feature on an older ref; the bundled list
|
|
103
|
+
// is still a reasonable guess at what to fetch.
|
|
104
|
+
try {
|
|
105
|
+
manifest = bundledManifest();
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const entries = [...(manifest.skills || []), ...(manifest.components || [])];
|
|
112
|
+
if (entries.length === 0) return null;
|
|
61
113
|
|
|
62
|
-
|
|
63
|
-
|
|
114
|
+
const files = [];
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
let text;
|
|
117
|
+
try {
|
|
118
|
+
text = await get(rawUrl(ref, entry.repo));
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
64
121
|
}
|
|
122
|
+
if (!looksLikeMarkdown(text)) return null;
|
|
123
|
+
files.push({ out: entry.out, content: applyRewrites(text) });
|
|
124
|
+
}
|
|
125
|
+
return files;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeFiles(target, files) {
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
const dest = join(target, file.out);
|
|
131
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
132
|
+
writeFileSync(dest, file.content);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function copyBundle(target) {
|
|
137
|
+
for (const dir of DIRS) {
|
|
138
|
+
const source = join(bundleRoot, dir);
|
|
139
|
+
if (existsSync(source)) cpSync(source, join(target, dir), { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// When each skills file is meant to be read. The split exists so that only the
|
|
144
|
+
// entry file is read up front, so saying so here is part of the install.
|
|
145
|
+
const SKILL_ROLES = {
|
|
146
|
+
"slider-pro-skills.md": "read this first, every session",
|
|
147
|
+
"slider-pro-skills-build.md": "read only when building from scratch",
|
|
148
|
+
"slider-pro-skills-reference.md": "lookup only, never read whole",
|
|
149
|
+
};
|
|
65
150
|
|
|
66
|
-
|
|
151
|
+
function installedSkills(target) {
|
|
152
|
+
try {
|
|
153
|
+
return readdirSync(join(target, OUT_SKILLS_DIR))
|
|
154
|
+
.filter((f) => f.endsWith(".md"))
|
|
155
|
+
.sort();
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
67
158
|
}
|
|
68
159
|
}
|
|
69
160
|
|
|
70
|
-
function countComponents() {
|
|
161
|
+
function countComponents(target) {
|
|
71
162
|
try {
|
|
72
|
-
return readdirSync(join(
|
|
163
|
+
return readdirSync(join(target, OUT_COMPONENTS_DIR)).filter((f) => f.endsWith(".md")).length;
|
|
73
164
|
} catch {
|
|
74
165
|
return 0;
|
|
75
166
|
}
|
|
76
167
|
}
|
|
77
168
|
|
|
78
|
-
function main() {
|
|
79
|
-
const { force, help, target } = parseArgs(process.argv.slice(2));
|
|
169
|
+
async function main() {
|
|
170
|
+
const { force, help, offline, ref, target } = parseArgs(process.argv.slice(2));
|
|
80
171
|
|
|
81
172
|
if (help) {
|
|
82
173
|
printHelp();
|
|
83
174
|
return;
|
|
84
175
|
}
|
|
85
176
|
|
|
86
|
-
const conflicts =
|
|
87
|
-
|
|
177
|
+
const conflicts = DIRS.filter((dir) => existsSync(join(target, dir))).map((dir) => `${dir}/`);
|
|
88
178
|
if (conflicts.length > 0 && !force) {
|
|
89
179
|
console.error("Installation blocked, the following already exist:");
|
|
90
|
-
for (const conflict of conflicts) {
|
|
91
|
-
console.error(` ${conflict}`);
|
|
92
|
-
}
|
|
180
|
+
for (const conflict of conflicts) console.error(` ${conflict}`);
|
|
93
181
|
console.error("\nRe-run with --force to overwrite.");
|
|
94
182
|
process.exit(1);
|
|
95
183
|
}
|
|
96
184
|
|
|
97
|
-
|
|
185
|
+
mkdirSync(target, { recursive: true });
|
|
186
|
+
|
|
187
|
+
let source;
|
|
188
|
+
if (offline) {
|
|
189
|
+
copyBundle(target);
|
|
190
|
+
source = "the copy bundled with this package (--offline)";
|
|
191
|
+
} else {
|
|
192
|
+
const files = await fetchLive(ref);
|
|
193
|
+
if (files) {
|
|
194
|
+
writeFiles(target, files);
|
|
195
|
+
source = `${REPO} (${ref})`;
|
|
196
|
+
} else {
|
|
197
|
+
copyBundle(target);
|
|
198
|
+
source = "the copy bundled with this package (network unavailable)";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
98
201
|
|
|
99
202
|
console.log(`Installed Slider Pro AI Connector skills to ${target}`);
|
|
100
|
-
console.log(`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
203
|
+
console.log(` source: ${source}`);
|
|
204
|
+
for (const name of installedSkills(target)) {
|
|
205
|
+
const role = SKILL_ROLES[name];
|
|
206
|
+
console.log(` ${OUT_SKILLS_DIR}/${name}${role ? ` (${role})` : ""}`);
|
|
207
|
+
}
|
|
208
|
+
console.log(` ${OUT_COMPONENTS_DIR}/ (${countComponents(target)} component prop docs)`);
|
|
209
|
+
console.log(`\nPoint your AI coding agent at ${OUT_SKILLS_DIR}/slider-pro-skills.md to load it.`);
|
|
210
|
+
console.log("It reads the other two only when a task needs them.");
|
|
104
211
|
console.log('Then tell the agent: "npx @digital-gravy/etch-connector serve"');
|
|
105
212
|
}
|
|
106
213
|
|
|
107
|
-
main()
|
|
214
|
+
main().catch((err) => {
|
|
215
|
+
console.error(err.message || err);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Single source of truth for where the skills come from and how their links are
|
|
2
|
+
// rewritten for an installed project. Both scripts/build.js (which bundles a
|
|
3
|
+
// copy at publish time) and bin/cli.js (which prefers a live copy at install
|
|
4
|
+
// time) import this, so the two paths cannot drift in how they rewrite links.
|
|
5
|
+
|
|
6
|
+
export const REPO = "udoro/SliderPro-Etch-Docs";
|
|
7
|
+
export const DEFAULT_REF = "master";
|
|
8
|
+
export const DOCS_URL = "https://design-with-cracka.gitbook.io/etchsliderpro";
|
|
9
|
+
|
|
10
|
+
// Where things live in the docs repo.
|
|
11
|
+
export const REPO_SKILLS_DIR = "ai-connector/slider-skills";
|
|
12
|
+
export const REPO_COMPONENTS_DIR = "components";
|
|
13
|
+
export const REPO_MANIFEST = "ai-connector/skills-manifest.json";
|
|
14
|
+
|
|
15
|
+
// Where they land in a user's project.
|
|
16
|
+
export const OUT_SKILLS_DIR = "slider-skills";
|
|
17
|
+
export const OUT_COMPONENTS_DIR = "components";
|
|
18
|
+
|
|
19
|
+
// The docs repo nests slider-skills/ two levels below the root
|
|
20
|
+
// (ai-connector/slider-skills/), so it links components/ as ../../components/.
|
|
21
|
+
// Installed, slider-skills/ sits directly under the project root, so that
|
|
22
|
+
// becomes ../components/.
|
|
23
|
+
//
|
|
24
|
+
// The sibling doc pages the skills link to do not ship in the package at all,
|
|
25
|
+
// so point those at the public site rather than leave a link resolving to
|
|
26
|
+
// nothing.
|
|
27
|
+
export const PATH_REWRITE = [
|
|
28
|
+
[/\.\.\/\.\.\/components\//g, "../components/"],
|
|
29
|
+
[/\.\.\/\.\.\/card-stack-templates\.md/g, `${DOCS_URL}/card-stack-templates`],
|
|
30
|
+
[/\.\.\/\.\.\/premade-templates\.md/g, `${DOCS_URL}/premade-templates`],
|
|
31
|
+
[/\.\.\/\.\.\/javascript-api\.md/g, `${DOCS_URL}/javascript-api`],
|
|
32
|
+
[/\.\.\/\.\.\/styling-and-responsive\.md/g, `${DOCS_URL}/styling-and-responsive`],
|
|
33
|
+
[/\.\.\/\.\.\/admin-settings\.md/g, `${DOCS_URL}/admin-settings`],
|
|
34
|
+
[/\.\.\/\.\.\/troubleshooting\.md/g, `${DOCS_URL}/troubleshooting`],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function applyRewrites(content) {
|
|
38
|
+
let out = content;
|
|
39
|
+
for (const [pattern, replacement] of PATH_REWRITE) out = out.replace(pattern, replacement);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Only a markdown LINK TARGET pointing outside the package is a defect: it would
|
|
45
|
+
* 404 for every installed user. A bare ../../ path in prose is not, because the
|
|
46
|
+
* skills files deliberately reference optional local files and always say what
|
|
47
|
+
* to do when they are absent.
|
|
48
|
+
*/
|
|
49
|
+
export function findStaleLinks(content) {
|
|
50
|
+
return [...content.matchAll(/\]\((\.\.\/\.\.\/[^)]+)\)/g)].map((m) => m[1]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function rawUrl(ref, repoPath) {
|
|
54
|
+
return `https://raw.githubusercontent.com/${REPO}/${ref}/${repoPath}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A fetched file only counts if it is non-empty markdown. A proxy or an error
|
|
59
|
+
* page returning 200 with HTML would otherwise be written out as a skills file.
|
|
60
|
+
*/
|
|
61
|
+
export function looksLikeMarkdown(text) {
|
|
62
|
+
if (!text || text.trim().length < 200) return false;
|
|
63
|
+
const head = text.slice(0, 400).toLowerCase();
|
|
64
|
+
return !head.includes("<!doctype html") && !head.includes("<html");
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sliderpro-agentic-skills-etch",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Installs the Slider Pro for Etch AI Connector skills files and component docs into any project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"lib",
|
|
11
12
|
"skills-package"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
@@ -58,3 +58,41 @@ Set **Custom Pagination Mode** to `Template` and design the *first* item as your
|
|
|
58
58
|
|
|
59
59
|
- **Numbers** count up starting from whatever number you type, so `1` produces `1, 2, 3, ...` while `2003` produces `2003, 2004, 2005, ...`. Leading zeros are preserved as the number grows (`007` → `007, 008, ..., 010, 011, ...`).
|
|
60
60
|
- **Letters and roman numerals** (`a`, `A`, `i`, `I`) always start at the beginning of their sequence (a/b/c…, i/ii/iii…). Only numbers support a custom starting point.
|
|
61
|
+
|
|
62
|
+
## Styling the active item
|
|
63
|
+
|
|
64
|
+
Slider Pro adds the class `is-active` to whichever pagination item matches the slide on screen. Use
|
|
65
|
+
it to style the current one:
|
|
66
|
+
|
|
67
|
+
```css
|
|
68
|
+
.my-dot { background: #e5e7eb; }
|
|
69
|
+
.my-dot.is-active { background: #45bf55; }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The class moves on its own as the slider changes, so you do not need any JavaScript. It works the
|
|
73
|
+
same in `Default` and `Template` mode.
|
|
74
|
+
|
|
75
|
+
***
|
|
76
|
+
|
|
77
|
+
## If your own layout does not apply
|
|
78
|
+
|
|
79
|
+
The pagination lays itself out as a flex row or column, using the **Direction** and **Gap**
|
|
80
|
+
settings above, and it is only as tall as its items.
|
|
81
|
+
|
|
82
|
+
If you write your own layout on it, for example a grid, and nothing seems to happen, this is why:
|
|
83
|
+
your rule and the plugin's rule are equally specific, so the plugin's wins. Put the plugin's class
|
|
84
|
+
in front of yours to make your rule stronger:
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
/* has no effect on its own */
|
|
88
|
+
.my-pagination { display: grid; }
|
|
89
|
+
|
|
90
|
+
/* wins */
|
|
91
|
+
.dwc-slider-pagination-wrapper.my-pagination {
|
|
92
|
+
display: grid;
|
|
93
|
+
block-size: 100%;
|
|
94
|
+
gap: 0;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Remember to reset `gap` if you set your own spacing, because the flex gap still applies.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generated": "2026-08-29",
|
|
3
|
+
"skills": [
|
|
4
|
+
{
|
|
5
|
+
"repo": "ai-connector/slider-skills/slider-pro-skills-build.md",
|
|
6
|
+
"out": "slider-skills/slider-pro-skills-build.md"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"repo": "ai-connector/slider-skills/slider-pro-skills-reference.md",
|
|
10
|
+
"out": "slider-skills/slider-pro-skills-reference.md"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"repo": "ai-connector/slider-skills/slider-pro-skills.md",
|
|
14
|
+
"out": "slider-skills/slider-pro-skills.md"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"components": [
|
|
18
|
+
{
|
|
19
|
+
"repo": "components/dwc-slide.md",
|
|
20
|
+
"out": "components/dwc-slide.md"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"repo": "components/dwc-slider-nav-button.md",
|
|
24
|
+
"out": "components/dwc-slider-nav-button.md"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"repo": "components/dwc-slider-pagination.md",
|
|
28
|
+
"out": "components/dwc-slider-pagination.md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"repo": "components/dwc-slider-play-pause.md",
|
|
32
|
+
"out": "components/dwc-slider-play-pause.md"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"repo": "components/dwc-slider-progress.md",
|
|
36
|
+
"out": "components/dwc-slider-progress.md"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"repo": "components/dwc-slider-wrapper.md",
|
|
40
|
+
"out": "components/dwc-slider-wrapper.md"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"repo": "components/dwc-slider.md",
|
|
44
|
+
"out": "components/dwc-slider.md"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|