Metadata-Version: 2.4
Name: agentsquire
Version: 0.5.0
Summary: Let a Python package carry its own agent integrations and install them into whatever agent harness is present.
Author: TacoTakumi
License-Expression: MIT
License-File: LICENSE
Keywords: agent,agent-skills,claude-code,cli,harness,skills
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: click
Requires-Dist: pyyaml
Requires-Dist: questionary
Description-Content-Type: text/markdown

# AgentSquire

A reusable Python library + CLI that lets a Python package carry its own agent
integrations (Agent Skills) and install them into whatever agent harness is
present - the executable is the framework.

Your CLI ships its skills as package data inside its own wheel, adds
`agentsquire` as a plain pip dependency, and mounts a ready-made subcommand
group. Your users only ever see your tool:

```
$ awiki skills install
installed awiki-search -> /home/you/.claude/skills/awiki-search
```

Supported harnesses at launch: Claude Code, pi, Hermes, and opencode. Detection
is by marker directory; every operation is local (no network in any verb). Each
harness's directories, scopes, and behaviour are recorded in
[docs/harnesses.md](docs/harnesses.md).

## Installation

    pip install agentsquire

This installs the library and its own CLI, `squire` (aliased `agentsquire`). As
a consumer you normally add `agentsquire` to your package's dependencies rather
than having users install it directly - it rides in your wheel, and your users
only ever see your tool.

agentsquire dogfoods its own contract: it carries the
`developing-with-agentsquire` skill and its reference docs as package data. When
you build an app on agentsquire, install that skill into your own agent harness
so the harness has the integration know-how on hand while it works:

    squire skills install

That copies `developing-with-agentsquire` (how to ship skills, mount the group,
and wire the staleness hook) into every detected harness. The same reference
docs are served at the terminal from the installed wheel - no checkout needed:

    squire guide              # topics: api, harnesses, integration
    squire guide api          # the Python API reference
    squire guide integration  # this consumer integration guide

### From source

Clone the repository and install it into your environment:

    git clone https://github.com/TacoTakumi/AgentSquire.git
    cd AgentSquire
    pip install .

