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

@caseywebb/elmq-linux-x64

v0.8.0

Published

elmq binary for Linux x64

Readme


update-when: CLI commands, output format, or installation steps change

elmq

A CLI for querying and editing Elm files — like jq for Elm.

Designed as a next-gen LSP for agents and scripts, not editors. Optimized for token efficiency and structured output.

Status: Active development. Supports reading and writing Elm declarations, imports, and module lines, plus project-wide operations (rename, move, extract, add/remove variant). See ROADMAP.md for what's planned.

[!TIP] Curious why elmq exists and what it's trying to prove? Read HYPOTHESIS.md for the claim, the experimental design, and the threats to validity.

Install

Homebrew

brew install caseyWebb/tap/elmq

npm

npm install -g @caseywebb/elmq

Or run without installing:

npx @caseywebb/elmq <command>

From source

Requires Rust:

cargo install --path .

Usage

Write-safety precondition

Every elmq command that mutates a .elm file — set decl, patch, rm decl, add/rm import, expose/unexpose, mv, rename decl, move-decl, add/rm variant, set let, set case, rm let, rm case, rm arg, rename let, rename arg, add arg — refuses to operate on a file that has pre-existing tree-sitter parse errors, and refuses to produce an output buffer that would not parse. If either check fires, elmq exits non-zero with a refusing to edit … or rejected '<op>' write to … message naming the file and the first error location, and the file on disk is left unchanged. Fix the file by hand (or with your editor) and retry. All write commands confirm success with ok output (sole exception: rm variant emits an actionable references_not_rewritten advisory section when the removed constructor appears in non-cleanly-rewritable positions). Read commands (list, get, grep, refs, variant cases) keep their existing tolerant behavior — they print a warning and continue so you can still inspect broken files.

File summary

elmq list src/Main.elm
module Main exposing (Model, Msg(..), update, view)  (38 lines)

imports:
  Html exposing (Html, div, text)
  Html.Attributes as Attr

type aliases:
  Model  L4-8

types:
  Msg  L11-15

functions:
  update  Msg -> Model -> Model  L18-28
  view    Model -> Html Msg      L31-34
  helper                         L37-38

With doc comments

elmq list src/Main.elm --docs
type aliases:
  Model  L4-8
    The model for our app

types:
  Msg  L11-15
    Messages for the update function
...

Extract a declaration

elmq get src/Main.elm update
update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }

        Reset ->
            { model | count = 0 }

Includes doc comments and type annotations when present. Returns non-zero exit code if the declaration is not found.

Read across multiple files in one call with -f:

elmq get -f src/Page/Home.elm update view -f src/Update.elm main

Each -f group is a file followed by one or more names. Output is framed as ## Module.decl blocks (falls back to ## file:decl without elm.json).

Upsert a declaration

echo 'helper x =
    x + 42' | elmq set decl src/Main.elm

Reads a full declaration from stdin, parses the name, and replaces the existing declaration (or appends if new). Use --content to pass the declaration inline instead of stdin:

elmq set decl src/Main.elm --content 'helper x = x + 1'

A name mismatch between the declaration source and the target is an error; use rename decl to rename instead of upsert.

Patch a declaration

elmq patch src/Main.elm update --old "model.count + 1" --new "model.count + 2"

Surgical find-and-replace scoped to a single declaration. The --old string must match exactly once.

Remove a declaration

elmq rm decl src/Main.elm helper

Removes the declaration, its type annotation, and doc comment. Cleans up excess blank lines.

Manage imports

elmq add import src/Main.elm "Browser exposing (element)"
elmq rm import src/Main.elm Html

add import inserts in alphabetical order or replaces an existing import with the same module name.

Manage exposing list

elmq expose src/Main.elm update
elmq expose src/Main.elm "Msg(..)"
elmq unexpose src/Main.elm helper

Granularly add or remove items from the module's exposing list. If the module has exposing (..), unexpose auto-expands to an explicit list then removes the target. expose is a no-op when exposing (..). Neither command ever produces exposing (..).

Rename/move a module

elmq mv src/Foo/Bar.elm src/Foo/Baz.elm
renamed src/Foo/Bar.elm -> src/Foo/Baz.elm
updated src/Main.elm
updated src/Page/Home.elm

