瀏覽代碼

wiki updates and file cleanup

xcad 2 月之前
父節點
當前提交
f64137a60f

+ 0 - 26
.editorconfig

@@ -1,26 +0,0 @@
-# https://editorconfig.org/
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-indent_size = 4
-indent_style = space
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.json]
-indent_size = 2
-
-[*.{js,jsx,ts,tsx}]
-indent_size = 2
-
-[*.md]
-indent_size = unset
-trim_trailing_whitespace = false
-
-[*.py]
-indent_size = 4
-
-[{*.{yaml,yml},.yamllint}]
-indent_size = 2

+ 0 - 252
.github/scripts/generate_wiki_docs.py

@@ -1,252 +0,0 @@
-#!/usr/bin/env python3
-"""Generate GitHub Wiki documentation for module variables.
-
-This script auto-generates variable documentation in GitHub Wiki markdown format
-for all registered modules, using the latest schema version for each.
-"""
-
-import sys
-from pathlib import Path
-
-# Add project root to path (script is in .github/scripts, so go up twice)
-project_root = Path(__file__).parent.parent.parent
-sys.path.insert(0, str(project_root))
-
-# ruff: noqa: E402
-# Import all modules to register them
-import cli.modules.ansible
-import cli.modules.compose
-import cli.modules.helm
-import cli.modules.kubernetes
-import cli.modules.packer
-import cli.modules.terraform  # noqa: F401
-from cli.core.registry import registry  # Module import after path manipulation
-
-
-def format_value(value):
-    """Format value for markdown display."""
-    if value is None or value == "":
-        return "_none_"
-    if isinstance(value, bool):
-        return "✓" if value else "✗"
-    if isinstance(value, list):
-        return ", ".join(f"`{v}`" for v in value)
-    return f"`{value}`"
-
-
-def generate_module_docs(module_name: str, output_dir: Path):  # noqa: PLR0912, PLR0915
-    """Generate wiki documentation for a single module."""
-    # Get module class from registry
-    module_classes = dict(registry.iter_module_classes())
-
-    if module_name not in module_classes:
-        sys.stderr.write(f"Warning: Module '{module_name}' not found, skipping\n")
-        return False
-
-    module_cls = module_classes[module_name]
-    schema_version = module_cls.schema_version
-
-    # Get the spec for the latest schema version
-    if hasattr(module_cls, "schemas") and schema_version in module_cls.schemas:
-        spec = module_cls.schemas[schema_version]
-    elif hasattr(module_cls, "spec"):
-        spec = module_cls.spec
-    else:
-        sys.stderr.write(f"Warning: No spec found for module '{module_name}', skipping\n")
-        return False
-
-    # Generate markdown content
-    lines = []
-
-    # Header
-    lines.append(f"# {module_name.title()} Variables")
-    lines.append("")
-    lines.append(f"**Module:** `{module_name}`  ")
-    lines.append(f"**Schema Version:** `{schema_version}`  ")
-    lines.append(f"**Description:** {module_cls.description}")
-    lines.append("")
-    lines.append("---")
-    lines.append("")
-    lines.append(
-        "This page documents all available variables for the "
-        + f"{module_name} module. Variables are organized into sections "
-        + "that can be enabled/disabled based on your configuration needs."
-    )
-    lines.append("")
-
-    # Table of contents
-    lines.append("## Table of Contents")
-    lines.append("")
-    for section_key, section_data in spec.items():
-        section_title = section_data.get("title", section_key)
-        anchor = section_title.lower().replace(" ", "-").replace("/", "")
-        lines.append(f"- [{section_title}](#{anchor})")
-    lines.append("")
-    lines.append("---")
-    lines.append("")
-
-    # Process each section
-    for section_key, section_data in spec.items():
-        section_title = section_data.get("title", section_key)
-        section_desc = section_data.get("description", "")
-        section_toggle = section_data.get("toggle", "")
-        section_needs = section_data.get("needs", "")
-        section_required = section_data.get("required", False)
-        section_vars = section_data.get("vars", {})
-
-        # Section header
-        lines.append(f"## {section_title}")
-        lines.append("")
-
-        # Section metadata
-        metadata = []
-        if section_required:
-            metadata.append("**Required:** Yes")
-        if section_toggle:
-            metadata.append(f"**Toggle Variable:** `{section_toggle}`")
-        if section_needs:
-            if isinstance(section_needs, list):
-                needs_str = ", ".join(f"`{n}`" for n in section_needs)
-            else:
-                needs_str = f"`{section_needs}`"
-            metadata.append(f"**Depends On:** {needs_str}")
-
-        if metadata:
-            lines.append("  \n".join(metadata))
-            lines.append("")
-
-        if section_desc:
-            lines.append(section_desc)
-            lines.append("")
-
-        # Skip sections with no variables
-        if not section_vars:
-            lines.append("_No variables defined in this section._")
-            lines.append("")
-            continue
-
-        # Variables table
-        lines.append("| Variable | Type | Default | Description |")
-        lines.append("|----------|------|---------|-------------|")
-
-        for var_name, var_data in section_vars.items():
-            var_type = var_data.get("type", "str")
-            var_default = format_value(var_data.get("default"))
-            var_description = var_data.get("description", "").replace("\n", " ")
-
-            # Add extra metadata to description
-            extra_parts = []
-            if var_data.get("sensitive"):
-                extra_parts.append("**Sensitive**")
-            if var_data.get("autogenerated"):
-                extra_parts.append("**Auto-generated**")
-            if "options" in var_data:
-                opts = ", ".join(f"`{o}`" for o in var_data["options"])
-                extra_parts.append(f"**Options:** {opts}")
-            if "needs" in var_data:
-                extra_parts.append(f"**Needs:** `{var_data['needs']}`")
-            if "extra" in var_data:
-                extra_parts.append(var_data["extra"])
-
-            if extra_parts:
-                var_description += "<br>" + " • ".join(extra_parts)
-
-            lines.append(f"| `{var_name}` | `{var_type}` | {var_default} | {var_description} |")
-
-        lines.append("")
-        lines.append("---")
-        lines.append("")
-
-    # Footer
-    lines.append("## Notes")
-    lines.append("")
-    lines.append("- **Required sections** must be configured")
-    lines.append("- **Toggle variables** enable/disable entire sections")
-    lines.append("- **Dependencies** (`needs`) control when sections/variables are available")
-    lines.append("- **Sensitive variables** are masked during prompts")
-    lines.append("- **Auto-generated variables** are populated automatically if not provided")
-    lines.append("")
-    lines.append("---")
-    lines.append("")
-    lines.append(f"_Last updated: Schema version {schema_version}_")
-
-    # Write to file
-    output_file = output_dir / f"Variables-{module_name.title()}.md"
-    output_file.write_text("\n".join(lines))
-
-    sys.stdout.write(f"Generated: {output_file.name}\n")
-    return True
-
-
-def generate_variables_index(modules: list[str], output_dir: Path):
-    """Generate index page for all variable documentation."""
-    lines = []
-
-    lines.append("# Variables Documentation")
-    lines.append("")
-    lines.append("This section contains auto-generated documentation for all " + "available variables in each module.")
-    lines.append("")
-    lines.append("## Available Modules")
-    lines.append("")
-
-    for module_name in sorted(modules):
-        lines.append(f"- [{module_name.title()}](Variables-{module_name.title()})")
-
-    lines.append("")
-    lines.append("---")
-    lines.append("")
-    lines.append("Each module page includes:")
-    lines.append("")
-    lines.append("- Schema version information")
-    lines.append("- Complete list of sections and variables")
-    lines.append("- Variable types, defaults, and descriptions")
-    lines.append("- Section dependencies and toggle configurations")
-    lines.append("")
-    lines.append("---")
-    lines.append("")
-    lines.append("_This documentation is auto-generated from module schemas._")
-
-    output_file = output_dir / "Variables.md"
-    output_file.write_text("\n".join(lines))
-
-    sys.stdout.write(f"Generated: {output_file.name}\n")
-
-
-# Minimum required arguments
-MIN_ARGS = 2
-
-
-def main():
-    """Main entry point."""
-    if len(sys.argv) < MIN_ARGS:
-        sys.stderr.write("Usage: python3 scripts/generate_wiki_docs.py <output_directory>\n")
-        sys.exit(1)
-
-    output_dir = Path(sys.argv[1])
-    output_dir.mkdir(parents=True, exist_ok=True)
-
-    sys.stdout.write(f"Generating wiki documentation in: {output_dir}\n")
-    sys.stdout.write("\n")
-
-    # Get all registered modules
-    module_classes = dict(registry.iter_module_classes())
-    successful_modules = []
-
-    for module_name in sorted(module_classes.keys()):
-        if generate_module_docs(module_name, output_dir):
-            successful_modules.append(module_name)
-
-    sys.stdout.write("\n")
-
-    # Generate index page
-    if successful_modules:
-        generate_variables_index(successful_modules, output_dir)
-        sys.stdout.write("\n")
-        sys.stdout.write(f"✓ Successfully generated documentation for {len(successful_modules)} module(s)\n")
-    else:
-        sys.stderr.write("Error: No documentation generated\n")
-        sys.exit(1)
-
-
-if __name__ == "__main__":
-    main()

