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

@omelhorsite/cli

v0.5.0

Published

oms - command line client for omelhorsite, built on @omelhorsite/sdk.

Readme

oms

oms is the command line client for omelhorsite, built on @omelhorsite/sdk. It adds exactly what an isolate cannot have: the filesystem, the environment, stdout and a browser. Everything talks to https://backend.omelhorsite.pt.

  • A shell, or an agent that has one. Use oms. It has a stable exit code contract, prints JSON under a pipe without being asked, and describes itself with oms manifest --json.
  • Code that has to run somewhere else - a Worker, another app, a job. Use the SDK directly. It needs no filesystem and no environment.
  • An agent with no shell. Use the MCP server, after reading its security note. It is not a sandbox.

Install

Node 20 or newer, nothing else.

Debian and Ubuntu, from the signed apt repository:

sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL https://afonsopc.github.io/oms-packages/apt/oms.gpg -o /etc/apt/keyrings/oms.gpg
echo "deb [signed-by=/etc/apt/keyrings/oms.gpg] https://afonsopc.github.io/oms-packages/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/oms.list
sudo apt-get update && sudo apt-get install -y oms

Homebrew, on macOS or Linux:

brew tap afonsopc/oms https://github.com/afonsopc/homebrew-oms
brew trust afonsopc/oms
brew install oms

brew trust is required: Homebrew will not load a formula from a third-party tap without it.

From npm:

npm install -g @omelhorsite/cli
oms --help

Zero-install: npx @omelhorsite/cli auth login (or bunx @omelhorsite/cli).

The packaged builds are one JavaScript file with the SDK bundled in, so node is the only thing they need. Debian 12 ships node 18, which is too old; Debian 13 and Ubuntu 24.04 are fine. The packaging lives in oms-packages.

A single-file binary with no runtime at all on the target machine comes out of bun run build:binary on a checkout (dist/oms, about 60 MB).

The authority on what a given build can do is the build itself:

oms manifest --json      # every command and flag, as data
oms <command> --help     # one command, with its examples and limits

Authenticate

oms auth login runs the OAuth device grant (RFC 8628). It prints a URL and a code to stderr before it tries to open a browser, so it works over SSH, and your password never enters this process.

oms auth login                                       # all scopes, default profile
oms auth login --profile work
oms auth login --scope 'openid storage:read'         # a token meant for one job

Credentials land in ~/.config/oms/config.json ($OMS_CONFIG_DIR and $XDG_CONFIG_HOME win in that order) at mode 0600, per named profile. Access tokens live two hours and refresh themselves; the refresh token in that file is what actually holds the grant.

For CI, containers and the MCP server there is no browser, so pass the token in the environment instead. Nothing in this CLI ever prompts without a terminal.

| Variable | Meaning | | --- | --- | | OMS_TOKEN | The credential for this run. Never written to the config file. | | OMS_BASE_URL | API root. OMS_API_URL is an accepted alias. | | OMS_PROFILE | Which profile to use. | | OMS_CONFIG_DIR | Directory holding config.json. |

Precedence is flag, then environment, then the config file, then the built-in default, resolved independently per axis.

An access token from oms auth login is the credential to use: whoami, quota, tools, storage and tickets all accept it, bounded by the scopes you were granted. The parts of the API that no scope reaches yet are listed under Security.

| Command | | | --- | --- | | oms auth login | Sign in through the browser and store the credentials. | | oms auth logout | Revoke the stored credentials and forget them. | | oms auth whoami | Who the credential belongs to. One request. | | oms auth status | What this machine holds, and until when. No network call. | | oms auth token | Print the access token on stdout, refreshing first if needed. | | oms auth profiles | List the profiles, or switch with --use. |

Commands

oms manifest --json is this table as data, generated from the same declarations that produce --help. Prefer it over reading this file.

oms quota - every ceiling, in one request

| Command | | | --- | --- | | quota [resource] | Every quota on the account: the metered tools, the storage item count and the music bytes. |

One request, against GET /quotas. tools quota is still there and still reads the per-tool endpoints; use it when the credential carries tools:read but not profile.

oms tools - the metered media tools

