@thinger-io/cli-core 0.4.1 → 0.5.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 +204 -0
- package/README.md +82 -31
- package/docs/thinger-concepts.md +7 -7
- package/docs/thinger-product-code.md +2 -2
- package/docs/thinger-server-admin.md +2 -2
- package/lib/bucket-export.js +91 -0
- package/lib/cli.js +106 -50
- package/lib/commands/_shared.js +89 -1
- package/lib/commands/api.js +90 -0
- package/lib/commands/arguments.js +62 -0
- package/lib/commands/bucket.js +52 -44
- package/lib/commands/dashboard.js +5 -2
- package/lib/commands/data.js +539 -0
- package/lib/commands/device.js +128 -241
- package/lib/commands/grammar.js +279 -0
- package/lib/commands/product.js +104 -214
- package/lib/commands/profile.js +1 -0
- package/lib/commands/resources.js +975 -0
- package/lib/dashboard-init.js +4 -3
- package/lib/dashboard.js +10 -2
- package/lib/mcp/catalog.js +303 -65
- package/lib/mcp/core-types.js +36 -0
- package/lib/mcp/helpers.js +23 -2
- package/lib/mcp/naming.js +20 -0
- package/lib/mcp/registry.js +18 -67
- package/lib/mcp/resource-hooks.js +19 -5
- package/lib/mcp/server.js +43 -11
- package/lib/mcp/tools-resource.js +1232 -330
- package/lib/mcp/types/bucket-data.js +151 -0
- package/lib/mcp/{tools-dashboards.js → types/dashboard-apps.js} +99 -37
- package/lib/mcp/types/device-resource.js +117 -0
- package/lib/mcp/types/doc.js +35 -0
- package/lib/mcp/types/plugin-call.js +75 -0
- package/lib/mcp/types/product-api.js +44 -0
- package/lib/mcp/types/profile.js +37 -0
- package/lib/mcp/types/storage-file.js +170 -0
- package/lib/openapi.js +18 -0
- package/lib/product-apply.js +121 -0
- package/lib/raw-api.js +111 -0
- package/lib/storage.js +41 -13
- package/lib/tokens.js +16 -2
- package/package.json +1 -1
- package/lib/mcp/tools-buckets.js +0 -130
- package/lib/mcp/tools-devices.js +0 -134
- package/lib/mcp/tools-misc.js +0 -48
- package/lib/mcp/tools-plugins.js +0 -63
- package/lib/mcp/tools-products.js +0 -52
- package/lib/mcp/tools-storage.js +0 -173
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,209 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-10-21
|
|
4
|
+
|
|
5
|
+
One grammar for every command, noun first:
|
|
6
|
+
`<cli> <noun> [<noun>…] <verb> [<parent ids>…] [<id>] [options]`. Nouns
|
|
7
|
+
nest under their parent (`device property`, `product profile api`), verbs
|
|
8
|
+
come from a closed set, ids are positional and named after their noun,
|
|
9
|
+
and the options keep one meaning everywhere. Some commands of 0.4 change
|
|
10
|
+
form (see the table below); the old forms are gone, not aliased.
|
|
11
|
+
|
|
12
|
+
The MCP serves eight tools, `resource_describe|list|get|create|update|
|
|
13
|
+
delete|clone|call`, instead of 25: what had a tool of its own (bucket data,
|
|
14
|
+
storage files, device resources, the guides, the profiles, dashboard apps,
|
|
15
|
+
plugin and product API calls) is a type or an action of them, and every
|
|
16
|
+
record is addressed with `ids`, one key per level named as the server's
|
|
17
|
+
REST path names it. The old tool names and the `parent`/`id` arguments are
|
|
18
|
+
gone, not aliased (see the second table below).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- MCP: eight tools for everything. Each takes a `type` and `ids`
|
|
23
|
+
(`{"device": "d1", "bucket": "b1"}`); the keys are the server's REST
|
|
24
|
+
placeholders (`type` for an asset type, `group` for an asset group,
|
|
25
|
+
`rule` and `instance` for alarms, `resource` for every product profile
|
|
26
|
+
category), so a body's id field and its `ids` key share a name and
|
|
27
|
+
`create` and `clone` need no mapping. Records answered by `list`, `get`
|
|
28
|
+
and `create` carry their `ids`, ready to pass back. A call with an
|
|
29
|
+
unknown argument is refused listing the ones the tool takes, and points
|
|
30
|
+
a misplaced key to `ids`, `filter` or `body` (an id in an action's body
|
|
31
|
+
too: `{"device": …}` goes in `ids`). An action that refuses its input
|
|
32
|
+
shows one of its examples.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
- The core's types and actions, served by the same tools: `bucket_data`
|
|
36
|
+
(list reads, create writes), `storage_file` (`ids.file` is the path;
|
|
37
|
+
`body.local` uploads files and folders of this machine), `device_resource`
|
|
38
|
+
(list, and its `call` action), `doc` (the guides), `profile`; actions
|
|
39
|
+
`device.send`, `device.overview`, `product_profile_api.call`,
|
|
40
|
+
`plugin.call` and `dashboard.init|dev|publish`. A CLI adds its own the
|
|
41
|
+
same way (`resourceTypes`, `resourceActions`).
|
|
42
|
+
- `resource_describe`: without a type, the catalog with each type's ids and
|
|
43
|
+
ready-to-send examples; with one, its ids, actions, the permission each
|
|
44
|
+
verb needs, its schemas and examples (the tests check every example
|
|
45
|
+
against the tools). A `describe` resource hook adds to it, and hooks
|
|
46
|
+
get `ids` and `merge` (an update that patches) in their context.
|
|
47
|
+
- Actions that change a lot at once answer a plan and run with
|
|
48
|
+
`confirm: true`: `replace_all` of the product profile categories (a diff
|
|
49
|
+
of what it adds, changes and removes), a host's `update_settings` (a diff
|
|
50
|
+
of the settings) and `restart`, `plugin.remove|kill`, alarm rules'
|
|
51
|
+
`test_notification`, and `dashboard.publish` with `prune`. MCP clients
|
|
52
|
+
that support elicitation are asked instead. The CLI asks unless `-y`.
|
|
53
|
+
- `mcp --read-only` (or `<PREFIX>_MCP_READ_ONLY=1`): only describe, list,
|
|
54
|
+
get and the read-only actions are served.
|
|
55
|
+
- Tool annotations (read-only, destructive, idempotent) on the eight tools.
|
|
56
|
+
- `doc list` and `doc get <doc>`: the guides from the command line.
|
|
57
|
+
- `api <path>`: any request to the profile's server, as `gh api` does for
|
|
58
|
+
GitHub. `{user}` stands for the account (`--user` for another), `-X`
|
|
59
|
+
picks the method (GET, or POST with a body), `--from <file|->` and
|
|
60
|
+
`-s key=value` (dots nest) build the body, `-q key=value` the query, and
|
|
61
|
+
`-o <file>` saves the answer (binary answers are only written to a file
|
|
62
|
+
or a pipe). Only paths of the profile's server: a full URL is refused.
|
|
63
|
+
- Commands for every resource type, generated from the MCP catalog:
|
|
64
|
+
`list|get|create|update|delete|clone|describe` and the type's actions,
|
|
65
|
+
for its 47 types. A child type nests under its parent's command and takes
|
|
66
|
+
its ids first (`device bucket tag get <device> <bucket> <tag>`); the
|
|
67
|
+
product profile categories go under `product profile` (`api`, `bucket`,
|
|
68
|
+
`property`, `autoprovision`, `flow`, `code`), alarms under `alarm`
|
|
69
|
+
(`alarm rule`, `alarm instance`) and assets under `asset` (`asset type`,
|
|
70
|
+
`asset group`); server types go under
|
|
71
|
+
`admin` (`admin host restart <host>`). They run the handlers of the
|
|
72
|
+
`resource_*` tools, hooks included (generated device credentials, token
|
|
73
|
+
expiries such as "30d"). Bodies come from `--from <file|->` and
|
|
74
|
+
`-s key=value` (JSON values, dots nest); create, update and clone take
|
|
75
|
+
`-n/--name` and `-d/--description` on the types that have them, `update
|
|
76
|
+
--merge` patches, `--projects` sets the projects; `list` takes
|
|
77
|
+
`--search`, `--filter`, `--count` and `--index` and returns every record
|
|
78
|
+
unless `--count`; `get` takes `-f <dot.path>` (one field) and
|
|
79
|
+
`-o <file>`; actions take `-f`, `-o` and `-q`; `delete`,
|
|
80
|
+
`replace-all`, `plugin uninstall` and `admin host restart` ask unless
|
|
81
|
+
`-y`. With `-j` they print `{ ok, data }`. What they print speaks of
|
|
82
|
+
commands, not tools: types by their command (`device property`, not
|
|
83
|
+
`device_property`), `--index` for the next page, and `describe` lists
|
|
84
|
+
the type's commands and actions where the MCP lists the tools that run
|
|
85
|
+
them, its ids as the command takes them, the create, update and clone
|
|
86
|
+
bodies as tables of fields (nested ones by their dot path, one table per
|
|
87
|
+
alternative) with their examples pretty-printed, and the permission each
|
|
88
|
+
verb needs (`-j` keeps the MCP's text).
|
|
89
|
+
- `set <parents…> <property> <value>` on every property type (device,
|
|
90
|
+
product, plugin, asset type, asset group, project and account
|
|
91
|
+
properties): the value, parsed as JSON, created when missing.
|
|
92
|
+
- Commands over the MCP tools that are not CRUD: `bucket write` (a JSON
|
|
93
|
+
sample, an array or NDJSON), `bucket export` (create the export, wait for
|
|
94
|
+
it, download it, delete it from the server unless `--keep`), `storage
|
|
95
|
+
file list|upload|download|delete` (folders and binary files), `device
|
|
96
|
+
send` (data of an HTTP device), `device overview`, `device resource
|
|
97
|
+
list|call`, `product profile api call` (a product API resource for the
|
|
98
|
+
whole product), `product apply --from <file|->` (create or update a
|
|
99
|
+
product from the JSON of `product export`: record, each profile
|
|
100
|
+
category, services and product properties) and `plugin call`.
|
|
101
|
+
- `device list`, `product list` and `bucket list` take the list options
|
|
102
|
+
too, keeping their tables; `device create` and `product create` take
|
|
103
|
+
`--from`, `-s` and `--projects`, and their id may come from the body
|
|
104
|
+
(when the record is created but its projects cannot be set, the error
|
|
105
|
+
says so).
|
|
106
|
+
- `buildProgram(opts)`: the command tree `runCli` runs, for tests; and
|
|
107
|
+
`checkGrammar(program, { exceptions })` (`commands/grammar`), which checks
|
|
108
|
+
a tree against the grammar (names, verbs, positionals, options, help
|
|
109
|
+
groups, the catalog's commands) for the tests of every CLI.
|
|
110
|
+
- `runCli({ argumentHelp })`: help of the positional arguments of the CLI's
|
|
111
|
+
own commands. Every positional argument has a description in the help.
|
|
112
|
+
- The help groups commands the same way at every level: Resources,
|
|
113
|
+
Administration, Configuration, Integration and Operations at the root;
|
|
114
|
+
Records, Data, Actions and Subresources inside a noun.
|
|
115
|
+
- MCP tool results carry their structured data for these commands (under
|
|
116
|
+
a symbol: MCP clients see the same text as before).
|
|
117
|
+
|
|
118
|
+
### Changed (breaking)
|
|
119
|
+
|
|
120
|
+
| 0.4 | 0.5 |
|
|
121
|
+
| -------------------------------------------- | ------------------------------------------------------------ |
|
|
122
|
+
| `device list <pattern>` | `device list --search <text>` (or `--filter name=<regex>`) |
|
|
123
|
+
| `device info <device>` | `device get <device>` |
|
|
124
|
+
| `device property <device>` | `device property list <device>` |
|
|
125
|
+
| `device property <device> <property> [-f p]` | `device property get <device> <property> [-f p]` |
|
|
126
|
+
| `device property-set <device> <prop> <json>` | `device property set <device> <property> <json>` |
|
|
127
|
+
| `device resource <device>` | `device resource list <device>` |
|
|
128
|
+
| `device resource <device> <res> -i k=v` | `device resource call <device> <resource> -s k=v` (`--from`) |
|
|
129
|
+
| `product info <product>` | `product get <product>` |
|
|
130
|
+
| `product property-list <product>` | `product property list <product>` |
|
|
131
|
+
| `product property-get <product> <prop>` | `product property get <product> <property>` |
|
|
132
|
+
| `product property-set <product> <prop> <v>` | `product property set <product> <property> <value>` |
|
|
133
|
+
| `product property-delete <product> <prop>` | `product property delete <product> <property> -y` |
|
|
134
|
+
|
|
135
|
+
- The outputs change with the commands: `device get` and `product get`
|
|
136
|
+
print the record as JSON (as `info` did); `device property get` and
|
|
137
|
+
`product property get` print the property record, with its value under
|
|
138
|
+
`value`, so `-f` paths start there (`-f value.setpoint` where 0.4 took
|
|
139
|
+
`-f setpoint`); `device property list` and
|
|
140
|
+
`product property list` print the property records (with `-j`, the
|
|
141
|
+
records instead of `{ product, properties: [names] }`); `product property
|
|
142
|
+
delete` asks unless `-y`. `device resource call` has no `-i/--input`: its
|
|
143
|
+
input comes from `--from` and `-s`, like every command that sends data.
|
|
144
|
+
- `register(program, groups)`'s groups are Resources at the root (they were
|
|
145
|
+
Operations), and `registerToolCommands(program)` is now
|
|
146
|
+
`registerDataCommands(program)` (`commands/data`) and no longer takes
|
|
147
|
+
them.
|
|
148
|
+
- The MCP tools (the CLI commands keep their forms and `-j` outputs; only
|
|
149
|
+
the help of some positionals is renamed: `asset type get <type>`,
|
|
150
|
+
`asset group get <group>`, `alarm rule get <rule>`, `alarm instance get
|
|
151
|
+
<instance>`, `product profile <category> get <product> <resource>`):
|
|
152
|
+
|
|
153
|
+
| 0.4 tool | 0.5 call |
|
|
154
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------ |
|
|
155
|
+
| `resource_* parent "d1" id "b1"` | `resource_* ids {"device": "d1", "bucket": "b1"}` |
|
|
156
|
+
| `bucket_data_read bucket` | `resource_list type "bucket_data" ids {bucket}` (`filter` for the query) |
|
|
157
|
+
| `bucket_data_write bucket data` | `resource_create type "bucket_data" ids {bucket} body <sample or samples>` |
|
|
158
|
+
| `storage_file_list storage path` | `resource_list type "storage_file" ids {storage} filter {path}` |
|
|
159
|
+
| `storage_file_read storage path` | `resource_get type "storage_file" ids {storage, file}` |
|
|
160
|
+
| `storage_file_write storage path …` | `resource_create type "storage_file" ids {storage, file} body {content\|local}` |
|
|
161
|
+
| `storage_file_delete storage path` | `resource_delete type "storage_file" ids {storage, file}` |
|
|
162
|
+
| `device_resource_list device` | `resource_list type "device_resource" ids {device}` |
|
|
163
|
+
| `device_resource_call device resource` | `resource_call type "device_resource" action "call" ids {device, resource}` |
|
|
164
|
+
| `device_data_send device data` | `resource_call type "device" action "send" ids {device} body <data>` |
|
|
165
|
+
| `device_overview` | `resource_call type "device" action "overview"` |
|
|
166
|
+
| `product_resource_call product resource` | `resource_call type "product_profile_api" action "call" ids {product, resource}` |
|
|
167
|
+
| `plugin_call plugin path` | `resource_call type "plugin" action "call" ids {plugin} body {path, method}` |
|
|
168
|
+
| `doc_read id` | `resource_get type "doc" ids {doc}` (`resource_list type "doc"` lists them) |
|
|
169
|
+
| `profile_list` | `resource_list type "profile"` |
|
|
170
|
+
| `dashboard_init` / `_dev` / `_publish` | `resource_call type "dashboard" action "init"` / `"dev"` / `"publish"` |
|
|
171
|
+
|
|
172
|
+
- `runCli({ mcpTools })` is gone, with `mcp/registry`'s `genericTools` and
|
|
173
|
+
`mcpTools`: a CLI adds types and actions (`resourceTypes`,
|
|
174
|
+
`resourceActions`) instead of tools, and `buildTools(opts)` builds the
|
|
175
|
+
eight. Texts refer to operations as `{op:<type>.<verb|action>}`.
|
|
176
|
+
- `lib/mcp/tools-*.js` moved to `lib/mcp/types/` (as types and actions) and
|
|
177
|
+
`lib/commands/tools.js` to `lib/commands/data.js`.
|
|
178
|
+
|
|
179
|
+
### Fixed
|
|
180
|
+
|
|
181
|
+
- Account tokens created with an expiry as a duration ("30d") through
|
|
182
|
+
`resource_create` (and `createToken`) got it in seconds: the server takes
|
|
183
|
+
unix milliseconds and refused it. A timestamp too small to be
|
|
184
|
+
milliseconds is still read as seconds. Device tokens keep seconds.
|
|
185
|
+
- Ids `.` and `..` are refused (`resource_*` tools too): the URL resolved
|
|
186
|
+
them to the parent, so deleting the property `..` of a device reached
|
|
187
|
+
the device.
|
|
188
|
+
- The storage helpers (storage files, thinr's product scripts)
|
|
189
|
+
escape each segment of a file path and refuse empty, `.` and `..`
|
|
190
|
+
segments: `a#b` reached the file `a`, `..` the storage. The plugin
|
|
191
|
+
`call` action also refuses `%2e%2e`.
|
|
192
|
+
- Listing a storage folder answered each file's path relative to that
|
|
193
|
+
folder: it is now the whole path, the one its `ids.file` reads.
|
|
194
|
+
- The help of `bucket read` and `device resource` printed some defaults
|
|
195
|
+
|
|
196
|
+
twice ("(default: 10) (default: 10)", "(default: {})").
|
|
197
|
+
|
|
198
|
+
## [0.4.2] - 2026-10-17
|
|
199
|
+
|
|
200
|
+
### Fixed
|
|
201
|
+
|
|
202
|
+
- The dashboard SDK ships with Thinger 7.4, not 7.5: the platform guides
|
|
203
|
+
mark as "since Thinger 7.4" what older servers lack, and the catalog
|
|
204
|
+
notes name Thinger 7.4 instead of develop builds (7.3.4-…). The SDK's
|
|
205
|
+
own base server keeps coming from its `compat.json`.
|
|
206
|
+
|
|
3
207
|
## [0.4.1] - 2026-10-16
|
|
4
208
|
|
|
5
209
|
### Fixed
|
package/README.md
CHANGED
|
@@ -19,7 +19,16 @@ It holds everything that is not specific to a product:
|
|
|
19
19
|
`profile` (product profiles), `tokens`, `alarms`, `storage`, with every
|
|
20
20
|
account path built in `paths`.
|
|
21
21
|
- **Generic commands** (`cli`, `commands/*`) — login, logout, profile, device,
|
|
22
|
-
product, bucket, dashboard, mcp
|
|
22
|
+
product, bucket, dashboard, mcp; `api` for any request to the server; and,
|
|
23
|
+
generated from the same catalog the MCP serves, the commands of every
|
|
24
|
+
resource type (`list`, `get`, `create`, `update`, `delete`, `clone`,
|
|
25
|
+
`describe`, `set` on properties, and its actions), run by the handlers and
|
|
26
|
+
hooks of the `resource_*` tools, so a type added to the catalog reaches the
|
|
27
|
+
CLI and the MCP at once. Commands over the data and files complete them:
|
|
28
|
+
`bucket write` and `export`, `storage file list|upload|download|delete`,
|
|
29
|
+
`device send`, `overview` and `resource list|call`, `product apply`,
|
|
30
|
+
`product profile api call`, `plugin call`, `doc list|get`. A command the
|
|
31
|
+
CLI registers itself keeps its name. They all follow one grammar (below).
|
|
23
32
|
- **Dashboard apps** (`dashboard`, `dashboard-init`, `dashboard-dev`,
|
|
24
33
|
`dashboard-sdk`) — create an app from a template of
|
|
25
34
|
`@thinger-io/dashboard-sdk`, develop it on its own dev server (a link with
|
|
@@ -31,11 +40,12 @@ It holds everything that is not specific to a product:
|
|
|
31
40
|
here, marking what needs a given server version), the dashboard SDK's
|
|
32
41
|
reference and types (from its package: the project's version, else the
|
|
33
42
|
CLI's), and the CLI's own.
|
|
34
|
-
- **MCP** (`mcp/*`) — the stdio server and
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
- **MCP** (`mcp/*`) — the stdio server and its eight `resource_*` tools
|
|
44
|
+
over a catalog of the server's resource types, plus the types and actions
|
|
45
|
+
every CLI serves next to it (bucket data, storage files, device
|
|
46
|
+
resources, the guides, the profiles, dashboard apps…) and those of the
|
|
47
|
+
CLI; the guides are MCP resources too. `mcp --read-only` serves only what
|
|
48
|
+
reads.
|
|
39
49
|
- **OpenAPI** (`openapi`) — the server's own API description, cached per
|
|
40
50
|
server and version, with a bundled copy as fallback.
|
|
41
51
|
|
|
@@ -76,42 +86,83 @@ servers.
|
|
|
76
86
|
or subcommands inside the generic groups.
|
|
77
87
|
- `onNoCommand(program)` — what to do when called without arguments
|
|
78
88
|
(default: help).
|
|
79
|
-
- `mcpTools` — the CLI's own MCP tools, declared with bare names
|
|
80
|
-
(`name: 'device_exec'`) and referring to others in descriptions as
|
|
81
|
-
`{tool:resource_list}` or `{family:storage_file}`. Unknown references fail
|
|
82
|
-
at startup. A tool flagged `replaces: true` swaps out the generic tool of
|
|
83
|
-
the same name.
|
|
84
89
|
- `resourceHooks` — the CLI's hooks on catalog types (`{ product: {
|
|
85
|
-
afterDelete } }`), run after the core's: see `mcp/resource-hooks.js`.
|
|
90
|
+
afterDelete } }`), run after the core's: see `mcp/resource-hooks.js`. A
|
|
91
|
+
`describe` hook adds to what `resource_describe` shows of the type.
|
|
86
92
|
- `resourceTypes` — resource types of the CLI's own platform, served by the
|
|
87
93
|
same `resource_*` tools as the server's: instead of a REST path each has
|
|
88
94
|
functions for the verbs it supports (`list`, `get`, `create`, `update`,
|
|
89
|
-
`delete`), its `parent` and `id
|
|
90
|
-
`resource_describe`, and `actions` for `resource_call`
|
|
91
|
-
`'collection'` or `'either'`
|
|
92
|
-
|
|
95
|
+
`delete`), its `parent` and `id` (the keys of `ids`), a `schema`, `notes`
|
|
96
|
+
and `examples` for `resource_describe`, and `actions` for `resource_call`
|
|
97
|
+
(`on: 'item'`, `'collection'` or `'either'`; `readOnly`, `confirm`,
|
|
98
|
+
`bodyRequired`). The core checks the ids, the filter and the examples,
|
|
99
|
+
and searches and pages what `list` returns. See `LocalType` in
|
|
93
100
|
`mcp/tools-resource.js`.
|
|
94
101
|
- `resourceActions` — actions the CLI adds to the server's types
|
|
95
102
|
(`{ device: { reboot: { description, run } } }`), run with
|
|
96
103
|
`resource_call` like the server's. A type or action that clashes with the
|
|
97
|
-
catalog fails at startup.
|
|
98
|
-
- `mcpInstructions` — server-level guidance sent to MCP clients.
|
|
104
|
+
catalog or the core fails at startup.
|
|
105
|
+
- `mcpInstructions` — server-level guidance sent to MCP clients. Texts
|
|
106
|
+
refer to an operation as `{op:<type>.<verb|action>}` (`{op:doc.get}`),
|
|
107
|
+
checked when the tools are built.
|
|
108
|
+
- `argumentHelp` — `{ byCommand, byName }`: help of the positional
|
|
109
|
+
arguments of the CLI's own commands (`{ 'device logs': { unit: '…' } }`).
|
|
110
|
+
|
|
111
|
+
`buildProgram(opts)` builds the same command tree without running it, for
|
|
112
|
+
tests.
|
|
113
|
+
|
|
114
|
+
## Command grammar
|
|
115
|
+
|
|
116
|
+
Every CLI on the core reads the same way:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
<cli> <noun> [<noun>…] <verb> [<parent ids>…] [<id>] [<extra>] [options]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **Nouns** are singular, kebab case, nested under their parent:
|
|
123
|
+
`device property`, `device bucket tag`, `product profile api`, at most
|
|
124
|
+
three deep; server types go under `admin`. Groups run nothing themselves.
|
|
125
|
+
- **Verbs** form a closed set: for records, `list`, `get`, `create`,
|
|
126
|
+
`update`, `delete`, `clone`, `describe` and `set` (the value of a
|
|
127
|
+
property); for data, `read`, `write`, `send`, `export`, `import`,
|
|
128
|
+
`overview`, `call`, `upload` and `download`; the catalog's actions in
|
|
129
|
+
kebab case (`test-source`, `replace-all`); and the local `init`, `dev`,
|
|
130
|
+
`publish`, `login`, `logout`, `use`, `current` and `add`.
|
|
131
|
+
- **Positionals** are ids, outermost parent first, named after their noun:
|
|
132
|
+
`device property get <device> <property>`.
|
|
133
|
+
- **Options** keep one meaning: `-j --json`, `-o --output`, `-y --yes`,
|
|
134
|
+
`-s --set`, `-q --query`, `-X --method`, `-f --field`; `-n` and `-d` are
|
|
135
|
+
name and description on create and update. Bodies come from
|
|
136
|
+
`--from <file|->` and `-s key=value`; lists take `--search`, `--filter`,
|
|
137
|
+
`--count` and `--index`; deletes ask unless `-y`.
|
|
138
|
+
|
|
139
|
+
`checkGrammar(program, { exceptions })` (`commands/grammar`) checks a whole
|
|
140
|
+
tree and returns what breaks it; each CLI's tests run it over its
|
|
141
|
+
`buildProgram`, stating what it adds (`root`, `verbs`), its short options
|
|
142
|
+
with another meaning (`shorts`) and the rules a command does not follow
|
|
143
|
+
(`skip`).
|
|
99
144
|
|
|
100
145
|
## MCP tools
|
|
101
146
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
The MCP serves eight tools — `resource_describe`, `resource_list`,
|
|
148
|
+
`resource_get`, `resource_create`, `resource_update`, `resource_delete`,
|
|
149
|
+
`resource_clone`, `resource_call` (actions that are not CRUD) — that take a
|
|
150
|
+
`type` from the catalog (`lib/mcp/catalog.js`), the core's types
|
|
151
|
+
(`lib/mcp/core-types.js`) or the CLI's, and `ids`: one key per level, named
|
|
152
|
+
as the server's REST path names it (`{"device": "d1", "bucket": "b1"}`,
|
|
153
|
+
`{"product": "p1", "resource": "temp"}`, `{"rule": "high_temp"}`). A record
|
|
154
|
+
the tools answer carries its `ids`, ready to pass back. The catalog is a
|
|
155
|
+
reviewed table, never discovered at run time: each type has its path, API
|
|
156
|
+
version, parent, id and the verbs it allows, with the server's operationId
|
|
157
|
+
for each (the tests check them against the bundled OpenAPI document).
|
|
158
|
+
`resource_describe` reads the request schemas and examples from the OpenAPI
|
|
159
|
+
document of the server the profile points at, and shows each type's ids,
|
|
160
|
+
actions, permissions and ready-to-send examples (checked by the tests).
|
|
161
|
+
Actions that change a lot at once (`replace_all`, a host's
|
|
162
|
+
`update_settings`, `dashboard publish` with prune…) answer a plan first and
|
|
163
|
+
run with `confirm: true`; MCP clients that support elicitation are asked
|
|
164
|
+
instead. `mcp --read-only` (or `<PREFIX>_MCP_READ_ONLY=1`) serves only
|
|
165
|
+
describe, list, get and the read-only actions.
|
|
115
166
|
|
|
116
167
|
To draft catalog entries after an API change:
|
|
117
168
|
|
package/docs/thinger-concepts.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Thinger concepts
|
|
2
2
|
|
|
3
3
|
How a Thinger.io account is organized, for agents that create resources, dashboards or
|
|
4
|
-
alarms. It describes Thinger 7.
|
|
5
|
-
older servers lack says "since Thinger 7.
|
|
4
|
+
alarms. It describes Thinger 7.4: what needs a later version says "since Thinger X", and what
|
|
5
|
+
older servers lack says "since Thinger 7.4" (a server's version: `GET /v1/server/version`).
|
|
6
6
|
`GET /v1/server/events` lists the live events; the OpenAPI document lists every route. Related
|
|
7
7
|
guides: product code (document `product-code`) and server administration (document
|
|
8
8
|
`server-admin`). The dashboard SDK is the npm package `@thinger-io/dashboard-sdk`, documented in
|
|
@@ -37,7 +37,7 @@ A **project** groups resources of the account so they can be shared and browsed
|
|
|
37
37
|
of the project's devices, and asking for another device is refused. It needs the bucket to
|
|
38
38
|
carry a `device` tag (product buckets do); a bucket without it is not filtered. A project with
|
|
39
39
|
no devices reads such a bucket as empty. It is **on by default for new projects** since Thinger
|
|
40
|
-
7.
|
|
40
|
+
7.4 (send
|
|
41
41
|
`config: {limit_bucket_access: false}` to opt out); for older projects, turn it on with
|
|
42
42
|
`PUT /v1/users/<u>/projects/<p>` and `{config: {limit_bucket_access: true}}`, especially before
|
|
43
43
|
sharing a project with members or public tokens.
|
|
@@ -159,7 +159,7 @@ Records get a `device` field; the bucket is created on the first write.
|
|
|
159
159
|
|
|
160
160
|
**Field names**: `ts` is the record time. In MongoDB buckets `tags` and `_id` are reserved (they
|
|
161
161
|
hold the record's tags and id): a record with a field of that name is refused, so rename it
|
|
162
|
-
(`tag_count`, …). Since Thinger 7.
|
|
162
|
+
(`tag_count`, …). Since Thinger 7.4 the refusal names the field.
|
|
163
163
|
|
|
164
164
|
**Reading** (`GET /v2/users/<u>/buckets/<b>/data`, or the SDK's `buckets.read`): records come as
|
|
165
165
|
`{ ts, ...fields }`, newest first. Query: `min_ts` / `max_ts` (ms), `items` (default 50, **at most
|
|
@@ -218,7 +218,7 @@ when null matters.
|
|
|
218
218
|
|
|
219
219
|
## Geofences
|
|
220
220
|
|
|
221
|
-
Since Thinger 7.
|
|
221
|
+
Since Thinger 7.4. The server checks geofences natively: do not compute zone entries and exits in your own code. A
|
|
222
222
|
property `geofences` on the device, or inherited from its type, group or product, holds a GeoJSON
|
|
223
223
|
`FeatureCollection`. Each `Feature` is a `Polygon`, or a `Point` with `properties.radius` in
|
|
224
224
|
meters. **Coordinates are `[lon, lat]`**, as in GeoJSON (the `location` property is `{lat, lon}`).
|
|
@@ -322,7 +322,7 @@ of the device.
|
|
|
322
322
|
list, a field of the listed documents (nested ones with dots) filters by exact value (numbers
|
|
323
323
|
as numbers, `true` / `false` also as booleans: `?connection.active=true` for connected devices),
|
|
324
324
|
and a repeated parameter matches any of its values (nested fields and booleans since Thinger
|
|
325
|
-
7.
|
|
325
|
+
7.4). Instance `state` values:
|
|
326
326
|
0 none, 1 activated, 2 acknowledged, 3 latched, 4 shelved, 5 cleared. `severity` values:
|
|
327
327
|
0 none, 1 low, 2 medium, 3 high, 4 critical.
|
|
328
328
|
- Notifications on activation, normalization and reminders call **endpoints**:
|
|
@@ -330,7 +330,7 @@ of the device.
|
|
|
330
330
|
(`name`, `severity`, `state`, `origin.id` = device, `evaluation.last_values`, `notification.type`…).
|
|
331
331
|
- Test a rule, a single source or a notification before saving with
|
|
332
332
|
`POST …/alarms/rules/<r>/test-rule`, `/test-source` and `/test-notification`. `test-rule` and
|
|
333
|
-
`test-source` work before the rule exists (any new id; since Thinger 7.
|
|
333
|
+
`test-source` work before the rule exists (any new id; since Thinger 7.4); `test-source` takes a `check_interval`
|
|
334
334
|
for bucket sources without aggregation (default: the rule's, or 1 minute).
|
|
335
335
|
|
|
336
336
|
## Endpoints, storages, tokens
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
JavaScript that a product's profile calls to compute, transform or route data for its devices:
|
|
4
4
|
resource values (including simulated devices), payload transforms, the device a product call is
|
|
5
|
-
for. Platform concepts: the document `concepts`. It describes Thinger 7.
|
|
5
|
+
for. Platform concepts: the document `concepts`. It describes Thinger 7.4; what needs a later
|
|
6
6
|
version says "since Thinger X" (a server's version: `GET /v1/server/version`).
|
|
7
7
|
|
|
8
8
|
## Where it lives
|
|
9
9
|
|
|
10
10
|
`PUT /v1/users/{user}/products/{product}/profile/code` with
|
|
11
11
|
`{ "code": "<JavaScript>", "storage": "" }`, or `{ "storage": "<storage id>" }` to load
|
|
12
|
-
`<storage>/index.js` from a file storage (with its `node_modules`). Since Thinger 7.
|
|
12
|
+
`<storage>/index.js` from a file storage (with its `node_modules`). Since Thinger 7.4, add
|
|
13
13
|
`"path": "<folder>"` to
|
|
14
14
|
load `<storage>/<folder>/index.js` (and that folder's `node_modules`) instead: one storage can then
|
|
15
15
|
hold a whole application, e.g. the dashboard in `app/` and each product's code in
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
For agents acting as a server **admin**: host settings, domains and brands, user accounts,
|
|
4
4
|
OAuth clients, identity providers and proxies. Every route here needs an admin session or
|
|
5
5
|
token. Read the OpenAPI document for each body; this guide covers what the schemas cannot say:
|
|
6
|
-
order of steps, side effects and risks. It describes Thinger 7.
|
|
6
|
+
order of steps, side effects and risks. It describes Thinger 7.4; what needs a later version says
|
|
7
7
|
"since Thinger X" (a server's version: `GET /v1/server/version`).
|
|
8
8
|
|
|
9
9
|
Before changing anything, read the current value and tell the user what will change. Several
|
|
@@ -38,7 +38,7 @@ self-hosted server raise `accounts.limits.<collection>.max_count` (`projects`,
|
|
|
38
38
|
brands limits are not shown by `GET settings`.
|
|
39
39
|
|
|
40
40
|
On cloud instances the plan owns `accounts.limits.{accounts,brands,domains,members}`, `database`,
|
|
41
|
-
`plugins`, `rate_limiter` and `http_server.hosts`: since Thinger 7.
|
|
41
|
+
`plugins`, `rate_limiter` and `http_server.hosts`: since Thinger 7.4, writing them returns 403.
|
|
42
42
|
To raise them, the customer changes the plan.
|
|
43
43
|
|
|
44
44
|
## Custom domain with its own brand
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
|
+
import api from './api.js';
|
|
4
|
+
import { TIMEOUTS } from './constants.js';
|
|
5
|
+
import { apiError, inputError } from './errors.js';
|
|
6
|
+
import { v1 } from './paths.js';
|
|
7
|
+
import { rawApiCall } from './raw-api.js';
|
|
8
|
+
|
|
9
|
+
/** States of an export record (buckets.cpp). */
|
|
10
|
+
const READY = 1;
|
|
11
|
+
const FAILED = 2;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Export a bucket's data to a file and download it: the server writes the
|
|
15
|
+
* file in the background and lists it among the bucket's exports when
|
|
16
|
+
* ready. The export is deleted from the server afterwards unless `keep`.
|
|
17
|
+
*
|
|
18
|
+
* @param {{
|
|
19
|
+
* bucket: string,
|
|
20
|
+
* user?: string | null,
|
|
21
|
+
* format?: 'csv' | 'json' | 'arff',
|
|
22
|
+
* timestamps?: 'iso' | 'seconds' | 'milliseconds',
|
|
23
|
+
* minTs?: number,
|
|
24
|
+
* maxTs?: number,
|
|
25
|
+
* keep?: boolean,
|
|
26
|
+
* pollMs?: number,
|
|
27
|
+
* timeoutMs?: number,
|
|
28
|
+
* }} opts
|
|
29
|
+
* @returns {Promise<{ file: string, data: Buffer | string }>}
|
|
30
|
+
*/
|
|
31
|
+
export async function exportBucket({
|
|
32
|
+
bucket,
|
|
33
|
+
user,
|
|
34
|
+
format = 'csv',
|
|
35
|
+
timestamps = 'iso',
|
|
36
|
+
minTs,
|
|
37
|
+
maxTs,
|
|
38
|
+
keep = false,
|
|
39
|
+
pollMs = 1000,
|
|
40
|
+
timeoutMs = TIMEOUTS.API_LONG_MS,
|
|
41
|
+
}) {
|
|
42
|
+
if (!['csv', 'json', 'arff'].includes(format)) throw inputError('format: csv, json or arff');
|
|
43
|
+
if (!['iso', 'seconds', 'milliseconds'].includes(timestamps)) {
|
|
44
|
+
throw inputError('timestamps: iso, seconds or milliseconds');
|
|
45
|
+
}
|
|
46
|
+
const base = `${v1(user)}/buckets/${encodeURIComponent(bucket)}/exports`;
|
|
47
|
+
let file;
|
|
48
|
+
try {
|
|
49
|
+
const res = await api.post(base, {
|
|
50
|
+
data_type: format,
|
|
51
|
+
timestamp_format: timestamps,
|
|
52
|
+
...(minTs !== undefined ? { min_ts: minTs } : {}),
|
|
53
|
+
...(maxTs !== undefined ? { max_ts: maxTs } : {}),
|
|
54
|
+
});
|
|
55
|
+
file = res.data?.file;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
throw apiError(e, { notFound: `Bucket not found: ${bucket}` });
|
|
58
|
+
}
|
|
59
|
+
if (!file) throw new Error('the server did not name the export file');
|
|
60
|
+
|
|
61
|
+
const deadline = Date.now() + timeoutMs;
|
|
62
|
+
for (;;) {
|
|
63
|
+
let state;
|
|
64
|
+
try {
|
|
65
|
+
const res = await api.get(base);
|
|
66
|
+
state = (Array.isArray(res.data) ? res.data : []).find((e) => e.file === file)?.state;
|
|
67
|
+
} catch (e) {
|
|
68
|
+
throw apiError(e);
|
|
69
|
+
}
|
|
70
|
+
if (state === READY) break;
|
|
71
|
+
if (state === FAILED) throw new Error(`the server could not export ${bucket}`);
|
|
72
|
+
if (Date.now() > deadline) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`the export ${file} is not ready yet: find it later with bucket export-file list`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
await sleep(pollMs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const res = await rawApiCall({ path: `${base}/${encodeURIComponent(file)}` });
|
|
81
|
+
// The bytes as the server wrote them: parsing would round numbers.
|
|
82
|
+
const data = res.raw;
|
|
83
|
+
if (!keep) {
|
|
84
|
+
try {
|
|
85
|
+
await api.delete(`${base}/${encodeURIComponent(file)}`);
|
|
86
|
+
} catch {
|
|
87
|
+
// The data is downloaded: a leftover file on the server is no failure.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { file, data };
|
|
91
|
+
}
|