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

@thinremote/thinr-cli

v1.2.0

Published

CLI for ThinRemote — remote management for IoT devices, with an MCP server for AI clients

Readme

Thinr — ThinRemote CLI

A command-line tool for managing remote IoT devices on the ThinRemote platform, and an MCP server that lets AI assistants drive those devices programmatically.

Two operating modes:

  • CLI — interactive or scripted device management from a shell.
  • MCP server — a stdio Model Context Protocol server that exposes the same operations as typed tools for AI clients like Claude Code.

Both modes share the same auth, profiles, and device API layer, so you configure the tool once and use it in whichever mode fits the task at hand.

Table of contents

Installation

From NPM (recommended)

npm install -g @thinremote/thinr-cli

From source

git clone https://github.com/Thin-Remote/thinr-cli.git
cd thinr-cli
npm install
npm link

Requires Node.js ≥ 18.

Initial setup

Run the CLI once with no arguments to authenticate:

thinr

You'll be prompted for the ThinRemote server, and can authenticate with either username/password (OAuth 2.0 device flow) or a pre-issued token. On success a profile is created at ~/.config/thinr-cli/config.json, named after the server hostname, and marked as the default.

Every later command reads that profile automatically. Logging into a second server creates a second profile side-by-side; see Profiles and multi-account use.

Global options and environment variables

These apply to every subcommand:

| Flag | Env var | Meaning | | ------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | --profile <name> | THINR_PROFILE | Use a non-default profile for this invocation. | | -u, --user <name> | — | Admin impersonation: act on behalf of another user (requires admin privileges on the server). | | — | THINR_INSECURE=1 | Accept self-signed TLS certificates globally. Localhost / 127.0.0.1 are always accepted; this flag extends the relaxation to any host, useful for LAN dev servers. |


Part 1 — CLI

All device- and product-scoped actions follow a subcommand-first shape: the action comes before the target id, matching patterns like kubectl describe pod <name> or git remote add <name> <url>.

thinr device <action>  <deviceId>  [args…] [options]
thinr product <action> <productId> [args…] [options]

JSON output

Every data-producing command accepts -j, --json and writes a single envelope to stdout so scripts can pipe it to jq:

{ "ok": true, "data": <payload> }
{ "ok": false, "error": { "message": "...", "code": "<code>" } }

Process exit codes always reflect success (0) or failure (non-zero), independently of the envelope.

Emitted error code values:

| code | meaning | | ---------------- | ------------------------------------------------- | | not_configured | CLI has no saved profile — run thinr to set up. | | not_found | Device, property, resource or profile not found. | | unauthorized | Token expired or insufficient permissions. | | server_error | Non-success HTTP response from the server. | | network_error | No response from the server. | | input_error | Bad or missing CLI argument. | | timeout | Command timed out on the device (exec). | | cancelled | User interrupted with Ctrl+C (exec). | | error | Fallback for anything uncategorised. |

Spinners and progress messages are suppressed in JSON mode so the output is valid JSON without wrappers. Interactive commands (console, tcp/tls/http) accept --json silently but don't change their behaviour — they don't produce a discrete result. exec buffers stdout/stderr when --json is set and emits a single envelope on exit.

thinr device <action> [<deviceId>]

Most actions take a <deviceId> as their first positional. The list action is the exception — it operates on the fleet, with an optional <pattern> to filter results.

list

List every device visible to the active profile, or filter with a case-insensitive regex (matched against device id and name on the server side):

thinr device list                    # every device
thinr device list supermicro         # only ids/names containing "supermicro"
thinr device list "^revpi|jetson"    # regex alternation works too
thinr device list --json             # JSON envelope with the raw API records

Combine with the global -u, --user flag for admin impersonation.

console

Open an interactive terminal on the device:

thinr device console <deviceId>

Proxies — tcp / tls / http

Create a TCP, TLS, or HTTP proxy to a remote endpoint reachable from the device:

thinr device tcp  <deviceId> [target] [options]
thinr device tls  <deviceId> [target] [options]
thinr device http <deviceId> [target] [options]

target is the remote address and port on the device side. Defaults when omitted: tcp → 22, tls → 443, http → 80, against localhost.

Options:

  • -p, --port <port>: Local port to bind (default: random in 50000–51000)
  • --no-open: Do not open the browser automatically (http only)

Examples:

# TCP proxy to SSH
thinr device tcp RevPi20679 22

# HTTP proxy to a specific port
thinr device http RevPi20679 8080

# HTTP proxy to a specific address behind the device
thinr device http RevPi20679 http://192.168.1.45:8080

