@intflow/sentinelctl 0.3.0 → 0.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intflow/sentinelctl",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Sentinel Fleet Console CLI: search device logs, inspect status and metrics, edit metadata and manage roles",
5
5
  "type": "module",
6
6
  "bin": {
package/src/client.mjs CHANGED
@@ -11,7 +11,7 @@ export class ApiError extends Error {
11
11
 
12
12
  const userAgent = 'sentinelctl';
13
13
 
14
- export function createClient({ server, token = null, fetch: fetchImplementation = globalThis.fetch, timeoutMs = 60_000, maxRetries = 2, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) }) {
14
+ export function createClient({ server, token = null, fetch: fetchImplementation = globalThis.fetch, timeoutMs = 60_000, maxRetries = 5, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) }) {
15
15
  async function request(method, path, { query, body, auth = true, allowStatuses = [] } = {}) {
16
16
  const url = new URL(path, server);
17
17
  for (const [key, value] of Object.entries(query ?? {})) {
@@ -45,7 +45,7 @@ export function createClient({ server, token = null, fetch: fetchImplementation
45
45
  }
46
46
  const retryAfterSeconds = Number(response.headers.get('retry-after')) || null;
47
47
  if (response.status === 429 && method === 'GET' && attempt < maxRetries) {
48
- await sleep(Math.min(retryAfterSeconds ?? 1, 30) * 1_000);
48
+ await sleep(Math.min(Math.max(retryAfterSeconds ?? 1, 2 ** attempt), 15) * 1_000);
49
49
  continue;
50
50
  }
51
51
  if (response.ok || allowStatuses.includes(response.status)) return { status: response.status, body: payload };
package/src/commands.mjs CHANGED
@@ -237,11 +237,12 @@ async function devicesShow(context, { options, positionals }) {
237
237
  const agentId = requireAgentId(positionals);
238
238
  const body = await context.client.get(`/api/devices/${encodeURIComponent(agentId)}`, { range: options.range, refresh: options.refresh ? 1 : undefined });
239
239
  const device = body.device;
240
+ const current = body.current ?? {};
240
241
  emit(context, body, () => renderKeyValues([
241
242
  ['Agent ID', device.agentId], ['Name', device.displayName], ['Host', device.host],
242
243
  ['Status', device.observedStatus ?? device.status], ['Last seen', ageText(device.ageSeconds)],
243
- ['CPU', percentText(device.cpu)], ['Memory', percentText(device.memory)], ['Disk', percentText(device.disk)],
244
- ['Load', device.load], ['Temperature', device.temperature], ['Role', device.metadata?.role ?? device.role],
244
+ ['CPU', percentText(current.cpu ?? device.cpu)], ['Memory', percentText(current.memory ?? device.memory)], ['Disk', percentText(current.disk ?? device.disk)],
245
+ ['Load', current.load ?? device.load], ['Temperature', current.temperature ?? device.temperature], ['Role', device.metadata?.role ?? device.role],
245
246
  ['Maintainer', device.metadata?.maintainer], ['Lifecycle', device.metadata?.lifecycle], ['Notes', device.metadata?.description],
246
247
  ['Data', body.stale ? `stale (${body.staleReason ?? ''})` : 'fresh'],
247
248
  ]));
@@ -273,12 +274,13 @@ async function devicesOverview(context, { options, positionals }) {
273
274
  normalRatio: Number.isFinite(history.normalRatio) ? Math.round(history.normalRatio * 1000) / 10 : null,
274
275
  }];
275
276
  }));
276
- const describe = (item) => `${item.state ?? '-'} · ${item.outages ?? '-'} outages, ${item.missingMinutes ?? '-'} min missing (longest ${item.longestOutageMinutes ?? '-'} min), normal ${item.normalRatio ?? '-'}%`;
277
+ const describe = (item) => `${item.state ?? '-'} · ${item.outages ?? '-'} ${item.outages === 1 ? 'outage' : 'outages'}, ${item.missingMinutes ?? '-'} min missing (longest ${item.longestOutageMinutes ?? '-'} min), normal ${item.normalRatio ?? '-'}%`;
277
278
  emit(context, { summary, ...body }, () => renderKeyValues([
278
279
  ['Agent ID', body.agentId], ['Name', body.device?.displayName], ['Window', options.range],
279
280
  ['Metrics', describe(summary.metrics)], ['Logs', describe(summary.logs)],
280
281
  ['Attention', (body.attentionReasons ?? []).join(', ') || 'none'],
281
- ['Related', (body.related ?? []).map((item) => item.agentId ?? item).join(', ')],
282
+ ['Hypervisor', body.related?.parent ? `${body.related.parent.displayName} (${body.related.parent.agentId})` : body.related?.expectedParent ?? null],
283
+ ['VMs', (body.related?.children ?? []).map((child) => child.displayName ?? child.agentId).join(', ') || null],
282
284
  ]));
283
285
  }
284
286
 
@@ -674,7 +676,7 @@ const commands = [
674
676
  args: [{ name: 'agent-id' }],
675
677
  options: { range: { alias: 'r', type: 'string', default: '24h', placeholder: 'window', description: 'History window, e.g. 24h or 7d.' } },
676
678
  examples: ['sentinelctl devices overview <agent-id> -r 7d -o json'],
677
- output: '{summary{metrics, logs}: {state, outages, delays, missingMinutes, longestOutageMinutes, normalRatio}, agentId, device, products, attentionReasons[], history, related[]}.',
679
+ output: '{summary{metrics, logs}: {state, outages, delays, missingMinutes, longestOutageMinutes, normalRatio}, agentId, device, products, attentionReasons[], history, related{parent, children[], expectedParent}}.',
678
680
  },
679
681
  {
680
682
  group: 'metadata', name: 'get', run: metadataGet, role: 'viewer', mutates: false,