DEV Community

Cover image for avanti: One YAML Spec, Files from Anywhere
Daniel Schroeder
Daniel Schroeder

Posted on

avanti: One YAML Spec, Files from Anywhere

You have a renovate.json in 30 repos. A platform team owns the canonical version. Six months later, half those repos are running a config from March that nobody updated. One team added a custom rule. Another deleted a section by accident. Nobody knows which version is "right" anymore.

This is config drift. By the time anyone looks, the configs have diverged.

What avanti does

avanti is a CLI tool that pulls local files from a declarative YAML spec. You describe what each file should contain and where it comes from. Then you run avanti pull.

It has two commands:

  • avanti diff — shows a colored git-diff-style preview of what would change, exits 1 if changes exist
  • avanti pull — fetches everything, shows the diff, and asks for confirmation before writing

No daemon, no server, no agent. Just a spec file and two commands.

Sources

A src value can be an HTTP URL, a local path, a GitHub file, a GitLab file, a shell command, or inline raw content. You pick per entry.

files:
  # HTTP
  - src: https://example.com/base-config.yml
    target: base-config.yml

  # Local path
  - src: ~/shared/scripts/deploy.sh
    target: scripts/deploy.sh
    mode: '0755'

  # GitHub
  - src:
      github:
        repo: org/standards
        file: eslint.config.js
        ref: main

  # Shell command
  - src:
      exec: aws ssm get-parameter --name /app/db-config --with-decryption --query Parameter.Value --output text
    target: config/db.json
    mode: '0600'

  # Inline content
  - src:
      raw: |
        # THIS FILE IS MANAGED — run `avanti pull` to update
    target: header.txt
Enter fullscreen mode Exit fullscreen mode

You can also combine multiple sources into one file by passing src as a list. They're fetched in order and joined with a newline.

Variables

Define reusable values once, reference them everywhere with $name. Environment variables use $env:NAME.

variables:
  standards_ref: v2.4.1
  region: eu-west-1

files:
  - src:
      github:
        repo: org/standards
        file: renovate.json
        ref: $standards_ref

  - src:
      github:
        repo: org/infra
        file: k8s/deployment-template.yaml
        ref: $env:DEPLOY_VERSION
    target: k8s/deployment.yaml
    replace:
      - from: '{REGION}'
        to: $region
      - from: '{ENV}'
        to: $env:ENVIRONMENT
Enter fullscreen mode Exit fullscreen mode

Bump standards_ref in one place. Run avanti diff. See every file that will change before you touch anything.

Post-processing

Each entry supports a replace list for string or regex substitutions, and a post field to pipe content through a shell script. Both run after fetching, before writing.

- src: https://example.com/template.yml
  target: config.yml
  replace:
    - from: '{EMAIL}'
      to: $email
    - from: /\d+\.\d+\.\d+/
      to: $env:APP_VERSION
  post: yq e '.metadata.name = "my-app"' -
Enter fullscreen mode Exit fullscreen mode

Use case 1: A global CLAUDE.md your whole team actually shares

Per-project CLAUDE.md files should contain project-specific context. But company-wide coding standards, team conventions, and security guidelines don't belong there — they belong in a global ~/.claude/CLAUDE.md that every developer on the team carries. The problem is keeping that file in sync across 20 machines.

avanti solves this by assembling the global file from central sources. Each developer has a personal .avanti.yml (or the team ships one via onboarding). Running avanti pull rebuilds the file from the current canonical versions.

variables:
  team: backend
  oncall_channel: '#backend-oncall'

files:
  - src:
      - raw: |
          # AI Assistant Guidelines
          <!-- THIS FILE IS MANAGED — run `avanti pull` to update -->
      - gitlab:
          project: platform/ai-standards
          file: teams/backend-rules.md
          ref: main
      - github:
          repo: org/shared-prompts
          file: company-standards.md
          ref: main
      - raw: |
          ## Team Context
          Team: $team
          Oncall: $oncall_channel
    target: ~/.claude/CLAUDE.md
Enter fullscreen mode Exit fullscreen mode

The platform team updates backend-rules.md once. Every developer who runs avanti pull gets it. No Slack message asking which version is current, no six-month-old guidelines silently telling the AI the wrong thing.

Use case 2: Pinned shared tooling configs

variables:
  standards_ref: v2.4.1

files:
  - src:
      github:
        repo: org/standards
        file: renovate.json
        ref: $standards_ref

  - src:
      github:
        repo: org/standards
        file: eslint.config.js
        ref: $standards_ref

  - src:
      github:
        repo: org/standards
        file: tsconfig.base.json
        ref: $standards_ref
Enter fullscreen mode Exit fullscreen mode

All files pinned to v2.4.1. When the platform team cuts v2.5.0, projects bump one variable, diff, and apply. No manual file hunting.

Use case 3: Drift detection in CI

files:
  - src:
      - raw: |
          # THIS FILE IS MANAGED — run `avanti pull` to update
      - github:
          repo: org/ci-templates
          file: workflows/security-scan.yml
          ref: main
    target: .github/workflows/security-scan.yml
Enter fullscreen mode Exit fullscreen mode

Add avanti diff as a CI step. If a developer edited the managed file by hand, the pipeline fails. The fix is to run avanti pull, not to argue about whose edit was right.

Atomic writes

avanti stages all files to a temp directory first. If any source fails, nothing gets written. You either get a full successful update or nothing changes. No half-applied state.

Working directory and path safety

All src and target paths resolve relative to the working directory, not the config file's location. This lets you use one shared config across many projects:

for dir in services/*/; do
  avanti -c shared/avanti.yml -w "$dir" pull --yes
done
Enter fullscreen mode Exit fullscreen mode

Target paths cannot escape the working directory. A target of ../../etc/passwd is an error. Absolute targets are only allowed when the working directory is /.

Install

npm install -g @udondan/avanti
Enter fullscreen mode Exit fullscreen mode

Or run without installing:

npx @udondan/avanti --help
Enter fullscreen mode Exit fullscreen mode

Drop a .avanti.yml in your project root, define your files, and run avanti diff to see what it would do.

The config format is in the README. The source is on GitHub at udondan/scync. Issues and PRs are open.

If you've handled this differently, I'm curious what you're doing.

Top comments (0)