Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dotagents

dotagents keeps your agent skills and global instructions in a single source tree, renders them for the machine you are currently on, and symlinks the result into whichever directories your tools read.

It exists so one repository can serve several computers without the machines having to be identical. A branch prefix that differs between machines, a skill only one of them needs, a path that exists on one and not the other: each of those is a declared difference in the source tree rather than a divergent copy of a file.

What it does

  • Works as a template for your personal agents repository. Use this template to centralize your skills and instructions accross multiple machines.
  • Renders per machine. Variables written as {{MY_VARIABLE}} are replaced at build time from a per machine JSON file layered over shared defaults.
  • Layers content per machine. A machine can add skills the others do not have, replace a shared skill entirely, and append its own paragraphs to the global instructions.
  • Installs anywhere. The directories it links into are declared in targets.json. Any tool that reads skills from a directory is supported by adding a line.
  • Fails loudly. A variable with no value stops the build and names the file and line, so a typo can never silently delete an instruction.

How it works

./install.sh    src/ -> dist/ -> every path in targets.json    when the set of skills changes
./build.sh      src/ -> dist/                                  when their content changes

src/ is what you edit. The build resolves the active machine, merges that machine's variables over the shared defaults, renders the tree, and writes dist/. It writes nothing outside the repository, so it is always safe to run.

install.sh builds and then symlinks dist/ into the declared targets, so a fresh clone needs nothing else.

Each target holds one symlink per skill, not a single link to the skills directory. Editing a skill therefore needs only build.sh, because its link already exists and resolves into dist/. Adding or removing one needs install.sh again, to create or prune that skill's link. You do not have to remember which case you are in: build.sh warns when a skill it just built has nothing pointing at it.

Quickstart

Needs bash and Node.js 18 or newer. Nothing to install beyond that.

git clone git@github.com:souyahia/dotagents.git
cd dotagents

echo "my-laptop" > .machine          # this computer's identity, gitignored
mkdir -p src/machines/my-laptop      # custom configuration for this machine

./install.sh --dry-run               # builds, then shows what it would link
./install.sh

Then put your skills in src/skills/<name>/SKILL.md and run ./install.sh, so each new skill gets its symlink. Once a skill exists, ./build.sh picks up your edits to it.

Layout

targets.json                   where dist/ gets symlinked
src/
  instructions.md              shared global instructions
  vars.default.json            declares every variable and its default
  skills/<name>/SKILL.md       shared skills
  machines/<id>/
    vars.json                  this machine's variable overrides
    instructions.md            appended to the shared instructions
    skills/<name>/SKILL.md     machine only skills, override shared by name
vars.local.json                gitignored, for secrets and one-offs
targets.local.json             gitignored, replaces targets.json on this machine
.machine                       gitignored, this computer's identity
dist/                          gitignored, generated by build.sh

Everything about one machine lives in one directory, so two computers never edit the same lines and never conflict.

Machines

The active machine is resolved in this order:

  1. the DOTAGENTS_MACHINE environment variable
  2. the .machine file
  3. the short hostname

If 1 or 2 names a machine with no directory under src/machines/, the build fails, because you asked for something that does not exist. If the hostname simply matches nothing, the build succeeds with shared content only, so a fresh clone works before you have configured anything.

Variables

Write {{UPPER_SNAKE}} anywhere in a .md or .txt file. Values are resolved by merging three layers, each overriding the one above it:

  1. src/vars.default.json
  2. src/machines/<id>/vars.json
  3. vars.local.json

All values are strings. Keep them small: a path, a prefix, a sentence. If a machine difference needs a whole paragraph of new behavior, make it a machine only skill instead, so the agent reads one coherent document rather than a document arguing with itself.

Two rules keep this honest:

  • A variable with no value is a build error. It is never silently replaced with an empty string, so a typo stops the build instead of quietly dropping an instruction your agent was meant to follow.
  • A variable set in a machine or local layer that nothing references is a warning. That catches the other half of the same typo, where the template says INTRUCTIONS and the JSON says INSTRUCTIONS.

To write a literal {{NAME}} without substituting it, put a ! immediately after the opening braces. The build strips the ! and leaves the rest alone.

Skills

Shared skills live in src/skills/. A skill in src/machines/<id>/skills/ with the same name replaces the shared one entirely on that machine; with a new name, it exists only on that machine.

Adding or deleting a skill changes the set of symlinks each target needs, so run ./install.sh rather than ./build.sh after either. It creates the links that are missing and prunes the ones whose skill is gone.

Install targets

targets.json declares where the build gets linked:

{
  "skills": [
    "~/.agents/skills",
    "~/.claude/skills"
  ],
  "instructions": [
    "~/.claude/CLAUDE.md"
  ]
}

A skills target is a directory that receives one symlink per built skill. An instructions target is a single symlink to the rendered instructions file, so the path includes the filename the tool expects. ~ and $VARIABLES are expanded.

Everything listed is installed, and missing directories are created. When a machine needs a different set, an uncommitted targets.local.json replaces targets.json entirely.

The defaults cover Claude Code and Codex. Adding another tool means adding the directory it reads.

install.sh never overwrites anything it did not create. Entries belonging to something else are reported and skipped, and --force backs them up first.

Why the build is manual

There is no dependency free, cross platform way to run a command whenever anything in a directory tree changes. macOS launchd WatchPaths does not recurse into subdirectories, Linux inotify is not recursive either, and the tools that paper over this (fswatch, watchexec, watchman) are binaries every user would have to install before this one worked at all. Polling on a timer would work, but it is a background process running forever to serve edits you make a few times a month.

So you run ./build.sh when you change a skill's content, and ./install.sh when you add or remove one. Agents that watch their skills directory pick the result up on their own. Claude Code "picks up the change within the current session, without a restart", and Codex "detects skill changes automatically. If an update doesn't appear, restart Codex."

One caveat no tool can work around: a skill already invoked in the current session keeps the copy loaded in context, so editing it and invoking it again in that same session may not reflect the change. This behaves identically for real files and symlinks, and is not something dotagents causes or can fix.

Using a private content directory

To keep the machinery public and your actual skills private, put them in a separate directory and point DOTAGENTS_HOME at it:

export DOTAGENTS_HOME=~/Projects/my-private-agents
./install.sh

DOTAGENTS_HOME is where src/, dist/, targets.json, .machine and vars.local.json live. The scripts stay in this repository, so you can pull updates to the tool without touching your content.

Commands

Command What it does
./build.sh renders src/ into dist/, writing nothing outside DOTAGENTS_HOME
./build.sh --no-link-check renders without warning about built skills that are linked nowhere
./install.sh builds, then symlinks dist/ into every applicable target
./install.sh --dry-run builds, then prints what it would link without touching anything outside the repository
./install.sh --force replaces entries dotagents did not create, backing up real files
./install.sh --no-build installs the existing dist/ instead of rebuilding it first

Requirements

bash and Node.js 18 or newer on your PATH. There is nothing to install beyond that: the scripts use only the Node standard library, so there is no package.json, no dependencies and no npm install step.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages