npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@allocator-one/operator

v1.30.1

Published

Operator from the terminal and any agent, with Harness issue tracking: the harness CLI as the operator command

Readme

@allocator-one/operator

The operator CLI and MCP server: the CLI for Harness, allocator.one's issue tracker, under the command operator, for driving Operator from the terminal and any agent. It is the same build and version as @allocator-one/harness-cli (the harness command) and shares its login, so every harness <command> below works as operator <command>.

Install

npm i -g @allocator-one/operator
operator --help

Or run it without installing:

npx -y @allocator-one/operator --help

As an MCP server for Claude Code (it reports its name as operator):

claude mcp add --scope user operator -- npx -y @allocator-one/operator mcp

operator --help lists the Operator commands first, then the issue-tracker commands under "Issues:". @allocator-one/harness-cli installs the harness command; the two packages can be installed side by side.

Requires Node >= 20.

Sign in

You don't need to configure anything. Commands target https://harness.infra.one, and the first command you run in a terminal opens a browser login and then carries on. One login covers every shell, worktree, and agent slot on the machine.

  • harness login: browser sign-in (PKCE, loopback callback on 127.0.0.1). --no-browser prints the URL and asks you to paste the token instead. --url <host> signs in to another host, and --timeout <ms> changes how long it waits for the callback (default 3 minutes). Login tokens last about 30 days.
  • harness logout: revokes the stored login on the server and deletes it locally.
  • harness whoami: shows the resolved host and where the credential came from. For a login it also shows the kind of credential, the user, the organization and when it expires, as GET /api/v1/session describes it. On a server without that route, or with an aone_… API token, those fields are null under --json. With an outside expert's pxc_… credential it names the expert's role and plan instead (see Outside experts).

A login gets one of two kinds of credential. The server picks the kind from your standing in the organization you choose:

  • harness-cli is the full credential that Harness staff get.
  • operator-cli is the Operator-only credential that any other admin or member Operator serves gets. It reaches the Operator commands, whoami and logout, and the server refuses everything else (issues, plans, gates) with 403.

login says after signing in which kind you got (kind under --json). If the new credential fails verification, nothing is stored. A credential the browser login just minted is then revoked, in one best-effort attempt, so a 30-day credential isn't left behind. A token you pasted is never revoked, whatever the failure, since you may be using it elsewhere. Holders keep their kind even after gaining staff standing: they log in again to get the full credential.

operator login, operator logout and operator whoami are the same commands and use the same stored login.

The automatic login only runs when stdout is a terminal and CI is unset or empty. Elsewhere (agents, CI, pipes), a command that has no credential prints how to authenticate and exits with code 2. For those headless contexts, mint an aone_… API token (admin → API tokens) with the harness:read scope, adding harness:write if the workflow changes anything (claim, create, update, comment, close), and export it as HARNESS_API_TOKEN. An outside expert uses their pxc_… credential the same way (see Outside experts).

Never set HARNESS_API_TOKEN or HARNESS_ACTOR in an interactive session. The browser login attributes writes to the real user, and overriding it falsifies the audit trail. Both variables are only for headless CI and agent slots.

Every identifier includes its team key, as in CLI-12 or ALL-4901. Older issues keep legacy prefixes: IO- issues were created before team-prefixed numbering, ALL- numbers up to the import ceiling came from Linear, and BD- from beads.

Daily workflow

harness ready --claim                           # claim the team's next ready, unassigned issue
harness show CLI-12                             # read it, including attached screenshots
# … do the work …
harness close CLI-12 --reason "Implemented in 1a2b3c4"

Queue. harness ready lists open, unblocked issues, highest priority first. Blocked issues, hold-labeled issues, and parents with sub-issues that are not completed are excluded. Only a completed blocker resolves: a cancelled or archived one still blocks until it is completed or the dependency is removed or re-pointed. An issue that is waiting on a wait condition (see Plans) is not ready either. harness blocked lists issues that are waiting on a blocker. Both commands take --team. ready marks agent steps, the plan steps Operator runs, with [agent] before the title and a legend under the table, and --json gives every issue an agent_step flag. Only the plan driver claims an agent step. The issue payload carries no executor, so the CLI goes by the team: agent steps are filed in the organization's Operator team (named Operator, keyed OPERATOR, or OPERATOR2 and so on). That is a heuristic. A step handed back to a person keeps its team and stays marked, and the claim itself is the authority.

Claim the next issue. harness ready --claim is a single request: the server picks the team's most important ready issue that has no assignee (urgent, high, medium, low, then none; oldest first, then by id), assigns you, and moves it to the team's first started state, all in one transaction. Concurrent callers get different issues, and the CLI does no paging, sorting, or retrying of its own.

  • It needs a team: --team, then HARNESS_TEAM, then the repo config. With none of them it is a usage error, and the claim is never sent.

  • --all, --limit, and --page are refused with --claim, because the server already ranks the whole ready set.

  • An empty queue is a normal outcome: it prints "Nothing to claim: team CLI has no unassigned ready issues." (null with --json) and exits 0.

  • A rate limit, server error, or unreadable response exits 1, and an unreachable API exits 3. None of these is reported as an empty queue.

    The CLI never retries the claim. When an unreadable response or a lost connection leaves it unknown whether the claim landed, the error says to check harness list --team <key> --assignee me --status in_progress before claiming again.

Work routed by label. When a team mixes executors (exec/codex, exec/claude-code, exec/michael), each agent filters its own queue and claims only its own steps:

harness ready --team FR --label exec/codex                  # only Codex steps
harness ready --team FR --label exec/codex --claim          # claim the next one
harness ready --team FR --exclude-label exec/codex          # everything else
harness blocked --team FR --label exec/codex                # Codex steps waiting on a blocker
  • --label repeats, and an issue must carry every label given. --exclude-label repeats, and drops issues carrying any of them. Both filter on the server.
  • --claim sends the same filter with the claim, so the server never hands you an issue outside it, even when several agents claim at once. An empty filtered queue prints "Nothing to claim: team FR has no unassigned ready issues labelled exec/codex." and exits 0.
  • Several labels or an exclusion need a server that supports label lists. The CLI checks with one read first. Against an older server it refuses rather than show an unfiltered queue or claim an issue outside the filter.
  • MCP: issue_list takes labels and exclude_labels, and issue_claim_next takes team, label and exclude_label with the same meaning.

