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

@artstorefronts/cli

v0.2.0

Published

ASF platform CLI — wraps the ASF GraphQL/REST API for scriptable site setup and admin automation.

Readme

@asf/cli

A first-class TypeScript CLI that wraps the Art Storefronts GraphQL/REST API so site owners, support, and admin agents can drive a customer site from the shell.

The CLI is a thin shim over the live API — no business logic lives here. Every command names the GraphQL field or REST endpoint it wraps in its --help output so you can cross-reference the GraphQL runbook when you need to know exactly what's being sent.

Status

v0.2 — browser-consent OAuth + explicit --host. First-party Authorization Code + PKCE flow against the doorkeeper provider that lands on icono_pregame wss/graphql-api-unlocks. Access + refresh tokens stored in the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service). Environment variables (ASF_TOKEN, ASF_BASE_URL, ASF_ENV, ASF_EMAIL) and the --env flag are gone — every command takes --host instead (default artstorefronts.com).

v0.1 shipped the verb-per-mutation surface against the legacy email/password JWT path.

Install

git clone [email protected]:Art-Storefronts/artstorefronts-cli.git
cd artstorefronts-cli
nvm use            # picks up .nvmrc → Node 20.x
npm install
node ./bin/asf.js --help

To put asf on your $PATH:

npm link
asf --help

The bin shim (bin/asf.js) runs sources via ts-node (transpileOnly: true) — no compile step is needed for development. Run npm run typecheck for tsc --noEmit, npm run lint for eslint, npm test for the vitest suite.

Auth

asf auth login                                       # → artstorefronts.com (production)
asf auth login --host staging.artstorefronts.com     # staging
asf auth login --host app.lvh.me:3000                # local dev
asf auth login --scope read_access                   # ask for read-only

asf auth login opens the system browser to https://{host}/oauth/authorize. You complete the consent screen with your existing Art Storefronts site_manager credentials; the CLI never sees your password. The resulting access + refresh tokens land in the OS keychain.

| Command | Effect | |---|---| | asf auth login | Authorization Code + PKCE; saves tokens to OS keychain. | | asf auth logout | Calls POST /oauth/revoke, then clears the local keychain entry. | | asf whoami | GET /api/v1/users/me with the cached token. Add --json for machine output. |

Token refresh is automatic: on a 401 the client exchanges the cached refresh_token for a new access token and retries the request once. If the refresh fails, the entry is wiped so the next command guides you back to asf auth login.

Headless workstations (no browser): pass --no-open-browser to asf auth login and the CLI prints the authorization URL for you to open from another machine. Long-lived headless CI without an interactive workstation will land on the device-authorization-grant flow in v0.3.

Host selection

Every command takes --host <hostname> (default artstorefronts.com). No environment variables, no --env flag.

| --host | Resolves to | |---|---| | (omitted) | https://artstorefronts.com | | staging.artstorefronts.com | https://staging.artstorefronts.com | | app.lvh.me:3000 | http://app.lvh.me:3000 (HTTP for local dev hosts) | | https://my.example.com | honored verbatim (overrides scheme inference) |

Local-dev shorthand applies HTTP automatically to any host whose authority includes lvh.me, localhost, or 127.0.0.1. Everything else gets HTTPS. Pass a full URL when you need to override that inference.

Rack::Attack note. The Art Storefronts stack 403s requests that arrive with curl's / undici's default User-Agent. The CLI sends a benign Mozilla/5.0 (compatible; asf-cli/...; ...) UA on every request so this never bites you. If you write a custom integration, do the same.

Verb surface

asf auth         login | logout | whoami
asf websites     list | update | update-subscribe-bar | update-announcement-bar
asf stores       list | update-markups | update-default-sizes | update-medium-availabilities
asf pages        list | update | delete
asf products     create | bulk-create | update | add-photo | remove-photo | delete | bulk-delete
asf galleries    create | add-products | remove-products | set-cover | reorder
asf contacts     create | bulk-create | delete | bulk-delete
asf shipping     update-datum | create-method | update-method | delete-method
asf assets       upload

Run asf <verb> --help for examples and the exact GraphQL field each verb wraps.

Cascade-safe deletes

pages delete, products delete, and the bulk-delete variants cascade server-side via PageDeleter.purge / ProductDeleter. For pages delete against a store or blog page, the server refuses without --cascade-confirmation because the cascade also destroys categories, products, and ES indexes. Non-destructive alternative for a store page: asf pages update --id <page> --no-online hides it without destroying the catalog.

The GraphQL field is deleteProduct but the input/payload types are DeleteCoreProductInput / DeleteCoreProductPayload — the "Core" prefix disambiguates from the older deleteSignatureProfileProduct. You won't normally see these names from the CLI; flagged here so the runbook reference makes sense if you ever inspect the wire.

