lekton-sync
CLI tool to sync markdown documents, prompt definitions, and schema artifacts to a Lekton instance.
It scans a directory for .md files, prompt YAML files, and schema manifests, calls the Lekton sync APIs to compute the delta, and uploads only the content that has changed.
Installation
Docker
Build the default minimal container image from the repository root:
Build a Jenkins-friendly variant with a debian:bookworm-slim runtime:
Run it by mounting the documentation workspace and passing the usual environment variables:
This image contains only the lekton-sync binary in a distroless runtime, so it fits well in documentation CI pipelines without installing Rust toolchains on the runner.
When you need a shell-capable image for CI systems that keep sidecars alive with commands such as sleep infinity, use the ci target instead. It ships the same lekton-sync binary on top of debian:bookworm-slim.
Tagged releases can also publish a ready-to-use Docker image via GitHub Actions:
The publish workflow also builds a companion lekton-sync-ci image for Jenkins/Kubernetes-style runners.
Usage
| Argument | Default | Description |
|---|---|---|
ROOT |
. |
Root directory to scan for markdown files, prompt definitions, and schema manifests |
--archive-missing |
— | Archive documents present in Lekton but not found locally |
--dry-run |
— | Show what would be done without making any changes |
--config <PATH> |
<ROOT>/.lekton.yml |
Path to config file |
-v, --verbose |
— | Verbose output |
Environment variables
| Variable | Required | Description |
|---|---|---|
LEKTON_TOKEN |
Yes | Service token for authentication |
LEKTON_URL |
Yes* | Base URL of the Lekton server |
*Can also be set via url in .lekton.yml.
Document format
Each markdown file must have a YAML front matter block. Files without front matter are skipped.
title: My Document
slug: optional/custom-slug # defaults to file path relative to root
access_level: public # defaults to "public"
service_owner: my-team # optional
tags: [guide, onboarding] # optional
order: 10 # optional, for ordering within a section
is_hidden: false # optional
Document body...
The slug is derived from the file path relative to the root directory (e.g. docs/guides/intro.md → docs/guides/intro), unless overridden by the slug field in the front matter.
Prompt format
Prompt definitions are loaded from prompts/*.yaml by default. This directory can be changed via .lekton.yml.
name: Code Review
description: Review a patch before merge
owner: platform-team
access_level: developer
status: active
publish_to_mcp: true
default_primary: true
context_cost: medium
tags:
variables:
- name: diff
description: Unified diff to inspect
required: true
prompt_body: |
Review the following diff:
{{diff}}
Supported prompt fields:
| Field | Required | Description |
|---|---|---|
name |
No | Human-readable prompt name. Defaults to the file slug segment. |
description |
Yes | Short description shown in Lekton. |
owner |
Yes* | Owning team or service. Falls back to default_service_owner if configured. |
access_level |
No | Falls back to default_access_level, then public. |
status |
No | Prompt lifecycle status. Defaults to active. |
tags |
No | Optional tags array. |
variables |
No | Optional variable descriptors (name, description, required). |
publish_to_mcp |
No | Publish this prompt to MCP discovery/context. Defaults to false. |
default_primary |
No | Include in the default MCP context unless hidden by the user. Defaults to false. |
context_cost |
No | Prompt context weight hint. Defaults to medium. |
slug |
No | Overrides the slug derived from the prompt file path. |
lekton-import |
No | Set to false to skip the prompt. |
prompt_body |
Yes | Raw prompt body uploaded to Lekton. |
The prompt slug is derived from the path relative to the prompt directory and prefixed with prompts/ by default (for example prompts/code-review.yaml → prompts/code-review).
Configuration file
Place a .lekton.yml file in the root directory (or pass --config) to set project-level defaults:
url: https://lekton.example.com
default_access_level: internal
default_service_owner: platform-team
slug_prefix: protocols/my-service
prompts_dir: prompts
prompt_slug_prefix: prompts
archive_missing: false
| Field | Description |
|---|---|
url |
Base URL of the Lekton server (overridden by LEKTON_URL) |
default_access_level |
Fallback access level when not set in front matter |
default_service_owner |
Fallback service owner when not set in front matter |
slug_prefix |
Prefix prepended to every document slug |
prompts_dir |
Directory containing prompt YAML files, relative to ROOT |
prompt_slug_prefix |
Prefix prepended to every prompt slug |
archive_missing |
Archive documents not found locally (overridden by --archive-missing) |
schemas_dir |
Directory containing schema manifests, relative to ROOT |
schema_name_prefix |
Prefix prepended to every schema name |
archive_missing_schemas |
Archive schema versions not found locally (overridden by --archive-missing) |
Schema format
Schema versions are discovered from lekton.schema.yml manifests under schemas/ by default.
name: payment-api
schema_type: openapi
service_owner: payments
default_access_level: internal
tags:
versions:
- file: openapi-v1.yaml
version: 1.0.0
status: deprecated
access_level: public
- file: openapi-v2.yaml
version: 2.0.0
status: stable
Supported manifest fields:
| Field | Required | Description |
|---|---|---|
name |
No | Schema name. Defaults to the manifest directory relative to schemas_dir. |
schema_type |
Yes | One of openapi, asyncapi, jsonschema. |
service_owner |
Yes* | Owning team/service. Falls back to default_service_owner. |
default_access_level |
No | Default applied to versions without access_level. |
tags |
No | Schema-level tags stored in Lekton metadata. |
versions[].file |
Yes | Relative path to the spec file from the manifest directory. |
versions[].version |
Yes | Semantic or release version string shown in the registry. |
versions[].status |
No | Version lifecycle: stable, beta, deprecated. Defaults to stable. |
versions[].access_level |
No | Per-version access level. Falls back to default_access_level, then project defaults, then public. |
The CLI computes separate content and metadata hashes for each schema version, calls POST /api/v1/schemas/sync, and uploads only new or changed versions.
Example
# Preview what would change
# Sync and archive documents/prompts no longer present locally
GitHub Actions example
- name: Sync docs to Lekton
run: |
docker run --rm \
-e LEKTON_URL="${{ secrets.LEKTON_URL }}" \
-e LEKTON_TOKEN="${{ secrets.LEKTON_TOKEN }}" \
-v "${{ github.workspace }}:/workspace" \
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/lekton-sync:${{ github.ref_name }} \
--archive-missing /workspace/docs
License
AGPL-3.0