status

thinr device status <deviceId> [-j, --json]

Returns the device's connection stats (uptime, tx/rx, last-seen).

property

List every property of a device, or read one by id:

thinr device property <deviceId>                                # list
thinr device property <deviceId> <propertyId> [options]         # read

Options:

  • -j, --json: Output as JSON
  • -f, --field <path>: Extract a sub-field via dot path (-f data.value)

resource

List the resources exposed by the device (with their in/out schemas when advertised), or call one:

thinr device resource <deviceId>                                # list
thinr device resource <deviceId> <resourceId> [options]         # call

Options:

  • -i, --input <key=value>: Resource input (repeatable)
  • -j, --json: Output as JSON
  • -f, --field <path>: Extract a sub-field from the result (dot path)

exec

Run a shell command on the device and stream stdout/stderr back:

thinr device exec <deviceId> "<command>" [options]

Options:

  • -j, --json: Buffer output and emit a single envelope {stdout, stderr, exitCode} on exit
  • --legacy: Use the non-streaming one-shot API (older agents)

The process exits with the remote command's exit code.

update

Check for or apply an agent update on the device:

thinr device update check <deviceId> [options]
thinr device update apply <deviceId> [options]

Options:

  • --channel <name>: Update channel (default: latest)
  • -j, --json: Output as JSON

thinr product <action> <productId>

Fan out an operation across every device that belongs to a product.

property

thinr product property <productId> <propertyId> [options]

Options:

  • -j, --json: Output as JSON. Emits one envelope with a results[] array, one entry per device, each with its own ok flag.
  • -f, --field <path>: Extract a sub-field from each property (dot path)
  • -a, --all: Include offline devices (default: only active)
  • -g, --group <group>: Filter devices by asset group

resource

thinr product resource <productId> <resource> [options]

Options:

  • -j, --json: Output as JSON (results[] array, same shape as above)
  • -f, --field <path>: Extract a sub-field from each result (dot path)
  • -g, --group <group>: Filter devices by asset group
  • -i, --input <key=value>: Resource input (repeatable)

Offline devices are skipped for resource — the server would reject the call anyway.

exec

Run a shell command in parallel across every active device of the product and report a per-device status table plus each device's output:

thinr product exec <productId> <command...> [options]

Options:

  • -j, --json: Output as JSON. Emits a single envelope with a summary block and a results[] array carrying stdout, stderr, exit code, timedOut flag and per-device duration.
  • -c, --concurrency <n>: Max parallel executions (default: 10)
  • --timeout <seconds>: Per-device timeout (default: 30)
  • --fail-fast: Stop dequeueing new devices as soon as one fails
  • -g, --group <group>: Filter devices by asset group
  • -a, --all: Include offline devices (default: only active)

Examples:

# hostname on every active device
thinr product exec my-product hostname

# apt upgrade across the fleet with 5 parallel slots and a long timeout
thinr product exec my-product "apt-get -y upgrade" -c 5 --timeout 600

# target only a subset of the product
thinr product exec my-product "systemctl restart agent" -g staging

Also exposed as an MCP tool (thinr_product_exec) with the same parameters, so agents can drive a fleet operation with a single call instead of looping thinr_exec per device.

playbook

Manage playbooks stored on a product and run them against one device or the whole fleet:

thinr product playbook list     <productId> [--json]
thinr product playbook upload   <productId> <name> [file] [--description <text>] [--skip-validation] [--json]
thinr product playbook download <productId> <name> [-o <file>] [--json]
thinr product playbook delete   <productId> <name> [--json]
thinr product playbook run      <productId> <name> (--device <id> | --fleet) [options]

upload reads the YAML from a file, or from stdin when the path is omitted or -. The document is validated against the playbook schema before being written to the product storage; an invalid document is rejected with a descriptive error and nothing is uploaded. Pass --skip-validation only to work around a schema-runtime gap (do not use it to bypass real errors).

run targets either a single device (--device <id>) or the whole fleet (--fleet, progressive batch rollout):

  • -v, --var <key=value> — override an overridable playbook variable (repeatable; values are coerced to the declared type).
  • --vars-file <path> — load variable overrides from a YAML/JSON file.
  • --dry-run — print the resolved plan without contacting any device.
  • --check — contact devices read-only and report what each step would change.
  • --yes / -y — skip the interactive confirmation prompt.

Fleet-only options:

  • --batch-size <n> — devices attempted in parallel per batch (default: 5).
  • --failure-threshold <p> — abort rollout when cumulative failure rate reaches P% (default: 25).
  • -g, --group <id> — restrict to an asset group.
  • --filter <key=value> — extra server-side filter (repeatable).
  • --include-offline — attempt offline devices too (default: active only).

