@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +167 -120
  3. package/bin/thinr.js +3 -58
  4. package/commands/_shared.js +1 -0
  5. package/commands/{device/update.js → agent/device.js} +10 -26
  6. package/commands/agent/fleet.js +254 -0
  7. package/commands/agent/index.js +79 -0
  8. package/commands/device/console.js +1 -1
  9. package/commands/device/exec.js +1 -1
  10. package/commands/device/fs.js +6 -6
  11. package/commands/device/index.js +4 -6
  12. package/commands/device/logs.js +1 -1
  13. package/commands/device/proxy.js +15 -6
  14. package/commands/device/status.js +1 -1
  15. package/commands/device/transfer.js +2 -2
  16. package/commands/product/exec.js +1 -1
  17. package/commands/product/fs.js +3 -3
  18. package/commands/product/index.js +5 -4
  19. package/commands/product/logs.js +35 -36
  20. package/commands/product/metric.js +24 -14
  21. package/commands/product/playbook.js +10 -5
  22. package/commands/product/property.js +4 -14
  23. package/commands/product/push.js +1 -1
  24. package/commands/product/resource.js +56 -56
  25. package/commands/product/status.js +1 -1
  26. package/docs/guides/thinremote-metrics-logs.md +21 -22
  27. package/docs/guides/thinremote-playbooks.md +15 -11
  28. package/docs/guides/thinremote-scripts.md +11 -10
  29. package/docs/guides/thinremote.md +53 -42
  30. package/lib/agent-releases.js +1 -1
  31. package/lib/cli.js +172 -0
  32. package/lib/constants.js +1 -1
  33. package/lib/guides.js +1 -1
  34. package/lib/identity.js +8 -6
  35. package/lib/mcp/{tools-exec.js → device-exec.js} +84 -55
  36. package/lib/mcp/helpers.js +2 -2
  37. package/lib/mcp/resource-hooks.js +104 -6
  38. package/lib/mcp/resource-types.js +114 -227
  39. package/lib/playbook/actions.js +1 -1
  40. package/lib/playbook/schema.js +1 -1
  41. package/lib/product/log-presets.js +1 -1
  42. package/lib/product.js +26 -0
  43. package/package.json +2 -2
  44. package/commands/fleet/index.js +0 -17
  45. package/commands/fleet/upgrade.js +0 -275
  46. 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`, with the product
14
- as parent and its name as id; validating, running and rolling it out are
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's `id` and
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 `id` takes
34
- the YAML in `body.content`, with the same `dry_run` and `check`; it targets
35
- `body.device`, `body.devices`, `body.group` or every device of the product.
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's
115
- `id`) runs a saved playbook on every matching device of the product, in batches of `body.batch_size` (default 5). A batch finishes before
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 `storage_file_read`, storage named like
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 `thinr_device_exec` whenever the operation will be repeated.
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` (parent the device) or
10
- `product_script` (parent the product), the file name as id.
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 | Product script |
17
- | ----------- | ------------------------------------- | --------------------------------------------------- |
18
- | Lives in | the device: `<agent base>/scripts/` | the product's file storage: `scripts/` |
19
- | Reaches | that device | every device of the product, current and future |
20
- | Changing it | one write per device | one write, effective on the next call everywhere |
21
- | Type | `device_script`, parent the device | `product_script`, parent the product |
22
- | Called with | `device_resource_call` on that device | `device_resource_call` on any device of the product |
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
- Almost everything is a **resource type** behind the generic tools
9
- `resource_list` / `_get` / `_create` / `_update` / `_delete` and
10
- `resource_call` (its actions), described by `resource_describe`: the
11
- server's types (device, product, bucket, property, token, alarm…; read the
12
- server's `concepts` guide with `doc_read`) and ThinRemote's, below. The one
13
- tool of its own is `device_exec`, for shell commands.
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 | `device_exec` with `device` (output and exit code; `timeout` in s) |
32
- | List a folder | `resource_list` type `device_fs`, parent the device, `filter: {"path": "/var/log"}` |
33
- | Read a file | `resource_get` type `device_fs`, parent the device, id the path |
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 | `device_resource_call` resource `monitoring` |
38
- | The same, as history | `bucket_data_read` bucket `monitoring` with `device` (fields such as `cpu.usage`, `memory.usage`, `disk.root.usage`, `load.1m`) |
39
- | A reusable, typed operation on one device | type `device_script` (see `thinremote-scripts`) |
40
- | Call what the agent or a script exposes | `device_resource_list`, `device_resource_call` |
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 id) |
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 types, all with the product as
60
- parent:
65
+ server's product, ThinRemote keeps these, all under the product (ids
66
+ `{"product", …}`):
61
67
 
62
- | Type | What it is | Where it lives | Guide |
63
- | -------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------- |
64
- | `product_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` |
65
- | `product_playbook` | Ordered steps rolled out across devices; actions `validate`, `run`, `rollout` | same storage, `playbooks/<name>.yaml`, indexed in the `playbooks` property | `thinremote-playbooks` |
66
- | `product_metric` | Metrics for thinr's terminal dashboard | `dashboard_metrics` product property | `thinremote-metrics-logs` |
67
- | `product_log_source` | Log sources for thinr (dashboard, `thinr device logs`) | `logs` product property | `thinremote-metrics-logs` |
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 one that already exists fails (update it instead), and updating one
70
- that does not exist fails too, as with the server's types.
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 | `device_exec` with `devices`, `product` (and `group`) or `all: true`; `dry_run` lists the targets first |
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 + `device_resource_call` per device, or a metric |
87
- | Which devices are online | `resource_list` type `device` with `filter: {"product": "<id>"}` |
88
- | Fleet health at a glance | `bucket_data_read` bucket `monitoring` without `device`, `group_by` device |
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 and rollouts act at once: confirm with the user first.
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 with `device_resource_call` to check it →
104
- register a metric over it (`resource_create` type `product_metric`) so
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
- `device_resource_call` resource `monitoring` → `device_exec` for specifics →
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`.
@@ -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 device update apply`.
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
- * `thinr_exec`. Callers override when they know better.
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 by `thinr_doc_read` and as MCP
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: thinr_doc_read id "thinremote" (the agent, products, which tool for each need, recipes); thinr_doc_read without id lists the rest (scripts, playbooks, metrics and logs) and the platform's guides (concepts, product code, server administration, the dashboard SDK).
72
- - Almost everything is a resource type behind thinr_resource_list / _get / _create / _update / _delete / _call, described by thinr_resource_describe: the server's (device, product, bucket, token, alarm…) and ThinRemote's: device_fs (a device's files and folders, parent = the device), device_script, product_script, product_metric, product_log_source and product_playbook (parent = the product; actions validate, run and rollout). Actions on devices: agent_update, agent_install (the command that installs the agent).
73
- - Shell commands: thinr_device_exec, on one device or on several (devices, product, group, all).
74
- - Monitoring: live with thinr_device_resource_call resource "monitoring"; history with thinr_bucket_data_read bucket "monitoring" (device for one), stored only for devices of products that have that bucket (ThinRemote products do).
75
- - List before acting to discover ids; never guess them. Fleet changes (thinr_device_exec on several devices, playbook rollouts) and deletes act immediately: confirm with the user first.
76
- - Every tool takes an optional \`profile\` (another configured server, see thinr_profile_list) and most take \`user\` (another account, admin only).`;
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).`;