Skip to main content
This page is about the to11 CLI’s configuration — the file that decides which skills reach your machine. Everything you decide lives here, not on the platform. Disabling a skill and holding a version are lines in this file: you edit it, and to11 skill sync applies it. There is no command for any of them, because a command per key would be a second interface to the same file.

Two locations, both at once

They are not alternatives. Inside a configured repository you have your own skills and the repository’s, each written into that location’s own directories. $TO11_HOME moves the home location. Your credential lives alone in ~/.to11/credentials.json at mode 0600 — configuration never contains a secret, which is what makes a repository’s copy safe to commit and easy to read.

The file

Every key

latest is the only label. Every skill with a stored version carries latest, which serves its newest version. Following it — on the project entry or on one skill — means each sync brings the newest stored version, and on a project entry every skill the project has, including one just created. Only storing a version moves it. Any other value is refused by every command that reads the file: error: projects[0]: `label: prod` is not a label a skill can follow — or, on one skill, `incident-triage` follows `staging`, which is not a label a skill can follow — followed by the file path and skills follow `latest`, or pin a version with `version: <n>`. See Follow the project. projectId is read and never written. It was a second key that named a project, and a file an earlier CLI wrote still carries it. It is accepted, folded into project, and gone from the next thing the CLI writes — with project winning if a file somehow has both. There is no key for the API URL. A repository carries its own config, so a key naming the host would let a checkout point the CLI — and the credential it sends — at a machine you never chose. It lives in TO11_API_URL, which nothing in a checkout can set. A file that names it is refused as an unknown key. Unknown keys are an error, naming the key. A typo in a file that decides what reaches an agent should not be silently ignored: a misspelled disabeld would leave on a skill you believe you disabled. version is the config’s own format version, not a skill version. An older file is converted forward; one newer than your CLI is refused, naming the file and the versions it reads. Guessing at instructions about where to write files on your machine is the one place less still beats nothing does not hold. cacheTtlSeconds belongs to you, so a repository’s copy setting it is ignored: refresh cadence is machine behavior, the same as a hook.

Disabling a skill

A disable decides your own folders. A skill disabled in your own config is not installed into your home directories. A repository whose config asks for the skill still installs it into the repository’s tree — unticking a skill for yourself is not a vote on every repository you work in. To keep it out of repositories too, enforce it:
An enforced disable is off everywhere, and nothing overrides it. A repository’s own disable always works in its own tree.

Holding a version

A pin is not enforced by default, and neither is a disable: a team’s decision about their own repository outranks your preference unless you insist. A pinned skill that is behind latest reads unchanged in to11 skill list, with the version column saying the rest: pinned 4 (latest: 7). No sync will change it — your configuration is already satisfied.

Taking one skill from a project

A project entry with no label installs only the skills named under it, and each of those says which version it follows:
That is how one skill is taken from a project without adopting the project. An empty skills map with no label installs nothing, and is reported as such rather than passing silently — it is almost always a half-finished edit.

Precedence

Two phases, in this order.

Phase 1 — each config governs its own scope

The repo config governs repo scope — what lands in the repository’s tree. Your config governs user scope — what lands in your own directories. They do not combine into one set. One thing crosses from your scope into a repository’s: an enforced: true statement, which wins that scope in the repository’s tree too. A disable crosses only when it is enforced; a plain one decides your own folders. Nothing crosses the other way. A committed config cannot redirect your targets, disable a skill you installed for yourself, or change how your machine behaves. Both apply only within the same organization: a config for acme says nothing about a repository’s config for globex.

Phase 2 — the most specific statement wins

A statement about one skill beats a statement about the whole project. That is the entire rule. Enforcement decides who owns a scope; it does not promote a statement to a higher scope. So your enforced project-wide latest still loses to a repository’s per-skill pin — enforcement won the project scope, and then the more specific statement answered.

Worked answers

Each row is one skill, in the location where the two statements actually meet. Access is the floor, not a phase. No statement, enforced or not, adds a skill you cannot access. A statement narrows what you already have. A declaration is not a guarantee. Enforcement exists, so any developer can enforce their way out of a repository’s set. Nothing — no review process, no build step — can rely on a skill being present on a machine.

Where skills land

