@maccesar/aiskills 1.7.0

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.
Files changed (73) hide show
  1. package/README.md +531 -0
  2. package/bin/aiskills.js +76 -0
  3. package/lib/cache.js +49 -0
  4. package/lib/cleanup.js +77 -0
  5. package/lib/commands/auto-update.js +131 -0
  6. package/lib/commands/doctor.js +139 -0
  7. package/lib/commands/list.js +77 -0
  8. package/lib/commands/skills.js +263 -0
  9. package/lib/commands/status.js +94 -0
  10. package/lib/commands/uninstall.js +182 -0
  11. package/lib/commands/update.js +149 -0
  12. package/lib/config.js +90 -0
  13. package/lib/downloader.js +110 -0
  14. package/lib/hooks.js +74 -0
  15. package/lib/installer.js +114 -0
  16. package/lib/platform.js +112 -0
  17. package/lib/prompts/checkboxCancel.js +264 -0
  18. package/lib/prompts/selectCancel.js +204 -0
  19. package/lib/symlink.js +154 -0
  20. package/lib/utils.js +49 -0
  21. package/package.json +61 -0
  22. package/skills/humaniza/SKILL.md +51 -0
  23. package/skills/humaniza/agents/openai.yaml +4 -0
  24. package/skills/humaniza/references/ai-patterns-es.md +51 -0
  25. package/skills/humaniza/references/checklist.md +9 -0
  26. package/skills/humaniza/references/examples.md +17 -0
  27. package/skills/humaniza/references/lexicon-es-mx.md +36 -0
  28. package/skills/humaniza/references/modes-es-mx.md +41 -0
  29. package/skills/humaniza/references/voice-es-mx.md +24 -0
  30. package/skills/refactoring-ui/SKILL.md +59 -0
  31. package/skills/refactoring-ui/references/01-design-process.md +72 -0
  32. package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
  33. package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
  34. package/skills/refactoring-ui/references/04-typography.md +70 -0
  35. package/skills/refactoring-ui/references/05-color.md +96 -0
  36. package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
  37. package/skills/refactoring-ui/references/07-images.md +75 -0
  38. package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
  39. package/skills/stitch-showcase/SKILL.md +411 -0
  40. package/skills/stitch-showcase/references/01-navbar.md +52 -0
  41. package/skills/stitch-showcase/references/02-hero.md +56 -0
  42. package/skills/stitch-showcase/references/03-design-system.md +102 -0
  43. package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
  44. package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
  45. package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
  46. package/skills/stitch-showcase/references/07-theme-system.md +77 -0
  47. package/skills/stitch-showcase/references/08-type-detection.md +81 -0
  48. package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
  49. package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
  50. package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
  51. package/skills/stitch-showcase/references/catalog-template.html +841 -0
  52. package/skills/stitch-showcase/references/index.html +299 -0
  53. package/skills/stitch-showcase/references/viewer.html +412 -0
  54. package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
  55. package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
  56. package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
  57. package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
  58. package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
  59. package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
  60. package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
  61. package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
  62. package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
  63. package/skills/stitch-showcase/scripts/component_utils.py +398 -0
  64. package/skills/stitch-showcase/scripts/detect_components.py +284 -0
  65. package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
  66. package/skills/stitch-showcase/scripts/extract_text.py +268 -0
  67. package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
  68. package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
  69. package/skills/vscode-extension-dev/SKILL.md +114 -0
  70. package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
  71. package/skills/vscode-extension-dev/references/architecture.md +287 -0
  72. package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
  73. package/skills/vscode-extension-dev/references/publishing.md +251 -0
