npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

praixis

v1.2.0

Published

Zero-dependency Node.js client for the Praixis Engine API

Readme

Praixis Engine — Node.js Client

A lightweight, zero-dependency Node.js client for the Praixis Engine API. It is built on the global fetch (Node 18+), so an upstream package release can never break it.

  • Promise-based, async/await API
  • No runtime dependencies
  • Ships hand-authored TypeScript declarations (index.d.ts) — no build step
  • Resource-grouped: client.chat, client.rag

The companion Python client lives in its own repository.

Installation

npm install praixis

Requires Node.js 18+. The package is ESM ("type": "module").

Authentication

Every request authenticates with your app API key, sent as the X-API-Key header. The server's admin panel (/api/system/*, HTTP Basic) is intentionally not exposed by this client — admin tasks belong in the browser UI, and embedding admin credentials in app code is an anti-pattern.

import { PraixisClient } from "praixis";

const client = new PraixisClient("http://localhost:8080", "your-api-key", {
  timeoutMs: 30000, // optional, default 30s
});

Chat

// Start a conversation
const reply = await client.chat.send("Hello, world!");
console.log(reply.session_id, reply.content);

// Continue it
await client.chat.send("And again?", { sessionId: reply.session_id });

// JSON-mode response, custom system prompt. `content` is still a string — the
// model's raw JSON text — which you parse yourself.
const r = await client.chat.send("List 3 colors", { responseFormat: "json", systemPrompt: "Be terse" });
const colors = JSON.parse(r.content);

// Sessions
await client.chat.listSessions();        // -> [sessionId, ...]
await client.chat.getHistory(sessionId); // -> { session_id, history: [...] }
await client.chat.clearHistory(sessionId);

// Per-session token usage. Counts the streamed answers (chat and RAG), RAG query
// reformulation, and compaction calls; counters expire with the session.
// estimated_context_tokens shows how close the session is to auto-compacting.
const usage = await client.chat.getUsage(sessionId);
// -> { session_id, requests, prompt_tokens, completion_tokens,
//      total_tokens, estimated_context_tokens }

// Compact a session on demand: fold older exchanges into an LLM-written summary
// (the server also does this automatically near its context budget). Rejects
// with an APIError of status 400 when there's nothing to fold yet.
await client.chat.compact(sessionId);
// -> { status, session_id, messages_before, messages_after,
//      estimated_tokens_before, estimated_tokens_after }

// Summarize an uploaded file ({ filename, content[, contentType] } or a File).
// Give the filename a .pdf/.docx/.txt extension — it's the primary format
// signal; contentType is only the fallback for extension-less names.
await client.chat.summarizeFile({ filename: "notes.txt", content: "raw text here" });

Streaming

The server's generative endpoints accept a stream toggle. The buffered methods (send, ask, summarizeFile, compare, summarizeDocument) send stream: false and return the server's native JSON — the right default for scripts and backends. The answer is always under content.

For token-by-token output, use the streaming variants, which return an async iterator of events. Marker events (session_id, search_query, sources, file, progress, error) arrive before the token events that carry content:

for await (const event of client.chat.stream("Tell me a story")) {
  if (event.type === "token") process.stdout.write(event.value);
  else if (event.type === "session_id") console.log("session:", event.value);
}

// Every buffered method has a streaming sibling:
//   client.rag.askStream(question, { collectionName })       -> session_id, search_query, sources, then tokens
//   client.chat.summarizeFileStream(file)                    -> file, [progress...], then tokens
//   client.rag.compareStream(coll, f1, f2)                   -> tokens
//   client.rag.summarizeDocumentStream(coll, filename)       -> file, then tokens

RAG

// Ingest one or many documents into a collection
await client.rag.upload({ filename: "manual.txt", content: "..." }, { collectionName: "docs" });
await client.rag.upload(
  [
    { filename: "a.txt", content: "..." },
    { filename: "b.txt", content: "..." },
  ],
  { collectionName: "docs" },
);

// Improved search: generate hypothetical questions in the background so plain,
// conversational queries match formal/technical text better. The document is
// searchable immediately; matching improves once generation finishes.
await client.rag.upload({ filename: "ley.pdf", content: "..." }, { collectionName: "docs", improvedSearch: true });

File inputs. Every upload method takes { filename, content, contentType? } or a File. The filename is required — the server uses it as the document's stored identity and as the primary format signal, so give it a .pdf/.docx/ .txt extension. When the extension is missing, the server falls back to the part's Content-Type (inferred from the extension when contentType is omitted), then to the file's magic bytes. A plain Blob is rejected because it has no name.

// Ask a question grounded in a collection
const ans = await client.rag.ask("What does the manual say about setup?", { collectionName: "docs" });
console.log(ans.content, ans.sources, ans.search_query);

// Restrict retrieval to one source document. Only the `source` key is honored;
// any other keys are ignored (not an error).
await client.rag.ask("What is the notice period?", {
  collectionName: "docs",
  metadataFilter: { source: "policy.pdf" },
});

// Retrieval only: ranked raw chunks, no LLM. Pass a standalone query (not reformulated).
const hits = await client.rag.search("setup steps", { collectionName: "docs" });
for (const r of hits.results) console.log(r.source, r.score, r.text); // hits.score_type: "rrf" | "similarity"

// Embeddings, listing, deletion, compare, summarize. compare/summarizeDocument
// return { ..., content } and accept an optional { responseFormat }.
await client.rag.embed("some text");
await client.rag.listCollections();
await client.rag.listFiles("docs");
await client.rag.deleteFile("docs", "a.txt");
await client.rag.deleteCollection("docs");
const cmp = await client.rag.compare("docs", "a.txt", "b.txt");   // { file_1, file_2, content }
const docSum = await client.rag.summarizeDocument("docs", "manual.txt"); // { filename, content }

Error handling

import { APIError, AuthenticationError, NotFoundError, RateLimitError, APIConnectionError } from "praixis";

try {
  await client.chat.send("hi");
} catch (err) {
  if (err instanceof AuthenticationError) { /* 401 / 403 */ }
  else if (err instanceof NotFoundError) { /* 404 */ }
  else if (err instanceof RateLimitError) { /* 429 */ }
  else if (err instanceof APIError) { console.log(err.statusCode, err.detail); }
  else if (err instanceof APIConnectionError) { /* never reached the server */ }
}

All errors inherit from PraixisError.

Testing

The suite runs against a standard-library mock HTTP server — no network, no dependencies:

npm test        # node --test

Privacy note

This client transmits whatever you pass to it (prompts, documents, session IDs) to the configured Praixis Engine server. Those payloads may contain personal data — handle them according to your own privacy obligations. The client stores nothing locally and adds no telemetry.

License

MIT — see LICENSE.