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

@elvine-sk/cli

v0.2.2

Published

Scaffold and update projects from the ELV template registry

Readme

@elvine-sk/cli

Scaffold and update projects from the ELV template registry. Works like the ui-thing CLI but for full project templates — clone a starter, then pull upstream updates interactively without losing your local changes.

Table of Contents


Quick Start

# Scaffold a full Nuxt app (ui-template + core module)
npx @elvine-sk/cli init app my-project

# Scaffold a Hono backend
npx @elvine-sk/cli init backend my-api

# Scaffold a full-stack project (Nuxt + Hono)
npx @elvine-sk/cli init fullstack my-platform

# Check for upstream updates
npx @elvine-sk/cli diff

# Apply updates interactively (never replaces without asking)
npx @elvine-sk/cli update

Installation

Run directly (recommended):

npx @elvine-sk/cli <command>

Install globally:

npm install -g @elvine-sk/cli
# then use:
elv <command>

Install as a dev dependency:

pnpm add -D @elvine-sk/cli
# then use via npx or package.json scripts

Templates

| Template | Description | Packages | | ------------- | ---------------------------------------------- | ---------------------------------- | | app | Full Nuxt app with core module | ui-template + core | | backend | Hono backend API (auth, RBAC, Kysely, Redis) | bn-template | | fullstack | Full-stack (Nuxt frontend + Hono backend) | ui-template + core + bn-template | | ui-only | UI component library template only | ui-template | | core-only | Core Nuxt module only | core + authorization |

Template Details

app — Merges ui-template and core into a single Nuxt project. The core package is placed into modules/core/ as a local Nuxt module. Dependencies are merged into one package.json, and catalog: versions are resolved to real versions. The shared authorization engine is copied into vendor/authorization/ and referenced with a file: dependency, because it is not published to npm.

backend — Standalone Hono backend with authentication, RBAC/ABAC, Kysely ORM (MySQL), Redis, and Pino logging. Includes migration and seed tooling via kysely-ctl.

fullstack — Combines app and backend into a monorepo-like structure:

my-project/
├── frontend/          # Nuxt app (same as "app" template)
│   ├── app/
│   ├── modules/core/
│   ├── vendor/authorization/
│   ├── nuxt.config.ts
│   └── package.json
├── backend/           # Hono API (same as "backend" template)
│   ├── src/
│   ├── vendor/authorization/
│   └── package.json
└── package.json       # Root with workspace scripts

core-only — The core Nuxt module on its own, at the project root. core depends on the shared authorization engine, so that package is copied into vendor/authorization/ and referenced with a file: dependency, exactly as in app and backend.

Note: All scaffolded projects start at version 0.1.0 regardless of the template's current version. The package name is derived from the project directory name.


Package Manager Selection

During elv init, you're prompted to choose a package manager (pnpm, npm, or bun). This choice affects:

  • Post-setup install commands
  • CI/CD workflow files (.github/workflows/) — install, run, and cache commands are patched
  • Dockerfiles — lockfile copy, vendored-directory copy, and install commands are patched
  • Fullstack root package.json scripts (dev, build commands)

npm: installing a scaffolded frontend currently fails with ERESOLVE, because @vee-validate/zod declares a peer dependency on zod@^3 while the template uses zod@^4. Use pnpm (the default) or run npm install --legacy-peer-deps.

bun on Windows: file: dependencies are copied into node_modules rather than symlinked. After editing files under vendor/authorization — or after an elv update that changes them — run bun install again for the changes to take effect.


CI/CD Workflows

Scaffolded projects include GitHub Actions workflows:

  • ci.yml — Runs typecheck and build on push to main/develop and on PRs
  • deploy-ssh.yml — Manual dispatch to deploy via SSH + PM2
  • deploy-docker.yml — Manual dispatch to build Docker image, push to GHCR, and deploy

All workflows are automatically patched with the correct package manager commands during scaffold.

