# How it works

A team is one private git repository, and everything the team shares is a file in it. The CLI runs on your machine, reads and writes that repository, and talks to no server of ours.

## A team is one git repository

Everything the team shares lives in one private repository, and nothing lives anywhere else.

| Thing | In the repository |
| --- | --- |
| A skill | A folder named for the skill, holding a `SKILL.md` and whatever you put beside it |
| A published version | An immutable folder, `skills/<name>/v<N>/`. Publishing appends one; nothing rewrites one. |
| Eval assets | `skills/<name>/evals/`, beside the versions, so editing a case does not mint a version |
| A person | `people/<handle>.json`: handle, name, email, GitHub login, installs, endorsements |
| A receipt | `evals/<skill-uuid>/v<N>/<runId>.json`: one eval run, written once, never edited |
| Team settings | `team.json`: name, categories, projects, archived handles, and the license publish stamps into each `SKILL.md` |

::: figure repo-model
Each machine publishes to and fetches from the one team repository, with no server in between.
:::

## No server, no account

Membership is repository access. You are an active member when `people/<handle>.json` exists and your handle is not in `team.json`'s `archived` list. Both are required: a departure archives the handle rather than deleting the file.

There is no Terum server and no Terum account. The team's data sits on your git host, and the rest happens here:

| Where | What happens there |
| --- | --- |
| Your machine | The CLI, the desktop app, every eval run, the team clone at `~/.terum/skills/teams/<team>`, and your placed skills |
| Your git host | One `git clone` when you join, then fetches and pushes. Any git remote works: GitHub, GitLab, Bitbucket, bare SSH. |
| Anthropic, through your Claude Code login | Eval runs, the judge, generated eval assets and the publish category suggestion, through `claude -p`. The CLI never handles a token; the cost goes to whoever is logged in. |
| GitHub's API, through `gh` | Invitations, access-revoking `team remove`, and a few lookups. GitHub-only; on another host these refuse. |
| The terum-skills release repository | A release probe at most once a day, only when a team has a GitHub remote, and desktop app downloads. |

[Privacy and network](https://www.terum.ai/docs/privacy) lists every request and how to avoid it.

## The CLI and the app

The desktop app is a window onto the CLI, and every verb works from a terminal without it. The app runs the CLI as a child process with `--frames`, which speaks JSON, one object per line. Every question a verb would ask in a terminal becomes an `ask` frame that waits for the app's answer.

So the app shows what the CLI returns. It has no HTTP client; its webview opens only `https://github.com/*` and `https://discord.gg/*` links. The protocol is in [the frame protocol](https://github.com/ryanliu-terum/terum-skills/blob/main/docs/frame-protocol.md).

## How a write reaches the team repo

Every write goes through one function that re-applies your change instead of rebasing it:

1. Fetch, and hard-reset the clone to `origin/main`.
2. Apply the verb's change to that fresh tree.
3. Run the write guard on the result.
4. Stage exactly the paths the change touched, and check the staged diff matches them.
5. Commit and push to `main`.
6. If someone else pushed first, discard the commit and start over, with backoff, until a 30-second deadline.

One writer holds a per-clone lock for the whole loop, so two terminals on one machine queue instead of racing. Publish takes this same path: it pushes to `main`, and no branch or pull request is created.

The guard is the authorization model, and it is per verb. `publish` may add files to a version folder that does not exist yet, and may not touch one that does. `unpublish` is the only verb that may remove a version folder. Every other verb may write your own people file, and `team.json` only in the shape that verb needs. A write that touches one path it does not own is refused whole.

The same guard runs as a `pre-push` hook in your clone, so a hand-written `git push` is checked too. It catches accidents, not abuse: a bypass is possible, and is attributed to whoever pushed.

## What sync does, and does not

```sh
npx -y terum-skills@latest sync
```

`sync` runs `git fetch` and `git reset --hard origin/main` on each team clone, under that clone's lock. Read verbs (`ls`, `search`, `status`) never fetch, so a teammate's publish reaches you only when something does. The app runs `sync` in the background at launch and on window focus, at most once a minute.

`sync` does not place, copy, move or delete skill folders, push, edit a skill you are authoring, or repair a missing or broken clone. One exception: when a GitHub team's repository answers "not found", `sync` looks for where it went and, at a terminal, offers to move you there. Accepting runs `team move`, which does remove and re-place your placed skills.

## The session hook

Setup offers a Claude Code session-start hook, defaulting to No. With it, every session start runs `sync --hook`, which fetches your team clones in the background, at most once an hour. It places nothing and edits none of your skill folders, but it does refresh Terum's own files (the bundled skills, the edit-hook script, its settings entry) when they are outdated.

The hook runs the copy of the CLI that installed it, pinned to that release, never `@latest`. A newer release reaches it when you re-run `setup`. Details are in [Claude Code integration](https://www.terum.ai/docs/claude-code).

## Why it is built this way

- **No server.** A server would mean a Terum account, a Terum-held copy of your skills, and an operator to trust. A private repository already does distribution, history, access control and offline reads.
- **No container.** A skill is a folder Claude Code reads, with no runtime to isolate. Running Claude Code in a container breaks host authentication, host MCP servers, editor and browser extensions, and your global skills and hooks.
- **Any git remote.** Storage and sync work anywhere git works. Only membership administration needs a collaborator API, so `invite` and access-revoking `team remove` are GitHub-only.
- **Git-host access is the permission model.** Who may read and write the skills is who may read and write the repository.
- **Evals run on your machine and budget.** No CI job holds an API key, and a receipt records a run someone on the team paid for.
