I built a package manager for my AI agent skills. Then I deleted it.
How I replaced a 6,000-line package manager with a private GitHub repository and Vercel's skills CLI to distribute AI agent skills across projects and coding agents.

On this page · 10 sections
I recently deleted 6,282 lines from a personal project and made it substantially more useful.
The project is called Harp. It was supposed to solve an almost embarrassingly small problem: I had a growing set of Agent Skills, reusable folders of instructions and workflows for coding agents, and I wanted to keep them in one private Git repository so I could use them across the projects, machines, and agents I work with.
Instead, I built a Bun package, a CLI, a registry, an installer, a plugin, an update system, a doctor command, a persistent memory layer, an interactive selector, a dogfood environment, and a release process.
All to distribute some Markdown files.
To be fair to myself, the idea was not stupid. None of those systems appeared out of nowhere, and most of them solved real problems. The mistake was that I never stopped to compare the size of the machinery with the value of the job it was doing.
By the time I finally did, Harp technically worked. It was also a project I had stopped using because my brain hurt every time I thought about opening it.
This is the story of how it got there, why I did not simply use a dotfiles repository, and how Harp eventually became the tiny private skills catalog I had wanted in the first place.
Why one person needed a distribution system
By the time I started thinking about Harp, terminal agents had become a fairly central part of how I worked.
They made it considerably easier to enter an unfamiliar codebase, reconstruct how things fit together, and start contributing without spending the first several days manually tracing every file and abstraction.
At some point, I found myself moving between three projects that barely resembled one another: different scopes, platforms, repositories, and workflows. I was also using multiple coding agents, working from a few physical machines, and occasionally relying on remote development environments over SSH.
That context matters, because otherwise the obvious question is:
Why would one person need a distribution system for their own skills?
Because, in practice, “one person” had stopped meaning one project, one computer, one agent, or one stable environment.
Every environment had its own setup. Each agent expected skills and instructions in slightly different places. A workflow I improved in one setup did not automatically follow me to the next. Project-specific instructions needed to remain private and separate, while still being easy to develop, version, install, and update.
The skills themselves were becoming increasingly valuable. Recreating their setup every time was becoming increasingly tedious.
That was the problem Harp was supposed to solve.
The files I kept teaching agents to recreate
Agent Skills are not especially mysterious.
At their core, they are directories containing instructions. Usually that means a SKILL.md file with a little YAML frontmatter describing what the skill does and when an agent should use it.
More elaborate skills can carry their own references, templates, assets, or helper scripts. That last part is particularly useful. Natural-language instructions are good at carrying judgment and context, but they are not always the best way to enforce something that must behave exactly the same way every time.
Suppose a skill asks an agent to produce a name that follows a strict format. You can explain the rules in Markdown and trust the model to verify its own output, or you can include a tiny Bash, Python, or JavaScript validator and tell the agent to run it.
The Markdown carries the workflow. The script carries the invariant.
As I worked across more repositories, these small packages became increasingly valuable. I had project-specific commit conventions, recurring investigation processes, browser-testing flows, instructions for navigating unusual workspaces, and preferences for how agents should document what they found.
Some of them made no sense outside one project context. Some were personal. None belonged in a public catalog.
I wanted one private place where I could improve them with agents, keep their history in Git, group them by project or domain, and reproduce the same setup from a terminal on any machine.
A dotfiles repository could technically carry the files, but it never felt like the right boundary.
My skills were not shell aliases or editor preferences. They were living documents that I wanted to open with an agent, discuss, revise, and test without dragging the rest of my machine configuration into the context. Some were tied to private codebases. Others included instructions I simply had no reason to publish.
Different agents also expected skills in different directories. Every new machine meant remembering which files belonged where, copying or linking them, and repeating the process whenever a skill changed.
So I decided to build something specifically for that.
I called it Harp.
The first Harp
The original plan sounded reasonable enough.
Harp would be a private package that I could install from the terminal. It would keep my skills in one canonical place, group them into categories or bundles, install the appropriate set for a repository, and update them when I released a new version.
I also wanted some form of persistent agent memory. A repository could accumulate a Markdown file containing durable things the agent had learned, while Harp would teach it how to append, retrieve, archive, and distinguish that memory from temporary task context.
Then I started asking what a good experience would require.
| What I wanted | What Harp now had to own |
|---|---|
| Install from a terminal | Package publishing, authentication, a compiled executable, and a stable shim |
| Work on any machine | Managed directories, platform paths, setup logic, and environment checks |
| Avoid losing local edits | Hashes, manifests, markers, conflict detection, and overwrite rules |
| Keep skills up to date | Versions, releases, update state, and migration behavior |
| Choose only relevant skills | Categories, bundles, selectors, and enable/disable state |
| Know when something broke | A doctor command covering paths, links, state, and formats |
| Trust a new release | An isolated dogfood installation and reinstall test |
| Support repository memory | Repository discovery, local state, archive operations, and resolution rules |
This is the annoying part about overengineering: it rarely feels like overengineering while you are doing it.
Every new piece of machinery is usually a reasonable response to a problem created by the previous piece of machinery. At no point did I sit down and decide to build a private package manager for Markdown files. I just kept solving the next legitimate problem until I had one.
The CLI alone became a project of its own.
I wanted the experience to be interactive, so I looked through terminal UI and prompting libraries in the Node ecosystem. The tools I found most attractive were often written in Rust, but I am not a Rust programmer and learning the language only to build this personal installer seemed like a ridiculous detour. I experimented with things such as Inquirer instead.
Then there were global installation paths, managed package copies, symlinks, plugin registration, update markers, checksums, and all the small failure cases that appear once a tool starts touching a user’s home directory.
The first version put its state under a global .harp area. It tracked configuration, installed skills, manifests, and enough metadata to understand what was current, stale, modified, or missing.
A doctor command checked whether Harp had been installed in the expected places. A dogfood script built the current version, installed it into an isolated temporary home directory, ran the generated executable from another project, and tested what happened during reinstallation.
Later versions added skill categories, default bundles, an interactive selector, local task memory, repository discovery, local skill overrides, editor integration, and generated repository harnesses.
None of these ideas was obviously absurd in isolation.
By version 0.1.11, however, Harp had 54 tracked files and 6,398 lines. A rename from engineering-core to repo-harness had to touch configuration, commands, tests, registry manifests, documentation, skills, and dogfood tooling.
The distribution layer was growing faster than the workflows it existed to distribute.
Technically functional, practically dead
In retrospect, I do not think the first Harp was bad engineering or even a bad product idea.
It had coherent reasons for doing what it did. If I genuinely wanted to maintain a private agent-workflow platform, most of those systems would eventually have been necessary.
The problem was that I did not want a platform.
I wanted to share my own skills across devices and repositories, keep them private, and update them without moving files around by hand.
I had badly underestimated how complex the project would become, how long it would take to make every subsystem trustworthy, and how little additional value most of that complexity returned to me personally.
After roughly a week of intense work, I had something relatively functional. I could install it. I could manage skills. I could point to the systems and explain what each was supposed to do.
What I could not do was confidently explain how the whole thing worked together.
I did not trust that every update path behaved the way I intended. I did not feel comfortable changing one subsystem without worrying about the others. Fixing and simplifying it properly looked like another month of work, and the reward at the end would still be a nicer way to move my own Markdown files around.
So I stopped.
For about a month, I did not think about Harp and did not use Harp. The project was technically available, but the mental cost of opening it was higher than the friction it was supposed to remove.
That is enough for a personal tool to fail.
The repository I could not understand
A few weeks ago, I came across an X post about Matt Pocock’s skills repository.
I did not immediately connect it to Harp. I was mostly interested in seeing how he structured his skills and what I could learn from the instructions themselves.
Then I saw the installation command:
npx skills@latest add mattpocock/skills
It was exactly the kind of experience I had wanted. One command, an interactive selection process, and support for installing the chosen skills into different coding agents.
My first assumption was that he had hand-rolled the installer.
So I went looking for it.
I expected to find the hidden equivalent of Harp: a substantial CLI, some kind of registry, package state, release machinery, and all the surrounding code required to make the experience work.
But the installation UX did not belong to his repository at all.
The repository supplied the skills. The distribution came from Vercel Labs’ open-source skills CLI.
At first, I was shocked that such a good experience could apparently come from a collection of Markdown files and a little configuration.
That was not quite what was happening, of course. The Markdown files were not somehow producing the UX. The useful part was that the repository did not need to produce the UX at all.
A shared tool was already responsible for discovering skills, showing the selector, choosing a project or global scope, targeting different agents, installing through symlinks or copies, and updating the result later.
In hindsight, this should not have been surprising.
Making skills portable and easy to distribute is clearly in the interest of the companies building this ecosystem. I had treated a shared ecosystem problem as though it were a uniquely personal infrastructure problem.
The skills CLI did not support half as many custom ideas as the original Harp was supposed to support.
It still did Harp’s primary job stupidly more efficiently.
And the best part was not that somebody else had written a better installer than mine.
It was that maintaining an installer was no longer my problem.
Deleting the platform
Once I understood that boundary, the renovation became obvious.
I did not need to repair the old version of Harp. I needed to remove almost all of it.
On July 22, 2026, I deleted the package manifest, Bun lockfile, TypeScript configuration, CLI implementation, test suite, build scripts, dogfood environment, plugin metadata, registry, bundle categories, setup logic, update logic, doctor command, memory system, overrides, generated repository harnesses, and release-specific documentation.
The commit changed 62 paths, added 434 lines, and deleted 6,282.
The repository fell from 54 tracked files to 12.
I do not bring up those numbers because deleting code is inherently impressive. It is perfectly possible to delete thousands of useful lines and make a project much worse.
What mattered was that each deleted area represented a category of responsibility I would never have to revisit on the next skill.
The responsibility boundary now looks like this:
| Responsibility | Owner |
|---|---|
| My private workflow knowledge | Harp |
| History, collaboration, and portability | Git and GitHub |
| Skill discovery and selection | skills CLI |
| Project or global scope | skills CLI |
| Agent targeting | skills CLI |
| Symlinking, copying, and updates | skills CLI |
That was the product I had been trying to build all along.
What Harp looks like now
Harp is now a private, Git-versioned catalog of self-contained skills.
The skills are grouped by the project context they belong to:
harp/
├── skills/
│ ├── sales-company/
│ │ └── sales-company-commit-guide/
│ │ ├── SKILL.md
│ │ ├── agents/
│ │ │ └── openai.yaml
│ │ └── scripts/
│ │ └── format-commit-message.mjs
│ ├── investments-company/
│ ├── analytics-platform-company/
│ └── pedro/
│ └── pedro-trace-api-flow/
│ ├── SKILL.md
│ └── agents/
│ └── openai.yaml
├── README.md
└── AGENTS.md
The grouping is intentionally boring. A workflow for the sales project lives under sales-company. A personal workflow lives under pedro. The other project groups can remain visible until I have something worth adding to them.
Every installable skill follows the same basic shape:
skills/<group>/<skill-name>/
├── SKILL.md
├── agents/
│ └── openai.yaml
├── references/ # optional supporting knowledge
├── scripts/ # optional deterministic helpers
└── assets/ # optional output resources
At minimum, the skill needs a SKILL.md. One of my personal skills begins like this:
---
name: pedro-trace-api-flow
description: Trace API flows end to end from repository source and write progressive Markdown guides with TypeScript contract projections. Use when a request needs a source-backed explanation of how an endpoint, client action, job, event, or cross-service business flow works.
---
# Trace API Flow
Build an evidence chain from a concrete trigger to its final observable outcome.
Use repository source as evidence. Keep implementation code and working-tree
checkout state unchanged; write only the generated guide.
The real file continues with instructions for establishing the boundaries of the trace, following the call path, separating proven requirements from caveats and unknowns, projecting relevant contracts into TypeScript, and generating a structured guide.
From Harp’s perspective, however, it is still just one directory.
The optional OpenAI configuration gives it a nicer display name and a default invocation:
interface:
display_name: "Trace API Flow"
short_description: "Trace API flows into source-backed guides"
default_prompt: >
Use $pedro-trace-api-flow to trace this API flow
from source and write its guide.
When a workflow contains something that should be deterministic, the script lives beside the instructions that use it.
One of my commit skills, for example, needs to derive a task number from the current branch, allow only a small set of commit types, normalize the description, and emit one exact message shape. The example below is simplified and anonymized, but the responsibility split is the same.
Rather than asking the model to remember every formatting rule and then verify itself, the skill calls a small JavaScript formatter:
const allowedTypes = ['FEAT', 'FIX', 'REFACTOR', 'CHORE']
const [branch, type, ...descriptionParts] = process.argv.slice(2)
const branchMatch = branch?.match(
/^TASK-([0-9]+)-[a-z0-9-]+$/,
)
if (!branchMatch) {
fail(`Invalid task branch: ${branch}`)
}
if (!allowedTypes.includes(type)) {
fail(`Invalid commit type: ${type}`)
}
const description = descriptionParts.join(' ').trim().replace(/\s+/g, ' ')
if (!description) {
fail('Commit description cannot be empty')
}
process.stdout.write(`TASK-${branchMatch[1]} ${type}: ${description}\n`)
function fail(message) {
process.stderr.write(`${message}\n`)
process.exit(1)
}
This has become a useful rule for how I write skills:
Use Markdown for judgment, context, and sequence. Use scripts for the parts that must be correct in exactly one way.
Most importantly, adding that formatter required no repository-wide scripting framework. It belongs to the one skill that needs it.
Installing and updating a private Agent Skills repository
Because Harp is private, a new machine first needs normal GitHub authentication:
gh auth login
gh auth setup-git
Then I can install Harp with:
npx skills@latest add melloirl/harp \
--agent codex \
--agent claude-code \
--agent pi
The installer discovers the skills in the repository and lets me choose which ones to install. It can place them at project or global scope and either symlink them from one canonical copy or create independent copies.
For a repository of your own, the command is the same shape:
npx skills@latest add <github-user>/<private-skills-repo>
To see what the repository exposes without installing anything:
npx skills@latest add <github-user>/<private-skills-repo> --list
To update installed skills later:
npx skills@latest update
That is now the entire distribution system.
There is no Harp release to publish. GitHub is the source. The upstream CLI handles installation and updates. I only maintain the contents I actually care about.
Extending it is equally boring
Suppose I want to preserve a new workflow for reviewing migrations.
I add a directory:
skills/pedro/pedro-review-migration/
├── SKILL.md
├── agents/
│ └── openai.yaml
├── references/
│ └── migration-rules.md
└── scripts/
└── validate-migration.mjs
I write the core instructions in SKILL.md, put detailed supporting material in references/, and include a script only when the workflow contains a check that should not depend on model judgment.
Then I validate the skill with the official skills-ref validator and confirm that the installer can discover the catalog:
skills-ref validate skills/pedro/pedro-review-migration
npx skills@latest add . --list
After that, I commit and push it.
There is no registry entry to synchronize. No CLI command to implement. No package version to bump. No release to publish. No bundle definition to update. No installer behavior to test. No migration for a global state file.
On another machine, I install the new skill or update the one already there.
| Task | What I do now |
|---|---|
| Create a skill | Add one self-contained directory |
| Improve a skill | Edit the files inside that directory |
| Add supporting knowledge | Add a local reference file |
| Add deterministic behavior | Add a local script |
| Validate it | Run skills-ref and catalog discovery |
| Distribute it | Commit and push to GitHub |
| Install it elsewhere | Run npx skills add |
| Refresh it | Run npx skills update |
This version does not support every idea I had for the original Harp.
The persistent memory system is gone. Custom bundles are gone. Repository harness generation is gone. Local override resolution is gone. Several things I spent time designing simply did not survive.
Maybe some of those ideas will return one day in another shape. The important part is that they no longer have to live inside Harp merely because Harp happens to contain my skills.
What I actually learned
I do not consider the first version a complete waste.
Building it taught me how much work hides behind a sentence such as “I just want to sync these files between machines.” It forced me to think about ownership, updates, conflict behavior, reproducibility, validation, and what a good skill should contain.
But I also do not think everybody needs to repeat the experiment.
The most important mistake was not writing too much code. It was putting the responsibility boundary in the wrong place.
Harp’s unique value was never its installer. It was the private workflow knowledge I had accumulated while working across different projects, repositories, machines, and agents.
Git already knew how to version that knowledge. A shared tool already knew how to distribute it. Once I let those systems own their parts, Harp could become responsible only for mine.
There is a broader AI lesson here too.
Coding agents made it cheap enough for me to build a surprisingly capable platform in a few days. That is impressive, but it also made it easy to postpone the more important question: should this platform exist at all?
AI reduces the cost of implementation. It does not automatically reduce the cost of understanding, trusting, and maintaining whatever gets implemented.
Sometimes it just lets you arrive at the wrong abstraction much faster.
The current Harp is less ambitious than the original version. It is also the version I actually use.
Now, when I find a workflow worth preserving, I do not have to think about a CLI, registry, release pipeline, installer, state manager, or migration path.
I write the skill, commit it, and move on.