Smoke test against the local dev stack

The canonical smoke target is filip's regression-testing website (id 3268, owner [email protected]) on the icono_pregame local dev stack. With OAuth landed, you just point at the local host and let the browser carry you through consent:

asf auth login --host app.lvh.me:3000     # browser → consent → tokens
asf whoami    --host app.lvh.me:3000
asf products create --host app.lvh.me:3000 \
  --website-id 3268 --store-id 7547 \
  --name AGENT_SMOKE_AP --product-type ART_PRINT \
  --image https://res.cloudinary.com/decosites/image/upload/v1426724962/image_quality_auditor_test_sml_el2jaa.jpg

For reference / curl-level work, the canonical smoke target story is still:

Bring the stack up via the workspace's icono-pregame-start-environment skill, then mint a JWT:

docker compose -f icono_pregame_environment/docker-compose.yml exec -T icono-pregame-agent bash -lc \
  'docker exec -i icono_pregame-web-1 bundle exec rails runner "
    website = Website.find(3268)
    owner   = website.owner
    exp     = ::Api::V1::Auth::TokenGeneration::JwtTokens.build_access_expires_at_from_now.to_i
    payload = ::Api::V1::Auth::TokenGeneration::Jwt::Payload.new(
      email: owner.email, website_id: website.id, user_id: owner.id, exp: exp
    )
    puts \"JWT=#{::Api::V1::Auth::TokenGeneration::JwtTokens.encode(payload: payload).success_or_raise!}\"
  "' 2>&1 | grep '^JWT='

Smoke flow:

export ASF_TOKEN="<minted JWT>"
export ASF_BASE_URL="http://app.lvh.me:3000"

asf whoami                                                              # → website_id: 3268
asf websites list --query filip-cloudinary-regression-testing            # discover store/page ids
asf products create --website-id 3268 --store-id <APS> \
  --name AGENT_SMOKE_AP --product-type ART_PRINT \
  --image https://res.cloudinary.com/decosites/image/upload/v1426724962/image_quality_auditor_test_sml_el2jaa.jpg
asf products create --website-id 3268 --store-id <SS> \
  --name AGENT_SMOKE_STD --product-type STANDARD --quantity 5 \
  --image https://res.cloudinary.com/decosites/image/upload/v1426724962/image_quality_auditor_test_sml_el2jaa.jpg
asf galleries create --website-id 3268 --name "Smoke Gallery"
asf galleries add-products --page-id <gal> --product-ids <p1>,<p2>
asf products delete --id <p1>
asf pages delete --id <gal>

Full smoke evidence for each release lives under artifacts/asf-cli/<release>/smoke/ in the artstore-agent workspace.

Architecture

artstorefronts-cli/
  bin/asf.js              # Node shim — loads ts-node + dispatches to src/cli.ts
  src/
    cli.ts                # clipanion Cli + command registration
    base.ts               # BaseCommand: --env flag + buildClient()
    utils/
      env.ts              # AsfEnv + base URL resolution
      config.ts           # ~/.config/asf/credentials.json read/write
      api.ts              # fetch wrapper: REST + GraphQL with JWT + Mozilla UA
    commands/
      auth/{login,whoami,logout}.ts
      assets/upload.ts
      websites/{list,update,update-subscribe-bar,update-announcement-bar}.ts
      stores/{list,update-markups,update-default-sizes,update-medium-availabilities}.ts
      pages/{list,update,delete}.ts
      products/{create,bulk-create,update,add-photo,remove-photo,delete,bulk-delete}.ts
      galleries/{create,add-products,remove-products,set-cover,reorder}.ts
      contacts/{create,bulk-create,delete,bulk-delete}.ts
      shipping/{update-datum,create-method,update-method,delete-method}.ts
  .github/workflows/ci.yml

The single source of truth for the API surface is the backend — this package imports nothing from the Rails app and talks only over HTTPS.

Sibling repo. @asf/mcp wraps the same GraphQL surface as an MCP server for end-customer agents. The two repos intentionally duplicate src/graphql/-style call patterns today — a future @asf/api extraction will collapse them.

Roadmap

  • 0.1 (this release): verb-per-mutation surface, JWT auth, smoke against local dev stack.
  • 0.2: asf setup pull / apply / diff declarative YAML orchestrator (port from the original draft).
  • 0.3: shell completions (asf install --bash | --zsh), npm publish to private registry.
  • Later: anything that requires a new GraphQL operation belongs in a follow-up icono_pregame PR first — file it there, then add the CLI verb.

References