Read. harness show <id> prints the fields, relations, children, and comments. It also downloads the images attached to the issue's description, design, acceptance criteria, notes, and comments (up to 20, cached locally) and prints their paths. Screenshots are often the reproduction, so read them. --no-images skips the downloads. If a download fails, a stderr notice says so and the command still succeeds. harness comments <id> prints the full, untruncated comment thread with comment ids, oldest first. It requests up to 200 comments per page and follows the oldest comment id until the server reports has_more=false. --json and the MCP comment_list tool return the same complete array of full comment objects. Missing pagination metadata, repeated comment ids, or an older-page failure produce an error without presenting a partial thread as complete. The Blocked: line names a cancelled or archived blocker as such (yes, by CLI-3 (cancelled), CLI-4 (archived), CLI-5). When the issue has wait conditions, Waiting: says how many still hold it, and a Wait conditions: section lists each one ([ ] gate "Finance sign-off" · <id>, [ ] gate "Compliance sign-off" [compliance] owned by Compliance officer (role:compliance_officer) · <id>, [x] not before … (satisfied …), system event) with the key and id harness gate satisfy takes. An issue that belongs to a plan gets a Plan: line naming the plan by its root identifier, which the plan commands take, then its status, the revision it runs, and whether that revision lists the issue (Plan: FR-1 · released · revision 8 · not in revision, with Operator step for a step Operator runs). --json carries the same as plan ({id, root, status, held, revision, in_revision, executor}, or null). harness activity <id> prints the issue's activity timeline: comments, creation, state and assignee changes, wait-condition events (Ada satisfied the gate "Finance sign-off"), and field changes (changed priority from High to Urgent, renamed the issue from “…” to “…”, edited the description, added label regression and removed label idea, set the parent to CLI-3, archived the issue). A description edit records only that it happened, never the text. When a write was made under an agent or CLI label, the person comes first and the label follows in quotes: Ada via “Claude Code” moved the issue from Backlog to In progress. A field this CLI does not know yet prints as changed the <field> field; --json passes every entry through as the server sent it. A server that does not record field changes yet refuses to serve them; the timeline then comes without them, and a stderr notice says so. It shows the newest 20 entries (--limit accepts 1–100), and --all reads further back.

Create. Creating an issue before you write code keeps the work tracked:

harness create --title "Fix login redirect" --type bug --priority high \
  --description "…" --parent CLI-3 --assignee me --due-date 2026-11-01 \
  --label regression --idempotency-key fix-login-redirect-1
  • --type task|bug|feature|epic is applied as a type/<type> label.
  • --priority urgent|high|medium|low|none takes names only. Bare numbers are refused because bd and Harness number priorities differently.
  • --team <key> falls back to HARNESS_TEAM and then to the repo config. With none of these set, the command is a usage error.
  • --label (repeatable) looks for a matching label in the target team first and then at workspace scope. Missing labels are created at workspace scope (which requires Harness administrator standing) before the issue is filed, and the issue is created in a single request with all its labels.
  • --idempotency-key <key> (1–200 printable ASCII characters, no leading or trailing space) makes a retry safe. If a create times out, run it again with the same key to get the original issue back instead of a duplicate. The CLI never retries a write on its own.
  • --design, --notes, and --acceptance set the remaining text fields.
  • --project <name> files the issue in a project of the organization, and --milestone <name> in one of that project's milestones. Names ignore case; a name two projects (or milestones) share is refused, so pass the id. A milestone needs the project named with it.

Update. harness update <id> with:

  • --claim: atomic. It assigns you and moves the issue to the team's first started state. A conflict (exit 1, HTTP 409) names its reason: someone else holds the issue, or it is not claimable yet. Some reasons come with a next step. agent_step: a person can't claim an Operator step; to do it yourself, set its executor to none with harness plan edit <plan> and release the plan again. plan_not_released, not_in_plan_revision and edited_since_release: when the server names the one plan governing the issue as the one to release, the error ends with the command (run harness plan release FR-1). When several plans govern it, the server names none as the one to release, and the error names the nearest to look at instead (harness plan show FR-1). A completed plan, or a server that does not name the plan, gets no such hint. waiting_on_condition: harness show lists what it waits on; tick a gate with harness gate satisfy. It cannot be combined with --assignee.
  • --state <name-or-type>: moves the issue to the workflow state whose name or type matches.
  • --title, --description, --design, --acceptance, --priority, --parent, --assignee ("" unassigns), --due-date ("" clears).
  • --project <name> moves the issue to a project ("" takes it out of its project). A new project drops the old project's milestone unless --milestone names one of the new project's. --milestone <name> alone is named within the issue's current project ("" clears it).
  • --notes replaces the notes and --append-notes appends to them. Pass only one of the two.
  • --add-label / --remove-label (repeatable, name or id) add or remove individual labels without replacing the rest. You can only add labels that belong to the issue's team or the workspace, and a missing name is created at workspace scope, as with create. If two attached labels match a name, remove by id.

Dependencies. harness dep add <issue> <blocker>: the second argument blocks the first. harness dep rm <issue> <blocker> removes the dependency. For other relation types, see relation under Other commands.

Close. harness close <id> [<id>…] closes one or more issues as completed. Use --cancel to close them as canceled, or --duplicate-of <id> to close a single issue as a duplicate of the canonical one. The duplicate-of form records a duplicate relation and cannot be combined with --cancel. --reason (or --reason-file) is posted as a comment on the issue, so put the whole summary there instead of commenting separately first. Closing an issue a newer one supersedes cancels it and moves its blocking edges to the successor; with no reason the server posts "Superseded by ", and the output names the successor. Record the replacement as a relation first (harness relation add <new> --type supersedes <old>), not only in a comment: the relation is what show follows to the live issue. The output lists the issues each closure unblocked (Closed CLI-12 — unblocked: CLI-13, CLI-14); act on them instead of leaving them in the queue. Only completing releases dependents: a canceled issue still blocks them. A duplicate close moves its blocking edges to the canonical issue, and any edge that cannot move stays on the duplicate. When the closed issue still blocks anything, an indented line names those dependents (Still blocking: CLI-13 (a cancelled issue still blocks its dependents)) so you can remove or re-point those dependencies. With several ids, each one is closed on its own. A failure prints FAILED <id>: …, and the command exits 1 after trying the rest. harness archive <id>… archives issues, --restore brings them back, and harness list --scope archived lists them.

Comment. harness comment <id> "text" [--mention <name>]… posts a comment. The server resolves each mention and notifies that person. harness comment edit <id> <comment-id> "new text" and harness comment delete <id> <comment-id> (alias rm) work on your own comments. Comment ids are UUIDs from harness comments.

