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

ailogic

v0.1.1

Published

Dynamically test whether any value evaluates to truthy or falsy by asking a configured AI model.

Readme

ailogic

Test whether a value is truthy or falsy by asking an AI model at runtime.

import { configure, isTruthy } from 'ailogic';

configure({ provider: 'claude-code' }); // uses your Claude.ai login, no API key

await isTruthy('yes, ship it'); // true
await isTruthy(''); // false
await isTruthy('0'); // true  — a non-empty string
await isTruthy([]); // true  — an object
await isTruthy(Number.NaN); // false

The default question is JavaScript's own rule: is Boolean(value) true? Exactly eight values are falsy — false, 0, -0, 0n, "", null, undefined, NaN — and everything else, including "0", "false", [] and {}, is truthy.

Supply a question and the model answers that instead, which is where the runtime part earns its keep:

await isTruthy('[email protected]', { question: 'Is this a valid email address?' });
await isTruthy(order, { question: 'Has this order been fully delivered?' });
await isTruthy(message, { question: 'Is this message spam?' });

Install

yarn add ailogic     # or: npm install ailogic

Node 18+ (needs a global fetch). Ships CommonJS, ES modules and TypeScript declarations; browser bundles work for every provider except claude-code, which spawns a local process.


Two ways to authenticate

1. Your Claude.ai subscription — no API key

The claude-code provider shells out to the Claude Code CLI in headless mode, so it reuses whatever claude auth login already stored. Nothing to configure, nothing to bill.

configure({
  provider: 'claude-code',
  model: 'claude-haiku-4-5', // cheaper than the claude-opus-5 default
  providerOptions: { forceSubscription: true }, // ignore any ANTHROPIC_API_KEY
});

Each call spawns a process, so expect a few seconds of startup per evaluation. Ideal for tests, scripts and local development; use an API-key provider for request-path work.

2. An API key

configure({ provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY });

Or set nothing at all: with no provider, ailogic picks the first backend whose API-key environment variable is populated (Anthropic → OpenAI → Google → …). The claude-code provider is never auto-selected, because it starts a process.


Supported providers

| Name | Aliases | Key from | Default model | | -------------- | ------------------------------------------------ | ---------------------------------- | ----------------------------------------- | | anthropic | claude, claude-api | ANTHROPIC_API_KEY | claude-opus-5 | | claude-code | claude.ai, claude-cli, claude-subscription | — (local login) | claude-opus-5 | | openai | | OPENAI_API_KEY | gpt-4.1-mini | | azure-openai | azure | AZURE_OPENAI_API_KEY | your deployment name | | google | gemini | GOOGLE_API_KEY, GEMINI_API_KEY | gemini-2.5-flash | | xai | grok | XAI_API_KEY | grok-4-fast | | mistral | | MISTRAL_API_KEY | mistral-small-latest | | deepseek | | DEEPSEEK_API_KEY | deepseek-chat | | groq | | GROQ_API_KEY | llama-3.3-70b-versatile | | cohere | | COHERE_API_KEY | command-a-03-2025 | | openrouter | | OPENROUTER_API_KEY | openai/gpt-4.1-mini | | together | | TOGETHER_API_KEY | meta-llama/Llama-3.3-70B-Instruct-Turbo | | fireworks | | FIREWORKS_API_KEY | llama-v3p3-70b-instruct | | perplexity | | PERPLEXITY_API_KEY | sonar | | ollama | local | — (local server) | llama3.1 | | lmstudio | | — (local server) | local-model |

listProviders() returns the live list. Anything else that speaks the OpenAI Chat Completions shape takes one call to add — see Custom providers.

Choosing a model

Defaults favour capability, not price. A truthiness check is a small task, so for volume, override the model:

configure({ provider: 'anthropic', model: 'claude-haiku-4-5' });
configure({ provider: 'openai', model: 'gpt-4.1-mini' });
configure({ provider: 'ollama', model: 'llama3.1' }); // free, fully local

API

Every function exists both as a module-level export (backed by the process-wide config) and as a method on a createClient() instance.

| Function | Returns | Notes | | ------------------------------- | ----------------------- | -------------------------------------------- | | isTruthy(value, options?) | Promise<boolean> | | | isFalsy(value, options?) | Promise<boolean> | | | evaluate(value, options?) | Promise<Evaluation> | verdict, confidence, reason, usage, timing | | assertTruthy(value, options?) | Promise<Evaluation> | throws AssertionError when falsy | | assertFalsy(value, options?) | Promise<Evaluation> | throws AssertionError when truthy | | evaluateAll(values, options?) | Promise<Evaluation[]> | bounded concurrency, input order preserved | | configure(patch) | config | merges; also getConfig(), resetConfig() | | createClient(config?) | AiLogicClient | isolated config and cache | | clearCache() | void | | | registerProvider(adapter) | void | also listProviders(), getProvider() |

Evaluation

{
  truthy: boolean;        // the answer
  verdict: 'truthy' | 'falsy' | 'unknown';
  confidence: number;     // 0..1, self-reported
  reason: string;         // one sentence from the model
  question: string;       // what it was actually asked
  provider: string;
  model: string;
  cached: boolean;
  durationMs: number;
  raw: string;            // unparsed model output
  usage?: { inputTokens?, outputTokens?, totalTokens?, costUsd? };
}

Options

Everything below works per call, on a client, or in configure().