Each fleet rollout writes a persistent JSON report to playbooks/runs/<timestamp>-<name>-<user>.json in the product's file storage, capturing resolved variables, per-device outcomes, and any abort reason.

thinr playbook <action>

Run or validate a local playbook YAML file (not stored on a product). Useful for one-shot or ad-hoc runs:

thinr playbook validate <file>
thinr playbook run      <file> [--dry-run] [--check] [options]

validate parses the YAML and reports any schema errors. run resolves the target block declared inside the playbook and executes it; the same variable-override flags as thinr product playbook run apply. For playbooks that should be reusable across users, upload them to their product with thinr product playbook upload instead.

thinr profile

Manage the profile store (see Profiles and multi-account use):

thinr profile list          [--json]
thinr profile current       [--json]
thinr profile use    <name> [--json]
thinr profile delete <name> [--json]

thinr logout

Remove the active profile's credentials and config:

thinr logout

Other profiles are left untouched.


Part 2 — MCP server

thinr ships an MCP server that exposes the CLI's device operations as a set of typed tools. AI clients (Claude Code, Claude Desktop, and any other MCP-compatible host) can then list devices, run shell commands, read/write files, call resources, and manage products through the same API the CLI uses — with stable schemas and a consistent error contract.

Starting the server

thinr mcp [-d, --device <deviceId>]

The server speaks MCP over stdio. -d/--device sets a default device that tools can omit in each call; --user can be passed through (via the global flag) to impersonate another account.

Tool catalog

Grouped by capability. Every tool accepts optional device, user, and profile arguments, so a single session can target any device/account/environment without restart.

| Area | Tools | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Discovery | thinr_devices (with optional query for regex filtering), thinr_device_info, thinr_profiles | | Shell | thinr_exec (buffered), with streaming stdout/stderr | | Filesystem | thinr_read, thinr_write, thinr_push, thinr_pull, thinr_ls, thinr_mkdir, thinr_rm, thinr_mv | | Resources | thinr_resource_list (with in/out schemas), thinr_resource_call | | Properties | thinr_property_get, thinr_property_set | | Scripts (device) | thinr_script_list, thinr_script_write, thinr_script_delete | | Monitoring and update | thinr_monitoring, thinr_bucket_read, thinr_update | | Products | thinr_products, thinr_product_delete, thinr_device_set_product, thinr_product_exec, thinr_product_write | | Product scripts | thinr_product_script_list, thinr_product_script_read, thinr_product_script_write, thinr_product_script_delete | | Playbooks (authoring) | thinr_playbook_schema, thinr_playbook_validate, thinr_playbook_run (ad-hoc) | | Product playbooks | thinr_product_playbook_list, thinr_product_playbook_read, thinr_product_playbook_write, thinr_product_playbook_run (single device), thinr_product_playbook_rollout (fleet), thinr_product_playbook_delete |

Full input/output schemas are published via standard MCP list_tools; the client will show them when you connect.

Authoring playbooks from natural language

Playbooks are declarative YAML documents — steps like exec, write, install_package, service, plus a target block and a vars block — that the runner applies to a device or a fleet. With the MCP server connected, an AI assistant can turn a natural-language request into a validated playbook and store it on the relevant product without the user ever writing YAML by hand.

The intended authoring loop is:

  1. Fetch the schema. Call thinr_playbook_schema once at the start of the session to get the authoritative list of top-level fields, target block, step actions with their parameters, and the two accepted forms of the vars block (plain and extended).
  2. Draft the YAML. Translate the user's request into a playbook draft. Use the extended vars form (default / description / type / overridable / required) whenever the user hints at knobs the caller may need to tune at run time.
  3. Validate and iterate. Call thinr_playbook_validate with the draft. On failure, read the consolidated error message, fix the YAML, and call validate again. Never skip this step.
  4. Upload. Once validation is clean, call thinr_product_playbook_write with the product id, a short name (letters / digits / underscore / dash), and the validated YAML. The tool re-validates server-side; never pass skip_validation to mask a real error.
  5. Pre-flight on one device. Call thinr_product_playbook_run with a single device (plus dry_run: true or check: true for a read-only preview) to confirm the steps behave as expected.
  6. Roll out. Finally, thinr_product_playbook_rollout rolls the playbook across the fleet in batches with a failure-rate kill switch, and writes a persistent report to playbooks/runs/ in the product file storage.

