Metadata-Version: 2.4
Name: ninchi-cli
Version: 0.2.1
Summary: Command-line client and MCP server for Ninchi checks
Author-email: Ninchi <support@ninchi.ai>
License: Proprietary
Project-URL: Homepage, https://ninchi.ai
Project-URL: Documentation, https://ninchi.ai/docs/cli
Keywords: ninchi,code-review,verification,mcp,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: mcp<2,>=1.2
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == "dev"

# Ninchi CLI and MCP server

Run Ninchi checks from a terminal, Git hook, CI job, or MCP-capable editor.
The package is a thin HTTP client: it does not import the Ninchi backend or
question engine.

A programmatic check records evidence of demonstrated understanding tied to
the submitted artifact. It is a separate evidence class from pull-request
verification, not a measurement of latent ability, mastery, or competency.

## Install

Install the published package from PyPI:

```bash
pipx install ninchi-cli
```

Use `ninchi-cli` 0.2.1 or later. Version 0.2.0 predates an API change in how
a check reports that its question is still being generated. Against the
current API, 0.2.0 shows an empty question instead of waiting for it. Check
the installed version with `pipx list` and upgrade with
`pipx upgrade ninchi-cli`.

### Development install from a checkout

From the repository root:

```bash
pipx install ./cli
```

For editable development with test dependencies:

```bash
cd cli
python3.12 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q
```

The package requires Python 3.11 or newer.

## PATH and GUI editors

After the verified public `pipx install ...` command:

```bash
pipx ensurepath
```

Close and reopen the shell, then verify:

```bash
command -v ninchi
ninchi --help
```

If an MCP client reports the literal error `spawn ninchi ENOENT`, it cannot
find the executable. GUI editors often do not inherit the PATH used by an
interactive shell. Run `command -v ninchi` (or `which ninchi`) in a terminal
and use the printed absolute path in the MCP configuration.

## Prerequisites

GitHub is not required. A check is submitted directly to an organization. It
needs no repository, pull request, or source-control connection.

1. Have a Ninchi account. Any sign-in method works: email and password
   (with optional TOTP MFA), your organization's SAML single sign-on, or a
   GitHub, GitLab, or Bitbucket account.
2. Join the target Ninchi organization. To start an organization without a
   source-control connection, create a **Direct** organization at
   `/dashboard/create-org` and invite members by email. GitHub, GitLab, and
   Bitbucket organizations work the same way for programmatic checks.
3. Find its ID in the dashboard URL — the segment after `/dashboard/orgs/`.
   Both identifier forms work: a numeric id (`/dashboard/orgs/42` →
   `--org-id 42`) or the org's opaque UUID
   (`/dashboard/orgs/1b671a64-…` → `--org-id 1b671a64-…`). New dashboard
   URLs use the UUID form; numeric ids in existing configs keep working
   indefinitely. The CLI does not currently have an org-list command.
