@allocator-one/harness-cli
v1.5.2
Published
CLI and MCP server for Harness, allocator.one's issue tracker
Downloads
800
Readme
@allocator-one/harness-cli
CLI and MCP server for Harness, allocator.one's issue tracker. A standalone Node client — no backend checkout, no Erlang toolchain, and no local issue state: issues live server-side, so there is nothing to commit or sync.
Install
Zero-install, always current:
npx -y @allocator-one/harness-cli list --team ALLOr globally:
npm i -g @allocator-one/harness-cli
harness readyRequires Node >= 20.
Auth
No setup needed: commands target https://harness.infra.one and start the
browser login the first time. One login covers every shell, worktree, and
agent slot on a machine — credentials are stored at
~/.config/harness/credentials.json (0600) as a single {url, token} pair,
and a token is only ever sent to the host that minted it.
harness login— browser flow (PKCE loopback);--no-browserprints the URL and prompts for a pasted token instead.harness logout— revokes the stored login server-side and deletes the file.harness whoami— shows the resolved host and credential source.
Headless contexts (agents, CI): mint an aone_… API token (admin → API
tokens) and set HARNESS_API_TOKEN. Do not set it in interactive sessions —
the browser login attributes writes to the real user.
Environment variables
| Variable | Effect |
| --- | --- |
| HARNESS_API_URL | Target host (default https://harness.infra.one). https only, plain http allowed against localhost. |
| HARNESS_API_TOKEN | Bearer token (aone_…); wins over the stored login. Never inherits the stored login's host. |
| HARNESS_TEAM | Default team key for commands that take --team. |
| HARNESS_ACTOR | Write attribution, sent only when explicitly set. |
| HARNESS_CACHE_DIR | Where show downloads attached images (default $XDG_CACHE_HOME/harness/attachments or ~/.cache/harness/attachments). Absolute paths only. |
For agents
harness mcp runs a stdio MCP server exposing every operation as a typed
tool — issue_list, issue_show, issue_create, issue_update, issue_claim,
issue_close, issue_comment, relation_add/rm, link_list/add/rm,
project_list/show/create/update, milestone_list/create,
document_list/show/create/update, label_list/create, team_list,
team_create, workflow_states, user_list, stats, upload. Read-only tools carry
readOnlyHint. Results are compact JSON of the data envelope.
Register it with Claude Code:
claude mcp add --scope user harness -- npx -y @allocator-one/harness-cli mcpExample tool call: issue_list with {"ready": true, "team": "ALL"}
returns the ready queue as JSON; issue_claim with {"id": "IO-123"}
claims atomically (a conflict means someone else holds it).
Auth resolution is identical to the CLI. Without a credential, tools return
an error telling the agent to run harness login in a terminal — the
server never opens a browser itself.
Text from a file, never from a shell argument
Every free-text option has a --<field>-file companion, and - means stdin:
harness create --title "…" --description-file ./body.md
harness update IO-123 --notes-file ./notes.md --acceptance-file ./ac.md
harness comment IO-123 --body-file ./comment.md
git log -1 --format=%B | harness close IO-123 --reason-file -create and update cover --title, --description, --design, --notes
and --acceptance (plus --append-notes on update); close takes
--reason-file, comment takes --body-file.
Use these whenever the text is not something you authored inline. Prose that
reaches a shell argument has to survive quoting, and quoting can be defeated —
a " closes the string, $(…) substitutes, and a heredoc ends at whatever
flush-left delimiter line the text happens to contain. Handing over a path
removes the question, the way git commit -F does. This matters most for
agents relaying issue text they did not write.
Passing both an inline value and its -file for the same field is a usage
error rather than a silent precedence, and only one field per invocation may
read stdin. Both are checked before anything is read, so a rejected command
never consumes your pipe. One trailing newline is dropped, like $(cat file).
Repo config
A committed .harness-cli/config.json, discovered git-style by walking up
from the working directory, gives every clone of a repo its defaults:
{ "team": "CLI" }| Field | Effect |
| --- | --- |
| team | Default team key. Precedence: --team > HARNESS_TEAM > repo config. |
| url | Target host. HARNESS_API_URL wins. Tokens stay host-bound: a stored login only serves the host that minted it, an env HARNESS_API_TOKEN refuses to pair with a repo-named host (set HARNESS_API_URL explicitly), and before the interactive auto-login opens a browser at a host named only by the repo, the CLI asks once per host (remembered in ~/.config/harness/trusted-hosts.json). |
| org | Reserved for Harness org-scoping; parsed today, url wins meanwhile. |
This is config, not state — issues stay server-side.
Development
npm ci
npm run typecheck
npm test
npm run build # bundles to dist/cli.js (tsup)
node dist/cli.js versionThe OpenAPI spec is vendored at spec/openapi.json and TypeScript types are
generated into src/generated/ (committed, so npx consumers never run
codegen):
npm run spec:refresh # fetch the spec from a live host (uses your login)
npm run codegen # regenerate src/generated/api.ts from the vendored specCI regenerates the types and fails on drift.
Release
Tag a version and push — the Release workflow builds and publishes to npm:
git tag v1.0.0 && git push origin v1.0.0Publishing requires the NPM_TOKEN actions secret (an automation token for
the allocator-one npm org). Provenance attestation is disabled: npm only
accepts it from public source repositories, and this repo is private — turn
--provenance back on in release.yml if the repo ever goes public.
