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

claude-sort

v0.1.1

Published

Sorts an array by asking Claude Opus to do it. A rigorously engineered joke.

Readme

claude-sort

Sorts an array by asking Claude Opus to do it.

import { claudeSort } from "claude-sort";

const result = await claudeSort([3, 1, 2]);
// { outcome: "sorted", items: [1, 2, 3], attempts: 1, usage: { inputTokens: 212, outputTokens: 51 } }

Array.prototype.sort does this in about 40 nanoseconds and no dollars. This package does it in about four seconds and a fraction of a cent, over the network, with retries. It is a joke about how much of that trade we have been making lately, and it is built like it is not one: strict TypeScript, a pure core, schema-validated I/O, assertions on both sides of every boundary, and 73 tests that never touch the network.

Install

npm install claude-sort

Credentials resolve the way the Anthropic SDK resolves them: ANTHROPIC_API_KEY, then ANTHROPIC_AUTH_TOKEN, then an ant auth login profile. Pass apiKey to override that for one call.

Usage

Every ending is a named outcome, so nothing has to be caught for a sort that simply did not happen:

import { claudeSort } from "claude-sort";

const result = await claudeSort(["Neptune", "Mercury", "Earth", "Jupiter"], {
  criterion: "distance from the Sun, nearest first",
});

switch (result.outcome) {
  case "sorted":
    console.log(result.items); // ["Mercury", "Earth", "Jupiter", "Neptune"]
    break;
  case "limitExceeded":
    console.log(`too many: ${result.limit} was ${result.actual}`);
    break;
  case "unsorted":
    console.log(`no valid ordering in ${result.attempts} attempts`);
    break;
  case "refused":
    console.log(result.detail);
    break;
  case "apiError":
    console.log(`api error ${result.httpStatus}`);
    break;
}

The comparator is a sentence, which is the one thing this does that Array.prototype.sort cannot:

await claudeSort(bugReports, { criterion: "how angry the reporter sounds" });
await claudeSort(names, { criterion: "Vietnamese alphabetical order" });
await claudeSort(commits, { criterion: "most likely to have caused the outage" });

Objects are labelled before they are sent, and the sorted array holds the original references — the model never sees or returns your objects, only their labels and a permutation of their indices:

const albums = [
  { title: "Kid A", year: 2000 },
  { title: "The Bends", year: 1995 },
];

const result = await claudeSort(albums, {
  criterion: "release year, oldest first",
  describeItem: (album) => `${album.title} (${album.year})`,
});
// result.items[0] === albums[1], by reference

Options

| Option | Default | What it does | | -------------- | ------------------------------------------------------------ | ----------------------------------------------- | | criterion | ascending, numbers numerically and strings lexicographically | The comparator, in English | | model | claude-opus-5 | Any model id the Messages API accepts | | effort | low | low through max. Sorting is shallow work | | describeItem | JSON-ish labels | Renders one item as the label the model sees | | attemptsMax | 3 | Total tries. A non-permutation answer costs one | | timeoutMs | 120000 | Deadline for a single request | | apiKey | from the environment | Overrides credential resolution | | client | built per call | Supplies your own Anthropic client |

Limits

  • 100 items per call. A longer list is yours to page. It is not truncated.
  • 512 characters per label. One pathological item cannot fill the context window on everything else's behalf.
  • The returned array is frozen. Array.from(result.items) if you need to mutate it.

Both limits come back as { outcome: "limitExceeded", limit, actual, allowed } before anything is spent.

What it costs

result.usage reports the tokens each sort took, summed across attempts. At Opus 5 rates, sorting three integers costs somewhere around one fiftieth of a cent, which is roughly infinity times what .sort() costs. The comparison is the point of the package.

What it does not do

  • Guarantee an answer. After attemptsMax rejected orderings you get { outcome: "unsorted" }. The model is asked again each time with the reason its last answer was refused.
  • Guarantee a correct answer. Every result is a genuine permutation of your input — validated, never trusted — so nothing is dropped, duplicated, or invented. Whether that permutation is sorted is the model's opinion.
  • Sort stably. Ask for a tiebreaker in the criterion if you need one.
  • Log anything. No credential, item, or label is written anywhere.

A missing credential throws rather than returning an outcome: that is a configuration error, not a sort that failed.

Design

Functional core, imperative shell. describe, prompt, ordering, plan, config, and attempt are pure and hold every decision — including what to do with a rejected answer; anthropic/request.ts and sort.ts are the only code that touches a network, and sort.ts only sends, appends, and returns. The split is what lets the core be tested exhaustively with no key, no mocks, and no spy assertions — the tests assert on returned values, never on which calls were made.

The model answers with indices, never with your data. An answer is accepted only after validateOrdering proves it a permutation of the input's indices, so a hallucinated, dropped, or duplicated element is a rejected attempt rather than a corrupted array.

Read src/anthropic/AGENTS.md before changing anything that talks to the API.

Development

pnpm install
pnpm test        # 73 unit tests, no network
pnpm typecheck
pnpm lint
pnpm build
pnpm example     # sorts [3, 1, 2] for real; needs credentials

test/sort.integration.test.ts exercises the shell against the live Messages API and skips itself unless ANTHROPIC_API_KEY is set. It is the only coverage the wiring has, by choice: once the decisions were extracted into pure functions, what was left holds no decision worth asserting against a fake.

License

MIT