Syncs rules, commands, and skills from ~/.config/dry-ai into supported agent targets (currently Copilot and Cursor).
Compared to skills.sh: with dry-ai skills add, you can edit those files, and the CLI will not override them on the next sync run. This allows you to use existing skills, tailor them to your needs, and keep track of their original sources.
Global CLI options:
--config-root <path>reads configs from a different root such as./config.--output-root <path>writes generated output somewhere other than your home directory.--testis a shortcut for--output-root ./output-test, and if both are provided,--output-rootwins.--debuglogs the full Effect failure cause on stderr after the normal user-facing error line when a command fails (you can also setDRY_AI_DEBUG=1).
These are root-level options for the CLI. They modify command behavior for any given command.
Input config files live under the selected config root:
commandsrulesskills
dry-ai will output these sources into:
~/.copilot/prompts~/.copilot/instructions~/.copilot/skills~/.cursor/rules~/.cursor/skills
A config root contains all three source types:
~/.config/dry-ai/
├── commands/
│ └── gen-commit-msg.md
├── rules/
│ └── say-yes-captain.md
└── skills/
└── review-helper/
└── SKILL.md
dry-ai sync — copy commands, rules, and skills from the config root into supported agent output trees.
sync reads commands, rules, and skills under the active config root and updates Copilot and Cursor targets under the output root. It maintains sync-manifest.json next to the config root: a record of what dry-ai generated so a later run can remove obsolete outputs when you change or delete sources, summarize what changed, and leave alone anything dry-ai did not create.
dry-ai skills add — import one or more managed skills from a remote git repository into the active config root.
dry-ai [global options] skills add <repo> --skill <name> [<name> ...] [import options]
<repo> is either a full git remote URL or a GitHub owner/repo shorthand (for example, anthropics/skills).
Copies each requested skill into skills/<name>/ under the config root and records it in skills.lock.json (along with source repo, path, ref, resolved commit, and content hashes). Local skill trees and the lockfile always use the selected --config-root (default: ~/.config/dry-ai).
By default each skill name is resolved from <repo root>/skills/<name>/. Use --path to use another directory inside the repo, or --path . to use the repository root.
--skill <name> [<name> ...]— Required at least once. Put every skill to import after the first--skilltoken (variadic), or use--skillagain for another group (for example--skill a --skill b). Duplicate names in one run are ignored.--path <repoPath>— Base directory inside<repo>for resolving skills (instead ofskills/).--path .— Resolve each skill from the repository root.--as <name>— Store the imported skill under a different local name (only when importing exactly one skill).--ref <gitRef>— Fetch a specific branch, tag, or commit instead of the remote default.--pin— Record the resolved commit so the lockfile does not follow a moving branch ref.
# Resolves from <repo root>/skills/skill-creator
dry-ai skills add anthropics/skills --skill skill-creator
# Resolves from <repo root>/review-helper
dry-ai skills add anthropics/skills --path . --skill review-helper
# Resolves from <repo root>/tools/review-helper
dry-ai skills add anthropics/skills --path tools --skill review-helper
# Resolves from <repo root>/skills/pr-review and <repo root>/skills/commit
dry-ai skills add vercel-labs/agent-skills --skill pr-review commit
# Same as above; repeated --skill is equivalent to one variadic --skill
dry-ai skills add vercel-labs/agent-skills --skill pr-review --skill commit
# Resolves from <repo root>/skills/pr-review and <repo root>/skills/commit
dry-ai skills add https://github.com/vercel-labs/agent-skills.git --skill pr-review commitBy default, imports track the requested ref. With no --ref, that means the remote default branch HEAD is tracked.
dry-ai skills update — re-fetch one managed skill from its tracked source and replace the local directory under the config root.
dry-ai [global options] skills update <name> [--force]
Uses skills.lock.json to find the skill’s remote and ref, fetches the same layout as at import time, and replaces files when the working tree still matches the lockfile hashes. If local files were added, removed, or edited, the update is skipped unless --force is set (see skills lockfile below).
--force— Overwrite local edits with the fetched remote copy and refresh hashes inskills.lock.json.
dry-ai skills update-all — re-fetch every managed skill and replace each local copy in turn.
dry-ai [global options] skills update-all [--force]
Runs the same hash-guarded update logic as skills update, once per lockfile entry. Skills with local drift are skipped unless --force is passed.
--force— Overwrite local edits for any skill where the remote copy would otherwise be skipped.
dry-ai skills remove — delete a managed skill’s local directory and drop its row from skills.lock.json.
dry-ai [global options] skills remove <name>
<name> is the managed skill name (the directory name under skills/ in the config root).
dry-ai skills list — show local skill directories and how they relate to the lockfile.
dry-ai [global options] skills list
Lists directories under skills/ in the config root. Managed skills include a summary from the lockfile; unmanaged entries are labeled as such. Lockfile rows that no longer have a local directory are reported so you can spot a broken or removed tree.
Rules are markdown files under rules/. Required top-level fields are validated (description, etc.). Under agents, each agent's block must be a YAML mapping when present; all keys in that mapping are copied into that agent's generated file (after stripping undefined values). Dry-ai does not infer Cursor fields from Copilot fields or vice versa.
Common keys:
agents.copilot.applyTo(Copilot instructions)agents.cursor.alwaysApply,agents.cursor.globs(Cursor rules)
---
description: Reply with "Yes, Captain!" before answering when the user says "Make it so" or "Engage".
agents:
copilot:
applyTo: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
cursor:
alwaysApply: false
globs: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
---
# Say Yes Captain
When the user says "Make it so" or "Engage", start your response with "Yes, Captain!".Commands are markdown files under commands/. dry-ai recognizes these command frontmatter fields:
namedescriptionagents.cursor.disable-model-invocation
---
name: gen-commit-msg
description: Generate a conventional commit message from the current staged git diff.
agents:
cursor:
disable-model-invocation: true
---
# Generate Commit Message
Read the staged diff and produce a conventional commit message with a concise subject and optional body.Skills live in directories under skills/. The directory is copied as-is into the configured agent skill targets.
Unlike commands and rules, dry-ai does not define or validate a fixed skill frontmatter schema. Skill files are passed through unchanged, so the allowed frontmatter fields depend on the skill format expected by the target agent.
skills/
└── review-helper/
└── SKILL.md
---
name: review-helper
description: Use this skill when the user asks for a code review, PR review, or wants bugs and risks called out first.
---
# Review Helper
Focus on findings first.
- Identify bugs, regressions, and missing tests before summarizing.
- Keep the overview brief unless the user asks for a deeper walkthrough.For development, use pnpm dev to rebuild into dest/ on change and pnpm dev:dry-ai <...> to run the built CLI.
To put this checkout’s CLI on your PATH as dry-ai, run pnpm link --global from the repo root after a build (or npm link with npm’s global link workflow). Use pnpm unlink --global / npm unlink -g dry-ai when you want to stop using the linked binary.
Run pnpm run setup:editor after installing dependencies if you want the Effect language service workspace patch applied locally.
pnpm run setup:editor
pnpm run build
pnpm run dev
pnpm run test
pnpm run test:watch
pnpm dev:dry-ai <...>VS Code does not automatically discover prompt (command) files from the Copilot prompt target at ~/.copilot/prompts.
Add this to your VS Code user settings if you want prompt files installed by dry-ai into that target to be picked up:
{
"chat.promptFilesLocations": {
"~/.copilot/prompts": true
}
}The CLI is built with Commander for the usual CLI surface (arguments, help, subcommands) and Effect.
Why Effect?
Effect helps us build the CLI from small, typed programs. Each one carries its dependencies and failures in its type, so we can compose them safely: sequence steps, run work in parallel, interrupt a whole run, or add retries and timeouts at the boundary. That strengthens local reasoning because each unit stays understandable on its own while TypeScript checks how the pieces fit together.
Effect in dry-ai — the features this repo uses today:
- Typed programs (
Effect.gen) — each top-level command (sync,skills add,skills update,skills update-all,skills list,skills remove) is built as anEffectand executed byrunCliEffect. Shared work insrc/lib/sync.ts,src/lib/skills.ts, andsrc/lib/fs.tsis split into smaller effects that the commands compose. - Services and layers (
Effect.provide) — filesystem access goes through@effect/platformFileSystem, so production and tests provide the same service shape. Tests swap in snapshot filesystems and Logger layers instead of changing command code. - Typed errors and causes — sync I/O, config, and skills validation failures become tagged errors on the Effect error channel.
runCliEffectturns them into one curated stderr line, while--debug/DRY_AI_DEBUGprints the full Effect cause for diagnosis. - Effect logging — command output that belongs to the Effect program uses
Effect.log*: sync reports and import messages at info level, recoverable skipped-input or manifest issues as warnings, and command failures as errors. - Composition and cleanup (
pipe,Effect.all,ensuring,catchIf) — independent filesystem work can run together withEffect.all;skills addusesensuringto clean temp clone dirs; focusedmapError/catchIfcalls keep expected filesystem failures explicit. effect/Schema— used where Schema adds value today: stable JSON encoding for sorted file-hash tuples when sync fingerprints directory skills. Other option, config, and manifest validation still uses Zod.
- Services & Layers — model clone/fetch as a
Context.Tagservice with aLayerinstead of ad hocsimple-gitwiring. - Clock — use Effect’s
Clockservice for deterministic time in tests and fewer ad hoc timestamps. - CLI — experiment with
@effect/clifor Effect-native command parsing (listed in dependencies; not wired insrc/yet). - Brands — use Schema brands for paths, agent names, commit SHAs, etc.
- Schema — gradually move Zod validation to
effect/Schemawhere it pays off alongside Effect.