@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-tokens.js
CHANGED
|
@@ -115,13 +115,11 @@ async function toolTokenDelete(args) {
|
|
|
115
115
|
// ─── Device-level tokens ────────────────────────────────────────────
|
|
116
116
|
|
|
117
117
|
async function toolDeviceTokenList(args) {
|
|
118
|
-
if (!args.
|
|
119
|
-
const tokens = await listDeviceTokens(args.
|
|
118
|
+
if (!args.device_id) throw inputError('device is required');
|
|
119
|
+
const tokens = await listDeviceTokens(args.device_id, args.user);
|
|
120
120
|
if (tokens.length === 0) {
|
|
121
121
|
return {
|
|
122
|
-
content: [
|
|
123
|
-
{ type: 'text', text: `No device tokens on device "${args.device}".` },
|
|
124
|
-
],
|
|
122
|
+
content: [{ type: 'text', text: `No device tokens on device "${args.device_id}".` }],
|
|
125
123
|
isError: false,
|
|
126
124
|
};
|
|
127
125
|
}
|
|
@@ -129,7 +127,7 @@ async function toolDeviceTokenList(args) {
|
|
|
129
127
|
content: [
|
|
130
128
|
{
|
|
131
129
|
type: 'text',
|
|
132
|
-
text: `${tokens.length} device token(s) on "${args.
|
|
130
|
+
text: `${tokens.length} device token(s) on "${args.device_id}":\n${JSON.stringify(tokens, null, 2)}`,
|
|
133
131
|
},
|
|
134
132
|
],
|
|
135
133
|
isError: false,
|
|
@@ -137,11 +135,11 @@ async function toolDeviceTokenList(args) {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
async function toolDeviceTokenCreate(args) {
|
|
140
|
-
if (!args.
|
|
138
|
+
if (!args.device_id) throw inputError('device is required');
|
|
141
139
|
if (!args.token_name) throw inputError('token_name is required');
|
|
142
140
|
|
|
143
141
|
const doc = await createDeviceToken(
|
|
144
|
-
args.
|
|
142
|
+
args.device_id,
|
|
145
143
|
{
|
|
146
144
|
token_name: args.token_name,
|
|
147
145
|
token_resources: args.token_resources,
|
|
@@ -153,7 +151,7 @@ async function toolDeviceTokenCreate(args) {
|
|
|
153
151
|
content: [
|
|
154
152
|
{
|
|
155
153
|
type: 'text',
|
|
156
|
-
text: `Created device token "${args.token_name}" on "${args.
|
|
154
|
+
text: `Created device token "${args.token_name}" on "${args.device_id}".\n${JSON.stringify(doc, null, 2)}`,
|
|
157
155
|
},
|
|
158
156
|
],
|
|
159
157
|
isError: false,
|
|
@@ -161,16 +159,16 @@ async function toolDeviceTokenCreate(args) {
|
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
async function toolDeviceTokenDelete(args) {
|
|
164
|
-
if (!args.
|
|
162
|
+
if (!args.device_id) throw inputError('device is required');
|
|
165
163
|
if (!args.token_id) throw inputError('token_id is required');
|
|
166
|
-
const removed = await deleteDeviceToken(args.
|
|
164
|
+
const removed = await deleteDeviceToken(args.device_id, args.token_id, args.user);
|
|
167
165
|
return {
|
|
168
166
|
content: [
|
|
169
167
|
{
|
|
170
168
|
type: 'text',
|
|
171
169
|
text: removed
|
|
172
|
-
? `Deleted device token "${args.token_id}" from "${args.
|
|
173
|
-
: `Device token "${args.token_id}" was not configured on "${args.
|
|
170
|
+
? `Deleted device token "${args.token_id}" from "${args.device_id}".`
|
|
171
|
+
: `Device token "${args.token_id}" was not configured on "${args.device_id}".`,
|
|
174
172
|
},
|
|
175
173
|
],
|
|
176
174
|
isError: false,
|
|
@@ -318,10 +316,10 @@ ${EXPIRY_NOTE}`,
|
|
|
318
316
|
inputSchema: {
|
|
319
317
|
type: 'object',
|
|
320
318
|
properties: {
|
|
321
|
-
|
|
319
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
322
320
|
user: { type: 'string', description: 'API user.' },
|
|
323
321
|
},
|
|
324
|
-
required: ['
|
|
322
|
+
required: ['device_id'],
|
|
325
323
|
},
|
|
326
324
|
handler: toolDeviceTokenList,
|
|
327
325
|
},
|
|
@@ -336,7 +334,7 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
336
334
|
inputSchema: {
|
|
337
335
|
type: 'object',
|
|
338
336
|
properties: {
|
|
339
|
-
|
|
337
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
340
338
|
token_name: { type: 'string', description: 'Display name for the new token.' },
|
|
341
339
|
token_resources: {
|
|
342
340
|
type: 'array',
|
|
@@ -350,7 +348,7 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
350
348
|
},
|
|
351
349
|
user: { type: 'string', description: 'API user.' },
|
|
352
350
|
},
|
|
353
|
-
required: ['
|
|
351
|
+
required: ['device_id', 'token_name'],
|
|
354
352
|
},
|
|
355
353
|
handler: toolDeviceTokenCreate,
|
|
356
354
|
},
|
|
@@ -360,11 +358,11 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
360
358
|
inputSchema: {
|
|
361
359
|
type: 'object',
|
|
362
360
|
properties: {
|
|
363
|
-
|
|
361
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
364
362
|
token_id: { type: 'string', description: 'Token id to remove.' },
|
|
365
363
|
user: { type: 'string', description: 'API user.' },
|
|
366
364
|
},
|
|
367
|
-
required: ['
|
|
365
|
+
required: ['device_id', 'token_id'],
|
|
368
366
|
},
|
|
369
367
|
handler: toolDeviceTokenDelete,
|
|
370
368
|
},
|
package/lib/monitoring.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import api from './api.js';
|
|
2
2
|
import { apiError } from './errors.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
function resolveUser(user) {
|
|
6
|
-
return user || requireConfig().username;
|
|
7
|
-
}
|
|
3
|
+
import { v2, v3 } from './paths.js';
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* Query the monitoring bucket. When `device` is provided, scopes to
|
|
@@ -55,7 +51,6 @@ export async function getMonitoringData({
|
|
|
55
51
|
fields,
|
|
56
52
|
group_by,
|
|
57
53
|
} = {}) {
|
|
58
|
-
const apiUser = resolveUser(user);
|
|
59
54
|
const params = { items, sort };
|
|
60
55
|
if (minutes) {
|
|
61
56
|
params.min_ts = Date.now() - minutes * 60 * 1000;
|
|
@@ -80,13 +75,12 @@ export async function getMonitoringData({
|
|
|
80
75
|
.split(',')
|
|
81
76
|
.map((s) => s.trim())
|
|
82
77
|
.includes('device');
|
|
83
|
-
params.fields =
|
|
84
|
-
!device && !effectiveGroupBy && !hasDevice ? `device,${fields}` : fields;
|
|
78
|
+
params.fields = !device && !effectiveGroupBy && !hasDevice ? `device,${fields}` : fields;
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
const url = device
|
|
88
|
-
?
|
|
89
|
-
:
|
|
82
|
+
? `${v3(user)}/devices/${device}/buckets/monitoring/data`
|
|
83
|
+
: `${v2(user)}/buckets/monitoring/data`;
|
|
90
84
|
|
|
91
85
|
try {
|
|
92
86
|
const res = await api.get(url, { params });
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { resolveUser } from './config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Account-scoped base paths, one per API version.
|
|
6
|
+
*
|
|
7
|
+
* Every endpoint the CLI talks to hangs off `/v{N}/users/{account}`, and
|
|
8
|
+
* the account is the part that is easy to get wrong: leaving it out still
|
|
9
|
+
* produces a valid URL, one that works against your own account and fails
|
|
10
|
+
* only when acting on behalf of another — with a "not found" that points
|
|
11
|
+
* nowhere near the real problem. Building the prefix in one place keeps
|
|
12
|
+
* the account in the signature, where it is visible, and `tests/paths.test.js`
|
|
13
|
+
* fails the build if a module starts hand-rolling these again.
|
|
14
|
+
*
|
|
15
|
+
* Pass `user` to address another account; omit it for the active profile.
|
|
16
|
+
*/
|
|
17
|
+
export function v1(user) {
|
|
18
|
+
return `/v1/users/${resolveUser(user)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function v2(user) {
|
|
22
|
+
return `/v2/users/${resolveUser(user)}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function v3(user) {
|
|
26
|
+
return `/v3/users/${resolveUser(user)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `/v3/users/{account}/devices/{deviceId}` — the most common prefix. */
|
|
30
|
+
export function device(user, deviceId) {
|
|
31
|
+
return `${v3(user)}/devices/${deviceId}`;
|
|
32
|
+
}
|
package/lib/playbook/actions.js
CHANGED
|
@@ -126,7 +126,7 @@ export const HANDLERS = {
|
|
|
126
126
|
},
|
|
127
127
|
|
|
128
128
|
async property_set(ctx, params) {
|
|
129
|
-
await setDeviceProperty(ctx.deviceId, params.property, params.value);
|
|
129
|
+
await setDeviceProperty(ctx.deviceId, params.property, params.value, ctx.user);
|
|
130
130
|
return `set ${params.property}`;
|
|
131
131
|
},
|
|
132
132
|
|
|
@@ -275,7 +275,7 @@ export const CHECKERS = {
|
|
|
275
275
|
async property_set(ctx, params) {
|
|
276
276
|
let current;
|
|
277
277
|
try {
|
|
278
|
-
current = await getDeviceProperty(ctx.deviceId, params.property);
|
|
278
|
+
current = await getDeviceProperty(ctx.deviceId, params.property, ctx.user);
|
|
279
279
|
} catch (err) {
|
|
280
280
|
if (err?.code === 'not_found') {
|
|
281
281
|
return { status: 'changed', summary: `create property ${params.property}` };
|
package/lib/product.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import api from './api.js';
|
|
2
2
|
import { apiError, inputError } from './errors.js';
|
|
3
|
-
import { requireConfig } from './config.js';
|
|
4
3
|
import {
|
|
5
4
|
storageExists,
|
|
6
5
|
createStorage,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
deleteStorageFile,
|
|
13
12
|
} from './storage.js';
|
|
14
13
|
import { parsePlaybook } from './playbook/loader.js';
|
|
14
|
+
import { v1 } from './paths.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Helpers for ThinRemote products and the product-script workflow.
|
|
@@ -28,14 +28,6 @@ import { parsePlaybook } from './playbook/loader.js';
|
|
|
28
28
|
* sequence.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
function resolveUser(user) {
|
|
32
|
-
return user || requireConfig().username;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function v1(user) {
|
|
36
|
-
return `/v1/users/${resolveUser(user)}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
// ─── Low-level: products ─────────────────────────────────────────────
|
|
40
32
|
|
|
41
33
|
export async function getProducts(user) {
|
package/lib/profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import api from './api.js';
|
|
3
3
|
import { apiError, inputError } from './errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import { v1 } from './paths.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Helpers for product *profile* configuration.
|
|
@@ -68,14 +68,6 @@ import { requireConfig } from './config.js';
|
|
|
68
68
|
* device code change.
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
|
-
function resolveUser(user) {
|
|
72
|
-
return user || requireConfig().username;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function v1(user) {
|
|
76
|
-
return `/v1/users/${resolveUser(user)}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
71
|
// ─── Listener config validators ──────────────────────────────────────
|
|
80
72
|
//
|
|
81
73
|
// Each `data.source` shape (for buckets/properties) and each request
|
package/lib/property.js
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveUser } from './config.js';
|
|
2
|
+
import { device } from './paths.js';
|
|
2
3
|
import api from './api.js';
|
|
3
4
|
import { apiError } from './errors.js';
|
|
4
5
|
import { info, success } from './format.js';
|
|
5
6
|
|
|
6
|
-
export async function getDeviceProperties(deviceId) {
|
|
7
|
-
const config = requireConfig();
|
|
8
|
-
|
|
7
|
+
export async function getDeviceProperties(deviceId, user) {
|
|
9
8
|
try {
|
|
10
|
-
const response = await api.get(
|
|
11
|
-
`/v3/users/${config.username}/devices/${deviceId}/properties`,
|
|
12
|
-
);
|
|
9
|
+
const response = await api.get(`${device(user, deviceId)}/properties`);
|
|
13
10
|
return response.data;
|
|
14
11
|
} catch (error) {
|
|
15
|
-
throw apiError(error, {
|
|
12
|
+
throw apiError(error, {
|
|
13
|
+
notFound: `Device not found: ${deviceId} (account ${resolveUser(user)})`,
|
|
14
|
+
});
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
export async function getDeviceProperty(deviceId, propertyId) {
|
|
20
|
-
const config = requireConfig();
|
|
21
|
-
|
|
18
|
+
export async function getDeviceProperty(deviceId, propertyId, user) {
|
|
22
19
|
try {
|
|
23
|
-
const response = await api.get(
|
|
24
|
-
`/v3/users/${config.username}/devices/${deviceId}/properties/${propertyId}`,
|
|
25
|
-
);
|
|
20
|
+
const response = await api.get(`${device(user, deviceId)}/properties/${propertyId}`);
|
|
26
21
|
return response.data.value;
|
|
27
22
|
} catch (error) {
|
|
28
23
|
throw apiError(error, {
|
|
@@ -33,17 +28,16 @@ export async function getDeviceProperty(deviceId, propertyId) {
|
|
|
33
28
|
|
|
34
29
|
// PUT so the operation is idempotent — missing properties are created,
|
|
35
30
|
// existing ones are overwritten wholesale.
|
|
36
|
-
export async function setDeviceProperty(deviceId, propertyId, value) {
|
|
37
|
-
const config = requireConfig();
|
|
38
|
-
|
|
31
|
+
export async function setDeviceProperty(deviceId, propertyId, value, user) {
|
|
39
32
|
try {
|
|
40
|
-
const response = await api.put(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
33
|
+
const response = await api.put(`${device(user, deviceId)}/properties/${propertyId}`, {
|
|
34
|
+
value,
|
|
35
|
+
});
|
|
44
36
|
return response.data;
|
|
45
37
|
} catch (error) {
|
|
46
|
-
throw apiError(error, {
|
|
38
|
+
throw apiError(error, {
|
|
39
|
+
notFound: `Device not found: ${deviceId} (account ${resolveUser(user)})`,
|
|
40
|
+
});
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
43
|
|
|
@@ -59,11 +53,7 @@ export function formatDeviceProperties(deviceId, properties) {
|
|
|
59
53
|
|
|
60
54
|
export function formatDeviceProperty(deviceId, propertyId, propertyValue) {
|
|
61
55
|
let formattedValue =
|
|
62
|
-
'\nValue of property ' +
|
|
63
|
-
info(propertyId) +
|
|
64
|
-
' for device ' +
|
|
65
|
-
info(deviceId) +
|
|
66
|
-
':\n';
|
|
56
|
+
'\nValue of property ' + info(propertyId) + ' for device ' + info(deviceId) + ':\n';
|
|
67
57
|
const flattenedValue = flatten(propertyValue);
|
|
68
58
|
for (const key in flattenedValue) {
|
|
69
59
|
formattedValue += `${key}: ${success(flattenedValue[key])}\n`;
|
package/lib/resource.js
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveUser } from './config.js';
|
|
2
|
+
import { device } from './paths.js';
|
|
2
3
|
import api from './api.js';
|
|
3
4
|
import { apiError } from './errors.js';
|
|
4
5
|
import { info, warning, hint } from './format.js';
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
* Account the request is addressed to: `options.user` for admin access to
|
|
8
|
-
* another account's devices, the active profile's own user otherwise.
|
|
9
|
-
* Matches `createDeviceAPI`, so both halves of a device interaction (files
|
|
10
|
-
* and resources) resolve the same way.
|
|
11
|
-
*/
|
|
12
|
-
function apiUser(options = {}) {
|
|
13
|
-
return options.user || requireConfig().username;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
7
|
// Drops internal resources whose keys start with `$`; those belong to
|
|
17
8
|
// the agent's own framework (cmd, fs, scripts…) and shouldn't surface
|
|
18
9
|
// through the user-facing resource list.
|
|
19
10
|
export async function getDeviceResources(deviceId, options = {}) {
|
|
20
|
-
const user =
|
|
11
|
+
const user = resolveUser(options.user);
|
|
21
12
|
|
|
22
13
|
try {
|
|
23
14
|
// `/resources/api` merges firmware resources with API resources
|
|
24
15
|
// inherited from the product profile, which `/resources` alone omits.
|
|
25
|
-
const response = await api.get(
|
|
16
|
+
const response = await api.get(`${device(user, deviceId)}/resources/api`);
|
|
26
17
|
return Object.fromEntries(
|
|
27
18
|
Object.entries(response.data).filter(([key]) => !key.startsWith('$')),
|
|
28
19
|
);
|
|
@@ -39,11 +30,9 @@ export async function getDeviceResources(deviceId, options = {}) {
|
|
|
39
30
|
* sample, not to trigger the input side.
|
|
40
31
|
*/
|
|
41
32
|
export async function readDeviceResource(deviceId, resourceId, options = {}) {
|
|
42
|
-
const user =
|
|
33
|
+
const user = resolveUser(options.user);
|
|
43
34
|
try {
|
|
44
|
-
const response = await api.get(
|
|
45
|
-
`/v3/users/${user}/devices/${deviceId}/resources/${resourceId}`,
|
|
46
|
-
);
|
|
35
|
+
const response = await api.get(`${device(user, deviceId)}/resources/${resourceId}`);
|
|
47
36
|
return response.data;
|
|
48
37
|
} catch (error) {
|
|
49
38
|
throw apiError(error, {
|
|
@@ -62,11 +51,9 @@ export async function readDeviceResource(deviceId, resourceId, options = {}) {
|
|
|
62
51
|
* exposes /api — callers should tolerate 404/501.
|
|
63
52
|
*/
|
|
64
53
|
export async function getDeviceResourceApi(deviceId, resourceId, options = {}) {
|
|
65
|
-
const user =
|
|
54
|
+
const user = resolveUser(options.user);
|
|
66
55
|
try {
|
|
67
|
-
const response = await api.get(
|
|
68
|
-
`/v3/users/${user}/devices/${deviceId}/resources/${resourceId}/api`,
|
|
69
|
-
);
|
|
56
|
+
const response = await api.get(`${device(user, deviceId)}/resources/${resourceId}/api`);
|
|
70
57
|
return response.data;
|
|
71
58
|
} catch (error) {
|
|
72
59
|
throw apiError(error, {
|
|
@@ -76,11 +63,11 @@ export async function getDeviceResourceApi(deviceId, resourceId, options = {}) {
|
|
|
76
63
|
}
|
|
77
64
|
|
|
78
65
|
export async function executeDeviceResource(deviceId, resourceId, inputs, options = {}) {
|
|
79
|
-
const user =
|
|
66
|
+
const user = resolveUser(options.user);
|
|
80
67
|
|
|
81
68
|
try {
|
|
82
69
|
const response = await api.post(
|
|
83
|
-
|
|
70
|
+
`${device(user, deviceId)}/resources/${resourceId}`,
|
|
84
71
|
inputs,
|
|
85
72
|
);
|
|
86
73
|
return response.data;
|
package/lib/status.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveUser } from './config.js';
|
|
2
|
+
import { v1 } from './paths.js';
|
|
2
3
|
import api from './api.js';
|
|
3
4
|
import { apiError } from './errors.js';
|
|
4
|
-
import {
|
|
5
|
-
formatUptime,
|
|
6
|
-
colorPct,
|
|
7
|
-
formatBytes,
|
|
8
|
-
label,
|
|
9
|
-
muted,
|
|
10
|
-
success,
|
|
11
|
-
error,
|
|
12
|
-
} from './format.js';
|
|
5
|
+
import { formatUptime, colorPct, formatBytes, label, muted, success, error } from './format.js';
|
|
13
6
|
|
|
14
7
|
/**
|
|
15
8
|
* Get device status
|
|
16
9
|
* @param {string} deviceId - Device ID to check
|
|
10
|
+
* @param {string} [user] - Account that owns the device (defaults to the active profile)
|
|
17
11
|
* @returns {Promise<Object>} Status information
|
|
18
12
|
*/
|
|
19
|
-
export async function getDeviceStatus(deviceId) {
|
|
20
|
-
const config = requireConfig();
|
|
21
|
-
|
|
13
|
+
export async function getDeviceStatus(deviceId, user) {
|
|
22
14
|
try {
|
|
23
|
-
const response = await api.get(
|
|
15
|
+
const response = await api.get(`${v1(user)}/devices/${deviceId}/stats`);
|
|
24
16
|
return response.data;
|
|
25
17
|
} catch (error) {
|
|
26
|
-
throw apiError(error, {
|
|
18
|
+
throw apiError(error, {
|
|
19
|
+
notFound: `Device not found: ${deviceId} (account ${resolveUser(user)})`,
|
|
20
|
+
});
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
|
|
@@ -80,17 +74,13 @@ export function formatDeviceStatus(deviceId, status, monitoring = null) {
|
|
|
80
74
|
const root = monitoring.disk.root;
|
|
81
75
|
const detail =
|
|
82
76
|
root.available != null && root.total != null
|
|
83
|
-
? muted(
|
|
84
|
-
` (${formatBytes(root.available)} free of ${formatBytes(root.total)})`,
|
|
85
|
-
)
|
|
77
|
+
? muted(` (${formatBytes(root.available)} free of ${formatBytes(root.total)})`)
|
|
86
78
|
: '';
|
|
87
79
|
lines.push(` Disk: ${colorPct(root.usage)}${detail}`);
|
|
88
80
|
}
|
|
89
81
|
if (monitoring.load) {
|
|
90
82
|
const l = monitoring.load;
|
|
91
|
-
const parts = [l['1m'], l['5m'], l['15m']].map((v) =>
|
|
92
|
-
v != null ? v.toFixed(2) : '—',
|
|
93
|
-
);
|
|
83
|
+
const parts = [l['1m'], l['5m'], l['15m']].map((v) => (v != null ? v.toFixed(2) : '—'));
|
|
94
84
|
lines.push(` Load: ${parts.join(' · ')}`);
|
|
95
85
|
}
|
|
96
86
|
if (monitoring.uptime != null) {
|
package/lib/storage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import api from './api.js';
|
|
2
2
|
import { apiError } from './errors.js';
|
|
3
|
-
import {
|
|
3
|
+
import { v1, v2 } from './paths.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Helpers for ThinRemote file storages — independent buckets that the
|
|
@@ -13,17 +13,6 @@ import { requireConfig } from './config.js';
|
|
|
13
13
|
* so orchestration code can stay branch-light.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
function resolveUser(user) {
|
|
17
|
-
return user || requireConfig().username;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function v1(user) {
|
|
21
|
-
return `/v1/users/${resolveUser(user)}`;
|
|
22
|
-
}
|
|
23
|
-
function v2(user) {
|
|
24
|
-
return `/v2/users/${resolveUser(user)}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
16
|
export async function getStorage(storageId, user) {
|
|
28
17
|
try {
|
|
29
18
|
const res = await api.get(`${v1(user)}/storages/${storageId}`);
|
package/lib/tokens.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import api from './api.js';
|
|
3
3
|
import { apiError, inputError } from './errors.js';
|
|
4
|
-
import {
|
|
4
|
+
import { v1 } from './paths.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Helpers for ThinRemote access tokens — both user-level (broad scope,
|
|
@@ -60,14 +60,6 @@ import { requireConfig } from './config.js';
|
|
|
60
60
|
* it.
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
|
-
function resolveUser(user) {
|
|
64
|
-
return user || requireConfig().username;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function v1(user) {
|
|
68
|
-
return `/v1/users/${resolveUser(user)}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
63
|
// ─── Duration helpers ───────────────────────────────────────────────
|
|
72
64
|
|
|
73
65
|
const DURATION_RE = /^(\d+)([smhdwy])$/;
|