| Command | | | --- | --- | | tools transcribe <file> | Transcribe audio to text, with SRT and VTT output. | | tools separate <file> | Split a track into vocals and instrumental. | | tools upscale <file> | Enlarge an image without turning it to mush. | | tools bg-remove <file> | Cut the subject out of an image. | | tools captions <file> | Burn karaoke captions into a video. | | tools jumpstyle <file> | Cut clips to the beat of a track. | | tools download <url> | Fetch media from a public URL, or find it by artist and title. | | tools status <tool> <id> | Poll a run started with --no-wait and write its result. | | tools quota [tool] | What is left of today's quotas. | | tools passgen | Generate a password or passphrase. Never leaves the machine. | | tools qr <text> | Render a QR code as SVG. Never leaves the machine. | | tools iplookup <ip> | Country, ASN and network of an IP address. |

Every tool that uploads waits for its result by default; --no-wait prints the id and exits, and tools status picks it up later. Quotas are daily, counted per user when signed in and per IP when not.

oms storage - the virtual filesystem

| Command | | | --- | --- | | storage ls [path] | List a directory. | | storage stat <path> | One node's metadata and where it sits. | | storage get <path> | Download a file. -o - streams to stdout. | | storage put <files...> | Upload into a directory. Multipart above 32 MiB, automatically. | | storage mkdir <paths...> | Create directories, nested levels included. | | storage mv <from> <to> | Move a node, rename it, or both. | | storage cp <from...> <to> | Copy nodes into another directory. | | storage trash <paths...> | Move to the trash. Reversible. | | storage rm <paths...> | Delete permanently. --yes required, --recursive for a directory. | | storage empty-trash | Destroy the trash and reclaim the quota. | | storage zip <path> | Download a directory as a zip. | | storage share <path> | Share a node, list its grants, or revoke one. |

Paths are a convenience of this CLI: the backend addresses nodes by id and has no path column, so every path costs one request per segment. Pass id:<id> when a script already knows the id.

oms tickets - support

| Command | | | --- | --- | | tickets list | Your tickets, most recently active first. | | tickets show <id> | One ticket and its whole thread. | | tickets new --subject ... | Open a ticket, screenshots included. | | tickets reply <id> | Add a message. Reads stdin when --message is omitted. | | tickets close / reopen <id> | The only fields an owner may change. |

oms links - everything that ends in a shareable URL

| Group | Leaves | | --- | --- | | links shortlink | create ls rm stats | | links notepad | get set | | links qr-dynamic | create ls set rm stats | | links chest | open ls add get rm destroy | | links form | ls show create publish submissions rm | | links linktree | ls show create cv stats rm |

Creating any of these is throttled per IP and signing in does not raise the ceiling. None is retried automatically: a replayed create would mint a second one under a different endpoint.

oms cron - scheduled scripts

A job is a TypeScript script the server runs on a cron schedule with the SDK built in. The commands edit one field of a job at a time, so changing the code does not touch the config and changing a secret does not touch the code.

oms cron templates list                                  # ready-made scripts
oms cron jobs create --name "Backup" --template hello --set greeting=Olá
oms cron jobs show <id>                                  # schedule, config, variables, state
oms cron jobs show <id> --code > job.ts                  # edit it...
oms cron jobs update <id> --code job.ts                  # ...and send it back
oms cron jobs update <id> --set max_candidates=60 --set 'ignore=["recon.pt"]'
oms cron jobs update <id> --secret TELEGRAM_TOKEN=123:abc --network true
oms cron jobs update <id> --enabled false
oms cron jobs run <id> --wait                            # run now, print the logs
oms cron jobs test <id> --wait                           # same, without saving state or notifying
oms cron runs list --job <id>
oms cron runs show <run-id> --logs

--set merges one key into the current config and parses the value as JSON when it is JSON (--set on=true, --set 'terms=["a","b"]', --set models.report=x for a nested key); --config file.json replaces the whole thing. run --wait exits 1 when the run ends in error, so a script can chain on it. The account needs the cron:read and cron:write scopes; a login from before they existed has to sign in again.