Renames the file, updates the module declaration, and rewrites all imports and qualified references (Foo.Bar.something -> Foo.Baz.something) across the project. Requires elm.json in a parent directory. Use --dry-run to preview changes without writing.

Rename a declaration

elmq rename decl src/Main.elm helper newHelper
renamed helper -> newHelper
updated src/Main.elm
updated src/Page/Home.elm

Renames a declaration (function, type, type alias, port, or variant) in the defining file and updates all references across the project — including qualified (Module.helper), aliased (M.helper), and exposed references. Requires elm.json in a parent directory. Use --dry-run to preview changes without writing.

Find references

elmq refs src/Lib/Utils.elm
src/Main.elm:3
src/Page/Home.elm:5
src/Page/Settings.elm:3

Find all files that import a module. Add a declaration name to find specific usage sites:

elmq refs src/Lib/Utils.elm helper
src/Page/Home.elm:3: import Lib.Utils exposing (helper)
src/Page/Settings.elm:5: LU.helper config
src/Main.elm:7: Lib.Utils.helper model

Resolves fully-qualified references (Lib.Utils.helper), aliased references (LU.helper), and explicitly-exposed names. Requires elm.json in a parent directory.

Move declarations between modules

elmq move-decl src/Page/Home.elm --to src/Shared/Layout.elm viewHeader viewFooter
moved viewHeader
moved viewFooter
auto-included renderNav
updated src/Page/Home.elm
updated src/Shared/Layout.elm
updated src/Main.elm

Moves declarations from one module to another, rewriting the declaration bodies to match the target file's import conventions (aliases, exposed names). Automatically includes unexposed helpers used only by the moved declarations. Creates the target file if it doesn't exist. Auto-upgrades the target to a port module when moving ports.

Use --copy-shared-helpers to duplicate (not move) helpers that are used by both moved and non-moved declarations. Use --dry-run to preview changes.

Add/remove type variant constructors

elmq add variant src/Types.elm --type Msg "SetName String"
ok
  src/Update.elm:22  update      — inserted branch
  src/View.elm:15    label       — inserted branch
  src/Main.elm:31    update      — skipped (wildcard branch covers new variant)

Appends a constructor to a custom type and inserts Debug.todo branches in all matching case expressions project-wide. Case expressions with wildcard (_) branches are skipped with an info message.

To fill branch bodies in the same call, first survey the sites with elmq variant cases, then pass their keys to --fill:

elmq variant cases src/Types.elm --type Msg
## case sites for type Types.Msg (2 files, 2 functions)

### src/Update.elm

#### update (key: update, line 12)
update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment -> ...
        Decrement -> ...

### src/View.elm

#### label (key: label, line 8)
...
elmq add variant src/Types.elm --type Msg "Reset" \
  --fill 'update=Reset -> { model | count = 0 }' \
  --fill 'label=Reset -> "reset"'

variant cases is read-only and returns every case expression project-wide that matches the target type, with its enclosing function body (including type annotation) and a stable site key. Pass those keys to --fill <key>=<branch_text> (repeatable) on add variant to replace the default Debug.todo "<Variant>" stub with real branch bodies in the same call. Unmatched fill keys fail validation before any file is touched; unfilled sites fall back to Debug.todo stubs (graceful degradation).

When one function contains multiple case expressions on the same type, or two files both define a function with the same name, variant cases disambiguates with function#N or file:function keys. Passing an ambiguous bare key to --fill errors with the valid alternatives listed.

elmq rm variant src/Types.elm --type Msg Decrement
ok
  src/Update.elm:22  update  — removed branch
  src/View.elm:15    label   — removed branch

Removes a constructor and its matching branches from all case expressions — including nested patterns like Just Decrement -> .... Errors if removing the last variant (use elmq rm decl instead). Use --dry-run to preview changes.

When the constructor is also used outside case branches (expression position, refutable patterns in function/lambda/let arguments), rm variant emits a references_not_rewritten advisory section listing every such site with its file, line, enclosing declaration, and classification so the agent can fix them by hand before running elm make:

ok
  src/Update.elm:47  update  — removed branch

references_not_rewritten (2):
  src/Init.elm:15  init      expression-position
      init = ( Model 0, Cmd.map Wrap (Increment 1) )
  src/Debug.elm:8  debugMsg  expression-position
      debugMsg m = m == Increment 0
  run `elm make` to confirm and fix these before continuing

