Metadata-Version: 2.4
Name: skillhost
Version: 0.1.6
Summary: Install Agent Skills from Git repositories using symlinks
Project-URL: Homepage, https://skillhost.dev
Project-URL: Repository, https://github.com/wang-chonghuan/skillhost
Project-URL: Issues, https://github.com/wang-chonghuan/skillhost/issues
Author: Skillhost contributors
License: MIT
License-File: LICENSE
Keywords: agents,claude,cli,codex,opencode,skills,symlinks
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# SkillHost

SkillHost manages Agent Skills from Git repositories. Git repositories are the source of truth for skill content, symlinks are the install mechanism, and `~/.skillhost/config.json` is the persistent source of truth for registered agents, projects, and skill repositories.

SkillHost is intentionally small: it does not run code from skill repositories, does not scan your whole disk, and does not overwrite user-owned existing skills.

## Install

```sh
pipx install skillhost
uv tool install skillhost
pip install skillhost
```

From a checkout:

```sh
uv tool install .
pip install .
```

SkillHost does not need an initialization step. Regular commands create the default `~/.skillhost` state they need on demand; commands that do not need persistent config can run before `config.json` exists.

## Current command list

```text
skillhost [-h] [--version]
  upgrade

  register --project <name> --git <project-git-url>
  register --agent <name> --user-dir <path> [--project-dir <path>]

  unregister --project <name>
  unregister --agent <name>

  add <skill-git-repo> [--project <name>] [--name <repo-name>]

  update [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]

  remove <repo-name> [--project <name>]

  relink [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]

  unlink [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]

  clean

  list [--project <name>]
  projects
  agents
  doctor [--project <name>]
  config
```

There are no `user`, `project`, `register-project`, `config path`, or `config edit` subcommands. Older flags such as `--dry-run`, `--force`, `--all-projects`, `--all-skills`, `--include`, and `--exclude` are not part of the current CLI.

## Scope model

User scope is the default. These commands operate on user-level skill repositories unless `--project <name>` is provided:

```sh
skillhost add <repo>
skillhost update
skillhost relink
skillhost unlink <repo-name>
skillhost list
skillhost doctor
skillhost remove <repo-name>
```

Project scope is selected only with `--project <name>`:

```sh
skillhost add <repo> --project nsdk
skillhost update --project nsdk
skillhost relink --project nsdk
skillhost unlink --project nsdk --all
skillhost list --project nsdk
skillhost doctor --project nsdk
skillhost remove nsdk-skills --project nsdk
```

`--all` means all skill repositories in the selected scope. For `update`, `relink`, `list`, and `doctor`, omitting `repo-name` already means all repositories in the selected scope. For `relink`, omitting `repo-name` also opens the target selector in an interactive terminal unless `--agent` is provided. For `unlink`, omitting both `repo-name` and `--all` is refused to avoid accidental bulk unlinking.

## State layout

Default state lives under `~/.skillhost`:

```text
~/.skillhost/
  config.json
  user_repos/
    <repo-name>/
  project_repos/
    <project-name>/
      <repo-name>/
```

`skillhost config` prints only the absolute path to `config.json`.

`config.json` stores:

```text
version
home
agents
user_repos
projects
projects.<project>.remotes
projects.<project>.repos
repo URLs
normalized URLs
local repo paths
```

Each agent target directory also has a target-local `.skillhost-links.json` manifest. This manifest is separate from `config.json` and is used so `unlink` and `remove` only touch SkillHost-managed symlinks.

## Agent targets

Built-in agents are initialized in config:

```text
codex    user: ~/.agents/skills                 project: .agents/skills
claude   user: ~/.claude/skills                 project: .claude/skills
opencode user: ~/.config/opencode/skills        project: .opencode/skills
openclaw user: ~/.openclaw/skills               project: —
hermes   user: ~/.hermes/skills                 project: —
```

Register another agent target:

```sh
skillhost register --agent cursor --user-dir ~/.cursor/skills --project-dir .cursor/skills
skillhost agents
skillhost unregister --agent cursor
```