The contract a script can rely on

  1. stdout is the result and nothing else. Progress, warnings and errors go to stderr, in every format.
  2. JSON is automatic when stdout is not a terminal. --json forces it on, --no-json off. A pipe needs no flag. (oms auth token is the deliberate exception: it prints the bare token, because $(oms auth token) is not a TTY either.)
  3. Nothing prompts without a terminal. Destructive commands refuse instead of defaulting.
  4. Exit codes are stable.

| | | | | | --- | --- | --- | --- | | 0 ok | 3 auth | 6 timeout | 9 unreachable | | 1 other failure | 4 quota or rate limit | 7 not found | 10 server broke | | 2 usage | 5 the job ran and failed | 8 API refused | 11 local problem |

130 is Ctrl-C. Everything above 6 is a narrower reading of what would otherwise be a 1; a script that only knows 0 to 6 still behaves correctly.

Security

Read this before handing a token to anything that is not you.

What a CLI token permits. oms auth login with no --scope asks for every scope the server defines: openid profile email tools:read tools:write storage:read storage:write tickets:write. A token holding those can read, write, move and permanently delete your entire storage tree, empty your trash, spend every daily tool quota, and open tickets in your name. Narrow it with --scope when the token is for one job; the approval page shows what is being asked before you consent.

What it does not permit. No scope grants administrative access. Scope enforcement is default-deny: an OAuth token reaching a controller that has not declared a scope for the action is refused, so opening a new area of the API to the SDK is a deliberate edit rather than a side effect of shipping an endpoint. Session and credential management - /sessions, /identities, /webauthn_credentials, /auth/* - is closed to OAuth tokens entirely.

Status. Both halves are live. The server resolves an OAuth access token on every request that carries one (a browser session, when both are present, wins), and every endpoint an access token may reach declares the scope it needs; a token arriving anywhere else, or with the wrong scope, gets 403 insufficient_scope naming the scope required and the scopes granted. That default-deny rule is enforced and tested, so scopes are a control, not a design.

What no scope reaches yet, and therefore what oms cannot do with an access token: everything under oms links (short links, notepads, dynamic QR codes, chests, forms, link trees), plus music, movies, the library, blogs, the assistant, direct messages, anything administrative, and the account's own session and application management. Those commands answer 403 with an OAuth token; a session token still works there, with the warning below.

A legacy session token is not the same thing. OMS_TOKEN also accepts the opaque UUID the web app holds. It carries full account authority, no scopes and no expiry, and the two are indistinguishable on the wire. Never give one to an agent, to CI, or to anything you would not hand your password.

Where the credential sits. ~/.config/oms/config.json, mode 0600, plaintext. It holds a refresh token, which is the grant itself and not just a copy of it. There is no OS keychain integration. Anything that can read that file, or run oms auth token, has your account for as long as the grant lives.

Revocation, and its asymmetry. oms auth logout revokes server-side, so it kills the whole grant and not only this machine's copy. Changing your password revokes everything, sessions and grants alike. Signing out of the browser revokes nothing here - that is the point of having two models.

Guards are against accidents, not intent. --yes on storage rm and empty-trash stops a script that forgot to think. It does not stop an agent that decided to pass it.

What the MCP server does not guarantee: a sandbox. execute runs the model's TypeScript in a bun subprocess on your machine, as you, with your credential in its environment. It can read your files, open sockets and await import("node:fs"). The subprocess bounds accidents - a runaway loop, a hang, output that would fill memory - and nothing else. The trust model is exactly that of giving the agent shell access. The full accounting, including where a real isolate would go, is in the MCP server's README.

Found a hole? oms tickets new --subject ..., or the repo's usual channel.

Working on it

bun install
bun run src/main.ts --help    # run it in place
bun link                      # then `oms` anywhere on this machine
bun run typecheck
bun test
bun run build                 # dist/oms.js, what npm ships; runs on node

src/host.ts is the only module that touches the filesystem, stdin, PATH or a subprocess, and it uses node builtins only: the same code runs under node (the published package) and under bun (the checkout and the tests). Adding a command is one file under src/commands/ and one line in src/commands/index.ts; the help and the manifest are generated from the same argument declarations, so a flag is written down once.