Required GitHub Secrets for deployment:

| Secret | Description | |---|---| | SSH_HOST | Server hostname/IP | | SSH_USER | SSH username | | SSH_PRIVATE_KEY | SSH private key | | SSH_PORT | SSH port (optional, default 22) |


Dotenv Migration

Previously, backend templates used --env-file=.env.dev (bun/tsx-specific). Scaffolded projects now use the dotenv package for universal compatibility. The entry point (src/server.ts) imports dotenv/config, which automatically loads .env. For different environments, set DOTENV_CONFIG_PATH=.env.staging before running.


Commands

elv init

Scaffold a new project from a template.

elv init [template] [directory]

Arguments:

| Argument | Description | Required | | ----------- | ---------------------------------------- | -------- | | template | Template name (app, backend, fullstack, ui-only, core-only) | No (interactive prompt) | | directory | Target directory for the new project | No (interactive prompt) |

Options:

| Flag | Alias | Description | Default | | --------------- | ----- | ------------------------------------- | ------- | | --source | -s | Source repository (github:owner/repo) | Built-in default | | --force | -f | Overwrite existing directory | false | | --token | | GitHub token for private repos | |

Examples:

# Interactive mode — prompts for everything
elv init

# Full Nuxt app
elv init app my-hrm-app

# Backend API only
elv init backend my-api

# Full-stack (Nuxt + Hono)
elv init fullstack my-platform

# UI template only
elv init ui-only my-design-system

# Core module only
elv init core-only shared-logic

# From a custom source repo
elv init app my-app --source github:my-org/my-templates

# Overwrite existing directory
elv init app my-app --force

What happens:

  1. Fetches the template registry from the source repo (falls back to built-in)
  2. Prompts for template + project name if not provided
  3. Downloads the full repo, extracts the relevant packages/ directories
  4. For app / fullstack: merges packages, resolves catalog: versions, patches nuxt.config.ts to load core from modules/core/
  5. For backend: copies bn-template, resolves catalog: versions
  6. Copies the authorization package into vendor/authorization/ (once per sub-project in fullstack) and points package.json at it
  7. Cleans up monorepo-specific files (pnpm-lock.yaml, .npmrc, etc.)
  8. Writes .elvrc.json with the current commit SHA for future updates
  9. Asks to run post-setup commands (install in each project; nuxt prepare for the frontend)

elv update

Interactively update your project from upstream changes. Never replaces files without asking.

elv update [options]

Options:

| Flag | Alias | Description | Default | | ------------ | ----- | ---------------------------------------- | ------- | | --target | -t | Target branch or commit to update to | main | | --cwd | | Project directory (if not current) | . | | --token | | GitHub personal access token | |

Examples:

# Update from current directory
elv update

# Update a project in another directory
elv update --cwd ./my-project

# Update to a specific branch
elv update --target develop

# Update a private repo
elv update --token ghp_xxxxxxxxxxxx

Interactive review for each file:

For every changed file, you get seven choices:

| Choice | What it does | | -------------------- | --------------------------------------------------------- | | Accept | Apply this change to your local file | | Skip | Ignore this file, keep your local version | | Skip always | Skip and add a glob pattern to .elvrc.json exclude list | | Merge hunks | Review and accept/skip individual sections of the diff | | Open in editor | Inline conflict markers — Accept / Reject per change | | Merge editor | 3-way split view with checkboxes (full control) | | View diff | Show the full colored diff, then ask again | | Accept all | Apply all remaining changes without more prompts |

Merge Hunks

When you choose Merge hunks, the diff is split into individual sections (hunks). For each hunk you can:

  • Accept — apply this specific change
  • Skip — keep your local version for this section
  • Accept remaining — apply all remaining hunks without prompting

Only the hunks you accept are applied. This gives you git add -p-style granularity over each update.

Open in Editor (primary)

