---
name: hcp-wrap
description: Turn a command-line tool into an HCP Harness Pack (three files) that any agent harness can install. Use when the user asks to wrap, package, or expose a CLI to agents with HCP.
---

# Wrap a CLI as an HCP pack

You are an agent. The input is a binary name, `<bin>`; if the user did not give
one, ask. You will produce `./packs/<id>/` with `surface.json`, `policy.json`,
and `HARNESS.md`, prove it, and report. The full guide is at
https://harnesscontextprotocol.io/docs/packs/wrap-a-cli

## Rules

- Name 5 to 12 commands. Never expose the whole binary and never add a
  catch-all `run` or `exec`.
- Never put a token, key, or password in any pack file.
- Never run a command that changes data to find out what it does, not even
  one that asks for `--yes`. Read `<bin> <command> --help` instead, because the
  user's data is real.
- Never run a write with `confirm=true` during this task.
- Type `hcpx` (the HCP CLI; `hcp` is the same program, and another tool also
  uses that name). If `hcpx doctor --json` does not answer `"protocol": "hcp"`,
  set it up with step 1 of https://harnesscontextprotocol.io/setup.md.
- Run `hcpx` commands with `--json` and read the result. `hcpx check … --agent`
  and `hcpx preview` print text for you to read instead.
- The CLI prints follow-up commands (`next`, `run`, `hint`) as `hcp …`; run
  them as `hcpx …`.

## 1. Check that the CLI is ready for agents

Read `<bin> --help` and the help of each subcommand you might wrap. The CLI must:

| Requirement | Look for |
|-------------|----------|
| Structured output | a `--json` flag, or JSON by default |
| No prompts on the agent path | `-y`, `--yes`, `--non-interactive`, or no prompts at all |
| Failure is visible | a non-zero exit code on error |
| Risky writes are explicit | `--confirm`, `--apply`, `--force`, or a separate subcommand |

If the CLI has no structured output or only works through an interactive
wizard, stop, because HCP wraps CLIs that already speak JSON. Report what is
missing and suggest adding `--json` and a non-interactive flag first.

## 2. Pick the class

The class depends on login only. Policy handles risky writes in step 5,
whatever the class.

| The CLI… | Class | Binding mode |
|----------|-------|--------------|
| Works without a login, token, or account (it may still write) | `public-read` | `public` |
| Needs a login, a token, or a one-time setup step | `gated-write` | `user` or `local` |
| Reads and sends mail, calendar, or chat for a user | `gated-write`, used as a linked channel | `user`, with a `family` |
| Administers servers, fleets, or deployments | control plane: ask the user; it must be a separate pack on `mothership` | `mothership` |

## 3. Draft the pack from the help

```bash
hcpx wrap <bin> --id <id> --class <public-read|gated-write> --json
```

`<id>` is lower case, short, and stable, usually the binary name. The pack goes
to `./packs/<id>/`, with a `package.json` for publishing it on npm beside the
three files; keep it. `hcpx wrap` reads `<bin> --help` (stdin closed, no colour,
nothing else run) and drafts one command per usage line it recognises. Read its
result:

| Field | Meaning |
|-------|---------|
| `drafted` | Commands it wrote, with the CLI form, guessed `operation`, and `confirm` |
| `candidates` | Subcommand names it saw but could not turn into commands; write these by hand |
| `json_support` | `false` means the help never mentions JSON; see step 1 |
| `warnings` | Things to fix before the check loop |

You finish the draft in steps 4 to 6.

## 4. Finish the commands

Edit `./packs/<id>/surface.json`. Keep 5 to 12 entries in `verbs`, taken from
the help output. If the draft is empty, write them yourself:

```json
"verbs": {
  "status": { "description": "Upstream health.", "cli": ["<bin>", "status"] },
  "find":   { "description": "Search items by text.", "cli": ["<bin>", "find", "{{query}}"] }
}
```

- Names are lower case; group families with dots (`setup.status`).
- Each `{{name}}` becomes a JSON argument. Use unambiguous names
  (`issue_number`, not `n`), and describe each one as JSON Schema with an
  example, so agents can call the command without guessing:
  `"args": { "type": "object", "properties": { "issue_number": { "type": "string", "description": "Issue number from issue.list", "examples": ["42"] } }, "required": ["issue_number"] }`.
  Leave an argument out of `required` when the CLI can do without it; the
  runtime then drops the value and the flag in front of it.
- Replace every `TODO` and every drafted description such as "The id." with
  real text. Look example values up with a read command; never invent ids.
- The runtime appends `--json`. If the CLI selects JSON differently, put that in
  `cli[]` (`"--json", "name,url"` or `"-o", "json"`) and nothing is appended. If
  it prints JSON by default, add `"json_flag": []` to the command.
