@thinremote/thinr-cli 1.3.4 → 1.4.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 +86 -0
- package/README.md +117 -123
- package/bin/thinr.js +43 -105
- package/commands/_shared.js +10 -90
- package/commands/device/_shared.js +2 -2
- package/commands/device/exec.js +2 -2
- package/commands/device/fs.js +2 -2
- package/commands/device/index.js +5 -19
- package/commands/device/logs.js +4 -4
- package/commands/device/status.js +1 -1
- package/commands/device/transfer.js +2 -2
- package/commands/device/update.js +8 -2
- package/commands/fleet/upgrade.js +3 -3
- package/commands/playbook/run.js +2 -2
- package/commands/playbook/validate.js +2 -2
- package/commands/product/_shared.js +1 -1
- package/commands/product/exec.js +2 -2
- package/commands/product/fs.js +2 -2
- package/commands/product/index.js +4 -11
- package/commands/product/logs.js +2 -2
- package/commands/product/metric.js +2 -2
- package/commands/product/playbook.js +5 -5
- package/commands/product/property.js +4 -4
- package/commands/product/push.js +2 -2
- package/commands/product/resource.js +4 -4
- package/commands/product/status.js +3 -3
- package/dist/dashboard.js +12 -12
- package/docs/guides/thinremote-metrics-logs.md +59 -0
- package/docs/guides/thinremote-playbooks.md +133 -0
- package/docs/guides/thinremote-scripts.md +115 -0
- package/docs/guides/thinremote.md +116 -0
- package/lib/command-stream.js +3 -2
- package/lib/console.js +6 -6
- package/lib/constants.js +1 -19
- package/lib/dashboard/event-stream.js +5 -5
- package/lib/device-api.js +4 -4
- package/lib/guides.js +41 -0
- package/lib/identity.js +75 -0
- package/lib/local-files.js +54 -0
- package/lib/mcp/{tools-install.js → agent-install.js} +12 -61
- package/lib/mcp/helpers.js +5 -25
- package/lib/mcp/playbook-ops.js +393 -0
- package/lib/mcp/registry.js +9 -43
- package/lib/mcp/resource-hooks.js +53 -0
- package/lib/mcp/resource-types.js +857 -0
- package/lib/mcp/tools-exec.js +217 -177
- package/lib/monitoring.js +3 -3
- package/lib/playbook/actions.js +30 -18
- package/lib/playbook/expression.js +1 -4
- package/lib/playbook/fleet.js +4 -1
- package/lib/playbook/loader.js +16 -8
- package/lib/playbook/runner.js +40 -8
- package/lib/playbook/schema.js +108 -34
- package/lib/product/log-presets.js +1 -1
- package/lib/product/logs.js +2 -2
- package/lib/product-orchestrator.js +14 -9
- package/lib/product.js +29 -227
- package/lib/proxy.js +4 -4
- package/lib/status.js +13 -5
- package/package.json +4 -2
- package/commands/device/delete.js +0 -53
- package/commands/device/list.js +0 -55
- package/commands/device/property.js +0 -52
- package/commands/device/resource.js +0 -52
- package/commands/logout.js +0 -47
- package/commands/product/list.js +0 -86
- package/commands/product/props.js +0 -125
- package/commands/profile.js +0 -173
- package/lib/alarms.js +0 -223
- package/lib/api.js +0 -89
- package/lib/auth.js +0 -428
- package/lib/bucket.js +0 -82
- package/lib/concurrency.js +0 -52
- package/lib/config.js +0 -230
- package/lib/devices.js +0 -60
- package/lib/errors.js +0 -65
- package/lib/format.js +0 -114
- package/lib/mcp/server.js +0 -67
- package/lib/mcp/tools-alarms.js +0 -507
- package/lib/mcp/tools-buckets.js +0 -99
- package/lib/mcp/tools-devices.js +0 -235
- package/lib/mcp/tools-filesystem.js +0 -281
- package/lib/mcp/tools-logs.js +0 -225
- package/lib/mcp/tools-playbook.js +0 -210
- package/lib/mcp/tools-products.js +0 -1557
- package/lib/mcp/tools-profile.js +0 -635
- package/lib/mcp/tools-profiles.js +0 -30
- package/lib/mcp/tools-properties.js +0 -79
- package/lib/mcp/tools-resources.js +0 -78
- package/lib/mcp/tools-scripts.js +0 -212
- package/lib/mcp/tools-tokens.js +0 -369
- package/lib/output.js +0 -114
- package/lib/paths.js +0 -32
- package/lib/profile.js +0 -657
- package/lib/property.js +0 -75
- package/lib/resource.js +0 -151
- package/lib/storage.js +0 -117
- package/lib/tokens.js +0 -366
package/lib/playbook/loader.js
CHANGED
|
@@ -21,10 +21,9 @@ export function parsePlaybook(source, { sourcePath } = {}) {
|
|
|
21
21
|
try {
|
|
22
22
|
raw = YAML.parse(source);
|
|
23
23
|
} catch (err) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
24
|
+
throw new Error(`Invalid YAML${sourcePath ? ` in ${sourcePath}` : ''}: ${err.message}`, {
|
|
25
|
+
cause: err,
|
|
26
|
+
});
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
const errors = [];
|
|
@@ -98,7 +97,9 @@ function validateVars(v, errors) {
|
|
|
98
97
|
const variables = [];
|
|
99
98
|
for (const [name, raw] of Object.entries(v)) {
|
|
100
99
|
if (!IDENT_RE.test(name)) {
|
|
101
|
-
errors.push(
|
|
100
|
+
errors.push(
|
|
101
|
+
`\`vars.${name}\` is not a valid identifier (letters, digits, underscore; no leading digit).`,
|
|
102
|
+
);
|
|
102
103
|
continue;
|
|
103
104
|
}
|
|
104
105
|
const def = normaliseVarDef(name, raw, errors);
|
|
@@ -132,7 +133,9 @@ function normaliseVarDef(name, raw, errors) {
|
|
|
132
133
|
|
|
133
134
|
for (const key of Object.keys(raw)) {
|
|
134
135
|
if (!VAR_META_KEY_SET.has(key)) {
|
|
135
|
-
errors.push(
|
|
136
|
+
errors.push(
|
|
137
|
+
`\`vars.${name}\`: unknown metadata key "${key}" (expected: ${VAR_META_KEYS.join(', ')}).`,
|
|
138
|
+
);
|
|
136
139
|
return null;
|
|
137
140
|
}
|
|
138
141
|
}
|
|
@@ -230,8 +233,13 @@ function validateSteps(s, errors) {
|
|
|
230
233
|
errors.push(`${where}: \`when\` must be a string expression.`);
|
|
231
234
|
return;
|
|
232
235
|
}
|
|
233
|
-
if (
|
|
234
|
-
|
|
236
|
+
if (
|
|
237
|
+
register !== undefined &&
|
|
238
|
+
(typeof register !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(register))
|
|
239
|
+
) {
|
|
240
|
+
errors.push(
|
|
241
|
+
`${where}: \`register\` must be a valid identifier (letters, digits, underscore; no leading digit).`,
|
|
242
|
+
);
|
|
235
243
|
return;
|
|
236
244
|
}
|
|
237
245
|
out.push({
|
package/lib/playbook/runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import { filterActiveDevices, getDevices } from '
|
|
3
|
-
import { runPool } from '
|
|
2
|
+
import { filterActiveDevices, getDevices } from '@thinger-io/cli-core/devices';
|
|
3
|
+
import { runPool } from '@thinger-io/cli-core/concurrency';
|
|
4
4
|
import { HANDLERS, CHECKERS, createActionContext } from './actions.js';
|
|
5
5
|
|
|
6
6
|
/** Actions whose effect isn't derivable from observable state, so
|
|
@@ -37,7 +37,9 @@ export async function resolveTargets(pb, opts = {}) {
|
|
|
37
37
|
// id isn't in the user's fleet (unlikely).
|
|
38
38
|
const all = await getDevices({}, user);
|
|
39
39
|
const byId = Object.fromEntries(all.map((d) => [d.device, d]));
|
|
40
|
-
return target.devices.map(
|
|
40
|
+
return target.devices.map(
|
|
41
|
+
(id) => byId[id] || { device: id, connection: { active: false } },
|
|
42
|
+
);
|
|
41
43
|
}
|
|
42
44
|
if (!target.product) {
|
|
43
45
|
throw new Error('playbook target has no product/devices after overrides');
|
|
@@ -98,6 +100,7 @@ export function buildDryRunPlan(pb, opts) {
|
|
|
98
100
|
* failFast?: boolean,
|
|
99
101
|
* continueOnError?: boolean,
|
|
100
102
|
* baseDir?: string,
|
|
103
|
+
* localRoot?: string,
|
|
101
104
|
* checkMode?: boolean,
|
|
102
105
|
* overrides?: Record<string, unknown>,
|
|
103
106
|
* onStepStart?: (args: { deviceId: string, stepIndex: number, step: any }) => void,
|
|
@@ -117,7 +120,7 @@ export async function runPlaybook(pb, devices, opts = {}) {
|
|
|
117
120
|
|
|
118
121
|
const results = await runPool(devices, concurrency, async (device) => {
|
|
119
122
|
const deviceId = device.device;
|
|
120
|
-
const ctx = createActionContext({ deviceId, user, baseDir });
|
|
123
|
+
const ctx = createActionContext({ deviceId, user, baseDir, localRoot: opts.localRoot });
|
|
121
124
|
/** @type {Record<string, unknown>} */
|
|
122
125
|
const scope = buildScope(pb, opts, { device: deviceId });
|
|
123
126
|
const steps = [];
|
|
@@ -125,7 +128,14 @@ export async function runPlaybook(pb, devices, opts = {}) {
|
|
|
125
128
|
|
|
126
129
|
for (let i = 0; i < pb.steps.length; i++) {
|
|
127
130
|
if (aborted) {
|
|
128
|
-
steps.push({
|
|
131
|
+
steps.push({
|
|
132
|
+
index: i,
|
|
133
|
+
name: pb.steps[i].name,
|
|
134
|
+
ok: false,
|
|
135
|
+
skipped: true,
|
|
136
|
+
summary: 'skipped (fail-fast)',
|
|
137
|
+
durationMs: 0,
|
|
138
|
+
});
|
|
129
139
|
continue;
|
|
130
140
|
}
|
|
131
141
|
const step = pb.steps[i];
|
|
@@ -149,7 +159,15 @@ export async function runPlaybook(pb, devices, opts = {}) {
|
|
|
149
159
|
error: msg,
|
|
150
160
|
durationMs,
|
|
151
161
|
});
|
|
152
|
-
opts.onStepEnd?.({
|
|
162
|
+
opts.onStepEnd?.({
|
|
163
|
+
deviceId,
|
|
164
|
+
stepIndex: i,
|
|
165
|
+
step,
|
|
166
|
+
ok: false,
|
|
167
|
+
summary: msg,
|
|
168
|
+
error: msg,
|
|
169
|
+
durationMs,
|
|
170
|
+
});
|
|
153
171
|
deviceOk = false;
|
|
154
172
|
if (failFast) aborted = true;
|
|
155
173
|
break;
|
|
@@ -163,7 +181,14 @@ export async function runPlaybook(pb, devices, opts = {}) {
|
|
|
163
181
|
summary: `skipped (when: ${step.when})`,
|
|
164
182
|
durationMs: 0,
|
|
165
183
|
});
|
|
166
|
-
opts.onStepEnd?.({
|
|
184
|
+
opts.onStepEnd?.({
|
|
185
|
+
deviceId,
|
|
186
|
+
stepIndex: i,
|
|
187
|
+
step,
|
|
188
|
+
ok: true,
|
|
189
|
+
summary: 'skipped',
|
|
190
|
+
durationMs: 0,
|
|
191
|
+
});
|
|
167
192
|
continue;
|
|
168
193
|
}
|
|
169
194
|
}
|
|
@@ -245,7 +270,14 @@ export async function runPlaybook(pb, devices, opts = {}) {
|
|
|
245
270
|
const stdout = err && typeof err === 'object' ? err.stdout : undefined;
|
|
246
271
|
const stderr = err && typeof err === 'object' ? err.stderr : undefined;
|
|
247
272
|
const exitCode = err && typeof err === 'object' ? err.exitCode : undefined;
|
|
248
|
-
const entry = {
|
|
273
|
+
const entry = {
|
|
274
|
+
index: i,
|
|
275
|
+
name: step.name,
|
|
276
|
+
ok: false,
|
|
277
|
+
summary: msg,
|
|
278
|
+
error: msg,
|
|
279
|
+
durationMs,
|
|
280
|
+
};
|
|
249
281
|
if (stdout !== undefined) entry.stdout = stdout;
|
|
250
282
|
if (stderr !== undefined) entry.stderr = stderr;
|
|
251
283
|
if (exitCode !== undefined) entry.exitCode = exitCode;
|
package/lib/playbook/schema.js
CHANGED
|
@@ -20,8 +20,17 @@ export const ACTIONS = [
|
|
|
20
20
|
name: 'exec',
|
|
21
21
|
description: 'Run a shell command on the device and fail the step on non-zero exit.',
|
|
22
22
|
params: [
|
|
23
|
-
{
|
|
24
|
-
|
|
23
|
+
{
|
|
24
|
+
name: 'command',
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: true,
|
|
27
|
+
description: 'Shell command to run.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'timeout',
|
|
31
|
+
type: 'number',
|
|
32
|
+
description: 'Per-device timeout in seconds (default: 30).',
|
|
33
|
+
},
|
|
25
34
|
],
|
|
26
35
|
summary: (p) => `exec: ${p.command}`,
|
|
27
36
|
},
|
|
@@ -35,10 +44,21 @@ export const ACTIONS = [
|
|
|
35
44
|
},
|
|
36
45
|
{
|
|
37
46
|
name: 'write',
|
|
38
|
-
description:
|
|
47
|
+
description:
|
|
48
|
+
'Write inline content to a file on the device. Use `push` to upload a local file instead.',
|
|
39
49
|
params: [
|
|
40
|
-
{
|
|
41
|
-
|
|
50
|
+
{
|
|
51
|
+
name: 'path',
|
|
52
|
+
type: 'string',
|
|
53
|
+
required: true,
|
|
54
|
+
description: 'Absolute path on the device.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'content',
|
|
58
|
+
type: 'string',
|
|
59
|
+
required: true,
|
|
60
|
+
description: 'Inline content to write.',
|
|
61
|
+
},
|
|
42
62
|
],
|
|
43
63
|
summary: (p) => `write ${p.path}`,
|
|
44
64
|
},
|
|
@@ -46,8 +66,19 @@ export const ACTIONS = [
|
|
|
46
66
|
name: 'push',
|
|
47
67
|
description: 'Upload a local file to the device.',
|
|
48
68
|
params: [
|
|
49
|
-
{
|
|
50
|
-
|
|
69
|
+
{
|
|
70
|
+
name: 'source',
|
|
71
|
+
type: 'string',
|
|
72
|
+
required: true,
|
|
73
|
+
description:
|
|
74
|
+
'Local file path (relative paths resolve against the playbook directory).',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'destination',
|
|
78
|
+
type: 'string',
|
|
79
|
+
required: true,
|
|
80
|
+
description: 'Absolute path on the device.',
|
|
81
|
+
},
|
|
51
82
|
],
|
|
52
83
|
summary: (p) => `push ${p.source} → ${p.destination}`,
|
|
53
84
|
},
|
|
@@ -55,44 +86,79 @@ export const ACTIONS = [
|
|
|
55
86
|
name: 'pull',
|
|
56
87
|
description: 'Download a remote file from the device to the local disk.',
|
|
57
88
|
params: [
|
|
58
|
-
{
|
|
59
|
-
|
|
89
|
+
{
|
|
90
|
+
name: 'source',
|
|
91
|
+
type: 'string',
|
|
92
|
+
required: true,
|
|
93
|
+
description: 'Absolute path on the device.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'destination',
|
|
97
|
+
type: 'string',
|
|
98
|
+
required: true,
|
|
99
|
+
description:
|
|
100
|
+
'Local file path (relative paths resolve against the playbook directory).',
|
|
101
|
+
},
|
|
60
102
|
],
|
|
61
103
|
summary: (p) => `pull ${p.source} → ${p.destination}`,
|
|
62
104
|
},
|
|
63
105
|
{
|
|
64
|
-
name: '
|
|
106
|
+
name: 'delete',
|
|
65
107
|
description: 'Remove a file or directory on the device.',
|
|
66
108
|
params: [
|
|
67
|
-
{
|
|
68
|
-
|
|
109
|
+
{
|
|
110
|
+
name: 'path',
|
|
111
|
+
type: 'string',
|
|
112
|
+
required: true,
|
|
113
|
+
description: 'Absolute path on the device.',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'recursive',
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
description: 'Recurse into directories (default: true).',
|
|
119
|
+
},
|
|
69
120
|
],
|
|
70
|
-
summary: (p) => `
|
|
121
|
+
summary: (p) => `delete ${p.path}`,
|
|
71
122
|
},
|
|
72
123
|
{
|
|
73
124
|
name: 'mkdir',
|
|
74
125
|
description: 'Create a directory on the device.',
|
|
75
126
|
params: [
|
|
76
|
-
{
|
|
127
|
+
{
|
|
128
|
+
name: 'path',
|
|
129
|
+
type: 'string',
|
|
130
|
+
required: true,
|
|
131
|
+
description: 'Absolute directory path on the device.',
|
|
132
|
+
},
|
|
77
133
|
],
|
|
78
134
|
summary: (p) => `mkdir ${p.path}`,
|
|
79
135
|
},
|
|
80
136
|
{
|
|
81
|
-
name: '
|
|
82
|
-
description:
|
|
137
|
+
name: 'move',
|
|
138
|
+
description:
|
|
139
|
+
'Move or rename a path on the device. Handy for atomic deploys (write to tmp, move into place).',
|
|
83
140
|
params: [
|
|
84
141
|
{ name: 'source', type: 'string', required: true, description: 'Path to move from.' },
|
|
85
142
|
{ name: 'destination', type: 'string', required: true, description: 'Target path.' },
|
|
86
|
-
{
|
|
143
|
+
{
|
|
144
|
+
name: 'overwrite',
|
|
145
|
+
type: 'boolean',
|
|
146
|
+
description: 'Overwrite destination if it exists (default: false).',
|
|
147
|
+
},
|
|
87
148
|
],
|
|
88
|
-
summary: (p) => `
|
|
149
|
+
summary: (p) => `move ${p.source} → ${p.destination}`,
|
|
89
150
|
},
|
|
90
151
|
{
|
|
91
152
|
name: 'property_set',
|
|
92
153
|
description: 'Create or overwrite a device property.',
|
|
93
154
|
params: [
|
|
94
155
|
{ name: 'property', type: 'string', required: true, description: 'Property name.' },
|
|
95
|
-
{
|
|
156
|
+
{
|
|
157
|
+
name: 'value',
|
|
158
|
+
type: 'object',
|
|
159
|
+
required: true,
|
|
160
|
+
description: 'Any JSON-serialisable value.',
|
|
161
|
+
},
|
|
96
162
|
],
|
|
97
163
|
summary: (p) => `property_set ${p.property}`,
|
|
98
164
|
},
|
|
@@ -101,7 +167,11 @@ export const ACTIONS = [
|
|
|
101
167
|
description: 'Invoke a device resource. Inputs are forwarded as the resource payload.',
|
|
102
168
|
params: [
|
|
103
169
|
{ name: 'resource', type: 'string', required: true, description: 'Resource name.' },
|
|
104
|
-
{
|
|
170
|
+
{
|
|
171
|
+
name: 'inputs',
|
|
172
|
+
type: 'object',
|
|
173
|
+
description: 'Resource inputs (omit for read-only resources).',
|
|
174
|
+
},
|
|
105
175
|
],
|
|
106
176
|
summary: (p) => `resource ${p.resource}`,
|
|
107
177
|
},
|
|
@@ -118,18 +188,25 @@ export const ACTIONS = [
|
|
|
118
188
|
name: 'script_install',
|
|
119
189
|
description: 'Install or replace a custom script on the device. Triggers a scripts reload.',
|
|
120
190
|
params: [
|
|
121
|
-
{
|
|
191
|
+
{
|
|
192
|
+
name: 'name',
|
|
193
|
+
type: 'string',
|
|
194
|
+
required: true,
|
|
195
|
+
description: 'Script filename (e.g. "battery.sh").',
|
|
196
|
+
},
|
|
122
197
|
{ name: 'content', type: 'string', description: 'Inline script source.' },
|
|
123
|
-
{
|
|
198
|
+
{
|
|
199
|
+
name: 'content_file',
|
|
200
|
+
type: 'string',
|
|
201
|
+
description: 'Local file with the script source.',
|
|
202
|
+
},
|
|
124
203
|
],
|
|
125
204
|
summary: (p) => `script_install ${p.name}`,
|
|
126
205
|
},
|
|
127
206
|
{
|
|
128
207
|
name: 'script_delete',
|
|
129
208
|
description: 'Remove a custom script from the device.',
|
|
130
|
-
params: [
|
|
131
|
-
{ name: 'name', type: 'string', required: true, description: 'Script filename.' },
|
|
132
|
-
],
|
|
209
|
+
params: [{ name: 'name', type: 'string', required: true, description: 'Script filename.' }],
|
|
133
210
|
summary: (p) => `script_delete ${p.name}`,
|
|
134
211
|
},
|
|
135
212
|
];
|
|
@@ -164,7 +241,8 @@ export function playbookSchema() {
|
|
|
164
241
|
description:
|
|
165
242
|
'Extended form for a single `vars` entry. Use it when a variable needs a description, an explicit type for override validation, or must be marked non-overridable or required.',
|
|
166
243
|
fields: {
|
|
167
|
-
default:
|
|
244
|
+
default:
|
|
245
|
+
'Default value. Optional when `required: true`. Must match the declared `type`.',
|
|
168
246
|
description: 'Optional human-readable text shown in the dashboard form.',
|
|
169
247
|
type: 'Expected value type for overrides: "string", "number", "boolean", or "object".',
|
|
170
248
|
overridable:
|
|
@@ -183,17 +261,13 @@ export function playbookSchema() {
|
|
|
183
261
|
action: 'Action name — one of the actions below.',
|
|
184
262
|
pause_after: 'Optional seconds to wait after this step completes.',
|
|
185
263
|
when: 'Optional boolean expression. When present and falsy the step is skipped. See `when_expression` below for supported syntax.',
|
|
186
|
-
register:
|
|
264
|
+
register:
|
|
265
|
+
'Optional identifier. When set, the step outcome is exposed under that name to later `when` expressions and string interpolation as `{ ok, result | error, duration_ms }`.',
|
|
187
266
|
'...': 'Additional fields are the action parameters (see `actions` below).',
|
|
188
267
|
},
|
|
189
268
|
when_expression: {
|
|
190
|
-
syntax:
|
|
191
|
-
|
|
192
|
-
examples: [
|
|
193
|
-
'env == "prod"',
|
|
194
|
-
'check.ok and agent.version != "1.6.0"',
|
|
195
|
-
'not dry_run',
|
|
196
|
-
],
|
|
269
|
+
syntax: 'Minimal boolean mini-language: `==`, `!=`, `>`, `<`, `>=`, `<=`, `and`, `or`, `not`, parentheses; literals for strings (single or double quoted), numbers, `true`, `false`, `null`; dotted identifier paths resolve against the current scope.',
|
|
270
|
+
examples: ['env == "prod"', 'check.ok and agent.version != "1.6.0"', 'not dry_run'],
|
|
197
271
|
},
|
|
198
272
|
actions: ACTIONS.map((a) => ({
|
|
199
273
|
name: a.name,
|
|
@@ -115,7 +115,7 @@ export const LEVEL_SEVERITY = /** @type {Record<string, number>} */ ({
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Return all presets as a sorted list. Used by `thinr product logs presets`
|
|
118
|
-
* and by
|
|
118
|
+
* and by `thinr_resource_describe` type `product_log_source` (MCP).
|
|
119
119
|
*
|
|
120
120
|
* @returns {LogPreset[]}
|
|
121
121
|
*/
|
package/lib/product/logs.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import { inputError } from '
|
|
2
|
+
import { inputError } from '@thinger-io/cli-core/errors';
|
|
3
3
|
import {
|
|
4
4
|
getProductProperty,
|
|
5
5
|
setProductProperty,
|
|
6
6
|
deleteProductProperty,
|
|
7
|
-
} from '
|
|
7
|
+
} from '@thinger-io/cli-core/product';
|
|
8
8
|
import { LEVEL_SEVERITY, PRESET_NAMES, getPreset, listPresets } from './log-presets.js';
|
|
9
9
|
|
|
10
10
|
export { LEVEL_SEVERITY, getPreset, listPresets };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
import { getDevices, filterActiveDevices } from '
|
|
3
|
-
import { runPool } from '
|
|
2
|
+
import { getDevices, filterActiveDevices } from '@thinger-io/cli-core/devices';
|
|
3
|
+
import { runPool } from '@thinger-io/cli-core/concurrency';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Resolve the target devices of a product (optionally filtered by
|
|
7
|
-
* asset group and active connection),
|
|
8
|
-
* them with bounded concurrency.
|
|
7
|
+
* asset group and active connection), or take the `devices` given, then
|
|
8
|
+
* run `worker(device)` over them with bounded concurrency.
|
|
9
9
|
*
|
|
10
10
|
* The worker is responsible for its own error handling and always
|
|
11
11
|
* returns a fully-populated entry. Any escaping throw is caught and
|
|
@@ -26,7 +26,8 @@ import { runPool } from './concurrency.js';
|
|
|
26
26
|
*
|
|
27
27
|
* @template E
|
|
28
28
|
* @param {{
|
|
29
|
-
* product
|
|
29
|
+
* product?: string,
|
|
30
|
+
* devices?: any[],
|
|
30
31
|
* group?: string,
|
|
31
32
|
* includeOffline?: boolean,
|
|
32
33
|
* user?: string | null,
|
|
@@ -49,6 +50,7 @@ import { runPool } from './concurrency.js';
|
|
|
49
50
|
export async function runProductFanOut(opts) {
|
|
50
51
|
const {
|
|
51
52
|
product,
|
|
53
|
+
devices: given,
|
|
52
54
|
group,
|
|
53
55
|
includeOffline = false,
|
|
54
56
|
user,
|
|
@@ -63,10 +65,13 @@ export async function runProductFanOut(opts) {
|
|
|
63
65
|
onItemFinish,
|
|
64
66
|
} = opts;
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
let devices = given;
|
|
69
|
+
if (!devices) {
|
|
70
|
+
const filter = { product };
|
|
71
|
+
if (group) filter.asset_group = group;
|
|
72
|
+
devices = await getDevices(filter, user || undefined);
|
|
73
|
+
if (!includeOffline) devices = filterActiveDevices(devices);
|
|
74
|
+
}
|
|
70
75
|
|
|
71
76
|
onDevicesResolved?.(devices);
|
|
72
77
|
|