Find. There are two search commands:

  • harness search <question…> searches by meaning. It returns a ranked list with an exact identifier first, followed by results ordered by relevance across a lexical and a semantic signal, so it can find issues that share none of your words. Write the question in plain language instead of keywords. Results default to 10 and are capped at 20, with no pages (--page/--all are accepted but ignored). Archived issues are not searched. If the semantic side is unavailable, stderr says semantic search unavailable — showing lexical matches only.
  • harness list --search <text> is the paginated lexical filter, which matches title or issue number.

harness list filters: --status open|in_progress|closed, --assignee <me|id|email|name|""> ("" means unassigned), --created-by <me|id>, --involved me, --priority <name>, --label <name> or --type <type> (only one of the two), --team <key>, --parent <id> (children of an epic), --project <name> and --milestone <name> (a name within --project, or an id alone), and --scope all|archived. harness ready and harness blocked take --project and --milestone too, though not with ready --claim.

Overview. harness stats prints per-team and total counts. harness prime prints those counts, the organization's ten most important ready issues across every team (urgent first and priority none last, ranked the way harness ready --claim ranks, though that claims only unassigned issues on one team), and a short workflow reminder. It is meant for the start of an agent session. Against a server older than this read it shows the ten newest ready issues instead.

Text from a file

When text comes from somewhere other than your own typing, such as issue content, a commit message, or model output, pass a file instead of a shell argument. Quoting can be defeated: a " ends the string, $(…) substitutes, and a heredoc ends at any flush-left delimiter line the text happens to contain.

harness create --title "…" --description-file ./body.md
harness update CLI-12 --notes-file ./notes.md --acceptance-file ./ac.md
harness comment CLI-12 --body-file ./comment.md
git log -1 --format=%B | harness close CLI-12 --reason-file -

| Command | File options | | --- | --- | | create | --title-file, --description-file, --design-file, --notes-file, --acceptance-file | | update | the same five, plus --append-notes-file | | close | --reason-file | | comment, comment edit | --body-file |

Other commands (project, milestone, doc, link, and so on) take inline values only. plan create and plan edit read a whole JSON or YAML document with --file (see Plans), under the same stdin and UTF-8 rules, up to 8 MB (the API's request limit). - reads from stdin, and only one option per invocation can do that. stdin must not be a terminal. Passing both the inline and the file form of a field is a usage error, and so is a file that produces an empty title, reason, or comment. Conflicting options are caught before anything is read, so a rejected command never consumes your pipe. A file must be valid UTF-8 and at most 1 MB, and one trailing newline is dropped, as with $(cat file).

Plans

A plan is a root issue and its steps, created in one request: their parents, blocked_by dependencies, executors, teams and wait conditions. Either the whole plan is created or nothing is. Plans start held, so nothing runs until a person releases one.

harness plan create --file plan.yaml --team IO --idempotency-key q3-report
harness plan list --team IO                   # the team's plans, by root
harness plan show IO-120                      # by its root issue, or its id
harness plan release IO-120 --collaborator [email protected]
harness gate satisfy IO-121 "Finance sign-off"
harness gate satisfy IO-121 compliance        # by the gate's key
harness gate decline IO-121 compliance --reason "The NAV doesn't reconcile"

Finding a plan. Every plan command takes the plan's id or its root issue's identifier (FR-1, in any casing). A step's identifier does not open its plan: harness show <issue> names the plan an issue belongs to by its root. harness plan list lists a team's plans, newest first, with their root, status, whether they are held, the revision they run (- before the first release) and their title:

ROOT    STATUS    HELD  REVISION  TITLE
FR-1    released  no    8         Format radar
FR-200  held      yes   -         Next quarter

It takes --team (or HARNESS_TEAM or the repo config), --status (held, released, running, completed or needs_attention) and --limit (default 50, at most 100). There are no pages: when a list comes back full, a stderr notice says older plans may exist. --json is the server's list unchanged, each plan with its id, root, title, status, held, revision, released_at and inserted_at.

The plan file is the API's request body, as JSON (.json) or YAML (anything else, and stdin with --file -). YAML is read as YAML 1.2 and held to what JSON can carry: dates stay strings, and a duplicate key, an explicit tag such as !!timestamp, or .inf/.nan is refused. The root's fields sit at the top level, and each step has a key that other steps refer to:

title: Ship the Q3 LP report
spend_budget_cents: 5000        # needed to release Operator steps
steps:
  - key: gather
    title: Gather the fund data
    executor: operator          # Operator runs it; the description is its instruction
    description: Pull the Q3 NAVs for every fund in the portfolio
  - key: review
    title: Review the numbers
    team: IO                    # a person's step; defaults to --team
    blocked_by: [gather]
    conditions:
      - kind: gate
        label: Finance sign-off
      - gate: compliance          # a gate with this key; the label defaults to it
        label: Compliance sign-off
        owner: role:compliance_officer
  - key: send
    title: Send the report
    blocked_by: [review]
    conditions:
      - kind: not_before
        not_before: "2026-10-15T09:00:00Z"
  • A step takes the issue fields (title, description, priority as a number from 0 for none to 4 for low, assignee, label_ids, project, milestone, …) plus key, parent (the key of the step that contains it), blocked_by (keys), executor (none, the default, or operator), team, and conditions. Operator steps are filed in the Operator team. Only steps without sub-steps carry an executor, edges, or conditions.
  • project and milestone take a name (ignoring case) or an id. The root's are its steps' too: a step that leaves project out takes the root's project, and the root's milestone unless it names its own. project: null files a step in no project.
  • Wait conditions: not_before (a UTC instant), gate (a label a person ticks), and event (event, subject_id, and for a stage change target_id). The allowed events are deal_stage_changed, opportunity_stage_changed, form_submitted, and investment_executed. Every condition must hold before the step is ready.
  • A gate can carry a key, a lowercase slug (a-z, 0-9 and -) unique among the step's gates, which gate satisfy accepts. gate: <key> is shorthand for kind: gate with that key, labelled with the key unless label is given. A gate can also have an owner: role:<role> (one of the fund-team roles gp, compliance_officer, valuer, accountant, auditor and counsel), user:<id or email> (a member who can use Operator), or expert:<email> (an outside expert, never a member's email). Only the owner can tick an owned gate. You can't name yourself as an owner, and that includes a role you hold. owner: null or no owner leaves the gate unowned.
  • A step can carry when, the predicates it runs on, all of which must hold: a gate decision on a step ({gate: <gate key>, step: <step key>, decision: approved | declined}) or a value in a step's output ({$from: <step key>, path: <JSON Pointer>} with one of equals, in or exists). When one doesn't hold, the step is skipped rather than blocked. optional: true lets the steps it blocks run when it is skipped, cancelled or fails. Both travel as written; the server checks them.
  • A plan has at most the organization's max plan steps, its root included: 1,000 unless a Harness admin changed it in Harness settings → Plans, and never more than 2,000. Separately, it has at most 100 Operator steps, the fixed limit of the delegated grant Operator runs them under.
  • --team (or HARNESS_TEAM or the repo config) is the root's team and the default team for people's steps. --idempotency-key works as it does for create: if a create times out, run it again with the same key to get the same plan back.