4. Ask an org admin to open org settings, choose the **Challenges** tab, find
   the **Programmatic Checks (CLI & MCP)** card, and turn on **Enable CLI &
   MCP check submission**. The same card sets **Answers submitted by AI
   agents**, which defaults to blocked (see [Answering-actor
   semantics](#answering-actor-semantics)).
5. Make sure the organization's plan includes programmatic checks. The
   Hobbyist / Free plan has a quota of zero, including for a new Direct
   organization.

Programmatic checks have a hard quota per developer per org in a trailing
30-day window. The limit comes from the org's plan, and checks you submit to
one org do not count against another org's quota. A developer who belongs to
two orgs has a separate allowance in each.

| Plan | Checks per developer, per org |
|---|---:|
| Hobbyist / Free | 0 |
| Standard | 20 |
| Pro | 100 |
| Enterprise | 100+ (operator-configured, never below 100) |

When the quota is exhausted, the API returns HTTP 429. On a plan that allows
some checks, the response also states the UTC time when the next check becomes
available. At that time, enough checks have left the window to make room for
another check. A plan downgrade can leave you above the cap. Several checks
must then leave the window, and the stated time moves later.

A zero-allowance plan never frees a slot, so the response carries no reset
time. Waiting does not help. The org needs a plan with a quota above zero.

## Authentication

### Device login

```bash
ninchi login
```

The CLI:

1. prints a verification URL and a code such as `XXXX-XXXX`;
2. waits while you sign in and approve the code at `/dashboard/device`; and
3. saves the issued personal access token to
   `~/.config/ninchi/credentials`.

The credentials file is mode `0600`; its parent directory is mode `0700`.
`NINCHI_CONFIG_DIR` overrides the directory. Otherwise the CLI honors
`XDG_CONFIG_HOME` before falling back to `~/.config/ninchi`.

Login flags:

```text
--timeout SECONDS   approval timeout (default: 600)
--api-url URL       API override (default: https://api.ninchi.ai)
--json              machine-readable output
```

### CI token

CI should use an environment variable rather than the credentials file:

```bash
export NINCHI_API_TOKEN="ninchi_pat_..."
export NINCHI_ORG_ID=42
```

Create and revoke PATs at `/dashboard/profile`. A token is shown once, is
stored by Ninchi as an HMAC-SHA256 digest, and stops working immediately after
revocation. The available scopes are:

- `checks:read` — read challenges, results, and personal history.
- `checks:write` — submit checks and answers.

Device login requests both scopes by default. Manually created tokens also
default to both scopes.

`NINCHI_API_URL` or `--api-url` can target another API environment.

## CLI usage

### `ninchi check`

With no source flag, `check` submits the working-tree diff from
`git diff HEAD`:

```bash
ninchi check --org-id 42
```

Choose exactly one alternate subject source:

```bash
git diff | ninchi check --stdin --org-id 42
ninchi check --staged --org-id 42
ninchi check --commit HEAD --org-id 42
ninchi check --file src/app.py --language python --org-id 42
```

Set the org once for subsequent commands:

```bash
export NINCHI_ORG_ID=42
ninchi check --staged
```

All `check` flags:

```text
--stdin
--staged
--commit SHA
--file PATH
--language LANGUAGE
--org-id ID
--answer TEXT
--actor human_attested|agent_assisted|agent_autonomous
--poll-interval SECONDS
--timeout SECONDS
--api-url URL
--json
```

`--language` is a hint for `--file`. `--poll-interval` defaults to 2 seconds;
`--timeout` defaults to 600 seconds.

Automation can answer non-interactively and emit JSON:

```bash
ninchi check --staged \
  --answer "$ANSWER" \
  --actor agent_assisted \
  --json
```

If `--json` is used without `--answer`, the CLI returns the generated
challenge and exits 2 with guidance to pass an answer.

### Answering-actor semantics

- An answer typed interactively defaults to `human_attested`.
- `--answer`, or a process with no terminal, defaults to `agent_assisted`.
- `--actor` overrides the default. Use the value that honestly describes who
  authored the answer:
  - `human_attested`: the human wrote the answer.
  - `agent_assisted`: a human meaningfully directed the answer's content.
  - `agent_autonomous`: an agent authored the answer.

The server enforces the organization's agent-answer policy. Never relabel an
answer to bypass that policy.

### `ninchi result`

```bash
ninchi result CHALLENGE_ID
ninchi result CHALLENGE_ID --json
```

### `ninchi history`

```bash
ninchi history
ninchi history --limit 50
ninchi history --limit 50 --json
```

The default limit is 20. The server accepts limits from 1 to 100.

### Exit codes

- `0`: challenge passed.
- `1`: challenge failed.
- `2`: pending state, configuration error, authentication error, API error, or
  another operational error.

## Pre-push hook

Example `.git/hooks/pre-push`:

```bash
#!/usr/bin/env bash
set -euo pipefail

base="${NINCHI_BASE_REF:-origin/main}"
git diff --patch "$base"...HEAD | ninchi check \
  --stdin \
  --org-id "${NINCHI_ORG_ID:?set NINCHI_ORG_ID}"
```

Make it executable:

```bash
chmod +x .git/hooks/pre-push
```

This form is human-attested: it omits `--answer` and prompts in the terminal.

## CI

The CLI needs only two environment variables, `NINCHI_API_TOKEN` and
`NINCHI_ORG_ID`, so it runs in any CI system: GitLab CI, Bitbucket
Pipelines, Jenkins, and others. The GitHub Actions job below is one example.
The same pipeline works elsewhere: install with pipx, pipe the diff against
the target branch into `ninchi check --stdin --json`, and store the token as a
masked secret.

### GitHub Actions

```yaml
name: Ninchi check
on:
  pull_request:

jobs:
  ninchi:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pipx install ninchi-cli
      - name: Submit Ninchi check and surface the challenge
        run: |
          git diff --patch origin/${{ github.base_ref }}...HEAD |
            ninchi check --stdin --json | tee ninchi-challenge.json || true
        env:
          NINCHI_API_TOKEN: ${{ secrets.NINCHI_API_TOKEN }}
          NINCHI_ORG_ID: ${{ vars.NINCHI_ORG_ID }}
```

Store the PAT in GitHub Secrets. This job stops at submission by design: with
`--json` and no `--answer`, the CLI prints the challenge JSON — including the
question — and exits 2 (the `|| true` keeps the step green). The answer must
come from whoever authored the change; do not wire a canned or scripted answer
into CI. The question is generated per submission, and the answer window (the
org-configured TTL) starts once the challenge is first viewed — so have authors
answer interactively where they work, such as the pre-push hook above or an
MCP-capable editor.

## MCP server (`ninchi mcp`)

Authenticate once with `ninchi login`, then configure the stdio server in an
MCP-capable client.

### Recommended: absolute path

Run:

```bash
command -v ninchi
```

Use that output as `command`:

```json
{
  "mcpServers": {
    "ninchi": {
      "command": "/absolute/path/from/command-v-ninchi",
      "args": ["mcp"]
    }
  }
}
```

Cursor accepts this shape in `.cursor/mcp.json` or its global MCP settings.
Other MCP clients use the same command and argument, though their config-file
location differs.

### Alternative when the editor inherits PATH

```json
{
  "mcpServers": {
    "ninchi": {
      "command": "ninchi",
      "args": ["mcp"]
    }
  }
}
```

If this produces `spawn ninchi ENOENT`, use the absolute-path form.

### Five MCP tools

| Tool | Purpose |
|---|---|
| `submit_check` | Submit exactly one diff, content block, or file region and start question generation. |
| `get_challenge` | Fetch the question, optionally waiting while generation finishes. |
| `submit_answer` | Submit an answer with a required, honest `actor` claim. |
| `get_result` | Fetch score and feedback, optionally waiting while evaluation finishes. |
| `list_history` | List the authenticated developer's recent challenges. |

`submit_answer` requires `actor`. When the org policy is `blocked`, the tool
returns guidance to hand the question to the human and relay only the human's
own words as `human_attested`. When the policy is `recorded`,
`agent_assisted` and `agent_autonomous` answers are accepted and stored with
that provenance.

MCP check submissions include:

```json
{
  "client_attribution": "mcp:<client>"
}
```

This names the submitting channel and feeds AI-authorship analytics as a
detection-floor signal. It does not, by itself, assert that AI assisted the
submitted work. When the client name is unavailable, the server uses `mcp`.

## Evidence and score semantics

Programmatic checks use `origin="api"` and are stored as a distinct,
lower-weight evidence class. Their question, answer, score, artifact context,
answering-actor claim, client attribution, and timestamps remain inspectable.

By default, this evidence does **not** count toward Ninchi Scores. An org admin
may opt in with **Count programmatic checks toward Ninchi Scores** in the
Programmatic Checks card. Pull-request evidence is unaffected either way.

## Troubleshooting

| Symptom | Resolution |
|---|---|
| `spawn ninchi ENOENT` | The editor cannot find the executable. Run `pipx ensurepath`, open a new shell, run `command -v ninchi`, and use the resulting absolute path in the MCP config. |
| 403: programmatic checks are not enabled | An org admin must enable **Enable CLI & MCP check submission** in **Programmatic Checks (CLI & MCP)** on the **Challenges** tab of org settings. |
| 429 | The trailing-30-day quota for this developer in this org is exhausted. When the plan allows some checks, the response gives the UTC time the window next frees a slot. On a zero-allowance plan (Hobbyist / Free) there is no reset time to report; the org needs a plan that includes programmatic checks. |
| 403 on an agent answer | The org's `programmatic_agent_answers` policy is `blocked`. Hand the question to the human; submit only their own words as `human_attested`. |
| Challenge expired | The answer window starts when a ready challenge is first viewed. The org configures the TTL; the shipped default is 5 minutes. |
| Status `generating` or `answered` | Generation and grading are asynchronous. `generating` means the question is not ready; `answered` means grading is still running. Keep polling `get_challenge` or `get_result` until a terminal result or client timeout. The MCP tools report a timed-out wait as `generating` or `evaluating`. |
| Empty question, then an error on answer | You are running `ninchi-cli` 0.2.0, which does not wait for generation against the current API. Run `pipx upgrade ninchi-cli`. |

CLI errors are printed to stderr, or as JSON with `--json`, and exit 2.
