Build a pack
Wrap a CLI you already ship in about ten minutes, then prove it installs, answers on both faces and refuses a gated write until confirmed.
A pack is three files beside a binary you already have: surface.json names
the commands, policy.json gives each a rule and HARNESS.md teaches the
agent. This tutorial scaffolds them, lints, checks and runs the result, and adds
one gated write. Install hcpx first (Quickstart)
and search the catalog, since someone may already
have wrapped your tool.
Build it step by step#
Scaffold the three files#
Pick the starting point closest to what you have. The pack classes explain each shape.
| You have | Run | You get |
|---|---|---|
| A read-mostly tool with no login | hcpx init weather --class public-read | Public read template with a demo binary |
| A tool with a connect step or a risky write | hcpx init payments --class gated-write | Gated write template with installer commands and a confirm write |
An existing binary on your PATH | hcpx wrap weather | The same files with wrap.bin pointing at that binary |
| A wish to read working code first | hcpx init transit --template transit | A copy of a gold pack (transit or ledger) |
Each writes ./<id>/ with the three files and a package.json that depends on
@harnesscontextprotocol/cli, so installing the pack also installs the CLI.
Pack layout lists the optional files and what
stays out.
Name the commands#
In surface.json, set the id, the binary, the Binding mode and the commands the
pack offers.
{
"id": "weather",
"name": "Weather",
"kind": "wrap",
"cliBin": "weather",
"plane": "runtime",
"transports": ["cli", "json_exec"],
"binding": { "mode": "public", "hub": "public" },
"wrap": { "bin": "weather" },
"verbs": {
"find": { "description": "Resolve a place name.", "cli": ["weather", "find", "{{query}}"] },
"now": { "description": "Current conditions.", "cli": ["weather", "now", "{{place}}"] },
"forecast": { "description": "Next 48 hours.", "cli": ["weather", "forecast", "{{place}}"] },
"alerts": { "description": "Active warnings.", "cli": ["weather", "alerts", "{{place}}"] },
"status": { "description": "Upstream health.", "cli": ["weather", "status"] }
}
}Keep the set closed and small, five to twelve names. Each cli array is the
command's CLI form and every {{name}} placeholder maps to a JSON argument; the
runtime appends --json and never passes arguments through a shell.
Manifest describes every field.
Give every command a Policy row#
Reads are read and anything that changes the world is write. A write that
must never fire unconfirmed also gets confirm: true.
{
"surface_id": "weather",
"version": 1,
"verbs": {
"find": { "operation": "read", "privacy": { "land": "personal" } },
"now": { "operation": "read", "privacy": { "land": "personal" } },
"forecast": { "operation": "read", "privacy": { "land": "personal" } },
"alerts": { "operation": "read", "privacy": { "land": "personal" } },
"status": { "operation": "read", "privacy": { "land": "personal" } }
},
"stimuli": {}
}land says where the data may end up (personal, brain or local). Roles,
user lists and keys stay out of the file because the runtime grants those, as
Policy file explains.
Teach it on one screen#
HARNESS.md teaches an agent, and a person, the hot path in a lead, a command
table, examples and the mistakes to avoid. It never holds essays, secrets or
host-absolute paths.
# HCP — Weather
Public read pack. No login. Resolve a place, then read conditions, forecast, or alerts.
## Hot path
| Command | CLI |
|---------|-----|
| `find` | `weather find "Berlin" --json` |
| `now` | `weather now berlin --json` |
| `forecast` | `weather forecast berlin --json` |
| `alerts` | `weather alerts berlin --json` |
| `status` | `weather status --json` |
## Anti-patterns
- Calling `now` in a tight loop. Read `forecast` once instead.
- Guessing a place id. Always `find` first.Skill file is the writing guide.
Lint until clean#
hcpx lint ./weather --json{ "ok": true, "pack_id": "weather", "issues": [] }Lint proves the files agree: every command has a Policy row, every row names a real command, and the Skill exists and matches the manifest. Testing lists each failure and its fix.
Check it#
hcpx check grades the pack as a reviewer would, and --probe runs the CLI as
an agent does.
hcpx check ./weather --init-evals # write hcp.eval.json: one case per read, one refusal per confirm write
hcpx check ./weather --probeReplace every TODO argument in hcp.eval.json with a real value and fix
findings until none is an error, using the rules in
Check and evaluate.
Install, activate, run#
hcpx install ./weather --json
hcpx activate weather --json
hcpx availability weather --json
hcpx exec weather now --json -- place=berlinhcpx exec sent this envelope:
{ "pack_id": "weather", "verb_key": "now", "args": { "place": "berlin" } }and got back:
{ "ok": true, "pack_id": "weather", "verb_key": "now", "result": { "temp_c": 17, "sky": "overcast" } }Running weather now berlin --json directly returns the same result, because
the two forms are the command's twin face.
Add a gated write#
Declare any mutation that must never fire from a hallucinated turn as a confirm
write, in the shape the ledger gold pack uses:
"transfer": {
"description": "Move funds. Requires confirm.",
"cli": ["ledger", "transfer", "{{amount}}", "--to", "{{payee}}", "--confirm"],
"policy": "write"
}"transfer": { "operation": "write", "privacy": { "land": "local" }, "confirm": true }Then prove the gate holds:
hcpx exec ledger transfer --json -- amount=2.80 payee=demo-transit
# { "ok": false, "error": "confirm required" }
hcpx exec ledger transfer --json -- amount=2.80 payee=demo-transit confirm=true
# { "ok": true, … }The runtime checks confirm before the adapter runs, so no Invoker can skip it.
Share it#
hcpx share ./weather --jsonshare lints the pack and prints a catalog row to add with a pull request, and
Share a pack has the checklist. Because hcpx init writes
"hcp": { "pack": "." } into package.json, the pack can also ship as an npm
package:
hcpx install npm:@scope/hcp-pack-weather --jsonHCP's own packages are a private preview on GitHub Packages for now, not on the public npm registry.
What stays out#
The pack has no connector code, server, host tool registration or token, and
it does not wrap the whole weather binary. Hosts pick it up through
hcpx adopt (Hosts), and a binary that needs credentials or a
setup step continues in Wrap a CLI.