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

@baublet/mote

v0.0.4

Published

Instrument, automate, track, and checkpoint personal dev environment VMs on GCP

Readme

mote

Instrument, automate, track, and checkpoint personal dev environments on GCP.

Size slugs. GPU shorthands. Layered config. Minimal dependencies.

Vision

VMs are a better development experience than containers: full OS, real systemd, easier virtualization, no layered filesystem quirks. Mote gives you a simple interface for steering those VMs in GCP, with an API tailored for LLM use that's also easy for humans.

The end goal: point an AI agent at a new repo's LLM.txt, and it uses Mote to create, configure, snapshot, branch, clone, and tear down dev environments on your behalf. You get reproducible, GPU-capable workspaces you can spin up, fork, and destroy without thinking about infrastructure.

Install

npm install -g mote   # or: git clone … && npm link

Requires: Node ≥ 20, gcloud CLI authed.

Setup

# point at your project (visible everywhere, persisted)
mote config set project my-gcp-project

# optionally change zone
mote config set zone us-west1-b

# see full config (shows source of each value)
mote config get
Configuration
──────────────────────────────────────────────────
  project        my-gcp-project               config
  zone           us-west1-b                   config
  machine        medium                       default
  disk           100                          default
  diskType       pd-balanced                  default
  image          ubuntu-2404-lts-amd64        default
  imageProject   ubuntu-os-cloud              default
  tag            mote-vm                      default
  exposeMethod   tunnel                       default
  autoIdleTimeout 180                         default
  showTips       true                         default

Config layers: code defaults → ~/.mote/config.json → CLI flags. Every default is overridable at every level.

Quick start

mote create my-app                      # medium (4 vCPU, 16 GB)
mote run my-app echo hello              # run a command
mote ssh my-app                         # interactive shell
mote save my-app "clean install"        # checkpoint
mote restore my-app "clean"             # fuzzy match
mote stop my-app                        # storage-only billing
mote start my-app                       # resume
mote rm my-app                          # destroy

Accessing your mote

SSH

mote ssh my-app                         # interactive shell
mote ssh my-app                         # aliases: mote console, mote c

Running commands

mote run my-app uname -a                # run a single command
mote run my-app -- ls -la /etc          # use -- for flags
mote run my-app --sudo apt update       # run as root

File transfer

mote push my-app ./local-file.txt       # upload to ~ on the mote
mote push my-app ./src ~/project/src    # upload to specific path
mote pull my-app ~/data/results.csv     # download to current directory
mote pull my-app ~/data ./local-data    # download to specific path

Scripts

mote script my-app ./setup.sh           # upload and run a local script
mote script my-app ./setup.sh --sudo    # run as root
mote install my-app docker              # run a bundled install script
mote install --list                     # see available scripts

Git + SSH keys

Copy your host's ~/.gitconfig and SSH keys to the mote so you can git clone private repos immediately:

mote install my-app git

This copies ~/.gitconfig and ~/.ssh/id_* from your machine to the mote, fixes permissions, and adds github.com/gitlab.com/bitbucket.org to known_hosts.

Note: This copies your private SSH keys to the VM. For personal dev environments this is fine. If you'd prefer not to copy keys, use SSH agent forwarding instead:

gcloud compute ssh mote-my-app --zone us-central1-a -- -A

Agent forwarding lets the VM use your local keys without copying them, but only while you're connected.

Port forwarding

To access a service running on your mote (e.g. a web server on port 8080), use mote expose:

mote expose my-app 8080                 # → public HTTPS URL via Cloudflare tunnel
mote expose my-app 3000,8080            # multiple ports
mote info my-app                        # see tunnel URLs
mote unexpose my-app                    # close all

This creates a Cloudflare Quick Tunnel — automatic HTTPS, no firewall rules, no raw IP exposure. cloudflared is installed on the mote automatically on first use. Tunnel URLs are ephemeral and change on restart.

For a stable IP-based endpoint (opens GCP firewall rules with 0.0.0.0/0 ingress):

mote expose my-app 8080 --method firewall
mote unexpose my-app --method firewall

Set the default method permanently with mote config set exposeMethod firewall.

You can also use standard gcloud SSH port forwarding for local-only access without exposing anything publicly:

gcloud compute ssh mote-my-app --zone us-central1-a -- -L 8080:localhost:8080
# now localhost:8080 on your machine → port 8080 on the mote