When you choose Open in editor, the CLI:

  1. Auto-detects your editor (Cursor, Windsurf, VS Code, VSCodium)
  2. Writes the file with inline conflict markers (<<<<<<< local (yours) / >>>>>>> upstream (incoming))
  3. Opens the file — your editor shows Accept Current Change / Accept Incoming Change / Accept Both / Resolve with AI buttons per change
  4. Click to accept or reject each change, then save and close the tab
  5. The CLI verifies all conflicts were resolved; if markers remain you can keep or restore

This is the fastest way to resolve changes — one click per conflict block directly in your code.

Merge Editor (optional, full control)

When you choose Merge editor, the CLI opens the editor's built-in 3-way merge editor (--merge flag):

┌──────────────────┬──────────────────┐
│  Current (yours) │ Incoming (upstream) │
│                  │                     │
│  Your local code │  Upstream changes   │
│  [✓] Accept      │  [✓] Accept         │
├──────────────────┴─────────────────────┤
│              Result                     │
│   Live preview of merged output         │
└─────────────────────────────────────────┘
  • Check/uncheck individual changes from each side
  • The Result pane updates in real-time
  • Full control over every line in the final output
  • Best for complex files with many interleaved changes

Editor detection:

| Method | Detects | | ------ | ------- | | VSCODE_GIT_ASKPASS_NODE env var | Cursor, Windsurf, VS Code (by binary path) | | CURSOR_CHANNEL / WINDSURF_CHANNEL env vars | Cursor, Windsurf specifically | | TERM_PROGRAM env var | VS Code | | CLI availability (where / which) | Any editor with a CLI command |

Supported editors: Cursor, Windsurf, VS Code, VSCodium.

Unsupported editors: If your editor doesn't support inline diffs, the CLI suggests using Merge hunks in the terminal instead.

Skip Always (Auto-Exclude)