Add `-e` (`pip install -e .`) for an editable install if you plan to work on
agentsquire itself. The repo uses [uv](https://docs.astral.sh/uv/): `uv sync`
provisions a dev environment and `uv run pytest` runs the test suite.

## Consumer integration guide

### 1. Ship skills as package data

Lay each skill out as a directory containing a `SKILL.md` (the agentskills.io
format) under a `skills/` resource inside your importable package:

```
your_pkg/
    __init__.py
    skills/
        my-skill/
            SKILL.md
            reference.md
```

The skills ride inside your wheel; make sure your build backend includes
package data (hatchling includes it by default, setuptools needs
`include-package-data`). No source checkout is needed at run time - skills are
enumerated straight from the installed wheel.

### 2. Mount the subcommand group

One call returns a click group with `install`, `status`, `update`, and
`uninstall` subcommands, parameterized by your package name, the resource path
(default `"skills"`), and the default scope.

For a click CLI:

```python
import click

from agentsquire.cli import skills_command_group


@click.group()
def cli():
    """Your CLI."""


cli.add_command(skills_command_group("your_pkg", default_scope="user"))
```

For a typer CLI, mount onto the underlying click command:

```python
import typer
import typer.main

from agentsquire.cli import skills_command_group

app = typer.Typer()


@app.callback()
def main():
    """Your CLI."""


cli = typer.main.get_command(app)
cli.add_command(skills_command_group("your_pkg", default_scope="user"))
```

Your users now run `your-cli skills install` and friends. Every subcommand
takes `--scope user|project` (overriding your declared default) and
`--harness NAME` (default: all detected harnesses).

Choosing the default scope: `user` installs into the harness's per-user
skills directory and follows the user everywhere - right for general-purpose
tools. `project` installs into the current project's directory - right for
skills that only make sense inside a repository that uses your tool.

### 3. Surface updates proactively (optional)

Place the one-call staleness hook at your CLI entry point. When installed
skills have updates available it prints a single advisory line on stderr,
for example:

    your-cli: a skills update is available for 1 skill (alpha); run `your-cli skills update`

The hook is notice-only: it never prompts, never reads stdin, and never
updates anything itself - the explicit `skills update` verb stays the sole
updater. It writes nothing to stdout, never changes your exit code, and
swallows its own errors, so it can never break the command it runs inside:

```python
from agentsquire import BundledPackageDataSource, check_stale


def main():
    check_stale(
        BundledPackageDataSource("your_pkg"),
        prog_name="your-cli",
        update_command="your-cli skills update",
    )
    # ... the rest of your entry point
```

The notice shows unless a suppression gate holds: `CI` set to a non-empty
value, or `AGENTSQUIRE_NO_UPDATE_CHECK` set to a non-empty value. It is not
gated on an interactive terminal - it fires on non-TTY stderr too, so an
agent harness that runs your CLI with captured stderr still sees that an
update is available. Suppression is presence-disables, the `NO_COLOR`
convention: any non-empty value disables the notice (`CI=false` and
`AGENTSQUIRE_NO_UPDATE_CHECK=0` both suppress), while an empty string is
treated as unset.

### 4. Mark your package as skill-carrying (optional)

One pyproject line registers your package under the `agentsquire.skills`
entry-point group. Nothing reads it today - it is reserved for a future
environment-wide listing of skill-carrying packages and changes no behaviour:

```toml
[project.entry-points."agentsquire.skills"]
your_pkg = "your_pkg"
```

### 5. Ship repo-level skills from your repo root (optional)

Skills can also live at your repo root - in a top-level `skills/` directory
outside your importable package - and still ship to your users. This suits
skills that are developed and used from the repository itself. agentsquire
resolves the union of your package-data skills (Root A, section 1) and these
repo-level skills (Root B), so `skills install` and friends see both.

Root B resolves wheel-first then checkout: in a built wheel it is read from a
fixed in-package location `your_pkg/_repo_skills`; in an editable checkout it is
read straight from `<repo>/skills` (found by walking up to the first directory
with a `pyproject.toml` and a `skills/` subdir). To copy the repo-root `skills/`
into your wheel at that location, add this single force-include line to your
pyproject:

```toml
[tool.hatch.build.targets.wheel.force-include]
"skills" = "your_pkg/_repo_skills"
```

This is the only packaging change agentsquire requires: agentsquire ships no
build-hook plugin and adds no build-time dependency to your project.

The two roots must be disjoint - a skill name present in both roots is a
packaging mistake, not an override, and raises `DuplicateSkillError`. Enforce it
in your test suite in one line (it runs wherever agentsquire is already a runtime
dependency, so it protects editable installs too, where a build hook cannot run):

```python
from agentsquire import verify_skill_roots


def test_skill_roots_are_disjoint():
    verify_skill_roots("your_pkg")
```

Optional completeness guard: `force-include` copies the directory, but a stray
ignore rule could still drop a skill from the wheel. To fail the build loudly if
that ever happens, drop this self-contained `hatch_build.py` beside your
pyproject and point hatchling at it:

```toml
[tool.hatch.build.targets.wheel.hooks.custom]
path = "hatch_build.py"
```

```python
# hatch_build.py - force-include every repo-root skill and fail the build if the
# skills/ directory has been dropped or emptied. Self-contained; not an
# agentsquire dependency.
from pathlib import Path

from hatchling.builders.hooks.plugin.interface import BuildHookInterface


class CustomBuildHook(BuildHookInterface):
    def initialize(self, version, build_data):
        skills = Path(self.root) / "skills"
        if not skills.is_dir():
            raise ValueError("hatch_build.py: repo-root skills/ is missing")
        found = [d for d in sorted(skills.iterdir()) if d.is_dir()]
        if not found:
            raise ValueError("hatch_build.py: repo-root skills/ has no skills")
        force_include = build_data.setdefault("force_include", {})
        for skill in found:
            force_include[str(skill)] = f"your_pkg/_repo_skills/{skill.name}"
```

agentsquire never wires itself into your build: the completeness hook is your
own optional file, and the force-include line above is the only mandatory change.

## The provenance and update model

Installs are plain copies - no symlinks, no lockfile, no references back into
site-packages - so an installed skill survives upgrade or removal of your
package. Each installed `SKILL.md` carries a provenance stamp in its
frontmatter `metadata.agentsquire` map: `installer`, `installer_version`,
`source_package`, `source_version`, and `content_hash`. The skill body is
byte-identical to what you shipped.

`status` classifies every skill by local hash compares only (no network,
ever): not-installed, up-to-date, update-available (your shipped copy moved
on), or locally-modified (the user edited the install, the directory carries
no stamp, or a symlink sits at the target - none of them ours to touch).
`update` refreshes update-available skills and skips locally modified ones
unless `--force` is given; `uninstall` removes only directories whose stamp
names your package. User content is never silently overwritten or deleted -
a pre-existing symlink at a target (a common hand-wired setup) is reported
and skipped, never followed or clobbered.

## Python API

Everything the CLI group does is available as plain Python - enumerate,
detect, install, status, update, uninstall, and the staleness check - with no
CLI involved. See [docs/api.md](docs/api.md) for the reference.

## License

MIT.
