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 CHANGED
@@ -1,14 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
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 sourceRoot = join(packageRoot, "skills-package");
27
+ const bundleRoot = join(packageRoot, "skills-package");
10
28
 
11
- const DIRS = ["slider-skills", "components"];
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 Overwrite existing slider-skills/ or components/
22
- --help, -h Show this help message
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 (const arg of argv) {
35
- if (arg === "--help" || arg === "-h") {
36
- options.help = true;
37
- } else if (arg === "--force" || arg === "-f") {
38
- options.force = true;
39
- } else if (!arg.startsWith("-")) {
40
- options.target = resolve(arg);
41
- } else {
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 listConflicts(target) {
52
- return DIRS.filter((dir) => existsSync(join(target, dir))).map((dir) => `${dir}/`);
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 copyPackageFiles(target, force) {
56
- mkdirSync(target, { recursive: true });
92
+ function bundledManifest() {
93
+ return JSON.parse(readFileSync(join(bundleRoot, "manifest.json"), "utf8"));
94
+ }
57
95
 
58
- for (const dir of DIRS) {
59
- const source = join(sourceRoot, dir);
60
- const dest = join(target, dir);
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
- if (existsSync(dest) && !force) {
63
- continue;
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
- cpSync(source, dest, { recursive: true });
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(sourceRoot, "components")).filter((f) => f.endsWith(".md")).length;
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 = listConflicts(target);
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
- copyPackageFiles(target, force);
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(` slider-skills/slider-pro-skills.md`);
101
- console.log(` slider-skills/slider-pro-skills-reference.md`);
102
- console.log(` components/ (${countComponents()} component prop docs)`);
103
- console.log("\nPoint your AI coding agent at slider-skills/slider-pro-skills.md to load it.");
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.2",
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
+ }