.md

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.

On this page

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 haveRunYou get
A read-mostly tool with no loginhcpx init weather --class public-readPublic read template with a demo binary
A tool with a connect step or a risky writehcpx init payments --class gated-writeGated write template with installer commands and a confirm write
An existing binary on your PATHhcpx wrap weatherThe same files with wrap.bin pointing at that binary
A wish to read working code firsthcpx init transit --template transitA 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.

weather/surface.json
{
  "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.

weather/policy.json
{
  "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.

weather/HARNESS.md
# 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#

Terminal
hcpx lint ./weather --json
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.

Terminal
hcpx check ./weather --init-evals    # write hcp.eval.json: one case per read, one refusal per confirm write
hcpx check ./weather --probe

Replace 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#

Terminal
hcpx install ./weather --json
hcpx activate weather --json
hcpx availability weather --json
hcpx exec weather now --json -- place=berlin

hcpx exec sent this envelope:

JSON
{ "pack_id": "weather", "verb_key": "now", "args": { "place": "berlin" } }

and got back:

JSON
{ "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:

surface.json (fragment)
"transfer": {
  "description": "Move funds. Requires confirm.",
  "cli": ["ledger", "transfer", "{{amount}}", "--to", "{{payee}}", "--confirm"],
  "policy": "write"
}
policy.json (fragment)
"transfer": { "operation": "write", "privacy": { "land": "local" }, "confirm": true }

Then prove the gate holds:

Terminal
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#

Terminal
hcpx share ./weather --json

share 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:

Terminal
hcpx install npm:@scope/hcp-pack-weather --json

HCP'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.