ninchi-cli 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. ninchi_cli-0.2.1/PKG-INFO +443 -0
  2. ninchi_cli-0.2.1/README.md +418 -0
  3. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/pyproject.toml +1 -1
  4. ninchi_cli-0.2.1/src/ninchi_cli/__init__.py +14 -0
  5. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli/client.py +19 -2
  6. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli/main.py +32 -2
  7. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli/mcp_server.py +3 -1
  8. ninchi_cli-0.2.1/src/ninchi_cli.egg-info/PKG-INFO +443 -0
  9. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/tests/test_client.py +29 -0
  10. ninchi_cli-0.2.1/tests/test_main.py +423 -0
  11. ninchi_cli-0.2.0/PKG-INFO +0 -138
  12. ninchi_cli-0.2.0/README.md +0 -113
  13. ninchi_cli-0.2.0/src/ninchi_cli/__init__.py +0 -5
  14. ninchi_cli-0.2.0/src/ninchi_cli.egg-info/PKG-INFO +0 -138
  15. ninchi_cli-0.2.0/tests/test_main.py +0 -65
  16. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/setup.cfg +0 -0
  17. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli/config.py +0 -0
  18. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli/diffs.py +0 -0
  19. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli.egg-info/SOURCES.txt +0 -0
  20. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli.egg-info/dependency_links.txt +0 -0
  21. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli.egg-info/entry_points.txt +0 -0
  22. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli.egg-info/requires.txt +0 -0
  23. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/src/ninchi_cli.egg-info/top_level.txt +0 -0
  24. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/tests/test_config.py +0 -0
  25. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/tests/test_diffs.py +0 -0
  26. {ninchi_cli-0.2.0 → ninchi_cli-0.2.1}/tests/test_mcp_server.py +0 -0
