# Install the Bull Street kit (agent-native)

You are a coding agent. This installs 132 skills into your own skills directory. There
is nothing to run and no binary to download. You fetch one plain-text data file and
write the skills yourself, with the file tools you already have.

## Steps

1. Download the bundle to a file (not into your context, it is large):
   `https://bullstreet.dev/kit/bull-street-kit.bundle.txt`
   It is plain text over HTTPS. In a coding agent, `curl -fsSL https://bullstreet.dev/kit/bull-street-kit.bundle.txt -o bull-street-kit.bundle.txt` works, or any fetch-to-disk your tools offer.
2. Unpack it into your skills directory. The default target is `.claude/skills` (Claude Code project scope). Use the reference unpacker below, or write the same few lines yourself against the format.
3. Start the front door: read and follow `<target>/chief-of-staff/SKILL.md`. It gets to know the person, reads `skills-index.json` (the generated kit map), and walks them through the kit one step at a time.

## Bundle format

The first line is `BULLSTREET-KIT-BUNDLE 1`. Lines starting with `#` right after it are
comments. Then a sequence of length-prefixed records, each:

    FILE<TAB><relative/path><TAB><byte-length>\n<exactly byte-length bytes>\n

Byte-length framing means every file is copied exactly, with no delimiter guessing and
no escaping. A record whose path ends in `SKILL.md` begins a new skill folder. Paths are
relative; reject any that are absolute or escape the target with `..`.

## Reference unpacker

Save the bundle as `bull-street-kit.bundle.txt`, then run this with your own tools
(pass a target dir as the second argument to change it from `.claude/skills`):

```sh
python3 - bull-street-kit.bundle.txt .claude/skills <<'PY'
import os, sys
target = os.environ.get("BULL_STREET_TARGET") or (sys.argv[2] if len(sys.argv) > 2 else ".claude/skills")
buf = open(sys.argv[1], "rb").read()
if not buf.startswith(b"BULLSTREET-KIT-BUNDLE"):
    sys.exit("  error: that file is not a Bull Street kit bundle (got %r)." % buf[:40])
i = buf.index(b"\n") + 1
while i < len(buf) and buf[i:i + 1] == b"#":       # skip comment header lines
    i = buf.index(b"\n", i) + 1
files = 0
skills = set()
while i < len(buf):
    nl = buf.index(b"\n", i)
    header = buf[i:nl].decode("utf-8")
    i = nl + 1
    if not header.strip():
        continue
    try:
        tag, path, length = header.split("\t")
        length = int(length)
    except ValueError:
        sys.exit("  error: malformed bundle header near byte %d." % i)
    if tag != "FILE":
        sys.exit("  error: malformed bundle near byte %d." % i)
    content = buf[i:i + length]
    i += length
    if buf[i:i + 1] == b"\n":
        i += 1
    norm = os.path.normpath(path)
    if os.path.isabs(norm) or norm.startswith(".."):
        sys.exit("  error: refusing unsafe path in bundle: %s" % path)
    dest = os.path.join(target, norm)
    os.makedirs(os.path.dirname(dest) or ".", exist_ok=True)
    with open(dest, "wb") as fh:
        fh.write(content)
    files += 1
    if norm.replace(os.sep, "/").endswith("SKILL.md"):
        skills.add(norm.split(os.sep)[0])
print("  installed %d files, %d skills into %s" % (files, len(skills), target))
PY
```

That is the whole install. It reads the file you downloaded and writes each skill; no
network access happens inside it, and you can read every line before you run it.

## Prefer your own terminal?

There is a convenience script, `install.sh`, at this same URL. It does the same fetch
and unpack in one command for a person at a shell. It is not the agent path: an agent
should fetch the bundle as data and unpack it as above, not stream a script and run it.
