Cecil logo

Cecil

Organization
Cecilapp
cecil

Build and configure Cecil static sites, with focused guidance for content, templates, and site generation.

Overview

PublisherCecilapp
RepositoryCecil
Skill namececil
Stars
299
Forks
32
Bundled files
Instructions only
LicenseEUPL-1.2
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by Cecilapp on GitHub. Read the source before you install it.

Installation

Install the Cecil AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Cecilapp/Cecil/tree/main/skills/cecil
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Cecilapp/Cecil.git /tmp/Cecil
mkdir -p .claude/skills
cp -r /tmp/Cecil/skills/cecil .claude/skills/cecil
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Cecil in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Cecil on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Cecil is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Cecil Site Builder

You are an expert Cecil developer capable of creating and generating static websites with Cecil, a PHP-based static site generator powered by Symfony components and Twig.

When to Use This Skill

Use this skill when:

  • Creating or scaffolding a new Cecil site
  • Building and generating static websites with Cecil
  • Configuring site settings, taxonomies, and content organization
  • Creating or updating Twig templates and layouts
  • Managing assets, including images and stylesheets
  • Deploying Cecil-generated static sites
  • Troubleshooting build issues or optimizing the performance of the generated site and build process
  • Working with Cecil's plugin/extension system

Project Structure

Directory Layout

my-site/
├── cecil.yml  # Main configuration file (or config.yml)
├── pages/     # Markdown pages
├── layouts/   # Twig templates
├── assets/    # Processed files (CSS, JS, images)
├── static/    # Static files copied as-is
├── data/      # Data collections (YAML/JSON/...)
└── extensions/ # Custom PHP classes (generators, Twig extensions, post-processors)

Key Directories

  • pages/ - Markdown content files organized into sections
  • layouts/ - Twig templates and partials
  • assets/ - Files handled by Cecil (Sass compilation, minification, image handling)
  • static/ - Files copied to output without transformation
  • data/ - Data files exposed in templates via site.data
  • extensions/ - Custom PHP classes autoloaded by Cecil (file path must match the class namespace, e.g. extensions/MyProject/Generator/CustomGenerator.php)

Cecil Fundamentals

Architecture

Cecil follows a build pipeline:

Builder → Steps → Generators → Renderer → Output
  • Steps (Step/): Sequential build phases, in this order

    • Load: pages, data files and static files
    • Pages Create / Convert: create pages collection, convert front matter and Markdown body
    • Taxonomies Create: build vocabularies and terms
    • Pages Generate: run generators (see below)
    • Menus Create: build navigation structures
    • StaticFiles Copy: copy static files
    • Pages Render / Save: render with Twig and write output files
    • Assets Save: save processed assets
    • Optimize: HTML, CSS, JS and images
  • Generators (Generator/): Page generators executed via priority queue

    • Generators are ordered by numeric weight; lower numbers execute first (e.g., DefaultPages at weight 10 runs before Alias at weight 80).
    • DefaultPages (10) → ExternalBody (30) → Section (40) → Taxonomy (50) → Homepage (60) → Pagination (70) → Alias (80) → Redirect (90)
  • Renderer (Renderer/): Twig-based rendering with custom extensions

  • Output: Built static site in _site/ directory

Content Model

  • Pages: Markdown files composed of front matter and body
  • Front matter: Metadata surrounded by separators (---, +++, or <!-- -->)
  • Section: Root folder in pages/ (e.g. pages/blog/post-1.md -> section blog)
  • File-based routing: Files under pages/ define generated paths
  • Collections: Pages, taxonomies, data and static files are exposed to templates

Nested Sections (Sub-sections)

A nested folder that explicitly contains an index.md file becomes a sub-section of its parent Section. A nested folder without an index.md file is not a sub-section: its pages simply belong to the parent section.

plaintext
pages/
└─ blog                 # Section "blog"
   ├─ index.md
   ├─ post-1.md         # Page in "blog"
   └─ 2024              # Sub-section (contains an "index.md")
      ├─ index.md
      └─ post-2.md      # Page in "blog" AND "blog/2024"

