@thinger-io/cli-core 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +207 -0
- package/README.md +82 -31
- package/lib/auth.js +25 -1
- 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 +302 -64
- 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 +1234 -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/lib/commands/device.js
CHANGED
|
@@ -5,159 +5,163 @@ import {
|
|
|
5
5
|
createDevice,
|
|
6
6
|
deleteDevice,
|
|
7
7
|
generateDeviceCredentials,
|
|
8
|
-
getDevice,
|
|
9
8
|
getDevices,
|
|
10
9
|
} from '../devices.js';
|
|
11
|
-
import {
|
|
12
|
-
formatDeviceProperties,
|
|
13
|
-
formatDeviceProperty,
|
|
14
|
-
getDeviceProperties,
|
|
15
|
-
getDeviceProperty,
|
|
16
|
-
setDeviceProperty,
|
|
17
|
-
} from '../property.js';
|
|
18
|
-
import {
|
|
19
|
-
callDeviceResource,
|
|
20
|
-
formatDeviceResourcesWithSchemas,
|
|
21
|
-
listDeviceResourcesWithSchemas,
|
|
22
|
-
} from '../resource.js';
|
|
23
10
|
import { setDeviceProduct } from '../product.js';
|
|
24
|
-
import {
|
|
11
|
+
import { inputError } from '../errors.js';
|
|
12
|
+
import { hint, info, label, success } from '../format.js';
|
|
25
13
|
import { classifyError, isJsonMode, printErr, printOk } from '../output.js';
|
|
14
|
+
import {
|
|
15
|
+
hasListOptions,
|
|
16
|
+
listOptions,
|
|
17
|
+
listRecords,
|
|
18
|
+
projectsOption,
|
|
19
|
+
setProjects,
|
|
20
|
+
} from './resources.js';
|
|
26
21
|
import {
|
|
27
22
|
applyJsonFlag,
|
|
28
|
-
|
|
23
|
+
bodyFromOptions,
|
|
24
|
+
bodyOptions,
|
|
29
25
|
confirmDestructive,
|
|
30
26
|
ensureConfigured,
|
|
31
|
-
extractField,
|
|
32
27
|
getGlobalUser,
|
|
33
|
-
parseJsonValue,
|
|
34
28
|
runCommand,
|
|
35
29
|
} from './_shared.js';
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
|
-
* `<cli> device <
|
|
39
|
-
*
|
|
32
|
+
* `<cli> device <verb> <device> …` — the device records written by hand
|
|
33
|
+
* (list as a table, create with generated credentials, delete, set-product);
|
|
34
|
+
* the rest of the group (get, update, property, resource, send…) comes from
|
|
35
|
+
* the tool and resource commands.
|
|
40
36
|
*/
|
|
41
37
|
export function registerDeviceCommands(program) {
|
|
42
38
|
const device = program
|
|
43
39
|
.command('device')
|
|
44
|
-
.helpGroup('
|
|
45
|
-
.description('Devices:
|
|
46
|
-
|
|
47
|
-
device
|
|
48
|
-
.command('list [pattern]')
|
|
49
|
-
.helpGroup('Discovery:')
|
|
50
|
-
.description(
|
|
51
|
-
'List devices, optionally filtered by name/id pattern (case-insensitive regex)',
|
|
52
|
-
)
|
|
53
|
-
.option('-j, --json', 'Output as JSON')
|
|
54
|
-
.action(async (pattern, opts, cmd) => {
|
|
55
|
-
applyJsonFlag(opts);
|
|
56
|
-
ensureConfigured();
|
|
57
|
-
const user = getGlobalUser(cmd);
|
|
58
|
-
const filter = pattern ? { name: pattern } : {};
|
|
59
|
-
await runCommand({
|
|
60
|
-
start: pattern
|
|
61
|
-
? `Searching devices matching "${pattern}"...`
|
|
62
|
-
: 'Fetching devices...',
|
|
63
|
-
fn: () => getDevices(filter, user),
|
|
64
|
-
success: (devices) => `Found ${devices.length} device(s)`,
|
|
65
|
-
failure: 'Failed to list devices',
|
|
66
|
-
onSuccess: (devices) => {
|
|
67
|
-
if (isJsonMode()) {
|
|
68
|
-
printOk(devices);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
// Pad the id column so trailing names line up even when
|
|
72
|
-
// ids have very different widths. A record without an id
|
|
73
|
-
// can exist (a broken document on the server): show it
|
|
74
|
-
// rather than fail the whole listing.
|
|
75
|
-
const idOf = (d) => d.device ?? '(no id)';
|
|
76
|
-
const idWidth = devices.reduce((w, d) => Math.max(w, idOf(d).length), 0);
|
|
77
|
-
for (const d of devices) {
|
|
78
|
-
const online = d.connection?.active ? success('online ') : hint('offline');
|
|
79
|
-
const id = label(idOf(d).padEnd(idWidth));
|
|
80
|
-
const extras = [
|
|
81
|
-
d.name ? hint(`(${d.name})`) : '',
|
|
82
|
-
d.type && d.type !== 'Generic' ? hint(`<${d.type}>`) : '',
|
|
83
|
-
d.product ? info(`[${d.product}]`) : '',
|
|
84
|
-
].filter(Boolean);
|
|
85
|
-
console.log(
|
|
86
|
-
` ${online} ${id}${extras.length ? ' ' + extras.join(' ') : ''}`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
});
|
|
40
|
+
.helpGroup('Resources:')
|
|
41
|
+
.description('Devices: records, properties, resources, data and their children');
|
|
92
42
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
43
|
+
listOptions(
|
|
44
|
+
device
|
|
45
|
+
.command('list')
|
|
46
|
+
.helpGroup('Records:')
|
|
47
|
+
.description('List devices: online state, id, name, type and product')
|
|
48
|
+
.option('-j, --json', 'Output as JSON'),
|
|
49
|
+
).action(async (opts, cmd) => {
|
|
50
|
+
applyJsonFlag(opts);
|
|
51
|
+
ensureConfigured();
|
|
52
|
+
const user = getGlobalUser(cmd);
|
|
53
|
+
await runCommand({
|
|
54
|
+
start: 'Fetching devices...',
|
|
55
|
+
fn: () =>
|
|
56
|
+
hasListOptions(opts) ? listRecords('device', opts, user) : getDevices({}, user),
|
|
57
|
+
success: (devices) => `Found ${devices.length} device(s)`,
|
|
58
|
+
failure: 'Failed to list devices',
|
|
59
|
+
onSuccess: (devices) => {
|
|
60
|
+
if (isJsonMode()) {
|
|
61
|
+
printOk(devices);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Pad the id column so trailing names line up even when
|
|
65
|
+
// ids have very different widths. A record without an id
|
|
66
|
+
// can exist (a broken document on the server): show it
|
|
67
|
+
// rather than fail the whole listing.
|
|
68
|
+
const idOf = (d) => d.device ?? '(no id)';
|
|
69
|
+
const idWidth = devices.reduce((w, d) => Math.max(w, idOf(d).length), 0);
|
|
70
|
+
for (const d of devices) {
|
|
71
|
+
const online = d.connection?.active ? success('online ') : hint('offline');
|
|
72
|
+
const id = label(idOf(d).padEnd(idWidth));
|
|
73
|
+
const extras = [
|
|
74
|
+
d.name ? hint(`(${d.name})`) : '',
|
|
75
|
+
d.type && d.type !== 'Generic' ? hint(`<${d.type}>`) : '',
|
|
76
|
+
d.product ? info(`[${d.product}]`) : '',
|
|
77
|
+
].filter(Boolean);
|
|
78
|
+
console.log(
|
|
79
|
+
` ${online} ${id}${extras.length ? ' ' + extras.join(' ') : ''}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
112
83
|
});
|
|
84
|
+
});
|
|
113
85
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
86
|
+
projectsOption(
|
|
87
|
+
bodyOptions(
|
|
88
|
+
device
|
|
89
|
+
.command('create [device]')
|
|
90
|
+
.helpGroup('Records:')
|
|
91
|
+
.description(
|
|
92
|
+
'Register a device; Generic/MQTT ones get random credentials unless given',
|
|
93
|
+
)
|
|
94
|
+
.option(
|
|
95
|
+
'-t, --type <type>',
|
|
96
|
+
`Device type: ${DEVICE_TYPES.join(', ')} (default: Generic)`,
|
|
97
|
+
)
|
|
98
|
+
.option('-c, --credentials <credentials>', 'Device credentials (6+ characters)')
|
|
99
|
+
.option('-n, --name <name>', 'Display name')
|
|
100
|
+
.option('-d, --description <text>', 'Description')
|
|
101
|
+
.option('-p, --product <product>', 'Assign to a product'),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
123
104
|
.option('-j, --json', 'Output as JSON')
|
|
124
|
-
.action(async (
|
|
105
|
+
.action(async (id, opts, cmd) => {
|
|
125
106
|
applyJsonFlag(opts);
|
|
126
107
|
ensureConfigured();
|
|
127
108
|
const user = getGlobalUser(cmd);
|
|
128
|
-
|
|
109
|
+
let body;
|
|
110
|
+
try {
|
|
111
|
+
// The flags go over what --from and --set give.
|
|
112
|
+
body = (await bodyFromOptions(opts)) ?? {};
|
|
113
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
114
|
+
throw inputError('create takes a JSON object');
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const { message, code } = classifyError(error);
|
|
118
|
+
printErr(message, { code });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const deviceId = id ?? body.device;
|
|
122
|
+
const type = opts.type ?? body.type ?? 'Generic';
|
|
123
|
+
if (!deviceId) {
|
|
124
|
+
printErr('give the device id: <device>, or the body field "device"', {
|
|
125
|
+
code: 'input_error',
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!DEVICE_TYPES.includes(type)) {
|
|
129
130
|
printErr(`--type must be one of ${DEVICE_TYPES.join(', ')}`, {
|
|
130
131
|
code: 'input_error',
|
|
131
132
|
});
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
|
-
const needsCredentials = CREDENTIAL_DEVICE_TYPES.has(
|
|
135
|
+
const needsCredentials = CREDENTIAL_DEVICE_TYPES.has(type);
|
|
135
136
|
if (!needsCredentials && opts.credentials) {
|
|
136
|
-
printErr(`${
|
|
137
|
+
printErr(`${type} devices do not take credentials`, { code: 'input_error' });
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
|
-
|
|
140
|
+
const given = opts.credentials ?? body.credentials;
|
|
141
|
+
if (given && given.length < 6) {
|
|
140
142
|
printErr('credentials must be at least 6 characters', { code: 'input_error' });
|
|
141
143
|
return;
|
|
142
144
|
}
|
|
143
|
-
const credentials = needsCredentials
|
|
144
|
-
? opts.credentials || generateDeviceCredentials()
|
|
145
|
-
: undefined;
|
|
145
|
+
const credentials = needsCredentials ? given || generateDeviceCredentials() : undefined;
|
|
146
146
|
await runCommand({
|
|
147
147
|
start: `Creating ${deviceId}...`,
|
|
148
|
-
fn: () =>
|
|
149
|
-
createDevice(
|
|
148
|
+
fn: async () => {
|
|
149
|
+
const sent = await createDevice(
|
|
150
150
|
{
|
|
151
|
+
...body,
|
|
151
152
|
device: deviceId,
|
|
152
|
-
type
|
|
153
|
-
name: opts.name,
|
|
154
|
-
description: opts.description,
|
|
153
|
+
type,
|
|
154
|
+
name: opts.name ?? body.name,
|
|
155
|
+
description: opts.description ?? body.description,
|
|
155
156
|
credentials,
|
|
156
|
-
product: opts.product,
|
|
157
|
+
product: opts.product ?? body.product,
|
|
157
158
|
},
|
|
158
159
|
user,
|
|
159
|
-
)
|
|
160
|
-
|
|
160
|
+
);
|
|
161
|
+
if (opts.projects) await setProjects('device', deviceId, opts.projects, user);
|
|
162
|
+
return sent;
|
|
163
|
+
},
|
|
164
|
+
success: `Created ${type} device ${deviceId}`,
|
|
161
165
|
failure: 'Failed to create device',
|
|
162
166
|
onSuccess: (sent) => {
|
|
163
167
|
if (isJsonMode()) {
|
|
@@ -177,35 +181,26 @@ export function registerDeviceCommands(program) {
|
|
|
177
181
|
});
|
|
178
182
|
|
|
179
183
|
device
|
|
180
|
-
.command('delete <
|
|
184
|
+
.command('delete <device>')
|
|
181
185
|
.helpGroup('Records:')
|
|
182
186
|
.description('Delete a device record from the platform (irreversible)')
|
|
183
187
|
.option('-y, --yes', 'Skip the interactive confirmation prompt')
|
|
184
188
|
.option('-j, --json', 'Output as JSON')
|
|
185
|
-
.action(async (
|
|
189
|
+
.action(async (id, opts, cmd) => {
|
|
186
190
|
applyJsonFlag(opts);
|
|
187
191
|
ensureConfigured();
|
|
188
192
|
const user = getGlobalUser(cmd);
|
|
189
|
-
if (
|
|
190
|
-
!(await confirmDestructive(
|
|
191
|
-
opts,
|
|
192
|
-
`Delete device ${deviceId}? This cannot be undone.`,
|
|
193
|
-
))
|
|
194
|
-
) {
|
|
193
|
+
if (!(await confirmDestructive(opts, `Delete device ${id}? This cannot be undone.`))) {
|
|
195
194
|
console.log(hint('Cancelled.'));
|
|
196
195
|
return;
|
|
197
196
|
}
|
|
198
197
|
try {
|
|
199
|
-
const removed = await deleteDevice(
|
|
198
|
+
const removed = await deleteDevice(id, user);
|
|
200
199
|
if (isJsonMode()) {
|
|
201
|
-
printOk({ device:
|
|
200
|
+
printOk({ device: id, removed });
|
|
202
201
|
return;
|
|
203
202
|
}
|
|
204
|
-
console.log(
|
|
205
|
-
removed
|
|
206
|
-
? `Deleted ${info(deviceId)}`
|
|
207
|
-
: `Device ${info(deviceId)} did not exist`,
|
|
208
|
-
);
|
|
203
|
+
console.log(removed ? `Deleted ${info(id)}` : `Device ${info(id)} did not exist`);
|
|
209
204
|
} catch (error) {
|
|
210
205
|
const { message, code } = classifyError(error);
|
|
211
206
|
printErr(message, { code });
|
|
@@ -213,129 +208,21 @@ export function registerDeviceCommands(program) {
|
|
|
213
208
|
});
|
|
214
209
|
|
|
215
210
|
device
|
|
216
|
-
.command('set-product <
|
|
217
|
-
.helpGroup('
|
|
211
|
+
.command('set-product <device> <product>')
|
|
212
|
+
.helpGroup('Actions:')
|
|
218
213
|
.description('Assign a device to a product ("" to unassign)')
|
|
219
214
|
.option('-j, --json', 'Output as JSON')
|
|
220
|
-
.action(async (
|
|
215
|
+
.action(async (id, productId, opts, cmd) => {
|
|
221
216
|
applyJsonFlag(opts);
|
|
222
217
|
ensureConfigured();
|
|
223
218
|
const user = getGlobalUser(cmd);
|
|
224
219
|
await runCommand({
|
|
225
|
-
start: `Assigning ${
|
|
226
|
-
fn: () => setDeviceProduct(
|
|
227
|
-
success: productId
|
|
228
|
-
? `Assigned ${deviceId} → ${productId}`
|
|
229
|
-
: `Unassigned ${deviceId}`,
|
|
220
|
+
start: `Assigning ${id}...`,
|
|
221
|
+
fn: () => setDeviceProduct(id, productId, user),
|
|
222
|
+
success: productId ? `Assigned ${id} → ${productId}` : `Unassigned ${id}`,
|
|
230
223
|
failure: 'Failed to assign product',
|
|
231
224
|
onSuccess: () => {
|
|
232
|
-
if (isJsonMode()) printOk({ device:
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
device
|
|
238
|
-
.command('property <deviceId> [propertyId]')
|
|
239
|
-
.helpGroup('State & resources:')
|
|
240
|
-
.description('List device properties, or read one by ID')
|
|
241
|
-
.option('-j, --json', 'Output as JSON')
|
|
242
|
-
.option('-f, --field <field>', 'Extract a sub-field (dot path, e.g. data.value)')
|
|
243
|
-
.action(async (deviceId, propertyId, opts, cmd) => {
|
|
244
|
-
applyJsonFlag(opts);
|
|
245
|
-
ensureConfigured();
|
|
246
|
-
const user = getGlobalUser(cmd);
|
|
247
|
-
if (!propertyId) {
|
|
248
|
-
await runCommand({
|
|
249
|
-
start: `Getting properties for ${deviceId}...`,
|
|
250
|
-
fn: () => getDeviceProperties(deviceId, user),
|
|
251
|
-
success: null,
|
|
252
|
-
failure: 'Property retrieval failed',
|
|
253
|
-
onSuccess: (result) => {
|
|
254
|
-
if (isJsonMode()) printOk(result);
|
|
255
|
-
else console.log(formatDeviceProperties(deviceId, result));
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
await runCommand({
|
|
261
|
-
start: `Retrieving property for ${deviceId}...`,
|
|
262
|
-
fn: () => getDeviceProperty(deviceId, propertyId, user),
|
|
263
|
-
success: null,
|
|
264
|
-
failure: 'Property retrieval failed',
|
|
265
|
-
onSuccess: (property) => {
|
|
266
|
-
const value = extractField(property, opts.field);
|
|
267
|
-
if (isJsonMode()) printOk(value);
|
|
268
|
-
else if (opts.field) console.log(value);
|
|
269
|
-
else console.log(formatDeviceProperty(deviceId, propertyId, property));
|
|
270
|
-
},
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
device
|
|
275
|
-
.command('property-set <deviceId> <propertyId> <value>')
|
|
276
|
-
.helpGroup('State & resources:')
|
|
277
|
-
.description('Create or overwrite a device property (value parsed as JSON)')
|
|
278
|
-
.option('-j, --json', 'Output as JSON')
|
|
279
|
-
.action(async (deviceId, propertyId, value, opts, cmd) => {
|
|
280
|
-
applyJsonFlag(opts);
|
|
281
|
-
ensureConfigured();
|
|
282
|
-
const user = getGlobalUser(cmd);
|
|
283
|
-
const parsed = parseJsonValue(value);
|
|
284
|
-
await runCommand({
|
|
285
|
-
start: `Saving ${propertyId} on ${deviceId}...`,
|
|
286
|
-
fn: () => setDeviceProperty(deviceId, propertyId, parsed, user),
|
|
287
|
-
success: `Saved ${propertyId} on ${deviceId}`,
|
|
288
|
-
failure: 'Failed to save property',
|
|
289
|
-
onSuccess: () => {
|
|
290
|
-
if (isJsonMode())
|
|
291
|
-
printOk({ device: deviceId, property: propertyId, value: parsed });
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
device
|
|
297
|
-
.command('resource <deviceId> [resource]')
|
|
298
|
-
.helpGroup('State & resources:')
|
|
299
|
-
.description('List device resources, or call one by name (the device must be online)')
|
|
300
|
-
.option('-j, --json', 'Output as JSON')
|
|
301
|
-
.option('-f, --field <field>', 'Extract a sub-field from the result (dot path)')
|
|
302
|
-
.option(
|
|
303
|
-
'-i, --input <key=value>',
|
|
304
|
-
'Resource input, value parsed as JSON (repeatable)',
|
|
305
|
-
collectKeyValue,
|
|
306
|
-
{},
|
|
307
|
-
)
|
|
308
|
-
.action(async (deviceId, resource, opts, cmd) => {
|
|
309
|
-
applyJsonFlag(opts);
|
|
310
|
-
ensureConfigured();
|
|
311
|
-
const user = getGlobalUser(cmd);
|
|
312
|
-
if (!resource) {
|
|
313
|
-
await runCommand({
|
|
314
|
-
start: `Getting resources for ${deviceId}...`,
|
|
315
|
-
fn: () => listDeviceResourcesWithSchemas(deviceId, { user }),
|
|
316
|
-
success: null,
|
|
317
|
-
failure: 'Resource retrieval failed',
|
|
318
|
-
onSuccess: (entries) => {
|
|
319
|
-
if (isJsonMode()) printOk(entries);
|
|
320
|
-
else console.log(formatDeviceResourcesWithSchemas(deviceId, entries));
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
await runCommand({
|
|
326
|
-
start: `Calling resource ${resource} for ${deviceId}...`,
|
|
327
|
-
fn: () => callDeviceResource(deviceId, resource, opts.input, { user }),
|
|
328
|
-
success: null,
|
|
329
|
-
failure: 'Resource call failed',
|
|
330
|
-
onSuccess: (result) => {
|
|
331
|
-
const value = extractField(result, opts.field);
|
|
332
|
-
if (isJsonMode()) printOk(value);
|
|
333
|
-
else if (value === undefined || value === '')
|
|
334
|
-
console.log(warning('(no output)'));
|
|
335
|
-
else
|
|
336
|
-
console.log(
|
|
337
|
-
typeof value === 'string' ? value : JSON.stringify(value, null, 2),
|
|
338
|
-
);
|
|
225
|
+
if (isJsonMode()) printOk({ device: id, product: productId || null });
|
|
339
226
|
},
|
|
340
227
|
});
|
|
341
228
|
});
|