Size slugs

Instead of remembering e2-standard-4, use a slug:

mote create tiny  --machine xs           # e2-small (2 vCPU, 2 GB)
mote create app   --machine small        # e2-standard-2 (2 vCPU, 8 GB)
mote create app   --machine medium       # e2-standard-4 (4 vCPU, 16 GB)  ← default
mote create app   --machine large        # e2-standard-8 (8 vCPU, 32 GB)
mote create app   --machine xl           # e2-standard-16 (16 vCPU, 64 GB)
mote create app   --machine 2xl          # e2-standard-32 (32 vCPU, 128 GB)

Or pass any GCP machine type directly:

mote create app --machine n2-standard-8  # validated against zone catalog

Set your preferred default:

mote config set machine large            # all future motes are 8 vCPU

GPU slugs

mote create ml --gpu t4                  # Tesla T4 → auto n1-standard-8
mote create ml --gpu l4                  # L4 → auto g2-standard-8
mote create ml --gpu a100                # A100 → auto a2-highgpu-1g
mote create ml --gpu t4 --gpu-count 4    # 4× T4
mote create ml --gpu t4 --machine large  # override machine for GPU

See all slugs: mote catalog sizes

Cloning

Clone a mote to get an identical copy — same disk contents, machine type, GPU config, and schedule settings:

mote clone my-app my-app-v2               # full clone (disk + settings + schedule)
mote clone my-app experiment --stopped     # clone but don't start it
mote clone my-app my-app-v2 --from "baseline"  # clone from a specific save
mote clone my-app my-app-v2 --no-copy-schedule # clone without schedule/auto-idle settings

By default, clone copies the source mote's scheduled start/stop times and auto-idle timeout. Use --no-copy-schedule to skip that.

Catalog

Machine types and GPUs are fetched from GCP and cached (24h):

mote catalog machines                    # what's in your zone
mote catalog gpus                        # GPUs in your zone
mote catalog sizes                       # all slug → type mappings
mote catalog refresh                     # force refresh cache
mote catalog machines --zone europe-west1-b   # different zone

If offline or no project configured, falls back to hardcoded catalog.

Config

mote config get                          # show all (with source labels)
mote config get project                  # show one key
mote config set project my-proj          # set
mote config set zone us-east1-b          # change zone
mote config set machine large            # change default size
mote config set disk 100                 # bigger default disk
mote config unset machine                # revert to code default
mote config path                         # ~/.mote/config.json

Override anything per-command:

mote create my-app --project other-proj --zone asia-east1-a --machine xl

Priority: CLI flag > config.json > code default > gcloud default.

Workspace isolation

By default mote stores state in ~/.mote/. Use --state to isolate per-project:

mote --state ./my-project/.mote/state.json create dev
mote --state ./my-project/.mote/state.json ls

Each workspace gets its own state, config, and scheduler cron entry.

Scheduling

Motes cost money while running. The scheduler saves you money by automatically stopping idle VMs and optionally starting/stopping them on a time-based schedule.

How it works

The scheduler is a lightweight cron job that runs on your host machine (laptop, desktop, whatever you run mote from). When you run mote scheduler install, it adds a single line to your crontab that calls mote scheduler tick once per minute. Each tick:

  1. Loops through your running motes
  2. SSHes in and checks for active users (via the w command)
  3. Stops any mote that's been idle longer than the timeout
  4. Starts/stops motes based on their time schedules (if configured)

The tick command is fast and quiet — it reads ~/.mote/state.json, makes a few quick SSH calls, and exits. Output goes to ~/.mote/scheduler.log.

Important: Your host machine must stay on for this to work. If your laptop sleeps or shuts down, the cron job won't run and motes won't be auto-managed.

Installing the scheduler

mote scheduler install

This does one thing: adds a crontab entry like:

* * * * * /path/to/mote scheduler tick >> ~/.mote/scheduler.log 2>&1

No daemons, no background processes, no system services — just a standard cron job. You can verify it with crontab -l and remove it anytime with mote scheduler uninstall.

mote scheduler status     # check if installed, when it last ran, mote counts
mote scheduler uninstall  # remove the cron job

Auto-idle (default: ON, 3 hours)

