Stateless CLI client for the tropkod dependency-analysis API.
  • TypeScript 91.8%
  • JavaScript 5%
  • Shell 3.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Łukasz Jerciński 2afd629912
All checks were successful
Dedupe check / dedupe-check (push) Successful in 37s
Release / quality-checks (26.5.0) (push) Successful in 48s
Release / quality-checks (24.15.0) (push) Successful in 48s
Release / quality-checks (push) Successful in 0s
Release / Release (push) Successful in 25s
chore: update fta-check to 1.5.8 (#18)
2026-10-07 05:57:23 +00:00
.forgejo/workflows chore: pin review wrapper 65f9120 for same-diff reuse 2026-10-05 14:57:29 +02:00
.githooks chore: update forge review pipeline configuration 2026-08-02 10:35:51 +02:00
.vscode chore: align repo config (#1) 2026-07-17 18:41:23 +00:00
bin feat!: non-blocking default, distinct exit codes, JSON error envelopes (#3) 2026-07-31 11:24:32 +00:00
src feat!: return success for pending and running jobs (#13) 2026-08-26 13:39:10 +00:00
types feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
.gitignore feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
.node-version ci: roll out OIDC review workflow (#4) 2026-07-31 05:31:10 +00:00
.oxfmtrc.json chore: align repo config (#1) 2026-07-17 18:41:23 +00:00
.review-enrolled ci: enroll in the managed review path (#15) 2026-09-01 09:51:43 +00:00
AGENTS.md chore: regenerate agent instructions 2026-10-03 07:27:44 +02:00
CLAUDE.md docs: add generated agent instruction files 2026-07-29 20:03:50 +02:00
fta.json feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
knip.json ci: use shared Forgejo review writer (#7) 2026-08-02 10:36:40 +00:00
LICENSE feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
oxlint.config.ts feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
package.json chore: update fta-check to 1.5.8 (#18) 2026-10-07 05:57:23 +00:00
pnpm-lock.yaml chore: update fta-check to 1.5.8 (#18) 2026-10-07 05:57:23 +00:00
pnpm-workspace.yaml feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
README.md feat!: return success for pending and running jobs (#13) 2026-08-26 13:39:10 +00:00
release.config.mjs chore: update forge review pipeline configuration 2026-08-02 10:35:51 +02:00
tsconfig.app.json chore: update forge review pipeline configuration 2026-08-02 10:35:51 +02:00
tsconfig.base.json chore: update forge review pipeline configuration 2026-08-02 10:35:51 +02:00
tsconfig.json feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00
tsconfig.test.json chore: update forge review pipeline configuration 2026-08-02 10:35:51 +02:00
vitest.config.ts feat: initial tropkod-client CLI 2026-07-17 19:42:22 +02:00

@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 successfully while the hosted service analyzes the question against the dependency's real source. Inspect the returned job status and poll its id until it reaches a terminal state; 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 default submission returns immediately with a job envelope and its id. Pending, running, and completed jobs exit 0; failed jobs exit 1. When job.status is pending or running, poll about every 30 seconds until it is completed or failed. Use the same service URL and credentials for every poll:

tropkod-client --job <job-id>

For a pending or running job, human output labels a one-line JSON array as Poll argv (JSON data, not shell syntax; do not paste it into a shell):. Parse that JSON and preserve each array element as one argument when invoking the client; never evaluate it with a shell. The rendered URL is the normalized credential-free service URL, and the API key is never printed — configure the same TROPKOD_API_KEY before polling.

To wait synchronously instead, pass a wait budget: --wait-ms 120000 blocks up to two minutes for the answer.

Pending and running jobs are successful protocol responses, so they exit 0 and are safe to use under set -e and set -o pipefail. Scripts should branch on job.status, not an exit code:

set -euo pipefail
if submission=$(tropkod-client --json "Does npm:zod@4 expose z.url?"); then
  status=$(printf '%s' "$submission" | jq -er '
    .job.status | select(. == "pending" or . == "running" or . == "completed")
  ')
  case "$status" in
    pending|running) printf '%s\n' "poll the returned job id later" ;;
    completed) printf '%s\n' "analysis is ready" ;;
  esac
else
  exit "$?" # failed job or an invocation failure
fi

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 Valid job state: pending, running, or completed (answered or unresolved). Also --help / --version. Yes — envelope printed, id in hand; inspect .job.status and poll while nonterminal A pending/running job remains to be polled; a completed job has its verdict
1 Job failed — the server's own verdict, envelope printed. No other CLI verdict exits 1. Yes — id in hand Verdict: failed
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 a job envelope and exits 0; inspect .job.status, and poll --job <id> --json while it is pending or running. 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 1 and 3–7, and on .error.kind/.error.status instead of stderr wording; on polls, the loop ends at a completed or failed job envelope, 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.