Example prompt the user can give the assistant:

"On product edge-gateways, create a playbook called nginx-upgrade that updates the nginx package, renders /etc/nginx/nginx.conf from a template, and restarts nginx only if the config check passes. Make worker_connections an overridable integer variable, default 1024. Then try it on edge-gw-17 in check mode."

A well-behaved assistant translates this into:

  • thinr_playbook_schema → read the authoring contract.
  • Draft YAML → thinr_playbook_validate until clean.
  • thinr_product_playbook_write with product="edge-gateways", name="nginx-upgrade", and the validated content.
  • thinr_product_playbook_run with device="edge-gw-17" and check: true to preview what would change.

After that a second message like "looks good, roll it out to staging" maps to thinr_product_playbook_rollout with the matching group / filter and a conservative batch_size.

Per-call controls

The server starts with no per-session pin: every tool call must say which device it's for, and may optionally override the account or profile for that single call.

  • device — required for any device-scoped tool. Use thinr_devices (optionally with query to filter by regex) to discover ids.
  • user — optional admin impersonation; falls back to the authenticated user of the active profile.
  • profile — optional. Switches the active profile for that single tool call so an agent can hop between prod and staging without restarting the server. List the configured profiles with thinr_profiles.

Integrating with Claude Code

Register the server once with the Claude Code CLI:

claude mcp add thinr -s user -- thinr mcp

That's it — a single MCP server entry handles every device of the active profile. Tool calls pass device (and optionally user / profile) per call.

Other MCP hosts follow the same pattern — they just need the command thinr mcp (optionally with -d <deviceId>) over stdio.


Profiles and multi-account use

~/.config/thinr-cli/config.json stores any number of profiles:

{
    "default": "perf.aws.thinger.io",
    "profiles": {
        "perf.aws.thinger.io": { "server": "...", "username": "...", "token": "..." },
        "staging.local": { "server": "...", "username": "...", "token": "..." }
    }
}

The active profile is picked, in order:

  1. --profile <name> flag on the command line.
  2. THINR_PROFILE environment variable.
  3. The default field in the config file.
  4. The sole profile, if there's only one.

Logging in to a new server creates a new profile whose name is the server hostname (so it never clobbers an existing one). Legacy single-profile files from older versions of the CLI are detected on read and migrated in-memory without a manual step.

Use thinr profile use <name> to change the persisted default, or --profile <name> / THINR_PROFILE=<name> for one-off runs.

Development

thinr-cli/
├── bin/           # CLI entry point (thinr)
├── commands/      # Commander subcommand modules
├── lib/           # Core: api, auth, config, device-api, errors,
│                  # output, mcp-server, proxy, console, …
├── package.json
└── README.md

Local development:

npm install
npm link            # makes `thinr` available on $PATH
node bin/thinr.js   # or run directly without linking

Unit tests live under tests/ and run offline:

npm test

Integration tests

The product-playbook end-to-end script at tests/integration/playbook-e2e.js drives a real ThinRemote cloud through the full playbook lifecycle (create product → upload flat/extended/internal playbooks → list → download → replace → reject invalid inputs → optionally run in check mode against a device → delete). Cleanup of the temporary product and its file storage is unconditional (try / finally), even if the run fails halfway.

Prerequisites:

  • An authenticated CLI profile (thinr once, or THINR_PROFILE=<name> pointing at an existing entry in ~/.config/thinr-cli/config.json).
  • The account must be allowed to create products and file storages.

Invocations:

# Offline checks only — parses fixtures, exercises resolveVarScope,
# asserts the dry-run plan. No network access. Safe in CI without
# credentials.
node tests/integration/playbook-e2e.js --offline

# Full flow against the active profile. Creates a throw-away product
# named "playbook-e2e-<timestamp>-<rand>" and deletes it on exit.
node tests/integration/playbook-e2e.js

# Add a test device to exercise `check`-mode execution with and
# without variable overrides. The device must belong to the active
# user; it does NOT need to be bound to the temporary product.
THINR_E2E_DEVICE=<device-id> node tests/integration/playbook-e2e.js

The script only touches resources it creates itself (identified by the playbook-e2e- product-id prefix). If a run is interrupted before the finally cleanup executes — e.g. the process is killed — list leftovers with thinr product list --json and drop each one through the MCP thinr_product_delete tool (from an AI client) or via a Node one-liner reusing deleteProductWithStorage from lib/product.js.

No credentials or tokens are ever written to the repository; the script reads them from the standard CLI profile store.

License

Released under the MIT License.

Copyright © Thinger.io

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.