Validation. The server checks the whole graph and an invalid plan creates nothing. The command exits 1, and stderr lists every violation by the step key it is about:

harness: The plan is not valid; nothing was changed (HTTP 422)
  step "review" (steps[1]): parent names no step of this plan, or no issue in this organization (parent: nope) [unknown_parent]
  plan: A plan with Operator steps needs a positive spend budget [spend_budget_required]

A plan over a step cap gets too_many_steps, once for each cap it is over, and the line says which cap it is and whether anyone can raise it (on create the root has no identifier yet, so the line starts plan:):

  FR-1: the plan has 1203 steps, above this organization's max plan steps (1000): a Harness admin can raise "Max plan steps" in Harness settings → Plans (max_plan_steps, up to 2000) [too_many_steps]
  FR-1: the plan has 103 Operator steps, above the fixed limit of 100: Operator's delegated grant caps a plan's agent steps, and no setting raises it — hand some to people (executor none) or split the plan [too_many_steps]

The same applies to plan edit and plan release, which check the caps again, and to the MCP tools' error text.

A gate's owner is checked the same way. The owner violations are invalid_owner, unknown_role, unknown_owner, owner_is_member, owner_is_actor (you named yourself), owner_not_allowed (an owner on a condition that is not a gate), expert_owner_requires_plan, and duplicate_gate_key. A gate: that conflicts with the condition's kind or key is invalid_condition.

Show. harness plan show <plan> prints the plan's status, budgets, revision, and spend estimate, and for a plan a schedule or trigger drafted, which one and its occurrence (Started by: schedule Weekly review (2026-11-09T08:00:00Z), or trigger <name> (event:<id>); --json carries it as started_by, null for a plan a person drafted), then every step in tree order (indented by depth) with its key, executor, state, and status. The status is ready, held (an Operator step changed since the release, which waits for the next one; a person's changed step stays ready), not in revision (added after it), waiting (on a wait condition), container, done, cancelled, skipped (closed by the plan without its work), archived, or -. A fan-out's copies sit under their fan-out step, each title prefixed with its item's place (Item 3 · Notify the LP for the item at index 2; --json carries it as copy, {"fanout": "<fan-out step>", "index": 2}, null for any other step) — never the item itself or its digest:

    IO-124  -       action    Todo     ready      Item 3 · Notify the LP

The edges come after the steps, then a Branching: section for the steps that have a when, are optional or were skipped, and then each step's wait conditions:

Branching:
  IO-121  skipped: its gate was declined
  IO-122  runs when gate compliance on step review is declined
  IO-123  runs when fetch output /status equals "ok" · optional

A skipped step says why: its gate was declined, its when didn't hold, a step it waits on or the container it sits in was skipped, or it was optional and was cancelled or failed. Each gate shows its key and owner. A Waiting for: section then lists the open gates each step waits for, with the gate's owner and the people who can tick it. When nobody can, it says nobody can tick:

Waiting for:
  IO-121  "Compliance sign-off" [compliance] · Compliance officer (role:compliance_officer) · can tick: Ada Lovelace, Grace Hopper (expert, Acme LLP)
  IO-122  "Legal review" [legal] · Bob Smith (user:7d3c2a1e-5b4f-4c8d-9e0a-1f2b3c4d5e6f) · nobody can tick
  IO-123  "Smoke test" · no owner, anyone with standing can tick

An owner shows as its name followed by how the server names it: a role (Compliance officer (role:compliance_officer)), a member by user id (Bob Smith (user:<uuid>), even when the plan document named them by email), or an outside expert by email ([email protected] (expert)).

An Approvals: section lists each action step's operation and approval: none (a read), release (it runs on the release), confirmation or callback (it waits on a person's approval in the app, with its gate's state and condition id), or why it can't run (operation_unknown, operation_not_supported, operation_not_previewable):

Approvals:
  FR-2  list_lps · approval none
  FR-3  send_capital_call · approval confirmation · gate open for approval · 6f1e9c2a-…

A Runs: section lists each step that has run in the plan, with its latest run's status, attempt, retries and continues used, outcome code, and Operator job id (for harness job <id>):

Runs:
  IO-121  failed · attempt 2 · 1 retry · outcome reached_max_iterations · job 0b9f3c55-6d3e-4a43-9a43-35ab3f4d1c11
  IO-122  succeeded · attempt 1

--json prints the plan as the server sends it, including each step's waiting_for (condition_id, key, label, owner, and holders: [] when nobody can tick, null for an unowned gate or when the holders are not known), execution (its latest run in the plan, shaped as harness show --json gives it, or null when it hasn't run) and an action step's approval (mode, and gate: its confirmation gate's condition_id and state).

Edit. harness plan edit <plan> --file edits.yaml changes step executors and keys, naming each step by its step key or issue identifier, and the budgets (retry_budget, spend_budget_cents):

steps:
  - step: gather
    executor: none              # hand the step to a person

Editing a released Operator step, or handing one to a person, holds it until the plan is released again. A person's step stays workable, and the next release takes the change in; until then the plan lists every changed step under Changed since the release:. A budget change needs the plan held first. Descriptions, parents, and edges are edited with the issue commands (update, dep). Conditions are set when the plan is created. Editing them later goes through the API's issue condition endpoints, which have no command yet; gate satisfy ticks a gate.

Release and hold. harness plan release <plan> releases the plan as you: it freezes the next revision, which takes in the steps added or edited since the last one, and lets ready steps run. --collaborator <id-or-email> (repeatable) adds people to the plan room where its Operator steps run, and --retry-budget and --spend-budget-cents set the budgets. Each Operator step runs as a job (harness jobs names its plan step), and the changes a job proposes need a person's approval in the app, at the job's approval link. The CLI never approves. harness plan hold <plan> pauses every step until the plan is released again.

