Skip to content
Skip to content
HCP
Quickstart

Build a pack

Wrap a CLI you already have into a Harness Pack. Copy a gold pack, change four fields, write one policy row per command, lint, exec.

By the end of this page you will have a pack that installs into the reference runtime, executes from the CLI face and the JSON face, and refuses a gated write without confirmation.

You start by copying the closest gold pack and changing four fields.

Pick a gold pack

If your capability…Copy
Needs no login and is mostly readsexamples/packs/transit
Needs a connect step, or has a write that must not auto-fireexamples/packs/ledger
cp -r examples/packs/transit ./packs/weather

Change four fields in the manifest

Open packs/weather/surface.json. Set the id, the CLI binary, the Binding mode, and the commands.

packs/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 command set closed and small: five to twelve names. Each cli array is the space-separated CLI form, and {{name}} placeholders map to JSON args. The runtime appends --json and never shell-interpolates.

Field by field: Manifest.

Write one Policy row per command

Every command needs a row. Reads are read; anything that changes the world is write; a write that must not fire unconfirmed adds confirm: true.

packs/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 data may land: personal (actor-private), brain (shared project), or local (a workspace directory). Nothing else goes in this file: no roles, user lists, or keys. Details: Policy file.

Write the Skill on one screen

HARNESS.md teaches an agent (and a human) the hot path. It has a lead, a table, examples, and anti-patterns. It contains no essays, secrets, or host-absolute paths.

packs/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

Writing guide: Skill file.

Lint until clean

hcp lint ./packs/weather --json
{ "ok": true, "pack_id": "weather", "issues": [] }

Lint fails when a command lacks a Policy row, a Policy row names a command that does not exist, HARNESS.md is missing, a wrap declares neither named commands nor pass_through, or a control-plane pack is not on mothership Binding.

Install, activate, exec

hcp install ./packs/weather --json
hcp activate weather --json
hcp availability weather --json
hcp exec weather now --json -- place=berlin

The exec envelope the CLI sent on your behalf:

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

And the result envelope:

{ "ok": true, "pack_id": "weather", "verb_key": "now", "result": { "temp_c": 17, "sky": "overcast" } }

The CLI face of the same command, weather now berlin --json, produces the same result. That is the twin face.

Add a gated write (optional)

If your CLI has a mutation that must never fire from a hallucinated turn, declare it as a confirm write. Copy the shape from ledger:

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

Then prove the gate:

hcp exec ledger transfer --json -- amount=10
# { "ok": false, "error": "confirm required" }

hcp exec ledger transfer --json -- amount=10 confirm=true
# { "ok": true, … }

The runtime enforces the gate before the adapter runs, so no Invoker can skip it.

What you did not do

You did not write a connector, implement a server, register tools in a host, put a token in a file, or wrap the whole weather binary. You named five commands and gave each a rule.

Next