The advisory is the same data surfaced by elmq refs (see below), included in the rm output so the removal loop is one or two elmq touches at most — don't chain elmq refs into the rm flow.

Audit constructor references

The regular elmq refs command auto-routes on what the name is. For top-level declarations it emits a flat list of call sites; for a constructor of a custom type declared in the target file it emits a classified report:

elmq refs src/Types.elm Increment
Msg.Increment — 3 references (1 clean, 2 blocking)
src/Update.elm
    47  update       case-branch
        Increment ->
src/Init.elm
    15  init         expression-position
        init = ( Model 0, Cmd.map Wrap (Increment 1) )
src/Debug.elm
     8  debugMsg     expression-position
        debugMsg m = m == Increment 0

Walks the entire project and classifies every reference by its syntactic role: case-branch and case-wildcard-covered are "clean" (what rm variant would rewrite); function-arg-pattern, lambda-arg-pattern, let-binding-pattern, and expression-position are "blocking" (what rm variant would leave for the agent). Use it to audit whether a constructor is still needed, to plan a rename, or to understand the blast radius of a type change — independent of any removal flow. --format json emits total_sites, total_clean, total_blocking, and a flat sites array with file, line, column, declaration, kind, and snippet per entry.

Decl and constructor names can be mixed in a single elmq refs call; each is framed under its own ## <arg> header.

Search Elm sources

elmq grep "Http\.get"
src/Api.elm:42:fetchUser:    Http.get { url = userUrl, expect = Http.expectJson GotUser decoder }
src/Page/Home.elm:88:init:    Http.get { url = feedUrl, expect = Http.expectJson GotFeed feedDecoder }

Regex search over Elm files (Rust regex dialect, same as ripgrep) that annotates each hit with its enclosing top-level declaration — the discovery entry point that feeds into elmq get. Use -F for literal matching and -i for case-insensitive. Matches inside -- / {- -} comments and string literals are filtered by default; pass --include-comments or --include-strings to opt back in independently.

Project discovery walks up for elm.json and honors its source-directories; if no elm.json is found, falls back to recursively walking the CWD. Both paths honor .gitignore. Exit codes match ripgrep: 0 on matches, 1 on none, 2 on error.

Two additional flags enable one-call definition lookup and source retrieval:

  • --definitions — only emit matches at the declaration name site (filters out call sites)
  • --source — emit full declaration source for each matched decl, deduped by (file, decl). Output is framed ## Module.decl (single result stays bare).

Combine them for definition lookup: elmq grep --definitions --source 'update' returns the full source of the update declaration without any call-site noise.

Pipe into elmq get for a find-then-retrieve workflow:

elmq grep --format json "Http\.get" \
  | jq -r 'select(.decl) | "\(.file) \(.decl)"' \
  | sort -u \
  | while read file decl; do elmq get "$file" "$decl"; done

Matches that land outside any top-level declaration (imports, module header) report decl: null in JSON and an empty decl slot in compact output.

Agent integration guide

elmq guide

Prints the built-in agent integration guide to stdout. This is the guide that tells LLM coding agents how to use elmq effectively — when to prefer elmq get over Read, how to chain edits, etc. The Claude Code plugin (/plugin install elmq@caseyWebb) uses this automatically via a SessionStart hook.

JSON output

elmq list src/Main.elm --format json
{
  "module_line": "module Main exposing (Model, Msg(..), update, view)",
  "imports": ["Html exposing (Html, div, text)"],
  "declarations": [
    {
      "name": "update",
      "kind": "function",
      "type_annotation": "Msg -> Model -> Model",
      "start_line": 18,
      "end_line": 28
    }
  ]
}

Using elmq with LLM coding agents

elmq is designed to be used from any coding agent that can shell out to a CLI (Claude Code, Cursor, Aider, Codex, etc.). The built-in agent guide (elmq guide) tells agents how to use elmq effectively.

Claude Code: Install the plugin with /plugin install elmq@caseyWebb. It automatically injects the guide into sessions working in Elm projects.

Other agents: Pipe elmq guide into your agent's system prompt or project instructions.

Roadmap

See ROADMAP.md for the phased development plan.