OpenClaw and Hermes Agent are user-level targets only; SkillHost intentionally does not create project-level links for them. Claude Cowork currently exposes personal skills through its UI rather than a documented stable local user skills directory, so SkillHost does not link to Claude Cowork yet.

Agent registration updates config only. It does not link automatically; run `skillhost relink` when you want to refresh links.

## User-level workflow

```sh
skillhost add git@github.com:org/company-skills.git
skillhost list
skillhost update
skillhost relink
skillhost unlink company-skills --agent codex
skillhost clean
skillhost remove company-skills
```

`add` clones the skill repository into `~/.skillhost/user_repos/<repo-name>`, records it in `config.json`, discovers skills, and then prompts where to link the added skills: Codex, Claude Code, OpenCode, OpenClaw, Hermes Agent, or All. In non-interactive shells it keeps the safe previous behavior and relinks all enabled user-level agent targets when possible.

Use `--name` when you want the local repo name to differ from the Git URL basename:

```sh
skillhost add git@github.com:org/company-skills.git --name shared-skills
```

## Project-level workflow

First register the project remote:

```sh
skillhost register --project nsdk --git git@github.com:org/nsdk.git
```

Then run project-scoped commands from inside the matching project checkout when you want project links to be created or removed:

```sh
cd ~/code/nsdk
skillhost add git@github.com:org/nsdk-skills.git --project nsdk
skillhost update --project nsdk
skillhost relink --project nsdk
skillhost unlink --project nsdk --all
skillhost remove nsdk-skills --project nsdk
```

Project `relink` and project `unlink` validate the current Git repository root and its `origin` remote against the registered project. SkillHost does not search the disk for project checkouts and does not perform cross-project bulk cleanup.

If you add or update a project skill repository outside the matching checkout, the repository is still recorded under `~/.skillhost/project_repos/<project>/<repo-name>`, and SkillHost prints the command to run inside the project checkout.

## Command details

### `skillhost upgrade`

```sh
skillhost upgrade
```

Updates the installed SkillHost package using the installer that appears to own the current command: `pipx upgrade skillhost` for pipx-managed installs, `uv tool upgrade skillhost` for uv tool installs, and `python -m pip install --upgrade skillhost` as the fallback for ordinary pip or virtualenv installs.

### `skillhost register`

```sh
skillhost register --project <name> --git <project-git-url>
skillhost register --agent <name> --user-dir <path> [--project-dir <path>]
```

Project registration normalizes and stores the project Git remote and creates `~/.skillhost/project_repos/<name>`. Agent registration stores user and project target directories. `--project` and `--agent` are mutually exclusive.

### `skillhost unregister`

```sh
skillhost unregister --project <name>
skillhost unregister --agent <name>
```

Removes the config entry only. It does not delete user-owned files and does not scan disk for unknown checkouts.

### `skillhost add`

```sh
skillhost add <skill-git-repo> [--project <name>] [--name <repo-name>]
```

Clones a skill repository into the selected scope and records it in config. Without `--name`, the repo name is derived from the Git URL basename with `.git` stripped. GitHub HTTPS URLs are cloned through SSH automatically, so `https://github.com/org/repo` and `git@github.com:org/repo.git` both work for users with SSH access. If no skills are discovered, `add` leaves config and repo storage unchanged and exits with an error. On success, it prints how many skills were added and suggests `skillhost list`.

After a successful interactive `add`, SkillHost opens a small terminal selector for where to link the newly added skills. Use ↑/↓ to move, Space to select or unselect, and Enter to confirm. Leaving nothing selected defaults to all enabled targets. If the terminal cannot run the selector, SkillHost falls back to a text prompt where you can enter one number, multiple comma-separated numbers such as `1,3`, or `all`/blank for all. Non-interactive `add` defaults to all enabled targets.

### `skillhost update`

```sh
skillhost update [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]
```

