The thinger command-line interface works with a Thinger.io server from your terminal: devices, products, Data Buckets and dashboard apps. It also runs an MCP server, so an AI client can use your account through the CLI's sign-in. The command reference on this page is generated from the CLI itself.
| Package | Installed from | Version | Source code |
|---|---|---|---|
@thinger-io/cli | npm | 0.3.0 | thinger-io/thinger-cli |
@thinger-io/cli-core | npm | 0.4.2 | thinger-io/thinger-cli-core |
- Install: Install, on this page (
@thinger-io/cli-corecomes with it). To use its MCP server from an AI client, follow Connect an AI agent. - Changelog: CLI changelog: every release, with what it adds, changes and fixes.
Versions 0.x
While the CLI is in 0.x versions, commands and options can still change between minor versions. Check the changelog before you upgrade scripts that depend on it.
Install
The CLI needs Node.js 20 or later. Install it globally from npm:
npm install -g @thinger-io/cliCheck the installed version:
thinger --versionYou can also run any command without installing it, with npx -y @thinger-io/cli <command>.
Sign in
thinger login signs in to a server and stores the session in a profile. Without options it asks for the sign-in method and the server. YOUR_SERVER is iot.thinger.io on the free Cloud account, or your own domain on a private instance. A bare host means HTTPS; give a full URL (http://… or a custom port) for anything else.
| Method | How | What the profile keeps |
|---|---|---|
| Username and password | thinger login, then choose Username and password. The password is always asked interactively. | An access token named Thinger CLI, created in your account with full permissions. thinger logout revokes it. |
| Token | thinger login --server YOUR_SERVER --username YOUR_USER --token YOUR_TOKEN. Works without a terminal, for scripts and CI. | The access token you gave. Here YOUR_TOKEN is an access token of your account. |
| OAuth | thinger login --oauth --client-id <id>. Opens the browser (OAuth device flow). Needs an OAuth client registered on the server. | The OAuth access and refresh tokens. |
When a profile is active, thinger login signs in again on that same profile. To add another server, use thinger profile add.
thinger logout forgets the current profile and revokes the token the CLI created at sign-in; --keep-token keeps it on the server, and --all forgets every profile.
Self-signed certificates are accepted only for localhost and 127.0.0.1, or when THINGER_INSECURE=1 is set.
Profiles
A profile is one server and one account. Use profiles to work with several servers or accounts:
thinger profile add staging --server YOUR_SERVER --username YOUR_USER --token YOUR_TOKEN
thinger profile list
thinger profile use staging
thinger device list --profile productionThe CLI picks the profile in this order:
- The
--profile <name>option of the command. - The
THINGER_PROFILEenvironment variable. THINGER_TOKENandTHINGER_SERVER, when both are set (see Environment variables).- The default profile, set with
thinger profile use. The first profile you create becomes the default.
Environment variables
| Variable | Effect |
|---|---|
THINGER_TOKEN | Access token to use instead of the saved profiles. Set it together with THINGER_SERVER. The token must be a JWT access token: the CLI reads the username from it. |
THINGER_SERVER | Server for THINGER_TOKEN: a host, or a URL for HTTP or a custom port. Setting THINGER_TOKEN without it is an error. |
THINGER_PROFILE | Profile to use when the command has no --profile. |
THINGER_INSECURE | Set it to 1 to accept self-signed certificates on any host. Any other value is ignored. |
THINGER_TOKEN and THINGER_SERVER take precedence over the saved default profile, but not over a profile you name with --profile or THINGER_PROFILE. They are the usual way to run the CLI in CI:
THINGER_SERVER=YOUR_SERVER THINGER_TOKEN=YOUR_TOKEN thinger device list --jsonJSON output
Commands that take -j, --json print a JSON envelope on standard output instead of tables and messages, so scripts and agents can parse the result. login, logout and mcp do not take it.
On success, data holds the result. For example, thinger device set-product YOUR_DEVICE "" --json (which removes the device from its product) prints:
{
"ok": true,
"data": { "device": "YOUR_DEVICE", "product": null }
}On failure, the envelope carries the error message and a code:
{
"ok": false,
"error": {
"message": "Unauthorized. Your token may have expired. Please reconfigure.",
"code": "unauthorized"
}
}code | Meaning |
|---|---|
unauthorized | The token is missing, expired or not valid for the server (HTTP 401), or it is not allowed to do this (HTTP 403, Forbidden). |
not_found | The device, product, bucket or other resource does not exist. |
already_exists | A resource with that ID already exists. |
input_error | An argument or option is not valid. |
not_configured | There is no profile: run thinger login. |
needs_confirm | A destructive command needs --yes in JSON mode or when there is no terminal to confirm it. |
server_error | The server answered with an error. |
network_error | The server could not be reached. |
timeout | The server did not answer in time. |
error | Any other error. |
The exit status is 0 on success and 1 on error.
Configuration file
Profiles are stored in ~/.config/thinger-cli/config.json in your home folder, on macOS, Linux and Windows alike. The folder is readable only by your user and the file is written with owner-only permissions, because it contains access tokens. Do not share it or commit it.
Global options
These options work with every command:
thinger [options] [command]| Option | Description |
|---|---|
-V, --version | output the version number |
-u, --user <username> | API user override (admin impersonation) |
--profile <name> | Configuration profile to use (defaults to the saved default) |
-h, --help | Display help for the command. |
Add --help to any command or group to print its usage, for example thinger dashboard publish --help.
Commands
| Command | Description |
|---|---|
thinger device | Devices: list, inspect, create, delete, properties and resources |
thinger product | Products: list, inspect, create, delete and product properties |
thinger bucket | Data buckets: list them and read their time series |
thinger dashboard | Dashboard apps (HTML + the dashboard SDK): develop them on their dev server and publish them |
thinger login | Sign in: re-authenticate the current profile, or create the first one. Use profile add for more servers. |
thinger logout | Forget the current profile (or --all) and revoke the API token the CLI created |
thinger profile | Manage configuration profiles |
thinger mcp | Start the MCP server (stdio) for AI tool integration |
thinger device
Devices: list, inspect, create, delete, properties and resources
thinger device [options] [command]| Command | Description |
|---|---|
thinger device list | List devices, optionally filtered by name/id pattern (case-insensitive regex) |
thinger device info | Show the server record of a device (type, product, connection…) |
thinger device create | Register a device; Generic/MQTT ones get random credentials unless given |
thinger device delete | Delete a device record from the platform (irreversible) |
thinger device set-product | Assign a device to a product ("" to unassign) |
thinger device property | List device properties, or read one by ID |
thinger device property-set | Create or overwrite a device property (value parsed as JSON) |
thinger device resource | List device resources, or call one by name (the device must be online) |
thinger device list
List devices, optionally filtered by name/id pattern (case-insensitive regex)
thinger device list [options] [pattern]| Argument | Required |
|---|---|
pattern | No |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger device info
Show the server record of a device (type, product, connection…)
thinger device info [options] <deviceId>| Argument | Required |
|---|---|
deviceId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger device create
Register a device; Generic/MQTT ones get random credentials unless given
thinger device create [options] <deviceId>| Argument | Required |
|---|---|
deviceId | Yes |
| Option | Description | Default |
|---|---|---|
-t, --type <type> | Device type: Generic, MQTT, HTTP, Virtual, Subdevice | Generic |
-c, --credentials <credentials> | Device credentials (6+ characters) | |
-n, --name <name> | Display name | |
-d, --description <text> | Description | |
-p, --product <productId> | Assign to a product | |
-j, --json | Output as JSON |
thinger device delete
Delete a device record from the platform (irreversible)
thinger device delete [options] <deviceId>| Argument | Required |
|---|---|
deviceId | Yes |
| Option | Description |
|---|---|
-y, --yes | Skip the interactive confirmation prompt |
-j, --json | Output as JSON |
thinger device set-product
Assign a device to a product ("" to unassign)
thinger device set-product [options] <deviceId> <productId>| Argument | Required |
|---|---|
deviceId | Yes |
productId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger device property
List device properties, or read one by ID
thinger device property [options] <deviceId> [propertyId]| Argument | Required |
|---|---|
deviceId | Yes |
propertyId | No |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
-f, --field <field> | Extract a sub-field (dot path, e.g. data.value) |
thinger device property-set
Create or overwrite a device property (value parsed as JSON)
thinger device property-set [options] <deviceId> <propertyId> <value>| Argument | Required |
|---|---|
deviceId | Yes |
propertyId | Yes |
value | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger device resource
List device resources, or call one by name (the device must be online)
thinger device resource [options] <deviceId> [resource]| Argument | Required |
|---|---|
deviceId | Yes |
resource | No |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
-f, --field <field> | Extract a sub-field from the result (dot path) |
-i, --input <key=value> | Resource input, value parsed as JSON (repeatable) |
thinger product
Products: list, inspect, create, delete and product properties
thinger product [options] [command]| Command | Description |
|---|---|
thinger product list | List every product configured on the account |
thinger product info | Print the full product document (config and profile) as JSON |
thinger product create | Create a product, bare or from a JSON document (config + profile) |
thinger product delete | Delete a product (its devices stay, unassigned) |
thinger product property-list | List the names of the properties attached to a product |
thinger product property-get | Read a property attached to a product |
thinger product property-set | Create or overwrite a product property (value parsed as JSON) |
thinger product property-delete | Delete a product property (idempotent) |
thinger product list
List every product configured on the account
thinger product list [options]| Option | Description | Default |
|---|---|---|
-j, --json | Output as JSON | |
-c, --concurrency <n> | Max parallel API-resource lookups (default: 10) | 10 |
thinger product info
Print the full product document (config and profile) as JSON
thinger product info [options] <productId>| Argument | Required |
|---|---|
productId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON envelope |
thinger product create
Create a product, bare or from a JSON document (config + profile)
thinger product create [options] <productId>| Argument | Required |
|---|---|
productId | Yes |
| Option | Description |
|---|---|
-n, --name <name> | Display name |
-d, --description <text> | Description |
--from <file> | JSON document with config and/or profile ("-" for stdin) |
-j, --json | Output as JSON |
thinger product delete
Delete a product (its devices stay, unassigned)
thinger product delete [options] <productId>| Argument | Required |
|---|---|
productId | Yes |
| Option | Description |
|---|---|
-y, --yes | Skip the interactive confirmation prompt |
-j, --json | Output as JSON |
thinger product property-list
List the names of the properties attached to a product
thinger product property-list [options] <productId>| Argument | Required |
|---|---|
productId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger product property-get
Read a property attached to a product
thinger product property-get [options] <productId> <propertyId>| Argument | Required |
|---|---|
productId | Yes |
propertyId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger product property-set
Create or overwrite a product property (value parsed as JSON)
thinger product property-set [options] <productId> <propertyId> <value>| Argument | Required |
|---|---|
productId | Yes |
propertyId | Yes |
value | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger product property-delete
Delete a product property (idempotent)
thinger product property-delete [options] <productId> <propertyId>| Argument | Required |
|---|---|
productId | Yes |
propertyId | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger bucket
Data buckets: list them and read their time series
thinger bucket [options] [command]| Command | Description |
|---|---|
thinger bucket list | List the data buckets of the account |
thinger bucket read | Read samples from a bucket (latest first by default) |
thinger bucket list
List the data buckets of the account
thinger bucket list [options]| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger bucket read
Read samples from a bucket (latest first by default)
thinger bucket read [options] <bucketId>| Argument | Required |
|---|---|
bucketId | Yes |
| Option | Description | Default |
|---|---|---|
-d, --device <deviceId> | Series a product bucket keeps for this device | |
-n, --items <n> | Number of samples (default: 10) | 10 |
-m, --minutes <n> | Only the last N minutes | |
--sort <order> | asc or desc (default: desc) | desc |
--agg <window> | Aggregation window, e.g. 5m, 1h | |
--agg-type <reducer> | Reducer: mean, max, min, sum, count, first, last… | |
--fields <list> | Comma-separated fields to return | |
--group-by <key> | Group key, e.g. device | |
-j, --json | Output as JSON |
thinger dashboard
Dashboard apps (HTML + the dashboard SDK): develop them on their dev server and publish them
thinger dashboard [options] [command]| Command | Description |
|---|---|
thinger dashboard init | Create a dashboard app (TypeScript, on @thinger-io/dashboard-sdk) from a template of the SDK |
thinger dashboard dev | Link that opens the app on its own dev server (ng serve, Vite…) against the server, with a temporary token |
thinger dashboard publish | Upload an app to a public storage and point dashboards at it |
thinger dashboard builds dashboards as web apps on the Dashboard SDK. init creates the project from a template of the SDK, dev prints a link that opens the app from its own dev server as a standalone page connected to your server, with a temporary token, and publish uploads the build to a public file storage and points the dashboards at it. See Dashboard apps for the workflow.
init writes a thinger.json manifest in the project folder. dev and publish read it from the current folder, or from the file given with --config; their options override it:
{
"dashboard": {
"dir": "dist",
"storage": "app_greenhouse_1a2b",
"prune": true,
"to": [{ "type": "device", "id": "YOUR_DEVICE" }],
"dev": { "url": "http://localhost:5173/" }
}
}| Field | Content |
|---|---|
dir | Build folder that publish uploads, relative to the manifest. It must be inside the project folder. |
storage | File storage the app is published to. publish creates it, public, if it does not exist. |
prune | true deletes files of the storage that are not in the upload. |
to | Destinations whose dashboard opens the app: { "type": "device", "id": "…" } with type device, product, asset_type, asset_group or project, or { "dashboard": "…" } for a dashboard. Each one can set its own entry and route. |
dev | Options of thinger dashboard dev: url of the dev server, context, route and hours the temporary token lasts. |
thinger dashboard init greenhouse --to device:YOUR_DEVICE --install
cd greenhouse
npm run build
thinger dashboard publishthinger dashboard init
Create a dashboard app (TypeScript, on @thinger-io/dashboard-sdk) from a template of the SDK
thinger dashboard init [options] <dir>| Argument | Required |
|---|---|
dir | Yes |
| Option | Description |
|---|---|
-t, --template <name> | Template of the SDK package: react, angular… (default react) |
--to <destination> | Where it publishes: device:<id>, product:<id>, dashboard:<id>… |
-s, --storage <id> | Storage it publishes to (default app_<folder>_<random>) |
--from <package> | SDK package to take the template from: a version or tag on npm, or a folder or .tgz |
--ci | Add a GitHub workflow that publishes it on every push to main |
--install | Run npm install |
-j, --json | Output as JSON |
thinger dashboard dev
Link that opens the app on its own dev server (ng serve, Vite…) against the server, with a temporary token
thinger dashboard dev [options] [url]| Argument | Required |
|---|---|
url | No |
| Option | Description |
|---|---|
--to <destination> | Context of a destination: device:<id>, product:<id>, dashboard:<id>… |
--device <id> | Device in the SDK context |
--product <id> | Product in the SDK context |
--asset-type <id> | Asset type in the SDK context |
--asset-group <id> | Asset group in the SDK context |
--project <id> | Project the app runs in |
--route <route> | Initial route, e.g. /devices |
--hours <hours> | Hours the token lasts (default 8) |
--open | Open the link in the browser |
--allow-remote | Allow an app url that is not on this machine (the link carries a full-access token) |
--config <file> | Manifest (default thinger.json) |
-j, --json | Output as JSON |
thinger dashboard publish
Upload an app to a public storage and point dashboards at it
thinger dashboard publish [options] [dir]| Argument | Required |
|---|---|
dir | No |
| Option | Description |
|---|---|
-s, --storage <id> | Storage of the app (created public if missing) |
--to <destination> | Dashboard to point (repeatable): dashboard:<id>, device:<id>, product:<id>, asset_type:<id>, asset_group:<id>, project:<id> |
--entry <page> | Page the destinations open (default index.html) |
--route <route> | Initial route of the destinations |
--name <name> | Name of a collection dashboard |
--prune | Delete files of the storage that are not in this upload |
--no-prune | Keep the storage's other files (overrides the manifest) |
--bump | Make consoles that have the app open reload it at once |
--preview [hours] | Also print standalone preview links (read-only, 1 h by default) |
--no-point | Upload without pointing the destinations (preview links only) |
--config <file> | Manifest (default thinger.json) |
-j, --json | Output as JSON |
thinger login
Sign in: re-authenticate the current profile, or create the first one. Use profile add for more servers.
thinger login [options]| Option | Description |
|---|---|
--server <server> | Server host (or URL for http / custom ports) |
--username <username> | Account username |
--token <token> | Existing API token (non-interactive login with --server and --username) |
--oauth | Sign in through the browser (OAuth device flow) |
--client-id <id> | OAuth client id registered on the server |
thinger logout
Forget the current profile (or --all) and revoke the API token the CLI created
thinger logout [options]| Option | Description |
|---|---|
--all | Forget every profile |
--keep-token | Do not revoke the API token on the server |
thinger profile
Manage configuration profiles
thinger profile [options] [command]| Command | Description |
|---|---|
thinger profile list | List configured profiles |
thinger profile current | Print the active profile |
thinger profile use | Set the default profile |
thinger profile add | Add a profile for another server (interactive, or with --server/--username/--token) |
thinger profile delete | Remove a profile |
thinger profile list
List configured profiles
thinger profile list [options]| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger profile current
Print the active profile
thinger profile current [options]| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger profile use
Set the default profile
thinger profile use [options] <name>| Argument | Required |
|---|---|
name | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger profile add
Add a profile for another server (interactive, or with --server/--username/--token)
thinger profile add [options] [name]| Argument | Required |
|---|---|
name | No |
| Option | Description |
|---|---|
--no-activate | Do not set the new profile as default |
--server <server> | Server host (or URL for http / custom ports) |
--username <username> | Account username |
--token <token> | Existing API token (non-interactive with --server and --username) |
--oauth | Sign in through the browser (OAuth device flow) |
--client-id <id> | OAuth client id registered on the server |
-j, --json | Output as JSON |
thinger profile delete
Remove a profile
thinger profile delete [options] <name>| Argument | Required |
|---|---|
name | Yes |
| Option | Description |
|---|---|
-j, --json | Output as JSON |
thinger mcp
Start the MCP server (stdio) for AI tool integration
thinger mcp [options]thinger mcp starts an MCP server over standard input and output with the session of the active profile, so an AI client can work with your account. Sign in first with thinger login. To register it in Claude Code for all your projects:
claude mcp add --scope user thinger -- thinger mcpOther MCP clients take the same command: thinger with the argument mcp. How to set up each client (Claude Desktop, VS Code, Cursor and others) is in Connect an AI agent, and the tools the server offers are listed in MCP tools.
Without installing the CLI
npx -y @thinger-io/cli mcp runs the server without installing the CLI, for example in Claude Code or a CI job. Desktop apps are more reliable with the installed thinger command: see Connect an AI agent.