Templates. harness plan save <plan> --as <name> saves a held plan's tree as the next version of the named template: each step's key, fields, parent, blocked_by, executor, campaign fields, wait conditions and labels, but no budgets, assignees, projects or due dates. Every step needs a key, and a template holds no plan step. The name is a lowercase slug (a-z, 0-9, - and _) that no built-in uses. Saving a name again adds a version; a saved version never changes. A plan step runs a template with plan_template_ref: <name> or <name>@<version>.

harness plan templates lists the built-ins first (read-only, with no step counts), then the latest version of each saved template by name, a page at a time: --limit (default 50, at most 100), and --after <name> for the next page, which a full page names on stderr:

NAME              VERSION  BUILT-IN  STEPS  OPERATOR  SAVED
capital-call      1        yes       -      -         2026-10-01
quarterly-letter  3        no        4      2         2026-10-30

Schedules and triggers. A plan schedule drafts a held plan from a pinned template on a cadence; in release mode it also releases it, within its release terms. A plan trigger drafts a held plan each time an event fires and never releases. Both run as their owner, the person who created them, and pin the template's version when created or edited (<name> pins its latest). The commands are the same for both:

harness plan schedule create --file schedule.yaml --name "Weekly review" --template weekly-review
harness plan schedule list                    # name, template, mode, state, next and last run, id
harness plan schedule show <id>               # the schedule and its latest 20 runs
harness plan schedule edit <id> --timezone UTC
harness plan schedule pause <id>              # resume <id> starts it again
harness plan schedule adopt <id>              # Harness staff: make it yours
harness plan schedule runs <id> --limit 200
harness plan schedule delete <id> --yes
harness plan trigger create --name "Onboard on submission" --template investor_onboarding --event form_submitted

create and edit read the API's request body from --file, as JSON or YAML under the plan file's rules, and take flags for its scalar fields: --name, --team (team_key), --template, and --mode, --timezone and --business-day for a schedule, or --event, --target (target_id) and --max-runs-per-day for a trigger. A field given both in the file and as a flag is refused. A schedule's cadence, arguments and release_terms, and a trigger's arguments, are structured, so they go in the file:

mode: release
timezone: Europe/Vienna
business_day: following          # a weekend occurrence runs on Monday
cadence: { kind: weekly, days: [1], hour: 9, minute: 0 }
arguments: { quarter: Q4 }
release_terms: { spend_budget_cents: 5000 }

A cadence is daily, weekdays, weekly with days (ISO weekdays, 1 is Monday), monthly with day (1 to 28, or last), or quarterly with month (1 to 3, of the quarter) and day, at hour and minute local time. A trigger's argument can bind an event field: {"$event": "subject_id"}, "target_id" for an event with a target, or "occurred_at". create files the schedule or trigger in --team, else HARNESS_TEAM or the repo config, when the body names no team_key. --mode, --business-day, --event, --target and --max-runs-per-day values the API does not take exit 2 before anything is sent; the server checks the rest (the template, cadence, time zone, bindings and the plan the template would draft) and names every refusal.

list shows each one's state: active, paused, or paused (<reason>) when the server paused it (owner_ineligible, repeated_failures, or a schedule's invalid_timezone):

NAME           TEMPLATE         MODE   STATE                       NEXT RUN              LAST RUN              ID
Weekly review  weekly-review@3  draft  active                      2026-11-09T08:00:00Z  2026-11-02T08:00:00Z  5c4ed01e-…
Month end      month-end@1      draft  paused (repeated_failures)  -                     -                     8d2f…