Before updating, `update` uses the same target selection flow as `add` and `relink` unless `--agent` is provided. It removes existing SkillHost-managed links for the selected repository or repositories only in the selected agent targets, so renamed or deleted skill directories do not leave stale symlinks behind. Then it runs `git pull --ff-only` for one repository or all repositories in the selected scope. It does not merge or rebase. After updating, it relinks the same selected agent targets when possible. Non-interactive `update` defaults to all enabled targets.

### `skillhost remove`

```sh
skillhost remove <repo-name> [--project <name>]
```

Unlinks SkillHost-managed symlinks for that repo where safely discoverable, deletes the cloned repo under `~/.skillhost`, and removes the repo entry from config. The repo argument can be the local repo name or a Git URL such as `https://github.com/org/repo` / `git@github.com:org/repo.git`; URLs are resolved to their repo basename. It does not support `--all`.

### `skillhost relink`

```sh
skillhost relink [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]
```

Creates or updates SkillHost-managed symlinks for one repo or all repos in the selected scope. Omitting `repo-name` means all registered repos in the selected scope; for example, `skillhost relink` refreshes every user-level repo, and `skillhost relink --project nsdk` refreshes every repo registered under project `nsdk`. Existing unmanaged files or directories are skipped. In an interactive terminal, `relink` uses the same target selector as `add` unless `--agent` is provided; non-interactive `relink` defaults to all enabled targets.

### `skillhost unlink`

```sh
skillhost unlink [repo-name] [--project <name>] [--agent {codex,claude,opencode,openclaw,hermes}] [--all]
```

Removes only symlinks recorded in `.skillhost-links.json`. To unlink all repos in a scope, pass `--all` explicitly:

```sh
skillhost unlink --all
skillhost unlink --project nsdk --all
```

### `skillhost clean`

```sh
skillhost clean
```

Scans all enabled user-level agent skill directories and removes broken symlinks. If you run it from inside a Git repository, it also scans that repo root's supported project-level agent skill directories, such as `.agents/skills`, `.claude/skills`, and `.opencode/skills`. If a broken symlink or missing destination is recorded in `.skillhost-links.json`, `clean` also removes the stale manifest entry. OpenClaw and Hermes are user-level-only targets, so they are not cleaned as project-level directories.

### `skillhost list`, `projects`, `agents`, `doctor`, `config`

```sh
skillhost list [--project <name>]
skillhost projects
skillhost agents
skillhost doctor [--project <name>]
skillhost config
```

`list` shows repos and discovered skills in the selected scope. `projects` shows registered projects and normalized remotes. `agents` shows registered agent user/project target directories. `doctor` checks config, repos, duplicate skill names, targets, manifests, broken symlinks, and missing sources. `config` prints the absolute config path only.

## Supported skill repository layouts

Single skill repo:

```text
my-skill/
  SKILL.md
```

Collection repo:

```text
company-skills/
  skills/
    git/
      SKILL.md
    db/
      SKILL.md
```

Flat collection repo:

```text
company-skills/
  git/
    SKILL.md
  db/
    SKILL.md
```

Skill discovery is shallow. SkillHost checks root `SKILL.md`, direct children under `skills/`, or direct children under the repo root. It ignores hidden directories and obvious non-skill directories such as `tests`, `docs`, `examples`, `scripts`, `references`, and `assets`.

## Git URL normalization

Common SSH and HTTPS Git URL forms normalize to `host/org/repo`:

```text
git@github.com:org/repo.git      -> github.com/org/repo
git@github.com:org/repo          -> github.com/org/repo
https://github.com/org/repo.git  -> github.com/org/repo
https://github.com/org/repo      -> github.com/org/repo
ssh://git@github.com/org/repo.git -> github.com/org/repo
```

Project matching uses the current Git root plus `git remote get-url origin`, normalized with the same rules.

## Safety rules

SkillHost does not execute code from skill repositories. It only clones repositories, pulls with `git pull --ff-only`, reads `SKILL.md` metadata, and creates or removes manifest-managed symlinks.

SkillHost never overwrites unmanaged existing targets, never removes user-owned skill directories, and never performs full-disk project discovery or cross-project bulk operations.
