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,46 @@
1
+ No,Type,Category,Returns,Keywords,Required Props,Optional Props,Default Behavior,JSON Example,Constraints,Notes
2
+ 1,checkbox,basic,boolean,"checkbox, boolean, toggle, true, false, on, off","type, id, label","default","Defaults to false if not set","{""type"":""checkbox"",""id"":""show_title"",""label"":""Show title"",""default"":true}","default must be boolean","Simple true/false toggle"
3
+ 2,number,basic,number,"number, integer, numeric, count, quantity","type, id, label","default, min, max, step, placeholder","No default if omitted","{""type"":""number"",""id"":""columns"",""label"":""Columns"",""default"":3,""min"":1,""max"":6}","Must be a finite number; min/max/step are optional","Use for numeric inputs like column counts or sizes"
4
+ 3,range,basic,number,"range, slider, min, max, step, unit, numeric","type, id, label, min, max, step","default, unit","Returns numeric value within min-max bounds","{""type"":""range"",""id"":""opacity"",""label"":""Opacity"",""min"":0,""max"":100,""step"":1,""default"":80,""unit"":""%""}","min/max/step are required; unit is display-only","Renders as a range slider in theme editor"
5
+ 4,select,basic,string,"select, dropdown, options, choice, enum, list","type, id, label, options","default","Returns value of selected option","{""type"":""select"",""id"":""layout"",""label"":""Layout"",""options"":[{""value"":""grid"",""label"":""Grid""},{""value"":""list"",""label"":""List""}],""default"":""grid""}","options array required; each option needs value and label","Renders as dropdown select"
6
+ 5,text,basic,string,"text, string, input, single line, short text, label","type, id, label","default, placeholder, info","Returns empty string if blank","{""type"":""text"",""id"":""heading"",""label"":""Heading"",""default"":""Welcome"",""placeholder"":""Enter heading""}","Single-line text; no HTML allowed","Use for short string inputs like headings or labels"
7
+ 6,textarea,basic,string,"textarea, multiline, long text, paragraph, string","type, id, label","default, placeholder, info","Returns empty string if blank","{""type"":""textarea"",""id"":""description"",""label"":""Description"",""default"":""Enter your text here""}","Multi-line plain text; no HTML rendered","Use for longer text content without formatting"
8
+ 7,radio,basic,string,"radio, radio button, choice, options, visual select","type, id, label, options","default","Returns value of selected radio option","{""type"":""radio"",""id"":""alignment"",""label"":""Alignment"",""options"":[{""value"":""left"",""label"":""Left""},{""value"":""right"",""label"":""Right""}],""default"":""left""}","options array required; each option needs value and label","Renders as visual radio buttons; similar to select"
9
+ 8,text_alignment,basic,string,"text alignment, align, left, center, right, justify","type, id, label","default","Returns left/center/right/justify string","{""type"":""text_alignment"",""id"":""text_align"",""label"":""Text alignment"",""default"":""left""}","Returns one of: left, center, right, justify","Renders as alignment picker with visual icons"
10
+ 9,color,color,string,"color, colour, hex, picker, rgba, background, foreground","type, id, label","default","Returns hex string or empty if not set","{""type"":""color"",""id"":""button_color"",""label"":""Button color"",""default"":""#1a1a1a""}","Stored as hex; supports rgba via color_to_rgb filter","Renders color picker; use color filters for manipulation"
11
+ 10,color_background,color,string,"color background, gradient, solid, linear, radial, color","type, id, label","default","Returns CSS-compatible color or gradient string","{""type"":""color_background"",""id"":""bg_color"",""label"":""Background color"",""default"":""#ffffff""}","Supports solid colors and CSS gradients","Use for background colors that may need gradient support"
12
+ 11,color_scheme,color,string,"color scheme, scheme, palette, theme colors, global","type, id, label","default","Returns reference to a color scheme object","{""type"":""color_scheme"",""id"":""color_scheme"",""label"":""Color scheme"",""default"":""scheme-1""}","Requires color_scheme_group defined in theme settings","References globally defined color schemes"
13
+ 12,color_scheme_group,color,object,"color scheme group, palette group, schemes, global colors","type, id, label","definition","Returns group containing multiple color scheme definitions","{""type"":""color_scheme_group"",""id"":""color_scheme_group"",""label"":""Color scheme group""}","Used in theme settings to define available schemes","Top-level definition; referenced by color_scheme type"
14
+ 13,image_picker,media,object,"image, photo, picture, media, upload, asset","type, id, label","default, info","Returns image object or null","{""type"":""image_picker"",""id"":""hero_image"",""label"":""Hero image"",""info"":""Recommended 1920x600px""}","Returns Shopify image object; use image_url filter","Access image via settings.image_picker_id | image_url"
15
+ 14,video,media,object,"video, shopify video, hosted video, media, mp4","type, id, label","info","Returns Shopify-hosted video object or null","{""type"":""video"",""id"":""background_video"",""label"":""Background video""}","Only supports Shopify-hosted videos; use video_tag filter","Use video_tag filter to render; different from video_url"
16
+ 15,video_url,media,string,"video url, youtube, vimeo, external video, embed, url","type, id, label","accept, placeholder, info","Returns URL string for YouTube or Vimeo","{""type"":""video_url"",""id"":""video_link"",""label"":""Video URL"",""accept"":[""youtube"",""vimeo""],""placeholder"":""Enter YouTube or Vimeo URL""}","accept array specifies allowed providers: youtube/vimeo","Use external_video_tag or external_video_url filter"
17
+ 16,richtext,content,string,"richtext, rich text, html, formatted, wysiwyg, content","type, id, label","default, info","Returns HTML string with block-level elements","{""type"":""richtext"",""id"":""content"",""label"":""Content"",""default"":""<p>Add your content here</p>""}","Default must be valid HTML with block elements (p/ul/ol/h1-h6)","Supports p/ul/ol/h1-h6/br/strong/em/a tags"
18
+ 17,inline_richtext,content,string,"inline richtext, inline, rich text, formatted, inline html","type, id, label","default, info","Returns HTML string without block-level elements","{""type"":""inline_richtext"",""id"":""subtitle"",""label"":""Subtitle"",""default"":""<strong>Bold text</strong> and normal text""}","No block-level elements allowed (no p/ul/h1 etc)","Use for inline formatted text within headings or captions"
19
+ 18,html,content,string,"html, raw html, markup, code, custom html, embed","type, id, label","default, placeholder, info","Returns raw HTML string","{""type"":""html"",""id"":""custom_html"",""label"":""Custom HTML"",""placeholder"":""<div>Your HTML</div>""}","No sanitization; raw output; XSS risk if user-generated","Renders raw HTML; use with caution for security"
20
+ 19,liquid,content,string,"liquid, liquid code, template, dynamic, code, markup","type, id, label","default, info","Returns Liquid code string to be rendered","{""type"":""liquid"",""id"":""custom_liquid"",""label"":""Custom Liquid"",""default"":""{{ shop.name }}""}","Must be valid Liquid; executed in section context","Powerful but risky; allows full Liquid template access"
21
+ 20,font_picker,content,object,"font, typography, typeface, google fonts, system font, font family","type, id, label","default, info","Returns font object with family/fallback/weight/style","{""type"":""font_picker"",""id"":""body_font"",""label"":""Body font"",""default"":""sans-serif""}","Returns font object; use font_face/font_url/font_modify filters","Access via settings.body_font.family for CSS font-family"
22
+ 21,url,resource-single,string,"url, link, href, external link, internal link, path","type, id, label","default, info","Returns URL string or null","{""type"":""url"",""id"":""button_link"",""label"":""Button link"",""default"":""/collections/all""}","Returns relative or absolute URL string","Supports internal Shopify paths and external URLs"
23
+ 22,collection,resource-single,object,"collection, product collection, catalog, group","type, id, label","info","Returns collection object or null","{""type"":""collection"",""id"":""featured_collection"",""label"":""Collection""}","Returns full Shopify collection object","Access products via collection.products array"
24
+ 23,product,resource-single,object,"product, item, variant, merchandise, single product","type, id, label","info","Returns product object or null","{""type"":""product"",""id"":""featured_product"",""label"":""Product""}","Returns full Shopify product object with variants","Access via settings.product_id with product properties"
25
+ 24,blog,resource-single,object,"blog, posts, articles, journal, news","type, id, label","info","Returns blog object or null","{""type"":""blog"",""id"":""featured_blog"",""label"":""Blog""}","Returns Shopify blog object; access articles via blog.articles","Use for blog section featuring latest posts"
26
+ 25,page,resource-single,object,"page, static page, content page, about","type, id, label","info","Returns page object or null","{""type"":""page"",""id"":""info_page"",""label"":""Page""}","Returns Shopify page object with content property","Access page.content for rendered HTML content"
27
+ 26,article,resource-single,object,"article, blog post, post, news item","type, id, label","info","Returns article object or null","{""type"":""article"",""id"":""featured_article"",""label"":""Article""}","Returns single Shopify article object","Access article.title/content/image/author etc"
28
+ 27,link_list,resource-single,object,"link list, menu, navigation, linklist, nav, links","type, id, label","default, info","Returns linklist object with links array","{""type"":""link_list"",""id"":""menu"",""label"":""Menu"",""default"":""main-menu""}","Returns Shopify linklist/menu object; access via links array","Iterate links array: for link in linklists[settings.menu].links"
29
+ 28,metaobject,resource-single,object,"metaobject, custom data, structured data, reference","type, id, label","info","Returns metaobject instance or null","{""type"":""metaobject"",""id"":""featured_meta"",""label"":""Featured metaobject""}","Requires metaobject definition to exist in store","Access fields via metaobject.field_key.value"
30
+ 29,collection_list,resource-list,array,"collection list, multiple collections, array, group, catalog","type, id, label","limit","Returns array of collection objects (max 20)","{""type"":""collection_list"",""id"":""collections"",""label"":""Collections"",""limit"":6}","Max 20 items by default; limit prop caps selection","Iterate with for collection in settings.collections"
31
+ 30,product_list,resource-list,array,"product list, multiple products, array, items, selection","type, id, label","limit","Returns array of product objects (max 20)","{""type"":""product_list"",""id"":""products"",""label"":""Products"",""limit"":4}","Max 20 items; limit prop caps selection","Iterate with for product in settings.products"
32
+ 31,article_list,resource-list,array,"article list, multiple articles, posts, blog posts, array","type, id, label","limit","Returns array of article objects (max 20)","{""type"":""article_list"",""id"":""articles"",""label"":""Articles"",""limit"":3}","Max 20 items; limit prop caps selection","Iterate with for article in settings.articles"
33
+ 32,metaobject_list,resource-list,array,"metaobject list, multiple metaobjects, array, custom data","type, id, label","limit","Returns array of metaobject instances (max 20)","{""type"":""metaobject_list"",""id"":""items"",""label"":""Items"",""limit"":10}","Max 20 items; limit prop caps selection","Requires metaobject definition in store"
34
+ 33,name,schema-property,string,"name, section name, display name, label, title","name","","Section name shown in theme editor","{""name"":""Hero Banner""}","Required string; shown as section label in editor","Displayed in Shopify theme editor section list"
35
+ 34,tag,schema-property,string,"tag, html tag, wrapper, element, section tag, div","tag","","Defaults to div if not specified","{""tag"":""section""}","Valid HTML5 element tags: div/section/article/aside/header/footer","Sets the HTML wrapper element for the section"
36
+ 35,class,schema-property,string,"class, css class, html class, wrapper class, styling","class","","No class added if not specified","{""class"":""featured-section spaced-section""}","Space-separated CSS class names string","Added to the section wrapper element's class attribute"
37
+ 36,limit,schema-property,number,"limit, max instances, section limit, count","limit","","No limit by default","{""limit"":1}","Integer; restricts how many times section can be added to template","Use limit: 1 for sections that should appear only once"
38
+ 37,max_blocks,schema-property,number,"max blocks, blocks limit, maximum, block count","max_blocks","","Default is 50 blocks","{""max_blocks"":16}","Integer; limits how many blocks can be added to section","Controls maximum number of blocks in section"
39
+ 38,presets,schema-property,array,"presets, defaults, initial, theme editor preset, add section","presets","","No presets means section cannot be added via theme editor","{""presets"":[{""name"":""Featured Product""}]}","Array of preset objects with name property; enables Add section","Without presets section only usable if in template JSON"
40
+ 39,blocks,schema-property,array,"blocks, block types, repeatable, dynamic blocks, content blocks","blocks","","No blocks if omitted","{""blocks"":[{""type"":""text"",""name"":""Text"",""settings"":[{""type"":""richtext"",""id"":""text"",""label"":""Text""}]}]}","Array of block type objects with type/name/settings","Define repeatable content units within a section"
41
+ 40,enabled_on,schema-property,object,"enabled on, template restriction, allow, whitelist, templates","enabled_on","","Available everywhere if not specified","{""enabled_on"":{""templates"":[""index"",""product""]}}","Object with templates array listing allowed template types","Restricts section to specific page template types"
42
+ 41,disabled_on,schema-property,object,"disabled on, template restriction, deny, blacklist, exclude","disabled_on","","No restrictions if not specified","{""disabled_on"":{""templates"":[""password""]}}","Object with templates array listing disallowed template types","Prevents section from appearing on specific template types"
43
+ 42,settings,schema-property,array,"settings, configuration, inputs, section settings, options","settings","","Empty settings array if omitted","{""settings"":[{""type"":""text"",""id"":""heading"",""label"":""Heading""}]}","Array of setting type objects; each requires type/id/label","Main container for all section-level input settings"
44
+ 43,default,schema-property,object,"default, initial values, preset defaults, starting configuration","default","","No default configuration if omitted","{""default"":{""settings"":{""heading"":""Welcome""},""blocks"":[{""type"":""text"",""settings"":{""text"":""Hello""}}]}}","Object with settings and blocks keys for initial values","Sets initial values when section is added via presets"
45
+ 44,locales,schema-property,object,"locales, translations, i18n, internationalization, language","locales","","No translations if omitted","{""locales"":{""en"":{""title"":""Title""},""fr"":{""title"":""Titre""}}}","Nested object by locale code then translation key","Provides translated strings for section labels"
46
+ 45,templates,schema-property,array,"templates, restrict, page types, template types, whitelist","templates","","Available on all templates if omitted","{""templates"":[""index"",""product"",""collection""]}","Array of template type strings","Restricts which templates the section appears on"
@@ -0,0 +1,36 @@
1
+ No,Rule,Category,Severity,Keywords,Description,Do,Dont,Code Example
2
+ 1,Use JSON templates,os2-architecture,critical,"json templates os2 sections theme editor","OS2.0 themes must use JSON templates for section-based customization","Use .json template files in /templates/","Use .liquid template files for page templates",""
3
+ 2,Presets required,os2-architecture,critical,"presets theme editor add section visibility","Every section must have non-empty presets array for Theme Editor visibility","Include presets: [{name: 'Section Name'}]","Omit presets array from schema",""
4
+ 3,Use render not include,performance,critical,"render include snippet partial performance deprecated","render tag is isolated and faster; include is deprecated and shares scope","{% render 'snippet' %}","{% include 'snippet' %}",""
5
+ 4,block.shopify_attributes required,schema,critical,"block attributes theme editor interaction click targeting","All block wrapper elements must include block.shopify_attributes for Theme Editor click targeting","<div {{ block.shopify_attributes }}>","<div class='block'>",""
6
+ 5,App blocks support,os2-architecture,high,"app blocks third-party extension type app","Include @app block type to allow third-party app integrations","blocks: [{ type: '@app' }]","Omit @app from blocks array",""
7
+ 6,Scope CSS to section ID,css,critical,"css scope section id specificity conflict leakage","CSS must be scoped to section ID to prevent style leakage across sections","#shopify-section-{{ section.id }} .title {}",".title { color: red; }",""
8
+ 7,Lazy load images,images,high,"lazy load image performance lcp loading attribute","Use loading=lazy on all images except hero/above-fold to improve performance","<img loading='lazy' src='...' alt='...'>","<img src='...' alt='...'>",""
9
+ 8,Always include alt text,accessibility,critical,"alt text image accessibility screen reader a11y","All images must have descriptive alt text for screen reader accessibility","<img src='...' alt='{{ block.settings.image_alt | escape }}'>","<img src='...'>",""
10
+ 9,Use semantic HTML,accessibility,high,"semantic html heading landmark section article a11y","Use proper semantic HTML elements for accessibility and SEO","<section>, <article>, <h2>, <nav>","<div class='section'>, <div class='heading'>",""
11
+ 10,ARIA labels for interactive elements,accessibility,high,"aria label button link interactive element a11y wcag","Interactive elements without visible text must have aria-label","<button aria-label='Close menu'>X</button>","<button>X</button>",""
12
+ 11,Limit section settings,schema,high,"settings count performance theme editor limit","Sections should have 20 or fewer settings to keep Theme Editor manageable","Group related settings into blocks","Add every possible setting to section-level schema",""
13
+ 12,Use image_picker with width/height,images,high,"image_picker width height aspect ratio responsive","Always pair image_picker with width and height settings for responsive images","Add image_width and image_height number settings","Use fixed pixel dimensions in CSS only",""
14
+ 13,Responsive images with image_url,images,high,"image_url responsive srcset width filter shopify cdn","Use Shopify image_url filter with width parameter for responsive images","{{ image | image_url: width: 800 }}","{{ image.src }}",""
15
+ 14,No hardcoded colors,css,high,"hardcoded color hex rgb css variable token design system","Use CSS variables or design tokens instead of hardcoded color values","color: var(--color-foreground)","color: #333333",""
16
+ 15,Mobile-first CSS,css,high,"mobile first responsive breakpoint media query min-width","Write base styles for mobile, use min-width media queries for larger screens","@media (min-width: 750px) { ... }","@media (max-width: 750px) { ... }",""
17
+ 16,Defer non-critical JavaScript,javascript,high,"defer javascript performance async non-critical load","Non-critical JS should use defer or async attribute to avoid render blocking","<script src='...' defer>","<script src='...'>",""
18
+ 17,Use section.settings not hardcoded text,schema,critical,"section settings hardcoded text content customizable","All user-facing text must come from schema settings, not hardcoded in Liquid","{{ section.settings.heading }}","<h2>Our Products</h2>",""
19
+ 18,Unique section IDs,schema,high,"section id unique namespace conflict css js","Use section.id to namespace CSS and JS to avoid conflicts between section instances","section-{{ section.id }}","my-section",""
20
+ 19,Color scheme support,os2-architecture,high,"color scheme picker background foreground os2 theme editor","Sections should support color_scheme setting for theme-wide color customization","Include color_scheme setting with type: color_scheme","Hardcode background and text colors",""
21
+ 20,Keyboard navigation support,accessibility,high,"keyboard navigation focus tab order interactive a11y wcag","All interactive elements must be keyboard accessible with visible focus states","button:focus-visible { outline: 2px solid; }","button:focus { outline: none; }",""
22
+ 21,Use aspect-ratio CSS,images,high,"aspect ratio css image video embed responsive layout shift","Use CSS aspect-ratio to prevent layout shift while images load","aspect-ratio: 16 / 9","Use padding-top hack for aspect ratio",""
23
+ 22,Limit block types to 16,schema,high,"block types limit maximum theme editor performance","Sections should have no more than 16 block types defined in schema","Keep block types focused and minimal","Add many redundant block type variations",""
24
+ 23,Enable section everywhere,os2-architecture,high,"enabled on templates json section groups everywhere","Sections should work in section groups to appear on any page template","Add section to section groups in JSON templates","Hardcode section to specific template only",""
25
+ 24,Preload hero images,performance,high,"preload hero image lcp performance above-fold critical resource","Hero/above-fold images should use fetchpriority=high and preload link","<img fetchpriority='high' loading='eager' ...>","<img loading='lazy' ...> for hero images",""
26
+ 25,Minify inline JavaScript,javascript,high,"minify javascript inline performance whitespace","Inline JS in sections should be minimal and deferred to external files when possible","Use external JS files loaded via defer","Write large inline <script> blocks in section files",""
27
+ 26,Use content_for_index,os2-architecture,high,"content_for_index homepage sections index json template","Homepage template must use content_for_index tag instead of hardcoded sections","{% content_for_index %}","{% section 'featured-products' %}",""
28
+ 27,Valid JSON schema,schema,critical,"json schema valid syntax parse error theme editor crash","Section schema must be valid JSON or the Theme Editor will fail to load","Validate JSON with a linter before deploying","Write schema JSON without validation",""
29
+ 28,Translatable strings,seo,high,"t translation i18n locale string shopify translate","User-facing strings should use Shopify translation filters for multi-language support","{{ 'sections.hero.title' | t }}","{{ 'Welcome to our store' }}",""
30
+ 29,Structured data for SEO,seo,high,"structured data json-ld schema org seo rich snippets","Add JSON-LD structured data to relevant sections like product and article","<script type='application/ld+json'>...</script>","Skip structured data markup",""
31
+ 30,forloop.index for unique IDs,schema,high,"forloop index unique id accessibility aria html attribute","Use forloop.index to generate unique IDs when rendering blocks in loops","id='tab-{{ forloop.index }}'","id='tab-1' hardcoded",""
32
+ 31,Avoid liquid in CSS files,css,high,"liquid css performance dynamic styles inline style attribute","Avoid using Liquid variables in .css files; use CSS variables set via inline styles","style='--color: {{ settings.color }}'","Liquid in .css asset files",""
33
+ 32,Use paginate for long lists,performance,high,"paginate pagination collection loop performance limit","Use Shopify paginate tag for collection loops to avoid loading all products","{% paginate collection.products by 12 %}","{% for product in collection.products %}",""
34
+ 33,section.blocks loop order,schema,high,"blocks loop order theme editor drag drop sortable","Always loop blocks via section.blocks to respect Theme Editor sort order","{% for block in section.blocks %}","Hardcode block rendering order",""
35
+ 34,Lazy-load videos,performance,high,"video lazy load autoplay performance bandwidth","Videos should be lazy-loaded and not autoplay with sound by default","<video loading='lazy' muted autoplay playsinline>","<video autoplay>",""
36
+ 35,Remove unused assets,performance,high,"unused assets css javascript dead code performance bundle size","Remove unused CSS and JS assets to reduce page weight and improve load time","Audit and remove unused snippet files and assets","Leave unused Liquid snippets and JS files in theme",""
@@ -0,0 +1,20 @@
1
+ No,Theme,Version,Publisher,Keywords,CSS Variable Prefix,Section Naming,Block Conventions,Color Scheme Pattern,Typography Pattern,Spacing Pattern,File Structure Signature,Settings Schema Pattern,Snippet Patterns,Notes
2
+ 1,Dawn,15.x,Shopify,"dawn, shopify, free, os2, popular, latest",--color-,kebab-case with main-* prefix for templates,"@app blocks, type blocks, @theme blocks","color_scheme setting, color_scheme_group in schema","--font-heading-family, --font-body-family","--page-width, section padding vars","sections/main-*.liquid + sections/*.liquid, assets/base.css","color_scheme_group with schemes array, presets in schema","snippets/icon-*.liquid, snippets/price.liquid","Most popular free Shopify theme, OS2.0 reference implementation"
3
+ 2,Dawn,12.x,Shopify,"dawn, shopify, free, os2, v12",--color-,kebab-case with main-* prefix,"@app blocks, type blocks","color_scheme setting, 4 preset schemes","--font-heading-family, --font-body-family","--page-width, section-vertical-padding","sections/main-*.liquid + sections/*.liquid, assets/base.css","color_scheme_group, no dynamic color_scheme","snippets/icon-*.liquid, snippets/price.liquid","OS2.0 older iteration, fewer color scheme options"
4
+ 3,Dawn,8.x,Shopify,"dawn, shopify, free, os2, v8, early",--color-,kebab-case with main-* prefix,"@app blocks limited","Simple color_scheme setting","--font-heading-family, --font-body-family","--page-width basic","sections/main-*.liquid, assets/base.css","Basic OS2.0 schema, limited presets","snippets/icon-*.liquid","Early OS2.0 adoption, limited block types"
5
+ 4,Craft,Latest,Shopify,"craft, shopify, free, artisan, handmade, minimal",--color-,kebab-case,"@app blocks, custom craft blocks","color_scheme with craft palettes","--font-heading-family, --font-body-family","Generous whitespace, --section-padding","sections/*.liquid, assets/craft.css","color_scheme_group, craft-specific presets","snippets/craft-*.liquid","Focused on artisan/handmade stores, editorial layout"
6
+ 5,Sense,Latest,Shopify,"sense, shopify, free, health, beauty, clean",--color-,kebab-case,"@app blocks, standard blocks","color_scheme with clean palettes","--font-heading-family serif options, --font-body-family","Tight spacing, --spacing-sections","sections/*.liquid, assets/sense.css","color_scheme_group, sense presets","snippets/sense-*.liquid","Health/beauty focus, clean minimal aesthetic"
7
+ 6,Refresh,Latest,Shopify,"refresh, shopify, free, modern, bold",--color-,kebab-case,"@app blocks, standard blocks","color_scheme with bold palettes","--font-heading-family bold, --font-body-family","Balanced spacing, --page-width","sections/*.liquid, assets/refresh.css","color_scheme_group, refresh presets","snippets/refresh-*.liquid","Modern design, bold typography choices"
8
+ 7,Ride,Latest,Shopify,"ride, shopify, free, sports, outdoor, adventure",--color-,kebab-case,"@app blocks, video blocks","color_scheme with high contrast options","--font-heading-family bold/extended, --font-body-family","Compact spacing, full-width sections","sections/*.liquid, assets/ride.css","color_scheme_group, ride presets","snippets/ride-*.liquid","Sports/outdoor stores, full-bleed imagery"
9
+ 8,Taste,Latest,Shopify,"taste, shopify, free, food, restaurant, beverage",--color-,kebab-case,"@app blocks, menu blocks","color_scheme with warm palettes","--font-heading-family elegant, --font-body-family","Relaxed spacing, restaurant-style","sections/*.liquid, assets/taste.css","color_scheme_group, taste presets","snippets/taste-*.liquid","Food/restaurant focus, menu-style layouts"
10
+ 9,Studio,Latest,Shopify,"studio, shopify, free, creative, portfolio, artist",--color-,kebab-case,"@app blocks, media blocks","color_scheme with neutral palettes","--font-heading-family display, --font-body-family","Grid-based spacing, portfolio layout","sections/*.liquid, assets/studio.css","color_scheme_group, studio presets","snippets/studio-*.liquid","Creative/portfolio stores, gallery-first design"
11
+ 10,Colorblock,Latest,Shopify,"colorblock, shopify, free, colorful, bold, vibrant",--color-,kebab-case,"@app blocks, color blocks","color_scheme with vivid multicolor palettes","--font-heading-family bold, --font-body-family","Block-based spacing, color sections","sections/*.liquid, assets/colorblock.css","color_scheme_group, colorblock presets","snippets/colorblock-*.liquid","Bold colorful aesthetic, block-based layout"
12
+ 11,Impulse,Latest,Archetype Themes,"impulse, archetype, paid, fashion, apparel, premium",--imp- or --color-,kebab-case with feature-* pattern,"Custom blocks, promotion blocks, announcement","Multiple color schemes with accent colors","Custom font vars, --heading-font, --body-font","Variable spacing, promotional padding","sections/*.liquid, snippets/imp-*.liquid, assets/impulse.css","Custom color_scheme with accent, promotion settings","snippets/imp-*.liquid, snippets/announcement-*.liquid","Feature-rich paid theme, popular for fashion brands"
13
+ 12,Impact,Latest,Maestrooo,"impact, maestrooo, paid, bold, high-conversion, fashion",--impact- or --color-,kebab-case,"Custom blocks, video blocks, countdown","Bold color scheme with accent","--heading-font large/bold, --body-font","Tight promotional spacing, hero-first","sections/*.liquid, assets/impact.css, snippets/impact-*.liquid","Custom schema with conversion-focused settings","snippets/impact-*.liquid, snippets/countdown.liquid","High-conversion focus, bold visual hierarchy"
14
+ 13,Prestige,Latest,Maestrooo,"prestige, maestrooo, paid, luxury, premium, editorial",--prestige- or --color-,kebab-case with editorial patterns,"Custom blocks, media blocks, editorial","Minimal refined color scheme","--heading-font serif/display, --body-font","Generous whitespace, editorial spacing","sections/*.liquid, assets/prestige.css, snippets/prestige-*.liquid","Luxury-focused schema, editorial settings","snippets/prestige-*.liquid, snippets/media-*.liquid","Luxury/editorial brands, white space first"
15
+ 14,Symmetry,Latest,Clean Canvas,"symmetry, clean canvas, paid, versatile, multipurpose",--sym- or --color-,kebab-case,"Standard blocks, custom symmetry blocks","Multiple balanced color schemes","--heading-font, --body-font balanced","Symmetric spacing, grid-based","sections/*.liquid, assets/symmetry.css","Multipurpose schema with many layout options","snippets/sym-*.liquid","Multipurpose paid theme, highly configurable"
16
+ 15,Warehouse,Latest,Maestrooo,"warehouse, maestrooo, paid, large-catalog, b2b, wholesale",--wh- or --color-,kebab-case with catalog patterns,"Product blocks, filter blocks, collection","Functional color scheme","--heading-font, --body-font utility","Dense spacing, catalog-optimized","sections/*.liquid, assets/warehouse.css, snippets/wh-*.liquid","Catalog-focused schema, mega menu settings","snippets/wh-*.liquid, snippets/filter-*.liquid","Large catalog B2B focus, product-first layout"
17
+ 16,Pipeline,Latest,Groupthought,"pipeline, groupthought, paid, minimalist, clean, modern",--pl- or --color-,kebab-case,"Standard blocks, pipeline blocks","Clean minimal color scheme","--heading-font geometric, --body-font","Minimal clean spacing, content-first","sections/*.liquid, assets/pipeline.css","Minimal schema, clean settings structure","snippets/pl-*.liquid","Minimalist design, content-focused layout"
18
+ 17,Generic OS2.0,2.0+,Various,"os2, online store 2, generic, standard, modern",--color-,kebab-case,"@app blocks, type blocks, @theme blocks","color_scheme setting basic","--font-heading-family, --font-body-family","--page-width standard, section padding","sections/*.liquid, assets/*.css","Standard OS2.0 schema with presets","snippets/*.liquid","Baseline OS2.0 defaults for unrecognized modern themes"
19
+ 18,Generic Legacy,1.x,Various,"legacy, os1, vintage, pre-os2, old, classic",--,kebab-case or underscores,No @app blocks,"Basic color settings, no color_scheme","Custom font vars or inline styles","Fixed widths, basic padding","sections/*.liquid basic, assets/*.css","Simple settings without presets","snippets/*.liquid basic","Pre-OS2.0 patterns, limited customization API"
20
+ 19,Unknown,0.0.0,Unknown,"unknown, unrecognized, custom, bespoke",--,kebab-case preferred,Standard blocks assumed,"Neutral defaults","System fonts fallback","Standard spacing","sections/*.liquid","Minimal schema assumed","Standard snippets","Fallback for completely unrecognized themes"
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Rebly Sections Core - BM25 search engine for Shopify section development"""
4
+
5
+ import csv
6
+ import re
7
+ from pathlib import Path
8
+ from math import log
9
+ from collections import defaultdict
10
+
11
+ DATA_DIR = Path(__file__).parent.parent / "data"
12
+ MAX_RESULTS = 3
13
+
14
+ CSV_CONFIG = {
15
+ "schema": {
16
+ "file": "schema-library.csv",
17
+ "search_cols": ["Type", "Category", "Keywords", "Notes"],
18
+ "output_cols": ["Type", "Category", "Returns", "Required Props", "Optional Props", "JSON Example", "Constraints"]
19
+ },
20
+ "liquid": {
21
+ "file": "liquid-stdlib.csv",
22
+ "search_cols": ["Name", "Kind", "Category", "Keywords"],
23
+ "output_cols": ["Name", "Kind", "Syntax", "Example", "Output", "Notes"]
24
+ },
25
+ "tokens": {
26
+ "file": "design-tokens.csv",
27
+ "search_cols": ["Token Name", "Category", "Keywords"],
28
+ "output_cols": ["Token Name", "Category", "Value", "CSS Variable", "Usage"]
29
+ },
30
+ "components": {
31
+ "file": "component-library/_index.csv",
32
+ "search_cols": ["Name", "Category", "Keywords", "Description"],
33
+ "output_cols": ["Name", "Slug", "Category", "Difficulty", "Blocks Used", "Settings Count", "ui_style_keywords", "File", "Description"]
34
+ },
35
+ "themes": {
36
+ "file": "theme-dna.csv",
37
+ "search_cols": ["Theme", "Keywords", "CSS Variable Prefix"],
38
+ "output_cols": ["Theme", "Version", "CSS Variable Prefix", "Section Naming", "Block Conventions", "Color Scheme Pattern"]
39
+ },
40
+ "practices": {
41
+ "file": "shopify-best-practices.csv",
42
+ "search_cols": ["Rule", "Category", "Keywords", "Description"],
43
+ "output_cols": ["Rule", "Category", "Severity", "Description", "Do", "Dont", "Code Example"]
44
+ }
45
+ }
46
+
47
+ # ============ BM25 IMPLEMENTATION ============
48
+ class BM25:
49
+ """BM25 ranking algorithm for text search"""
50
+
51
+ def __init__(self, k1=1.5, b=0.75):
52
+ self.k1 = k1
53
+ self.b = b
54
+ self.corpus = []
55
+ self.doc_lengths = []
56
+ self.avgdl = 0
57
+ self.idf = {}
58
+ self.doc_freqs = defaultdict(int)
59
+ self.N = 0
60
+
61
+ def tokenize(self, text):
62
+ """Lowercase, split, remove punctuation, filter short words"""
63
+ text = re.sub(r'[^\w\s]', ' ', str(text).lower())
64
+ return [w for w in text.split() if len(w) > 2]
65
+
66
+ def fit(self, documents):
67
+ """Build BM25 index from documents"""
68
+ self.corpus = [self.tokenize(doc) for doc in documents]
69
+ self.N = len(self.corpus)
70
+ if self.N == 0:
71
+ return
72
+ self.doc_lengths = [len(doc) for doc in self.corpus]
73
+ self.avgdl = sum(self.doc_lengths) / self.N
74
+
75
+ for doc in self.corpus:
76
+ seen = set()
77
+ for word in doc:
78
+ if word not in seen:
79
+ self.doc_freqs[word] += 1
80
+ seen.add(word)
81
+
82
+ for word, freq in self.doc_freqs.items():
83
+ self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
84
+
85
+ def score(self, query):
86
+ """Score all documents against query"""
87
+ query_tokens = self.tokenize(query)
88
+ scores = []
89
+
90
+ for idx, doc in enumerate(self.corpus):
91
+ score = 0
92
+ doc_len = self.doc_lengths[idx]
93
+ term_freqs = defaultdict(int)
94
+ for word in doc:
95
+ term_freqs[word] += 1
96
+
97
+ for token in query_tokens:
98
+ if token in self.idf:
99
+ tf = term_freqs[token]
100
+ idf = self.idf[token]
101
+ numerator = tf * (self.k1 + 1)
102
+ denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
103
+ score += idf * numerator / denominator
104
+
105
+ scores.append((idx, score))
106
+
107
+ return sorted(scores, key=lambda x: x[1], reverse=True)
108
+
109
+
110
+ # ============ SEARCH FUNCTIONS ============
111
+ def _load_csv(filepath):
112
+ """Load CSV and return list of dicts"""
113
+ with open(filepath, 'r', encoding='utf-8') as f:
114
+ return list(csv.DictReader(f))
115
+
116
+
117
+ def _search_csv(filepath, search_cols, output_cols, query, max_results):
118
+ """Core search function using BM25"""
119
+ if not filepath.exists():
120
+ return []
121
+
122
+ data = _load_csv(filepath)
123
+ documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
124
+
125
+ bm25 = BM25()
126
+ bm25.fit(documents)
127
+ ranked = bm25.score(query)
128
+
129
+ results = []
130
+ for idx, score in ranked[:max_results]:
131
+ if score > 0:
132
+ row = data[idx]
133
+ results.append({col: row.get(col, "") for col in output_cols if col in row})
134
+
135
+ return results
136
+
137
+
138
+ # ============ DOMAIN DETECTION ============
139
+ domain_keywords = {
140
+ "schema": ["schema", "setting", "type", "input", "picker", "range", "checkbox", "select", "block type"],
141
+ "liquid": ["liquid", "tag", "filter", "object", "render", "assign", "capture", "for loop", "if condition"],
142
+ "tokens": ["token", "spacing", "color", "font", "breakpoint", "shadow", "variable", "css var", "design token"],
143
+ "components": ["section", "hero", "testimonial", "faq", "product grid", "cta", "newsletter", "gallery", "feature", "component", "pattern"],
144
+ "themes": ["theme", "dawn", "impulse", "impact", "craft", "detect", "dna", "theme profile"],
145
+ "practices": ["best practice", "performance", "accessibility", "seo", "os2", "lazy load", "responsive", "a11y"]
146
+ }
147
+
148
+
149
+ def detect_domain(query):
150
+ """Auto-detect domain from query keywords"""
151
+ query_lower = query.lower()
152
+ scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
153
+ best = max(scores, key=scores.get)
154
+ return best if scores[best] > 0 else "components"
155
+
156
+
157
+ def search(query, domain=None, max_results=MAX_RESULTS):
158
+ """Main search function with auto-domain detection"""
159
+ if domain is None:
160
+ domain = detect_domain(query)
161
+
162
+ config = CSV_CONFIG.get(domain, CSV_CONFIG["components"])
163
+ filepath = DATA_DIR / config["file"]
164
+
165
+ if not filepath.exists():
166
+ return {"error": f"File not found: {filepath}", "domain": domain}
167
+
168
+ results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
169
+
170
+ return {
171
+ "domain": domain,
172
+ "query": query,
173
+ "file": config["file"],
174
+ "count": len(results),
175
+ "results": results
176
+ }
177
+
178
+
179
+ def search_component(query, max_results=MAX_RESULTS):
180
+ """Enhanced component search - returns _index.csv match + .liquid file content"""
181
+ results = search(query, domain="components", max_results=max_results)
182
+ if "error" in results or not results.get("results"):
183
+ return results
184
+
185
+ component_dir = (DATA_DIR / "component-library").resolve()
186
+ for result in results["results"]:
187
+ liquid_file = result.get("File", "")
188
+ if liquid_file:
189
+ liquid_path = (component_dir / liquid_file).resolve()
190
+ # Path traversal guard: ensure resolved path stays within component dir
191
+ if str(liquid_path).startswith(str(component_dir)) and liquid_path.exists():
192
+ result["liquid_content"] = liquid_path.read_text(encoding="utf-8")
193
+
194
+ return results
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Rebly Sections Quality Gate - Validate OS2.0 compliance
5
+ Usage: python3 quality-gate.py path/to/section.liquid [--json]
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import re
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ QUALITY_CHECKS = [
16
+ ("schema_valid_json", "Schema block contains valid JSON"),
17
+ ("has_presets", "Schema has non-empty presets array"),
18
+ ("has_app_block", "Blocks array includes @app type"),
19
+ ("block_attributes", "Block wrappers have block.shopify_attributes"),
20
+ ("no_include_tag", "No {% include %} usage"),
21
+ ("css_scoped", "CSS uses section-scoped selector"),
22
+ ("no_liquid_in_style", "No complex Liquid inside <style>"),
23
+ ("has_section_tag", "Uses <section> HTML element"),
24
+ ("images_lazy_load", "Non-hero images use loading='lazy'"),
25
+ ("settings_have_labels", "All settings have label property"),
26
+ ]
27
+
28
+
29
+ def extract_schema_json(content):
30
+ """Extract JSON from {% schema %} block"""
31
+ match = re.search(r'\{%[-\s]*schema\s*[-\s]*%\}(.*?)\{%[-\s]*endschema\s*[-\s]*%\}', content, re.DOTALL)
32
+ if not match:
33
+ return None
34
+ try:
35
+ return json.loads(match.group(1).strip())
36
+ except json.JSONDecodeError:
37
+ return None
38
+
39
+
40
+ def check_schema_valid_json(content):
41
+ return extract_schema_json(content) is not None
42
+
43
+
44
+ def check_has_presets(content):
45
+ schema = extract_schema_json(content)
46
+ if not schema:
47
+ return False
48
+ presets = schema.get("presets", [])
49
+ return isinstance(presets, list) and len(presets) > 0
50
+
51
+
52
+ def check_has_app_block(content):
53
+ schema = extract_schema_json(content)
54
+ if not schema:
55
+ return False
56
+ blocks = schema.get("blocks", [])
57
+ return any(b.get("type") == "@app" for b in blocks)
58
+
59
+
60
+ def check_block_attributes(content):
61
+ # If no blocks iteration, pass
62
+ if "section.blocks" not in content and "block.type" not in content:
63
+ return True
64
+ return "block.shopify_attributes" in content
65
+
66
+
67
+ def check_no_include_tag(content):
68
+ return not re.search(r'\{%[-\s]*include\s', content)
69
+
70
+
71
+ def check_css_scoped(content):
72
+ style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
73
+ if not style_match:
74
+ return True # No styles, pass
75
+ return "section.id" in content or "section_id" in content
76
+
77
+
78
+ def check_no_liquid_in_style(content):
79
+ style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
80
+ if not style_match:
81
+ return True
82
+ style_content = style_match.group(1)
83
+ # Allow simple output tags {{ }} but flag complex logic {% %}
84
+ complex_tags = re.findall(r'\{%[-\s]*(if|for|case|unless|elsif)\s', style_content)
85
+ return len(complex_tags) == 0
86
+
87
+
88
+ def check_has_section_tag(content):
89
+ return bool(re.search(r'<section[\s>]', content))
90
+
91
+
92
+ def check_images_lazy_load(content):
93
+ img_tags = re.findall(r'<img\s[^>]+>', content)
94
+ if not img_tags:
95
+ return True
96
+ # Skip first image (likely hero/eager); check subsequent ones
97
+ for img in img_tags[1:]:
98
+ if 'loading=' not in img:
99
+ return False
100
+ return True
101
+
102
+
103
+ def check_settings_have_labels(content):
104
+ schema = extract_schema_json(content)
105
+ if not schema:
106
+ return True # Can't check without valid schema
107
+ settings = schema.get("settings", [])
108
+ for s in settings:
109
+ if s.get("type") not in ("header", "paragraph") and "label" not in s:
110
+ return False
111
+ for block in schema.get("blocks", []):
112
+ for s in block.get("settings", []):
113
+ if s.get("type") not in ("header", "paragraph") and "label" not in s:
114
+ return False
115
+ return True
116
+
117
+
118
+ CHECK_FUNCTIONS = {
119
+ "schema_valid_json": check_schema_valid_json,
120
+ "has_presets": check_has_presets,
121
+ "has_app_block": check_has_app_block,
122
+ "block_attributes": check_block_attributes,
123
+ "no_include_tag": check_no_include_tag,
124
+ "css_scoped": check_css_scoped,
125
+ "no_liquid_in_style": check_no_liquid_in_style,
126
+ "has_section_tag": check_has_section_tag,
127
+ "images_lazy_load": check_images_lazy_load,
128
+ "settings_have_labels": check_settings_have_labels,
129
+ }
130
+
131
+
132
+ def run_quality_gate(filepath):
133
+ """Run all quality checks on a .liquid file, return markdown report string"""
134
+ path = Path(filepath)
135
+ if not path.exists():
136
+ return f"Error: File not found: {filepath}"
137
+
138
+ content = path.read_text(encoding="utf-8")
139
+ results = []
140
+ passed = 0
141
+ failed = 0
142
+
143
+ for check_id, description in QUALITY_CHECKS:
144
+ check_fn = CHECK_FUNCTIONS[check_id]
145
+ try:
146
+ result = check_fn(content)
147
+ status = "PASS" if result else "FAIL"
148
+ if result:
149
+ passed += 1
150
+ else:
151
+ failed += 1
152
+ results.append(f"{'✓' if result else '✗'} {description}: {status}")
153
+ except Exception as e:
154
+ failed += 1
155
+ results.append(f"✗ {description}: ERROR ({e})")
156
+
157
+ summary = f"\n## Quality Gate: {path.name}\n"
158
+ summary += f"**Score:** {passed}/{passed + failed} checks passed\n\n"
159
+ summary += "\n".join(results)
160
+
161
+ if failed == 0:
162
+ summary += "\n\n**Result: PASS** ✓"
163
+ else:
164
+ summary += f"\n\n**Result: FAIL** — {failed} issue(s) found"
165
+
166
+ return summary
167
+
168
+
169
+ def main():
170
+ parser = argparse.ArgumentParser(description="Rebly Sections Quality Gate")
171
+ parser.add_argument("filepath", help="Path to .liquid section file")
172
+ args = parser.parse_args()
173
+
174
+ result = run_quality_gate(args.filepath)
175
+ print(result)
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main()
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Rebly Sections Search CLI
4
+ Usage: python3 search.py "<query>" [--domain <domain>] [-n 3] [--component] [--json]
5
+ Domains: schema, liquid, tokens, components, themes, practices
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ import io
11
+ from core import CSV_CONFIG, MAX_RESULTS, search, search_component
12
+
13
+ # UTF-8 stdout wrapper
14
+ if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
15
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
16
+ if sys.stderr.encoding and sys.stderr.encoding.lower() != 'utf-8':
17
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
18
+
19
+
20
+ def format_output(result):
21
+ """Format results as Claude-optimized markdown"""
22
+ if "error" in result:
23
+ return f"Error: {result['error']}"
24
+ output = []
25
+ output.append(f"## Rebly Sections Search")
26
+ output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
27
+ output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
28
+ for i, row in enumerate(result['results'], 1):
29
+ output.append(f"### Result {i}")
30
+ for key, value in row.items():
31
+ if key == "liquid_content":
32
+ continue
33
+ value_str = str(value)
34
+ if len(value_str) > 300:
35
+ value_str = value_str[:300] + "..."
36
+ output.append(f"- **{key}:** {value_str}")
37
+ output.append("")
38
+ return "\n".join(output)
39
+
40
+
41
+ def format_component_output(result):
42
+ """Format component results including .liquid content"""
43
+ base = format_output(result)
44
+ for row in result.get('results', []):
45
+ if 'liquid_content' in row:
46
+ base += f"\n---\n### Pattern: {row.get('Name', 'Unknown')}\n```liquid\n{row['liquid_content']}\n```\n"
47
+ return base
48
+
49
+
50
+ if __name__ == "__main__":
51
+ parser = argparse.ArgumentParser(description="Rebly Sections Search")
52
+ parser.add_argument("query", help="Search query")
53
+ parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
54
+ parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS)
55
+ parser.add_argument("--component", "-c", action="store_true", help="Component search with .liquid content")
56
+ parser.add_argument("--json", action="store_true", help="JSON output")
57
+ args = parser.parse_args()
58
+
59
+ if args.component:
60
+ result = search_component(args.query, args.max_results)
61
+ if args.json:
62
+ import json
63
+ print(json.dumps(result, indent=2, ensure_ascii=False))
64
+ else:
65
+ print(format_component_output(result))
66
+ else:
67
+ result = search(args.query, args.domain, args.max_results)
68
+ if args.json:
69
+ import json
70
+ print(json.dumps(result, indent=2, ensure_ascii=False))
71
+ else:
72
+ print(format_output(result))