A sub-section:

  • Is a full Section (same type, variables, and layout resolution) available at its own URL (e.g. /blog/2024/)
  • Falls back to the templates of its parent sections (e.g. blog/list.html.twig for blog/2024 if blog/2024/list.html.twig doesn't exist)
  • Can be nested at any depth (e.g. blog/2024/06/)
  • Lists its own pages; those pages also belong to each parent section
  • Is not listed among the pages of its parent section

Sub-sections support the same front matter variables as any section (sortby, pagination, cascade, circular). Use cascade on a parent index.md to propagate variables down to sub-sections and their pages.

In templates, use page.parent, page.ancestors, page.sections and page.toplevel to build navigation, or include the ready-to-use partials/breadcrumb.html.twig partial.

Previous/next navigation (page.prev / page.next) follows the sections tree: the pages of a top level section and of all its sub-sections are chained depth-first, sorted with the section's sortby, each sub-section index page being followed by its own pages.

Front Matter

Front matter is YAML by default (pages.frontmatter also accepts ini, toml and json). Common variables:

VariablePurpose
title, descriptionTitle (default: file name without prefix and language suffix) and description
date, updatedCreation and update dates
layoutTemplate to use (see Lookup Rules)
slug, pathOverride the URL (values must already be slugified)
weightSort order
published / draftdraft: true is the same as published: false (preview with build --drafts / serve --drafts)
schedule{publish: <date>, expiry: <date>}: published if publish is reached and expiry is not passed
menuAdd the page to a menu (e.g. menu: main or menu: {main: {weight: 10}})
redirect, aliasRedirect the page to a URL; generate redirections from old paths to the page
outputOutput format(s) of the page (e.g. [html, json])
uglyurltrue to output page.html instead of page/index.html
excludedtrue to hide the page from lists, sitemap and feeds (formerly exclude)
language, langrefLanguage code (default: file suffix) and translations reference

A file name prefix sets the date (2019-04-23_My post.md) or the weight (1_First project.md) of the page; separators are - and _ by default (pages.prefix.separator).

A section (<section>/index.md) also accepts:

  • sortby: date (default), updated, title or weight, or a map {variable: date, desc_title: false, reverse: false} (desc_title sorts pages with the same date by descending title); the site default is pages.sortby
  • pagination: {max: 5, path: page} to override pages.pagination, false to disable it, or {enabled: true} to enable it when it is disabled globally (pages.pagination: false)
  • cascade (variables propagated to the section pages) and circular (circular prev/next navigation)

Configuration

Configuration is defined in cecil.yml or config.yml at project root:

  • Core options are top-level keys such as title, baseurl, description, taxonomies, menus
  • Dot notation in templates applies to site variable access (for example site.title)
  • Defaults (including generators, default pages and output formats) are defined in config/default.php; priority is defaults < themes config.yml < site config < env vars (CECIL_*)
  • Maps are merged key by key with the defaults; a boolean applied to a map toggles its enabled key (e.g. cache: false); in templates, check it with the enabled test ({% if config.cache is enabled %})
  • A list of values (pages.ext, output.pagetypeformats.<type>, assets.images.responsive.widths…) replaces the default list but is added to the theme list; a map of booleans adjusts the default or theme list instead (e.g. homepage: {rss: true, atom: false})
  • output.formats entries are merged by name: override a default format with only its name and the properties to change
  • Deprecated options (renamed, moved or removed, listed in config/deprecated.php) make the build fail, with a message giving the source (site, theme or env vars) and the new YAML snippet to use
  • Pages without content file (front matter only) are declared in pages.default, indexed by ID (pages.virtual is removed); like any page they are listed (showable, sitemap, homepage) unless they set excluded: true
  • A page is "generated" (page.generated, formerly page.virtual) when it has no content file (section, term, page declared in configuration, etc.) or is a paginated page

Building a Cecil Site

Step 1: Download Cecil

Download Cecil using curl:

bash
curl -LO https://cecil.app/cecil.phar
chmod +x cecil.phar

If PHP is not available, use the standalone binary for the platform instead (e.g.: cecil-linux-x64, cecil-macos-arm64, cecil-windows-x64.exe from the latest GitHub release), and replace php cecil.phar with ./cecil in the commands below. The binary has no self-update command, only supports GD for image processing, and its serve command requires a local PHP executable (for the PHP built-in web server).

Step 2: Create a New Site

Use the new:site command to scaffold a new website:

bash
php cecil.phar new:site

Step 3: Configure the Site

Edit cecil.yml:

yaml
title: My Site
baseurl: https://example.com/
description: My awesome static site
taxonomies:
  categories: category
  tags: tag

Step 4: Create Content

Create a page with:

bash
php cecil.phar new:page

Then edit the generated file in pages/:

markdown
---
title: My First Post
description: Welcome to my blog
date: 2024-05-14
tags: [Welcome, "First post"]
---
# My First Post

This is my first post content.

Step 5: Create Templates (Optional)

Cecil ships with built-in templates (resources/layouts/), so a site builds without any template in layouts/. Only create templates to customize the rendering, and prefer extending the built-in ones (see Built-in Templates).

Create Twig templates in layouts/ (for example layouts/page.html.twig):

twig
<!DOCTYPE html>
<html lang="{{ site.language }}">
  <head>
    <meta charset="utf-8">
    {# generates <title>, description, canonical, Open Graph, etc. #}
    {{ include('partials/metatags.html.twig') }}
  </head>
  <body>
    <header>
      <h1>{{ site.title }}</h1>
    </header>
    <main>
      {{ page.content }}
    </main>
    <footer>
      <p>&copy; {{ site.title }}</p>
    </footer>
  </body>
</html>

Step 6: Build the Site

bash
php cecil.phar build

Output is generated in _site/ directory.

CLI Commands

CommandPurpose
php cecil.phar new:siteCreate a new website (--demo to add demo content)
php cecil.phar new:pageCreate a new page
php cecil.phar buildBuild the static site (--drafts, --optimize, --baseurl, --output, --config, --page, --dry-run, --clear-cache)
php cecil.phar serveStart local server with live reload (same options as build, plus --open, --host, --port, --no-watch)
php cecil.phar serve --incrementalServe with incremental builds (rebuild only changed pages)
php cecil.phar serve --backgroundRun the server in the background (serve:log to show its log)
php cecil.phar serve:stopStop the local server
php cecil.phar editOpen pages directory with the configured editor
php cecil.phar show:configDisplay effective configuration (--key=pages.dir to show a single key)
php cecil.phar show:contentDisplay content tree (pages, data, static files)
php cecil.phar doctorDiagnose site and environment (see also doctor:frontmatter, doctor:seo, doctor:links, doctor:cache)
php cecil.phar cache:clearClear all cache files (or only cache:clear:assets, cache:clear:templates, cache:clear:translations)
php cecil.phar clearRemove generated files (or only clear:output, clear:temporary / clear:tmp)
php cecil.phar util:templates:extractExtract built-in templates into layouts/ (Phar and standalone binary only)
php cecil.phar util:translations:extractExtract translation strings from templates
php cecil.phar aboutDisplay Cecil version and environment information
php cecil.phar self-updateUpdate Cecil to the latest version (Phar only)

doctor:links and doctor:seo return a failure exit code according to --fail-on: bad (default), ok, feedback or never. doctor:links also accepts --page, --exclude, --format=json and --include-generated; its defaults can be set under the doctor.links configuration key.

Template Development

Twig templates live in layouts/ and follow Cecil naming conventions.

Naming Convention

Use this pattern:

plaintext
layouts/(<section>/)<type>|<layout>.<format>(.<language>).twig

Examples:

  • layouts/page.html.twig - default page template
  • layouts/list.html.twig - section/home listing template
  • layouts/blog/list.atom.twig - Atom feed template for blog section (RSS is not output by default: enable it with output.pagetypeformats.section: {rss: true})
  • layouts/page.html.fr.twig - French page template
  • layouts/_default/page.html.twig - fallback template

Lookup Rules (How Cecil Chooses a Template)

Cecil uses the first existing template, in priority order, for each page type. <layout> is the front matter layout variable, and each entry resolves to <name>.<format>.twig (e.g. blog/list.html.twig):

Page typeLookup order
Homepage<layout> → index → home → list → _default/<layout> → _default/index → _default/home → _default/list → _default/page
Page<section>/<layout> → <layout> → <section>/page → _default/<layout> → page → _default/page
Section<layout> → <section>/index → <section>/list → section/<section> → <parent>/index → <parent>/list → section/<parent> → _default/section → list → _default/list
Vocabularytaxonomy/<plural> → vocabulary → _default/vocabulary
Termtaxonomy/<term> → taxonomy/<singular> → term → _default/term → _default/list

For a sub-section, <section> is its full path (e.g. blog/2024) and the <parent> entries are repeated for each parent section, nearest first.

To render a section with the templates of another section, map it with layouts.sections (applies to the section and its pages):

yaml
layouts:
  sections:
    news: blog # "news" uses blog/list.html.twig and blog/page.html.twig

Each candidate is searched in layouts/ (site), then in themes/<theme>/layouts/, then in Cecil's built-in templates (resources/layouts/). Most _default/* templates exist built-in, which is why a site renders without any custom layout.

In practice, you usually need only:

  • layouts/page.html.twig
  • layouts/list.html.twig
  • optional overrides per section

Built-in Templates

Cecil embeds default templates in resources/layouts/. They are always available to Twig (lowest priority, after site and theme layouts), so they can be rendered, included or extended without being copied into layouts/.

  • _default/ - fallback layouts: page.html.twig, list.html.twig, home.html.twig, vocabulary.html.twig, term.html.twig, 404.html.twig, 404.json.twig, redirect.html.twig, feeds (list.atom.twig, list.rss.twig, list.jsonfeed.twig), embeds (page.embed.twig, page.oembed.twig), JSON/Markdown/LLMs outputs, sitemap.xml.twig, robots.txt.twig, etc.
  • partials/ - reusable fragments (see Built-in Partials)
  • extended/ - base templates extended by other built-ins (feed.twig, extended by the Atom, RSS and JSON Feed templates)
  • components/ - none built-in, but site and theme components live in layouts/components/
  • shortcodes.twig - built-in shortcodes

Rules to follow:

  1. Don't recreate what already exists: before writing a template, check whether a built-in one covers the need (feeds, sitemap, robots.txt, 404, redirects, JSON outputs are already provided).

  2. Extend rather than copy: _default/page.html.twig exposes the blocks head, head_metatags, head_css, header, content and footer.

    twig
    {# layouts/page.html.twig #}
    {% extends '_default/page.html.twig' %}
    {% block content %}
      <article>{{ page.content }}</article>
    {% endblock %}
  3. Don't shadow a built-in template by accident: a site file with the same path (e.g. layouts/_default/page.html.twig or layouts/partials/metatags.html.twig) fully replaces the built-in one for the whole site, and can't extends itself.

  4. Extract only as a last resort: php cecil.phar util:templates:extract copies all built-in templates into layouts/; the copies then no longer receive Cecil updates.

Metatags (partials/metatags.html.twig)

Always use the built-in partials/metatags.html.twig partial in the <head> of HTML layouts instead of hand-writing SEO/social tags. It is already included by _default/page.html.twig (block head_metatags).

twig
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  {{ include('partials/metatags.html.twig') }}
</head>

It generates:

  • <title> (page title + divider + site title; site title + baseline on the homepage; page number on paginated lists)
  • description, keywords (from tags), author, robots (noindex on paginated pages; pages with a noindex metatags.robots are also excluded from the sitemap)
  • favicons (from favicon.ico, favicon.svg, favicon.png assets, resized)
  • prev/next/first/last links, canonical and alternate formats, feeds, hreflang alternates
  • rel=me links, Open Graph, Facebook, Twitter/X Card, Fediverse creator
  • optional Dublin Core and JSON-LD structured data

Important:

  • Never add a separate <title>, <meta name="description">, canonical or og:* tags next to this partial: they would be duplicated.

  • Feed it through front matter (page) or configuration (site fallback): title, description, tags, author, image, canonical.url, social.*.

  • Tune it with the metatags configuration (per page with front matter metatags):

    yaml
    metatags:
      title:
        divider: " &middot; "
        only: false        # page title only
      robots: "index,follow"
      image: true          # default social image (`false` to disable)
      favicon: true        # or `{enabled: true, image: favicon.png, sizes: {...}}`
      og: true
      twitter: true
      mastodon: true
      articles: "blog"     # site config only: section(s) rendered as Open Graph "article" and JSON-LD `BlogPosting`
      dc: false            # Dublin Core
      data: false          # JSON-LD structured data (`true` or a map of options)
  • JSON-LD structured data (metatags.data) is a @graph of linked entities (WebSite, publisher, WebPage/CollectionPage, BreadcrumbList, BlogPosting, VideoObject, main menu ItemList). For a personal site, set the publisher as a Person; per page, set a specific type (AboutPage, ContactPage, etc.):

    yaml
    metatags:
      data:
        article: BlogPosting   # or NewsArticle
        publisher:
          type: Person         # Organization by default
          name: "Jane Doe"     # site title (or site author for a Person) by default
          logo: logo.png       # favicon image by default
    yaml
    # page front matter
    metatags:
      data:
        type: AboutPage
  • Override title or image for a specific template:

    twig
    {{ include('partials/metatags.html.twig', {title: 'Custom title', image: og_image}) }}
  • Customize one part with embed and its blocks (title, description, metatags_favicon, metatags_alternates, metatags_og, metatags_twitter, metatags_dc, metatags_structured_data), instead of copying the whole file:

    twig
    {% embed 'partials/metatags.html.twig' %}
      {% block metatags_twitter %}{% endblock %}
    {% endembed %}
  • Run php cecil.phar doctor:seo to check the generated metatags.

See the metatags documentation for all options.

Template Variables

Most useful variables in Twig:

  • site.title, site.baseurl, site.description
  • site.pages - pages collection (current language)
  • site.allpages - pages in all languages
  • site.taxonomies - vocabularies and terms
  • site.menus.<name> - menu entries (a main menu is created automatically with the homepage and the top-level sections, unless they define their own menu variable)
  • page.title, page.date, page.content, page.path, page.type, page.section

Multilingual Sites

Configure languages in cecil.yml:

yaml
language: en
languages:
  - code: en
    name: English
    locale: en_US
  - code: fr
    name: Français
    locale: fr_FR

Use suffixed filenames for translations:

plaintext
pages/about.md
pages/about.fr.md

You can render a language switcher in templates with:

twig
{% include 'partials/languages.html.twig' %}

Useful collection helpers:

  • site.pages.showable to skip draft/paginated/redirect/excluded pages (generated sections and taxonomies are included)
  • sort_by_weight filter for menu entries

Example Template

twig
{# layouts/page.html.twig #}
<!DOCTYPE html>
<html lang="{{ site.language }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    {# no <title> here: metatags.html.twig generates it #}
    {{ include('partials/metatags.html.twig') }}
  </head>
  <body>
    <header>
      <h1><a href="{{ url('/') }}">{{ site.title }}</a></h1>
      {% if site.menus.main is defined %}
      <nav>
        <ul>
        {% for entry in site.menus.main|sort_by_weight %}
          <li><a href="{{ url(entry.url) }}">{{ entry.name }}</a></li>
        {% endfor %}
        </ul>
      </nav>
      {% endif %}
    </header>
    <main>
      <article>
        <h2>{{ page.title }}</h2>
        {% if page.date %}
          <time datetime="{{ page.date|date('c') }}">{{ page.date|date('Y-m-d') }}</time>
        {% endif %}
        {{ page.content }}
      </article>
    </main>
  </body>
</html>

Built-in Partials and Utilities

Available in every site, without extraction (include them rather than rewriting them):

  • partials/metatags.html.twig - all <head> SEO/social tags, including <title> (see Metatags)
  • partials/alternates.html.twig - canonical and alternate formats links (included by metatags)
  • partials/alternates-languages.html.twig - hreflang links (included by metatags)
  • partials/feeds-from-section.html.twig - section feeds links (included by metatags)
  • partials/jsonld.js.twig - JSON-LD structured data (included by metatags when metatags.data is enabled)
  • partials/navigation.html.twig - main menu navigation
  • partials/page-navigation.html.twig - previous/next page links (follows the sections tree)
  • partials/paginator.html.twig - pagination links
  • partials/languages.html.twig - language switcher
  • partials/breadcrumb.html.twig - breadcrumb (nested sections aware)
  • partials/terms-list.html.twig - taxonomy terms list
  • partials/theme-selector.html.twig - light/dark theme toggle
  • partials/googleanalytics.js.twig - Google Analytics snippet
  • partials/data.json.twig - page data serialized as JSON (used by JSON outputs)
  • partials/pico.css.twig, partials/highlight.css.twig - CSS used by the default layouts
  • partials/notes/<type>.html.twig - rendering of the Markdown :::<type> notes (tabs, code-group, steps, accordion, cards), rendered during conversion with a note variable only (no page or site); add one to render another note type. To nest notes, open the parent with a longer fence (::::, then :::::…): a note is closed by a line with at least as many : as its opening line

If a built-in template really needs to be modified, extract them all into layouts/ (last resort, see Built-in Templates):

bash
php cecil.phar util:templates:extract

Pagination

Pagination is configured globally under pages.pagination (defaults: max: 5, path: page), and can be overridden in section front matter (see Front Matter).

yaml
pages:
  pagination:
    max: 10

pages.pagination: false disables it globally; a section can then enable it again with pagination: {enabled: true}.

In templates, paginated lists expose page.paginator (pages, count, current, links).

In list templates, include paginator links with:

twig
{% include 'partials/paginator.html.twig' %}

Custom Filters and Functions

Cecil Twig helpers commonly used in templates (full list in the templates reference):

  • Functions: url() (internal/absolute URLs), asset() (reference and process assets), html() (HTML element of an asset: <img>, <picture>, <link>, <script>), image(), css(), js(), integrity(), readtime()
  • Asset filters: resize, webp, avif, lqip, dataurl, inline, minify, fingerprint, to_css
  • Content filters: markdown_to_html, toc, excerpt, excerpt_html, slugify, highlight
  • Collection filters: filter_by, sort_by_date, sort_by_title, sort_by_weight
  • Tests: enabled (config options), external (URL or menu entry outside the site), asset, image_large, image_square
twig
{{ html(asset(page.image), {alt: page.title}, {responsive: true, formats: ['webp']}) }}

The html filter (asset|html) is deprecated: use the html() function.

Components

Reusable Twig components are stored in layouts/components/ (e.g. layouts/components/button.twig) and used with the x tag or an HTML-like syntax; the slot variable holds the inner content and attributes the passed attributes:

twig
{% x:button with {class: 'primary'} %}Click me{% endx %}
<x-button class="primary">Click me</x-button>

Build Optimization

Asset Processing

Configure asset optimization:

yaml
assets:
  minify: true
  fingerprint: true
  compile:
    style: compressed
  images:
    optimize: true

Image Variants (Dark and Mobile)

Cecil can serve alternative versions of an image in a <picture> element, when files with a configured suffix exist next to the original image (e.g. photo.dark.jpg, photo.mobile.jpg, photo.mobile.dark.jpg):

yaml
layouts:
  images:                          # images rendered with the `html` Twig function
    dark_suffix: .dark             # adds <source media="(prefers-color-scheme: dark)">
    mobile_suffix: .mobile         # adds <source> with the mobile media query
    mobile_media_query: "(max-width: 767px)"
pages:
  body:
    images:                        # same options for images in Markdown content
      dark_suffix: .dark
      mobile_suffix: .mobile

Both are disabled by default (null).

Code Syntax Highlighting

Fenced code blocks are highlighted at build time (pages.body.highlight: true by default). The engine can be changed:

yaml
pages:
  body:
    highlight:
      engine: tempest  # `highlightjs` (default, `hljs-*` CSS classes) or `tempest` (fewer languages, `hl-*` CSS classes)

The theme CSS must match the engine's classes (the built-in partials/highlight.css.twig handles both).

In templates, the highlight filter does not use this option: its engine is set by layouts.highlight.engine (highlightjs by default) or by its engine argument: {{ code|highlight('php', 'tempest') }}.

Performance Tips

  1. Use draft: true to exclude non-published content from builds
  2. Enable output optimization in production (optimize: true or build --optimize); asset minification and fingerprinting are already enabled by default
  3. Use output and format settings adapted to your pages types
  4. Use responsive image options and image optimization when needed

Extension & Plugins

Custom Generators

Extend Cecil by creating custom generators:

php
<?php

namespace MyProject\Generator;

use Cecil\Generator\AbstractGenerator;

class CustomGenerator extends AbstractGenerator
{
    public function generate(): void
    {
        // Custom generation logic
    }
}

Save it as extensions/MyProject/Generator/CustomGenerator.php, then register it in configuration with pages.generators.

yaml
pages:
  generators:
    100: MyProject\Generator\CustomGenerator

Note: use single backslashes in YAML. Double backslashes (\\) are only needed inside JSON or PHP strings.

Twig Extensions and Post-processors

You can also extend Twig (via layouts.extensions) and post-process output (via output.postprocessors).

yaml
layouts:
  extensions:
    MyExtension: MyProject\Twig\MyExtension

The Twig extension class should implement Twig\Extension\ExtensionInterface (or extend Twig\Extension\AbstractExtension).

yaml
output:
  postprocessors:
    MyProcessor: MyProject\Renderer\PostProcessor\MyProcessor

Post-processors should implement Cecil\Renderer\PostProcessor\PostProcessorInterface.

Deployment

Static Site Hosting

Cecil generates pure static HTML, compatible with:

  • GitHub Pages
  • Netlify
  • Vercel
  • AWS S3
  • Any web server

Build & Deploy Workflow

bash
# Check internal links (add --internal --external to also check external links); fails on broken links
php cecil.phar doctor:links

# Build
php cecil.phar build

# Deploy output directory (_site/)
# to your hosting platform

GitHub Pages Example

bash
php cecil.phar build
# Commit _site/ directory and push to gh-pages branch

Code Quality Standards

When extending or contributing to Cecil:

  • Follow PSR-12 coding standards
  • Use declare(strict_types=1); in all PHP files
  • Include proper PHPDoc blocks for all classes and methods
  • Use 4-space indentation for PHP, 2-space for YAML/Twig

Useful Resources

Common Workflows

Create a Blog

  1. Create pages/blog/index.md for blog section
  2. Add individual posts in pages/blog/post-*.md
  3. Configure taxonomy for tags/categories
  4. Rely on built-in _default/list.html.twig and _default/page.html.twig, or extend them in layouts/blog/
  5. Build with php cecil.phar build

Add Custom Pages

  1. Create markdown files in pages/ directory
  2. Add front matter with title and, if needed, layout
  3. If needed, create a template in layouts/ (preferably extending a built-in one, with partials/metatags.html.twig in <head>)
  4. Let lookup rules pick the template, or set layout: <name> in front matter
  5. Build to generate output

Use Dynamic Content (Twig in Page Body)

By default, Twig syntax in a Markdown body is output as is. To evaluate it, create a dedicated layout and use it only for pages that need it:

twig
{# layouts/dynamic.html.twig #}
{% extends '_default/page.html.twig' %}

{% block content %}
{{ include(template_from_string(page.content, "dynamic content for page " ~ page.id)) }}
{% endblock content %}

Then set layout: dynamic in the page front matter. The body can use page.*, site.*, functions, filters and Twig tags.

Caveats (Markdown is converted to HTML before Twig rendering):

  • <, > and => are escaped: comparisons and arrow functions fail (Unexpected character "&")
  • Twig expressions inside HTML attributes (e.g. href="{{ url(post) }}") are URL-encoded and not evaluated
  • An expression alone on its line is wrapped in <p>: wrap it in an HTML element separated by line breaks
  • Twig in code spans/blocks is evaluated: use {% verbatim %} to display it as is

Keep the body simple and move complex logic into a partial template or a macro. See the dynamic content documentation.

Implement Search

  1. Create pages/search.md with front matter layout: search and output: json
  2. Use JavaScript library (e.g., Lunr.js) on frontend
  3. Create layouts/search.json.twig that iterates site.pages.showable and emits a JSON array of {title, url, content} objects
  4. Add search functionality to templates

Troubleshooting

When a user reports unexpected behavior or asks about a specific feature, ask them to run php cecil.phar doctor and include the output. If you are uncertain whether a feature is available in the user's Cecil version, say so explicitly and direct them to the official documentation at https://cecil.app/documentation/ rather than guessing version ranges.

Common Issues

  • Site not generating: Check cecil.yml syntax and configuration
  • Deprecated configuration option(s): move or remove the listed options as shown in the error message
  • Missing pages: Ensure content files are in pages/ directory
  • Template not loading: Verify the layout front matter variable, template naming and lookup rules
  • Build errors: Run php cecil.phar build -vv for verbose output
  • Cache issues: Clear cache with php cecil.phar cache:clear
  • Broken links or anchors: Run php cecil.phar doctor:links (internal links) or php cecil.phar doctor:links --internal --external

Debug Output

Get detailed build information:

bash
php cecil.phar build -v    # Verbose
php cecil.phar build -vv   # Very verbose
php cecil.phar build -vvv  # Debug

Frequently asked questions

What does the Cecil AI skill do?

Build and configure Cecil static sites, with focused guidance for content, templates, and site generation.

Why use Cecil on TypingMind?

Because you install it once and use it with any model. Cecil is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Cecil in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Cecilapp/Cecil/tree/main/skills/cecil. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Cecil?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Cecil?

As many as you like. As long as a model supports skills, you can use Cecil with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Cecil AI skill free?

Yes. It is published on GitHub by Cecilapp under the EUPL-1.2 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