@pushary/agent-hooks 0.62.6 → 0.63.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 +38 -0
- package/dist/bin/pushary-clean.js +2 -2
- package/dist/bin/pushary-connect.js +2 -2
- package/dist/bin/pushary-daemon.js +19 -2
- package/dist/bin/pushary-doctor.js +2 -2
- package/dist/bin/pushary-hook.js +2 -2
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +2 -2
- package/dist/bin/pushary-mode.js +2 -2
- package/dist/bin/pushary-setup.js +1 -1
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +43 -6
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/bin/pushary-wait.js +2 -2
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-KVUBLB6K.js → chunk-PPTC2QDB.js} +4 -1
- package/dist/chunk-R22IAMSS.js +86 -0
- package/dist/{chunk-674M3JQF.js → chunk-VYD27DKZ.js} +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.63.1
|
|
4
|
+
|
|
5
|
+
### `pushary status --bogus` printed a stack trace
|
|
6
|
+
|
|
7
|
+
An unknown flag on `status` threw where nothing was catching it, so Node printed
|
|
8
|
+
a raw stack trace and exited `1` instead of the clean usage error and exit `2`
|
|
9
|
+
every other command gives. Other commands were unaffected.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## 0.63.0
|
|
13
|
+
|
|
14
|
+
### Machines
|
|
15
|
+
|
|
16
|
+
`pushary daemon` now reports itself to your workspace once a minute, and
|
|
17
|
+
`pushary status --devices` lists what is there:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Machines
|
|
21
|
+
● macbook-pro 0.63.0 just now
|
|
22
|
+
○ build-server 0.62.6 4h ago
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`online` is worked out from the last heartbeat rather than stored, so a laptop
|
|
26
|
+
that closes mid-session reads offline a few minutes later without anything
|
|
27
|
+
having to notice it left.
|
|
28
|
+
|
|
29
|
+
This is what lets a phone start a session on a machine you name, instead of on
|
|
30
|
+
whichever one happens to answer first.
|
|
31
|
+
|
|
32
|
+
Presence is best effort throughout. A heartbeat that fails never stops the daemon
|
|
33
|
+
draining its queue, and `status` still reports your key and channels if the
|
|
34
|
+
machine list cannot be read. Against a server that predates the endpoint,
|
|
35
|
+
`--devices` says so rather than reporting an empty list.
|
|
36
|
+
|
|
37
|
+
**Needs a database migration** (`0066`). Until it is applied, `--devices` reports
|
|
38
|
+
that it could not read the list, and nothing else is affected.
|
|
39
|
+
|
|
40
|
+
|
|
3
41
|
## 0.62.6
|
|
4
42
|
|
|
5
43
|
### `clean` rewrote your project's Cursor config and left your own alone
|
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
} from "../chunk-MUEW424A.js";
|
|
35
35
|
import {
|
|
36
36
|
exitOnHelpFlag
|
|
37
|
-
} from "../chunk-
|
|
38
|
-
import "../chunk-
|
|
37
|
+
} from "../chunk-VYD27DKZ.js";
|
|
38
|
+
import "../chunk-PPTC2QDB.js";
|
|
39
39
|
import "../chunk-DQAN3JQP.js";
|
|
40
40
|
import "../chunk-KZERVKTD.js";
|
|
41
41
|
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
import "../chunk-BC3VCZ3E.js";
|
|
25
25
|
import {
|
|
26
26
|
exitOnHelpFlag
|
|
27
|
-
} from "../chunk-
|
|
28
|
-
import "../chunk-
|
|
27
|
+
} from "../chunk-VYD27DKZ.js";
|
|
28
|
+
import "../chunk-PPTC2QDB.js";
|
|
29
29
|
import "../chunk-BSZYIAZL.js";
|
|
30
30
|
import "../chunk-SAF6HGAA.js";
|
|
31
31
|
import {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HEARTBEAT_INTERVAL_MS,
|
|
4
|
+
sendHeartbeat
|
|
5
|
+
} from "../chunk-R22IAMSS.js";
|
|
2
6
|
import {
|
|
3
7
|
spawnClaude
|
|
4
8
|
} from "../chunk-XHKBHWLX.js";
|
|
@@ -7,8 +11,8 @@ import {
|
|
|
7
11
|
} from "../chunk-MUEW424A.js";
|
|
8
12
|
import {
|
|
9
13
|
exitOnHelpFlag
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-VYD27DKZ.js";
|
|
15
|
+
import "../chunk-PPTC2QDB.js";
|
|
12
16
|
import {
|
|
13
17
|
getMachineId
|
|
14
18
|
} from "../chunk-RN3NOEJF.js";
|
|
@@ -87,6 +91,7 @@ var runSpawnDaemon = async () => {
|
|
|
87
91
|
let fatalCode = null;
|
|
88
92
|
let finish = () => {
|
|
89
93
|
};
|
|
94
|
+
let heartbeat;
|
|
90
95
|
const launch = (req) => {
|
|
91
96
|
spawnTimestamps.push(Date.now());
|
|
92
97
|
const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
|
|
@@ -166,8 +171,20 @@ var runSpawnDaemon = async () => {
|
|
|
166
171
|
if (stopped) return;
|
|
167
172
|
stopped = true;
|
|
168
173
|
if (timer) clearTimeout(timer);
|
|
174
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
169
175
|
inflight?.abort();
|
|
170
176
|
};
|
|
177
|
+
const beat = async () => {
|
|
178
|
+
if (stopped) return;
|
|
179
|
+
const result = await sendHeartbeat(apiKey, { repoKey: basename(process.cwd()) });
|
|
180
|
+
if (result.kind === "unauthorized" && !stopped) {
|
|
181
|
+
fatalCode = EXIT.UNAUTHENTICATED;
|
|
182
|
+
stderr("[pushary] daemon stopping: this machine's key was rejected. Run `pushary login` to sign in again.\n");
|
|
183
|
+
finish();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
void beat();
|
|
187
|
+
heartbeat = setInterval(() => void beat(), HEARTBEAT_INTERVAL_MS);
|
|
171
188
|
stderr(
|
|
172
189
|
`[pushary] daemon online \u2014 this machine (${machineId}) can now be sent a new session from your phone. Ctrl-C to stop.
|
|
173
190
|
`
|
|
@@ -57,10 +57,10 @@ import {
|
|
|
57
57
|
} from "../chunk-MUEW424A.js";
|
|
58
58
|
import {
|
|
59
59
|
exitOnHelpFlag
|
|
60
|
-
} from "../chunk-
|
|
60
|
+
} from "../chunk-VYD27DKZ.js";
|
|
61
61
|
import {
|
|
62
62
|
getPackageVersion
|
|
63
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-PPTC2QDB.js";
|
|
64
64
|
import {
|
|
65
65
|
readLedgerSummary
|
|
66
66
|
} from "../chunk-WLGEY4UX.js";
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
import "../chunk-CAJZAFVS.js";
|
|
6
6
|
import {
|
|
7
7
|
exitOnHelpFlag
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-VYD27DKZ.js";
|
|
9
|
+
import "../chunk-PPTC2QDB.js";
|
|
10
10
|
import "../chunk-YHG74UFF.js";
|
|
11
11
|
import "../chunk-5ZDYDU2Q.js";
|
|
12
12
|
import "../chunk-WLGEY4UX.js";
|
|
@@ -21,10 +21,10 @@ import {
|
|
|
21
21
|
import "../chunk-BC3VCZ3E.js";
|
|
22
22
|
import {
|
|
23
23
|
exitOnHelpFlag
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-VYD27DKZ.js";
|
|
25
25
|
import {
|
|
26
26
|
getPackageVersion
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-PPTC2QDB.js";
|
|
28
28
|
import "../chunk-SAF6HGAA.js";
|
|
29
29
|
import {
|
|
30
30
|
UsageError
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
import "../chunk-BC3VCZ3E.js";
|
|
20
20
|
import {
|
|
21
21
|
exitOnHelpFlag
|
|
22
|
-
} from "../chunk-
|
|
23
|
-
import "../chunk-
|
|
22
|
+
} from "../chunk-VYD27DKZ.js";
|
|
23
|
+
import "../chunk-PPTC2QDB.js";
|
|
24
24
|
import {
|
|
25
25
|
UsageError
|
|
26
26
|
} from "../chunk-7QLSKOSU.js";
|
package/dist/bin/pushary-mode.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from "../chunk-GMXKITVA.js";
|
|
12
12
|
import {
|
|
13
13
|
exitOnHelpFlag
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-VYD27DKZ.js";
|
|
15
|
+
import "../chunk-PPTC2QDB.js";
|
|
16
16
|
import "../chunk-SAF6HGAA.js";
|
|
17
17
|
import {
|
|
18
18
|
UsageError
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
describeLastSeen,
|
|
4
|
+
listMachines
|
|
5
|
+
} from "../chunk-R22IAMSS.js";
|
|
2
6
|
import {
|
|
3
7
|
describeReach,
|
|
4
8
|
fetchChannels,
|
|
@@ -22,14 +26,15 @@ import {
|
|
|
22
26
|
import "../chunk-BC3VCZ3E.js";
|
|
23
27
|
import {
|
|
24
28
|
exitOnHelpFlag
|
|
25
|
-
} from "../chunk-
|
|
26
|
-
import "../chunk-
|
|
29
|
+
} from "../chunk-VYD27DKZ.js";
|
|
30
|
+
import "../chunk-PPTC2QDB.js";
|
|
27
31
|
import "../chunk-BSZYIAZL.js";
|
|
28
32
|
import "../chunk-SAF6HGAA.js";
|
|
29
33
|
import {
|
|
30
34
|
UsageError
|
|
31
35
|
} from "../chunk-7QLSKOSU.js";
|
|
32
36
|
import "../chunk-DQAN3JQP.js";
|
|
37
|
+
import "../chunk-RN3NOEJF.js";
|
|
33
38
|
import "../chunk-2UMNXADU.js";
|
|
34
39
|
import {
|
|
35
40
|
EXIT
|
|
@@ -57,10 +62,10 @@ var reachLine = (channels) => {
|
|
|
57
62
|
var siteLine = (channels) => {
|
|
58
63
|
const site = channels.site;
|
|
59
64
|
if (site === null || site.otherMembers === 0) return null;
|
|
60
|
-
const
|
|
65
|
+
const devices2 = site.appDevices + site.webActive;
|
|
61
66
|
return {
|
|
62
67
|
label: "Team",
|
|
63
|
-
value: `${
|
|
68
|
+
value: `${devices2} device${devices2 === 1 ? "" : "s"} across ${site.otherMembers + 1} members`,
|
|
64
69
|
ok: null
|
|
65
70
|
};
|
|
66
71
|
};
|
|
@@ -178,8 +183,15 @@ var statusJson = (facts, report) => ({
|
|
|
178
183
|
exitOnHelpFlag("status");
|
|
179
184
|
var { bold, dim, green, red, yellow } = createIo();
|
|
180
185
|
var args = process.argv.slice(2);
|
|
181
|
-
|
|
186
|
+
try {
|
|
187
|
+
rejectUnknownFlags(args.filter((a) => a !== "status"), ["--json", "--devices"]);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
process.stderr.write(` ! ${err instanceof Error ? err.message : String(err)}
|
|
190
|
+
`);
|
|
191
|
+
process.exit(EXIT.USAGE);
|
|
192
|
+
}
|
|
182
193
|
var json = args.includes("--json");
|
|
194
|
+
var devices = args.includes("--devices");
|
|
183
195
|
var main = async () => {
|
|
184
196
|
const source = readKeySource();
|
|
185
197
|
let keyCheck = null;
|
|
@@ -188,10 +200,16 @@ var main = async () => {
|
|
|
188
200
|
keyCheck = await checkApiKey(source.key);
|
|
189
201
|
if (keyCheck.kind === "ok") channels = await fetchChannels(source.key);
|
|
190
202
|
}
|
|
203
|
+
const machines = devices && keyCheck?.kind === "ok" ? await listMachines(source.key) : null;
|
|
191
204
|
const facts = { source, keyCheck, channels };
|
|
192
205
|
const report = buildStatusReport(facts);
|
|
193
206
|
if (json) {
|
|
194
|
-
|
|
207
|
+
const payload = statusJson(facts, report);
|
|
208
|
+
if (machines) {
|
|
209
|
+
payload.machines = machines.kind === "ok" ? machines.machines : [];
|
|
210
|
+
if (machines.kind !== "ok") payload.machinesUnavailable = machines.kind;
|
|
211
|
+
}
|
|
212
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
195
213
|
`);
|
|
196
214
|
process.exit(report.exitCode);
|
|
197
215
|
}
|
|
@@ -203,6 +221,25 @@ var main = async () => {
|
|
|
203
221
|
const icon = line.ok === true ? green("\u2713") : line.ok === false ? red("\u2717") : dim("\xB7");
|
|
204
222
|
console.log(` ${icon} ${line.label.padEnd(width)}${line.value}`);
|
|
205
223
|
}
|
|
224
|
+
if (machines) {
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(` ${bold("Machines")}`);
|
|
227
|
+
if (machines.kind === "unsupported") {
|
|
228
|
+
console.log(` ${dim("This server does not report machines yet.")}`);
|
|
229
|
+
} else if (machines.kind === "failed") {
|
|
230
|
+
console.log(` ${dim(`Could not read the machine list (${machines.detail}).`)}`);
|
|
231
|
+
} else if (machines.machines.length === 0) {
|
|
232
|
+
console.log(` ${dim("None registered. Run")} pushary daemon ${dim("to keep a machine reachable.")}`);
|
|
233
|
+
} else {
|
|
234
|
+
const hostWidth = Math.max(...machines.machines.map((m) => m.hostname.length)) + 2;
|
|
235
|
+
for (const machine of machines.machines) {
|
|
236
|
+
const mark = machine.online ? green("\u25CF") : dim("\u25CB");
|
|
237
|
+
console.log(
|
|
238
|
+
` ${mark} ${machine.hostname.padEnd(hostWidth)}${dim(`${machine.cliVersion} ${describeLastSeen(machine.lastSeenAt)}`)}`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
206
243
|
console.log();
|
|
207
244
|
console.log(` ${report.exitCode === 0 ? green(bold(report.headline)) : yellow(bold(report.headline))}`);
|
|
208
245
|
for (const next of report.next) console.log(` ${dim(next)}`);
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
} from "../chunk-GMXKITVA.js";
|
|
8
8
|
import {
|
|
9
9
|
exitOnHelpFlag
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-VYD27DKZ.js";
|
|
11
|
+
import "../chunk-PPTC2QDB.js";
|
|
12
12
|
import {
|
|
13
13
|
UsageError
|
|
14
14
|
} from "../chunk-7QLSKOSU.js";
|
|
@@ -34,10 +34,10 @@ import {
|
|
|
34
34
|
} from "../chunk-MUEW424A.js";
|
|
35
35
|
import {
|
|
36
36
|
exitOnHelpFlag
|
|
37
|
-
} from "../chunk-
|
|
37
|
+
} from "../chunk-VYD27DKZ.js";
|
|
38
38
|
import {
|
|
39
39
|
isNewerVersion
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-PPTC2QDB.js";
|
|
41
41
|
import "../chunk-DQAN3JQP.js";
|
|
42
42
|
import {
|
|
43
43
|
getApiKey
|
package/dist/bin/pushary-wait.js
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from "../chunk-GMXKITVA.js";
|
|
15
15
|
import {
|
|
16
16
|
exitOnHelpFlag
|
|
17
|
-
} from "../chunk-
|
|
18
|
-
import "../chunk-
|
|
17
|
+
} from "../chunk-VYD27DKZ.js";
|
|
18
|
+
import "../chunk-PPTC2QDB.js";
|
|
19
19
|
import "../chunk-SAF6HGAA.js";
|
|
20
20
|
import {
|
|
21
21
|
UsageError
|
package/dist/bin/pushary.js
CHANGED
|
@@ -80,7 +80,10 @@ var COMMAND_OPTIONS = {
|
|
|
80
80
|
["status", "Show the whole ladder"],
|
|
81
81
|
["clear", "Reset the default window"]
|
|
82
82
|
],
|
|
83
|
-
status: [
|
|
83
|
+
status: [
|
|
84
|
+
["--json", "Emit the whole report as one object instead of the screen"],
|
|
85
|
+
["--devices", "Also list the machines registered to this site"]
|
|
86
|
+
],
|
|
84
87
|
logout: [
|
|
85
88
|
["--revoke", "Also kill the key server-side, so it stops working everywhere"],
|
|
86
89
|
["--json", "Emit the result as one object instead of the screen"]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPackageVersion
|
|
3
|
+
} from "./chunk-PPTC2QDB.js";
|
|
4
|
+
import {
|
|
5
|
+
getMachineId
|
|
6
|
+
} from "./chunk-RN3NOEJF.js";
|
|
7
|
+
import {
|
|
8
|
+
getBaseUrl
|
|
9
|
+
} from "./chunk-2UMNXADU.js";
|
|
10
|
+
|
|
11
|
+
// src/machines.ts
|
|
12
|
+
import { hostname } from "os";
|
|
13
|
+
var MACHINES_PATH = "/api/v1/server/machines";
|
|
14
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
15
|
+
var sendHeartbeat = async (apiKey, input = {}, deps = {}) => {
|
|
16
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
17
|
+
const baseUrl = deps.baseUrl ?? getBaseUrl();
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetchImpl(`${baseUrl}${MACHINES_PATH}`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
machineId: getMachineId(),
|
|
24
|
+
hostname: hostname(),
|
|
25
|
+
cliVersion: getPackageVersion(),
|
|
26
|
+
repoKey: input.repoKey ?? null,
|
|
27
|
+
capabilities: input.capabilities ?? []
|
|
28
|
+
}),
|
|
29
|
+
signal: AbortSignal.timeout(1e4)
|
|
30
|
+
});
|
|
31
|
+
if (res.status === 401 || res.status === 403) return { kind: "unauthorized" };
|
|
32
|
+
if (!res.ok) return { kind: "failed", detail: `HTTP ${res.status}` };
|
|
33
|
+
return { kind: "ok" };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { kind: "failed", detail: err instanceof Error ? err.message : "network error" };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var parseMachine = (value) => {
|
|
39
|
+
if (typeof value !== "object" || value === null) return null;
|
|
40
|
+
const raw = value;
|
|
41
|
+
if (typeof raw.machineId !== "string" || typeof raw.hostname !== "string") return null;
|
|
42
|
+
return {
|
|
43
|
+
machineId: raw.machineId,
|
|
44
|
+
hostname: raw.hostname,
|
|
45
|
+
cliVersion: typeof raw.cliVersion === "string" ? raw.cliVersion : "unknown",
|
|
46
|
+
repoKey: typeof raw.repoKey === "string" ? raw.repoKey : null,
|
|
47
|
+
capabilities: Array.isArray(raw.capabilities) ? raw.capabilities.filter((c) => typeof c === "string") : [],
|
|
48
|
+
lastSeenAt: typeof raw.lastSeenAt === "string" ? raw.lastSeenAt : "",
|
|
49
|
+
online: raw.online === true
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
var listMachines = async (apiKey, deps = {}) => {
|
|
53
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
54
|
+
const baseUrl = deps.baseUrl ?? getBaseUrl();
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetchImpl(`${baseUrl}${MACHINES_PATH}`, {
|
|
57
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
58
|
+
signal: AbortSignal.timeout(1e4)
|
|
59
|
+
});
|
|
60
|
+
if (res.status === 404) return { kind: "unsupported" };
|
|
61
|
+
if (!res.ok) return { kind: "failed", detail: `HTTP ${res.status}` };
|
|
62
|
+
const body = await res.json().catch(() => null);
|
|
63
|
+
const list = body && Array.isArray(body.machines) ? body.machines : [];
|
|
64
|
+
return { kind: "ok", machines: list.map(parseMachine).filter((m) => m !== null) };
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return { kind: "failed", detail: err instanceof Error ? err.message : "network error" };
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var describeLastSeen = (iso, now = Date.now()) => {
|
|
70
|
+
const seen = Date.parse(iso);
|
|
71
|
+
if (!Number.isFinite(seen)) return "unknown";
|
|
72
|
+
const seconds = Math.max(0, Math.round((now - seen) / 1e3));
|
|
73
|
+
if (seconds < 60) return "just now";
|
|
74
|
+
const minutes = Math.round(seconds / 60);
|
|
75
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
76
|
+
const hours = Math.round(minutes / 60);
|
|
77
|
+
if (hours < 48) return `${hours}h ago`;
|
|
78
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
HEARTBEAT_INTERVAL_MS,
|
|
83
|
+
sendHeartbeat,
|
|
84
|
+
listMachines,
|
|
85
|
+
describeLastSeen
|
|
86
|
+
};
|