@thinremote/thinr-cli 1.3.1 → 1.3.3
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 +35 -0
- package/lib/alarms.js +11 -40
- package/lib/auth.js +4 -3
- package/lib/bucket.js +3 -8
- package/lib/command-stream.js +2 -1
- package/lib/config.js +9 -0
- package/lib/console.js +9 -7
- package/lib/dashboard/event-stream.js +11 -4
- package/lib/device-api.js +6 -4
- package/lib/devices.js +4 -10
- package/lib/mcp/tools-alarms.js +4 -7
- package/lib/mcp/tools-buckets.js +5 -9
- package/lib/mcp/tools-devices.js +10 -10
- package/lib/mcp/tools-exec.js +19 -12
- package/lib/mcp/tools-filesystem.js +24 -24
- package/lib/mcp/tools-install.js +5 -6
- package/lib/mcp/tools-playbook.js +5 -6
- package/lib/mcp/tools-products.js +16 -24
- package/lib/mcp/tools-profile.js +2 -1
- package/lib/mcp/tools-properties.js +9 -9
- package/lib/mcp/tools-resources.js +6 -6
- package/lib/mcp/tools-scripts.js +9 -9
- package/lib/mcp/tools-tokens.js +17 -19
- package/lib/monitoring.js +4 -10
- package/lib/paths.js +32 -0
- package/lib/playbook/actions.js +2 -2
- package/lib/product.js +1 -9
- package/lib/profile.js +1 -9
- package/lib/property.js +17 -27
- package/lib/resource.js +10 -23
- package/lib/status.js +11 -21
- package/lib/storage.js +1 -12
- package/lib/tokens.js +1 -9
- package/package.json +1 -1
package/lib/mcp/tools-exec.js
CHANGED
|
@@ -5,7 +5,7 @@ import { BYTES, TIMEOUTS } from '../constants.js';
|
|
|
5
5
|
import { getAPI } from './helpers.js';
|
|
6
6
|
|
|
7
7
|
async function toolExec(args) {
|
|
8
|
-
const { api } = getAPI(args.
|
|
8
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
9
9
|
// Default guards against hung commands locking the session; callers
|
|
10
10
|
// override for known-long operations. Knob lives in lib/constants.js.
|
|
11
11
|
const timeout =
|
|
@@ -33,7 +33,7 @@ async function toolExec(args) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async function toolUpdate(args) {
|
|
36
|
-
const { api: deviceApi } = getAPI(args.
|
|
36
|
+
const { api: deviceApi } = getAPI(args.device_id, args.user);
|
|
37
37
|
const payload = {
|
|
38
38
|
action: args.action,
|
|
39
39
|
channel: args.channel || 'latest',
|
|
@@ -46,7 +46,7 @@ async function toolUpdate(args) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function toolMonitoring(args) {
|
|
49
|
-
const device = args.
|
|
49
|
+
const device = args.device_id;
|
|
50
50
|
const user = args.user;
|
|
51
51
|
|
|
52
52
|
// Real-time: read the device's `monitoring` resource directly.
|
|
@@ -143,11 +143,11 @@ export const tools = [
|
|
|
143
143
|
type: 'object',
|
|
144
144
|
properties: {
|
|
145
145
|
command: { type: 'string', description: 'Shell command to execute' },
|
|
146
|
-
|
|
146
|
+
device_id: { type: 'string', description: 'Device ID (from thinr_devices)' },
|
|
147
147
|
timeout: { type: 'number', description: 'Timeout in seconds (default: 30)' },
|
|
148
148
|
user: { type: 'string', description: 'API user (for admin impersonation)' },
|
|
149
149
|
},
|
|
150
|
-
required: ['command', '
|
|
150
|
+
required: ['command', 'device_id'],
|
|
151
151
|
},
|
|
152
152
|
handler: toolExec,
|
|
153
153
|
},
|
|
@@ -157,7 +157,7 @@ export const tools = [
|
|
|
157
157
|
inputSchema: {
|
|
158
158
|
type: 'object',
|
|
159
159
|
properties: {
|
|
160
|
-
|
|
160
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
161
161
|
action: {
|
|
162
162
|
type: 'string',
|
|
163
163
|
description: '"check" to check for updates, "apply" to install the update',
|
|
@@ -165,7 +165,7 @@ export const tools = [
|
|
|
165
165
|
channel: { type: 'string', description: 'Update channel (default: "latest")' },
|
|
166
166
|
user: { type: 'string', description: 'API user' },
|
|
167
167
|
},
|
|
168
|
-
required: ['action', '
|
|
168
|
+
required: ['action', 'device_id'],
|
|
169
169
|
},
|
|
170
170
|
handler: toolUpdate,
|
|
171
171
|
},
|
|
@@ -185,14 +185,15 @@ Modes:
|
|
|
185
185
|
description:
|
|
186
186
|
'If true, get live data directly from the device. If false/omitted, query historical data from the monitoring bucket.',
|
|
187
187
|
},
|
|
188
|
-
|
|
188
|
+
device_id: {
|
|
189
189
|
type: 'string',
|
|
190
190
|
description:
|
|
191
191
|
'Device ID. Required for realtime. Omit for a one-row-per-device fleet view.',
|
|
192
192
|
},
|
|
193
193
|
items: {
|
|
194
194
|
type: 'number',
|
|
195
|
-
description:
|
|
195
|
+
description:
|
|
196
|
+
'Number of data points to return (default: 10). In multi-device mode set ≥ number of devices (e.g. 200) since each device contributes one row.',
|
|
196
197
|
},
|
|
197
198
|
minutes: {
|
|
198
199
|
type: 'number',
|
|
@@ -205,12 +206,18 @@ Modes:
|
|
|
205
206
|
},
|
|
206
207
|
sort: {
|
|
207
208
|
type: 'string',
|
|
208
|
-
description:
|
|
209
|
+
description:
|
|
210
|
+
'Sort order by timestamp: "asc" or "desc" (default: "desc"). Does NOT sort by metric — do that client-side on the result.',
|
|
211
|
+
},
|
|
212
|
+
agg: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
description:
|
|
215
|
+
'Time-window size for windowed aggregation: "5m", "10m", "1h", "6h". Only meaningful with a specific device. For a per-device single value across the whole range, omit `agg` and pass `agg_type` alone.',
|
|
209
216
|
},
|
|
210
|
-
agg: { type: 'string', description: 'Time-window size for windowed aggregation: "5m", "10m", "1h", "6h". Only meaningful with a specific device. For a per-device single value across the whole range, omit `agg` and pass `agg_type` alone.' },
|
|
211
217
|
agg_type: {
|
|
212
218
|
type: 'string',
|
|
213
|
-
description:
|
|
219
|
+
description:
|
|
220
|
+
'Reducer applied to each group/window. Supported: "mean", "max", "min", "sum", "count", "first", "last", "median", "spread" (max-min), "stddev". Most common for monitoring: max, mean, min.',
|
|
214
221
|
},
|
|
215
222
|
fields: {
|
|
216
223
|
type: 'string',
|
|
@@ -5,7 +5,7 @@ import { getAPI } from './helpers.js';
|
|
|
5
5
|
import { inputError } from '../errors.js';
|
|
6
6
|
|
|
7
7
|
async function toolRead(args) {
|
|
8
|
-
const { api } = getAPI(args.
|
|
8
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
9
9
|
const content = await api.readFile(args.path);
|
|
10
10
|
return {
|
|
11
11
|
content: [{ type: 'text', text: content.toString('utf8') }],
|
|
@@ -14,7 +14,7 @@ async function toolRead(args) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async function toolWrite(args) {
|
|
17
|
-
const { api } = getAPI(args.
|
|
17
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
18
18
|
await api.writeFile(args.path, Buffer.from(args.content, 'utf8'));
|
|
19
19
|
return {
|
|
20
20
|
content: [{ type: 'text', text: `Written ${args.content.length} bytes to ${args.path}` }],
|
|
@@ -23,7 +23,7 @@ async function toolWrite(args) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async function toolLs(args) {
|
|
26
|
-
const { api } = getAPI(args.
|
|
26
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
27
27
|
const entries = await api.listDir(args.path || '/', args.include_hidden || false);
|
|
28
28
|
const lines = entries.map((e) => {
|
|
29
29
|
const type = e.type === 'directory' ? 'd' : '-';
|
|
@@ -38,7 +38,7 @@ async function toolLs(args) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function toolMkdir(args) {
|
|
41
|
-
const { api } = getAPI(args.
|
|
41
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
42
42
|
await api.mkdir(args.path);
|
|
43
43
|
return {
|
|
44
44
|
content: [{ type: 'text', text: `Created directory: ${args.path}` }],
|
|
@@ -47,7 +47,7 @@ async function toolMkdir(args) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function toolRm(args) {
|
|
50
|
-
const { api } = getAPI(args.
|
|
50
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
51
51
|
await api.delete(args.path, args.recursive !== false);
|
|
52
52
|
return {
|
|
53
53
|
content: [{ type: 'text', text: `Deleted: ${args.path}` }],
|
|
@@ -56,7 +56,7 @@ async function toolRm(args) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async function toolMv(args) {
|
|
59
|
-
const { api } = getAPI(args.
|
|
59
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
60
60
|
await api.move(args.source, args.destination, args.overwrite || false);
|
|
61
61
|
return {
|
|
62
62
|
content: [{ type: 'text', text: `Moved: ${args.source} → ${args.destination}` }],
|
|
@@ -75,7 +75,7 @@ async function toolPush(args) {
|
|
|
75
75
|
const remotePath = args.destination.endsWith('/')
|
|
76
76
|
? args.destination + basename(localPath)
|
|
77
77
|
: args.destination;
|
|
78
|
-
const { api } = getAPI(args.
|
|
78
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
79
79
|
const content = await readLocalFile(localPath);
|
|
80
80
|
await api.writeFile(remotePath, content);
|
|
81
81
|
return {
|
|
@@ -99,7 +99,7 @@ async function toolPull(args) {
|
|
|
99
99
|
const localPath = args.destination.endsWith('/')
|
|
100
100
|
? resolvePath(args.destination, basename(args.source))
|
|
101
101
|
: resolvePath(args.destination);
|
|
102
|
-
const { api } = getAPI(args.
|
|
102
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
103
103
|
const buf = await api.readFile(args.source);
|
|
104
104
|
await writeLocalFile(localPath, buf);
|
|
105
105
|
return {
|
|
@@ -121,10 +121,10 @@ export const tools = [
|
|
|
121
121
|
type: 'object',
|
|
122
122
|
properties: {
|
|
123
123
|
path: { type: 'string', description: 'Absolute file path on the device' },
|
|
124
|
-
|
|
124
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
125
125
|
user: { type: 'string', description: 'API user' },
|
|
126
126
|
},
|
|
127
|
-
required: ['path', '
|
|
127
|
+
required: ['path', 'device_id'],
|
|
128
128
|
},
|
|
129
129
|
handler: toolRead,
|
|
130
130
|
},
|
|
@@ -136,10 +136,10 @@ export const tools = [
|
|
|
136
136
|
properties: {
|
|
137
137
|
path: { type: 'string', description: 'Absolute file path on the device' },
|
|
138
138
|
content: { type: 'string', description: 'File content to write' },
|
|
139
|
-
|
|
139
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
140
140
|
user: { type: 'string', description: 'API user' },
|
|
141
141
|
},
|
|
142
|
-
required: ['path', 'content', '
|
|
142
|
+
required: ['path', 'content', 'device_id'],
|
|
143
143
|
},
|
|
144
144
|
handler: toolWrite,
|
|
145
145
|
},
|
|
@@ -154,10 +154,10 @@ export const tools = [
|
|
|
154
154
|
type: 'boolean',
|
|
155
155
|
description: 'Include hidden files (default: false)',
|
|
156
156
|
},
|
|
157
|
-
|
|
157
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
158
158
|
user: { type: 'string', description: 'API user' },
|
|
159
159
|
},
|
|
160
|
-
required: ['
|
|
160
|
+
required: ['device_id'],
|
|
161
161
|
},
|
|
162
162
|
handler: toolLs,
|
|
163
163
|
},
|
|
@@ -168,10 +168,10 @@ export const tools = [
|
|
|
168
168
|
type: 'object',
|
|
169
169
|
properties: {
|
|
170
170
|
path: { type: 'string', description: 'Directory path to create' },
|
|
171
|
-
|
|
171
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
172
172
|
user: { type: 'string', description: 'API user' },
|
|
173
173
|
},
|
|
174
|
-
required: ['path', '
|
|
174
|
+
required: ['path', 'device_id'],
|
|
175
175
|
},
|
|
176
176
|
handler: toolMkdir,
|
|
177
177
|
},
|
|
@@ -186,10 +186,10 @@ export const tools = [
|
|
|
186
186
|
type: 'boolean',
|
|
187
187
|
description: 'Delete directory contents recursively (default: true)',
|
|
188
188
|
},
|
|
189
|
-
|
|
189
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
190
190
|
user: { type: 'string', description: 'API user' },
|
|
191
191
|
},
|
|
192
|
-
required: ['path', '
|
|
192
|
+
required: ['path', 'device_id'],
|
|
193
193
|
},
|
|
194
194
|
handler: toolRm,
|
|
195
195
|
},
|
|
@@ -205,10 +205,10 @@ export const tools = [
|
|
|
205
205
|
type: 'boolean',
|
|
206
206
|
description: 'Overwrite destination if exists (default: false)',
|
|
207
207
|
},
|
|
208
|
-
|
|
208
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
209
209
|
user: { type: 'string', description: 'API user' },
|
|
210
210
|
},
|
|
211
|
-
required: ['source', 'destination', '
|
|
211
|
+
required: ['source', 'destination', 'device_id'],
|
|
212
212
|
},
|
|
213
213
|
handler: toolMv,
|
|
214
214
|
},
|
|
@@ -227,10 +227,10 @@ export const tools = [
|
|
|
227
227
|
description:
|
|
228
228
|
'Absolute path on the device. Trailing "/" keeps the source basename.',
|
|
229
229
|
},
|
|
230
|
-
|
|
230
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
231
231
|
user: { type: 'string', description: 'API user' },
|
|
232
232
|
},
|
|
233
|
-
required: ['source', 'destination', '
|
|
233
|
+
required: ['source', 'destination', 'device_id'],
|
|
234
234
|
},
|
|
235
235
|
handler: toolPush,
|
|
236
236
|
},
|
|
@@ -249,10 +249,10 @@ export const tools = [
|
|
|
249
249
|
description:
|
|
250
250
|
'Local file path (absolute or relative to the MCP server CWD). Trailing "/" keeps the source basename.',
|
|
251
251
|
},
|
|
252
|
-
|
|
252
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
253
253
|
user: { type: 'string', description: 'API user' },
|
|
254
254
|
},
|
|
255
|
-
required: ['source', 'destination', '
|
|
255
|
+
required: ['source', 'destination', 'device_id'],
|
|
256
256
|
},
|
|
257
257
|
handler: toolPull,
|
|
258
258
|
},
|
package/lib/mcp/tools-install.js
CHANGED
|
@@ -41,7 +41,7 @@ async function toolAgentInstallCommand(args) {
|
|
|
41
41
|
const url = `${scheme}://get.thinremote.io/${script}`;
|
|
42
42
|
|
|
43
43
|
const agentArgs = ['install', '--token', shQuote(config.token)];
|
|
44
|
-
if (args.
|
|
44
|
+
if (args.device_id) agentArgs.push('--device', shQuote(args.device_id));
|
|
45
45
|
if (args.product) agentArgs.push('--product', shQuote(args.product));
|
|
46
46
|
if (args.overwrite) agentArgs.push('--overwrite');
|
|
47
47
|
if (args.no_start) agentArgs.push('--no-start');
|
|
@@ -83,7 +83,7 @@ async function toolAgentInstallCommand(args) {
|
|
|
83
83
|
command,
|
|
84
84
|
'',
|
|
85
85
|
'Notes:',
|
|
86
|
-
|
|
86
|
+
"- The command embeds the active profile's API token — treat it as a credential.",
|
|
87
87
|
'- Installing the system service needs root. Pass `sudo: true` if the target host is not already root.',
|
|
88
88
|
'- The agent reads the target server from the JWT, so `--host` is not needed.',
|
|
89
89
|
];
|
|
@@ -106,10 +106,10 @@ export const tools = [
|
|
|
106
106
|
inputSchema: {
|
|
107
107
|
type: 'object',
|
|
108
108
|
properties: {
|
|
109
|
-
|
|
109
|
+
device_id: {
|
|
110
110
|
type: 'string',
|
|
111
111
|
description:
|
|
112
|
-
|
|
112
|
+
"Custom device identifier. Omit to let the agent default to the target host's hostname.",
|
|
113
113
|
},
|
|
114
114
|
product: {
|
|
115
115
|
type: 'string',
|
|
@@ -127,8 +127,7 @@ export const tools = [
|
|
|
127
127
|
},
|
|
128
128
|
no_verify_ssl: {
|
|
129
129
|
type: 'boolean',
|
|
130
|
-
description:
|
|
131
|
-
'Disable TLS certificate verification (for self-signed servers).',
|
|
130
|
+
description: 'Disable TLS certificate verification (for self-signed servers).',
|
|
132
131
|
},
|
|
133
132
|
channel: {
|
|
134
133
|
type: 'string',
|
|
@@ -170,15 +170,15 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
170
170
|
},
|
|
171
171
|
target_product: {
|
|
172
172
|
type: 'string',
|
|
173
|
-
description:
|
|
173
|
+
description: "Override the playbook's target.product.",
|
|
174
174
|
},
|
|
175
175
|
target_group: {
|
|
176
176
|
type: 'string',
|
|
177
|
-
description:
|
|
177
|
+
description: "Override the playbook's target.group.",
|
|
178
178
|
},
|
|
179
179
|
target_devices: {
|
|
180
180
|
type: 'array',
|
|
181
|
-
description:
|
|
181
|
+
description: "Override the playbook's target.devices with an explicit list.",
|
|
182
182
|
},
|
|
183
183
|
concurrency: {
|
|
184
184
|
type: 'number',
|
|
@@ -187,7 +187,7 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
187
187
|
fail_fast: {
|
|
188
188
|
type: 'boolean',
|
|
189
189
|
description:
|
|
190
|
-
|
|
190
|
+
"Stop dequeueing new devices on the first failure (default: playbook's own value).",
|
|
191
191
|
},
|
|
192
192
|
continue_on_error: {
|
|
193
193
|
type: 'boolean',
|
|
@@ -196,8 +196,7 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
196
196
|
},
|
|
197
197
|
vars: {
|
|
198
198
|
type: 'object',
|
|
199
|
-
description:
|
|
200
|
-
'Merge into the playbook\'s `vars` block before substitution.',
|
|
199
|
+
description: "Merge into the playbook's `vars` block before substitution.",
|
|
201
200
|
},
|
|
202
201
|
user: {
|
|
203
202
|
type: 'string',
|
|
@@ -141,20 +141,14 @@ async function toolProductUpdate(args) {
|
|
|
141
141
|
|
|
142
142
|
const fields = Object.keys(patch);
|
|
143
143
|
if (fields.length === 0) {
|
|
144
|
-
throw inputError(
|
|
145
|
-
'at least one of name, description, enabled, icon must be provided',
|
|
146
|
-
);
|
|
144
|
+
throw inputError('at least one of name, description, enabled, icon must be provided');
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
await updateProduct(args.product, args.user, patch);
|
|
150
148
|
|
|
151
|
-
const summary = fields
|
|
152
|
-
.map((f) => (f === 'config' ? 'icon' : f))
|
|
153
|
-
.join(', ');
|
|
149
|
+
const summary = fields.map((f) => (f === 'config' ? 'icon' : f)).join(', ');
|
|
154
150
|
return {
|
|
155
|
-
content: [
|
|
156
|
-
{ type: 'text', text: `Updated product "${args.product}" (${summary}).` },
|
|
157
|
-
],
|
|
151
|
+
content: [{ type: 'text', text: `Updated product "${args.product}" (${summary}).` }],
|
|
158
152
|
isError: false,
|
|
159
153
|
};
|
|
160
154
|
}
|
|
@@ -430,7 +424,7 @@ async function toolProductPlaybookWrite(args) {
|
|
|
430
424
|
async function toolProductPlaybookRun(args) {
|
|
431
425
|
if (!args.product) throw inputError('product is required');
|
|
432
426
|
if (!args.name) throw inputError('name is required');
|
|
433
|
-
if (!args.
|
|
427
|
+
if (!args.device_id) throw inputError('device is required (single-device only)');
|
|
434
428
|
|
|
435
429
|
const entry = await findProductPlaybook(args.product, args.name, args.user);
|
|
436
430
|
if (!entry) {
|
|
@@ -453,7 +447,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
453
447
|
throw inputError(err.message);
|
|
454
448
|
}
|
|
455
449
|
|
|
456
|
-
pb.target.devices = [args.
|
|
450
|
+
pb.target.devices = [args.device_id];
|
|
457
451
|
pb.target.product = args.product;
|
|
458
452
|
pb.target.group = null;
|
|
459
453
|
|
|
@@ -463,7 +457,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
463
457
|
dry_run: true,
|
|
464
458
|
product: args.product,
|
|
465
459
|
name: args.name,
|
|
466
|
-
device: args.
|
|
460
|
+
device: args.device_id,
|
|
467
461
|
variables: listVariables(pb),
|
|
468
462
|
vars: resolvedScope,
|
|
469
463
|
steps: plan,
|
|
@@ -475,11 +469,10 @@ async function toolProductPlaybookRun(args) {
|
|
|
475
469
|
}
|
|
476
470
|
|
|
477
471
|
const all = await getDevices({ product: args.product }, args.user);
|
|
478
|
-
const device =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
};
|
|
472
|
+
const device = all.find((d) => d.device === args.device_id) || {
|
|
473
|
+
device: args.device_id,
|
|
474
|
+
connection: { active: false },
|
|
475
|
+
};
|
|
483
476
|
|
|
484
477
|
const results = await runPlaybook(pb, [device], {
|
|
485
478
|
user: args.user,
|
|
@@ -492,7 +485,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
492
485
|
const payload = {
|
|
493
486
|
product: args.product,
|
|
494
487
|
name: args.name,
|
|
495
|
-
device: args.
|
|
488
|
+
device: args.device_id,
|
|
496
489
|
mode: args.check ? 'check' : 'apply',
|
|
497
490
|
ok: !!result?.ok,
|
|
498
491
|
result,
|
|
@@ -508,9 +501,7 @@ async function toolProductPlaybookRollout(args) {
|
|
|
508
501
|
if (!args.name) throw inputError('name is required');
|
|
509
502
|
|
|
510
503
|
const batchSize =
|
|
511
|
-
Number.isFinite(args.batch_size) && args.batch_size > 0
|
|
512
|
-
? Math.floor(args.batch_size)
|
|
513
|
-
: 5;
|
|
504
|
+
Number.isFinite(args.batch_size) && args.batch_size > 0 ? Math.floor(args.batch_size) : 5;
|
|
514
505
|
const failureThreshold =
|
|
515
506
|
args.failure_threshold === undefined || args.failure_threshold === null
|
|
516
507
|
? DEFAULT_FAILURE_THRESHOLD
|
|
@@ -1455,9 +1446,10 @@ Pass \`dry_run: true\` to get the resolved plan without contacting the device. P
|
|
|
1455
1446
|
type: 'string',
|
|
1456
1447
|
description: 'Playbook name as registered in the product index.',
|
|
1457
1448
|
},
|
|
1458
|
-
|
|
1449
|
+
device_id: {
|
|
1459
1450
|
type: 'string',
|
|
1460
|
-
description:
|
|
1451
|
+
description:
|
|
1452
|
+
'Target device ID (single-device only). Pick any device of the product.',
|
|
1461
1453
|
},
|
|
1462
1454
|
vars: {
|
|
1463
1455
|
type: 'object',
|
|
@@ -1477,7 +1469,7 @@ Pass \`dry_run: true\` to get the resolved plan without contacting the device. P
|
|
|
1477
1469
|
},
|
|
1478
1470
|
user: { type: 'string', description: 'API user (admin impersonation).' },
|
|
1479
1471
|
},
|
|
1480
|
-
required: ['product', 'name', '
|
|
1472
|
+
required: ['product', 'name', 'device_id'],
|
|
1481
1473
|
},
|
|
1482
1474
|
handler: toolProductPlaybookRun,
|
|
1483
1475
|
},
|
package/lib/mcp/tools-profile.js
CHANGED
|
@@ -401,7 +401,8 @@ Gotchas:
|
|
|
401
401
|
},
|
|
402
402
|
enabled: {
|
|
403
403
|
type: 'boolean',
|
|
404
|
-
description:
|
|
404
|
+
description:
|
|
405
|
+
'Default: true. Set to false to stage a definition without activating it.',
|
|
405
406
|
},
|
|
406
407
|
user: { type: 'string', description: 'API user.' },
|
|
407
408
|
},
|
|
@@ -3,17 +3,17 @@ import { getDeviceProperties, getDeviceProperty, setDeviceProperty } from '../pr
|
|
|
3
3
|
import { inputError } from '../errors.js';
|
|
4
4
|
|
|
5
5
|
async function toolPropertyGet(args) {
|
|
6
|
-
const device = args.
|
|
6
|
+
const device = args.device_id;
|
|
7
7
|
if (!device)
|
|
8
8
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
9
9
|
if (args.property) {
|
|
10
|
-
const value = await getDeviceProperty(device, args.property);
|
|
10
|
+
const value = await getDeviceProperty(device, args.property, args.user);
|
|
11
11
|
return {
|
|
12
12
|
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
13
13
|
isError: false,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
const list = await getDeviceProperties(device);
|
|
16
|
+
const list = await getDeviceProperties(device, args.user);
|
|
17
17
|
const names = Array.isArray(list) ? list.map((p) => p.property).filter(Boolean) : [];
|
|
18
18
|
return {
|
|
19
19
|
content: [{ type: 'text', text: names.length ? names.join('\n') : '(no properties)' }],
|
|
@@ -22,12 +22,12 @@ async function toolPropertyGet(args) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function toolPropertySet(args) {
|
|
25
|
-
const device = args.
|
|
25
|
+
const device = args.device_id;
|
|
26
26
|
if (!device)
|
|
27
27
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
28
28
|
if (!args.property) throw inputError('property is required');
|
|
29
29
|
if (args.value === undefined) throw inputError('value is required');
|
|
30
|
-
const saved = await setDeviceProperty(device, args.property, args.value);
|
|
30
|
+
const saved = await setDeviceProperty(device, args.property, args.value, args.user);
|
|
31
31
|
return {
|
|
32
32
|
content: [
|
|
33
33
|
{
|
|
@@ -51,10 +51,10 @@ export const tools = [
|
|
|
51
51
|
description:
|
|
52
52
|
'Property name. Omit to list the names of all properties on the device.',
|
|
53
53
|
},
|
|
54
|
-
|
|
54
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
55
55
|
user: { type: 'string', description: 'API user' },
|
|
56
56
|
},
|
|
57
|
-
required: ['
|
|
57
|
+
required: ['device_id'],
|
|
58
58
|
},
|
|
59
59
|
handler: toolPropertyGet,
|
|
60
60
|
},
|
|
@@ -69,10 +69,10 @@ export const tools = [
|
|
|
69
69
|
description:
|
|
70
70
|
'Any JSON-serialisable value (string, number, boolean, object, array).',
|
|
71
71
|
},
|
|
72
|
-
|
|
72
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
73
73
|
user: { type: 'string', description: 'API user' },
|
|
74
74
|
},
|
|
75
|
-
required: ['property', 'value', '
|
|
75
|
+
required: ['property', 'value', 'device_id'],
|
|
76
76
|
},
|
|
77
77
|
handler: toolPropertySet,
|
|
78
78
|
},
|
|
@@ -3,7 +3,7 @@ import { callDeviceResource, listDeviceResourcesWithSchemas } from '../resource.
|
|
|
3
3
|
import { inputError } from '../errors.js';
|
|
4
4
|
|
|
5
5
|
async function toolResourceList(args) {
|
|
6
|
-
const device = args.
|
|
6
|
+
const device = args.device_id;
|
|
7
7
|
if (!device)
|
|
8
8
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
9
9
|
const entries = await listDeviceResourcesWithSchemas(device, { user: args.user });
|
|
@@ -22,7 +22,7 @@ async function toolResourceList(args) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function toolResourceCall(args) {
|
|
25
|
-
const device = args.
|
|
25
|
+
const device = args.device_id;
|
|
26
26
|
if (!device)
|
|
27
27
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
28
28
|
if (!args.resource) throw inputError('resource is required');
|
|
@@ -47,10 +47,10 @@ export const tools = [
|
|
|
47
47
|
inputSchema: {
|
|
48
48
|
type: 'object',
|
|
49
49
|
properties: {
|
|
50
|
-
|
|
50
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
51
51
|
user: { type: 'string', description: 'API user' },
|
|
52
52
|
},
|
|
53
|
-
required: ['
|
|
53
|
+
required: ['device_id'],
|
|
54
54
|
},
|
|
55
55
|
handler: toolResourceList,
|
|
56
56
|
},
|
|
@@ -68,10 +68,10 @@ export const tools = [
|
|
|
68
68
|
description:
|
|
69
69
|
'JSON object with the input parameters declared by the resource. Omit or pass {} for input-less resources.',
|
|
70
70
|
},
|
|
71
|
-
|
|
71
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
72
72
|
user: { type: 'string', description: 'API user' },
|
|
73
73
|
},
|
|
74
|
-
required: ['resource', '
|
|
74
|
+
required: ['resource', 'device_id'],
|
|
75
75
|
},
|
|
76
76
|
handler: toolResourceCall,
|
|
77
77
|
},
|
package/lib/mcp/tools-scripts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { inputError } from '../errors.js';
|
|
|
4
4
|
import { getAPI } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
async function toolScriptList(args) {
|
|
7
|
-
const device = args.
|
|
7
|
+
const device = args.device_id;
|
|
8
8
|
if (!device)
|
|
9
9
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
10
10
|
const info = await readDeviceResource(device, '$scripts/info', { user: args.user });
|
|
@@ -25,7 +25,7 @@ async function toolScriptList(args) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function toolScriptWrite(args) {
|
|
28
|
-
const device = args.
|
|
28
|
+
const device = args.device_id;
|
|
29
29
|
if (!device)
|
|
30
30
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
31
31
|
if (!args.name) throw inputError('name is required');
|
|
@@ -76,7 +76,7 @@ async function toolScriptWrite(args) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async function toolScriptDelete(args) {
|
|
79
|
-
const device = args.
|
|
79
|
+
const device = args.device_id;
|
|
80
80
|
if (!device)
|
|
81
81
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
82
82
|
if (!args.name) throw inputError('name is required');
|
|
@@ -113,10 +113,10 @@ export const tools = [
|
|
|
113
113
|
inputSchema: {
|
|
114
114
|
type: 'object',
|
|
115
115
|
properties: {
|
|
116
|
-
|
|
116
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
117
117
|
user: { type: 'string', description: 'API user' },
|
|
118
118
|
},
|
|
119
|
-
required: ['
|
|
119
|
+
required: ['device_id'],
|
|
120
120
|
},
|
|
121
121
|
handler: toolScriptList,
|
|
122
122
|
},
|
|
@@ -144,10 +144,10 @@ This tool writes the script, makes it executable, and calls the agent's \`$scrip
|
|
|
144
144
|
type: 'string',
|
|
145
145
|
description: 'Full script source code, including shebang.',
|
|
146
146
|
},
|
|
147
|
-
|
|
147
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
148
148
|
user: { type: 'string', description: 'API user' },
|
|
149
149
|
},
|
|
150
|
-
required: ['name', 'content', '
|
|
150
|
+
required: ['name', 'content', 'device_id'],
|
|
151
151
|
},
|
|
152
152
|
handler: toolScriptWrite,
|
|
153
153
|
},
|
|
@@ -162,10 +162,10 @@ This tool writes the script, makes it executable, and calls the agent's \`$scrip
|
|
|
162
162
|
description:
|
|
163
163
|
'Name of the script to remove (must match the file basename, without extension).',
|
|
164
164
|
},
|
|
165
|
-
|
|
165
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
166
166
|
user: { type: 'string', description: 'API user' },
|
|
167
167
|
},
|
|
168
|
-
required: ['name', '
|
|
168
|
+
required: ['name', 'device_id'],
|
|
169
169
|
},
|
|
170
170
|
handler: toolScriptDelete,
|
|
171
171
|
},
|