A trigger's list shows its event and the most runs it makes in a UTC day instead. show adds the owner, cadence or event, arguments and failures, then the latest 20 runs: each run's occurrence (a schedule's nominal time, or event:<id>), status (pending, drafted, released, failed or skipped), when it ran, the plan it drafted (harness plan show takes it) and why it failed or was skipped (missed, rate_limited, template_changed, …). runs lists up to --limit runs (default 50, at most 200); there are no pages, and a full list says on stderr that older runs may exist.

edit is its owner's alone, and confirms it again. pause, resume and delete are its owner's or Harness staff's; resume checks it again as its owner and is refused (owner_ineligible) while the owner may no longer start plans. adopt lets Harness staff make one theirs: it runs as them from then on, and a paused one stays paused until they resume it. delete needs --yes, and the runs and plans it drafted stay. --json prints the server's data unchanged, {"deleted": "<id>"} for a delete.

Gates. harness gate satisfy <issue> <gate> ticks a gate and harness gate unsatisfy <issue> <gate> unticks it. <gate> is the gate's key, its label, or its condition id, all shown by harness show. A condition UUID writes directly without listing conditions, so a non-staff gate owner can reach the server's write authorization. A UUID always addresses that condition, even if another gate uses it as its key or label. Keys and labels require the staff-only lookup; a denied lookup explains how to use a condition id instead. When a name matches multiple gates, the command lists their ids instead of choosing. Only when nothing matches exactly does a label or key match ignoring case, and that too is refused when more than one gate matches. Ticking a gate requires staff standing in the organization or a place in its plan's room. The server refuses an Operator-only login (operator-cli) here, even for a person in the plan's room: it reaches only the Operator commands, whoami and logout. Only the owner can tick an owned gate, and only its owner or Harness staff can untick it. Anyone else gets an error that names the gate's owner as of the refusal, which the server sends with it, and exits 1:

harness: Cannot satisfy gate "Compliance sign-off" on IO-121: only its owner, Compliance officer (role:compliance_officer), can tick it (HTTP 403)

A gate with no owner by then gets the same error without a name. Against a server that does not send the owner, a key or label write reads the conditions again to find it. A direct condition-id write never lists conditions, including on errors, and leaves the owner unnamed if the refusal does not name one. Direct writes also make no claim about other conditions still holding the issue.

Gate decisions. harness gate decline <issue> <gate> --reason <text> declines a gate on a step of a live plan, and the step closes as declined. Steps whose when names the gate's decision branch on it, and the steps the declined one blocks are skipped in turn unless it is optional. --reason is required, up to 2,000 characters, and a decline without one exits 2 before anything is sent. harness gate approve <issue> <gate> ticks the gate as gate satisfy does, and takes no reason: the server keeps reasons only for declines. Both find the gate as gate satisfy does (key, label or condition id) and need the same standing; an owned gate's owner alone decides it. A decision is final. A decline can't be undone, and an approval can't be once a step's when has read it. The server refuses another decision, or an untick, with a 409 (already_decided, decision_final or decision_used), and the error says where to see the decision:

harness: The gate has already been decided — a gate is decided once; harness conditions IO-121 shows its decision (HTTP 409)

A gate that is not on a live plan's step can't be declined (422 not_a_plan_step).

Confirmation gates. A plan action step whose write waits on a person's approval carries a confirmation gate (kind: confirmation). Approving it runs the step's Infra One operation as the plan's releaser, on the exact preview the person saw. The server lets nobody approve or decline one with a credential, so an agent working on someone's login can't approve its own writes: a person signed in to the app does it there. The gate commands still find one, by its condition id or by its operation id as its name (harness gate satisfy FR-3 send_capital_call), and send their usual request, never a preview. The server refuses it with a 409 (confirmation_requires_session), which changes nothing, and the error prints the gate's operation, approval mode and state, the preview it would approve (its sequence number, summary and target), and the link to approve it in the app. It exits 1:

harness: Cannot satisfy the confirmation gate for send_capital_call on FR-3: a plan step's confirmation gate is approved or declined only in the app, by a person signed in, never with a credential (HTTP 409)
  operation: send_capital_call · approval confirmation · open for approval
  preview 3 (2026-10-31T09:00:00Z): Send the Q3 capital call to 12 LPs
  target: capital_call cc-42
  approve or decline it in the app: https://harness.infra.one/acme/issues/FR-3

The server sends the preview only to the gate's holders and the plan's releaser. Anyone else, and everyone before the step has been previewed (awaiting its preview), gets the line that says so instead. gate_set and gate_decide over MCP return the same error and take no preview.

Conditions. harness conditions <issue> lists an issue's wait conditions, oldest first, one per line as show lists them: each one's box, what it waits for (a gate's label, key and owner), when and by whom it was satisfied, and its id. A decided gate says how: [x] … (approved <when> by <who>), or [-] … (declined <when> by <who>: <reason>). A confirmation gate is named by its operation, with its approval mode, its state (awaiting its preview, open for approval, approved, waiting for its run, approved and run, declined or its preview was refused) and, when the server sends one, its preview's summary:

[ ] confirmation gate send_capital_call owned by GP (role:gp) · approval confirmation · open for approval · preview 3: Send the Q3 capital call to 12 LPs (capital_call cc-42) · <id>

harness show and harness plan show list conditions the same way. --json is the list as the server sends it, with each gate's decision and decision_reason, and a confirmation gate's confirmation (operation, mode, state and preview, which is null for anyone who may not approve it).

A server without the plan API answers the plan commands with a 404, and the error says that the server may predate plans. A root identifier that opens no plan is the same 404 as an unknown id, and the error says how to find the plan an issue belongs to.

Outside experts

An outside expert invited to a plan can hold a pxc_… credential. The expert mints it in their own expert view, with a fresh code from their authenticator app, and it lasts at most 30 days. It reaches only:

  • their assignments (GET /api/v1/expert/assignments);
  • on a step assigned to them, the gates they own: listed, ticked and unticked.

To the CLI it is an ordinary bearer credential. In a terminal, paste it into harness login --no-browser, which verifies it against the assignments and stores it like any login. For an agent or a script, export it as HARNESS_API_TOKEN. Writes are attributed to the expert's own account either way.

harness login --no-browser                # paste the pxc_ credential
harness whoami                            # the expert's role and plan
harness assignments                       # their steps, and the gates they own on each
harness conditions IO-130                 # their gates on one step
harness gate satisfy IO-130 valuation     # tick one, by key, label or id

harness whoami says the credential is an outside expert's and names the expert's role, the plan and how many steps are assigned to them:

Host:        https://harness.infra.one (via stored login)
Credential:  stored login, an outside expert's pxc_ credential
Expert:      Valuer on plan 0b6f5e9a-6c1e-4d3f-9a39-2f1f8f7e4c11 (released) · 1 assigned step
Reaches:     its own assignments and the gates it owns on them (assignments, conditions, gate)
Verified:    ok (server API 1.4.693)

whoami --json always carries expert: null for any other credential, and for a pxc_… one {id, role, plan: {id, status} | null, assigned_steps}, beside the usual url, source, verified, server_version and host_source.

harness assignments lists each step assigned to the expert that they still hold, in identifier order: its state, due date, how many open issues block it, the gates on it they own, and its description (left out for a step Operator runs). The expert view never names a member: a gate shows its owner as a role, or you for the expert's own gate, and who ticked it as you, the role it was ticked as, an outside expert or a member. --json is the server's data unchanged: expert ({id, role}), plan ({id, status} or null) and assignments, each with identifier, title, description, state ({name, type}), due_date, open_blockers and gates. Any other credential gets a 403 (expert_credential_required).

harness conditions <issue>, harness gate satisfy|unsatisfy and harness gate approve|decline read, tick and decide only the expert's own gates. After a tick the CLI counts only the expert's own gates still open, because the step's other conditions are out of the expert's view. A step that isn't assigned to the expert answers 404, which the error says.

Every other command that reads or changes Harness or Operator data is refused with a 403 expert_bound_credential, reads and writes alike. (whoami, version, login and logout keep working: they use only the routes the credential reaches.) The CLI says what the credential reaches and exits 1:

harness: This expert credential may only read its own assignments and tick its own gates — an outside expert's pxc_ credential only lists its own assignments and reads, ticks and decides the gates it owns on them: assignments, conditions <issue> and gate satisfy|unsatisfy|approve|decline (assignment_list, condition_list, gate_set and gate_decide over MCP) (HTTP 403)

The platform reads the credential, the expert's seat and the account again on every request. An expired or revoked credential, a revoked or ended seat, a suspended account or a changed authenticator answers 401 at once. With a pxc_… credential the error lists those causes, and the command exits 1. If the seat is still open, mint a new credential in the expert view. harness logout forgets a stored pxc_… credential without revoking it, because the session route refuses it; revoke it in the expert view to end it at once. Storing a pxc_… credential replaces (and revokes) a stored member login, so log in again to use the other commands.

Operator jobs

jobs, job and watch read the Operator jobs you see in the Chat Mode workbench, in the organization your credential belongs to. They need the operator:read scope (a browser login carries it) and an admin or member role there.

harness jobs --status waiting --mine
harness job <job-id>
harness watch <job-id>

Everything is by reference: states, codes, counts, ids and workbench links. None of them shows a job's objective text, because the API never returns it: it can quote what Operator read. Each job shows its objective_kind and the workbench link where its objective can be read instead. Approval happens in the app. No command approves anything; they print the approval link.

  • harness jobs lists jobs newest first, with their state, what they wait on, kind, owner and plan step, followed by the approval link of each job waiting for approval. --status takes comma-separated job states (draft, planning, ready, running, waiting_for_approval, waiting_for_human, paused, failed, needs_reconciliation, completed, cancelled) or the aliases open (not done), waiting and done. --mine lists only your own jobs. Pages hold 20 jobs (--limit, at most 50). A page can come back short, or even empty, while older jobs exist, because the server drops the jobs you may not see after reading the page. stderr says so. --all follows every page, 50 jobs each unless --limit says otherwise, for up to 100 pages; past that it stops, exits 1 and asks you to narrow the list with --status or --mine.
  • harness job <job-id> shows the job's status, plan version, progress, remaining window and budget, spend, the change set waiting for approval with its link, the grant, verification, effect counts, blockers, artifacts, pinned skill, people, saved procedures, and what you can do in the workbench.
  • harness job <job-id> --changes shows the job's latest change set instead, by reference: its id, version, status, digest and plan version, and each action's type, version, order, effect count, required capabilities and digest, with the approval link. Values and targets never leave the app; review them there. A job that has proposed nothing prints "No pending change set" (change_set is null under --json).
  • harness job <job-id> --people shows who is on the job, by name: the requester, the execution principal (the person it runs as), the owner, the approvers and the collaborators. A person the job doesn't name shows as their user id; the API never sends emails. Pass both flags to see both, and --json returns them under the job's id, state, link and approval link.
  • harness watch <job-id> prints the job's events and state changes as they happen, polling every 2 seconds, until the job completes, fails or is cancelled, or a person has to act. A person has to act exactly when the job's waiting_on is set: an approval, a person, reconciliation, verification or delivery. watch stops there with the link to act on: the approval link when the job waits for approval. A job paused with nothing for a person to do, such as one whose deliveries are still sending, resumes by itself, so watch keeps following it, every 10 seconds. --through-waits follows through a person's waits too, also every 10 seconds. --after <seq> starts after an event sequence instead of replaying the history. --json prints one document when the watch ends, with the outcome, the job's last state, the events read (ids, codes, numbers and booleans only), and next_after to resume from.
  • watch only reads, so it retries a failed poll three times (after 1, 2 and 4 seconds, or longer when the server sends Retry-After) when the API is unreachable, answers with a 5xx, or rate-limits. If the poll still fails, or you press Ctrl-C, stderr names the --after to resume from, and --json still prints the document with the events read so far. When the reader of its output goes away (watch <id> | head -1), it stops polling and exits quietly.

watch exits 0 when the job completed, 4 when a person has to act, 5 when the job failed or was cancelled, and 130 on Ctrl-C (see Scripting contract).

A 403 names why the server refused: Operator switched off for the organization (operator_disabled), no admin or member role there (operator_not_permitted), a credential that acts as no person (actor_required), or a missing operator:read scope (forbidden). A job that does not exist, is in another organization, or is not shared with you answers the same 404.

Steering, cancelling and pausing a job

steer, cancel and pause change or stop an Operator job the way the workbench's controls do, under the same rules and with your current authority. They need the operator:write scope (a browser login carries it) and an admin or member role in the organization.

harness steer <job-id> "Only use the audited Q3 figures"
harness steer <job-id> --instruction-file instruction.md   # "-" reads stdin
harness cancel <job-id>
harness pause <job-id>
  • harness steer <job-id> <instruction> posts the instruction in the job's room, attributed to you, wherever the app offers Steer. The job's owner and collaborators may steer. Work the instruction changes needs a fresh approval: earlier approvals stop applying to it. Steering a running job stops its current run so it can take the instruction. An instruction is 1 to 10,000 characters once trimmed, and is sent trimmed; a longer or blank one is refused before anything is sent (exit 2). Quote it: unquoted words that look like options (--json, --help) are read as options, not text. Put -- before an instruction that starts with -, or pass it with --instruction-file <path> (- for stdin), which takes any text as it is. Words and --instruction-file together are a usage error.
  • harness cancel <job-id> ends the job. Effects already committed stay committed, and the command prints their counts; staged effects that never ran count as unknown.
  • harness pause <job-id> holds a running job for a person without resetting its active-time budget. Continue it to run again.

Only the job's execution principal may cancel or pause it. Each command prints the job as job shows it afterwards (--json: the same document), and approving still happens in the app.

None of them is idempotent, so the CLI sends each one once and never retries it, not even after a timeout or a server error. What a failure means depends on whether the request can have reached the server:

  • It cannot have (the name did not resolve, the connection was refused, the TLS handshake failed): nothing changed, and the command exits 3 like any unreachable API.
  • It may have (a timeout, a dropped connection, a 5xx, an answer that cannot be read): the outcome is unknown. The command exits 1 and says so; check harness job <job-id> before trying again.

The server's message comes first, followed by what to do about it:

| Status | Code | Meaning | | --- | --- | --- | | 400 | bad_request | The request carried a field the server does not accept. Nothing changed; update the CLI. | | 403 | principal_required | cancel and pause are the job's execution principal's own. Its owner or a collaborator can still steer it, which stops its current run. | | 403 | operator_disabled, operator_not_permitted, actor_required, forbidden, run_bound_credential | As for jobs, except that forbidden means a missing operator:write scope. | | 404 | not_found | The job does not exist, is in another organization, or is not shared with you. | | 409 | invalid_state | The app wouldn't offer the control now: Steer on a finished, failed or awaiting-approval job (or one needing reconciliation), Cancel on a job that already ended, Pause on a job that isn't running. | | 409 | reconciliation_required | Steer: partial effects need reconciling in the app first. | | 409 | deliveries_unsettled | Steer: a delivery is still sending, or its outcome is unknown. If job shows the job waiting on delivery, settle it in the app (an unknown delivery never settles by itself); otherwise steer again once it has sent. | | 409 | principal_inactive | The job's execution principal no longer holds the rights to run it. Nothing changed. | | 409 | cancellation_scope_changed | Cancel: the job's work changed while it was being cancelled. It was not cancelled; read it and decide again. | | 422 | invalid_instruction, validation_error | Steer: the instruction is blank or too long, or the job's room refused it. |

Continue and saved procedures

harness continue <job-id>                               # a new window for a job, as Continue does in the app
harness procedure save <job-id> --name "Monthly close"  # save a verified job as a procedure
harness procedure show <procedure-id>                   # its versions and the parameters a run takes
harness procedure run <procedure-id> --param period=2026-09 --param count:=3
harness procedure run <procedure-id> --params-file params.json --version 2

They act with the authority, budgets and kill controls of the Chat Mode workbench, and need the operator:write scope (a browser login carries it; procedure show needs operator:read).

  • continue opens a new audited window for a job that ran out of time or waits on a person, or retries a failed run or verification, exactly as Continue does in the app. Only the job's execution principal, the person it runs as, may continue it. It prints the job afterwards, as job does.
  • procedure save saves a job's recipe as a versioned procedure. Only the job's owner can save it, and only once it completed with its work verified. Saving again saves another procedure.
  • procedure show prints the procedure's name, the job it was saved from and that job's link, the current version, each version's recipe digest, capability ids and action roster, and the JSON schema of the parameters a run takes. It never shows a version's recipe or anything the saved job read. When the schema's scoped_inputs require contact_reconciliation, a run needs --group <contact-group-id> and --source <uploaded-file-id>.
  • procedure run starts a new job from the procedure, as Rerun does in the app. Nothing of the saved job carries over: the run has fresh inputs, your own current rights, and a change set that needs its own approval, which happens in the app. It prints the new job's link. --version <n> runs only while that version is still current. --param name=value passes a string and --param name:=<json> a JSON value (repeatable); --params-file <path> (- for stdin) passes one JSON object instead. A bad --param (one without =, a name given twice or with whitespace around it), an unreadable or invalid --params-file, both together, a --version that isn't a positive integer, --version, --params-file, --group or --source given twice, or a --group or --source with whitespace around it exits 2 before anything is sent.
  • When the new procedure or job can't be read back as you, the answer is only its id and link, for people and under --json alike.

None of these writes is idempotent, so none is ever retried automatically, not after a timeout or a 5xx either: a second continue opens another window, a second save saves another procedure, a second run starts another job. Their exit codes say whether trying again is safe:

  • 3 only when the request never reached the server (the name did not resolve, the connection was refused, the TLS handshake failed). Nothing changed.
  • 1 with "Outcome unknown" when it may have: a timeout, a dropped connection, an answer that could not be read (then the server received it), or a 5xx. Check what the error names first (job <id>, the job's procedures, or jobs for a run's new job) before trying again. A 503 service_unavailable from procedure run is the exception: the server refused the run before starting it.

The MCP tools follow the same rule: only a request that never reached the server says "cannot reach the API".

A continue that answers 404 may still have happened, if the job went out of your sight just as it was continued: check job <id> first. job <id> --changes reads the job and then its change set; when a new change set lands between the two reads, stderr says so.

Every refusal exits 1 with the server's message, what it means and the HTTP status:

| Status | Code | Meaning | | --- | --- | --- | | 400 | bad_request | The request carried a field the server does not accept. Nothing changed; update the CLI. | | 403 | principal_required | continue: only the job's execution principal may continue it. | | 403 | owner_required | procedure save: only the job's owner can save it. | | 403 | operator_disabled, operator_not_permitted, actor_required, forbidden | As for every Operator command; forbidden here means a missing operator:write scope. | | 404 | not_found | The same answer for a job or procedure that doesn't exist, is in another organization, or that you can't see. | | 409 | invalid_state, reconciliation_required, principal_inactive, deliveries_unsettled | continue: nothing to continue, partial effects to reconcile in the app, a principal who lost the rights, or a delivery still sending or of unknown outcome (settle it in the app when job <id> shows it waiting on delivery). | | 409 | version_conflict, concurrent_change | procedure run: the named version is no longer current, or the procedure changed while the run started. No job was started. | | 422 | not_saveable, validation_error | procedure save: the job isn't completed and verified, or the name holds control characters. | | 422 | invalid_rerun | procedure run: the inputs don't fit the parameters, or your current rights don't cover the run. No job was started. | | 503 | service_unavailable | procedure run: Operator's operational profile is unavailable. No job was started. |

Operator's catalog

What Operator can do in your organization, and the skills it has:

harness tools                                   # the capabilities Operator can use now, by id
harness tools --domain crm                      # one domain's
harness tools domains                           # every domain, with how many
harness tools search "find a party"             # by keyword, best match first
harness tool crm.party.search                   # one capability, with its whole input schema
harness tool crm.party.search --schema result   # a read's result schema
harness skills                                  # the skills assigned to Operator
  • Only what Operator can use now. A capability that ships disabled or is switched off for the organization never appears, so there is no "enabled" column.
  • Kinds. Each capability is a read, a write, or a send (a write delivered beyond the platform, such as an email). Write and send capabilities, and any proposal, always need a person's approval in the app before they take effect. A capability without a kind shows as unknown, never as a read. These commands run and approve nothing.
  • Pages. The index comes 25 to a page and a search 15, with no page size to choose. When there is more, stderr says how many there are in all and names the next page's cursor: --all follows every page (up to 200), and --cursor <cursor> reads the next one, or every page from there with --all. tools search takes --domain, --all, and --cursor as well. An empty --domain or --cursor is a usage error.
  • Schemas. tool <id> fetches every page of the schema and prints it whole. A page that is out of order, skipped, of another schema, or at odds with the others' fragment count fails the command, and no schema is printed. --json returns the capability with its input_schema (or result_schema) as an object. Only a read has a result schema.
  • Skills show each skill's invocation name, title, the SHA-256 of its content, and its id. Their content and description are never exposed.

Operator needs the operator:read scope, an admin or member role in the organization the credential is bound to, and Operator switched on there. Login tokens carry the scope; aone_ tokens need to be minted with it. A refusal exits 1 with the server's message and what it means (operator_disabled, operator_not_permitted, actor_required). tool answers a capability that doesn't exist, ships disabled, or is switched off with the same 404.

Code index

harness code sync                    # sync this worktree’s overlay
harness code sync --path ../app      # use that worktree’s host/org config
harness code status --json           # active version and local freshness
harness code status --skipped        # include the base’s skipped-files report

The repository must be shared through the organization’s Harness GitHub connection and code indexing must be enabled for that org. The server builds commit bases from GitHub. The CLI polls a fetching base with backoff, opens an overlay sync, uploads only blobs that sync requests, and activates it. It never uploads a base or follows source symlinks. A successful sync reports the server’s state; queued means indexing has been requested, not that queries are ready. Safe source reads require macOS 11 or later, or Linux with /proc/self/fd; code sync refuse