A target has a type and a path. The type is also the render target: it decides what is rendered, not only where it lands, which is why a bare path is not accepted. Paths are relative and contained. Absolute paths are refused. For a repository’s config this is a security boundary rather than a style rule: that file is committed, so an absolute path in it is a way for one person to write directories onto every colleague’s machine. .. is refused too, and a resolved path that leaves the root through a symlink is refused as well. codex’s folder, .agents/skills, is shared. In a repository Codex, Cursor, GitHub Copilot, Gemini CLI and other agents read it; at home Codex, Cline, Warp, Zed and other agents do, while Cursor, GitHub Copilot and Gemini CLI read ~/.cursor/skills, ~/.copilot/skills and ~/.gemini/skills. The type keeps its name. standard is how an agent nobody has heard of still works: a known type at a stated path, receiving the portable form. targets absent means both defaults; present means exactly what is listed. A skill occupies one directory, named <slug> — or <prefix>-<slug> when prefix names one. The prefix is not what makes the directory ours; the state file is. Nor is it what keeps the skills out of git: every installed directory carries its own .gitignore holding *, which hides the directory and the ignore file with it, whatever the directory is called. What a prefix is for is a name collision. A published code-review and a code-review/ you already have want the same directory, and the CLI refuses that path rather than taking it. A prefix moves every directory out of the way at once. The value carries no separator — the dash is the CLI’s. acme and acme- are the same prefix and both install acme-<slug>; acme-- installs acme--<slug> for somebody who meant two. Only the file and to11 link --prefix set it: to11, to11 link and to11 ui do not ask for it, and keep it as it is when they save. A prefix names a directory, it does not choose where one goes: a value holding / or \ is refused on every read, so a skill cannot land outside the target it was resolved for.

What the CLI records

Each location holds .to11/.skills.state.json beside its configuration, recording what the CLI wrote: one entry per skill and target, with its project, the version installed, the directory it went to relative to the state file, and per file its name, its hash and its block id. This file is what makes a directory ours — not the name it has. A directory the CLI did not record writing is never touched, never read, never counted — unless you run to11 skill sync --force and a configured skill needs its name, which reports it as replaced.
Ownership is recorded per file, and hidden files are not compared. The CLI skips dot-prefixed entries when it compares a skill against what it wrote — it is what keeps a .DS_Store from marking a skill changed and blocking published updates — so editing or adding a hidden file never marks a skill dirty, and a new version is written beside it.The one exception is the .gitignore the CLI writes. It is dot-prefixed and it is recorded and compared, because we wrote it — editing it makes the skill read as changed, which is what stops a sync replacing it without saying so.
It is state, not a cache: the hash of what was written cannot be recomputed once it is gone, and neither can ownership. Deleting it orphans skills rather than freeing them — the next sync finds the paths occupied and reports conflict, and to11 skill sync --force is the recovery. It also records when a refresh last ran, which is the cache window’s clock. It records the attempt rather than the outcome: a refresh that failed stamps it too. Nothing else about a past run is kept here. A visible file you edit inside an installed skill makes that skill a local variant, and sync leaves the whole of it alone. Dot-prefixed files are not compared, so editing one changes nothing here — see the warning above. Writing a new version of one file beside an edited copy of another would produce a tree whose links point at content that never shipped together. It ends one of two ways, both explicit: commit it with to11 skill store, or revert it with to11 skill sync --force. That is about a skill being UPDATED. A skill or target the configuration stops asking for is judged per file: each file the CLI wrote that still holds what it wrote is removed, and every other file stays — one you changed, one you added, hidden or not, and one reached through a link inside the skill directory, because the CLI never writes through a link — except a .DS_Store written by macOS Finder, which holds only how Finder displays the folder. A directory left empty is removed. One that is not, or one the CLI cannot read, is reported as orphaned on every run until it is empty, and no flag removes it. Your home also holds ~/.to11/.sessions.state.json: for each Claude Code transcript, how far to11 session sync has sent it to each API and project.

sessions.yaml

Your own session-sync settings, never the team’s: which project your Claude Code sessions go to, and whether they are sent automatically. It is read from <repo>/.to11/sessions.yaml and ~/.to11/sessions.yaml. The repository’s file comes before your home’s, for project and for autoSync each: a repository’s file that sets only autoSync takes its project from your home’s.
to11 link writes organization and project from the project it links when the location’s file names no project yet, so sessions go to the project the skills come from. A file that already names one keeps it, so you can point the sessions at another project by editing the file. to11 link and to11 session autosync write autoSync. An unknown key is an error naming the key, as in skills.yaml. The CLI keeps the repository’s file out of git through the .to11/.gitignore it writes. When that .gitignore is one your team wrote, the CLI leaves it alone and says to add sessions.yaml to it. CLI 0.33.0 to 0.35.0 kept the auto-sync setting in .to11/local.yaml, under sessions:. That file is no longer read: run to11 session autosync on again, or to11 link, to write sessions.yaml. Earlier CLIs also wrote code.claude.yaml and code.codex.yaml, for a to11 code that ran a coding agent. They are no longer read, and can be deleted.

Environment overrides

Each takes precedence over the file, for CI and one-off runs — except TO11_API_KEY, which is used only when no person is signed in.

Which credential a command uses

Every command except to11 ui picks, in order:
  1. the person signed in with to11 login, to11, to11 link or to11 ui;
  2. TO11_API_KEY, when it is set;
  3. an API key stored by an older CLI.
API keys are for machines where nobody signs in, such as CI and containers, and reach the CLI only through TO11_API_KEY. to11 logout hands commands back to the key. to11 ui uses only the signed-in person.