@@ -0,0 +1,443 @@
1
+ Metadata-Version: 2.4
2
+ Name: ninchi-cli
3
+ Version: 0.2.1
4
+ Summary: Command-line client and MCP server for Ninchi checks
5
+ Author-email: Ninchi <support@ninchi.ai>
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://ninchi.ai
8
+ Project-URL: Documentation, https://ninchi.ai/docs/cli
9
+ Keywords: ninchi,code-review,verification,mcp,cli
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: httpx<1.0,>=0.27
22
+ Requires-Dist: mcp<2,>=1.2
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest<9,>=8; extra == "dev"
25
+
26
+ # Ninchi CLI and MCP server
27
+
28
+ Run Ninchi checks from a terminal, Git hook, CI job, or MCP-capable editor.
29
+ The package is a thin HTTP client: it does not import the Ninchi backend or
30
+ question engine.
31
+
32
+ A programmatic check records evidence of demonstrated understanding tied to
33
+ the submitted artifact. It is a separate evidence class from pull-request
34
+ verification, not a measurement of latent ability, mastery, or competency.
35
+
36
+ ## Install
37
+
38
+ Install the published package from PyPI:
39
+
40
+ ```bash
41
+ pipx install ninchi-cli
42
+ ```
43
+
44
+ Use `ninchi-cli` 0.2.1 or later. Version 0.2.0 predates an API change in how
45
+ a check reports that its question is still being generated. Against the
46
+ current API, 0.2.0 shows an empty question instead of waiting for it. Check
47
+ the installed version with `pipx list` and upgrade with
48
+ `pipx upgrade ninchi-cli`.
49
+
50
+ ### Development install from a checkout
51
+
52
+ From the repository root:
53
+
54
+ ```bash
55
+ pipx install ./cli
56
+ ```
57
+
58
+ For editable development with test dependencies:
59
+
60
+ ```bash
61
+ cd cli
62
+ python3.12 -m venv .venv
63
+ .venv/bin/pip install -e ".[dev]"
64
+ .venv/bin/pytest -q
65
+ ```
66
+
67
+ The package requires Python 3.11 or newer.
68
+
69
+ ## PATH and GUI editors
70
+
71
+ After the verified public `pipx install ...` command:
72
+
73
+ ```bash
74
+ pipx ensurepath
75
+ ```
76
+
77
+ Close and reopen the shell, then verify:
78
+
79
+ ```bash
80
+ command -v ninchi
81
+ ninchi --help
82
+ ```
83
+
84
+ If an MCP client reports the literal error `spawn ninchi ENOENT`, it cannot
85
+ find the executable. GUI editors often do not inherit the PATH used by an
86
+ interactive shell. Run `command -v ninchi` (or `which ninchi`) in a terminal
87
+ and use the printed absolute path in the MCP configuration.
88
+
89
+ ## Prerequisites
90
+
91
+ GitHub is not required. A check is submitted directly to an organization. It
92
+ needs no repository, pull request, or source-control connection.
93
+
94
+ 1. Have a Ninchi account. Any sign-in method works: email and password
95
+ (with optional TOTP MFA), your organization's SAML single sign-on, or a
96
+ GitHub, GitLab, or Bitbucket account.
97
+ 2. Join the target Ninchi organization. To start an organization without a
98
+ source-control connection, create a **Direct** organization at
99
+ `/dashboard/create-org` and invite members by email. GitHub, GitLab, and
100
+ Bitbucket organizations work the same way for programmatic checks.
101
+ 3. Find its ID in the dashboard URL — the segment after `/dashboard/orgs/`.
102
+ Both identifier forms work: a numeric id (`/dashboard/orgs/42` →
103
+ `--org-id 42`) or the org's opaque UUID
104
+ (`/dashboard/orgs/1b671a64-…` → `--org-id 1b671a64-…`). New dashboard
105
+ URLs use the UUID form; numeric ids in existing configs keep working
106
+ indefinitely. The CLI does not currently have an org-list command.
107
+ 4. Ask an org admin to open org settings, choose the **Challenges** tab, find
108
+ the **Programmatic Checks (CLI & MCP)** card, and turn on **Enable CLI &
109
+ MCP check submission**. The same card sets **Answers submitted by AI
110
+ agents**, which defaults to blocked (see [Answering-actor
111
+ semantics](#answering-actor-semantics)).
112
+ 5. Make sure the organization's plan includes programmatic checks. The
113
+ Hobbyist / Free plan has a quota of zero, including for a new Direct
114
+ organization.
115
+
116
+ Programmatic checks have a hard quota per developer per org in a trailing
117
+ 30-day window. The limit comes from the org's plan, and checks you submit to
118
+ one org do not count against another org's quota. A developer who belongs to
119
+ two orgs has a separate allowance in each.
120
+
121
+ | Plan | Checks per developer, per org |
122
+ |---|---:|
123
+ | Hobbyist / Free | 0 |
124
+ | Standard | 20 |
125
+ | Pro | 100 |
126
+ | Enterprise | 100+ (operator-configured, never below 100) |
127
+
128
+ When the quota is exhausted, the API returns HTTP 429. On a plan that allows
129
+ some checks, the response also states the UTC time when the next check becomes
130
+ available. At that time, enough checks have left the window to make room for
131
+ another check. A plan downgrade can leave you above the cap. Several checks
132
+ must then leave the window, and the stated time moves later.
133
+
134
+ A zero-allowance plan never frees a slot, so the response carries no reset
135
+ time. Waiting does not help. The org needs a plan with a quota above zero.
136
+
137
+ ## Authentication
138
+
139
+ ### Device login
140
+
141
+ ```bash
142
+ ninchi login
143
+ ```
144
+
145
+ The CLI:
146
+
147
+ 1. prints a verification URL and a code such as `XXXX-XXXX`;
148
+ 2. waits while you sign in and approve the code at `/dashboard/device`; and
149
+ 3. saves the issued personal access token to
150
+ `~/.config/ninchi/credentials`.
151
+
152
+ The credentials file is mode `0600`; its parent directory is mode `0700`.
153
+ `NINCHI_CONFIG_DIR` overrides the directory. Otherwise the CLI honors
154
+ `XDG_CONFIG_HOME` before falling back to `~/.config/ninchi`.
155
+
156
+ Login flags:
157
+
158
+ ```text
159
+ --timeout SECONDS approval timeout (default: 600)
160
+ --api-url URL API override (default: https://api.ninchi.ai)
161
+ --json machine-readable output
162
+ ```
163
+
164
+ ### CI token
165
+
166
+ CI should use an environment variable rather than the credentials file:
167
+
168
+ ```bash
169
+ export NINCHI_API_TOKEN="ninchi_pat_..."
170
+ export NINCHI_ORG_ID=42
171
+ ```
172
+
173
+ Create and revoke PATs at `/dashboard/profile`. A token is shown once, is
174
+ stored by Ninchi as an HMAC-SHA256 digest, and stops working immediately after
175
+ revocation. The available scopes are:
176
+
177
+ - `checks:read` — read challenges, results, and personal history.
178
+ - `checks:write` — submit checks and answers.
179
+
180
+ Device login requests both scopes by default. Manually created tokens also
181
+ default to both scopes.
182
+
183
+ `NINCHI_API_URL` or `--api-url` can target another API environment.
184
+
185
+ ## CLI usage
186
+
187
+ ### `ninchi check`
188
+
189
+ With no source flag, `check` submits the working-tree diff from
190
+ `git diff HEAD`:
191
+
192
+ ```bash
193
+ ninchi check --org-id 42
194
+ ```
195
+
196
+ Choose exactly one alternate subject source:
197
+
198
+ ```bash
199
+ git diff | ninchi check --stdin --org-id 42
200
+ ninchi check --staged --org-id 42
201
+ ninchi check --commit HEAD --org-id 42
202
+ ninchi check --file src/app.py --language python --org-id 42
203
+ ```
204
+
205
+ Set the org once for subsequent commands:
206
+
207
+ ```bash
208
+ export NINCHI_ORG_ID=42
209
+ ninchi check --staged
210
+ ```
211
+
212
+ All `check` flags:
213
+
214
+ ```text
215
+ --stdin
216
+ --staged
217
+ --commit SHA
218
+ --file PATH
219
+ --language LANGUAGE
220
+ --org-id ID
221
+ --answer TEXT
222
+ --actor human_attested|agent_assisted|agent_autonomous
223
+ --poll-interval SECONDS
224
+ --timeout SECONDS
225
+ --api-url URL
226
+ --json
227
+ ```
228
+
229
+ `--language` is a hint for `--file`. `--poll-interval` defaults to 2 seconds;
230
+ `--timeout` defaults to 600 seconds.
231
+
232
+ Automation can answer non-interactively and emit JSON:
233
+
234
+ ```bash
235
+ ninchi check --staged \
236
+ --answer "$ANSWER" \
237
+ --actor agent_assisted \
238
+ --json
239
+ ```
240
+
241
+ If `--json` is used without `--answer`, the CLI returns the generated
242
+ challenge and exits 2 with guidance to pass an answer.
243
+
244
+ ### Answering-actor semantics
245
+
246
+ - An answer typed interactively defaults to `human_attested`.
247
+ - `--answer`, or a process with no terminal, defaults to `agent_assisted`.
248
+ - `--actor` overrides the default. Use the value that honestly describes who
249
+ authored the answer:
250
+ - `human_attested`: the human wrote the answer.
251
+ - `agent_assisted`: a human meaningfully directed the answer's content.
252
+ - `agent_autonomous`: an agent authored the answer.
253
+
254
+ The server enforces the organization's agent-answer policy. Never relabel an
255
+ answer to bypass that policy.
256
+
257
+ ### `ninchi result`
258
+
259
+ ```bash
260
+ ninchi result CHALLENGE_ID
261
+ ninchi result CHALLENGE_ID --json
262
+ ```
263
+
264
+ ### `ninchi history`
265
+
266
+ ```bash
267
+ ninchi history
268
+ ninchi history --limit 50
269
+ ninchi history --limit 50 --json
270
+ ```
271
+
272
+ The default limit is 20. The server accepts limits from 1 to 100.
273
+
274
+ ### Exit codes
275
+
276
+ - `0`: challenge passed.
277
+ - `1`: challenge failed.
278
+ - `2`: pending state, configuration error, authentication error, API error, or
279
+ another operational error.
280
+
281
+ ## Pre-push hook
282
+
283
+ Example `.git/hooks/pre-push`:
284
+
285
+ ```bash
286
+ #!/usr/bin/env bash
287
+ set -euo pipefail
288
+
289
+ base="${NINCHI_BASE_REF:-origin/main}"
290
+ git diff --patch "$base"...HEAD | ninchi check \
291
+ --stdin \
292
+ --org-id "${NINCHI_ORG_ID:?set NINCHI_ORG_ID}"
293
+ ```
294
+
295
+ Make it executable:
296
+
297
+ ```bash
298
+ chmod +x .git/hooks/pre-push
299
+ ```
300
+
301
+ This form is human-attested: it omits `--answer` and prompts in the terminal.
302
+
303
+ ## CI
304
+
305
+ The CLI needs only two environment variables, `NINCHI_API_TOKEN` and
306
+ `NINCHI_ORG_ID`, so it runs in any CI system: GitLab CI, Bitbucket
307
+ Pipelines, Jenkins, and others. The GitHub Actions job below is one example.
308
+ The same pipeline works elsewhere: install with pipx, pipe the diff against
309
+ the target branch into `ninchi check --stdin --json`, and store the token as a
310
+ masked secret.
311
+
312
+ ### GitHub Actions
313
+
314
+ ```yaml
315
+ name: Ninchi check
316
+ on:
317
+ pull_request:
318
+
319
+ jobs:
320
+ ninchi:
321
+ runs-on: ubuntu-latest
322
+ steps:
323
+ - uses: actions/checkout@v4
324
+ with:
325
+ fetch-depth: 0
326
+ - uses: actions/setup-python@v5
327
+ with:
328
+ python-version: "3.12"
329
+ - run: pipx install ninchi-cli
330
+ - name: Submit Ninchi check and surface the challenge
331
+ run: |
332
+ git diff --patch origin/${{ github.base_ref }}...HEAD |
333
+ ninchi check --stdin --json | tee ninchi-challenge.json || true
334
+ env:
335
+ NINCHI_API_TOKEN: ${{ secrets.NINCHI_API_TOKEN }}
336
+ NINCHI_ORG_ID: ${{ vars.NINCHI_ORG_ID }}
337
+ ```
338
+
339
+ Store the PAT in GitHub Secrets. This job stops at submission by design: with
340
+ `--json` and no `--answer`, the CLI prints the challenge JSON — including the
341
+ question — and exits 2 (the `|| true` keeps the step green). The answer must
342
+ come from whoever authored the change; do not wire a canned or scripted answer
343
+ into CI. The question is generated per submission, and the answer window (the
344
+ org-configured TTL) starts once the challenge is first viewed — so have authors
345
+ answer interactively where they work, such as the pre-push hook above or an
346
+ MCP-capable editor.
347
+
348
+ ## MCP server (`ninchi mcp`)
349
+
350
+ Authenticate once with `ninchi login`, then configure the stdio server in an
351
+ MCP-capable client.
352
+
353
+ ### Recommended: absolute path
354
+
355
+ Run:
356
+
357
+ ```bash
358
+ command -v ninchi
359
+ ```
360
+
361
+ Use that output as `command`:
362
+
363
+ ```json
364
+ {
365
+ "mcpServers": {
366
+ "ninchi": {
367
+ "command": "/absolute/path/from/command-v-ninchi",
368
+ "args": ["mcp"]
369
+ }
370
+ }
371
+ }
372
+ ```
373
+
374
+ Cursor accepts this shape in `.cursor/mcp.json` or its global MCP settings.
375
+ Other MCP clients use the same command and argument, though their config-file
376
+ location differs.
377
+
378
+ ### Alternative when the editor inherits PATH
379
+
380
+ ```json
381
+ {
382
+ "mcpServers": {
383
+ "ninchi": {
384
+ "command": "ninchi",
385
+ "args": ["mcp"]
386
+ }
387
+ }
388
+ }
389
+ ```
390
+
391
+ If this produces `spawn ninchi ENOENT`, use the absolute-path form.
392
+
393
+ ### Five MCP tools
394
+
395
+ | Tool | Purpose |
396
+ |---|---|
397
+ | `submit_check` | Submit exactly one diff, content block, or file region and start question generation. |
398
+ | `get_challenge` | Fetch the question, optionally waiting while generation finishes. |
399
+ | `submit_answer` | Submit an answer with a required, honest `actor` claim. |
400
+ | `get_result` | Fetch score and feedback, optionally waiting while evaluation finishes. |
401
+ | `list_history` | List the authenticated developer's recent challenges. |
402
+
403
+ `submit_answer` requires `actor`. When the org policy is `blocked`, the tool
404
+ returns guidance to hand the question to the human and relay only the human's
405
+ own words as `human_attested`. When the policy is `recorded`,
406
+ `agent_assisted` and `agent_autonomous` answers are accepted and stored with
407
+ that provenance.
408
+
409
+ MCP check submissions include:
410
+
411
+ ```json
412
+ {
413
+ "client_attribution": "mcp:<client>"
414
+ }
415
+ ```
416
+
417
+ This names the submitting channel and feeds AI-authorship analytics as a
418
+ detection-floor signal. It does not, by itself, assert that AI assisted the
419
+ submitted work. When the client name is unavailable, the server uses `mcp`.
420
+
421
+ ## Evidence and score semantics
422
+
423
+ Programmatic checks use `origin="api"` and are stored as a distinct,
424
+ lower-weight evidence class. Their question, answer, score, artifact context,
425
+ answering-actor claim, client attribution, and timestamps remain inspectable.
426
+
427
+ By default, this evidence does **not** count toward Ninchi Scores. An org admin
428
+ may opt in with **Count programmatic checks toward Ninchi Scores** in the
429
+ Programmatic Checks card. Pull-request evidence is unaffected either way.
430
+
431
+ ## Troubleshooting
432
+
433
+ | Symptom | Resolution |
434
+ |---|---|
435
+ | `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. |
436
+ | 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. |
437
+ | 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. |
438
+ | 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`. |
439
+ | Challenge expired | The answer window starts when a ready challenge is first viewed. The org configures the TTL; the shipped default is 5 minutes. |
440
+ | 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`. |
441
+ | 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`. |
442
+
443
+ CLI errors are printed to stderr, or as JSON with `--json`, and exit 2.