@j4k-oss/tropkod-client (2.0.1)
Installation
@j4k-oss:registry=npm install @j4k-oss/tropkod-client@2.0.1"@j4k-oss/tropkod-client": "2.0.1"About this package
@j4k-oss/tropkod-client
Stateless CLI client for the tropkod dependency-analysis API. It submits one natural-language question about a public npm or crates.io dependency, prints the created job id, and exits 2 while the hosted service analyzes the question against the dependency's real source. Poll the job id while it exits 2, branching per the exit-code table otherwise; opt into a synchronous wait with --wait-ms.
The client holds no state: it opens no local store, spawns no agent, and inspects no source. It only sends HTTP requests to a tropkod service and renders the response.
Install
The package is published to the code.j4k.dev registry under the @j4k-oss scope, which allows anonymous reads (no token needed):
npm config set @j4k-oss:registry https://code.j4k.dev/api/packages/j4k-oss/npm/
npm i -g @j4k-oss/tropkod-client
Authentication
Requests need a service URL and an API key. Keys are provisioned manually — there is no self-serve signup. Set both (or pass --url / --api-key):
export TROPKOD_URL=https://api.tropkod.ai
export TROPKOD_API_KEY=<provisioned-key>
TROPKOD_URL has no default: every request requires --url or TROPKOD_URL, otherwise the client exits 3 with requests require --url or TROPKOD_URL. The hosted deployment is https://api.tropkod.ai; point at a self-hosted service with its own URL.
Usage
tropkod-client "Does npm:fastify@5.6.0 expose schemaCompiler?"
Name the dependency inline as npm:<package>@<version> or crate:<name>@<version>.
The submission returns immediately with a pending job and its id (exit 2). Poll the job — waiting about 30 seconds between polls — until it completes (exit 0) or fails (exit 1):
tropkod-client --job <job-id>
To wait synchronously instead, pass a wait budget: --wait-ms 120000 blocks up to two minutes for the answer.
Read the question from stdin when no positional argument is given and stdin is not a terminal:
echo "Does crate serde@1.0.197 expose Deserialize?" | tropkod-client
Stdin is read to EOF: an open pipe that never closes blocks the command indefinitely, outside the exit-code taxonomy entirely. Pass the question as an argument, or pipe from a source that closes. URL and credential errors are checked before stdin is read, so a missing --url or --api-key exits 3 immediately instead of blocking.
JSON output
--json makes every invocation that reaches a verdict print exactly one JSON document on stdout: .job present means the parsed submission envelope (the fields this client build knows — unknown server keys are stripped, and a null analysis is omitted); .error present means the invocation failed. Two exceptions: (1) --help/--version print human text and exit 0; (2) a crashed client exits with empty stdout — exit 7 from the bin wrapper's catch, or the runtime's own exit 1 on a crash outside the handlers. Empty stdout is never a verdict.
Discriminate both shapes in one expression:
tropkod-client --json "Does npm:zod@4 expose z.url?" | jq -r '.error.kind // .job.status'
A failed invocation prints an error envelope:
{
"error": {
"kind": "http-error",
"message": "invalid api key (HTTP 401)",
"status": 401,
"exit_code": 4
}
}
| Field | Meaning |
|---|---|
kind |
usage, transport, http-error, invalid-response, or unexpected |
message |
The failure message; what non-JSON mode prints to stderr (see the commander caveat below) |
status |
Numeric HTTP status; present iff kind is http-error or invalid-response |
job_id |
Present when an invalid-response body still carried a readable job id |
exit_code |
The process exit code, so stdout alone recovers the full verdict |
Without --json, a failed invocation prints the bare message on stderr and nothing on stdout. Exit 7 additionally writes the original error, stack included, to stderr in both modes — the envelope's one-line message is not a filable bug report.
One caveat for commander-originated usage errors (an unknown option, a rejected --wait-ms value): commander prints its own prose on stderr even under --json — the stdout contract still holds (exactly one JSON document) — and the envelope's message carries commander's wording (error: prefix, possible embedded newlines) minus the trailing (add --help for usage) hint, so it is close to but not byte-identical with stderr. The CLI's own errors match stderr exactly.
Flags
| Flag | Env fallback | Default | Notes |
|---|---|---|---|
--url <url> |
TROPKOD_URL |
none (required) | tropkod service URL; the hosted deployment is https://api.tropkod.ai; must be an http(s) URL with no credentials, query, or fragment, on a port fetch will connect to (the WHATWG bad-port list is rejected client-side) |
--api-key <key> |
TROPKOD_API_KEY |
none (required) | Provisioned manually |
--session <id> |
— | — | Reuse a session for follow-up questions |
--job <id> |
— | — | Fetch an existing job instead of submitting |
--wait-ms <ms> |
— | 0 |
Synchronous wait budget; 0 (the default) submits without waiting. Values above 300000 are rejected client-side, matching the server cap |
--json |
— | false |
Print the full JSON envelope |
Pass either a question or --job, not both. --session and --wait-ms apply only to submissions; when --job is given they are ignored.
Exit codes
| Code | Meaning | On a submit: job created? | On a poll (--job) |
|---|---|---|---|
0 |
Job completed (answered or unresolved). Also --help / --version. |
Yes — envelope printed, id in hand | Job ended; verdict in hand |
1 |
Job failed — the server's own verdict, envelope printed. No other CLI verdict exits 1. |
Yes — id in hand | Verdict: failed |
2 |
Job pending or running (the normal result of a default submit). |
Yes — id in hand | Keep polling |
3 |
Usage error — invalid flags/arguments/environment, including URL and key shapes the HTTP client is known to refuse before connecting; no HTTP request was dispatched. | No | Nothing was sent — fix the input, resume polling the same id |
4 |
HTTP error — non-2xx response; numeric status in message and envelope. The status line alone decides: an error body that stalls or resets loses only the message decoration, never the code. | 4xx except 408: no. 408 and 5xx: unknown (an intermediary may mask an accepted request) — branch on status |
The GET touched nothing: 401/403 → fix credential, keep polling; 404 → re-check the id (a confirmed-correct-id 404 ends the poll); 408/429/5xx → keep polling |
5 |
Transport failure — no response completed (network failure, the client abort after max(wait_ms + 5000, 30000) ms on a submit / the fixed 30 s poll timeout — including an abort mid-body of a 2xx response; a non-2xx with an unreadable body stays exit 4). |
Unknown — assume yes | Job untouched — keep polling |
6 |
Invalid response — 2xx received but the body was not a parseable submission envelope. | Yes — id recovered iff the envelope carries .error.job_id (the body held a readable job.id); lost otherwise |
Job untouched. A transient blip (truncated body, proxy garbling) recovers — retry once or twice. A repeated exit 6 with an identical message is deterministic schema drift, terminal for this client build: the job has likely already ended in a shape this client cannot render — report it, don't keep looping |
7 |
Unexpected client error (a bug in this CLI). | Unknown — assume yes | Job untouched — report the bug; polling the same id stays safe |
After a failed submit, resubmitting is safe iff code == 3 || (code == 4 && status < 500 && status != 408) — and only when every earlier submit in the task is accounted for. 408 is excluded for the same reason as 5xx: an intermediary can mint it after forwarding the POST. A failed poll never licenses a resubmit: GET /v1/jobs/<id> can neither create nor destroy a job — repair the input and keep polling.
--help and --version exit 0. Exit 1 with empty stdout is a crashed client, not a failed job: the discriminator is the envelope, not the code.
Scripts that must distinguish an answered result from an unresolved one should use --json and inspect analysis.status.
Orphaned jobs
A submit that dies before printing its job id — a harness-killed process, a transport failure (exit 5), an accepted response without a recoverable id (exit 6 with no .error.job_id; an exit 6 whose envelope carries .error.job_id orphans nothing), or a client crash (exit 7, or a bare runtime exit 1 with empty stdout) — orphans the job, not the analysis: the server never cancels a job because the client went away, and runs the paid analysis to completion on its single processing slot. The client has exactly two routes, POST /v1/queries and GET /v1/jobs/<id>; there is no job-listing or lookup route, so an id that was never printed is unrecoverable. --session does not help: it is submit-only input, and reusing it queues a new paid analysis behind the invisible first one. After losing a submit without an id, report and stop — do not resubmit the question. Exit 3, and exit 4 on a 4xx other than 408, are the codes that prove no job was created by the failed invocation.
Agent Rule
Reach for tropkod-client when you need to check a claim about a public npm or crates.io dependency ("does fastify@5.6.0 expose schemaCompiler?") against the dependency's real source rather than guessing from memory or package metadata. Run tropkod-client --help before first use to confirm the current flags, then submit one inline-named question (npm:<pkg>@<version> or crate:<name>@<version>) with --json. The default submit is non-blocking: it prints the job envelope and exits 2; poll --job <id> --json while it exits 2, branching per the exit-code table otherwise. Wait about 30 seconds between polls — a tight loop earns the 429 the table then tells you to poll through — and bound the whole loop at about 35 minutes, since the server's own analysis budget is 30. Branch on exit codes 3–7 and on .error.kind/.error.status instead of stderr wording; on polls, the loop ends at a stdout verdict, a confirmed-correct-id 404, a repeated exit 6 with an identical message (deterministic schema drift — report, don't loop), or the overall bound expiring — a job still pending past the bound, or a service answering only exit 5/5xx for that long, means stop and report the job id, not poll on. It needs TROPKOD_URL and TROPKOD_API_KEY in the environment.
Development
This repo lints with @j4k/oxlint-config, which is published only to the code.j4k.dev registry under the private @j4k scope. pnpm install therefore needs a code.j4k.dev read token even though the repository is public:
npm config set //code.j4k.dev/api/packages/j4k/npm/:_authToken "$FORGEJO_NPM_TOKEN"
pnpm install
pnpm test
pnpm lint
pnpm typecheck
pnpm build
License
MIT. See LICENSE.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @commander-js/extra-typings | ^15.0.0 |
| commander | ^15.0.0 |
| zod | ^4.4.3 |
Development dependencies
| ID | Version |
|---|---|
| @j4k/oxlint-config | ^2.11.0 |
| @j4k/semantic-release-forgejo | ^1.2.1 |
| @total-typescript/ts-reset | ^0.6.1 |
| @types/node | ^24.0.0 |
| @vitest/coverage-v8 | ^4.1.8 |
| conventional-changelog-conventionalcommits | ^9.3.1 |
| fta-check | ^1.5.2 |
| fta-cli | ^3.0.0 |
| knip | ^6.16.1 |
| oxfmt | ^0.57.0 |
| oxlint | ^1.72.0 |
| oxlint-tsgolint | ^0.23.0 |
| semantic-release | ^25.0.7 |
| typescript | ^7.0.2 |
| vitest | ^4.1.8 |