> ## Documentation Index
> Fetch the complete documentation index at: https://to11.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI configuration

> The file that decides which skills reach your machine, where they land, and how to turn one off.

This page is about the **to11 CLI's** configuration — the file that decides which [skills](/docs/platform/prompts/skills) reach your machine. The gateway has [its own configuration](/docs/reference/configuration), which is a different thing with the same name.

**Everything you decide lives here, not on the platform.** Turning a skill off, holding a version and choosing a label 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

| Location | File                       | Owned by                 |
| -------- | -------------------------- | ------------------------ |
| **home** | `~/.to11/skills.yaml`      | you                      |
| **repo** | `<repo>/.to11/skills.yaml` | your team, and committed |

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

```yaml theme={null}
version: 1
organization: acme                      # one config, one organization

projects:
  # A label: the whole project follows it, and `skills` holds the exceptions.
  - project: sre/runbooks               # workspace/project within the organization
    label: live
    skills:
      incident-triage: { version: 4 }
      legacy-migration: { disabled: true }
      house-style: { version: 9, enforced: true }

  # No label: only the skills named here, each saying which version it follows.
  - project: web/checklists
    skills:
      release-checklist: { label: live }

targets:
  - type: claude-code                   # path omitted → the default for this type
  - type: codex

cacheTtlSeconds: 60                            # seconds; 0 means never a cache hit
```

### Every key

| Key                                 | Type                                   | Required                  | Omitted means                                                                |
| ----------------------------------- | -------------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| `version`                           | integer                                | yes                       | the file is rejected — a config with no format version cannot be read safely |
| `organization`                      | slug                                   | yes                       | the file is rejected; one config names one organization                      |
| `projects`                          | list                                   | yes                       | nothing is installed from this location                                      |
| `projects[].project`                | `workspace/project`, or `proj_` + ULID | yes                       | —                                                                            |
| `projects[].label`                  | slug                                   | no                        | **only the skills named under `skills` are installed**                       |
| `projects[].enforced`               | boolean                                | no                        | not enforced; a repository's entry wins a contested project                  |
| `projects[].skills`                 | map, keyed by slug                     | no                        | with a label, the whole project; without one, nothing                        |
| `projects[].skills.<slug>.label`    | slug                                   | no                        | follows the project's label                                                  |
| `projects[].skills.<slug>.version`  | integer                                | no                        | follows the label                                                            |
| `projects[].skills.<slug>.disabled` | boolean                                | no                        | not disabled                                                                 |
| `projects[].skills.<slug>.enforced` | boolean                                | no                        | not enforced; the repo config wins a contested scope                         |
| `targets`                           | list                                   | no                        | **both defaults** — `.claude/skills` and `.agents/skills`                    |
| `targets[].type`                    | `claude-code`, `codex`, `standard`     | yes, per entry            | —                                                                            |
| `targets[].path`                    | relative path                          | no, except for `standard` | the default for that type                                                    |
| `prefix`                            | string                                 | no                        | no prefix, so a skill lands in `<slug>`. Carries no separator                |
| `cacheTtlSeconds`                   | integer, seconds                       | no                        | 60. Zero means never a cache hit. **Home config only**                       |

**`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 or gateway 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. Both live in
`TO11_API_URL` and `TO11_GATEWAY_URL`, which nothing in a checkout can set. A
file that still names either one 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 a skill you believe you turned off.

**`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.

## Turning a skill off

```yaml theme={null}
skills:
  legacy-migration: { disabled: true }
```

**A disable always works, and nothing overrides it.** A skill turned off in your own config is not installed into a repository's tree either, whether or not you write `enforced: true` beside it. Writing it is allowed and redundant.

## Holding a version

```yaml theme={null}
skills:
  house-style: { version: 9 }                 # your preference
  incident-triage: { version: 4, enforced: true }   # your insistence
```

A pin is **not** enforced by default. Refusal is unconditional; a preference is not, and a team's decision about their own repository should outrank a preference unless you insist.

A pinned skill that is behind its label reads `unchanged` in `to11 skill list`, with the version column saying the rest: `pinned 4 (live: 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:

```yaml theme={null}
projects:
  - project: web/checklists
    skills:
      release-checklist: { label: live }