- Prefer the commands an agent needs daily: health, discovery, read, and at most
  one or two writes.
- Keep `binding.mode` from step 2.
- Keep `"workspace": { "mode": "project" }` under `wrap`: the CLI then runs in
  the user's project, as it would from a terminal. Never copy project files into
  the pack to make a command work, and never edit the CLI you are wrapping.

A finished `surface.json` for a small notes CLI looks like this; compare
yours against it instead of reading other packs' files:

```json
{
  "id": "notes",
  "name": "Notes",
  "kind": "wrap",
  "cliBin": "notes",
  "binding": { "mode": "public", "hub": "none" },
  "wrap": { "bin": "notes", "workspace": { "mode": "project" } },
  "verbs": {
    "list": {
      "description": "Notes in this project, newest first, optionally one tag.",
      "cli": ["notes", "list", "--tag", "{{tag}}"],
      "args": { "type": "object", "properties": { "tag": { "type": "string", "description": "Tag to filter by, from list output", "examples": ["shopping"] } } }
    },
    "delete": {
      "description": "Delete one note by id; returns the deleted id.",
      "cli": ["notes", "delete", "{{id}}", "--yes"],
      "args": { "type": "object", "properties": { "id": { "type": "string", "description": "Note id from list or search", "examples": ["1"] } }, "required": ["id"] }
    }
  }
}
```

with `policy.json` rows `"list": { "operation": "read", "privacy": { "land": "local" } }`
and `"delete": { "operation": "write", "privacy": { "land": "local" }, "confirm": true }`.

## 5. Check the Policy rows

Edit `./packs/<id>/policy.json` and check each row, because the draft guesses
from command names. Every command in `verbs` gets a row, and no row names a
command that does not exist.

| The command… | `operation` | `privacy.land` | `confirm` |
|--------------|-------------|----------------|-----------|
| Only reads | `read` | `personal` for the user's own data, `brain` for shared project data, `local` for files in the working directory | omit |
| Changes something reversible | `write` | as above | omit |
| Sends, deletes, publishes, or deploys | `write` | as above | `true` |
| Moves money, or deletes what cannot be restored | `write` | as above | `"always"` (asks even in access mode `run`) |

Leave `"stimuli": {}` unless the tool pushes events on its own.

## 5b. Give risky commands a card

A command with `confirm` asks a person before it runs, showing a card built from
the call's arguments with the exact command under it. `hcpx wrap` writes a
`preview` for each such command; check that it reads well:

```bash
hcpx install ./packs/<id> --json && hcpx activate <id> --json
hcpx preview <id> <command> -- key=value
```

Adjust `preview` in `surface.json` if a field is wrong: `kind` is one of
`message`, `payment`, `event`, `removal`, `publish`, `record`, `fields`, and
every `{{name}}` must be an argument of the command. `hcpx check` suggests the
JSON (M025) for confirm commands that have none.

## 6. Finish the Skill

`./packs/<id>/HARNESS.md` must stay one screen. Fix the lead and the hot path,
and add anti-patterns you saw while reading the help:

```md
# HCP — <Name>

<One sentence: what the pack does.> <One sentence: "No login." or "Run connect once.">

## Hot path

| Command | CLI | Policy |
|---------|-----|--------|
| `status` | `<bin> status --json` | read |

## Anti-patterns

- <A mistake an agent would make, and what to do instead>
```

Keep secrets, absolute paths, and copied manuals out of it.

## 7. Check until clean

Write eval cases, then let the checker drive the fixes:

```bash
hcpx check ./packs/<id> --init-evals
```

Replace every `TODO` argument in `./packs/<id>/hcp.eval.json` with a real value
(look them up with the CLI; never invent ids). Then loop:

```bash
hcpx check ./packs/<id> --probe --agent
```

Run it from the project the CLI works on, so probes and evals see real data.
Apply every item under "Fix now (errors)" exactly as its "Do:" line says, run
the check again, then work through the warnings. Stop when the score is at least
90 with no errors. Never change an eval expectation to make it pass; fix the
command instead. The checker runs the CLI with stdin closed and never runs a
write; it proves writes exist with `--help` and replays confirm-gated writes only
to see them refused.

## 8. Install and run once

```bash
hcpx install ./packs/<id> --json
hcpx activate <id> --json
hcpx exec <id> <read-verb> --json -- key=value
```

The read must return `"ok": true`. A confirm-gated write run without
`confirm=true` must return `"code": "confirm_required"` and
`"error": "confirm required"`; nothing runs. Never add `confirm=true`.

## 9. Report

Give the user the command table (name, CLI form, read or write, confirm), the
final `hcpx check` score and eval table, and the files you wrote. Offer to
prepare a catalog entry with `hcpx share ./packs/<id> --json`; do not open pull
requests without asking.