When you choose Skip always, you're prompted to select an exclude pattern:

  • The exact file path (e.g. app/pages/login.vue)
  • The entire directory (e.g. app/pages/**)

The selected pattern is immediately added to your .elvrc.json exclude list, so future updates automatically skip matching files.

After review, .elvrc.json is updated with the new commit hash.


elv diff

Preview what upstream changes exist without applying anything. Read-only.

elv diff [options]

Options:

| Flag | Alias | Description | Default | | ------------ | ----- | ---------------------------------------- | ------- | | --target | -t | Target branch or commit to diff against | main | | --cwd | | Project directory | . | | --full | -F | Show full file diffs (not just summary) | false | | --token | | GitHub personal access token | |

Examples:

# Summary only — lists changed files
elv diff

# Full diffs with colored output
elv diff --full

# Diff against a specific branch
elv diff --target release/v2

elv info

Show project configuration and check for available updates.

elv info [options]

Options:

| Flag | Description | Default | | --------- | ---------------------------------- | ------- | | --cwd | Project directory | . | | --token | GitHub personal access token | |

Example output:

  @elvine-sk/cli - Project Info

  Template:     app
  Version:      0.1.0
  Source:       github:my-org/my-templates
  Commit:       a1b2c3d4
  Updated at:   2026-07-13T03:15:00.000Z
  Excludes:
    · app/pages/**
    · app/composables/stores/**

  ✔ Up to date!

elv list

List all available templates from the registry.

elv list [options]

Options:

| Flag | Alias | Description | Default | | ----------- | ----- | ------------------------------------- | ------- | | --source | -s | Source repository | Built-in default | | --token | | GitHub personal access token | |

Example output:

  Available Templates

  app
    Full app (ui-template + core module)
    Packages: ui-template, core

  backend
    Hono backend API (auth, RBAC, Kysely, Redis)
    Packages: bn-template

  fullstack
    Full-stack app (Nuxt frontend + Hono backend)
    Packages: ui-template, core, bn-template

  ui-only
    UI component library template only
    Packages: ui-template

  core-only
    Core Nuxt module only
    Packages: core

  Source: github:Elvine-SK/hrm-mono-app
  Usage: npx @elvine-sk/cli init <template> <directory>

Configuration (.elvrc.json)

When you run elv init, a .elvrc.json file is created in your project root:

{
  "$schema": "https://github.com/Elvine-SK/hrm-mono-app/blob/main/schema.json",
  "version": "0.1.0",
  "sourceCommit": "a1b2c3d4e5f6...",
  "template": "app",
  "source": "github:Elvine-SK/hrm-mono-app",
  "updatedAt": "2026-07-13T03:00:00.000Z",
  "exclude": [
    "app/pages/**",
    "app/composables/stores/**"
  ]
}

| Field | Description | | --------------- | ------------------------------------------------------------------------ | | version | Your project's local version string | | sourceCommit | The exact commit SHA your project was scaffolded/last updated from | | template | Which template was used (app, backend, fullstack, ui-only, core-only) | | source | The GitHub repo the template came from | | updatedAt | Timestamp of last init or update | | exclude | Glob patterns for files that should never be updated by elv update |

Customizing Excludes

Edit the exclude array to protect files you've customized:

{
  "exclude": [
    "app/pages/**",
    "app/composables/stores/**",
    "app/assets/css/main.css",
    "nuxt.config.ts",
    "app/app.config.ts"
  ]
}

These files will be silently skipped during elv update and elv diff.

You can also add excludes interactively during elv update by choosing Skip always when prompted for a file action.


Using with Private Repos

The CLI uses the GitHub REST API. For private repositories, you need a personal access token (classic or fine-grained) with repo / contents:read permissions.

Option 1: Pass via --token flag

elv update --token ghp_xxxxxxxxxxxxxxxxxxxx
elv diff --token ghp_xxxxxxxxxxxxxxxxxxxx
elv init app my-app --source github:my-org/my-templates --token ghp_xxx

Option 2: Set the GITHUB_TOKEN environment variable

# Linux / macOS
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
elv update

# Windows (PowerShell)
$env:GITHUB_TOKEN = "ghp_xxxxxxxxxxxxxxxxxxxx"
elv update

# Windows (CMD)
set GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
elv update

Option 3: Save token interactively

When you run elv init without a token, the CLI will prompt you to enter one and optionally save it to ~/.elv/credentials.json for future use.

Creating a GitHub Token

  1. Go to github.com/settings/tokens
  2. Click Generate new token (classic) or Fine-grained token
  3. For classic: check the repo scope
  4. For fine-grained: select the target repository, grant Contents: Read-only
  5. Copy the token and use it with --token or GITHUB_TOKEN

Rate Limits

  • Without token: 60 requests/hour (shared across your IP)
  • With token: 5,000 requests/hour

Each elv update or elv diff uses approximately 2 + N API calls (where N is the number of changed files), so a token is recommended for frequent use.


Local Development & Testing

Prerequisites

  • Node.js >= 18
  • pnpm (the monorepo uses pnpm workspaces)

Building the CLI

From the monorepo root:

# Build the CLI
pnpm elv:build

# Or from the CLI package directly
cd packages/cli
pnpm build

Running Locally (without publishing)

After building, run directly from the dist output:

# From monorepo root
node packages/cli/dist/index.mjs --help
node packages/cli/dist/index.mjs list
node packages/cli/dist/index.mjs init app test-project

# Or use the stub mode for development (auto-rebuilds on change)
cd packages/cli
pnpm dev
node dist/index.mjs list

Using pnpm link for Global Testing

To test as if it were installed globally:

cd packages/cli
pnpm build
pnpm link --global

# Now you can run from anywhere:
elv --help
elv list
elv init app ~/Desktop/test-project

# When done, unlink:
pnpm unlink --global

Testing the Full Workflow

# 1. Build the CLI
cd packages/cli && pnpm build && cd ../..

# 2. Test "list" (no network needed — falls back to built-in)
node packages/cli/dist/index.mjs list

# 3. Test "init" — scaffold each template type
node packages/cli/dist/index.mjs init app /tmp/elv-test/my-app
node packages/cli/dist/index.mjs init backend /tmp/elv-test/my-api
node packages/cli/dist/index.mjs init fullstack /tmp/elv-test/my-platform

# 4. Verify the scaffolded projects
ls /tmp/elv-test/my-app/
ls /tmp/elv-test/my-api/
ls /tmp/elv-test/my-platform/

# 5. Test "info" inside a scaffolded project
node packages/cli/dist/index.mjs info --cwd /tmp/elv-test/my-app

# 6. Test "diff" — check for upstream changes
node packages/cli/dist/index.mjs diff --cwd /tmp/elv-test/my-app

# 7. Test "update" — interactive merge with new options
node packages/cli/dist/index.mjs update --cwd /tmp/elv-test/my-app

# 8. Clean up
rm -rf /tmp/elv-test

Testing with a Private Repo

# Set your token once
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

# All commands now authenticate automatically
node packages/cli/dist/index.mjs init app /tmp/test-private \
  --source github:my-org/private-repo \
  --token $GITHUB_TOKEN

Development Mode (stub)

For rapid iteration, use unbuild's stub mode:

cd packages/cli
pnpm dev   # Creates stub dist that auto-resolves to src/

# Now changes to src/ are reflected immediately:
node dist/index.mjs list
# Edit src/commands/list.ts...
node dist/index.mjs list  # Changes reflected without rebuild

Publishing to npm

First-time Setup

  1. Make sure you have an npm account and are logged in:
npm login
  1. If using an @elvine-sk scope, either create the org on npmjs.com or publish as unscoped.

Publishing

cd packages/cli

# Build first
pnpm build

# Publish (dry run to verify)
npm publish --access public --dry-run

# Publish for real
npm publish --access public

Versioning

Update the version in packages/cli/package.json and src/index.ts (the citty meta version) before publishing:

cd packages/cli
npm version patch   # 0.1.0 → 0.1.1
# or
npm version minor   # 0.1.0 → 0.2.0

Adding New Templates

1. Add the package to the monorepo

mkdir packages/my-new-template
# Set up your template files...

2. Register it in registry.json

Edit the root registry.json:

{
  "source": "github:Elvine-SK/hrm-mono-app",
  "templates": {
    "app": { "..." : "..." },
    "backend": { "..." : "..." },
    "fullstack": { "..." : "..." },
    "my-new-template": {
      "description": "Description of what this template provides",
      "packages": ["my-new-template"],
      "postSetup": ["pnpm install"]
    }
  }
}

3. Map its files in LAYOUTS

Add the template's package-to-directory mapping to LAYOUTS in packages/cli/src/utils/layout.ts — it tells elv update / elv diff where each upstream packages/<name>/… file lands inside a generated project:

export const LAYOUTS: Record<string, TemplateLayout> = {
  // ...existing templates...
  "my-new-template": {
    "my-new-template": [""],          // package → project root
    authorization: [VENDOR_DIR],      // only if the template vendors it
  },
};

Without an entry here elv init still scaffolds the template, but elv update and elv diff refuse to run for projects created from it, because they cannot map upstream paths to local ones.

4. (Optional) Add to built-in fallback

If you want the template available even when the registry can't be fetched, add it to packages/cli/src/utils/template.ts:

export const BUILT_IN_TEMPLATES: RegistryManifest = {
  source: DEFAULT_SOURCE,
  templates: {
    // ...existing templates...
    "my-new-template": {
      description: "Description of what this template provides",
      packages: ["my-new-template"],
      postSetup: ["pnpm install"],
    },
  },
};

5. Rebuild and test

pnpm elv:build
node packages/cli/dist/index.mjs list

Example: bn-template

The bn-template package is registered as both a standalone backend template and part of the fullstack combined template. When used standalone, it scaffolds directly to the project root. In the fullstack template, it goes into a backend/ subdirectory alongside the Nuxt frontend/.


How the Update Flow Works

elv update
    │
    ├─ Read .elvrc.json → get sourceCommit (e.g. a1b2c3d4)
    │
    ├─ Fetch latest commit from GitHub (e.g. e5f6g7h8)
    │
    ├─ Compare a1b2c3d4...e5f6g7h8 via GitHub API
    │  └─ Returns list of changed files in the repo
    │
    ├─ Filter to only files inside template packages
    │  └─ e.g. only packages/ui-template/** and packages/core/**
    │
    ├─ Remove files matching .elvrc.json exclude patterns
    │
    ├─ For each remaining file:
    │  ├─ Show status badge (ADDED / MODIFIED / DELETED)
    │  ├─ Prompt: Accept | Skip | Skip always | Merge hunks
    │  │          | Open in editor | View diff | Accept all
    │  │
    │  ├─ If "View diff" → show colored diff → ask again
    │  ├─ If "Accept" → write remote content to local file
    │  ├─ If "Skip always" → prompt for pattern → add to exclude
    │  ├─ If "Merge hunks" → show each hunk → accept/skip per hunk
    │  ├─ If "Open in editor" → conflict markers → accept/reject inline
    │  └─ If "Merge editor" → 3-way split view → checkboxes
    │
    └─ Update .elvrc.json with new commit SHA

Key behaviors:

  • Files in exclude patterns are always skipped — your custom pages, stores, and configs stay untouched
  • Modified files show a line count before you decide — you know the scope of the change
  • Merge hunks gives you section-level control — accept only the parts you want, like git add -p
  • Inline conflict resolution — open in your editor (Cursor, Windsurf, VS Code) and accept/reject each change with one click
  • Skip always auto-manages your exclude list — no need to manually edit .elvrc.json
  • "Accept all remaining" is available to batch-accept when you trust the rest
  • Deleted files prompt too — upstream removals don't auto-delete your files
  • The commit hash is only updated after the review — if you cancel, nothing changes

Troubleshooting

"No .elvrc.json found"

You're running elv update, elv diff, or elv info in a directory that wasn't scaffolded with elv init. Either:

  • cd into your elv-managed project, or
  • Use --cwd /path/to/project

"Could not fetch remote registry, using built-in templates"

The CLI couldn't reach the GitHub API. Possible causes:

  • No internet connection
  • The source repo doesn't exist or is private (use --token)
  • GitHub API rate limit exceeded (use a token for 5,000 req/hr)

The CLI falls back to built-in templates, so elv init still works.

"Failed to compare commits. The base commit may be too old."

GitHub's compare API has a limit of ~250 commits. If your project is very far behind:

  1. Note the current files you've customized
  2. Re-scaffold with elv init --force
  3. Manually re-apply your customizations

"Could not fetch commit SHA"

During elv init, the CLI couldn't fetch the latest commit to pin in .elvrc.json. The project is still scaffolded, but you'll need to manually set sourceCommit in .elvrc.json before elv update will work.

Rate limiting (403 errors)

Add a GitHub token:

elv update --token ghp_xxxxxxxxxxxxxxxxxxxx

"Could not open editor" during merge

The CLI auto-detects your editor (Cursor, Windsurf, VS Code), but the CLI command needs to be in your system PATH. To install it:

  1. Open the Command Palette in your editor (Ctrl+Shift+P / Cmd+Shift+P)
  2. Search for "Shell Command: Install" and run the matching command
    • Cursor: "Install 'cursor' command in PATH"
    • Windsurf: "Install 'windsurf' command in PATH"
    • VS Code: "Install 'code' command in PATH"
  3. Restart your terminal

Binary files or large files fail to fetch

The GitHub Contents API has a 1MB file size limit. Files larger than 1MB are skipped during elv update. These are typically assets (images, fonts) that rarely change.