+ 0 - 98
.github/workflows/docs-update-wiki.yaml

@@ -1,98 +0,0 @@
----
-name: Docs - Update Wiki
-
-'on':
-  push:
-    branches:
-      - main
-    paths:
-      - 'cli/core/schema/**/*.json'  # JSON schema files
-      - '.wiki/**'  # Static wiki pages
-      - '.github/scripts/generate_wiki_docs.py'  # Wiki generation script
-      - '.github/workflows/docs-update-wiki.yaml'  # This workflow
-  workflow_dispatch:  # Allow manual trigger
-
-permissions:
-  contents: write
-
-jobs:
-  update-wiki:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v6
-        with:
-          fetch-depth: 1
-
-      - name: Checkout wiki repository
-        uses: actions/checkout@v6
-        with:
-          repository: ${{ github.repository }}.wiki
-          path: wiki
-          token: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Set up Python
-        uses: actions/setup-python@v6
-        with:
-          python-version: '3.14'
-
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip
-          pip install -e .
-
-      - name: Generate variable documentation
-        run: |
-          python3 .github/scripts/generate_wiki_docs.py wiki/
-
-      - name: Sync wiki pages from .wiki directory
-        run: |
-          # Copy all markdown files from .wiki/ to wiki/ (except Variables-*.md which are auto-generated)
-          if [ -d ".wiki" ]; then
-            echo "Syncing wiki pages from .wiki/ directory..."
-            for file in .wiki/*.md; do
-              filename=$(basename "$file")
-              # Skip auto-generated variable documentation files
-              if [[ ! "$filename" =~ ^Variables- ]]; then
-                echo "  Copying $filename"
-                cp "$file" "wiki/$filename"
-              fi
-            done
-          else
-            echo "No .wiki directory found, skipping static wiki pages sync"
-          fi
-
-      - name: Check for changes
-        id: changes
-        working-directory: wiki
-        run: |
-          git add .
-          if git diff --staged --quiet; then
-            echo "has_changes=false" >> $GITHUB_OUTPUT
-            echo "No changes detected in wiki documentation"
-          else
-            echo "has_changes=true" >> $GITHUB_OUTPUT
-            echo "Changes detected in wiki documentation"
-          fi
-
-      - name: Commit and push changes
-        if: steps.changes.outputs.has_changes == 'true'
-        working-directory: wiki
-        run: |
-          git config user.name "github-actions[bot]"
-          git config user.email "github-actions[bot]@users.noreply.github.com"
-          git commit -m "Auto-update wiki pages"
-
-          # Pull with rebase to handle any remote changes, then push
-          # GitHub wikis use master as default branch
-          git pull --rebase origin master
-          git push origin master
-
-      - name: Summary
-        run: |
-          if [ "${{ steps.changes.outputs.has_changes }}" == "true" ]; then
-            echo "Wiki variable documentation updated successfully"
-            echo "View at: https://github.com/${{ github.repository }}/wiki"
-          else
-            echo "No changes to wiki documentation"
-          fi

+ 22 - 15
.wiki/Core-Concepts-Defaults.md

@@ -4,7 +4,7 @@ Save time by setting default values for variables you use frequently. This page
 
 ## What are Default Variables?
 
-**Default variables** are user-defined values that override module and template defaults. They allow you to:
+**Default variables** are user-defined values that override template manifest values for a module. They allow you to:
 - Avoid repetitive typing during template generation
 - Standardize values across multiple templates
 - Customize your environment once, use everywhere
@@ -13,13 +13,13 @@ Save time by setting default values for variables you use frequently. This page
 
 Variables are resolved in this order (lowest to highest priority):
 
-1. Module runtime defaults
-2. Template defaults from `template.json`
-3. **User config** (your saved defaults) ← This page
-4. `--var-file`
-5. CLI arguments (`--var`)
+1. Template manifest values from `template.json`
+2. **User config** (your saved defaults) ← This page
+3. `--var-file`
+4. CLI arguments (`--var`)
+5. Interactive prompt answers
 
-Your saved defaults override module and template defaults, but they can still be overridden at generation time.
+Your saved defaults override template manifest values, but they can still be overridden at generation time.
 
 ## Managing Defaults
 
@@ -104,7 +104,7 @@ Response:
 Removed default: container_timezone
 ```
 
-The variable will now use module/template defaults again.
+The variable will now use template manifest values again.
 
 ### Clear All Defaults
 
@@ -123,11 +123,13 @@ Cleared all defaults for compose
 
 ## Configuration Storage
 
-Defaults are stored in:
+Defaults are usually stored in:
 ```
 ~/.config/boilerplates/config.yaml
 ```
 
+If a local `./config.yaml` exists in your current working directory, the CLI uses that file instead.
+
 Example content:
 ```yaml
 libraries:
@@ -153,13 +155,15 @@ defaults:
 You can manually edit the config file:
 
 ```bash
-# Edit configuration
+# Edit the global configuration example
 nano ~/.config/boilerplates/config.yaml
 
 # Verify defaults
 boilerplates compose defaults list
 ```
 
+If you are using a local `./config.yaml`, edit that file instead.
+
 ## Common Use Cases
 
 ### Timezone Configuration
@@ -251,6 +255,8 @@ Defaults don't transfer between modules—they're module-specific.
 
 ## Backup and Restore
 
+The examples below use the global config path. If the CLI is using a local `./config.yaml`, substitute that path instead.
+
 ### Backup Configuration
 
 Save your configuration:
@@ -332,7 +338,7 @@ cat ~/.config/boilerplates/config.yaml
 If config file is corrupted:
 
 ```bash
-# Validate YAML syntax
+# Validate global config YAML syntax
 python3 -c "import yaml; yaml.safe_load(open('~/.config/boilerplates/config.yaml'))"
 
 # Or remove and recreate
@@ -346,15 +352,16 @@ If wrong values appear:
 
 ```bash
 # Check precedence
-# 1. Module spec
-# 2. Template spec
-# 3. User defaults ← Check here
+# 1. Template manifest values
+# 2. User defaults ← Check here
+# 3. --var-file
 # 4. CLI --var
+# 5. Interactive answers
 
 # Verify your defaults
 boilerplates compose defaults list
 
-# Check template spec
+# Check the loaded template state
 boilerplates compose show <template-name>
 ```
 

+ 26 - 8
.wiki/Core-Concepts-Libraries.md

@@ -40,14 +40,28 @@ libraries:
 
 ## Local Storage
 
-Git libraries are stored under:
+Git libraries are stored under the active config directory's `libraries/` folder.
+
+Global-config default:
 
 ```text
 ~/.config/boilerplates/libraries/
 ```
 
+If the CLI is using a local `./config.yaml`, the library checkout location becomes `./libraries/`.
+
 The configured `directory` is applied inside that checkout. For the official library the directory is `.`.
 
+## Configuration Location
+
+By default, Boilerplates uses:
+
+```text
+~/.config/boilerplates/config.yaml
+```
+
+If a local `./config.yaml` exists in your current working directory, the CLI uses that file instead.
+
 ## Discovery Rules
 
 Boilerplates discovers templates by module directory, for example:
@@ -79,11 +93,21 @@ boilerplates repo update
 Add a custom Git library:
 
 ```bash
-boilerplates repo add my-templates https://github.com/user/templates \
+boilerplates repo add my-templates \
+  --library-type git \
+  --url https://github.com/user/templates \
   --directory . \
   --branch main
 ```
 
+Add a static library:
+
+```bash
+boilerplates repo add local \
+  --library-type static \
+  --path ~/my-templates
+```
+
 Remove a library:
 
 ```bash
@@ -127,12 +151,6 @@ Templates with `metadata.draft: true` are excluded from normal listings and look
 
 ## Config File
 
-Library configuration lives in:
-
-```text
-~/.config/boilerplates/config.yaml
-```
-
 Example:
 
 ```yaml

+ 70 - 64
.wiki/Core-Concepts-Templates.md

@@ -1,10 +1,8 @@
 # Templates
 
-Templates are the core unit of Boilerplates. A template is a directory with a `template.json` manifest and a `files/` directory containing the files to render.
+Templates are the core unit of Boilerplates. A supported template is a directory with a `template.json` manifest and a `files/` directory containing every renderable output file.
 
-## Template Structure
-
-Every supported template looks like this:
+## Required Layout
 
 ```text
 my-template/
@@ -16,11 +14,13 @@ my-template/
         └── app.yaml
 ```
 
-Only `template.json` is a supported manifest format. Legacy `template.yaml` and `template.yml` manifests are not supported.
+Rules:
+- `template.json` is the only supported manifest format
+- all rendered content must live under `files/`
+- legacy `template.yaml`, `template.yml`, and top-level `.j2` layouts are incompatible with the current runtime
 
-## Manifest Shape
+## Top-Level Manifest Shape
 
-Top-level fields:
 - `slug`
 - `kind`
 - `metadata`
@@ -35,6 +35,8 @@ Example:
   "metadata": {
     "name": "My Template",
     "description": "Short human description",
+    "author": "Your Name",
+    "date": "2026-04-22",
     "tags": ["infra", "dev"],
     "icon": {
       "provider": "mdi",
@@ -48,7 +50,7 @@ Example:
       "source_dep_version": "1.1.0",
       "source_dep_digest": "sha256:abc123def456",
       "upstream_ref": "release-2026-04-22",
-      "notes": "Tracks upstream container release used by this template snapshot"
+      "notes": "Tracks the tested upstream dependency snapshot"
     }
   },
   "variables": [
@@ -68,24 +70,38 @@ Example:
 }
 ```
 
+## `slug` and Template IDs
+
+`slug` is the canonical template ID exposed by the CLI.
+
+Behavior:
+- if `slug` is present, it wins over the directory name
+- if `slug` ends with `-<kind>`, that suffix is normalized away for CLI use
+- if `slug` is missing, the directory name is used
+
+Example:
+- directory: `portainer/`
+- `kind`: `compose`
+- `slug`: `portainer-compose`
+- CLI ID: `portainer`
+
 ## Metadata
 
 Common metadata fields:
 - `name`
 - `description`
+- `author`
+- `date`
 - `tags`
 - `icon`
 - `draft`
-- `author`
-- `date`
-- `guide`
 - `version`
 
 ### Version Metadata
 
-`metadata.version` is optional. If present, it must be an object.
+`metadata.version` is optional, but when present it must be an object.
 
-Supported version fields:
+Supported fields:
 - `name`
 - `source_dep_name`
 - `source_dep_version`
@@ -93,57 +109,34 @@ Supported version fields:
 - `upstream_ref`
 - `notes`
 
-Important rules:
-- the whole `version` object may be omitted
-- any individual `version` field may be omitted
-- the CLI uses `metadata.version.name` as the visible version label in list/show output
+Important behavior:
+- `metadata.version.name` is the user-facing version label shown in list/show output
+- the rest of the version object is for upstream dependency tracking
+- the full object may be omitted
+- individual fields inside the object may also be omitted
 
-This means these are all valid:
+## Variable Declarations Are Mandatory
 
-```json
-{
-  "metadata": {
-    "name": "My Template"
-  }
-}
-```
+Any variable used in files under `files/` must be declared in `template.json`.
 
-```json
-{
-  "metadata": {
-    "name": "My Template",
-    "version": {
-      "name": "v1.1"
-    }
-  }
-}
-```
+If a file references an undeclared variable, the template fails validation and load/render operations surface a template error.
 
-```json
-{
-  "metadata": {
-    "name": "My Template",
-    "version": {
-      "source_dep_name": "ghcr.io/example/my-image",
-      "source_dep_version": "1.1.0"
-    }
-  }
-}
-```
+Use the [Variables](Core-Concepts-Variables) page for the manifest structure.
 
 ## Files and Rendering
 
-All files inside `files/` are part of the template output.
+Everything under `files/` is part of the output tree.
 
-Rendering rules:
-- Boilerplates renders every file under `files/`
-- files without template expressions pass through unchanged
-- output paths currently match the relative file paths under `files/`
-- template discovery ignores anything without `template.json`
+Rendering behavior:
+- Boilerplates walks every file under `files/`
+- files are rendered with the custom delimiter set
+- files without template expressions still pass through the render pipeline
+- output paths currently mirror the relative paths inside `files/`
+- rendered output is sanitized to normalize blank lines and trailing whitespace
 
 ## Delimiters
 
-Templates use custom delimiters, not default Jinja syntax:
+Templates use custom delimiters rather than default Jinja syntax:
 
 - variables: `<< value >>`
 - blocks: `<% if condition %>`
@@ -165,17 +158,21 @@ Legacy `{{ }}`, `{% %}`, and `{# #}` delimiters are rejected.
 
 ## Includes and Imports
 
-Includes and imports are resolved relative to the template's `files/` directory.
-
-Example:
+Includes and imports resolve relative to the template's `files/` directory.
 
 ```jinja
 <% include 'partials/header.yaml' %>
 ```
 
-## Template Discovery
+## Discovery Rules
+
+Templates are discovered from configured libraries.
 
-Templates are discovered from configured libraries. A directory is considered a template only when it contains `template.json`.
+A directory is considered a valid template only when:
+- `template.json` exists
+- `files/` exists
+
+In practice, Boilerplates discovers templates by module directory path such as `compose/<template>/` or `terraform/<template>/`.
 
 Useful commands:
 
@@ -186,11 +183,11 @@ boilerplates compose show nginx
 ```
 
 Draft templates:
-- set `metadata.draft` to `true` to hide a template from normal discovery
+- set `metadata.draft` to `true` to hide the template from normal discovery
 
-## Generation
+## Generation Workflow
 
-Typical generation flow:
+Typical workflow:
 
 ```bash
 boilerplates compose show nginx
@@ -204,6 +201,7 @@ Useful flags:
 - `--var` for direct CLI overrides
 - `--no-interactive` for non-interactive generation
 - `--dry-run` to preview generation without writing files
+- `--show-files` to print rendered files during dry-run
 
 ## Validation
 
@@ -219,11 +217,19 @@ Validate all templates in a module:
 boilerplates compose validate
 ```
 
+Validation covers:
+- manifest structure
+- variable declaration coverage
+- delimiter compatibility
+- renderability
+- optional semantic validators for module-specific output
+
 ## Best Practices
 
 - keep manifests in `template.json`
-- keep all generated content under `files/`
+- keep every generated file under `files/`
+- declare every variable that appears in rendered content
 - use the custom delimiter set consistently
-- hardcode tested upstream application versions in rendered files unless a variable is truly required
+- hardcode tested upstream application versions in rendered files unless a variable is actually needed
 - use `metadata.version.name` for the user-facing label
-- use the remaining `metadata.version` fields to track upstream dependency context when useful
+- use the remaining `metadata.version` fields for upstream snapshot context when useful

+ 62 - 42
.wiki/Core-Concepts-Variables.md

@@ -1,10 +1,10 @@
 # Variables
 
-Variables define the configurable inputs available to a template. In the current runtime they live in `template.json` under `variables`, grouped into sections with `items`.
+Variables are defined per template in `template.json` under the top-level `variables` array. There is no separate schema reference layer anymore, so the manifest is the source of truth.
 
-## Variable Layout
+## Runtime Shape
 
-Example:
+Each entry in `variables` is a group. Each group contains `items`.
 
 ```json
 {
@@ -24,7 +24,7 @@ Example:
           "name": "environment",
           "type": "enum",
           "title": "Environment",
-          "default": "prod",
+          "value": "stage",
           "config": {
             "options": ["prod", "stage", "dev"]
           }
@@ -35,15 +35,16 @@ Example:
 }
 ```
 
-Each group requires:
+Group fields:
 - `name`
 - `title`
+- `description`
+- `toggle`
+- `needs`
 - `items`
 
-Each item requires:
+Item fields:
 - `name`
-
-Common item fields:
 - `type`
 - `title`
 - `description`
@@ -54,11 +55,13 @@ Common item fields:
 - `extra`
 - `config`
 
-`title` is used as the prompt label. If `description` is omitted, the runtime falls back to `title`.
+Runtime mapping notes:
+- `title` is used as the prompt label
+- `description` is used as the display/help text
+- if `description` is omitted, the runtime falls back to `title`
 
-## Variable Types
+## Supported Types
 
-Supported types:
 - `str`
 - `int`
 - `float`
@@ -69,25 +72,37 @@ Supported types:
 - `hostname`
 - `secret`
 
-### Secret Variables
+Type notes:
+- `bool` defaults to `false` when omitted
+- other types default to empty unless a `default` or `value` is provided
+- `secret` values are masked in prompts and display output
+
+## `default` vs `value`
+
+Use `default` for the normal manifest default.
 
-Use `type: "secret"` for values that should be masked in prompts and output.
+Use `value` when the template should pin the runtime value directly. If both are present, `value` wins.
 
 Example:
 
 ```json
 {
-  "name": "api_token",
-  "type": "secret",
-  "title": "API token"
+  "name": "environment",
+  "type": "enum",
+  "title": "Environment",
+  "default": "prod",
+  "value": "stage",
+  "config": {
+    "options": ["prod", "stage", "dev"]
+  }
 }
 ```
 
 ## Config Object
 
-Extra behavior is defined under `config`.
+Additional behavior is configured through `config`.
 
-Supported config fields:
+Supported fields:
 - `placeholder`
 - `textarea`
 - `unit`
@@ -143,7 +158,7 @@ Supported config fields:
 }
 ```
 
-### Autogenerated Secret Values
+### Autogenerated Secrets
 
 `config.autogenerated` is supported only for `secret` variables.
 
@@ -180,22 +195,12 @@ Base64 example:
 }
 ```
 
-## Default Values and Overrides
-
-Value precedence is:
-
-1. module runtime defaults
-2. template defaults from `template.json`
-3. saved user defaults in `config.yaml`
-4. `--var-file`
-5. `--var`
+Rules:
+- autogenerated secrets cannot define `default`
+- base64 generation uses `bytes`, not `length`
+- `autogenerated` on non-secret variables is rejected
 
-Useful notes:
-- `default` defines the standard value
-- `value` lets a template pin a value more directly in the manifest
-- autogenerated secrets cannot also define a default
-
-## Dependencies
+## Dependencies with `needs`
 
 Both groups and items support `needs`.
 
@@ -207,14 +212,18 @@ Examples:
 
 Semantics:
 - semicolon means AND
-- comma means OR within the same comparison
-- `!=` means the current value must not match
+- comma means OR within a single comparison
+- `=` means the value must match
+- `!=` means the value must not match
 
-## Toggles
+Behavior notes:
+- group `needs` controls whether the group is considered available
+- item `needs` controls whether the item is shown and validated
+- disabled boolean items are reset to `false` unless they were explicitly set via CLI
 
-Groups may define `toggle`, but the toggle variable must exist as a boolean item in that same group.
+## Group Toggles
 
-Example:
+Groups may define `toggle`, but the referenced toggle variable must exist as a boolean item in that same group.
 
 ```json
 {
@@ -237,14 +246,25 @@ Example:
 }
 ```
 
-If a referenced toggle variable is not present, the runtime drops the toggle behavior.
+If the toggle variable is missing, the runtime ignores the toggle.
+
+## Override Order
+
+For a loaded template, values are applied in this order:
+
+1. `default` / `value` from `template.json`
+2. saved defaults from the active config file
+3. `--var-file`
+4. `--var`
+5. interactive prompt answers
 
 ## Inspecting Variables
 
-The fastest way to see the actual variables for a template is still:
+Use `show` against the template you care about:
 
 ```bash
 boilerplates compose show nginx
+boilerplates terraform show cloudflare-dns-record
 ```
 
-That reflects the template as the runtime sees it, including defaults, dependencies, and optional sections.
+That view reflects the actual runtime state for that template, including defaults, dependencies, toggle state, and file structure.

+ 0 - 4
.wiki/Home.md

@@ -20,10 +20,6 @@ The current runtime supports:
 - [Libraries](Core-Concepts-Libraries) - official and custom libraries, discovery, priority, and config
 - [Defaults](Core-Concepts-Defaults) - saved default values and precedence
 
-## Variable Discovery
-
-- [Variables](Variables) - how to inspect the variables a template actually exposes
-
 ## Additional Docs
 
 - [Repository README](https://github.com/ChristianLempa/boilerplates/blob/main/README.md)

+ 6 - 1
.wiki/Installation.md

@@ -283,11 +283,16 @@ Sync the default template library:
 boilerplates repo update
 ```
 
-This downloads all available templates to:
+This syncs the configured Git library checkouts under the active config directory's `libraries/` folder.
+
+Global-config default:
+
 ```
 ~/.config/boilerplates/libraries/
 ```
 
+If the CLI is using a local `./config.yaml`, the sync location becomes `./libraries/`.
+
 ### Shell Completion (Optional)
 
 Enable tab completion for your shell:

+ 0 - 35
.wiki/Variables.md

@@ -1,35 +0,0 @@
-# Variables Reference
-
-Boilerplates no longer publishes static schema-version variable reference pages.
-
-The current runtime is template-driven:
-- modules provide baseline variable behavior
-- each template can override defaults and add template-specific variables
-- the exact variable set depends on the template you are inspecting
-
-## How to Inspect Variables
-
-Use the CLI against the template itself:
-
-```bash
-boilerplates compose show nginx
-boilerplates terraform show cloudflare-dns-record
-boilerplates ansible show ubuntu-vm-core
-```
-
-This shows:
-- template metadata
-- rendered version label from `metadata.version.name` when present
-- the template file tree
-- the actual variable groups and items exposed by that template
-
-## Recommended Workflow
-
-1. List templates for a module.
-2. Show the template you want to use.
-3. Review defaults, dependencies, and optional sections.
-4. Generate with `--output`, `--var-file`, and `--var` as needed.
-
-## Why the Old Pages Were Removed
-
-The older wiki pages were generated from schema-version snapshots and no longer matched the current `template.json` runtime. They were removed to avoid documenting variables that may not exist, may have changed shape, or may be template-specific.

+ 0 - 3
.wiki/_Sidebar.md

@@ -11,6 +11,3 @@
 - [Variables](Core-Concepts-Variables)
 - [Libraries](Core-Concepts-Libraries)
 - [Defaults](Core-Concepts-Defaults)
-
-### Reference
-- [Variables](Variables)

+ 4 - 6
README.md

@@ -6,8 +6,7 @@ Create reusable templates and turn them into configurable workloads for homelabs
 
 Create reusable templates for infrastructure expertise like Docker, Kubernetes, Terraform, Ansible, Python, and more. Use the built-in *Jinja2-like* templating syntax with `<< >>` variables, `<% %>` blocks, and `<# #>` comments to keep configuration modular and conditional. Sync with Git in both directions or manage everything locally. Render templates, configure variables through a guided wizard, and wire up secrets. Copy them to remote servers and environments or any local directory.
 
-*Don't want to start from scratch?*
-> Explore 100+ template presets for homelabs and self-hosted infrastructure: https://github.com/ChristianLempa/boilerplates-library
+✨ Explore 100+ template presets for homelabs and self-hosted infrastructure: https://github.com/ChristianLempa/boilerplates-library
 
 ## Boilerplates CLI
 
@@ -15,10 +14,9 @@ The Boilerplates CLI is the main interface for working with template libraries l
 
 It combines template-defined variables and defaults, guided interactive prompts, CLI variable overrides, and git-backed template libraries into one workflow. In practice, that means you can keep reusable boilerplates in a repository and turn them into concrete, environment-specific configurations with a single command.
 
-> **WARNING**
-> Boilerplates `0.2.0` introduced the new template format. Legacy `template.yaml` / `template.yml` manifests and `.j2` template files are no longer supported.
->
-> New templates must use `template.json`, keep renderable content under `files/`, and use the custom *Jinja2*-like delimiters `<< >>`, `<% %>`, and `<# #>` instead of default *Jinja2* syntax.
+⚠️ Boilerplates `0.2.0` introduced the new template format. Legacy `template.yaml` / `template.yml` manifests and `.j2` template files are no longer supported.
+
+ℹ️ New templates must use `template.json`, keep renderable content under `files/`, and use the custom *Jinja2*-like delimiters `<< >>`, `<% %>`, and `<# #>` instead of default *Jinja2* syntax.
 
 ### Installation
 

+ 0 - 2
cli/core/template/template.py

@@ -203,7 +203,6 @@ class TemplateMetadata:
     library: str = "unknown"
     library_type: str = "git"
     draft: bool = False
-    guide: str = ""
     icon: dict[str, Any] = field(default_factory=dict)
 
     def __init__(
@@ -226,7 +225,6 @@ class TemplateMetadata:
         self.library = library_name or "unknown"
         self.library_type = library_type
         self.draft = bool(metadata.get("draft", False))
-        self.guide = str(metadata.get("guide", "")).rstrip("\n")
         self.icon = metadata.get("icon", {}) if isinstance(metadata.get("icon"), dict) else {}