@j4k-oss/tropkod-client (2.0.1)

Published 2026-08-07 15:32:25 +00:00 by oss-publisher in j4k-oss/tropkod-client

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 37 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
Details
npm
2026-08-07 15:32:25 +00:00
2
Łukasz Jerciński
MIT
latest
22 KiB
Assets (1)
Versions (3) View all
2.0.1 2026-08-07
2.0.0 2026-08-01
1.0.0 2026-07-17