| Option | Default | Meaning | | ----------------- | ---------------- | --------------------------------------------------------- | | question | Boolean(value) | The yes/no question to answer about the value | | context | — | Extra domain rules appended to the prompt | | provider | auto-detected | Provider name or alias | | model | provider default | | | apiKey | from environment | | | baseUrl | provider default | Proxies, gateways, self-hosted endpoints | | headers | {} | Extra HTTP headers | | temperature | 0 | Not sent to Anthropic, which rejects it on current models | | maxTokens | 512 | | | timeoutMs | 60000 | Per attempt | | retries | 2 | Transport errors and 429/5xx, with jittered backoff | | cache | true | In-process LRU | | cacheTtlMs | 300000 | | | maxValueLength | 2000 | Characters of the serialized value sent to the model | | minConfidence | 0 | Below this, a verdict is downgraded to unknown | | onUnknown | 'throw' | Or 'truthy' / 'falsy' to coerce instead | | signal | — | AbortSignal for cancellation | | providerOptions | {} | Merged into the provider request body |

Handling "I can't tell"

The model may answer unknown. By default that throws IndeterminateError; pick a side instead if your call site needs a boolean no matter what:

await isTruthy(mystery, { onUnknown: 'falsy' });

// Or demand certainty:
await isTruthy(value, { minConfidence: 0.9, onUnknown: 'falsy' });

Errors

All extend AiLogicError and carry a stable .code.

| Class | .code | When | | -------------------- | ----------------------- | ---------------------------------------------- | | ConfigurationError | AILOGIC_CONFIGURATION | No provider, no API key, unknown provider name | | ProviderError | AILOGIC_PROVIDER | Non-2xx, refusal, malformed provider response | | TimeoutError | AILOGIC_TIMEOUT | timeoutMs elapsed, or the caller aborted | | ParseError | AILOGIC_PARSE | The model answered unreadably (.raw has it) | | IndeterminateError | AILOGIC_INDETERMINATE | Verdict unknown under onUnknown: 'throw' | | AssertionError | AILOGIC_ASSERTION | assertTruthy / assertFalsy failed |


Isolated clients

configure() is process-wide. Inside a library, or when talking to two providers at once, use a client instead — it owns its own config and cache:

import { createClient } from 'ailogic';

const fast = createClient({ provider: 'groq', model: 'llama-3.3-70b-versatile' });
const careful = createClient({ provider: 'anthropic', model: 'claude-opus-5' });

const quick = await fast.isTruthy(value);
if (!quick) await careful.assertTruthy(value);

Custom providers

An OpenAI-compatible endpoint needs only a preset:

import { createOpenAiCompatibleProvider, registerProvider } from 'ailogic';

registerProvider(
  createOpenAiCompatibleProvider({
    name: 'my-gateway',
    label: 'Internal Gateway',
    defaultModel: 'internal-small',
    defaultBaseUrl: 'https://llm.internal.example/v1',
    apiKeyEnv: ['INTERNAL_LLM_TOKEN'],
    structuredOutput: 'json_object', // or 'json_schema' | 'none'
  }),
);

Anything else implements complete(request):

registerProvider({
  name: 'my-backend',
  label: 'My Backend',
  defaultModel: 'v1',
  apiKeyEnv: ['MY_TOKEN'],
  requiresApiKey: true,
  async complete(request) {
    // request: { system, prompt, model, apiKey, baseUrl, schema, timeoutMs, ... }
    return { text: '{"verdict":"truthy","confidence":1,"reason":"…"}' };
  },
});

The same interface makes an excellent test double — register a deterministic provider and your suite never touches the network. See examples/custom-provider.mjs.


Behaviour worth knowing

Caching. Identical (provider, model, base URL, prompt, temperature, maxTokens) evaluations are served from a bounded in-process LRU for cacheTtlMs. It is per process and never persisted. Disable with cache: false.

Value serialization. Values are rendered with their runtime type, bounded depth and size, cycles collapsed to [Circular], and hard-capped at maxValueLength. A throwing getter degrades to a note, never an exception.

Prompt injection. The value is always fenced and the system prompt tells the model to treat it as inert data. That raises the bar; it does not make hostile input safe to act on unreviewed. Do not wire assertTruthy straight into an authorization decision over attacker-controlled text.

Determinism. temperature: 0 and a schema-constrained response make results stable in practice, not guaranteed. Treat a verdict as a judgement, not a proof — including for the default question, where a plain Boolean(value) is exact, free and instant. Reach for ailogic when you want the question to be dynamic.

Structured output. Where a provider supports JSON Schema responses it is used; where it only supports "some JSON" that is used; otherwise the parser falls back to extracting JSON from prose, and finally to a keyword scan.


Development

yarn install --frozen-lockfile
yarn run lint        # eslint + prettier, must pass before staging
yarn run test        # jest
yarn run typecheck   # tsc --noEmit
yarn run build       # dist/cjs, dist/esm, dist/types

Sources are TypeScript, transpiled by Babel against the browserslist in package.json, with polyfills pulled from @babel/runtime-corejs3 rather than installed on globals. Tests are plain .test.js next to the code they cover. The repository's working rules live in AGENTS.md.

Releases

Pushing code to main publishes to npm automatically (.github/workflows/release.yml): resolve version → lint → typecheck → test → build → publish → tag.

Version numbers are split between a human and CI:

  • major.minor is yours. It lives in the version field of package.json and only changes when you change it.
  • The revision is CI's. Each release takes one past the highest major.minor.<n> the registry has ever seen, starting at 0 for a new series. Gaps are skipped rather than reused, so the number only counts up. Bump the minor and the next release starts again at .0.

So the revision committed in package.json is a placeholder — 0.4.99 in the repo still publishes 0.4.0 if no 0.4.x exists yet. Nothing is committed back to main; each release is recorded as a v<version> tag and GitHub Release.

Publishing needs the NPM_TOKEN repository secret. It must be a token that can publish while two-factor auth is enforced — a Granular Access Token with Bypass two-factor authentication enabled, or a classic Automation token. Docs, examples and devcontainer changes do not trigger a release, because they cannot change the published tarball.

License

MIT