@thinremote/thinr-cli 1.5.0 → 1.6.1
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 +113 -0
- package/README.md +167 -120
- package/bin/thinr.js +3 -58
- package/commands/_shared.js +1 -0
- package/commands/{device/update.js → agent/device.js} +10 -26
- package/commands/agent/fleet.js +254 -0
- package/commands/agent/index.js +79 -0
- package/commands/device/console.js +1 -1
- package/commands/device/exec.js +1 -1
- package/commands/device/fs.js +6 -6
- package/commands/device/index.js +4 -6
- package/commands/device/logs.js +1 -1
- package/commands/device/proxy.js +15 -6
- package/commands/device/status.js +1 -1
- package/commands/device/transfer.js +2 -2
- package/commands/product/exec.js +1 -1
- package/commands/product/fs.js +3 -3
- package/commands/product/index.js +5 -4
- package/commands/product/logs.js +35 -36
- package/commands/product/metric.js +24 -14
- package/commands/product/playbook.js +10 -5
- package/commands/product/property.js +4 -14
- package/commands/product/push.js +1 -1
- package/commands/product/resource.js +56 -56
- package/commands/product/status.js +1 -1
- package/docs/guides/thinremote-metrics-logs.md +21 -22
- package/docs/guides/thinremote-playbooks.md +15 -11
- package/docs/guides/thinremote-scripts.md +11 -10
- package/docs/guides/thinremote.md +53 -42
- package/lib/agent-releases.js +1 -1
- package/lib/cli.js +172 -0
- package/lib/constants.js +1 -1
- package/lib/guides.js +1 -1
- package/lib/identity.js +8 -6
- package/lib/mcp/{tools-exec.js → device-exec.js} +84 -55
- package/lib/mcp/helpers.js +2 -2
- package/lib/mcp/resource-hooks.js +104 -6
- package/lib/mcp/resource-types.js +114 -227
- package/lib/playbook/actions.js +1 -1
- package/lib/playbook/schema.js +1 -1
- package/lib/product/log-presets.js +1 -1
- package/lib/product.js +26 -0
- package/package.json +2 -2
- package/commands/fleet/index.js +0 -17
- package/commands/fleet/upgrade.js +0 -275
- package/lib/mcp/registry.js +0 -12
|
@@ -10,9 +10,9 @@ Tool names below omit the `thinr_` prefix.
|
|
|
10
10
|
|
|
11
11
|
## Authoring flow
|
|
12
12
|
|
|
13
|
-
A saved playbook is a resource of type `product_playbook`,
|
|
14
|
-
|
|
15
|
-
its actions (`resource_call`).
|
|
13
|
+
A saved playbook is a resource of type `product_playbook`, ids
|
|
14
|
+
`{"product", "playbook"}` (its name); validating, running and rolling it
|
|
15
|
+
out are its actions (`resource_call`).
|
|
16
16
|
|
|
17
17
|
1. `resource_describe` type `product_playbook` once: its `playbook_format`
|
|
18
18
|
is the full schema (document, `vars`, step fields, every action with its
|
|
@@ -24,15 +24,17 @@ its actions (`resource_call`).
|
|
|
24
24
|
saved if invalid; it writes `playbooks/<name>.yaml` in the product storage
|
|
25
25
|
and registers it in the `playbooks` product property). Change it later
|
|
26
26
|
with `resource_update`.
|
|
27
|
-
4. Try it on one device: action `run` with the playbook
|
|
27
|
+
4. Try it on one device: action `run` with the playbook in `ids.playbook` and
|
|
28
28
|
`body.device`, first with `dry_run: true` (the resolved plan, no device
|
|
29
29
|
contacted), then `check: true` (contacts the device read-only: each step
|
|
30
30
|
says changed / unchanged / unknown), then for real.
|
|
31
31
|
5. Roll it out: action `rollout` (below).
|
|
32
32
|
|
|
33
|
-
For a one-off run that is not worth saving, action `run` without
|
|
34
|
-
the YAML in `body.content`, with the same `dry_run`
|
|
35
|
-
`body.device`, `body.devices`, `body.group` or every
|
|
33
|
+
For a one-off run that is not worth saving, action `run` without
|
|
34
|
+
`ids.playbook` takes the YAML in `body.content`, with the same `dry_run`
|
|
35
|
+
and `check`; it targets `body.device`, `body.devices`, `body.group` or every
|
|
36
|
+
device of the product (`body.all`). On several devices it answers its
|
|
37
|
+
targets first, and runs with `confirm: true` in the body.
|
|
36
38
|
|
|
37
39
|
`resource_list` and `resource_get` show what a product has (`get` includes
|
|
38
40
|
the YAML); `resource_delete` removes one (index entry, then file).
|
|
@@ -111,17 +113,19 @@ inside its local folder (`THINR_MCP_LOCAL_DIR`, or where it started).
|
|
|
111
113
|
|
|
112
114
|
## Rolling out
|
|
113
115
|
|
|
114
|
-
Action `rollout` (`resource_call` type `product_playbook`, the playbook
|
|
115
|
-
`
|
|
116
|
+
Action `rollout` (`resource_call` type `product_playbook`, the playbook in
|
|
117
|
+
`ids.playbook`) runs a saved playbook on every matching device of the product, in batches of `body.batch_size` (default 5). A batch finishes before
|
|
116
118
|
the next starts; after each one the cumulative failure rate is compared
|
|
117
119
|
with `body.failure_threshold` (default 10 %), and reaching it stops the rollout
|
|
118
120
|
without touching the remaining devices. Narrow the targets with `group` or
|
|
119
121
|
`filter`; offline devices are skipped unless `include_offline`. `dry_run`
|
|
120
122
|
lists the plan and the devices; `check` reports what would change.
|
|
123
|
+
Without `dry_run` or `check` it answers that plan first: show it to the
|
|
124
|
+
user and call again with `confirm: true` in the body once they agree.
|
|
121
125
|
|
|
122
126
|
Each rollout writes a report to `playbooks/runs/<timestamp>-<name>-<user>.json`
|
|
123
|
-
in the product storage (read it with `
|
|
124
|
-
the product): resolved variables, per-device outcome, and why it stopped if
|
|
127
|
+
in the product storage (read it with `resource_get` type `storage_file`,
|
|
128
|
+
ids `{"storage": <the product's id>, "file": "playbooks/runs/…"}`): resolved variables, per-device outcome, and why it stopped if
|
|
125
129
|
it did. A rollout that stops does not undo the devices it already changed:
|
|
126
130
|
write files to a temporary path and `move` them into place, so a device is
|
|
127
131
|
never left with half a change.
|
|
@@ -3,23 +3,24 @@
|
|
|
3
3
|
A script turns an operation (read a sensor, rotate a log, run a backup,
|
|
4
4
|
collect a health report) into a **resource**: something with a name and an
|
|
5
5
|
input/output shape that tools, dashboards and other devices can call. Use one
|
|
6
|
-
instead of `
|
|
6
|
+
instead of the `exec` action whenever the operation will be repeated.
|
|
7
7
|
|
|
8
8
|
Both kinds are resource types: `resource_list` / `_get` / `_create` /
|
|
9
|
-
`_update` / `_delete` with type `device_script` (
|
|
10
|
-
`product_script` (
|
|
9
|
+
`_update` / `_delete` with type `device_script` (ids
|
|
10
|
+
`{"device", "script"}`) or `product_script` (ids `{"product", "script"}`),
|
|
11
|
+
the file name as `ids.script` (or as `name` in the body of a create).
|
|
11
12
|
|
|
12
13
|
Tool names below omit the `thinr_` prefix.
|
|
13
14
|
|
|
14
15
|
## Device scripts or product scripts
|
|
15
16
|
|
|
16
|
-
| | Device script
|
|
17
|
-
| ----------- |
|
|
18
|
-
| Lives in | the device: `<agent base>/scripts/`
|
|
19
|
-
| Reaches | that device
|
|
20
|
-
| Changing it | one write per device
|
|
21
|
-
| Type | `device_script`,
|
|
22
|
-
| Called with | `
|
|
17
|
+
| | Device script | Product script |
|
|
18
|
+
| ----------- | ------------------------------------------- | ----------------------------------------------------- |
|
|
19
|
+
| Lives in | the device: `<agent base>/scripts/` | the product's file storage: `scripts/` |
|
|
20
|
+
| Reaches | that device | every device of the product, current and future |
|
|
21
|
+
| Changing it | one write per device | one write, effective on the next call everywhere |
|
|
22
|
+
| Type | `device_script`, ids `{"device", "script"}` | `product_script`, ids `{"product", "script"}` |
|
|
23
|
+
| Called with | `device_resource` `call` on that device | `device_resource` `call` on any device of the product |
|
|
23
24
|
|
|
24
25
|
Prefer **product scripts** for fleets: the code is downloaded on each call,
|
|
25
26
|
so there is nothing to deploy and no drift between devices. Use a device
|
|
@@ -5,12 +5,18 @@ Thinger.io server. Each machine runs **thinr-agent**, which connects to the
|
|
|
5
5
|
server as a device and lets you operate it remotely: files, commands,
|
|
6
6
|
scripts, monitoring, logs.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
Everything is a **resource type** behind eight tools: `resource_list` /
|
|
9
|
+
`_get` / `_create` / `_update` / `_delete` / `_clone` and `resource_call`
|
|
10
|
+
(its actions), described by `resource_describe`: the server's types
|
|
11
|
+
(device, product, bucket, property, token, alarm…; read the server's
|
|
12
|
+
`concepts` guide with `resource_get` type `doc`, ids `{"doc": "concepts"}`),
|
|
13
|
+
data and files (`bucket_data`, `storage_file`, `device_resource`) and
|
|
14
|
+
ThinRemote's, below. Shell commands are the `exec` action of type `device`.
|
|
15
|
+
|
|
16
|
+
Each call names the record with `ids`, one key per level:
|
|
17
|
+
`{"device": "gw_1"}`, `{"device": "gw_1", "path": "/etc/hosts"}`,
|
|
18
|
+
`{"product": "gateways", "playbook": "app-config"}`. The records the tools
|
|
19
|
+
answer carry their `ids`: pass them back as they are.
|
|
14
20
|
|
|
15
21
|
Tool names below omit the `thinr_` prefix.
|
|
16
22
|
|
|
@@ -26,20 +32,20 @@ to system paths.
|
|
|
26
32
|
|
|
27
33
|
What the agent offers, and how to reach it:
|
|
28
34
|
|
|
29
|
-
| Need | How
|
|
30
|
-
| ------------------------------------------- |
|
|
31
|
-
| Run a shell command | `
|
|
32
|
-
| List a folder | `resource_list` type `device_fs`,
|
|
33
|
-
| Read a file | `resource_get` type `device_fs`,
|
|
34
|
-
| Write a file / create a folder | `resource_create` (or `_update`) type `device_fs`, body `{"content": "…"}` / `{"type": "directory"}`
|
|
35
|
-
| Delete a file or folder | `resource_delete` type `device_fs`
|
|
36
|
-
| Move, or copy binaries from/to this machine | `resource_call` type `device_fs`, action `move`, `push` or `pull`
|
|
37
|
-
| CPU, memory, disk, network, load: now | `
|
|
38
|
-
| The same, as history | `
|
|
39
|
-
| A reusable, typed operation on one device | type `device_script` (see `thinremote-scripts`)
|
|
40
|
-
| Call what the agent or a script exposes | `
|
|
41
|
-
| Update the agent | `resource_call` type `device`, action `agent_update`, body `{"action": "check"}` or `{"action": "apply"}`
|
|
42
|
-
| Install the agent on a new machine | `resource_call` type `device`, action `agent_install` (a shell one-liner; no
|
|
35
|
+
| Need | How |
|
|
36
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
37
|
+
| Run a shell command | `resource_call` type `device`, action `exec`, ids `{"device"}`, body `{"command": "…"}` (output and exit code) |
|
|
38
|
+
| List a folder | `resource_list` type `device_fs`, ids `{"device"}`, `filter: {"path": "/var/log"}` |
|
|
39
|
+
| Read a file | `resource_get` type `device_fs`, ids `{"device", "path"}` |
|
|
40
|
+
| Write a file / create a folder | `resource_create` (or `_update`) type `device_fs`, body `{"content": "…"}` / `{"type": "directory"}` |
|
|
41
|
+
| Delete a file or folder | `resource_delete` type `device_fs` |
|
|
42
|
+
| Move, or copy binaries from/to this machine | `resource_call` type `device_fs`, action `move`, `push` or `pull` |
|
|
43
|
+
| CPU, memory, disk, network, load: now | `resource_call` type `device_resource`, action `call`, ids `{"device", "resource": "monitoring"}` |
|
|
44
|
+
| The same, as history | `resource_list` type `bucket_data`, ids `{"bucket": "monitoring"}`, filter `{"device"}` (fields such as `cpu.usage`, `memory.usage`, `disk.root.usage`, `load.1m`) |
|
|
45
|
+
| A reusable, typed operation on one device | type `device_script` (see `thinremote-scripts`) |
|
|
46
|
+
| Call what the agent or a script exposes | `resource_list` type `device_resource`; `resource_call` type `device_resource`, action `call` |
|
|
47
|
+
| Update the agent | `resource_call` type `device`, action `agent_update`, body `{"action": "check"}` or `{"action": "apply"}` |
|
|
48
|
+
| Install the agent on a new machine | `resource_call` type `device`, action `agent_install` (a shell one-liner; no ids) |
|
|
43
49
|
|
|
44
50
|
Paths are absolute on the device. `/` on its own lists the agent's working
|
|
45
51
|
directory (normally the root, reported as `fs_base_path`); nothing is
|
|
@@ -56,18 +62,20 @@ products are created with `resource_create` type `product` and come with
|
|
|
56
62
|
`config.type: "thinremote"` and a `monitoring` bucket that stores each
|
|
57
63
|
device's `monitoring` resource every 60 s (the history above; a device of
|
|
58
64
|
another kind of product has no history unless its product gets that bucket). On top of the
|
|
59
|
-
server's product, ThinRemote keeps these
|
|
60
|
-
|
|
65
|
+
server's product, ThinRemote keeps these, all under the product (ids
|
|
66
|
+
`{"product", …}`):
|
|
61
67
|
|
|
62
|
-
| Type
|
|
63
|
-
|
|
|
64
|
-
| `product_script`
|
|
65
|
-
| `product_playbook`
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
+
| Type or property | What it is | Where it lives | Guide |
|
|
69
|
+
| -------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------- |
|
|
70
|
+
| `product_script` (ids.script) | Code run on any device of the product, kept in one place | file storage named like the product, `scripts/`; one product API resource | `thinremote-scripts` |
|
|
71
|
+
| `product_playbook` (ids.playbook) | Ordered steps rolled out across devices; actions `validate`, `run`, `rollout` | same storage, `playbooks/<name>.yaml`, indexed in the `playbooks` property | `thinremote-playbooks` |
|
|
72
|
+
| `product_property` `dashboard_metrics` | Metrics for thinr's terminal dashboard (an array, written whole) | that product property | `thinremote-metrics-logs` |
|
|
73
|
+
| `product_property` `logs` | Log sources for thinr (dashboard, `thinr device logs`; written whole) | that product property | `thinremote-metrics-logs` |
|
|
68
74
|
|
|
69
|
-
Creating
|
|
70
|
-
that does not exist fails too, as with the
|
|
75
|
+
Creating a script or playbook that already exists fails (update it
|
|
76
|
+
instead), and updating one that does not exist fails too, as with the
|
|
77
|
+
server's types. The two product properties are checked before they are
|
|
78
|
+
written: `resource_describe` type `product_property` shows their shape.
|
|
71
79
|
|
|
72
80
|
Deleting a product with `resource_delete` also deletes its file storage (its
|
|
73
81
|
scripts and playbooks).
|
|
@@ -78,17 +86,19 @@ Devices join a product when they are created with `product`, with
|
|
|
78
86
|
|
|
79
87
|
## Fleet operations
|
|
80
88
|
|
|
81
|
-
| Need | How
|
|
82
|
-
| ------------------------------------------ |
|
|
83
|
-
| Same command on many devices, once | `
|
|
84
|
-
| Same file on every device | a playbook with a `write` step (to a temporary file, then `move`), rolled out
|
|
85
|
-
| A repeatable, versioned change with checks | a playbook: action `run` on one device, then action `rollout`
|
|
86
|
-
| A value from every device | a product script + `
|
|
87
|
-
| Which devices are online | `resource_list` type `device` with `filter: {"product": "<id>"}`
|
|
88
|
-
| Fleet health at a glance | `
|
|
89
|
+
| Need | How |
|
|
90
|
+
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| Same command on many devices, once | `resource_call` type `device`, action `exec`, no ids, body `devices`, `product` (and `group`) or `all: true`; `dry_run` lists the targets |
|
|
92
|
+
| Same file on every device | a playbook with a `write` step (to a temporary file, then `move`), rolled out |
|
|
93
|
+
| A repeatable, versioned change with checks | a playbook: action `run` on one device, then action `rollout` |
|
|
94
|
+
| A value from every device | a product script + `device_resource` `call` per device, or a metric |
|
|
95
|
+
| Which devices are online | `resource_list` type `device` with `filter: {"product": "<id>"}` |
|
|
96
|
+
| Fleet health at a glance | `resource_list` type `bucket_data`, ids `{"bucket": "monitoring"}`, filter `{"group_by": "device"}` |
|
|
89
97
|
|
|
90
98
|
Offline devices are skipped unless asked (`include_offline`). Commands on
|
|
91
|
-
several devices
|
|
99
|
+
several devices, playbook runs on several devices and rollouts answer
|
|
100
|
+
their targets first: show them to the user and call again with
|
|
101
|
+
`confirm: true` in the body once they agree.
|
|
92
102
|
|
|
93
103
|
## Recipes
|
|
94
104
|
|
|
@@ -100,8 +110,9 @@ command on the machine → the device appears (autoprovisioned or created) →
|
|
|
100
110
|
|
|
101
111
|
**Expose a health check for the whole fleet.** Write it once as a product
|
|
102
112
|
script (`resource_create` type `product_script`, output-only, JSON on
|
|
103
|
-
stdout) → call it on a device
|
|
104
|
-
|
|
113
|
+
stdout) → call it on a device (`resource_call` type `device_resource`,
|
|
114
|
+
action `call`) to check it → add a metric over it to the product property
|
|
115
|
+
`dashboard_metrics` (read it, append, `resource_update` the whole array) so
|
|
105
116
|
thinr's dashboard aggregates it across the fleet.
|
|
106
117
|
|
|
107
118
|
**Change a config file on every machine, safely.** `resource_describe` type
|
|
@@ -111,6 +122,6 @@ action `run` on one device with `check: true`, then for real → action
|
|
|
111
122
|
`rollout` with a small `batch_size`.
|
|
112
123
|
|
|
113
124
|
**Investigate one machine.** `resource_get` type `device` (agent info) →
|
|
114
|
-
`
|
|
125
|
+
`device_resource` `call` of `monitoring` → the `exec` action for specifics →
|
|
115
126
|
`resource_get` type `device_fs` for log files, or the product's log sources
|
|
116
127
|
with `thinr device logs`.
|
package/lib/agent-releases.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `https://get.thinremote.io/<channel>.json`. Same file the agent itself
|
|
6
6
|
* polls to decide whether a self-update is available — keeping a single
|
|
7
7
|
* source of truth between what the CLI offers and what a device would
|
|
8
|
-
* actually download if the user ran `thinr
|
|
8
|
+
* actually download if the user ran `thinr agent update`.
|
|
9
9
|
*
|
|
10
10
|
* Why a dedicated module: the TUI hook (`useLatestAgentVersion`) needed
|
|
11
11
|
* React-aware polling, the CLI just wants a one-shot fetch. Sharing the
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import figlet from 'figlet';
|
|
3
|
+
import { authenticate } from '@thinger-io/cli-core/auth';
|
|
4
|
+
import { configExists } from '@thinger-io/cli-core/config';
|
|
5
|
+
import { accent, info } from '@thinger-io/cli-core/format';
|
|
6
|
+
import { mcpInstructions } from './identity.js';
|
|
7
|
+
import { resourceHooks } from './mcp/resource-hooks.js';
|
|
8
|
+
import { resourceActions, resourceTypes } from './mcp/resource-types.js';
|
|
9
|
+
import { guides } from './guides.js';
|
|
10
|
+
import { registerDeviceCommands } from '../commands/device/index.js';
|
|
11
|
+
import { registerProductCommands } from '../commands/product/index.js';
|
|
12
|
+
import { agentCommand } from '../commands/agent/index.js';
|
|
13
|
+
import { playbookCommand } from '../commands/playbook/index.js';
|
|
14
|
+
import { runDashboard } from '../commands/dashboard.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* What thinr adds to the CLI of @thinger-io/cli-core: its commands, MCP
|
|
18
|
+
* tools, resource types and guides. `bin/thinr.js` runs it with runCli;
|
|
19
|
+
* the tests build the same tree with buildProgram.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
function displayBanner() {
|
|
23
|
+
console.log(accent(figlet.textSync('thinr-cli', { horizontalLayout: 'full' })));
|
|
24
|
+
console.log(info('Thin Remote CLI - Remote management for IoT devices'));
|
|
25
|
+
console.log();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Exceptions of thinr's commands to the grammar of cli-core
|
|
30
|
+
* (`checkGrammar`): the verbs of a remote shell and file system, its
|
|
31
|
+
* groups, and the options it published with another meaning.
|
|
32
|
+
*
|
|
33
|
+
* @type {import('@thinger-io/cli-core/commands/grammar').GrammarExceptions}
|
|
34
|
+
*/
|
|
35
|
+
export const grammarExceptions = {
|
|
36
|
+
root: ['agent', 'playbook'],
|
|
37
|
+
verbs: [
|
|
38
|
+
// Remote access and execution.
|
|
39
|
+
'tcp',
|
|
40
|
+
'tls',
|
|
41
|
+
'http',
|
|
42
|
+
'console',
|
|
43
|
+
'exec',
|
|
44
|
+
'status',
|
|
45
|
+
'logs',
|
|
46
|
+
// Files of a device (`read` and `write` are verbs already).
|
|
47
|
+
'ls',
|
|
48
|
+
'mkdir',
|
|
49
|
+
'rm',
|
|
50
|
+
'mv',
|
|
51
|
+
'push',
|
|
52
|
+
'pull',
|
|
53
|
+
// Agent, fan-outs, playbooks and log sources.
|
|
54
|
+
'check',
|
|
55
|
+
'device-property',
|
|
56
|
+
'device-resource',
|
|
57
|
+
'run',
|
|
58
|
+
'validate',
|
|
59
|
+
'set-default',
|
|
60
|
+
'presets',
|
|
61
|
+
],
|
|
62
|
+
shorts: {
|
|
63
|
+
'device logs': { '-f': '--follow' },
|
|
64
|
+
'device mv': { '-f': '--force' },
|
|
65
|
+
'product mv': { '-f': '--force' },
|
|
66
|
+
},
|
|
67
|
+
skip: {
|
|
68
|
+
// Interactive sessions.
|
|
69
|
+
'device console': ['json'],
|
|
70
|
+
'device tcp': ['json'],
|
|
71
|
+
'device tls': ['json'],
|
|
72
|
+
'device http': ['json'],
|
|
73
|
+
// A file written on the device, a log source, an agent update: not JSON bodies.
|
|
74
|
+
'device write': ['body'],
|
|
75
|
+
'product logs create': ['body'],
|
|
76
|
+
'agent update': ['body'],
|
|
77
|
+
// Entries of a product property: short lists.
|
|
78
|
+
'product logs list': ['list'],
|
|
79
|
+
'product metric list': ['list'],
|
|
80
|
+
'product playbook list': ['list'],
|
|
81
|
+
// The presets ship with thinr: they belong to no product.
|
|
82
|
+
'product logs presets': ['parents'],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** @type {Omit<Parameters<typeof import('@thinger-io/cli-core/cli').runCli>[0], 'version'>} */
|
|
87
|
+
export const cliOptions = {
|
|
88
|
+
resourceHooks,
|
|
89
|
+
resourceTypes,
|
|
90
|
+
resourceActions,
|
|
91
|
+
docs: guides,
|
|
92
|
+
mcpInstructions,
|
|
93
|
+
argumentHelp: {
|
|
94
|
+
byCommand: {
|
|
95
|
+
'device tcp': { target: 'Port on the device, or host:port (default: 22)' },
|
|
96
|
+
'device tls': { target: 'Port on the device, or host:port (default: 443)' },
|
|
97
|
+
'device http': { target: 'Port on the device, or host:port (default: 80)' },
|
|
98
|
+
'device exec': { command: 'Command to run on the device' },
|
|
99
|
+
'device ls': { path: 'Folder on the device (default: /)' },
|
|
100
|
+
'device read': { path: 'File on the device' },
|
|
101
|
+
'device write': {
|
|
102
|
+
path: 'File on the device',
|
|
103
|
+
content: 'What to write (default: stdin)',
|
|
104
|
+
},
|
|
105
|
+
'device mkdir': { path: 'Folder to create on the device' },
|
|
106
|
+
'device rm': { path: 'File or folder to delete on the device' },
|
|
107
|
+
'device mv': { source: 'Path on the device', destination: 'New path on the device' },
|
|
108
|
+
'device push': {
|
|
109
|
+
'local-path': 'Local file or folder',
|
|
110
|
+
'remote-path': 'Where on the device',
|
|
111
|
+
},
|
|
112
|
+
'device pull': {
|
|
113
|
+
'remote-path': 'File or folder on the device',
|
|
114
|
+
'local-path': 'Where to save it',
|
|
115
|
+
},
|
|
116
|
+
'product exec': { command: 'Command to run on every device' },
|
|
117
|
+
'product mkdir': { path: 'Folder to create on every device' },
|
|
118
|
+
'product rm': { path: 'File or folder to delete on every device' },
|
|
119
|
+
'product mv': { source: 'Path on the devices', destination: 'New path on the devices' },
|
|
120
|
+
'product push': {
|
|
121
|
+
'local-path': 'Local file or folder',
|
|
122
|
+
'remote-path': 'Where on every device',
|
|
123
|
+
},
|
|
124
|
+
'product device-property': { property: 'Property to read on every device' },
|
|
125
|
+
'product device-resource': { resource: 'Resource to call on every device' },
|
|
126
|
+
'product metric set': { name: 'Name of the metric' },
|
|
127
|
+
'product metric delete': { name: 'Name of the metric' },
|
|
128
|
+
'product logs get': { name: 'Name of the log source' },
|
|
129
|
+
'product logs create': { name: 'Name of the log source' },
|
|
130
|
+
'product logs delete': { name: 'Name of the log source' },
|
|
131
|
+
'product logs set-default': { name: 'Name of the log source' },
|
|
132
|
+
'product playbook upload': {
|
|
133
|
+
name: 'Name of the playbook',
|
|
134
|
+
file: 'YAML file (default: stdin)',
|
|
135
|
+
},
|
|
136
|
+
'product playbook download': { name: 'Name of the playbook' },
|
|
137
|
+
'product playbook delete': { name: 'Name of the playbook' },
|
|
138
|
+
'product playbook run': { name: 'Name of the playbook' },
|
|
139
|
+
'playbook run': { file: 'Playbook file (YAML)' },
|
|
140
|
+
'playbook validate': { file: 'Playbook file (YAML)' },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
register(program, groups) {
|
|
144
|
+
registerDeviceCommands(groups.device);
|
|
145
|
+
registerProductCommands(groups.product);
|
|
146
|
+
agentCommand(program);
|
|
147
|
+
playbookCommand(program);
|
|
148
|
+
|
|
149
|
+
const originalHelp = program.help;
|
|
150
|
+
/** @type {any} */ (program).help = function (cb) {
|
|
151
|
+
displayBanner();
|
|
152
|
+
return originalHelp.call(this, cb);
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
// Just `thinr`: launch the dashboard when running interactively (signing
|
|
156
|
+
// in first if there is no profile yet). Non-TTY contexts (pipes,
|
|
157
|
+
// scripts, `thinr | cat`) fall back to `--help` so they get something
|
|
158
|
+
// useful instead of an Ink crash.
|
|
159
|
+
async onNoCommand(program) {
|
|
160
|
+
if (!process.stdout.isTTY) program.help();
|
|
161
|
+
if (!configExists()) {
|
|
162
|
+
displayBanner();
|
|
163
|
+
try {
|
|
164
|
+
await authenticate();
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`Configuration error: ${error.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await runDashboard();
|
|
171
|
+
},
|
|
172
|
+
};
|
package/lib/constants.js
CHANGED
|
@@ -36,7 +36,7 @@ export const TIMEOUTS = {
|
|
|
36
36
|
/**
|
|
37
37
|
* Default command timeout (in *seconds*) for user-facing exec
|
|
38
38
|
* flows: CLI `device exec`, `product exec`, playbook `exec`, MCP
|
|
39
|
-
* `
|
|
39
|
+
* device action `exec`. Callers override when they know better.
|
|
40
40
|
*/
|
|
41
41
|
DEFAULT_EXEC_SECONDS: 30,
|
|
42
42
|
|
package/lib/guides.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The guides thinr ships for agents (served
|
|
4
|
+
* The guides thinr ships for agents (served as type `doc` and as MCP
|
|
5
5
|
* resources, next to the server's documentation). They describe what thinr
|
|
6
6
|
* implements on top of the server — the agent, product scripts, playbooks,
|
|
7
7
|
* the product dashboard — so they live and change with this code. Tool
|
package/lib/identity.js
CHANGED
|
@@ -68,9 +68,11 @@ export const thinrIdentity = {
|
|
|
68
68
|
/** Server-level guidance sent to MCP clients on `initialize`. */
|
|
69
69
|
export const mcpInstructions = `Tools to operate ThinRemote fleets: machines running thinr-agent on a Thinger.io server, with the permissions of the token stored in the CLI profile.
|
|
70
70
|
|
|
71
|
-
- Read the guide first:
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
71
|
+
- Read the guide first: thinr_resource_get type "doc" ids {"doc": "thinremote"} (the agent, products, which type or action for each need, recipes); thinr_resource_list type "doc" lists the rest (scripts, playbooks, metrics and logs) and the platform's guides (concepts, product code, server administration, the dashboard SDK).
|
|
72
|
+
- Everything is a resource type behind eight tools: thinr_resource_list / _get / _create / _update / _delete / _clone and thinr_resource_call (actions), described by thinr_resource_describe: the server's (device, product, bucket, token, alarm…), data and files (bucket_data, storage_file, device_resource) and ThinRemote's: device_fs (a device's files and folders, ids {"device", "path"}), device_script (ids {"device", "script"}), product_script and product_playbook (ids {"product", "script"} and {"product", "playbook"}; playbook actions validate, run and rollout). Actions on devices: exec, agent_update, agent_install (the command that installs the agent). Each type's ids, filter, bodies, actions and ready-to-send examples are in thinr_resource_describe with that type: read it before the first call on a type.
|
|
73
|
+
- A record is located by \`ids\`, one key per level; the records the tools answer carry their ids: pass them back as they are.
|
|
74
|
+
- Shell commands: thinr_resource_call type "device" action "exec", with ids {"device": <id>} for one device, or without ids and body devices, product (and group), group or all for several; body.command; body.dry_run lists the targets first.
|
|
75
|
+
- Monitoring: live with thinr_resource_call type "device_resource" action "call" ids {"device": <device>, "resource": "monitoring"}; history with thinr_resource_list type "bucket_data" ids {"bucket": "monitoring"} (filter {"device": <device>} for one), stored only for devices of products that have that bucket (ThinRemote products do).
|
|
76
|
+
- Fleet metrics and log sources of thinr's terminal dashboard: the product properties "dashboard_metrics" and "logs" (thinr_resource_describe type "product_property" shows their shape).
|
|
77
|
+
- List before acting to discover ids; never guess them. Deletes act immediately: confirm with the user first. Fleet changes (exec on several devices, playbook rollouts and runs on several devices) answer their targets first: show them to the user and repeat the call with confirm: true in the body only if they agree.
|
|
78
|
+
- Every tool takes an optional \`profile\` (another configured server, see thinr_resource_list type "profile") and most take \`user\` (another account, admin only).`;
|