@thinremote/thinr-cli 1.2.0-alpha.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/README.md +235 -30
- package/bin/thinr.js +43 -25
- package/commands/_shared.js +154 -0
- package/commands/dashboard.js +17 -0
- package/commands/device/_shared.js +17 -34
- package/commands/device/console.js +1 -0
- package/commands/device/delete.js +53 -0
- package/commands/device/exec.js +7 -6
- package/commands/device/fs.js +193 -0
- package/commands/device/index.js +9 -0
- package/commands/device/list.js +35 -23
- package/commands/device/logs.js +178 -0
- package/commands/device/property.js +30 -28
- package/commands/device/proxy.js +3 -0
- package/commands/device/resource.js +30 -29
- package/commands/device/status.js +35 -14
- package/commands/device/transfer.js +236 -0
- package/commands/device/update.js +15 -10
- package/commands/fleet/index.js +17 -0
- package/commands/fleet/upgrade.js +275 -0
- package/commands/logout.js +10 -11
- package/commands/playbook/_shared.js +12 -0
- package/commands/playbook/index.js +18 -0
- package/commands/playbook/run.js +302 -0
- package/commands/playbook/validate.js +38 -0
- package/commands/product/_shared.js +8 -0
- package/commands/product/exec.js +243 -0
- package/commands/product/fs.js +218 -0
- package/commands/product/index.js +35 -0
- package/commands/product/list.js +86 -0
- package/commands/product/logs.js +289 -0
- package/commands/product/metric.js +124 -0
- package/commands/product/playbook.js +911 -0
- package/commands/product/property.js +76 -0
- package/commands/product/props.js +125 -0
- package/commands/product/push.js +184 -0
- package/commands/product/resource.js +76 -0
- package/commands/product/status.js +213 -0
- package/commands/profile.js +76 -8
- package/dist/dashboard.js +5644 -0
- package/lib/agent-releases.js +59 -0
- package/lib/agent-versions.js +73 -0
- package/lib/alarms.js +252 -0
- package/lib/api.js +8 -2
- package/lib/auth.js +68 -20
- package/lib/bucket.js +87 -0
- package/lib/command-stream.js +213 -0
- package/lib/config.js +5 -3
- package/lib/console.js +19 -5
- package/lib/constants.js +83 -0
- package/lib/dashboard/event-stream.js +239 -0
- package/lib/dashboard/metric-aggregate.js +64 -0
- package/lib/dashboard/metric-filter.js +39 -0
- package/lib/debug-log.js +93 -0
- package/lib/device-api.js +131 -148
- package/lib/devices.js +18 -21
- package/lib/errors.js +12 -0
- package/lib/fleet-upgrade.js +106 -0
- package/lib/format.js +114 -0
- package/lib/mcp/registry.js +14 -0
- package/lib/mcp/tools-alarms.js +510 -0
- package/lib/mcp/tools-buckets.js +103 -0
- package/lib/mcp/tools-devices.js +33 -3
- package/lib/mcp/tools-exec.js +30 -17
- package/lib/mcp/tools-filesystem.js +102 -6
- package/lib/mcp/tools-install.js +153 -0
- package/lib/mcp/tools-logs.js +225 -0
- package/lib/mcp/tools-playbook.js +211 -0
- package/lib/mcp/tools-products.js +1226 -105
- package/lib/mcp/tools-profile.js +634 -0
- package/lib/mcp/tools-tokens.js +371 -0
- package/lib/monitoring.js +26 -1
- package/lib/playbook/actions.js +447 -0
- package/lib/playbook/expression.js +302 -0
- package/lib/playbook/fleet.js +175 -0
- package/lib/playbook/loader.js +247 -0
- package/lib/playbook/runner.js +300 -0
- package/lib/playbook/schema.js +209 -0
- package/lib/playbook/vars.js +209 -0
- package/lib/product/log-presets.js +196 -0
- package/lib/product/logs.js +485 -0
- package/lib/product-orchestrator.js +124 -0
- package/lib/product.js +523 -20
- package/lib/profile.js +665 -0
- package/lib/property.js +7 -40
- package/lib/proxy.js +9 -8
- package/lib/resource.js +10 -14
- package/lib/status.js +78 -37
- package/lib/tokens.js +374 -0
- package/package.json +14 -3
- package/commands/product.js +0 -407
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.
|
|
3
|
+
## [1.3.0] - 2026-09-13
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
6
|
|
|
7
|
+
- `thinr login` — re-authenticate the current profile in place, without
|
|
8
|
+
deleting and recreating it. Renews an expired session in one step and
|
|
9
|
+
defaults the server prompt to the profile's existing server.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Session-expiry errors now point to the working command: "Session
|
|
14
|
+
expired. Run `thinr login` to sign in again." (previously suggested
|
|
15
|
+
`thinr`, which opens the dashboard instead of re-authenticating).
|
|
16
|
+
- More robust token refresh for WebSocket sessions: the interactive
|
|
17
|
+
console and streaming `exec` now refresh an expired access token
|
|
18
|
+
before the handshake and retry once on a 401, matching the
|
|
19
|
+
refresh-on-401 behaviour of HTTP calls.
|
|
20
|
+
|
|
21
|
+
## [1.2.0] - 2026-06-10
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `thinr` with no arguments now launches the interactive dashboard
|
|
26
|
+
directly when stdout is a TTY (and a configuration exists), so the
|
|
27
|
+
fleet view is one keystroke away. Non-TTY contexts (pipes, scripts)
|
|
28
|
+
fall back to `--help`. First-time users still get the auth flow,
|
|
29
|
+
then drop straight into the dashboard.
|
|
30
|
+
- TUI dashboard: alarms panel on the overview tab, with rule- and
|
|
31
|
+
instance-level views, severity colouring and live updates over the
|
|
32
|
+
same event stream the rest of the dashboard already uses.
|
|
33
|
+
- `thinr device delete <deviceId>` — remove a device record from the
|
|
34
|
+
platform. Interactive `confirm` by default, `--yes` to skip in
|
|
35
|
+
scripts, idempotent on a missing device. Useful after re-provisioning
|
|
36
|
+
an agent under a new hostname leaves the old `ip-…` record offline.
|
|
37
|
+
- MCP: `thinr_device_delete` — same operation for AI clients.
|
|
38
|
+
- MCP: `thinr_push` and `thinr_pull` — upload a local file to a
|
|
39
|
+
device and download a remote file to local disk, respectively.
|
|
40
|
+
Rounds out filesystem parity with the CLI so an agent can move
|
|
41
|
+
binary or large payloads that don't fit inline in `thinr_write`.
|
|
42
|
+
- MCP: alarms tools (`thinr_alarm_instances`,
|
|
43
|
+
`thinr_alarm_instance_get`, `thinr_alarm_instance_stats`,
|
|
44
|
+
`thinr_alarm_instance_update`, `thinr_alarm_instance_delete`,
|
|
45
|
+
`thinr_alarm_rules`, `thinr_alarm_rule_read`, `thinr_alarm_rule_write`,
|
|
46
|
+
`thinr_alarm_rule_delete`) — list, inspect and manage alarm
|
|
47
|
+
instances and rules from AI clients, with state/severity parsing
|
|
48
|
+
shared with the dashboard.
|
|
49
|
+
- MCP: product profile config tools (buckets, properties, API
|
|
50
|
+
resources) and access-token tools (user-level and device-level).
|
|
7
51
|
- `thinr product exec <productId> <command...>` — run a shell command
|
|
8
52
|
in parallel on every active device of a product, with bounded
|
|
9
53
|
concurrency (`-c, --concurrency`, default 10), per-device timeout
|
|
@@ -23,9 +67,27 @@
|
|
|
23
67
|
|
|
24
68
|
### Changed
|
|
25
69
|
|
|
70
|
+
- Unified filesystem verbs across CLI, MCP and playbooks so the same
|
|
71
|
+
eight nouns mean the same thing everywhere: `read` / `write`
|
|
72
|
+
(inline content) · `push` / `pull` (local ↔ remote file transfer)
|
|
73
|
+
· `ls` · `mkdir` · `rm` · `mv`.
|
|
74
|
+
- CLI: `thinr device cat` is now `thinr device read` (`cat` kept as
|
|
75
|
+
a hidden alias). New `thinr device write <deviceId> <path>
|
|
76
|
+
[content]` accepts the payload as an argument or on stdin.
|
|
77
|
+
- MCP: `thinr_delete` renamed to `thinr_rm`, `thinr_move` renamed to
|
|
78
|
+
`thinr_mv`. No backwards-compatibility aliases.
|
|
79
|
+
- Playbook actions: `delete` → `rm`, `move` → `mv`. The `write`
|
|
80
|
+
action now only accepts inline `content`; use the new `push`
|
|
81
|
+
action to upload a local file (`source`, `destination`). New
|
|
82
|
+
`pull` action downloads a remote file to the local disk.
|
|
26
83
|
- `cli-table3` added as a direct dependency to render the
|
|
27
84
|
product-exec summary.
|
|
28
85
|
|
|
86
|
+
### Removed
|
|
87
|
+
|
|
88
|
+
- `thinr dashboard` subcommand. The dashboard is now the default for
|
|
89
|
+
bare `thinr`; the explicit subcommand had not shipped to anyone yet.
|
|
90
|
+
|
|
29
91
|
## [1.1.0] - 2026-04-16
|
|
30
92
|
|
|
31
93
|
Major release: MCP server for AI clients, subcommand-first CLI, JSON
|
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ Two operating modes:
|
|
|
12
12
|
|
|
13
13
|
- **CLI** — interactive or scripted device management from a shell.
|
|
14
14
|
- **MCP server** — a stdio [Model Context Protocol][mcp] server that
|
|
15
|
-
exposes the same operations as typed tools for AI clients like
|
|
16
|
-
Claude Code.
|
|
15
|
+
exposes the same operations as 80+ typed tools for AI clients like
|
|
16
|
+
Claude Code, Claude Desktop and Cursor.
|
|
17
17
|
|
|
18
18
|
Both modes share the same auth, profiles, and device API layer, so
|
|
19
19
|
you configure the tool once and use it in whichever mode fits the
|
|
@@ -30,13 +30,16 @@ task at hand.
|
|
|
30
30
|
- [JSON output](#json-output)
|
|
31
31
|
- [`thinr device <action> [<deviceId>]`](#thinr-device-action-deviceid)
|
|
32
32
|
- [`thinr product <action> <productId>`](#thinr-product-action-productid)
|
|
33
|
+
- [`thinr playbook <action>`](#thinr-playbook-action)
|
|
33
34
|
- [`thinr profile`](#thinr-profile)
|
|
34
35
|
- [`thinr logout`](#thinr-logout)
|
|
35
36
|
- [Part 2 — MCP server](#part-2--mcp-server)
|
|
36
37
|
- [Starting the server](#starting-the-server)
|
|
37
38
|
- [Tool catalog](#tool-catalog)
|
|
39
|
+
- [Authoring playbooks from natural language](#authoring-playbooks-from-natural-language)
|
|
38
40
|
- [Per-call controls](#per-call-controls)
|
|
39
41
|
- [Integrating with Claude Code](#integrating-with-claude-code)
|
|
42
|
+
- [Integrating with Claude Desktop, Cursor and other MCP hosts](#integrating-with-claude-desktop-cursor-and-other-mcp-hosts)
|
|
40
43
|
- [Profiles and multi-account use](#profiles-and-multi-account-use)
|
|
41
44
|
- [Development](#development)
|
|
42
45
|
- [License](#license)
|
|
@@ -337,6 +340,69 @@ Also exposed as an MCP tool (`thinr_product_exec`) with the same
|
|
|
337
340
|
parameters, so agents can drive a fleet operation with a single call
|
|
338
341
|
instead of looping `thinr_exec` per device.
|
|
339
342
|
|
|
343
|
+
#### `playbook`
|
|
344
|
+
|
|
345
|
+
Manage playbooks stored on a product and run them against one device
|
|
346
|
+
or the whole fleet:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
thinr product playbook list <productId> [--json]
|
|
350
|
+
thinr product playbook upload <productId> <name> [file] [--description <text>] [--skip-validation] [--json]
|
|
351
|
+
thinr product playbook download <productId> <name> [-o <file>] [--json]
|
|
352
|
+
thinr product playbook delete <productId> <name> [--json]
|
|
353
|
+
thinr product playbook run <productId> <name> (--device <id> | --fleet) [options]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`upload` reads the YAML from a file, or from stdin when the path is
|
|
357
|
+
omitted or `-`. The document is validated against the playbook schema
|
|
358
|
+
before being written to the product storage; an invalid document is
|
|
359
|
+
rejected with a descriptive error and nothing is uploaded. Pass
|
|
360
|
+
`--skip-validation` only to work around a schema-runtime gap (do not
|
|
361
|
+
use it to bypass real errors).
|
|
362
|
+
|
|
363
|
+
`run` targets either a single device (`--device <id>`) or the whole
|
|
364
|
+
fleet (`--fleet`, progressive batch rollout):
|
|
365
|
+
|
|
366
|
+
- `-v, --var <key=value>` — override an overridable playbook variable
|
|
367
|
+
(repeatable; values are coerced to the declared type).
|
|
368
|
+
- `--vars-file <path>` — load variable overrides from a YAML/JSON file.
|
|
369
|
+
- `--dry-run` — print the resolved plan without contacting any device.
|
|
370
|
+
- `--check` — contact devices read-only and report what each step would
|
|
371
|
+
change.
|
|
372
|
+
- `--yes` / `-y` — skip the interactive confirmation prompt.
|
|
373
|
+
|
|
374
|
+
Fleet-only options:
|
|
375
|
+
|
|
376
|
+
- `--batch-size <n>` — devices attempted in parallel per batch
|
|
377
|
+
(default: 5).
|
|
378
|
+
- `--failure-threshold <p>` — abort rollout when cumulative failure
|
|
379
|
+
rate reaches P% (default: 25).
|
|
380
|
+
- `-g, --group <id>` — restrict to an asset group.
|
|
381
|
+
- `--filter <key=value>` — extra server-side filter (repeatable).
|
|
382
|
+
- `--include-offline` — attempt offline devices too (default: active
|
|
383
|
+
only).
|
|
384
|
+
|
|
385
|
+
Each fleet rollout writes a persistent JSON report to
|
|
386
|
+
`playbooks/runs/<timestamp>-<name>-<user>.json` in the product's file
|
|
387
|
+
storage, capturing resolved variables, per-device outcomes, and any
|
|
388
|
+
abort reason.
|
|
389
|
+
|
|
390
|
+
### `thinr playbook <action>`
|
|
391
|
+
|
|
392
|
+
Run or validate a local playbook YAML file (not stored on a product).
|
|
393
|
+
Useful for one-shot or ad-hoc runs:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
thinr playbook validate <file>
|
|
397
|
+
thinr playbook run <file> [--dry-run] [--check] [options]
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`validate` parses the YAML and reports any schema errors. `run`
|
|
401
|
+
resolves the target block declared inside the playbook and executes
|
|
402
|
+
it; the same variable-override flags as `thinr product playbook run`
|
|
403
|
+
apply. For playbooks that should be reusable across users, upload
|
|
404
|
+
them to their product with `thinr product playbook upload` instead.
|
|
405
|
+
|
|
340
406
|
### `thinr profile`
|
|
341
407
|
|
|
342
408
|
Manage the profile store (see [Profiles and multi-account
|
|
@@ -363,46 +429,116 @@ Other profiles are left untouched.
|
|
|
363
429
|
|
|
364
430
|
## Part 2 — MCP server
|
|
365
431
|
|
|
366
|
-
`thinr` ships an [MCP][mcp] server that exposes the CLI's device
|
|
367
|
-
operations as a set of typed tools. AI clients (Claude Code,
|
|
368
|
-
Desktop, and any other MCP-compatible host) can
|
|
369
|
-
run shell commands, read/write files, call resources,
|
|
370
|
-
products
|
|
371
|
-
|
|
432
|
+
`thinr` ships an [MCP][mcp] server that exposes the CLI's device and
|
|
433
|
+
fleet operations as a set of 80+ typed tools. AI clients (Claude Code,
|
|
434
|
+
Claude Desktop, Cursor, and any other MCP-compatible host) can list
|
|
435
|
+
devices, run shell commands, read/write files, call resources, manage
|
|
436
|
+
products, author and roll out playbooks, triage alarms and rotate
|
|
437
|
+
access tokens through the same API the CLI uses — with stable schemas
|
|
438
|
+
and a consistent error contract.
|
|
372
439
|
|
|
373
440
|
### Starting the server
|
|
374
441
|
|
|
375
442
|
```bash
|
|
376
|
-
thinr mcp
|
|
443
|
+
thinr mcp
|
|
377
444
|
```
|
|
378
445
|
|
|
379
|
-
The server speaks [MCP over stdio][mcp-stdio].
|
|
380
|
-
|
|
381
|
-
|
|
446
|
+
The server speaks [MCP over stdio][mcp-stdio]. Every tool call carries
|
|
447
|
+
its own `device`, `user` and `profile` arguments, so a single running
|
|
448
|
+
server handles any device of any configured environment without a
|
|
449
|
+
restart.
|
|
382
450
|
|
|
383
451
|
[mcp-stdio]: https://modelcontextprotocol.io/specification/server/transport#stdio
|
|
384
452
|
|
|
385
453
|
### Tool catalog
|
|
386
454
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
target any device
|
|
390
|
-
|
|
391
|
-
| Area
|
|
392
|
-
|
|
|
393
|
-
| Discovery
|
|
394
|
-
|
|
|
395
|
-
|
|
|
396
|
-
|
|
|
397
|
-
|
|
|
398
|
-
|
|
|
399
|
-
|
|
|
400
|
-
|
|
|
401
|
-
|
|
|
455
|
+
Grouped by capability. Every tool accepts optional `device` (when
|
|
456
|
+
relevant), `user` and `profile` arguments, so a single session can
|
|
457
|
+
target any device, account or environment without restart.
|
|
458
|
+
|
|
459
|
+
| Area | Tools |
|
|
460
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
461
|
+
| Discovery | `thinr_devices` (with optional `query` for regex filtering), `thinr_device_info`, `thinr_profiles` |
|
|
462
|
+
| Provisioning and lifecycle | `thinr_agent_install_command`, `thinr_device_set_product`, `thinr_device_delete` |
|
|
463
|
+
| Shell | `thinr_exec` (buffered), with streaming stdout/stderr |
|
|
464
|
+
| Filesystem | `thinr_read`, `thinr_write`, `thinr_push`, `thinr_pull`, `thinr_ls`, `thinr_mkdir`, `thinr_rm`, `thinr_mv` |
|
|
465
|
+
| Resources | `thinr_resource_list` (with `in`/`out` schemas), `thinr_resource_call` |
|
|
466
|
+
| Properties (device) | `thinr_property_get`, `thinr_property_set` |
|
|
467
|
+
| Scripts (device) | `thinr_script_list`, `thinr_script_write`, `thinr_script_delete` |
|
|
468
|
+
| Monitoring and agent updates | `thinr_monitoring`, `thinr_bucket_read`, `thinr_update` |
|
|
469
|
+
| Alarms | `thinr_alarm_instances`, `thinr_alarm_instance_stats`, `thinr_alarm_instance_get`, `thinr_alarm_instance_update`, `thinr_alarm_instance_delete`, `thinr_alarm_rules`, `thinr_alarm_rule_read/write/delete` |
|
|
470
|
+
| Access tokens | User: `thinr_token_list`, `thinr_token_get`, `thinr_token_create`, `thinr_token_update`, `thinr_token_delete`. Device: `thinr_device_token_list`, `thinr_device_token_create`, `thinr_device_token_delete` |
|
|
471
|
+
| Products | `thinr_products`, `thinr_product_create`, `thinr_product_update`, `thinr_product_delete`, `thinr_product_exec`, `thinr_product_write` |
|
|
472
|
+
| Product scripts | `thinr_product_script_list`, `thinr_product_script_read`, `thinr_product_script_write`, `thinr_product_script_delete` |
|
|
473
|
+
| Product properties | `thinr_product_property_get`, `thinr_product_property_set`, `thinr_product_property_delete` |
|
|
474
|
+
| Product profile schema | `thinr_product_profile_api_{list,get,set,delete}`, `thinr_product_profile_bucket_{list,get,set,delete}`, `thinr_product_profile_property_{list,get,set,delete}` |
|
|
475
|
+
| Product log sources | `thinr_product_logs_list`, `thinr_product_logs_add`, `thinr_product_logs_remove`, `thinr_product_logs_presets`, `thinr_product_logs_set_default` |
|
|
476
|
+
| Product metrics | `thinr_product_metric_list`, `thinr_product_metric_set`, `thinr_product_metric_delete` |
|
|
477
|
+
| Playbooks (authoring) | `thinr_playbook_schema`, `thinr_playbook_validate`, `thinr_playbook_run` (ad-hoc) |
|
|
478
|
+
| 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` |
|
|
402
479
|
|
|
403
480
|
Full input/output schemas are published via standard MCP
|
|
404
481
|
`list_tools`; the client will show them when you connect.
|
|
405
482
|
|
|
483
|
+
### Authoring playbooks from natural language
|
|
484
|
+
|
|
485
|
+
Playbooks are declarative YAML documents — steps like `exec`, `write`,
|
|
486
|
+
`install_package`, `service`, plus a `target` block and a `vars`
|
|
487
|
+
block — that the runner applies to a device or a fleet. With the MCP
|
|
488
|
+
server connected, an AI assistant can turn a natural-language request
|
|
489
|
+
into a validated playbook and store it on the relevant product
|
|
490
|
+
without the user ever writing YAML by hand.
|
|
491
|
+
|
|
492
|
+
The intended authoring loop is:
|
|
493
|
+
|
|
494
|
+
1. **Fetch the schema.** Call `thinr_playbook_schema` once at the
|
|
495
|
+
start of the session to get the authoritative list of top-level
|
|
496
|
+
fields, target block, step actions with their parameters, and the
|
|
497
|
+
two accepted forms of the `vars` block (plain and extended).
|
|
498
|
+
2. **Draft the YAML.** Translate the user's request into a playbook
|
|
499
|
+
draft. Use the extended `vars` form
|
|
500
|
+
(`default` / `description` / `type` / `overridable` / `required`)
|
|
501
|
+
whenever the user hints at knobs the caller may need to tune at
|
|
502
|
+
run time.
|
|
503
|
+
3. **Validate and iterate.** Call `thinr_playbook_validate` with the
|
|
504
|
+
draft. On failure, read the consolidated error message, fix the
|
|
505
|
+
YAML, and call validate again. Never skip this step.
|
|
506
|
+
4. **Upload.** Once validation is clean, call
|
|
507
|
+
`thinr_product_playbook_write` with the product id, a short name
|
|
508
|
+
(letters / digits / underscore / dash), and the validated YAML.
|
|
509
|
+
The tool re-validates server-side; never pass `skip_validation` to
|
|
510
|
+
mask a real error.
|
|
511
|
+
5. **Pre-flight on one device.** Call
|
|
512
|
+
`thinr_product_playbook_run` with a single `device` (plus
|
|
513
|
+
`dry_run: true` or `check: true` for a read-only preview) to
|
|
514
|
+
confirm the steps behave as expected.
|
|
515
|
+
6. **Roll out.** Finally, `thinr_product_playbook_rollout` rolls the
|
|
516
|
+
playbook across the fleet in batches with a failure-rate kill
|
|
517
|
+
switch, and writes a persistent report to `playbooks/runs/` in the
|
|
518
|
+
product file storage.
|
|
519
|
+
|
|
520
|
+
Example prompt the user can give the assistant:
|
|
521
|
+
|
|
522
|
+
> "On product `edge-gateways`, create a playbook called
|
|
523
|
+
> `nginx-upgrade` that updates the nginx package, renders
|
|
524
|
+
> `/etc/nginx/nginx.conf` from a template, and restarts nginx only
|
|
525
|
+
> if the config check passes. Make `worker_connections` an
|
|
526
|
+
> overridable integer variable, default 1024. Then try it on
|
|
527
|
+
> `edge-gw-17` in check mode."
|
|
528
|
+
|
|
529
|
+
A well-behaved assistant translates this into:
|
|
530
|
+
|
|
531
|
+
- `thinr_playbook_schema` → read the authoring contract.
|
|
532
|
+
- Draft YAML → `thinr_playbook_validate` until clean.
|
|
533
|
+
- `thinr_product_playbook_write` with `product="edge-gateways"`,
|
|
534
|
+
`name="nginx-upgrade"`, and the validated content.
|
|
535
|
+
- `thinr_product_playbook_run` with `device="edge-gw-17"` and
|
|
536
|
+
`check: true` to preview what would change.
|
|
537
|
+
|
|
538
|
+
After that a second message like "looks good, roll it out to staging"
|
|
539
|
+
maps to `thinr_product_playbook_rollout` with the matching
|
|
540
|
+
`group` / `filter` and a conservative `batch_size`.
|
|
541
|
+
|
|
406
542
|
### Per-call controls
|
|
407
543
|
|
|
408
544
|
The server starts with no per-session pin: every tool call must say
|
|
@@ -431,8 +567,27 @@ That's it — a single MCP server entry handles every device of the
|
|
|
431
567
|
active profile. Tool calls pass `device` (and optionally `user` /
|
|
432
568
|
`profile`) per call.
|
|
433
569
|
|
|
434
|
-
|
|
435
|
-
|
|
570
|
+
### Integrating with Claude Desktop, Cursor and other MCP hosts
|
|
571
|
+
|
|
572
|
+
Other MCP hosts use a JSON config file (`claude_desktop_config.json`,
|
|
573
|
+
`.cursor/mcp.json`, etc.). The entry shape is the same on every host:
|
|
574
|
+
|
|
575
|
+
```json
|
|
576
|
+
{
|
|
577
|
+
"mcpServers": {
|
|
578
|
+
"thinr": {
|
|
579
|
+
"command": "thinr",
|
|
580
|
+
"args": ["mcp"]
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The `thinr` binary must be on `PATH` for the host to spawn it —
|
|
587
|
+
`npm install -g @thinremote/thinr-cli` puts it there. If the host
|
|
588
|
+
runs under a shell that doesn't load your global npm bin
|
|
589
|
+
(common on macOS GUI apps), point `command` at the absolute path
|
|
590
|
+
(`which thinr`) instead.
|
|
436
591
|
|
|
437
592
|
---
|
|
438
593
|
|
|
@@ -485,7 +640,57 @@ npm link # makes `thinr` available on $PATH
|
|
|
485
640
|
node bin/thinr.js # or run directly without linking
|
|
486
641
|
```
|
|
487
642
|
|
|
488
|
-
|
|
643
|
+
Unit tests live under `tests/` and run offline:
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
npm test
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Integration tests
|
|
650
|
+
|
|
651
|
+
The product-playbook end-to-end script at
|
|
652
|
+
[`tests/integration/playbook-e2e.js`](tests/integration/playbook-e2e.js)
|
|
653
|
+
drives a real ThinRemote cloud through the full playbook lifecycle
|
|
654
|
+
(create product → upload flat/extended/internal playbooks → list →
|
|
655
|
+
download → replace → reject invalid inputs → optionally run in
|
|
656
|
+
`check` mode against a device → delete). Cleanup of the temporary
|
|
657
|
+
product and its file storage is unconditional (`try / finally`),
|
|
658
|
+
even if the run fails halfway.
|
|
659
|
+
|
|
660
|
+
Prerequisites:
|
|
661
|
+
|
|
662
|
+
- An authenticated CLI profile (`thinr` once, or `THINR_PROFILE=<name>`
|
|
663
|
+
pointing at an existing entry in `~/.config/thinr-cli/config.json`).
|
|
664
|
+
- The account must be allowed to create products and file storages.
|
|
665
|
+
|
|
666
|
+
Invocations:
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
# Offline checks only — parses fixtures, exercises resolveVarScope,
|
|
670
|
+
# asserts the dry-run plan. No network access. Safe in CI without
|
|
671
|
+
# credentials.
|
|
672
|
+
node tests/integration/playbook-e2e.js --offline
|
|
673
|
+
|
|
674
|
+
# Full flow against the active profile. Creates a throw-away product
|
|
675
|
+
# named "playbook-e2e-<timestamp>-<rand>" and deletes it on exit.
|
|
676
|
+
node tests/integration/playbook-e2e.js
|
|
677
|
+
|
|
678
|
+
# Add a test device to exercise `check`-mode execution with and
|
|
679
|
+
# without variable overrides. The device must belong to the active
|
|
680
|
+
# user; it does NOT need to be bound to the temporary product.
|
|
681
|
+
THINR_E2E_DEVICE=<device-id> node tests/integration/playbook-e2e.js
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
The script only touches resources it creates itself (identified by
|
|
685
|
+
the `playbook-e2e-` product-id prefix). If a run is interrupted
|
|
686
|
+
before the `finally` cleanup executes — e.g. the process is killed
|
|
687
|
+
— list leftovers with `thinr product list --json` and drop each one
|
|
688
|
+
through the MCP `thinr_product_delete` tool (from an AI client) or
|
|
689
|
+
via a Node one-liner reusing `deleteProductWithStorage` from
|
|
690
|
+
`lib/product.js`.
|
|
691
|
+
|
|
692
|
+
No credentials or tokens are ever written to the repository; the
|
|
693
|
+
script reads them from the standard CLI profile store.
|
|
489
694
|
|
|
490
695
|
## License
|
|
491
696
|
|
package/bin/thinr.js
CHANGED
|
@@ -4,10 +4,9 @@ import { readFileSync } from 'fs';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname, resolve } from 'path';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
7
|
import figlet from 'figlet';
|
|
9
|
-
import
|
|
10
|
-
import { configExists, readConfig, setActiveProfile } from '../lib/config.js';
|
|
8
|
+
import { accent, info } from '../lib/format.js';
|
|
9
|
+
import { configExists, getActiveProfile, readConfig, setActiveProfile } from '../lib/config.js';
|
|
11
10
|
import { detectJsonModeFromArgv } from '../lib/output.js';
|
|
12
11
|
|
|
13
12
|
// Single source of truth for the version: package.json. Avoids the drift
|
|
@@ -34,22 +33,23 @@ for (let i = 2; i < process.argv.length; i++) {
|
|
|
34
33
|
detectJsonModeFromArgv();
|
|
35
34
|
import { authenticate } from '../lib/auth.js';
|
|
36
35
|
import { deviceCommand } from '../commands/device/index.js';
|
|
36
|
+
import { fleetCommand } from '../commands/fleet/index.js';
|
|
37
37
|
import { logoutCommand } from '../commands/logout.js';
|
|
38
|
-
import {
|
|
38
|
+
import { playbookCommand } from '../commands/playbook/index.js';
|
|
39
|
+
import { productCommand } from '../commands/product/index.js';
|
|
39
40
|
import { profileCommand } from '../commands/profile.js';
|
|
41
|
+
import { runDashboard } from '../commands/dashboard.js';
|
|
40
42
|
import { setBaseURL } from '../lib/api.js';
|
|
41
43
|
import { startMCPServer } from '../lib/mcp/server.js';
|
|
42
44
|
|
|
43
|
-
// Function to display the banner
|
|
44
45
|
function displayBanner() {
|
|
45
|
-
console.log(
|
|
46
|
-
console.log(
|
|
46
|
+
console.log(accent(figlet.textSync('thinr-cli', { horizontalLayout: 'full' })));
|
|
47
|
+
console.log(info('Thin Remote CLI - Remote management for IoT devices'));
|
|
47
48
|
console.log();
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const program = new Command();
|
|
51
52
|
|
|
52
|
-
// Set up CLI information
|
|
53
53
|
program
|
|
54
54
|
.name('thinr')
|
|
55
55
|
.description('CLI for ThinRemote - Remote management for IoT devices')
|
|
@@ -57,14 +57,35 @@ program
|
|
|
57
57
|
.option('-u, --user <username>', 'API user override (admin impersonation)')
|
|
58
58
|
.option('--profile <name>', 'Configuration profile to use (defaults to the saved default)');
|
|
59
59
|
|
|
60
|
-
// Register commands
|
|
61
60
|
deviceCommand(program);
|
|
61
|
+
fleetCommand(program);
|
|
62
62
|
productCommand(program);
|
|
63
|
+
playbookCommand(program);
|
|
63
64
|
profileCommand(program);
|
|
64
65
|
logoutCommand(program);
|
|
65
66
|
|
|
67
|
+
program
|
|
68
|
+
.command('login')
|
|
69
|
+
.helpGroup('Profile:')
|
|
70
|
+
.description('Re-authenticate the current profile (renew an expired session)')
|
|
71
|
+
.action(async () => {
|
|
72
|
+
try {
|
|
73
|
+
// Pin the target to the active/default profile by name so the
|
|
74
|
+
// refreshed credentials overwrite it in place, even when the
|
|
75
|
+
// profile name differs from the server hostname.
|
|
76
|
+
const name = getActiveProfile();
|
|
77
|
+
if (name) setActiveProfile(name);
|
|
78
|
+
await authenticate();
|
|
79
|
+
console.log(info('Signed in.'));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(`Error: ${error.message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
66
86
|
program
|
|
67
87
|
.command('mcp')
|
|
88
|
+
.helpGroup('Integration:')
|
|
68
89
|
.description('Start the MCP server (stdio) for AI tool integration')
|
|
69
90
|
.action(async () => {
|
|
70
91
|
try {
|
|
@@ -91,27 +112,24 @@ if (configExists()) {
|
|
|
91
112
|
}
|
|
92
113
|
}
|
|
93
114
|
|
|
94
|
-
// Handle no command (just "thinr")
|
|
115
|
+
// Handle no command (just "thinr"): launch the dashboard when running
|
|
116
|
+
// interactively. Non-TTY contexts (pipes, scripts, `thinr | cat`) fall
|
|
117
|
+
// back to `--help` so they get something useful instead of an Ink crash.
|
|
95
118
|
if (process.argv.length <= 2) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
if (!process.stdout.isTTY) {
|
|
120
|
+
program.help();
|
|
121
|
+
} else {
|
|
99
122
|
if (!configExists()) {
|
|
100
|
-
// Display banner only if configuration doesn't exist
|
|
101
123
|
displayBanner();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
program.help();
|
|
124
|
+
try {
|
|
125
|
+
await authenticate();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`Configuration error: ${error.message}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
109
130
|
}
|
|
110
|
-
|
|
111
|
-
spinner.fail(`Configuration error: ${error.message}`);
|
|
112
|
-
process.exit(1);
|
|
131
|
+
await runDashboard();
|
|
113
132
|
}
|
|
114
|
-
spinner.succeed('Configuration found');
|
|
115
133
|
} else {
|
|
116
134
|
// Parse arguments - don't show banner for regular commands
|
|
117
135
|
program.parse(process.argv);
|