@@ -0,0 +1,268 @@
1
+ """
2
+ extract_text.py — Extract visible text from Stitch HTML files.
3
+
4
+ Produces a compact text summary per screen, suitable for LLM consumption
5
+ without reading the full HTML. This dramatically reduces token usage when
6
+ generating DESIGN.md descriptions.
7
+
8
+ Usage:
9
+ # As a module (from build_showcase.py):
10
+ from extract_text import extract_visible_text, extract_all_screens_text
11
+
12
+ # Standalone:
13
+ python extract_text.py /path/to/assets/ → prints summaries to stdout
14
+ """
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+ from html import unescape
19
+
20
+
21
+ def extract_visible_text(html_path: Path) -> dict:
22
+ """
23
+ Extract visible text content from a Stitch HTML file.
24
+
25
+ Returns a dict with:
26
+ - headings: list of h1-h6 text
27
+ - paragraphs: list of paragraph text
28
+ - buttons: list of button/link text
29
+ - lists: list of li text
30
+ - inputs: list of placeholder/label text
31
+ - meta_title: <title> text if any
32
+ - meta_desc: <meta description> if any
33
+ - css_colors: list of hex colors found in CSS
34
+ - css_fonts: list of font families found in CSS
35
+ """
36
+ if not html_path.exists():
37
+ return {}
38
+
39
+ raw = html_path.read_text(encoding="utf-8", errors="replace")
40
+
41
+ # Extract meta info before stripping
42
+ meta_title = _extract_meta_title(raw)
43
+ meta_desc = _extract_meta_desc(raw)
44
+ css_colors = _extract_css_colors(raw)
45
+ css_fonts = _extract_css_fonts(raw)
46
+
47
+ # Strip non-visible content
48
+ clean = _strip_invisible(raw)
49
+
50
+ # Extract structured text
51
+ headings = _extract_by_tag(clean, r"<h[1-6][^>]*>(.*?)</h[1-6]>")
52
+ paragraphs = _extract_by_tag(clean, r"<p[^>]*>(.*?)</p>")
53
+ buttons = _extract_buttons(clean)
54
+ lists = _extract_by_tag(clean, r"<li[^>]*>(.*?)</li>")
55
+ inputs = _extract_inputs(clean)
56
+
57
+ return {
58
+ "headings": headings,
59
+ "paragraphs": paragraphs,
60
+ "buttons": buttons,
61
+ "lists": lists,
62
+ "inputs": inputs,
63
+ "meta_title": meta_title,
64
+ "meta_desc": meta_desc,
65
+ "css_colors": css_colors,
66
+ "css_fonts": css_fonts,
67
+ }
68
+
69
+
70
+ def extract_all_screens_text(assets_dir: Path) -> list[dict]:
71
+ """
72
+ Extract text from all HTML files in an assets directory.
73
+
74
+ Returns a list of dicts, each with 'slug' and 'text' keys.
75
+ """
76
+ results = []
77
+ html_files = sorted(assets_dir.glob("*.html"))
78
+
79
+ for html_path in html_files:
80
+ slug = html_path.stem
81
+ text_data = extract_visible_text(html_path)
82
+ if text_data:
83
+ results.append({"slug": slug, "text": text_data})
84
+
85
+ return results
86
+
87
+
88
+ def format_screen_summary(slug: str, text_data: dict) -> str:
89
+ """
90
+ Format extracted text into a compact summary string for LLM consumption.
91
+
92
+ Produces ~10-30 lines per screen instead of 200+ lines of raw HTML.
93
+ """
94
+ lines = [f"## {slug}"]
95
+
96
+ if text_data.get("meta_title"):
97
+ lines.append(f"Title: {text_data['meta_title']}")
98
+
99
+ if text_data.get("meta_desc"):
100
+ lines.append(f"Description: {text_data['meta_desc']}")
101
+
102
+ if text_data.get("headings"):
103
+ lines.append(f"Headings: {' | '.join(text_data['headings'][:10])}")
104
+
105
+ if text_data.get("paragraphs"):
106
+ # Truncate long paragraphs
107
+ paras = [p[:150] for p in text_data["paragraphs"][:5]]
108
+ lines.append(f"Text: {' // '.join(paras)}")
109
+
110
+ if text_data.get("buttons"):
111
+ lines.append(f"Buttons: {', '.join(text_data['buttons'][:15])}")
112
+
113
+ if text_data.get("lists"):
114
+ lines.append(f"List items: {', '.join(text_data['lists'][:15])}")
115
+
116
+ if text_data.get("inputs"):
117
+ lines.append(f"Inputs: {', '.join(text_data['inputs'][:10])}")
118
+
119
+ if text_data.get("css_colors"):
120
+ lines.append(f"Colors: {', '.join(text_data['css_colors'][:10])}")
121
+
122
+ if text_data.get("css_fonts"):
123
+ lines.append(f"Fonts: {', '.join(text_data['css_fonts'][:5])}")
124
+
125
+ return "\n".join(lines)
126
+
127
+
128
+ def format_all_summaries(screen_texts: list[dict]) -> str:
129
+ """Format all screen summaries into a single string."""
130
+ parts = []
131
+ for item in screen_texts:
132
+ summary = format_screen_summary(item["slug"], item["text"])
133
+ parts.append(summary)
134
+ return "\n\n".join(parts)
135
+
136
+
137
+ # ─── Internal helpers ──────────────────────────────────────────────────────────
138
+
139
+
140
+ def _strip_invisible(html: str) -> str:
141
+ """Remove script, style, svg, noscript, and HTML comments."""
142
+ # Remove script blocks
143
+ html = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.IGNORECASE)
144
+ # Remove style blocks
145
+ html = re.sub(r"<style[^>]*>.*?</style>", "", html, flags=re.DOTALL | re.IGNORECASE)
146
+ # Remove SVG blocks
147
+ html = re.sub(r"<svg[^>]*>.*?</svg>", "", html, flags=re.DOTALL | re.IGNORECASE)
148
+ # Remove noscript
149
+ html = re.sub(r"<noscript[^>]*>.*?</noscript>", "", html, flags=re.DOTALL | re.IGNORECASE)
150
+ # Remove HTML comments
151
+ html = re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL)
152
+ return html
153
+
154
+
155
+ def _strip_tags(text: str) -> str:
156
+ """Remove all HTML tags and decode entities."""
157
+ text = re.sub(r"<[^>]+>", " ", text)
158
+ text = unescape(text)
159
+ text = re.sub(r"\s+", " ", text).strip()
160
+ return text
161
+
162
+
163
+ def _extract_by_tag(html: str, pattern: str) -> list[str]:
164
+ """Extract and clean text matching a regex pattern."""
165
+ matches = re.findall(pattern, html, flags=re.DOTALL | re.IGNORECASE)
166
+ results = []
167
+ for m in matches:
168
+ clean = _strip_tags(m).strip()
169
+ if clean and len(clean) > 1:
170
+ results.append(clean)
171
+ return results
172
+
173
+
174
+ def _extract_buttons(html: str) -> list[str]:
175
+ """Extract text from buttons and clickable links."""
176
+ patterns = [
177
+ r"<button[^>]*>(.*?)</button>",
178
+ r'<a[^>]*class="[^"]*btn[^"]*"[^>]*>(.*?)</a>',
179
+ r'<a[^>]*role="button"[^>]*>(.*?)</a>',
180
+ r'<input[^>]*type="submit"[^>]*value="([^"]*)"',
181
+ ]
182
+ results = []
183
+ for pat in patterns:
184
+ for m in re.findall(pat, html, flags=re.DOTALL | re.IGNORECASE):
185
+ clean = _strip_tags(m).strip()
186
+ if clean and len(clean) > 1:
187
+ results.append(clean)
188
+ return results
189
+
190
+
191
+ def _extract_inputs(html: str) -> list[str]:
192
+ """Extract placeholder text and labels from form elements."""
193
+ results = []
194
+ # Placeholders
195
+ for m in re.findall(r'placeholder="([^"]*)"', html, flags=re.IGNORECASE):
196
+ if m.strip():
197
+ results.append(m.strip())
198
+ # Labels
199
+ for m in re.findall(r"<label[^>]*>(.*?)</label>", html, flags=re.DOTALL | re.IGNORECASE):
200
+ clean = _strip_tags(m).strip()
201
+ if clean and len(clean) > 1:
202
+ results.append(clean)
203
+ return results
204
+
205
+
206
+ def _extract_meta_title(html: str) -> str:
207
+ """Extract <title> content."""
208
+ m = re.search(r"<title[^>]*>(.*?)</title>", html, flags=re.DOTALL | re.IGNORECASE)
209
+ if m:
210
+ title = _strip_tags(m.group(1)).strip()
211
+ # Skip generic titles
212
+ if title.lower() not in ("untitled", "index", "screen", "document", ""):
213
+ return title
214
+ return ""
215
+
216
+
217
+ def _extract_meta_desc(html: str) -> str:
218
+ """Extract meta description."""
219
+ m = re.search(r'<meta[^>]*name="description"[^>]*content="([^"]*)"', html, flags=re.IGNORECASE)
220
+ if not m:
221
+ m = re.search(r'<meta[^>]*property="og:description"[^>]*content="([^"]*)"', html, flags=re.IGNORECASE)
222
+ return m.group(1).strip() if m else ""
223
+
224
+
225
+ def _extract_css_colors(html: str) -> list[str]:
226
+ """Extract unique hex colors from inline styles and style blocks."""
227
+ colors = set()
228
+ for m in re.findall(r"#([0-9a-fA-F]{6})\b", html):
229
+ hex_val = f"#{m.upper()}"
230
+ # Skip near-black and near-white (too common/generic)
231
+ if hex_val not in ("#000000", "#FFFFFF", "#000", "#FFF"):
232
+ colors.add(hex_val)
233
+ return sorted(colors)[:10]
234
+
235
+
236
+ def _extract_css_fonts(html: str) -> list[str]:
237
+ """Extract font-family declarations from CSS."""
238
+ fonts = set()
239
+ for m in re.findall(r"font-family:\s*['\"]?([^;'\"}{,]+)", html, flags=re.IGNORECASE):
240
+ font = m.strip().strip("'\"")
241
+ if font.lower() not in ("inherit", "initial", "unset", "sans-serif", "serif", "monospace", "system-ui"):
242
+ fonts.add(font)
243
+ # Also check Google Fonts links
244
+ for m in re.findall(r"fonts\.googleapis\.com/css2?\?family=([^&\"' ]+)", html):
245
+ font = m.replace("+", " ").split(":")[0]
246
+ fonts.add(font)
247
+ return sorted(fonts)
248
+
249
+
250
+ # ─── CLI ───────────────────────────────────────────────────────────────────────
251
+
252
+ if __name__ == "__main__":
253
+ if len(sys.argv) < 2:
254
+ print("Usage: python extract_text.py /path/to/assets/", file=sys.stderr)
255
+ sys.exit(1)
256
+
257
+ assets = Path(sys.argv[1]).resolve()
258
+ if not assets.is_dir():
259
+ print(f"Error: '{assets}' is not a directory.", file=sys.stderr)
260
+ sys.exit(1)
261
+
262
+ screen_texts = extract_all_screens_text(assets)
263
+ if not screen_texts:
264
+ print("No HTML files found.", file=sys.stderr)
265
+ sys.exit(1)
266
+
267
+ print(format_all_summaries(screen_texts))
268
+ print(f"\n--- {len(screen_texts)} screens extracted ---", file=sys.stderr)
@@ -0,0 +1,178 @@
1
+ """
2
+ extract_zips.py — Extracts Stitch zips and renames files for stitch-showcase.
3
+
4
+ Each Stitch zip contains:
5
+ code.html → renamed to {zip_name}.html
6
+ screen.png → renamed to {zip_name}.png
7
+
8
+ Usage:
9
+ python extract_zips.py /path/to/source /path/to/output/assets
10
+ → extracts all .zip files in /path/to/source to /path/to/output/assets/
11
+ """
12
+ import sys
13
+ import shutil
14
+ import zipfile
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+
19
+ def extract_all(source_dir: str, assets_dir: str) -> list[dict]:
20
+ """
21
+ Extract and rename all Stitch zips in source_dir.
22
+
23
+ Returns:
24
+ List of dicts with {slug, html_path, png_path} per processed screen.
25
+ """
26
+ source = Path(source_dir)
27
+ assets = Path(assets_dir)
28
+ assets.mkdir(parents=True, exist_ok=True)
29
+
30
+ screens = []
31
+
32
+ # Collect sources: zips + already-extracted folders
33
+ zips = sorted(source.glob("*.zip"))
34
+ extracted_dirs = [d for d in sorted(source.iterdir())
35
+ if d.is_dir() and (d / "code.html").exists()]
36
+
37
+ # Process zips
38
+ for zip_path in zips:
39
+ slug = _slug_from_name(zip_path.stem)
40
+ result = _process_zip(zip_path, slug, assets)
41
+ if result:
42
+ screens.append(result)
43
+
44
+ # Process already-extracted folders (if zip was unpacked previously)
45
+ already_processed = {s["slug"] for s in screens}
46
+ for dir_path in extracted_dirs:
47
+ slug = _slug_from_name(dir_path.name)
48
+ if slug in already_processed:
49
+ continue
50
+ result = _process_dir(dir_path, slug, assets)
51
+ if result:
52
+ screens.append(result)
53
+
54
+ # Sort by slug name
55
+ screens.sort(key=lambda s: s["slug"])
56
+ return screens
57
+
58
+
59
+ def _process_zip(zip_path: Path, slug: str, assets: Path) -> dict | None:
60
+ """Extract a Stitch zip and copy renamed files to assets/."""
61
+ html_dst = assets / f"{slug}.html"
62
+ png_dst = assets / f"{slug}.png"
63
+
64
+ # Incremental: skip if output is newer than the zip
65
+ if html_dst.exists() and html_dst.stat().st_mtime > zip_path.stat().st_mtime:
66
+ print(f" ↩ {slug} (unchanged)")
67
+ return {
68
+ "slug": slug,
69
+ "html_path": str(html_dst),
70
+ "png_path": str(png_dst) if png_dst.exists() else None,
71
+ }
72
+
73
+ with tempfile.TemporaryDirectory() as tmp:
74
+ tmp_path = Path(tmp)
75
+
76
+ try:
77
+ with zipfile.ZipFile(zip_path, "r") as zf:
78
+ zf.extractall(tmp_path)
79
+ except zipfile.BadZipFile:
80
+ print(f" ⚠ {zip_path.name}: not a valid zip, skipping.", file=sys.stderr)
81
+ return None
82
+
83
+ # Find code.html and screen.png (may be inside a subdirectory)
84
+ html_src = _find_file(tmp_path, "code.html")
85
+ png_src = _find_file(tmp_path, "screen.png")
86
+
87
+ if not html_src:
88
+ print(f" ⚠ {zip_path.name}: does not contain code.html, skipping.", file=sys.stderr)
89
+ return None
90
+
91
+ _copy_html(html_src, html_dst)
92
+
93
+ png_dst_result = None
94
+ if png_src:
95
+ shutil.copy2(png_src, png_dst)
96
+ png_dst_result = png_dst
97
+ else:
98
+ print(f" ⚠ {zip_path.name}: does not contain screen.png.", file=sys.stderr)
99
+
100
+ print(f" ✓ {slug}")
101
+ return {
102
+ "slug": slug,
103
+ "html_path": str(html_dst),
104
+ "png_path": str(png_dst_result) if png_dst_result else None,
105
+ }
106
+
107
+
108
+ def _process_dir(dir_path: Path, slug: str, assets: Path) -> dict | None:
109
+ """Copy files from an already-extracted folder to assets/."""
110
+ html_src = dir_path / "code.html"
111
+ png_src = dir_path / "screen.png"
112
+ html_dst = assets / f"{slug}.html"
113
+ png_dst = assets / f"{slug}.png"
114
+
115
+ # Incremental: skip if output is newer than source
116
+ if html_dst.exists() and html_dst.stat().st_mtime > html_src.stat().st_mtime:
117
+ print(f" ↩ {slug} (unchanged)")
118
+ return {
119
+ "slug": slug,
120
+ "html_path": str(html_dst),
121
+ "png_path": str(png_dst) if png_dst.exists() else None,
122
+ }
123
+
124
+ _copy_html(html_src, html_dst)
125
+
126
+ png_dst_result = None
127
+ if png_src.exists():
128
+ shutil.copy2(png_src, png_dst)
129
+ png_dst_result = png_dst
130
+
131
+ print(f" ✓ {slug} (folder)")
132
+ return {
133
+ "slug": slug,
134
+ "html_path": str(html_dst),
135
+ "png_path": str(png_dst_result) if png_dst_result else None,
136
+ }
137
+
138
+
139
+ _NO_SCROLLBAR_CSS = '<style>*::-webkit-scrollbar{display:none!important}*{scrollbar-width:none!important;-ms-overflow-style:none!important}</style>'
140
+
141
+ def _copy_html(src: Path, dst: Path) -> None:
142
+ """Copy HTML injecting scrollbar-hiding CSS into <head>."""
143
+ text = src.read_text(encoding="utf-8", errors="ignore")
144
+ if "</head>" in text:
145
+ text = text.replace("</head>", f"{_NO_SCROLLBAR_CSS}</head>", 1)
146
+ elif "<body" in text:
147
+ text = text.replace("<body", f"{_NO_SCROLLBAR_CSS}<body", 1)
148
+ dst.write_text(text, encoding="utf-8")
149
+
150
+
151
+ def _find_file(root: Path, filename: str) -> Path | None:
152
+ """Recursively find a file by name inside root."""
153
+ matches = list(root.rglob(filename))
154
+ return matches[0] if matches else None
155
+
156
+
157
+ def _slug_from_name(name: str) -> str:
158
+ """
159
+ Convert filename/folder name to a slug.
160
+ '01-splash-screen' → '01_splash_screen'
161
+ 'Login Screen' → 'login_screen'
162
+ """
163
+ import re
164
+ s = name.strip().lower()
165
+ s = re.sub(r"[\s\-]+", "_", s)
166
+ s = re.sub(r"[^a-z0-9_]", "", s)
167
+ s = re.sub(r"_+", "_", s).strip("_")
168
+ return s
169
+
170
+
171
+ if __name__ == "__main__":
172
+ if len(sys.argv) < 3:
173
+ print("Usage: python extract_zips.py /path/to/source /path/to/output/assets", file=sys.stderr)
174
+ sys.exit(1)
175
+
176
+ import json
177
+ screens = extract_all(sys.argv[1], sys.argv[2])
178
+ print(json.dumps(screens, ensure_ascii=False, indent=2))