Once the scheduler is installed, it checks each running mote for activity every minute. "Activity" means someone is logged in via SSH — the scheduler runs w -hs | wc -l via SSH and only considers a mote active when more than one session is present (the check's own SSH session counts as one).

  • If users are connected: records the mote as active
  • If nobody is connected and the mote has been idle longer than the timeout: warm-stops it

The default timeout is 3 hours. Change it globally:

mote config set autoIdleTimeout 180   # 3 hours (default)
mote config set autoIdleTimeout 60    # 1 hour
mote config set autoIdleTimeout 0     # disable auto-idle globally

Or override per-mote:

mote schedule my-app --auto-idle 120  # 2h timeout for this mote only
mote schedule my-app --no-auto-idle   # never auto-stop this mote

Scheduled stop/start (opt-in)

You can also set time-based schedules to stop and start motes automatically:

mote schedule my-app --stop-at 20:00 --start-at 08:00                    # daily
mote schedule my-app --stop-at 20:00 --start-at 08:00 --days mon,tue,wed,thu,fri  # weekdays only
mote schedule my-app --stop-at 20:00 --start-at 08:00 --tz America/New_York       # specific timezone
mote schedule my-app --clear                                              # remove schedule
mote schedule my-app                                                      # view current settings

Testing the scheduler

You can run a tick manually and override the idle timeout for quick testing:

mote scheduler tick                       # run one cycle now
mote scheduler tick --auto-idle-timeout 1  # use 1-minute timeout (for testing)

All commands

CONFIG
  mote config get [key]       Show config
  mote config set <key> <val> Set a value
  mote config unset <key>     Revert to default
  mote config path            Config file location

CATALOG
  mote catalog machines       Machine types in zone
  mote catalog gpus           GPU types in zone
  mote catalog sizes          All slug mappings
  mote catalog refresh        Force refresh cache

MOTES
  mote create <name>             Create (--machine, --gpu, --expose, etc.)
  mote ls                        List (grouped by project)
  mote run <name> <cmd...>       Run a command
  mote ssh <name>                Interactive shell (aliases: console, c)
  mote save <name> [label]       Checkpoint
  mote saves <name>              List checkpoints
  mote restore <name> [save]     Restore (id, label, or latest)
  mote stop <name>               Stop (storage-only)
  mote start <name>              Start
  mote expose <name> <ports>     Expose ports (HTTPS tunnel by default)
  mote unexpose <name> [ports]   Close exposed ports
  mote push <name> <src> [dst]   Upload files
  mote pull <name> <src> [dst]   Download files
  mote clone <src> <name>        Clone a mote (--no-copy-schedule to skip)
  mote script <name> <path>      Upload + run script
  mote install [name] [software] Install bundled script (docker, node, vscode, git)
  mote install --list            List available install scripts
  mote info <name>               Details
  mote resize <name>             Change size (--machine, --gpu, --gpu-count, --disk)
  mote cost [name]               Cost summary (all) or detailed breakdown (one)
  mote rm <name>                 Destroy
  mote nuke                      Destroy everything

MAINTENANCE
  mote gc                        Reconcile state against GCP
  mote cron                      Cold-store warm-stopped motes
  mote skill                     Install Claude Code skill into cwd

SCHEDULER
  mote scheduler install      Install cron job
  mote scheduler uninstall    Remove cron job
  mote scheduler status       Show scheduler status
  mote scheduler tick         Run one scheduler cycle

SCHEDULE
  mote schedule <name>           View/set per-mote schedule

Cost

| State | Pay for | |---|---| | Running | vCPUs + RAM + GPU + disk + IP | | Warm-stopped (mote stop) | Disk only (~$10/mo for 100 GB pd-balanced) | | Cold-stored (mote stop --freeze) | Snapshot only (~$2.60/mo for 100 GB) | | Saves | Snapshots (~$0.026/GB/mo, incremental) | | Removed | $0 |

Cost command

mote cost                         # summary table of all motes
mote cost my-app                  # detailed breakdown for one mote

mote cost (no name) shows a table with current cost and projected monthly for every mote.

mote cost <name> shows a per-component rate card (compute, GPU, disk, IP), warm-stopped/cold-stored costs, and — if a schedule is set — a projected blended monthly cost with savings vs 24/7 running.

Disk resize

mote resize my-app --disk 200     # grow disk to 200 GB (GCP only allows growing)
mote resize my-app --disk 200 --machine large  # resize disk and machine together

Disk resize works in any state. For running/warm-stopped motes, the disk is resized live via gcloud compute disks resize. For cold-stored motes, the new size is recorded and applied on next mote start.

Testing

Prerequisites

  1. Node.js ≥ 20
  2. gcloud CLI installed and authenticated (mote checks this on every command):
# install: https://cloud.google.com/sdk/docs/install
gcloud auth login
gcloud config set project <YOUR_PROJECT_ID>
  1. Install dependencies:
npm install

Typecheck

npm run typecheck    # runs tsc over all JS with JSDoc types

Manual test sequence

Run these in order. Each step builds on the last.

# 1. Preflight — verify gcloud detection works
node bin/mote.js --help                # should print help (no gcloud check)
node bin/mote.js ls                    # should pass preflight, print "No motes yet"

# 2. Config
node bin/mote.js config get            # show resolved config with source labels
node bin/mote.js config path           # print ~/.mote/config.json path

# 3. Catalog (hits GCP API, verifies auth + project)
node bin/mote.js catalog sizes         # list slug mappings (no API call)
node bin/mote.js catalog machines      # fetch machine types for default zone
node bin/mote.js catalog gpus          # fetch GPU types for default zone

# 4. Create a mote
node bin/mote.js create test-mote      # default medium (e2-standard-4, 100 GB)
node bin/mote.js ls                    # should show test-mote RUNNING with IP
node bin/mote.js info test-mote        # full details

# 5. Run commands on it
node bin/mote.js run test-mote uname -a
node bin/mote.js run test-mote whoami

# 5b. Install scripts
node bin/mote.js install --list            # should list docker, git, node, vscode
node bin/mote.js install test-mote git     # copies SSH keys + git config, sets up known_hosts

# 6. Save + restore
node bin/mote.js save test-mote "baseline"
node bin/mote.js saves test-mote       # should show the save
node bin/mote.js restore test-mote "baseline"

# 7. Stop / start cycle
node bin/mote.js stop test-mote
node bin/mote.js ls                    # status should be WARM_STOPPED
node bin/mote.js start test-mote
node bin/mote.js ls                    # status should be RUNNING

# 8. Cleanup
node bin/mote.js rm test-mote -y       # destroy VM + disk + saves + firewall rules
node bin/mote.js ls                    # should be empty again

Optional: GPU test

Requires GPU quota in your project/zone:

node bin/mote.js create gpu-test --gpu t4
node bin/mote.js run gpu-test nvidia-smi
node bin/mote.js rm gpu-test -y

Optional: Clone + expose test

node bin/mote.js create src-mote
node bin/mote.js clone src-mote dst-mote
node bin/mote.js expose dst-mote 8080                  # tunnel (default)
node bin/mote.js expose dst-mote 443 --method firewall # firewall
node bin/mote.js info dst-mote                         # shows both
node bin/mote.js unexpose dst-mote
node bin/mote.js rm src-mote -y
node bin/mote.js rm dst-mote -y

Architecture

mote/
  bin/mote.js       CLI entry + command routing (commander)
  src/
    commands.js       command handlers
    config.js         layered config (code → user → CLI)
    db.js             JSON state store (atomic writes)
    gcloud.js         thin gcloud CLI wrapper (sync, async, interactive)
    catalog.js        GCP machine/GPU catalog + 24h cache
    slugs.js          size/GPU slug resolution
    pricing.js        cost estimation (hourly + monthly)
    scheduler.js      cron-based auto-idle + scheduled start/stop
    reconcile.js      drift detection (state ↔ GCP)
    tips.js           contextual tips
    warnLongOp.js     duration warnings for long operations
  scripts/
    <name>/vm.sh       VM-side install script (runs with sudo on the mote)
    <name>/host.sh     host-side script (runs locally before vm.sh)

Install scripts live in scripts/<name>/. Each script can have a vm.sh (uploaded and run on the mote with sudo), a host.sh (run locally on your machine first), or both. Host scripts receive MOTE_INSTANCE, MOTE_ZONE, and MOTE_PROJECT as environment variables for gcloud calls.

Three bundled dependencies (commander, cli-table3, write-file-atomic). No native modules. Source runs directly (node bin/mote.js); esbuild bundles to dist/mote.js for distribution.

State: ~/.mote/state.json Config: ~/.mote/config.json Cache: ~/.mote/cache/<zone>-machines.json, <zone>-gpus.json