rebly-sections 1.0.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 (46) hide show
  1. package/README.md +67 -0
  2. package/assets/SKILL.md +65 -0
  3. package/assets/commands/section.md +27 -0
  4. package/assets/commands/theme-init.md +20 -0
  5. package/assets/data/component-library/_index.csv +19 -0
  6. package/assets/data/component-library/announcement-bar.liquid +69 -0
  7. package/assets/data/component-library/blog-posts-grid.liquid +77 -0
  8. package/assets/data/component-library/collection-list.liquid +71 -0
  9. package/assets/data/component-library/cta-banner.liquid +58 -0
  10. package/assets/data/component-library/faq-accordion.liquid +76 -0
  11. package/assets/data/component-library/features-grid.liquid +94 -0
  12. package/assets/data/component-library/hero-banner.liquid +104 -0
  13. package/assets/data/component-library/image-gallery.liquid +77 -0
  14. package/assets/data/component-library/image-with-text.liquid +84 -0
  15. package/assets/data/component-library/logo-cloud.liquid +70 -0
  16. package/assets/data/component-library/newsletter-signup.liquid +72 -0
  17. package/assets/data/component-library/pricing-table.liquid +101 -0
  18. package/assets/data/component-library/product-grid.liquid +74 -0
  19. package/assets/data/component-library/rich-text-content.liquid +52 -0
  20. package/assets/data/component-library/stats-counter.liquid +73 -0
  21. package/assets/data/component-library/team-members.liquid +81 -0
  22. package/assets/data/component-library/testimonials-slider.liquid +93 -0
  23. package/assets/data/component-library/video-hero.liquid +82 -0
  24. package/assets/data/data-version.json +19 -0
  25. package/assets/data/design-tokens.csv +57 -0
  26. package/assets/data/liquid-stdlib.csv +134 -0
  27. package/assets/data/schema-library.csv +46 -0
  28. package/assets/data/shopify-best-practices.csv +36 -0
  29. package/assets/data/theme-dna.csv +20 -0
  30. package/assets/scripts/core.py +194 -0
  31. package/assets/scripts/quality-gate.py +179 -0
  32. package/assets/scripts/search.py +72 -0
  33. package/assets/scripts/section-generator.py +202 -0
  34. package/assets/scripts/theme-init.py +181 -0
  35. package/assets/templates/generation-prompt.md +31 -0
  36. package/assets/templates/section-base.liquid +49 -0
  37. package/assets/templates/theme-profile-template.md +21 -0
  38. package/dist/commands/init.d.ts +1 -0
  39. package/dist/commands/init.js +70 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +14 -0
  42. package/dist/utils/copy-assets.d.ts +1 -0
  43. package/dist/utils/copy-assets.js +44 -0
  44. package/dist/utils/detect-platform.d.ts +6 -0
  45. package/dist/utils/detect-platform.js +18 -0
  46. package/package.json +35 -0
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Rebly Sections Generator - Assemble context for Claude to generate Shopify sections
5
+ Usage: python3 section-generator.py "name" "description" [--theme-profile path] [--refine path]
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ import io
11
+ from pathlib import Path
12
+
13
+ # Add scripts dir to path for sibling imports
14
+ _scripts_dir = str(Path(__file__).parent)
15
+ if _scripts_dir not in sys.path:
16
+ sys.path.append(_scripts_dir)
17
+ from core import search, search_component, DATA_DIR
18
+
19
+ TEMPLATE_DIR = Path(__file__).parent.parent / "templates"
20
+
21
+ # UTF-8 stdout
22
+ if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
23
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
24
+
25
+
26
+ def load_theme_profile(path):
27
+ """Load theme-profile.md or return defaults"""
28
+ if path and Path(path).exists():
29
+ return Path(path).read_text(encoding="utf-8")
30
+ return "No theme profile available. Use generic OS2.0 conventions:\n- CSS Variable Prefix: --\n- Section Naming: kebab-case\n- Follow standard OS2.0 patterns"
31
+
32
+
33
+ def load_template(name):
34
+ """Load a template file"""
35
+ path = TEMPLATE_DIR / name
36
+ if path.exists():
37
+ return path.read_text(encoding="utf-8")
38
+ return ""
39
+
40
+
41
+ def search_components(description, max_results=2):
42
+ """Search component library for closest pattern"""
43
+ result = search_component(description, max_results=max_results)
44
+ if "error" in result or not result.get("results"):
45
+ return "No matching component patterns found."
46
+
47
+ output = []
48
+ for r in result["results"]:
49
+ output.append(f"**{r.get('Name', 'Unknown')}** ({r.get('Category', '')}, {r.get('Difficulty', '')})")
50
+ output.append(f"Blocks: {r.get('Blocks Used', '')} | Settings: {r.get('Settings Count', '')}")
51
+ if 'liquid_content' in r:
52
+ output.append(f"```liquid\n{r['liquid_content'][:2000]}\n```")
53
+ output.append("")
54
+ return "\n".join(output)
55
+
56
+
57
+ def search_schema_settings(description, max_results=5):
58
+ """Search schema-library for relevant setting types"""
59
+ result = search(description, domain="schema", max_results=max_results)
60
+ if "error" in result or not result.get("results"):
61
+ return "No matching schema settings found."
62
+
63
+ output = []
64
+ for r in result["results"]:
65
+ output.append(f"- **{r.get('Type', '')}** ({r.get('Category', '')}): {r.get('Returns', '')} — Props: {r.get('Required Props', '')}")
66
+ json_ex = r.get('JSON Example', '')
67
+ if json_ex:
68
+ output.append(f" ```json\n {json_ex[:300]}\n ```")
69
+ return "\n".join(output)
70
+
71
+
72
+ def search_best_practices(description, max_results=5):
73
+ """Search best practices for applicable rules"""
74
+ result = search(description, domain="practices", max_results=max_results)
75
+ if "error" in result or not result.get("results"):
76
+ return "No matching best practices found."
77
+
78
+ output = []
79
+ for r in result["results"]:
80
+ output.append(f"- **[{r.get('Severity', '')}] {r.get('Rule', '')}**: {r.get('Description', '')}")
81
+ do = r.get('Do', '')
82
+ dont = r.get('Dont', '')
83
+ if do:
84
+ output.append(f" - DO: {do}")
85
+ if dont:
86
+ output.append(f" - DON'T: {dont}")
87
+ return "\n".join(output)
88
+
89
+
90
+ def try_ui_ux_bridge(ui_style_keywords):
91
+ """Optional: query ui-ux-pro-max if available"""
92
+ try:
93
+ ux_search_path = Path(__file__).parent.parent.parent.parent / ".claude" / "skills" / "ui-ux-pro-max" / "scripts" / "core.py"
94
+ if not ux_search_path.exists():
95
+ ux_search_path = Path(__file__).parent.parent.parent.parent / ".research" / "ui-ux-pro-max-skill" / "src" / "ui-ux-pro-max" / "scripts" / "core.py"
96
+
97
+ if ux_search_path.exists():
98
+ import importlib.util
99
+ spec = importlib.util.spec_from_file_location("ux_core", ux_search_path)
100
+ ux_module = importlib.util.module_from_spec(spec)
101
+ spec.loader.exec_module(ux_module)
102
+ result = ux_module.search(ui_style_keywords, domain="style", max_results=2)
103
+ if result.get("results"):
104
+ lines = ["**UI/UX Style Recommendations (from ui-ux-pro-max):**"]
105
+ for r in result["results"]:
106
+ lines.append(f"- {r.get('Style Category', '')}: {r.get('Keywords', '')}")
107
+ lines.append(f" Effects: {r.get('Effects & Animation', '')}")
108
+ return "\n".join(lines)
109
+ except Exception:
110
+ pass
111
+ return "N/A — ui-ux-pro-max not available"
112
+
113
+
114
+ def assemble_context(name, description, theme_profile, component, schema, practices, design_recs):
115
+ """Merge all context into generation prompt"""
116
+ template = load_template("generation-prompt.md")
117
+ base_template = load_template("section-base.liquid")
118
+
119
+ if template:
120
+ content = template
121
+ content = content.replace("{{ name }}", name)
122
+ content = content.replace("{{ description }}", description)
123
+ content = content.replace("{{ theme_profile_summary }}", theme_profile)
124
+ content = content.replace("{{ component_pattern_content }}", component)
125
+ content = content.replace("{{ schema_search_results }}", schema)
126
+ content = content.replace("{{ best_practices_results }}", practices)
127
+ content = content.replace("{{ ui_ux_recommendations }}", design_recs)
128
+ base_block = f"```liquid\n{base_template}\n```" if base_template else "See standard OS2.0 section structure"
129
+ content = content.replace("{{ section_base_content }}", base_block)
130
+ return content
131
+
132
+ # Fallback: manual assembly
133
+ return f"""# Generate Shopify Section: {name}
134
+
135
+ ## Description
136
+ {description}
137
+
138
+ ## Theme Context
139
+ {theme_profile}
140
+
141
+ ## Closest Pattern Template
142
+ {component}
143
+
144
+ ## Relevant Schema Settings
145
+ {schema}
146
+
147
+ ## Applicable Best Practices
148
+ {practices}
149
+
150
+ ## Design Recommendations
151
+ {design_recs}
152
+
153
+ ## Base Template
154
+ ```liquid
155
+ {base_template}
156
+ ```
157
+
158
+ ## Requirements
159
+ - OS2.0 compliant (presets, @app blocks, block.shopify_attributes)
160
+ - Scoped CSS via #section-{{{{ section.id }}}}
161
+ - Responsive (mobile-first)
162
+ - Accessible (semantic HTML, aria-labels, alt text)
163
+ - Performance (lazy-load non-hero images, no Liquid in CSS/JS)
164
+ - Use {{% render %}} not {{% include %}}
165
+ """
166
+
167
+
168
+ def main():
169
+ parser = argparse.ArgumentParser(description="Rebly Sections Generator")
170
+ parser.add_argument("name", help="Section name (e.g., 'hero', 'pricing-table')")
171
+ parser.add_argument("description", help="Section description")
172
+ parser.add_argument("--theme-profile", "-t", default=None, help="Path to theme-profile.md")
173
+ parser.add_argument("--refine", "-r", default=None, help="Path to existing section.liquid to refine")
174
+ args = parser.parse_args()
175
+
176
+ theme = load_theme_profile(args.theme_profile)
177
+ component = search_components(f"{args.name} {args.description}")
178
+ schema = search_schema_settings(args.description)
179
+ practices = search_best_practices(args.description)
180
+ design_recs = try_ui_ux_bridge(args.name + " " + args.description)
181
+
182
+ if args.refine:
183
+ refine_path = Path(args.refine)
184
+ try:
185
+ existing = refine_path.read_text(encoding="utf-8") if refine_path.exists() else None
186
+ except OSError:
187
+ existing = None
188
+ if existing:
189
+ try:
190
+ from quality_gate import run_quality_gate
191
+ gate_result = run_quality_gate(args.refine)
192
+ refine_context = f"\n## Existing Section (for refinement)\n```liquid\n{existing[:3000]}\n```\n\n## Quality Gate Results\n{gate_result}\n\nPlease fix the issues identified above while preserving the section's functionality."
193
+ component += refine_context
194
+ except ImportError:
195
+ component += f"\n## Existing Section (for refinement)\n```liquid\n{existing[:3000]}\n```"
196
+
197
+ context = assemble_context(args.name, args.description, theme, component, schema, practices, design_recs)
198
+ print(context)
199
+
200
+
201
+ if __name__ == "__main__":
202
+ main()
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Rebly Sections Theme Init - Scan Shopify theme and generate profile.
5
+ Usage: python3 theme-init.py /path/to/theme [--output theme-profile.md]
6
+ Stdlib only. No external dependencies.
7
+ """
8
+
9
+ import argparse
10
+ import csv
11
+ import json
12
+ import re
13
+ import sys
14
+ from pathlib import Path
15
+ from datetime import date
16
+
17
+ DATA_DIR = Path(__file__).parent.parent / "data"
18
+
19
+
20
+ def detect_theme_info(theme_path: Path) -> dict:
21
+ """Read config/settings_schema.json for theme_info block."""
22
+ schema_path = theme_path / "config" / "settings_schema.json"
23
+ defaults = {"name": "Unknown", "version": "0.0.0", "author": "Unknown"}
24
+ if not schema_path.exists():
25
+ return defaults
26
+ try:
27
+ data = json.loads(schema_path.read_text(encoding="utf-8"))
28
+ if not isinstance(data, list):
29
+ return defaults
30
+ for item in data:
31
+ if isinstance(item, dict) and "theme_info" in item:
32
+ info = item["theme_info"]
33
+ return {"name": info.get("theme_name", "Unknown"),
34
+ "version": info.get("theme_version", "0.0.0"),
35
+ "author": info.get("theme_author", "Unknown")}
36
+ first = data[0] if data else {}
37
+ if isinstance(first, dict) and "name" in first:
38
+ return {"name": first["name"],
39
+ "version": first.get("theme_version", "0.0.0"),
40
+ "author": first.get("theme_author", "Unknown")}
41
+ except (json.JSONDecodeError, KeyError, IndexError):
42
+ pass
43
+ return defaults
44
+
45
+
46
+ def scan_css_variables(theme_path: Path, max_files: int = 50) -> dict:
47
+ """Scan assets/*.css for CSS custom property prefixes, return top 10 by count."""
48
+ assets_dir = theme_path / "assets"
49
+ if not assets_dir.exists():
50
+ return {}
51
+ var_pattern = re.compile(r"--([\w]+)-")
52
+ counts: dict = {}
53
+ for css_file in list(assets_dir.glob("*.css"))[:max_files]:
54
+ try:
55
+ content = css_file.read_text(encoding="utf-8", errors="ignore")
56
+ for m in var_pattern.finditer(content):
57
+ p = m.group(1)
58
+ counts[p] = counts.get(p, 0) + 1
59
+ except Exception:
60
+ continue
61
+ return dict(sorted(counts.items(), key=lambda x: x[1], reverse=True)[:10])
62
+
63
+
64
+ def analyze_sections(theme_path: Path) -> dict:
65
+ """List sections, extract naming conventions, count files."""
66
+ sections_dir = theme_path / "sections"
67
+ if not sections_dir.exists():
68
+ return {"count": 0, "patterns": [], "files": []}
69
+ files = [f.stem for f in sections_dir.glob("*.liquid")]
70
+ patterns: set = set()
71
+ for name in files:
72
+ if name.startswith("main-"):
73
+ patterns.add("main-* prefix")
74
+ if name.startswith("featured-"):
75
+ patterns.add("featured-* prefix")
76
+ if "-" in name:
77
+ patterns.add("kebab-case")
78
+ elif "_" in name:
79
+ patterns.add("snake_case")
80
+ return {"count": len(files), "patterns": sorted(patterns), "files": files[:20]}
81
+
82
+
83
+ def match_theme_dna(theme_info: dict, dna_path: Path) -> dict | None:
84
+ """Match detected theme against theme-dna.csv by name then author+keyword."""
85
+ if not dna_path.exists():
86
+ return None
87
+ name_l = theme_info["name"].lower()
88
+ author_l = theme_info["author"].lower()
89
+ try:
90
+ with open(dna_path, "r", encoding="utf-8") as f:
91
+ rows = list(csv.DictReader(f))
92
+ for row in rows:
93
+ if name_l in row.get("Theme", "").lower():
94
+ return row
95
+ for row in rows:
96
+ if (author_l in row.get("Publisher", "").lower()
97
+ and any(name_l in kw for kw in row.get("Keywords", "").lower().split(","))):
98
+ return row
99
+ except Exception:
100
+ pass
101
+ return None
102
+
103
+
104
+ def generate_profile(theme_info: dict, css_vars: dict, sections: dict,
105
+ dna: dict | None, output_path: str | None) -> str:
106
+ """Build and optionally write theme-profile.md content."""
107
+ d = dna or {}
108
+ top_prefix = f"--{list(css_vars.keys())[0]}-" if css_vars else "--"
109
+ detected_colors = ", ".join(f"--{k}-*" for k in list(css_vars.keys())[:5]) or "Not detected"
110
+ patterns_str = ", ".join(sections["patterns"]) if sections["patterns"] else "N/A"
111
+
112
+ lines = [
113
+ f"# Theme Profile: {theme_info['name']}",
114
+ f"Generated: {date.today()} | Version: {theme_info['version']} | Author: {theme_info['author']}",
115
+ "",
116
+ "## Conventions",
117
+ f"- CSS Variable Prefix: `{d.get('CSS Variable Prefix', top_prefix)}`" + ("" if dna else " (detected)"),
118
+ f"- Section Naming: {d.get('Section Naming', 'kebab-case')}",
119
+ f"- Block Convention: {d.get('Block Conventions', 'standard OS2.0')}",
120
+ "",
121
+ "## Design Tokens (from theme)",
122
+ f"- Color Scheme: {d.get('Color Scheme Pattern', detected_colors)}",
123
+ f"- Typography: {d.get('Typography Pattern', 'Use theme defaults')}",
124
+ f"- Spacing: {d.get('Spacing Pattern', 'Use theme defaults')}",
125
+ "",
126
+ "## Section Patterns",
127
+ f"- Detected sections: {sections['count']}",
128
+ f"- Naming patterns: {patterns_str}",
129
+ "",
130
+ "## Recommendations",
131
+ ]
132
+ if dna:
133
+ lines += [
134
+ f"- Follow {theme_info['name']} naming conventions",
135
+ f"- Use `{d.get('CSS Variable Prefix', '--')}` prefix for CSS variables",
136
+ f"- Match {d.get('Color Scheme Pattern', 'existing')} color scheme pattern",
137
+ ]
138
+ else:
139
+ lines += [
140
+ "- Use kebab-case for section file names",
141
+ "- Scope CSS with #shopify-section-{{ section.id }}",
142
+ "- Follow OS2.0 standards (presets, @app blocks)",
143
+ ]
144
+
145
+ content = "\n".join(lines) + "\n"
146
+ if output_path:
147
+ Path(output_path).write_text(content, encoding="utf-8")
148
+ print(f"Theme profile saved to: {output_path}")
149
+ return content
150
+
151
+
152
+ def main() -> None:
153
+ parser = argparse.ArgumentParser(
154
+ description="Rebly Sections Theme Init — scan a Shopify theme and generate a profile.")
155
+ parser.add_argument("theme_path", help="Path to Shopify theme directory")
156
+ parser.add_argument("--output", "-o", default=None, help="Output path for theme-profile.md")
157
+ args = parser.parse_args()
158
+
159
+ theme_path = Path(args.theme_path)
160
+ if not theme_path.exists():
161
+ print(f"Error: Theme path not found: {theme_path}", file=sys.stderr)
162
+ sys.exit(1)
163
+
164
+ theme_info = detect_theme_info(theme_path)
165
+ print(f"Detected theme: {theme_info['name']} v{theme_info['version']} by {theme_info['author']}")
166
+
167
+ css_vars = scan_css_variables(theme_path)
168
+ print(f"CSS variable prefixes: {', '.join(f'--{k}-({v})' for k, v in list(css_vars.items())[:5]) or 'none detected'}")
169
+
170
+ sections = analyze_sections(theme_path)
171
+ print(f"Sections found: {sections['count']}")
172
+
173
+ dna_match = match_theme_dna(theme_info, DATA_DIR / "theme-dna.csv")
174
+ print(f"Theme DNA match: {dna_match.get('Theme', 'Unknown')}" if dna_match else "No exact DNA match — using generic OS2.0 profile")
175
+
176
+ profile = generate_profile(theme_info, css_vars, sections, dna_match, args.output or "theme-profile.md")
177
+ print(f"\n{profile}")
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
@@ -0,0 +1,31 @@
1
+ # Generate Shopify Section: {{ name }}
2
+
3
+ ## Description
4
+ {{ description }}
5
+
6
+ ## Theme Context
7
+ {{ theme_profile_summary }}
8
+
9
+ ## Closest Pattern Template
10
+ {{ component_pattern_content }}
11
+
12
+ ## Relevant Schema Settings
13
+ {{ schema_search_results }}
14
+
15
+ ## Applicable Best Practices
16
+ {{ best_practices_results }}
17
+
18
+ ## Design Recommendations
19
+ {{ ui_ux_recommendations }}
20
+
21
+ ## Base Template
22
+ {{ section_base_content }}
23
+
24
+ ## Requirements
25
+ - OS2.0 compliant (presets, @app blocks, block.shopify_attributes)
26
+ - Scoped CSS via #section-{{ section.id }}
27
+ - Responsive (mobile-first)
28
+ - Accessible (semantic HTML, aria-labels, alt text)
29
+ - Performance (lazy-load non-hero images, no Liquid in CSS/JS)
30
+ - Use {% render %} not {% include %}
31
+ - Include {{ block.shopify_attributes }} on ALL block wrappers
@@ -0,0 +1,49 @@
1
+ {%- comment -%}
2
+ rebly-sections: {{ name }}
3
+ Generated: {{ date }}
4
+ Theme: {{ theme_name }}
5
+ {%- endcomment -%}
6
+
7
+ {%- liquid
8
+ assign section_id = section.id
9
+ -%}
10
+
11
+ <section
12
+ id="section-{{ section_id }}"
13
+ class="rebly-{{ slug }} section-{{ section_id }}"
14
+ >
15
+ <div class="page-width">
16
+ {%- comment -%} Section content goes here {%- endcomment -%}
17
+
18
+ {%- for block in section.blocks -%}
19
+ {%- case block.type -%}
20
+ {%- when '@app' -%}
21
+ <div {{ block.shopify_attributes }}>
22
+ {% render block %}
23
+ </div>
24
+ {%- comment -%} Custom block types go here {%- endcomment -%}
25
+ {%- endcase -%}
26
+ {%- endfor -%}
27
+ </div>
28
+ </section>
29
+
30
+ <style>
31
+ #section-{{ section_id }} {
32
+ padding: var(--section-padding-top) 0 var(--section-padding-bottom);
33
+ }
34
+ </style>
35
+
36
+ {% schema %}
37
+ {
38
+ "name": "{{ display_name }}",
39
+ "tag": "section",
40
+ "class": "section",
41
+ "settings": [],
42
+ "blocks": [
43
+ { "type": "@app" }
44
+ ],
45
+ "presets": [
46
+ { "name": "{{ display_name }}" }
47
+ ]
48
+ }
49
+ {% endschema %}
@@ -0,0 +1,21 @@
1
+ # Theme Profile: {theme_name}
2
+ Generated: {date} | Version: {version} | Author: {author}
3
+
4
+ ## Conventions
5
+ - CSS Variable Prefix: `{css_prefix}`
6
+ - Section Naming: {naming_convention}
7
+ - Block Convention: {block_convention}
8
+
9
+ ## Design Tokens (from theme)
10
+ - Color Scheme: {color_scheme}
11
+ - Typography: {typography}
12
+ - Spacing: {spacing}
13
+
14
+ ## Section Patterns
15
+ - Detected sections: {section_count}
16
+ - Naming patterns: {naming_patterns}
17
+
18
+ ## Recommendations
19
+ - {rec_1}
20
+ - {rec_2}
21
+ - {rec_3}
@@ -0,0 +1 @@
1
+ export declare function init(cwd: string): void;
@@ -0,0 +1,70 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { execSync } from 'node:child_process';
5
+ import { detectPlatforms } from '../utils/detect-platform.js';
6
+ import { copyAssets } from '../utils/copy-assets.js';
7
+ export function init(cwd) {
8
+ console.log('rebly-sections installer\n');
9
+ const targets = detectPlatforms(cwd);
10
+ if (targets.length === 0) {
11
+ console.log('No .claude/ or .agent/ directory detected.');
12
+ console.log('');
13
+ console.log('To use with Claude Code:');
14
+ console.log(' mkdir -p .claude/skills');
15
+ console.log(' npx rebly-sections init');
16
+ console.log('');
17
+ console.log('To use with Antigravity Kit:');
18
+ console.log(' mkdir -p .agent/skills');
19
+ console.log(' npx rebly-sections init');
20
+ process.exit(1);
21
+ }
22
+ // In dist/commands/init.js -> assets is at ../../assets/
23
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
24
+ const assetsDir = join(scriptDir, '..', '..', 'assets');
25
+ if (!existsSync(assetsDir)) {
26
+ console.error('Error: Assets directory not found. Package may be corrupted.');
27
+ process.exit(1);
28
+ }
29
+ const commandsDir = join(assetsDir, 'commands');
30
+ for (const target of targets) {
31
+ console.log(`Installing to ${target.platform}...`);
32
+ console.log(` Target: ${target.targetDir}`);
33
+ try {
34
+ const fileCount = copyAssets(assetsDir, target.targetDir);
35
+ console.log(` Copied ${fileCount} files`);
36
+ }
37
+ catch (err) {
38
+ console.error(` Error: ${err instanceof Error ? err.message : err}`);
39
+ process.exit(1);
40
+ }
41
+ // Install slash commands to .claude/commands/ or .agent/commands/
42
+ if (existsSync(commandsDir)) {
43
+ const platformDir = join(cwd, target.platform === 'claude' ? '.claude' : '.agent');
44
+ const targetCommandsDir = join(platformDir, 'commands');
45
+ try {
46
+ const cmdCount = copyAssets(commandsDir, targetCommandsDir);
47
+ console.log(` Installed ${cmdCount} slash commands (/theme-init, /section)\n`);
48
+ }
49
+ catch (err) {
50
+ console.error(` Warning: Could not install slash commands: ${err instanceof Error ? err.message : err}\n`);
51
+ }
52
+ }
53
+ }
54
+ console.log('Checking Python...');
55
+ try {
56
+ const version = execSync('python3 --version', { encoding: 'utf-8' }).trim();
57
+ console.log(` ${version}`);
58
+ }
59
+ catch {
60
+ console.log(' Warning: python3 not found. Python 3.x is required for search scripts.');
61
+ }
62
+ console.log('\nInstallation complete!\n');
63
+ console.log('Usage:');
64
+ console.log(' /theme-init — Scan your Shopify theme');
65
+ console.log(' /section — Generate a new section');
66
+ console.log('');
67
+ console.log('Search:');
68
+ console.log(' python3 scripts/search.py "hero section" --component');
69
+ console.log(' python3 scripts/search.py "image_picker" --domain schema');
70
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { init } from './commands/init.js';
3
+ const args = process.argv.slice(2);
4
+ const command = args[0];
5
+ if (command === 'init') {
6
+ init(process.cwd());
7
+ }
8
+ else {
9
+ console.log('rebly-sections - Shopify section AI skill installer\n');
10
+ console.log('Usage: npx rebly-sections init\n');
11
+ console.log('Commands:');
12
+ console.log(' init Install rebly-sections skill to detected platform(s)');
13
+ process.exit(command ? 1 : 0);
14
+ }
@@ -0,0 +1 @@
1
+ export declare function copyAssets(assetsDir: string, targetDir: string): number;
@@ -0,0 +1,44 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const SKIP_PATTERNS = ['.gitkeep', '__pycache__', '.pyc', '.DS_Store', 'commands'];
4
+ function shouldSkip(name) {
5
+ return SKIP_PATTERNS.some(p => name.includes(p));
6
+ }
7
+ export function copyAssets(assetsDir, targetDir) {
8
+ if (!existsSync(assetsDir)) {
9
+ throw new Error(`Assets directory not found: ${assetsDir}`);
10
+ }
11
+ if (!existsSync(targetDir)) {
12
+ mkdirSync(targetDir, { recursive: true });
13
+ }
14
+ let fileCount = 0;
15
+ const entries = readdirSync(assetsDir, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ if (shouldSkip(entry.name))
18
+ continue;
19
+ const srcPath = join(assetsDir, entry.name);
20
+ const destPath = join(targetDir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ cpSync(srcPath, destPath, { recursive: true, filter: (src) => !shouldSkip(src) });
23
+ fileCount += countFiles(destPath);
24
+ }
25
+ else {
26
+ cpSync(srcPath, destPath);
27
+ fileCount++;
28
+ }
29
+ }
30
+ return fileCount;
31
+ }
32
+ function countFiles(dir) {
33
+ let count = 0;
34
+ const entries = readdirSync(dir, { withFileTypes: true });
35
+ for (const entry of entries) {
36
+ if (entry.isDirectory()) {
37
+ count += countFiles(join(dir, entry.name));
38
+ }
39
+ else {
40
+ count++;
41
+ }
42
+ }
43
+ return count;
44
+ }
@@ -0,0 +1,6 @@
1
+ export type Platform = 'claude' | 'antigravity';
2
+ export interface PlatformTarget {
3
+ platform: Platform;
4
+ targetDir: string;
5
+ }
6
+ export declare function detectPlatforms(cwd: string): PlatformTarget[];
@@ -0,0 +1,18 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export function detectPlatforms(cwd) {
4
+ const targets = [];
5
+ if (existsSync(join(cwd, '.claude'))) {
6
+ targets.push({
7
+ platform: 'claude',
8
+ targetDir: join(cwd, '.claude', 'skills', 'rebly-sections')
9
+ });
10
+ }
11
+ if (existsSync(join(cwd, '.agent'))) {
12
+ targets.push({
13
+ platform: 'antigravity',
14
+ targetDir: join(cwd, '.agent', 'skills', 'rebly-sections')
15
+ });
16
+ }
17
+ return targets;
18
+ }