Metadata-Version: 2.5
Name: click-agentcli
Version: 0.2.0
Summary: Shared CLI conventions for agent-facing tools: exit codes, JSON output, skill installation, and the in-binary guide.
Project-URL: Homepage, https://github.com/owahltinez/click-agentcli
Author: owahltinez
License-Expression: MIT
License-File: LICENSE
Keywords: agent,cli,click,json,skill
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.13
Requires-Dist: click>=8.1
Description-Content-Type: text/markdown

# agentcli

Shared conventions for command-line tools whose primary callers are agents.
It owns no food domain: it owns predictable errors, JSON output, skills,
in-binary guides, and the candidate record used for composition.

## Install and test

```sh
uv sync --project .
uv run --project . pytest -q
```

## CLI contract

Every consuming tool uses `click`, declares `--json` per command with
`json_option`, and makes its top-level group `JsonAwareGroup`. The group scans
raw arguments so even parse failures that happen before a subcommand exists
honour a `--json` request. Importing `agentcli.exits` also changes Click's own
usage-error code from 2 to 1; consumers must not repeat that correction.

| code | meaning |
| --- | --- |
| 0 | success |
| 1 | usage error or a caller-liftable refusal |
| 2 | remote, network, or site failure after allowed retries |
| 3 | a caller-stated assertion did not hold |
| 4 | a data-quality warning escalated by `--strict` |

An exhausted request budget is code 1, because the caller can lift it. A
proportional recipe fit with no solution is code 3.

`--json` emits exactly one JSON object on stdout and nothing else. Success and
failure are symmetric:

```json
{"ok":true,"data":{}}
{"ok":false,"error":{"message":"..."}}
```

A search with no matches is successful with an empty list. Under `--json`,
errors go to stdout so a caller never has to merge streams to recover the one
promised document. Human errors go to stderr.

The stable public surface is:

- `UsageError`, `RemoteError`, `AssertionFailure`, and `StrictFailure`.
- `dumps`, `emit`, `emit_error`, `json_option`, and `limit_option`.
- `JsonAwareGroup` for every consuming tool's top-level group.
- `skill_group(name=..., package=...)` for `skill install`, `uninstall`, and
  `status`. Installation refuses an unrelated destination, recognises owned
  broken symlinks, copies by default, and supports `--link`, `--to`, and
  `--dry-run`. With no options it installs everywhere the skill is wanted
  and refreshes its own earlier copies, so plain `install` is the whole
  job; a directory holding somebody else's skill is still refused.
- `guide_command(text)` for a complete manual available without a network.
- `candidate`, `macro_options`, `matches`, `rank`, and `unverifiable` for the
  shared composition record and filters below.

## Candidate contract

Candidate sources answer the same question: filter things someone could eat by
per-serving macros, then rank them with provenance. Recipes and restaurant
meals therefore emit the same record:

```json
{
  "kind":"recipe",
  "id":"sourdough-pizza",
  "name":"Sourdough Pizza",
  "per_serving":{"kcal":384.2,"protein":31.5,"fat":12.1,"carbs":38.4},
  "complete":true,
  "detail":{}
}
```

`kind` is `recipe` or `meal`. `id` is accepted back by the emitting tool;
display-only slugs are not identifiers. Source-specific fields live under
`detail`, which shared code never reads.

Sources accept `macro_options` (`--max-kcal`, `--min-protein`) and use `rank`.
The rank key is unrounded protein per 100 kcal, then absolute protein, then
name. `--max-kcal 0` is valid because zero-calorie records exist.

`per_serving` contains only macros actually known by the source. Missing is
never filled with zero. `complete` exposes whether the full shape is present;
a candidate missing a requested filter macro is excluded and returned in the
source's `unverifiable` or equivalent bucket. Every source emits that bucket,
even when its loader makes it structurally empty.

This contract is the reason the tools can be independent packages: an
orchestrator can merge and rank results without knowing which source answered.