```

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.

**Two things cross from your scope into a repository's**, and both are you refusing or insisting:

* **a disable**, because declining always works;
* **an `enforced: true` statement**, which wins that scope in the repository's tree too.

**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* label still loses to a repository's *per-skill* statement — 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.

| Your config                            | The repository's             | Result                                                                                           |
| -------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------ |
| nothing                                | nothing                      | **absent** — nothing names it                                                                    |
| project follows `beta`                 | project follows `live`       | **`live`** — same scope contested, the repository wins                                           |
| project follows `beta`, enforced       | project follows `live`       | **`beta`** — enforced wins that scope                                                            |
| `incident-triage: version 4`           | project follows `live`       | **v4** in your directories; the repository's tree keeps `live`                                   |
| project follows `beta`                 | `incident-triage: version 4` | **v4** — the more specific statement wins                                                        |
| `incident-triage: version 4`           | `incident-triage: version 7` | **v7** — same scope contested, the repository wins                                               |
| `incident-triage: version 4, enforced` | `incident-triage: version 7` | **v4** — enforced wins that scope                                                                |
| project follows `beta`, enforced       | `incident-triage: version 7` | **v7** — enforcement won the project scope, and the per-skill statement is still more specific   |
| `incident-triage: disabled`            | `incident-triage: version 7` | **absent** — a disable is always enforced                                                        |
| anything at all                        | anything at all              | **absent** if you cannot access the skill — the install fails and says so, and the run exits `1` |

**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.

| type          | relative path    | resolved against       |
| ------------- | ---------------- | ---------------------- |
| `claude-code` | `.claude/skills` | home, or the repo root |
| `codex`       | `.agents/skills` | home, or the repo root |
| `standard`    | required         | home, or the repo root |

**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.

`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.

`to11 init` asks, and writes the key only when you answer. `--prefix` supplies it without being asked, and `--prefix ""` clears one already set.

A prefix names a directory, it does not choose where one goes: a value holding `/` or `\` is refused, at `init` and 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.

<Warning>
  Ownership is recorded **per file, and hidden files are not recorded**. The CLI skips dot-prefixed entries everywhere — it is what keeps a `.DS_Store` from marking a skill `changed` and blocking published updates — so a hidden file you add inside an installed directory is neither compared nor protected, and goes with the directory when the skill or its target leaves your configuration. A visible file you add does hold the removal. Keep nothing you care about inside an installed skill directory.

  **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.
</Warning>

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 directory**: each one is taken back if it still holds what the CLI wrote, and is otherwise reported and left — edited, or unreadable. So one edited copy no longer keeps the rest of the skill on disk, and the directories that remain are named on every run until you move what you want to keep.

## `code.claude.yaml`

A second file, beside the first, naming the provider [`to11 code`](/docs/reference/cli/commands#to11-code) routes a coding session to. It is required for a routed session: without one the command refuses to start the agent.

```yaml theme={null}
project: acme/platform    # workspace/project, or a proj_ id
environment: production   # an environment slug, or its id
provider: anthropic       # a provider slug, or its id
```

| Key           | Type                          | Required | Omitted means        |
| ------------- | ----------------------------- | -------- | -------------------- |
| `project`     | `workspace/project`, or an id | yes      | the file is rejected |
| `environment` | slug, or an id                | yes      | the file is rejected |
| `provider`    | slug, or an id                | yes      | the file is rejected |

All three keys are required. Together they name one provider activation: a provider is connected to a project and activated per environment.

Each value takes either a slug or an id, in one field. The value is matched against both the id and the slug of what the project holds. `to11 init` writes slugs.

An unknown key is an error naming the key, as in `skills.yaml`.

The file is read from two locations — `~/.to11/code.claude.yaml` and `<repo>/.to11/code.claude.yaml`. Unlike `skills.yaml` they are alternatives, not additive: inside a configured repository that file is used whole, and no key merges across the two.

There is no `model` key; the agent chooses its own model. There is no version key; a provider's config version is a property of its activation in an environment, so `environment` names one.

## Environment overrides

Each takes precedence over the file, for CI and one-off runs.

| Variable           | Overrides                                |
| ------------------ | ---------------------------------------- |
| `TO11_HOME`        | where the home location lives            |
| `TO11_API_URL`     | the platform host; production when unset |
| `TO11_GATEWAY_URL` | the gateway host; production when unset  |
| `TO11_API_KEY`     | the stored credential                    |
| `TO11_PROJECT`     | the project a write goes to, by name     |
| `TO11_PROJECT_ID`  | the project a write goes to, by id       |
| `TO11_LABEL`       | the label a write uses                   |
