@scopebond/agent 0.1.0 → 0.2.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/README.md +17 -3
- package/dist/autostart.d.ts +15 -6
- package/dist/autostart.d.ts.map +1 -1
- package/dist/autostart.js +90 -19
- package/dist/autostart.js.map +1 -1
- package/dist/cli.js +30 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/selfcheck.d.ts +19 -0
- package/dist/selfcheck.d.ts.map +1 -0
- package/dist/selfcheck.js +55 -0
- package/dist/selfcheck.js.map +1 -0
- package/dist/service.d.ts +23 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +93 -11
- package/dist/service.js.map +1 -1
- package/dist/update.d.ts +28 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +86 -0
- package/dist/update.js.map +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -10,6 +10,13 @@ agent never takes part in a decision. It only keeps everything around the decisi
|
|
|
10
10
|
queue, and renews the machine credential before it expires.
|
|
11
11
|
- **Repair.** It notices when an agent's settings lost the Scopebond hook entry (an update or a reset can remove it)
|
|
12
12
|
and, on request, puts it back without touching anything else in the file.
|
|
13
|
+
- **Updates, under the workspace's control.** Every six hours it asks the workspace which versions to run. When
|
|
14
|
+
the workspace recommends a newer agent, it installs it from npm and restarts itself; when the workspace holds
|
|
15
|
+
updates, it changes nothing. It also moves the Scopebond hook entries in agent settings to the recommended hook
|
|
16
|
+
and repairs any entry that can no longer start. Other tools' hook entries are never touched.
|
|
17
|
+
- **Self-check.** Once a day it checks this computer's side (hook entry present and able to start, autostart on,
|
|
18
|
+
nothing stuck in the queue, connection not about to lapse) and sends the result, signed with the key the computer
|
|
19
|
+
enrolled with, so the workspace can show that the whole path works end to end, or what broke.
|
|
13
20
|
- **Health.** It reports the same machine-readable status as `scopebond status --json` (`scopebond.status.v1`).
|
|
14
21
|
|
|
15
22
|
## Use
|
|
@@ -28,15 +35,22 @@ On Windows, type `npx.cmd` instead of `npx` in PowerShell if you run it without
|
|
|
28
35
|
| `scopebond-agent status [--json]` | Delivery state, records waiting and the last problem |
|
|
29
36
|
| `scopebond-agent flush` | Deliver waiting records now |
|
|
30
37
|
| `scopebond-agent repair` | Put the Scopebond hook back into agent settings that lost it |
|
|
38
|
+
| `scopebond-agent check` | Check for updates and run the self-check now |
|
|
31
39
|
| `scopebond-agent autostart on\|off` | Start with your sign-in, or stop doing so |
|
|
32
40
|
|
|
33
41
|
## Local control channel
|
|
34
42
|
|
|
35
43
|
The agent listens on `127.0.0.1` only, on a random port, and every request must carry a random token. Port, token and
|
|
36
|
-
process id are in `~/.scopebond/agent.json`, readable by you only. Routes: `GET /status`, `POST /flush`, `POST /repair`.
|
|
44
|
+
process id are in `~/.scopebond/agent.json`, readable by you only. Routes: `GET /status`, `POST /flush`, `POST /repair`, `POST /maintain`.
|
|
37
45
|
Exactly one agent serves a computer's Scopebond home; a second one exits.
|
|
38
46
|
|
|
47
|
+
## Autostart
|
|
48
|
+
|
|
49
|
+
`autostart on` writes a small launcher script into the Scopebond home that finds Node and the agent each time it
|
|
50
|
+
runs (the paths recorded at setup first, then the ones on the system), so a Node upgrade or a version manager never
|
|
51
|
+
leaves autostart pointing at nothing. On Windows the Run entry starts it under `conhost --headless`, so no window
|
|
52
|
+
opens at sign-in. `status` says whether autostart is on and working.
|
|
53
|
+
|
|
39
54
|
## Status
|
|
40
55
|
|
|
41
|
-
Experimental. A tray
|
|
42
|
-
are next.
|
|
56
|
+
Experimental. A tray icon with a one-click fix for every amber or red state is next.
|
package/dist/autostart.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
export declare const LABEL = "com.scopebond.agent";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
2
|
+
export declare function launcherPath(home: string, platform?: NodeJS.Platform): string;
|
|
3
|
+
export declare function windowsLauncher(node: string, cli: string, logFile: string): string;
|
|
4
|
+
export declare function posixLauncher(node: string, cli: string): string;
|
|
5
|
+
/** The Run value: the launcher under a headless console host, so nothing appears on screen. */
|
|
6
|
+
export declare function windowsRunCommand(launcher: string): string;
|
|
7
|
+
export declare function macLaunchAgent(launcher: string, logFile: string): string;
|
|
8
|
+
export declare function linuxUserUnit(launcher: string): string;
|
|
5
9
|
export declare function autostartPaths(home?: string): {
|
|
6
10
|
macPlist: string;
|
|
7
11
|
linuxUnit: string;
|
|
8
|
-
log: string;
|
|
9
12
|
};
|
|
10
13
|
/** Turn autostart on for this user. Returns a one-line description of what changed. */
|
|
11
|
-
export declare function enableAutostart(cli: string, node?: string, platform?: NodeJS.Platform): string;
|
|
12
|
-
export declare function disableAutostart(platform?: NodeJS.Platform): string;
|
|
14
|
+
export declare function enableAutostart(scopebondHome: string, cli: string, node?: string, platform?: NodeJS.Platform): string;
|
|
15
|
+
export declare function disableAutostart(scopebondHome: string, platform?: NodeJS.Platform): string;
|
|
16
|
+
/** Whether autostart is on and its launcher can start the agent now. */
|
|
17
|
+
export declare function autostartHealth(scopebondHome: string, platform?: NodeJS.Platform): {
|
|
18
|
+
on: boolean;
|
|
19
|
+
ok: boolean;
|
|
20
|
+
detail: string;
|
|
21
|
+
};
|
|
13
22
|
//# sourceMappingURL=autostart.d.ts.map
|
package/dist/autostart.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autostart.d.ts","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"autostart.d.ts","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,KAAK,wBAAwB,CAAC;AAO3C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAE/F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAclF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAkB/D;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAexE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CActD;AAED,wBAAgB,cAAc,CAAC,IAAI,SAAY;;;EAK9C;AAWD,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,SAAmB,EAAE,QAAQ,kBAAmB,GAAG,MAAM,CAkBhI;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,kBAAmB,GAAG,MAAM,CAiB3F;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,kBAAmB,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAShI"}
|
package/dist/autostart.js
CHANGED
|
@@ -1,27 +1,68 @@
|
|
|
1
1
|
// Start the agent when the person signs in to the computer, per user, with no administrator
|
|
2
|
-
// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux.
|
|
3
|
+
//
|
|
4
|
+
// Each of them starts a small launcher script in the Scopebond home, not Node directly. The
|
|
5
|
+
// launcher finds Node and the agent when it runs (the recorded paths first, then the system's),
|
|
6
|
+
// so upgrading Node or switching versions never leaves an autostart entry pointing at nothing.
|
|
7
|
+
// On Windows it runs under `conhost --headless`, so no window opens at sign-in.
|
|
5
8
|
import { execFileSync } from "node:child_process";
|
|
6
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
7
10
|
import { homedir } from "node:os";
|
|
8
11
|
import { dirname, join } from "node:path";
|
|
9
12
|
export const LABEL = "com.scopebond.agent";
|
|
10
13
|
const RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
11
14
|
const RUN_VALUE = "ScopebondAgent";
|
|
12
|
-
const quoteWin = (s) => `"${s.replace(/"/g, "")}"`;
|
|
13
15
|
const xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
const noQuotes = (s) => s.replace(/"/g, "");
|
|
17
|
+
export function launcherPath(home, platform = process.platform) {
|
|
18
|
+
return join(home, platform === "win32" ? "agent-launch.cmd" : "agent-launch.sh");
|
|
16
19
|
}
|
|
17
|
-
export function
|
|
20
|
+
export function windowsLauncher(node, cli, logFile) {
|
|
21
|
+
return [
|
|
22
|
+
"@echo off",
|
|
23
|
+
"rem Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.",
|
|
24
|
+
"setlocal",
|
|
25
|
+
`set "NODE=${noQuotes(node)}"`,
|
|
26
|
+
`if not exist "%NODE%" set "NODE="`,
|
|
27
|
+
`if not defined NODE for /f "delims=" %%i in ('where node 2^>nul') do if not defined NODE set "NODE=%%i"`,
|
|
28
|
+
`set "CLI=${noQuotes(cli)}"`,
|
|
29
|
+
`if not exist "%CLI%" for /f "delims=" %%i in ('npm.cmd root -g 2^>nul') do set "CLI=%%i\\@scopebond\\agent\\dist\\cli.js"`,
|
|
30
|
+
`if not defined NODE exit /b 1`,
|
|
31
|
+
`"%NODE%" "%CLI%" run >> "${noQuotes(logFile)}" 2>&1`,
|
|
32
|
+
"",
|
|
33
|
+
].join("\r\n");
|
|
34
|
+
}
|
|
35
|
+
export function posixLauncher(node, cli) {
|
|
36
|
+
const q = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
37
|
+
return `#!/bin/sh
|
|
38
|
+
# Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.
|
|
39
|
+
NODE=${q(node)}
|
|
40
|
+
if [ ! -x "$NODE" ]; then
|
|
41
|
+
[ -s "$HOME/.nvm/nvm.sh" ] && . "$HOME/.nvm/nvm.sh" >/dev/null 2>&1
|
|
42
|
+
NODE="$(command -v node 2>/dev/null)"
|
|
43
|
+
for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
|
|
44
|
+
[ -n "$NODE" ] && break
|
|
45
|
+
[ -x "$candidate" ] && NODE="$candidate"
|
|
46
|
+
done
|
|
47
|
+
fi
|
|
48
|
+
CLI=${q(cli)}
|
|
49
|
+
[ -f "$CLI" ] || CLI="$(npm root -g 2>/dev/null)/@scopebond/agent/dist/cli.js"
|
|
50
|
+
[ -n "$NODE" ] || exit 1
|
|
51
|
+
exec "$NODE" "$CLI" run
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
/** The Run value: the launcher under a headless console host, so nothing appears on screen. */
|
|
55
|
+
export function windowsRunCommand(launcher) {
|
|
56
|
+
return `conhost.exe --headless cmd.exe /d /c "${noQuotes(launcher)}"`;
|
|
57
|
+
}
|
|
58
|
+
export function macLaunchAgent(launcher, logFile) {
|
|
18
59
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
19
60
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
20
61
|
<plist version="1.0">
|
|
21
62
|
<dict>
|
|
22
63
|
<key>Label</key><string>${LABEL}</string>
|
|
23
64
|
<key>ProgramArguments</key>
|
|
24
|
-
<array><string
|
|
65
|
+
<array><string>/bin/sh</string><string>${xml(launcher)}</string></array>
|
|
25
66
|
<key>RunAtLoad</key><true/>
|
|
26
67
|
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
|
27
68
|
<key>StandardOutPath</key><string>${xml(logFile)}</string>
|
|
@@ -30,14 +71,14 @@ export function macLaunchAgent(node, cli, logFile) {
|
|
|
30
71
|
</plist>
|
|
31
72
|
`;
|
|
32
73
|
}
|
|
33
|
-
export function linuxUserUnit(
|
|
74
|
+
export function linuxUserUnit(launcher) {
|
|
34
75
|
const q = (s) => `"${s.replace(/(["\\])/g, "\\$1")}"`;
|
|
35
76
|
return `[Unit]
|
|
36
77
|
Description=Scopebond Agent (delivers this computer's records to its Scopebond workspace)
|
|
37
78
|
After=network-online.target
|
|
38
79
|
|
|
39
80
|
[Service]
|
|
40
|
-
ExecStart
|
|
81
|
+
ExecStart=/bin/sh ${q(launcher)}
|
|
41
82
|
Restart=on-failure
|
|
42
83
|
RestartSec=30
|
|
43
84
|
|
|
@@ -49,20 +90,28 @@ export function autostartPaths(home = homedir()) {
|
|
|
49
90
|
return {
|
|
50
91
|
macPlist: join(home, "Library", "LaunchAgents", `${LABEL}.plist`),
|
|
51
92
|
linuxUnit: join(home, ".config", "systemd", "user", "scopebond-agent.service"),
|
|
52
|
-
log: join(home, ".scopebond", "agent.log"),
|
|
53
93
|
};
|
|
54
94
|
}
|
|
95
|
+
function writeLauncher(scopebondHome, node, cli, platform) {
|
|
96
|
+
mkdirSync(scopebondHome, { recursive: true });
|
|
97
|
+
const launcher = launcherPath(scopebondHome, platform);
|
|
98
|
+
const log = join(scopebondHome, "agent.log");
|
|
99
|
+
writeFileSync(launcher, platform === "win32" ? windowsLauncher(node, cli, log) : posixLauncher(node, cli), { mode: 0o700 });
|
|
100
|
+
if (platform !== "win32")
|
|
101
|
+
chmodSync(launcher, 0o700);
|
|
102
|
+
return launcher;
|
|
103
|
+
}
|
|
55
104
|
/** Turn autostart on for this user. Returns a one-line description of what changed. */
|
|
56
|
-
export function enableAutostart(cli, node = process.execPath, platform = process.platform) {
|
|
105
|
+
export function enableAutostart(scopebondHome, cli, node = process.execPath, platform = process.platform) {
|
|
106
|
+
const launcher = writeLauncher(scopebondHome, node, cli, platform);
|
|
57
107
|
const paths = autostartPaths();
|
|
58
108
|
if (platform === "win32") {
|
|
59
|
-
execFileSync("reg", ["add", RUN_KEY, "/v", RUN_VALUE, "/t", "REG_SZ", "/d", windowsRunCommand(
|
|
60
|
-
return `added ${RUN_VALUE} to ${RUN_KEY}`;
|
|
109
|
+
execFileSync("reg", ["add", RUN_KEY, "/v", RUN_VALUE, "/t", "REG_SZ", "/d", windowsRunCommand(launcher), "/f"], { stdio: "ignore" });
|
|
110
|
+
return `added ${RUN_VALUE} to ${RUN_KEY} (launcher ${launcher})`;
|
|
61
111
|
}
|
|
62
112
|
if (platform === "darwin") {
|
|
63
113
|
mkdirSync(dirname(paths.macPlist), { recursive: true });
|
|
64
|
-
|
|
65
|
-
writeFileSync(paths.macPlist, macLaunchAgent(node, cli, paths.log));
|
|
114
|
+
writeFileSync(paths.macPlist, macLaunchAgent(launcher, join(scopebondHome, "agent.log")));
|
|
66
115
|
try {
|
|
67
116
|
execFileSync("launchctl", ["load", "-w", paths.macPlist], { stdio: "ignore" });
|
|
68
117
|
}
|
|
@@ -70,7 +119,7 @@ export function enableAutostart(cli, node = process.execPath, platform = process
|
|
|
70
119
|
return `wrote ${paths.macPlist}`;
|
|
71
120
|
}
|
|
72
121
|
mkdirSync(dirname(paths.linuxUnit), { recursive: true });
|
|
73
|
-
writeFileSync(paths.linuxUnit, linuxUserUnit(
|
|
122
|
+
writeFileSync(paths.linuxUnit, linuxUserUnit(launcher));
|
|
74
123
|
try {
|
|
75
124
|
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
76
125
|
execFileSync("systemctl", ["--user", "enable", "--now", "scopebond-agent.service"], { stdio: "ignore" });
|
|
@@ -80,8 +129,9 @@ export function enableAutostart(cli, node = process.execPath, platform = process
|
|
|
80
129
|
}
|
|
81
130
|
return `wrote and enabled ${paths.linuxUnit}`;
|
|
82
131
|
}
|
|
83
|
-
export function disableAutostart(platform = process.platform) {
|
|
132
|
+
export function disableAutostart(scopebondHome, platform = process.platform) {
|
|
84
133
|
const paths = autostartPaths();
|
|
134
|
+
rmSync(launcherPath(scopebondHome, platform), { force: true });
|
|
85
135
|
if (platform === "win32") {
|
|
86
136
|
try {
|
|
87
137
|
execFileSync("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"], { stdio: "ignore" });
|
|
@@ -110,4 +160,25 @@ export function disableAutostart(platform = process.platform) {
|
|
|
110
160
|
rmSync(paths.linuxUnit, { force: true });
|
|
111
161
|
return `removed ${paths.linuxUnit}`;
|
|
112
162
|
}
|
|
163
|
+
/** Whether autostart is on and its launcher can start the agent now. */
|
|
164
|
+
export function autostartHealth(scopebondHome, platform = process.platform) {
|
|
165
|
+
const paths = autostartPaths();
|
|
166
|
+
let on = false;
|
|
167
|
+
if (platform === "win32") {
|
|
168
|
+
try {
|
|
169
|
+
execFileSync("reg", ["query", RUN_KEY, "/v", RUN_VALUE], { stdio: "ignore" });
|
|
170
|
+
on = true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
on = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else
|
|
177
|
+
on = existsSync(platform === "darwin" ? paths.macPlist : paths.linuxUnit);
|
|
178
|
+
if (!on)
|
|
179
|
+
return { on, ok: false, detail: "the agent does not start with sign-in (run: scopebond-agent autostart on)" };
|
|
180
|
+
if (!existsSync(launcherPath(scopebondHome, platform)))
|
|
181
|
+
return { on, ok: false, detail: "the autostart launcher is missing (run: scopebond-agent autostart on)" };
|
|
182
|
+
return { on, ok: true, detail: "starts with sign-in" };
|
|
183
|
+
}
|
|
113
184
|
//# sourceMappingURL=autostart.js.map
|
package/dist/autostart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"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","sourcesContent":["// Start the agent when the person signs in to the computer, per user, with no administrator\n// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux. The\n// builders are pure (and tested); `enable`/`disable` apply them. The command pins this Node and\n// this agent's absolute path, so it never depends on a shell's PATH or npm's temporary cache.\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const LABEL = \"com.scopebond.agent\";\nconst RUN_KEY = \"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\";\nconst RUN_VALUE = \"ScopebondAgent\";\n\nconst quoteWin = (s: string) => `\"${s.replace(/\"/g, \"\")}\"`;\nconst xml = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n\nexport function windowsRunCommand(node: string, cli: string): string {\n return `${quoteWin(node)} ${quoteWin(cli)} run`;\n}\n\nexport function macLaunchAgent(node: string, cli: string, logFile: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>${xml(node)}</string><string>${xml(cli)}</string><string>run</string></array>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>StandardOutPath</key><string>${xml(logFile)}</string>\n <key>StandardErrorPath</key><string>${xml(logFile)}</string>\n</dict>\n</plist>\n`;\n}\n\nexport function linuxUserUnit(node: string, cli: string): string {\n const q = (s: string) => `\"${s.replace(/([\"\\\\])/g, \"\\\\$1\")}\"`;\n return `[Unit]\nDescription=Scopebond Agent (delivers this computer's records to its Scopebond workspace)\nAfter=network-online.target\n\n[Service]\nExecStart=${q(node)} ${q(cli)} run\nRestart=on-failure\nRestartSec=30\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function autostartPaths(home = homedir()) {\n return {\n macPlist: join(home, \"Library\", \"LaunchAgents\", `${LABEL}.plist`),\n linuxUnit: join(home, \".config\", \"systemd\", \"user\", \"scopebond-agent.service\"),\n log: join(home, \".scopebond\", \"agent.log\"),\n };\n}\n\n/** Turn autostart on for this user. Returns a one-line description of what changed. */\nexport function enableAutostart(cli: string, node = process.execPath, platform = process.platform): string {\n const paths = autostartPaths();\n if (platform === \"win32\") {\n execFileSync(\"reg\", [\"add\", RUN_KEY, \"/v\", RUN_VALUE, \"/t\", \"REG_SZ\", \"/d\", windowsRunCommand(node, cli), \"/f\"], { stdio: \"ignore\" });\n return `added ${RUN_VALUE} to ${RUN_KEY}`;\n }\n if (platform === \"darwin\") {\n mkdirSync(dirname(paths.macPlist), { recursive: true });\n mkdirSync(dirname(paths.log), { recursive: true });\n writeFileSync(paths.macPlist, macLaunchAgent(node, cli, paths.log));\n try { execFileSync(\"launchctl\", [\"load\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* loads at next sign-in */ }\n return `wrote ${paths.macPlist}`;\n }\n mkdirSync(dirname(paths.linuxUnit), { recursive: true });\n writeFileSync(paths.linuxUnit, linuxUserUnit(node, cli));\n try { execFileSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { stdio: \"ignore\" }); execFileSync(\"systemctl\", [\"--user\", \"enable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); }\n catch { return `wrote ${paths.linuxUnit} (enable it with: systemctl --user enable --now scopebond-agent)`; }\n return `wrote and enabled ${paths.linuxUnit}`;\n}\n\nexport function disableAutostart(platform = process.platform): string {\n const paths = autostartPaths();\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"delete\", RUN_KEY, \"/v\", RUN_VALUE, \"/f\"], { stdio: \"ignore\" }); } catch { return \"autostart was not on\"; }\n return `removed ${RUN_VALUE} from ${RUN_KEY}`;\n }\n if (platform === \"darwin\") {\n if (!existsSync(paths.macPlist)) return \"autostart was not on\";\n try { execFileSync(\"launchctl\", [\"unload\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* not loaded */ }\n rmSync(paths.macPlist, { force: true });\n return `removed ${paths.macPlist}`;\n }\n if (!existsSync(paths.linuxUnit)) return \"autostart was not on\";\n try { execFileSync(\"systemctl\", [\"--user\", \"disable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); } catch { /* not enabled */ }\n rmSync(paths.linuxUnit, { force: true });\n return `removed ${paths.linuxUnit}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"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","sourcesContent":["// Start the agent when the person signs in to the computer, per user, with no administrator\n// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux.\n//\n// Each of them starts a small launcher script in the Scopebond home, not Node directly. The\n// launcher finds Node and the agent when it runs (the recorded paths first, then the system's),\n// so upgrading Node or switching versions never leaves an autostart entry pointing at nothing.\n// On Windows it runs under `conhost --headless`, so no window opens at sign-in.\n\nimport { execFileSync } from \"node:child_process\";\nimport { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const LABEL = \"com.scopebond.agent\";\nconst RUN_KEY = \"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\";\nconst RUN_VALUE = \"ScopebondAgent\";\n\nconst xml = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\nconst noQuotes = (s: string) => s.replace(/\"/g, \"\");\n\nexport function launcherPath(home: string, platform: NodeJS.Platform = process.platform): string {\n return join(home, platform === \"win32\" ? \"agent-launch.cmd\" : \"agent-launch.sh\");\n}\n\nexport function windowsLauncher(node: string, cli: string, logFile: string): string {\n return [\n \"@echo off\",\n \"rem Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.\",\n \"setlocal\",\n `set \"NODE=${noQuotes(node)}\"`,\n `if not exist \"%NODE%\" set \"NODE=\"`,\n `if not defined NODE for /f \"delims=\" %%i in ('where node 2^>nul') do if not defined NODE set \"NODE=%%i\"`,\n `set \"CLI=${noQuotes(cli)}\"`,\n `if not exist \"%CLI%\" for /f \"delims=\" %%i in ('npm.cmd root -g 2^>nul') do set \"CLI=%%i\\\\@scopebond\\\\agent\\\\dist\\\\cli.js\"`,\n `if not defined NODE exit /b 1`,\n `\"%NODE%\" \"%CLI%\" run >> \"${noQuotes(logFile)}\" 2>&1`,\n \"\",\n ].join(\"\\r\\n\");\n}\n\nexport function posixLauncher(node: string, cli: string): string {\n const q = (s: string) => `'${s.replace(/'/g, \"'\\\\''\")}'`;\n return `#!/bin/sh\n# Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.\nNODE=${q(node)}\nif [ ! -x \"$NODE\" ]; then\n [ -s \"$HOME/.nvm/nvm.sh\" ] && . \"$HOME/.nvm/nvm.sh\" >/dev/null 2>&1\n NODE=\"$(command -v node 2>/dev/null)\"\n for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do\n [ -n \"$NODE\" ] && break\n [ -x \"$candidate\" ] && NODE=\"$candidate\"\n done\nfi\nCLI=${q(cli)}\n[ -f \"$CLI\" ] || CLI=\"$(npm root -g 2>/dev/null)/@scopebond/agent/dist/cli.js\"\n[ -n \"$NODE\" ] || exit 1\nexec \"$NODE\" \"$CLI\" run\n`;\n}\n\n/** The Run value: the launcher under a headless console host, so nothing appears on screen. */\nexport function windowsRunCommand(launcher: string): string {\n return `conhost.exe --headless cmd.exe /d /c \"${noQuotes(launcher)}\"`;\n}\n\nexport function macLaunchAgent(launcher: string, logFile: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>/bin/sh</string><string>${xml(launcher)}</string></array>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>StandardOutPath</key><string>${xml(logFile)}</string>\n <key>StandardErrorPath</key><string>${xml(logFile)}</string>\n</dict>\n</plist>\n`;\n}\n\nexport function linuxUserUnit(launcher: string): string {\n const q = (s: string) => `\"${s.replace(/([\"\\\\])/g, \"\\\\$1\")}\"`;\n return `[Unit]\nDescription=Scopebond Agent (delivers this computer's records to its Scopebond workspace)\nAfter=network-online.target\n\n[Service]\nExecStart=/bin/sh ${q(launcher)}\nRestart=on-failure\nRestartSec=30\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function autostartPaths(home = homedir()) {\n return {\n macPlist: join(home, \"Library\", \"LaunchAgents\", `${LABEL}.plist`),\n linuxUnit: join(home, \".config\", \"systemd\", \"user\", \"scopebond-agent.service\"),\n };\n}\n\nfunction writeLauncher(scopebondHome: string, node: string, cli: string, platform: NodeJS.Platform): string {\n mkdirSync(scopebondHome, { recursive: true });\n const launcher = launcherPath(scopebondHome, platform);\n const log = join(scopebondHome, \"agent.log\");\n writeFileSync(launcher, platform === \"win32\" ? windowsLauncher(node, cli, log) : posixLauncher(node, cli), { mode: 0o700 });\n if (platform !== \"win32\") chmodSync(launcher, 0o700);\n return launcher;\n}\n\n/** Turn autostart on for this user. Returns a one-line description of what changed. */\nexport function enableAutostart(scopebondHome: string, cli: string, node = process.execPath, platform = process.platform): string {\n const launcher = writeLauncher(scopebondHome, node, cli, platform);\n const paths = autostartPaths();\n if (platform === \"win32\") {\n execFileSync(\"reg\", [\"add\", RUN_KEY, \"/v\", RUN_VALUE, \"/t\", \"REG_SZ\", \"/d\", windowsRunCommand(launcher), \"/f\"], { stdio: \"ignore\" });\n return `added ${RUN_VALUE} to ${RUN_KEY} (launcher ${launcher})`;\n }\n if (platform === \"darwin\") {\n mkdirSync(dirname(paths.macPlist), { recursive: true });\n writeFileSync(paths.macPlist, macLaunchAgent(launcher, join(scopebondHome, \"agent.log\")));\n try { execFileSync(\"launchctl\", [\"load\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* loads at next sign-in */ }\n return `wrote ${paths.macPlist}`;\n }\n mkdirSync(dirname(paths.linuxUnit), { recursive: true });\n writeFileSync(paths.linuxUnit, linuxUserUnit(launcher));\n try { execFileSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { stdio: \"ignore\" }); execFileSync(\"systemctl\", [\"--user\", \"enable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); }\n catch { return `wrote ${paths.linuxUnit} (enable it with: systemctl --user enable --now scopebond-agent)`; }\n return `wrote and enabled ${paths.linuxUnit}`;\n}\n\nexport function disableAutostart(scopebondHome: string, platform = process.platform): string {\n const paths = autostartPaths();\n rmSync(launcherPath(scopebondHome, platform), { force: true });\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"delete\", RUN_KEY, \"/v\", RUN_VALUE, \"/f\"], { stdio: \"ignore\" }); } catch { return \"autostart was not on\"; }\n return `removed ${RUN_VALUE} from ${RUN_KEY}`;\n }\n if (platform === \"darwin\") {\n if (!existsSync(paths.macPlist)) return \"autostart was not on\";\n try { execFileSync(\"launchctl\", [\"unload\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* not loaded */ }\n rmSync(paths.macPlist, { force: true });\n return `removed ${paths.macPlist}`;\n }\n if (!existsSync(paths.linuxUnit)) return \"autostart was not on\";\n try { execFileSync(\"systemctl\", [\"--user\", \"disable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); } catch { /* not enabled */ }\n rmSync(paths.linuxUnit, { force: true });\n return `removed ${paths.linuxUnit}`;\n}\n\n/** Whether autostart is on and its launcher can start the agent now. */\nexport function autostartHealth(scopebondHome: string, platform = process.platform): { on: boolean; ok: boolean; detail: string } {\n const paths = autostartPaths();\n let on = false;\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"query\", RUN_KEY, \"/v\", RUN_VALUE], { stdio: \"ignore\" }); on = true; } catch { on = false; }\n } else on = existsSync(platform === \"darwin\" ? paths.macPlist : paths.linuxUnit);\n if (!on) return { on, ok: false, detail: \"the agent does not start with sign-in (run: scopebond-agent autostart on)\" };\n if (!existsSync(launcherPath(scopebondHome, platform))) return { on, ok: false, detail: \"the autostart launcher is missing (run: scopebond-agent autostart on)\" };\n return { on, ok: true, detail: \"starts with sign-in\" };\n}\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
// scopebond-agent status [--json] what the running agent reports, or why it is not running
|
|
6
6
|
// scopebond-agent flush deliver now
|
|
7
7
|
// scopebond-agent repair put the Scopebond hook back where agent settings lost it
|
|
8
|
+
// scopebond-agent check update check, hook upkeep and the end-to-end self-check, now
|
|
8
9
|
// scopebond-agent autostart on|off start with this user's sign-in, or stop doing so
|
|
9
10
|
import { realpathSync } from "node:fs";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { isEphemeralPath, userHome } from "@scopebond/hook";
|
|
12
13
|
import { computerStatus } from "./agent.js";
|
|
13
|
-
import { disableAutostart, enableAutostart } from "./autostart.js";
|
|
14
|
+
import { autostartHealth, disableAutostart, enableAutostart } from "./autostart.js";
|
|
14
15
|
import { callAgent } from "./ipc.js";
|
|
15
|
-
import { startService } from "./service.js";
|
|
16
|
+
import { AFTER_PID_ENV, AGENT_VERSION, startService } from "./service.js";
|
|
16
17
|
const [cmd = "help", ...rest] = process.argv.slice(2);
|
|
17
18
|
const dir = process.env.SCOPEBOND_HOME ?? userHome();
|
|
18
19
|
const cliPath = realpathSync(fileURLToPath(import.meta.url));
|
|
@@ -23,6 +24,7 @@ function help() {
|
|
|
23
24
|
scopebond-agent status [--json] what the agent reports about this computer
|
|
24
25
|
scopebond-agent flush deliver waiting records now
|
|
25
26
|
scopebond-agent repair put the Scopebond hook back where agent settings lost it
|
|
27
|
+
scopebond-agent check check for updates and run the end-to-end self-check now
|
|
26
28
|
scopebond-agent autostart on|off start with your sign-in, or stop doing so
|
|
27
29
|
|
|
28
30
|
The hook keeps deciding every action on its own; the agent only keeps delivery, rules and the
|
|
@@ -31,6 +33,10 @@ connection current. Home: ${dir}`);
|
|
|
31
33
|
async function main() {
|
|
32
34
|
switch (cmd) {
|
|
33
35
|
case "run": {
|
|
36
|
+
// Started by an agent that just updated itself: let it exit first.
|
|
37
|
+
const previous = Number(process.env[AFTER_PID_ENV]);
|
|
38
|
+
if (Number.isInteger(previous) && previous > 0)
|
|
39
|
+
await waitForExit(previous, 30_000);
|
|
34
40
|
const service = await startService({ dir });
|
|
35
41
|
const stop = () => { void service.stop().finally(() => process.exit(0)); };
|
|
36
42
|
process.on("SIGINT", stop);
|
|
@@ -39,7 +45,7 @@ async function main() {
|
|
|
39
45
|
}
|
|
40
46
|
case "status": {
|
|
41
47
|
const live = await callAgent(dir, "GET", "/status");
|
|
42
|
-
const status = live ?? { ...computerStatus(dir), agent: null };
|
|
48
|
+
const status = { ...(live ?? { ...computerStatus(dir), agent: null }), autostart: autostartHealth(dir) };
|
|
43
49
|
if (rest.includes("--json")) {
|
|
44
50
|
console.log(JSON.stringify(status, null, 2));
|
|
45
51
|
return;
|
|
@@ -51,13 +57,19 @@ async function main() {
|
|
|
51
57
|
console.log(` waiting to send ${d.pending} record(s)${d.oldest_pending_age_s !== null ? `, oldest ${Math.round(d.oldest_pending_age_s / 60)} min` : ""}`);
|
|
52
58
|
if (d.last_error)
|
|
53
59
|
console.log(` last problem ${d.last_error}`);
|
|
60
|
+
console.log(` version ${status.agent?.version ?? AGENT_VERSION}`);
|
|
61
|
+
console.log(` autostart ${status.autostart.detail}`);
|
|
62
|
+
const check = status.agent?.last_maintenance?.selfCheck;
|
|
63
|
+
if (check)
|
|
64
|
+
console.log(` self-check ${check.ok ? "passed" : `failed: ${check.failed.join(", ")}`}`);
|
|
54
65
|
if (!live)
|
|
55
66
|
console.log(`\nStart it with: scopebond-agent run (or: scopebond-agent autostart on)`);
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
58
69
|
case "flush":
|
|
59
|
-
case "repair":
|
|
60
|
-
|
|
70
|
+
case "repair":
|
|
71
|
+
case "check": {
|
|
72
|
+
const answer = await callAgent(dir, "POST", `/${cmd === "check" ? "maintain" : cmd}`, {}, 6 * 60_000);
|
|
61
73
|
if (!answer) {
|
|
62
74
|
console.error("The Scopebond Agent is not running. Start it with: scopebond-agent run");
|
|
63
75
|
process.exitCode = 1;
|
|
@@ -79,7 +91,7 @@ async function main() {
|
|
|
79
91
|
process.exitCode = 1;
|
|
80
92
|
return;
|
|
81
93
|
}
|
|
82
|
-
console.log(on ? enableAutostart(cliPath) : disableAutostart());
|
|
94
|
+
console.log(on ? enableAutostart(dir, cliPath) : disableAutostart(dir));
|
|
83
95
|
return;
|
|
84
96
|
}
|
|
85
97
|
default:
|
|
@@ -88,5 +100,17 @@ async function main() {
|
|
|
88
100
|
process.exitCode = 1;
|
|
89
101
|
}
|
|
90
102
|
}
|
|
103
|
+
async function waitForExit(pid, timeoutMs) {
|
|
104
|
+
const until = Date.now() + timeoutMs;
|
|
105
|
+
while (Date.now() < until) {
|
|
106
|
+
try {
|
|
107
|
+
process.kill(pid, 0);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
91
115
|
void main().catch((error) => { console.error(error.message); process.exitCode = 1; });
|
|
92
116
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// scopebond-agent: run, look at and control the Scopebond Agent for this user.\n//\n// scopebond-agent run run in the foreground (what autostart starts)\n// scopebond-agent status [--json] what the running agent reports, or why it is not running\n// scopebond-agent flush deliver now\n// scopebond-agent repair put the Scopebond hook back where agent settings lost it\n// scopebond-agent autostart on|off start with this user's sign-in, or stop doing so\n\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isEphemeralPath, userHome } from \"@scopebond/hook\";\nimport { computerStatus } from \"./agent.js\";\nimport { disableAutostart, enableAutostart } from \"./autostart.js\";\nimport { callAgent } from \"./ipc.js\";\nimport { startService } from \"./service.js\";\n\nconst [cmd = \"help\", ...rest] = process.argv.slice(2);\nconst dir = process.env.SCOPEBOND_HOME ?? userHome();\nconst cliPath = realpathSync(fileURLToPath(import.meta.url));\n\nfunction help(): void {\n console.log(`Scopebond Agent — keeps this computer delivering to its Scopebond workspace.\n\n scopebond-agent run run in the foreground (what autostart starts)\n scopebond-agent status [--json] what the agent reports about this computer\n scopebond-agent flush deliver waiting records now\n scopebond-agent repair put the Scopebond hook back where agent settings lost it\n scopebond-agent autostart on|off start with your sign-in, or stop doing so\n\nThe hook keeps deciding every action on its own; the agent only keeps delivery, rules and the\nconnection current. Home: ${dir}`);\n}\n\nasync function main(): Promise<void> {\n switch (cmd) {\n case \"run\": {\n const service = await startService({ dir });\n const stop = () => { void service.stop().finally(() => process.exit(0)); };\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n return;\n }\n case \"status\": {\n const live = await callAgent(dir, \"GET\", \"/status\") as (ReturnType<typeof computerStatus> & { agent?: unknown }) | null;\n const status = live ?? { ...computerStatus(dir), agent: null };\n if (rest.includes(\"--json\")) { console.log(JSON.stringify(status, null, 2)); return; }\n const d = status.delivery;\n console.log(`Scopebond Agent: ${live ? \"running\" : \"not running\"}`);\n console.log(` state ${status.state}`);\n console.log(` connected ${d.connected ? \"yes\" : \"no\"}`);\n console.log(` waiting to send ${d.pending} record(s)${d.oldest_pending_age_s !== null ? `, oldest ${Math.round(d.oldest_pending_age_s / 60)} min` : \"\"}`);\n if (d.last_error) console.log(` last problem ${d.last_error}`);\n if (!live) console.log(`\\nStart it with: scopebond-agent run (or: scopebond-agent autostart on)`);\n return;\n }\n case \"flush\":\n case \"repair\": {\n const answer = await callAgent(dir, \"POST\", `/${cmd}`, {}, 60_000);\n if (!answer) { console.error(\"The Scopebond Agent is not running. Start it with: scopebond-agent run\"); process.exitCode = 1; return; }\n console.log(JSON.stringify(answer, null, 2));\n return;\n }\n case \"autostart\": {\n const on = rest[0] === \"on\";\n if (rest[0] !== \"on\" && rest[0] !== \"off\") { console.error(\"usage: scopebond-agent autostart on|off\"); process.exitCode = 1; return; }\n if (on && isEphemeralPath(cliPath)) {\n // npx runs from a cache npm clears; an autostart entry pointing there would stop working.\n console.error(\"Install the agent first so autostart has a stable path: npm install -g @scopebond/agent\");\n process.exitCode = 1;\n return;\n }\n console.log(on ? enableAutostart(cliPath) : disableAutostart());\n return;\n }\n default:\n help();\n if (cmd !== \"help\" && cmd !== \"--help\") process.exitCode = 1;\n }\n}\n\nvoid main().catch((error) => { console.error((error as Error).message); process.exitCode = 1; });\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// scopebond-agent: run, look at and control the Scopebond Agent for this user.\n//\n// scopebond-agent run run in the foreground (what autostart starts)\n// scopebond-agent status [--json] what the running agent reports, or why it is not running\n// scopebond-agent flush deliver now\n// scopebond-agent repair put the Scopebond hook back where agent settings lost it\n// scopebond-agent check update check, hook upkeep and the end-to-end self-check, now\n// scopebond-agent autostart on|off start with this user's sign-in, or stop doing so\n\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { isEphemeralPath, userHome } from \"@scopebond/hook\";\nimport { computerStatus } from \"./agent.js\";\nimport { autostartHealth, disableAutostart, enableAutostart } from \"./autostart.js\";\nimport { callAgent } from \"./ipc.js\";\nimport { AFTER_PID_ENV, AGENT_VERSION, startService } from \"./service.js\";\n\nconst [cmd = \"help\", ...rest] = process.argv.slice(2);\nconst dir = process.env.SCOPEBOND_HOME ?? userHome();\nconst cliPath = realpathSync(fileURLToPath(import.meta.url));\n\nfunction help(): void {\n console.log(`Scopebond Agent — keeps this computer delivering to its Scopebond workspace.\n\n scopebond-agent run run in the foreground (what autostart starts)\n scopebond-agent status [--json] what the agent reports about this computer\n scopebond-agent flush deliver waiting records now\n scopebond-agent repair put the Scopebond hook back where agent settings lost it\n scopebond-agent check check for updates and run the end-to-end self-check now\n scopebond-agent autostart on|off start with your sign-in, or stop doing so\n\nThe hook keeps deciding every action on its own; the agent only keeps delivery, rules and the\nconnection current. Home: ${dir}`);\n}\n\nasync function main(): Promise<void> {\n switch (cmd) {\n case \"run\": {\n // Started by an agent that just updated itself: let it exit first.\n const previous = Number(process.env[AFTER_PID_ENV]);\n if (Number.isInteger(previous) && previous > 0) await waitForExit(previous, 30_000);\n const service = await startService({ dir });\n const stop = () => { void service.stop().finally(() => process.exit(0)); };\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n return;\n }\n case \"status\": {\n const live = await callAgent(dir, \"GET\", \"/status\") as (ReturnType<typeof computerStatus> & { agent?: AgentReport | null }) | null;\n const status = { ...(live ?? { ...computerStatus(dir), agent: null }), autostart: autostartHealth(dir) };\n if (rest.includes(\"--json\")) { console.log(JSON.stringify(status, null, 2)); return; }\n const d = status.delivery;\n console.log(`Scopebond Agent: ${live ? \"running\" : \"not running\"}`);\n console.log(` state ${status.state}`);\n console.log(` connected ${d.connected ? \"yes\" : \"no\"}`);\n console.log(` waiting to send ${d.pending} record(s)${d.oldest_pending_age_s !== null ? `, oldest ${Math.round(d.oldest_pending_age_s / 60)} min` : \"\"}`);\n if (d.last_error) console.log(` last problem ${d.last_error}`);\n console.log(` version ${status.agent?.version ?? AGENT_VERSION}`);\n console.log(` autostart ${status.autostart.detail}`);\n const check = status.agent?.last_maintenance?.selfCheck;\n if (check) console.log(` self-check ${check.ok ? \"passed\" : `failed: ${check.failed.join(\", \")}`}`);\n if (!live) console.log(`\\nStart it with: scopebond-agent run (or: scopebond-agent autostart on)`);\n return;\n }\n case \"flush\":\n case \"repair\":\n case \"check\": {\n const answer = await callAgent(dir, \"POST\", `/${cmd === \"check\" ? \"maintain\" : cmd}`, {}, 6 * 60_000);\n if (!answer) { console.error(\"The Scopebond Agent is not running. Start it with: scopebond-agent run\"); process.exitCode = 1; return; }\n console.log(JSON.stringify(answer, null, 2));\n return;\n }\n case \"autostart\": {\n const on = rest[0] === \"on\";\n if (rest[0] !== \"on\" && rest[0] !== \"off\") { console.error(\"usage: scopebond-agent autostart on|off\"); process.exitCode = 1; return; }\n if (on && isEphemeralPath(cliPath)) {\n // npx runs from a cache npm clears; an autostart entry pointing there would stop working.\n console.error(\"Install the agent first so autostart has a stable path: npm install -g @scopebond/agent\");\n process.exitCode = 1;\n return;\n }\n console.log(on ? enableAutostart(dir, cliPath) : disableAutostart(dir));\n return;\n }\n default:\n help();\n if (cmd !== \"help\" && cmd !== \"--help\") process.exitCode = 1;\n }\n}\n\ninterface AgentReport { version?: string; last_maintenance?: { selfCheck?: { ok: boolean; failed: string[] } | null } | null }\n\nasync function waitForExit(pid: number, timeoutMs: number): Promise<void> {\n const until = Date.now() + timeoutMs;\n while (Date.now() < until) {\n try { process.kill(pid, 0); } catch { return; }\n await new Promise((r) => setTimeout(r, 250));\n }\n}\n\nvoid main().catch((error) => { console.error((error as Error).message); process.exitCode = 1; });\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export { runCycle, computerStatus, expectedHarnesses, missingHookEntries } from "./agent.js";
|
|
2
2
|
export type { CycleOptions, CycleResult } from "./agent.js";
|
|
3
|
-
export { startService, repairHookEntries, AGENT_VERSION } from "./service.js";
|
|
4
|
-
export type { Service, ServiceOptions } from "./service.js";
|
|
3
|
+
export { startService, repairHookEntries, spawnReplacement, AGENT_VERSION, AFTER_PID_ENV } from "./service.js";
|
|
4
|
+
export type { Service, ServiceOptions, MaintenanceResult } from "./service.js";
|
|
5
5
|
export { startControl, callAgent, readEndpoint, AGENT_FILE, TOKEN_HEADER } from "./ipc.js";
|
|
6
6
|
export type { AgentEndpoint } from "./ipc.js";
|
|
7
|
-
export { windowsRunCommand, macLaunchAgent, linuxUserUnit, enableAutostart, disableAutostart, autostartPaths, LABEL } from "./autostart.js";
|
|
7
|
+
export { launcherPath, windowsLauncher, posixLauncher, windowsRunCommand, macLaunchAgent, linuxUserUnit, enableAutostart, disableAutostart, autostartHealth, autostartPaths, LABEL, } from "./autostart.js";
|
|
8
|
+
export { agentVersion, fetchClientVersion, compareVersions, commandHookVersion, maintainedHookCommand, maintainHookEntries, installAgent, } from "./update.js";
|
|
9
|
+
export type { ClientVersion } from "./update.js";
|
|
10
|
+
export { selfCheckProof, localChecks, runSelfCheck } from "./selfcheck.js";
|
|
11
|
+
export type { SelfCheckItem } from "./selfcheck.js";
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC7F,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC7F,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/G,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EACL,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAC9F,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,KAAK,GAC1E,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,YAAY,GAChI,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3E,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { runCycle, computerStatus, expectedHarnesses, missingHookEntries } from "./agent.js";
|
|
2
|
-
export { startService, repairHookEntries, AGENT_VERSION } from "./service.js";
|
|
2
|
+
export { startService, repairHookEntries, spawnReplacement, AGENT_VERSION, AFTER_PID_ENV } from "./service.js";
|
|
3
3
|
export { startControl, callAgent, readEndpoint, AGENT_FILE, TOKEN_HEADER } from "./ipc.js";
|
|
4
|
-
export { windowsRunCommand, macLaunchAgent, linuxUserUnit, enableAutostart, disableAutostart, autostartPaths, LABEL } from "./autostart.js";
|
|
4
|
+
export { launcherPath, windowsLauncher, posixLauncher, windowsRunCommand, macLaunchAgent, linuxUserUnit, enableAutostart, disableAutostart, autostartHealth, autostartPaths, LABEL, } from "./autostart.js";
|
|
5
|
+
export { agentVersion, fetchClientVersion, compareVersions, commandHookVersion, maintainedHookCommand, maintainHookEntries, installAgent, } from "./update.js";
|
|
6
|
+
export { selfCheckProof, localChecks, runSelfCheck } from "./selfcheck.js";
|
|
5
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE7F,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE7F,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE/G,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE3F,OAAO,EACL,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAC9F,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,KAAK,GAC1E,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,YAAY,GAChI,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export { runCycle, computerStatus, expectedHarnesses, missingHookEntries } from \"./agent.js\";\nexport type { CycleOptions, CycleResult } from \"./agent.js\";\nexport { startService, repairHookEntries, spawnReplacement, AGENT_VERSION, AFTER_PID_ENV } from \"./service.js\";\nexport type { Service, ServiceOptions, MaintenanceResult } from \"./service.js\";\nexport { startControl, callAgent, readEndpoint, AGENT_FILE, TOKEN_HEADER } from \"./ipc.js\";\nexport type { AgentEndpoint } from \"./ipc.js\";\nexport {\n launcherPath, windowsLauncher, posixLauncher, windowsRunCommand, macLaunchAgent, linuxUserUnit,\n enableAutostart, disableAutostart, autostartHealth, autostartPaths, LABEL,\n} from \"./autostart.js\";\nexport {\n agentVersion, fetchClientVersion, compareVersions, commandHookVersion, maintainedHookCommand, maintainHookEntries, installAgent,\n} from \"./update.js\";\nexport type { ClientVersion } from \"./update.js\";\nexport { selfCheckProof, localChecks, runSelfCheck } from \"./selfcheck.js\";\nexport type { SelfCheckItem } from \"./selfcheck.js\";\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Harness, type HookConnection } from "@scopebond/hook";
|
|
2
|
+
export interface SelfCheckItem {
|
|
3
|
+
id: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
detail?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function selfCheckProof(credentialId: string, gatewayId: string, day: string): string;
|
|
8
|
+
export declare function localChecks(dir: string, connection: HookConnection, harnesses: Harness[], now?: number): SelfCheckItem[];
|
|
9
|
+
export declare function runSelfCheck(dir: string, connection: HookConnection, harnesses: Harness[], agentVersion: string, options?: {
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
now?: number;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
ok: boolean;
|
|
15
|
+
signature_verified: boolean;
|
|
16
|
+
failed: string[];
|
|
17
|
+
checks: SelfCheckItem[];
|
|
18
|
+
} | null>;
|
|
19
|
+
//# sourceMappingURL=selfcheck.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfcheck.d.ts","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,cAAc,EAClC,MAAM,iBAAiB,CAAC;AAGzB,MAAM,WAAW,aAAa;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AAE3E,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,GAAG,SAAa,GAAG,aAAa,EAAE,CAgB5H;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,EACnF,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3E,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBzG"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The daily end-to-end self-check (SB347). The agent checks this computer's side (the hook entry
|
|
2
|
+
// is present and starts, autostart works, nothing is stuck, the connection is not about to lapse),
|
|
3
|
+
// then signs the day with the key it enrolled with and sends everything to the workspace, which
|
|
4
|
+
// verifies the signature. A pass means the whole path works, end to end; a failure names what broke.
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { canonical } from "@scopebond/gateway";
|
|
8
|
+
import { loadOrCreateAttester } from "@scopebond/gateway/node";
|
|
9
|
+
import { configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, queueStatus, readDeliveryState, userHarnessFile, } from "@scopebond/hook";
|
|
10
|
+
import { autostartHealth } from "./autostart.js";
|
|
11
|
+
export function selfCheckProof(credentialId, gatewayId, day) {
|
|
12
|
+
return canonical({ type: "scopebond:self-check", version: 1, credential_id: credentialId, gateway_id: gatewayId, day });
|
|
13
|
+
}
|
|
14
|
+
export function localChecks(dir, connection, harnesses, now = Date.now()) {
|
|
15
|
+
const checks = [];
|
|
16
|
+
const entries = harnesses.flatMap((h) => configuredHookCommands(userHarnessFile(h)).filter((c) => isScopebondHookCommand(c)));
|
|
17
|
+
checks.push({ id: "hook_entry", ok: entries.length > 0, ...(entries.length ? {} : { detail: "no agent setting holds the Scopebond hook" }) });
|
|
18
|
+
const broken = entries.filter((c) => !hookCommandResolves(c));
|
|
19
|
+
checks.push({ id: "hook_starts", ok: entries.length > 0 && broken.length === 0, ...(broken.length ? { detail: `${broken.length} hook command(s) cannot start` } : {}) });
|
|
20
|
+
const autostart = autostartHealth(dir);
|
|
21
|
+
checks.push({ id: "autostart", ok: autostart.ok, ...(autostart.ok ? {} : { detail: autostart.detail }) });
|
|
22
|
+
const { pending, oldest } = queueStatus(dir);
|
|
23
|
+
const state = readDeliveryState(dir);
|
|
24
|
+
const stuck = pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000;
|
|
25
|
+
checks.push({ id: "queue", ok: !stuck, ...(stuck ? { detail: `${pending} record(s) waiting since ${new Date(oldest).toISOString()}${state.last_error ? ` (${state.last_error.slice(0, 120)})` : ""}` } : {}) });
|
|
26
|
+
const expires = Date.parse(connection.expires_at ?? "");
|
|
27
|
+
const lapsing = Number.isFinite(expires) && expires - now < 7 * 24 * 60 * 60 * 1000;
|
|
28
|
+
checks.push({ id: "credential", ok: !lapsing, ...(lapsing ? { detail: `the connection expires ${new Date(expires).toISOString()} and was not renewed` } : {}) });
|
|
29
|
+
return checks;
|
|
30
|
+
}
|
|
31
|
+
export async function runSelfCheck(dir, connection, harnesses, agentVersion, options = {}) {
|
|
32
|
+
const now = options.now ?? Date.now();
|
|
33
|
+
const checks = localChecks(dir, connection, harnesses, now);
|
|
34
|
+
const keyFile = join(dir, "attester.key");
|
|
35
|
+
if (!existsSync(keyFile))
|
|
36
|
+
return { ok: false, signature_verified: false, failed: ["signing_key"], checks };
|
|
37
|
+
const day = new Date(now).toISOString().slice(0, 10);
|
|
38
|
+
const { attester } = loadOrCreateAttester({ file: keyFile });
|
|
39
|
+
const signature = await attester.sign(selfCheckProof(connection.credential_id, connection.gateway_id, day));
|
|
40
|
+
try {
|
|
41
|
+
const res = await (options.fetchImpl ?? fetch)(new URL("/v1/self-check", connection.url).toString(), {
|
|
42
|
+
method: "POST", redirect: "error", signal: AbortSignal.timeout(options.timeoutMs ?? 10_000),
|
|
43
|
+
headers: { authorization: `Bearer ${connection.credential}`, "content-type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ signature, day, checks, hook_version: hookVersion(), agent_version: agentVersion }),
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok)
|
|
47
|
+
return { ok: false, signature_verified: false, failed: [`workspace_http_${res.status}`], checks };
|
|
48
|
+
const body = await res.json();
|
|
49
|
+
return { ok: !!body.ok, signature_verified: !!body.signature_verified, failed: Array.isArray(body.failed) ? body.failed : [], checks };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return { ok: false, signature_verified: false, failed: [`workspace_unreachable: ${error.message.slice(0, 80)}`], checks };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=selfcheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfcheck.js","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"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","sourcesContent":["// The daily end-to-end self-check (SB347). The agent checks this computer's side (the hook entry\n// is present and starts, autostart works, nothing is stuck, the connection is not about to lapse),\n// then signs the day with the key it enrolled with and sends everything to the workspace, which\n// verifies the signature. A pass means the whole path works, end to end; a failure names what broke.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { canonical } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport {\n configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, queueStatus, readDeliveryState, userHarnessFile,\n type Harness, type HookConnection,\n} from \"@scopebond/hook\";\nimport { autostartHealth } from \"./autostart.js\";\n\nexport interface SelfCheckItem { id: string; ok: boolean; detail?: string }\n\nexport function selfCheckProof(credentialId: string, gatewayId: string, day: string): string {\n return canonical({ type: \"scopebond:self-check\", version: 1, credential_id: credentialId, gateway_id: gatewayId, day });\n}\n\nexport function localChecks(dir: string, connection: HookConnection, harnesses: Harness[], now = Date.now()): SelfCheckItem[] {\n const checks: SelfCheckItem[] = [];\n const entries = harnesses.flatMap((h) => configuredHookCommands(userHarnessFile(h)).filter((c) => isScopebondHookCommand(c)));\n checks.push({ id: \"hook_entry\", ok: entries.length > 0, ...(entries.length ? {} : { detail: \"no agent setting holds the Scopebond hook\" }) });\n const broken = entries.filter((c) => !hookCommandResolves(c));\n checks.push({ id: \"hook_starts\", ok: entries.length > 0 && broken.length === 0, ...(broken.length ? { detail: `${broken.length} hook command(s) cannot start` } : {}) });\n const autostart = autostartHealth(dir);\n checks.push({ id: \"autostart\", ok: autostart.ok, ...(autostart.ok ? {} : { detail: autostart.detail }) });\n const { pending, oldest } = queueStatus(dir);\n const state = readDeliveryState(dir);\n const stuck = pending > 0 && oldest !== null && now - oldest > 60 * 60 * 1000;\n checks.push({ id: \"queue\", ok: !stuck, ...(stuck ? { detail: `${pending} record(s) waiting since ${new Date(oldest!).toISOString()}${state.last_error ? ` (${state.last_error.slice(0, 120)})` : \"\"}` } : {}) });\n const expires = Date.parse(connection.expires_at ?? \"\");\n const lapsing = Number.isFinite(expires) && expires - now < 7 * 24 * 60 * 60 * 1000;\n checks.push({ id: \"credential\", ok: !lapsing, ...(lapsing ? { detail: `the connection expires ${new Date(expires).toISOString()} and was not renewed` } : {}) });\n return checks;\n}\n\nexport async function runSelfCheck(\n dir: string, connection: HookConnection, harnesses: Harness[], agentVersion: string,\n options: { fetchImpl?: typeof fetch; now?: number; timeoutMs?: number } = {},\n): Promise<{ ok: boolean; signature_verified: boolean; failed: string[]; checks: SelfCheckItem[] } | null> {\n const now = options.now ?? Date.now();\n const checks = localChecks(dir, connection, harnesses, now);\n const keyFile = join(dir, \"attester.key\");\n if (!existsSync(keyFile)) return { ok: false, signature_verified: false, failed: [\"signing_key\"], checks };\n const day = new Date(now).toISOString().slice(0, 10);\n const { attester } = loadOrCreateAttester({ file: keyFile });\n const signature = await attester.sign(selfCheckProof(connection.credential_id, connection.gateway_id, day));\n try {\n const res = await (options.fetchImpl ?? fetch)(new URL(\"/v1/self-check\", connection.url).toString(), {\n method: \"POST\", redirect: \"error\", signal: AbortSignal.timeout(options.timeoutMs ?? 10_000),\n headers: { authorization: `Bearer ${connection.credential}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ signature, day, checks, hook_version: hookVersion(), agent_version: agentVersion }),\n });\n if (!res.ok) return { ok: false, signature_verified: false, failed: [`workspace_http_${res.status}`], checks };\n const body = await res.json() as { ok?: boolean; signature_verified?: boolean; failed?: string[] };\n return { ok: !!body.ok, signature_verified: !!body.signature_verified, failed: Array.isArray(body.failed) ? body.failed : [], checks };\n } catch (error) {\n return { ok: false, signature_verified: false, failed: [`workspace_unreachable: ${(error as Error).message.slice(0, 80)}`], checks };\n }\n}\n"]}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,22 +1,44 @@
|
|
|
1
1
|
import { type Harness } from "@scopebond/hook";
|
|
2
2
|
import { type CycleResult } from "./agent.js";
|
|
3
|
-
|
|
3
|
+
import { runSelfCheck } from "./selfcheck.js";
|
|
4
|
+
export declare const AGENT_VERSION: string;
|
|
5
|
+
export declare const AFTER_PID_ENV = "SCOPEBOND_AGENT_AFTER_PID";
|
|
4
6
|
export interface ServiceOptions {
|
|
5
7
|
dir: string;
|
|
6
8
|
intervalMs?: number;
|
|
7
9
|
fetchImpl?: typeof fetch;
|
|
8
10
|
log?: (line: string) => void;
|
|
11
|
+
/** Off in tests: the update check and self-check run only when asked. */
|
|
12
|
+
maintenance?: boolean;
|
|
13
|
+
/** Called after a successful update instead of restarting (tests). */
|
|
14
|
+
onUpdated?: (version: string) => void;
|
|
9
15
|
}
|
|
10
16
|
export interface Service {
|
|
11
17
|
stop(): Promise<void>;
|
|
12
18
|
cycleNow(): Promise<CycleResult>;
|
|
19
|
+
maintainNow(): Promise<MaintenanceResult>;
|
|
13
20
|
port: number;
|
|
14
21
|
}
|
|
22
|
+
export interface MaintenanceResult {
|
|
23
|
+
at: number;
|
|
24
|
+
policy: "recommended" | "hold" | "unknown";
|
|
25
|
+
updatedTo: string | null;
|
|
26
|
+
hookEntries: Array<{
|
|
27
|
+
harness: Harness;
|
|
28
|
+
file: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
}>;
|
|
31
|
+
selfCheck: Awaited<ReturnType<typeof runSelfCheck>>;
|
|
32
|
+
error: string | null;
|
|
33
|
+
}
|
|
15
34
|
/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is
|
|
16
35
|
* logged and returned, and only settings of agents this computer has are touched. */
|
|
17
36
|
export declare function repairHookEntries(harnesses?: Harness[]): Array<{
|
|
18
37
|
harness: Harness;
|
|
19
38
|
file: string;
|
|
20
39
|
}>;
|
|
40
|
+
/** Start a fresh agent (the updated one) that waits for this process to exit, through the
|
|
41
|
+
* autostart launcher when there is one so it finds Node the same way sign-in does. */
|
|
42
|
+
export declare function spawnReplacement(dir: string): void;
|
|
21
43
|
export declare function startService(options: ServiceOptions): Promise<Service>;
|
|
22
44
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiF,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9H,OAAO,EAAmE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAG/G,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,eAAO,MAAM,aAAa,QAA4B,CAAC;AAKvD,eAAO,MAAM,aAAa,8BAA8B,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzF,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACjC,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;IACpD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;sFACsF;AACtF,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,EAAwB,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAOvH;AAED;uFACuF;AACvF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CASlD;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA8F5E"}
|
package/dist/service.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
// The resident loop: one cycle now, then one every interval, sooner after a change is asked for,
|
|
2
2
|
// backing off while the workspace is unreachable. Exactly one agent serves a Scopebond home: a
|
|
3
|
-
// second one finds the first answering and exits.
|
|
4
|
-
|
|
3
|
+
// second one finds the first answering and exits. Every six hours it asks the workspace which
|
|
4
|
+
// versions to run, and once a day it runs the end-to-end self-check.
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { hookCommand, hookVersion, loadConnection, userHarnessFile, writeHarnessConfig } from "@scopebond/hook";
|
|
5
9
|
import { computerStatus, expectedHarnesses, missingHookEntries, runCycle } from "./agent.js";
|
|
10
|
+
import { launcherPath } from "./autostart.js";
|
|
6
11
|
import { callAgent, startControl } from "./ipc.js";
|
|
7
|
-
|
|
12
|
+
import { runSelfCheck } from "./selfcheck.js";
|
|
13
|
+
import { agentVersion, compareVersions, fetchClientVersion, installAgent, maintainHookEntries } from "./update.js";
|
|
14
|
+
export const AGENT_VERSION = `agent/${agentVersion()}`;
|
|
8
15
|
const INTERVAL_MS = 60_000;
|
|
9
16
|
const MAX_BACKOFF_MS = 15 * 60_000;
|
|
17
|
+
const UPDATE_EVERY_MS = 6 * 60 * 60 * 1000;
|
|
18
|
+
const SELF_CHECK_EVERY_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
export const AFTER_PID_ENV = "SCOPEBOND_AGENT_AFTER_PID";
|
|
10
20
|
/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is
|
|
11
21
|
* logged and returned, and only settings of agents this computer has are touched. */
|
|
12
22
|
export function repairHookEntries(harnesses = expectedHarnesses()) {
|
|
@@ -17,16 +27,31 @@ export function repairHookEntries(harnesses = expectedHarnesses()) {
|
|
|
17
27
|
}
|
|
18
28
|
return repaired;
|
|
19
29
|
}
|
|
30
|
+
/** Start a fresh agent (the updated one) that waits for this process to exit, through the
|
|
31
|
+
* autostart launcher when there is one so it finds Node the same way sign-in does. */
|
|
32
|
+
export function spawnReplacement(dir) {
|
|
33
|
+
const launcher = launcherPath(dir);
|
|
34
|
+
const cli = realpathSync(fileURLToPath(new URL("./cli.js", import.meta.url)));
|
|
35
|
+
const env = { ...process.env, [AFTER_PID_ENV]: String(process.pid) };
|
|
36
|
+
const [command, args] = existsSync(launcher)
|
|
37
|
+
? process.platform === "win32" ? ["cmd.exe", ["/d", "/c", launcher]] : ["/bin/sh", [launcher]]
|
|
38
|
+
: [process.execPath, [cli, "run"]];
|
|
39
|
+
const child = spawn(command, [...args], { env, detached: true, stdio: "ignore", windowsHide: true });
|
|
40
|
+
child.unref();
|
|
41
|
+
}
|
|
20
42
|
export async function startService(options) {
|
|
21
43
|
const log = options.log ?? ((line) => console.log(`${new Date().toISOString()} ${line}`));
|
|
22
44
|
if (await callAgent(options.dir, "GET", "/status", undefined, 2_000))
|
|
23
45
|
throw new Error("a Scopebond Agent is already running for this computer");
|
|
24
46
|
const interval = options.intervalMs ?? INTERVAL_MS;
|
|
25
47
|
let last = null;
|
|
48
|
+
let lastMaintenance = null;
|
|
26
49
|
let failures = 0;
|
|
27
50
|
let timer = null;
|
|
51
|
+
let maintenanceTimer = null;
|
|
28
52
|
let running = null;
|
|
29
53
|
let stopped = false;
|
|
54
|
+
let lastSelfCheckAt = 0;
|
|
30
55
|
const cycle = async () => {
|
|
31
56
|
if (running)
|
|
32
57
|
return running;
|
|
@@ -41,6 +66,50 @@ export async function startService(options) {
|
|
|
41
66
|
}).finally(() => { running = null; });
|
|
42
67
|
return running;
|
|
43
68
|
};
|
|
69
|
+
const maintain = async (forceSelfCheck = false) => {
|
|
70
|
+
const result = { at: Date.now(), policy: "unknown", updatedTo: null, hookEntries: [], selfCheck: null, error: null };
|
|
71
|
+
try {
|
|
72
|
+
const connection = loadConnection(options.dir);
|
|
73
|
+
const harnesses = expectedHarnesses();
|
|
74
|
+
const target = connection ? await fetchClientVersion(connection, options.fetchImpl) : null;
|
|
75
|
+
if (target)
|
|
76
|
+
result.policy = target.policy;
|
|
77
|
+
const current = agentVersion();
|
|
78
|
+
if (target?.policy === "recommended" && target.agent && compareVersions(target.agent, current) > 0) {
|
|
79
|
+
log(`updating the Scopebond Agent ${current} -> ${target.agent}`);
|
|
80
|
+
const installed = await installAgent(target.agent);
|
|
81
|
+
if (installed.ok) {
|
|
82
|
+
result.updatedTo = target.agent;
|
|
83
|
+
lastMaintenance = result;
|
|
84
|
+
if (options.onUpdated)
|
|
85
|
+
options.onUpdated(target.agent);
|
|
86
|
+
else {
|
|
87
|
+
spawnReplacement(options.dir);
|
|
88
|
+
setTimeout(() => { void stop().finally(() => process.exit(0)); }, 500);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;
|
|
93
|
+
log(result.error);
|
|
94
|
+
}
|
|
95
|
+
// Hook entries: under "recommended" they move to the newest hook named or carried; under "hold"
|
|
96
|
+
// only broken ones are repaired, at the version they already name or the one the agent carries.
|
|
97
|
+
const hookTarget = target?.policy === "recommended" && target.hook && compareVersions(target.hook, hookVersion()) > 0 ? target.hook : hookVersion();
|
|
98
|
+
result.hookEntries = maintainHookEntries(harnesses, hookTarget, target?.policy !== "hold");
|
|
99
|
+
for (const change of result.hookEntries)
|
|
100
|
+
log(`${change.reason}: ${change.file}`);
|
|
101
|
+
if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {
|
|
102
|
+
result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });
|
|
103
|
+
lastSelfCheckAt = Date.now();
|
|
104
|
+
log(`self-check ${result.selfCheck?.ok ? "passed" : `failed: ${result.selfCheck?.failed.join(", ")}`}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
result.error = error.message;
|
|
109
|
+
}
|
|
110
|
+
lastMaintenance = result;
|
|
111
|
+
return result;
|
|
112
|
+
};
|
|
44
113
|
const schedule = () => {
|
|
45
114
|
if (stopped)
|
|
46
115
|
return;
|
|
@@ -48,7 +117,7 @@ export async function startService(options) {
|
|
|
48
117
|
timer = setTimeout(() => { void cycle().finally(schedule); }, delay);
|
|
49
118
|
};
|
|
50
119
|
const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {
|
|
51
|
-
"GET /status": () => ({ ...computerStatus(options.dir), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last } }),
|
|
120
|
+
"GET /status": () => ({ ...computerStatus(options.dir), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last, last_maintenance: lastMaintenance } }),
|
|
52
121
|
"POST /flush": async () => ({ cycle: await cycle() }),
|
|
53
122
|
"POST /repair": () => {
|
|
54
123
|
const repaired = repairHookEntries();
|
|
@@ -56,15 +125,28 @@ export async function startService(options) {
|
|
|
56
125
|
log(`repaired the Scopebond hook entry in ${r.file}`);
|
|
57
126
|
return { repaired };
|
|
58
127
|
},
|
|
128
|
+
"POST /maintain": async () => ({ maintenance: await maintain(true) }),
|
|
59
129
|
});
|
|
60
|
-
|
|
130
|
+
const stop = async () => {
|
|
131
|
+
stopped = true;
|
|
132
|
+
if (timer)
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
if (maintenanceTimer)
|
|
135
|
+
clearInterval(maintenanceTimer);
|
|
136
|
+
await running?.catch(() => undefined);
|
|
137
|
+
await control.close();
|
|
138
|
+
};
|
|
139
|
+
log(`Scopebond Agent ${agentVersion()} running for ${options.dir} (control on 127.0.0.1:${control.endpoint.port})`);
|
|
61
140
|
await cycle();
|
|
62
141
|
schedule();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
142
|
+
if (options.maintenance !== false) {
|
|
143
|
+
// Shortly after start (so a fresh sign-in is not slowed), then every six hours.
|
|
144
|
+
setTimeout(() => { if (!stopped)
|
|
145
|
+
void maintain(); }, 2 * 60_000).unref?.();
|
|
146
|
+
maintenanceTimer = setInterval(() => { if (!stopped)
|
|
147
|
+
void maintain(); }, UPDATE_EVERY_MS);
|
|
148
|
+
maintenanceTimer.unref?.();
|
|
149
|
+
}
|
|
150
|
+
return { port: control.endpoint.port, cycleNow: cycle, maintainNow: () => maintain(true), stop };
|
|
69
151
|
}
|
|
70
152
|
//# sourceMappingURL=service.js.map
|
package/dist/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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","sourcesContent":["// The resident loop: one cycle now, then one every interval, sooner after a change is asked for,\n// backing off while the workspace is unreachable. Exactly one agent serves a Scopebond home: a\n// second one finds the first answering and exits.\n\nimport { userHarnessFile, writeHarnessConfig, hookCommand, hookVersion, type Harness } from \"@scopebond/hook\";\nimport { computerStatus, expectedHarnesses, missingHookEntries, runCycle, type CycleResult } from \"./agent.js\";\nimport { callAgent, startControl } from \"./ipc.js\";\n\nexport const AGENT_VERSION = \"agent/1\";\nconst INTERVAL_MS = 60_000;\nconst MAX_BACKOFF_MS = 15 * 60_000;\n\nexport interface ServiceOptions { dir: string; intervalMs?: number; fetchImpl?: typeof fetch; log?: (line: string) => void }\n\nexport interface Service { stop(): Promise<void>; cycleNow(): Promise<CycleResult>; port: number }\n\n/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is\n * logged and returned, and only settings of agents this computer has are touched. */\nexport function repairHookEntries(harnesses: Harness[] = expectedHarnesses()): Array<{ harness: Harness; file: string }> {\n const repaired: Array<{ harness: Harness; file: string }> = [];\n for (const harness of missingHookEntries(harnesses)) {\n const file = writeHarnessConfig(userHarnessFile(harness), harness, hookCommand(harness));\n repaired.push({ harness, file });\n }\n return repaired;\n}\n\nexport async function startService(options: ServiceOptions): Promise<Service> {\n const log = options.log ?? ((line: string) => console.log(`${new Date().toISOString()} ${line}`));\n if (await callAgent(options.dir, \"GET\", \"/status\", undefined, 2_000)) throw new Error(\"a Scopebond Agent is already running for this computer\");\n const interval = options.intervalMs ?? INTERVAL_MS;\n let last: CycleResult | null = null;\n let failures = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let running: Promise<CycleResult> | null = null;\n let stopped = false;\n\n const cycle = async (): Promise<CycleResult> => {\n if (running) return running;\n running = runCycle({ dir: options.dir, fetchImpl: options.fetchImpl }).then((result) => {\n last = result;\n failures = result.deliveryError ? failures + 1 : 0;\n if (result.delivered) log(`delivered ${result.delivered} record(s); ${result.pending} waiting`);\n if (result.deliveryError) log(`delivery problem: ${result.deliveryError}`);\n return result;\n }).finally(() => { running = null; });\n return running;\n };\n const schedule = () => {\n if (stopped) return;\n const delay = failures ? Math.min(MAX_BACKOFF_MS, interval * 2 ** Math.min(failures, 6)) : interval;\n timer = setTimeout(() => { void cycle().finally(schedule); }, delay);\n };\n\n const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {\n \"GET /status\": () => ({ ...computerStatus(options.dir), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last } }),\n \"POST /flush\": async () => ({ cycle: await cycle() }),\n \"POST /repair\": () => {\n const repaired = repairHookEntries();\n for (const r of repaired) log(`repaired the Scopebond hook entry in ${r.file}`);\n return { repaired };\n },\n });\n log(`Scopebond Agent running for ${options.dir} (control on 127.0.0.1:${control.endpoint.port})`);\n await cycle();\n schedule();\n return {\n port: control.endpoint.port,\n cycleNow: cycle,\n stop: async () => { stopped = true; if (timer) clearTimeout(timer); await running?.catch(() => undefined); await control.close(); },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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","sourcesContent":["// The resident loop: one cycle now, then one every interval, sooner after a change is asked for,\n// backing off while the workspace is unreachable. Exactly one agent serves a Scopebond home: a\n// second one finds the first answering and exits. Every six hours it asks the workspace which\n// versions to run, and once a day it runs the end-to-end self-check.\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { hookCommand, hookVersion, loadConnection, userHarnessFile, writeHarnessConfig, type Harness } from \"@scopebond/hook\";\nimport { computerStatus, expectedHarnesses, missingHookEntries, runCycle, type CycleResult } from \"./agent.js\";\nimport { launcherPath } from \"./autostart.js\";\nimport { callAgent, startControl } from \"./ipc.js\";\nimport { runSelfCheck } from \"./selfcheck.js\";\nimport { agentVersion, compareVersions, fetchClientVersion, installAgent, maintainHookEntries } from \"./update.js\";\n\nexport const AGENT_VERSION = `agent/${agentVersion()}`;\nconst INTERVAL_MS = 60_000;\nconst MAX_BACKOFF_MS = 15 * 60_000;\nconst UPDATE_EVERY_MS = 6 * 60 * 60 * 1000;\nconst SELF_CHECK_EVERY_MS = 24 * 60 * 60 * 1000;\nexport const AFTER_PID_ENV = \"SCOPEBOND_AGENT_AFTER_PID\";\n\nexport interface ServiceOptions {\n dir: string; intervalMs?: number; fetchImpl?: typeof fetch; log?: (line: string) => void;\n /** Off in tests: the update check and self-check run only when asked. */\n maintenance?: boolean;\n /** Called after a successful update instead of restarting (tests). */\n onUpdated?: (version: string) => void;\n}\n\nexport interface Service {\n stop(): Promise<void>;\n cycleNow(): Promise<CycleResult>;\n maintainNow(): Promise<MaintenanceResult>;\n port: number;\n}\n\nexport interface MaintenanceResult {\n at: number;\n policy: \"recommended\" | \"hold\" | \"unknown\";\n updatedTo: string | null;\n hookEntries: Array<{ harness: Harness; file: string; reason: string }>;\n selfCheck: Awaited<ReturnType<typeof runSelfCheck>>;\n error: string | null;\n}\n\n/** Put the Scopebond hook back into agent settings that lost it. A visible change: each one is\n * logged and returned, and only settings of agents this computer has are touched. */\nexport function repairHookEntries(harnesses: Harness[] = expectedHarnesses()): Array<{ harness: Harness; file: string }> {\n const repaired: Array<{ harness: Harness; file: string }> = [];\n for (const harness of missingHookEntries(harnesses)) {\n const file = writeHarnessConfig(userHarnessFile(harness), harness, hookCommand(harness));\n repaired.push({ harness, file });\n }\n return repaired;\n}\n\n/** Start a fresh agent (the updated one) that waits for this process to exit, through the\n * autostart launcher when there is one so it finds Node the same way sign-in does. */\nexport function spawnReplacement(dir: string): void {\n const launcher = launcherPath(dir);\n const cli = realpathSync(fileURLToPath(new URL(\"./cli.js\", import.meta.url)));\n const env = { ...process.env, [AFTER_PID_ENV]: String(process.pid) };\n const [command, args] = existsSync(launcher)\n ? process.platform === \"win32\" ? [\"cmd.exe\", [\"/d\", \"/c\", launcher]] as const : [\"/bin/sh\", [launcher]] as const\n : [process.execPath, [cli, \"run\"]] as const;\n const child = spawn(command, [...args], { env, detached: true, stdio: \"ignore\", windowsHide: true });\n child.unref();\n}\n\nexport async function startService(options: ServiceOptions): Promise<Service> {\n const log = options.log ?? ((line: string) => console.log(`${new Date().toISOString()} ${line}`));\n if (await callAgent(options.dir, \"GET\", \"/status\", undefined, 2_000)) throw new Error(\"a Scopebond Agent is already running for this computer\");\n const interval = options.intervalMs ?? INTERVAL_MS;\n let last: CycleResult | null = null;\n let lastMaintenance: MaintenanceResult | null = null;\n let failures = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let maintenanceTimer: ReturnType<typeof setInterval> | null = null;\n let running: Promise<CycleResult> | null = null;\n let stopped = false;\n let lastSelfCheckAt = 0;\n\n const cycle = async (): Promise<CycleResult> => {\n if (running) return running;\n running = runCycle({ dir: options.dir, fetchImpl: options.fetchImpl }).then((result) => {\n last = result;\n failures = result.deliveryError ? failures + 1 : 0;\n if (result.delivered) log(`delivered ${result.delivered} record(s); ${result.pending} waiting`);\n if (result.deliveryError) log(`delivery problem: ${result.deliveryError}`);\n return result;\n }).finally(() => { running = null; });\n return running;\n };\n\n const maintain = async (forceSelfCheck = false): Promise<MaintenanceResult> => {\n const result: MaintenanceResult = { at: Date.now(), policy: \"unknown\", updatedTo: null, hookEntries: [], selfCheck: null, error: null };\n try {\n const connection = loadConnection(options.dir);\n const harnesses = expectedHarnesses();\n const target = connection ? await fetchClientVersion(connection, options.fetchImpl) : null;\n if (target) result.policy = target.policy;\n const current = agentVersion();\n if (target?.policy === \"recommended\" && target.agent && compareVersions(target.agent, current) > 0) {\n log(`updating the Scopebond Agent ${current} -> ${target.agent}`);\n const installed = await installAgent(target.agent);\n if (installed.ok) {\n result.updatedTo = target.agent;\n lastMaintenance = result;\n if (options.onUpdated) options.onUpdated(target.agent);\n else { spawnReplacement(options.dir); setTimeout(() => { void stop().finally(() => process.exit(0)); }, 500); }\n return result;\n }\n result.error = `update to ${target.agent} failed: ${installed.output.slice(-300)}`;\n log(result.error);\n }\n // Hook entries: under \"recommended\" they move to the newest hook named or carried; under \"hold\"\n // only broken ones are repaired, at the version they already name or the one the agent carries.\n const hookTarget = target?.policy === \"recommended\" && target.hook && compareVersions(target.hook, hookVersion()) > 0 ? target.hook : hookVersion();\n result.hookEntries = maintainHookEntries(harnesses, hookTarget, target?.policy !== \"hold\");\n for (const change of result.hookEntries) log(`${change.reason}: ${change.file}`);\n if (connection && (forceSelfCheck || Date.now() - lastSelfCheckAt >= SELF_CHECK_EVERY_MS)) {\n result.selfCheck = await runSelfCheck(options.dir, connection, harnesses, current, { fetchImpl: options.fetchImpl });\n lastSelfCheckAt = Date.now();\n log(`self-check ${result.selfCheck?.ok ? \"passed\" : `failed: ${result.selfCheck?.failed.join(\", \")}`}`);\n }\n } catch (error) { result.error = (error as Error).message; }\n lastMaintenance = result;\n return result;\n };\n\n const schedule = () => {\n if (stopped) return;\n const delay = failures ? Math.min(MAX_BACKOFF_MS, interval * 2 ** Math.min(failures, 6)) : interval;\n timer = setTimeout(() => { void cycle().finally(schedule); }, delay);\n };\n\n const control = await startControl(options.dir, `${AGENT_VERSION} hook/${hookVersion()}`, {\n \"GET /status\": () => ({ ...computerStatus(options.dir), agent: { pid: process.pid, version: AGENT_VERSION, last_cycle: last, last_maintenance: lastMaintenance } }),\n \"POST /flush\": async () => ({ cycle: await cycle() }),\n \"POST /repair\": () => {\n const repaired = repairHookEntries();\n for (const r of repaired) log(`repaired the Scopebond hook entry in ${r.file}`);\n return { repaired };\n },\n \"POST /maintain\": async () => ({ maintenance: await maintain(true) }),\n });\n const stop = async () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n if (maintenanceTimer) clearInterval(maintenanceTimer);\n await running?.catch(() => undefined);\n await control.close();\n };\n log(`Scopebond Agent ${agentVersion()} running for ${options.dir} (control on 127.0.0.1:${control.endpoint.port})`);\n await cycle();\n schedule();\n if (options.maintenance !== false) {\n // Shortly after start (so a fresh sign-in is not slowed), then every six hours.\n setTimeout(() => { if (!stopped) void maintain(); }, 2 * 60_000).unref?.();\n maintenanceTimer = setInterval(() => { if (!stopped) void maintain(); }, UPDATE_EVERY_MS);\n maintenanceTimer.unref?.();\n }\n return { port: control.endpoint.port, cycleNow: cycle, maintainNow: () => maintain(true), stop };\n}\n"]}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Harness, type HookConnection } from "@scopebond/hook";
|
|
2
|
+
export declare function agentVersion(): string;
|
|
3
|
+
export interface ClientVersion {
|
|
4
|
+
policy: "recommended" | "hold";
|
|
5
|
+
hook: string | null;
|
|
6
|
+
agent: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchClientVersion(connection: Pick<HookConnection, "url" | "credential">, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<ClientVersion | null>;
|
|
9
|
+
/** Compare x.y.z versions: negative when a < b. */
|
|
10
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
11
|
+
/** The hook version a settings command runs, when it names one (an `npx` pin or a pinned copy). */
|
|
12
|
+
export declare function commandHookVersion(command: string): string | null;
|
|
13
|
+
/** The command an agent-maintained settings entry runs: the hook version this agent carries,
|
|
14
|
+
* through npx, so it resolves Node at run time and an agent update never swaps files under it. */
|
|
15
|
+
export declare function maintainedHookCommand(harness: Harness, version?: string): string;
|
|
16
|
+
/** Bring the user-level Scopebond hook entries to `version`, and repair any that cannot start.
|
|
17
|
+
* Entries of other tools are never touched. Returns the files changed. */
|
|
18
|
+
export declare function maintainHookEntries(harnesses: Harness[], version?: string, moveVersions?: boolean): Array<{
|
|
19
|
+
harness: Harness;
|
|
20
|
+
file: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** Install `@scopebond/agent@version` globally with this computer's npm. */
|
|
24
|
+
export declare function installAgent(version: string, timeoutMs?: number): Promise<{
|
|
25
|
+
ok: boolean;
|
|
26
|
+
output: string;
|
|
27
|
+
}>;
|
|
28
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,OAAO,EAAE,KAAK,cAAc,EAClC,MAAM,iBAAiB,CAAC;AAIzB,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,MAAM,WAAW,aAAa;IAAG,MAAM,EAAE,aAAa,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAE5G,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,YAAY,CAAC,EAAE,SAAS,GAAE,OAAO,KAAa,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAUnL;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5D;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AAED;mGACmG;AACnG,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,SAAgB,GAAG,MAAM,CAEvF;AAED;2EAC2E;AAC3E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,OAAO,SAAgB,EAAE,YAAY,UAAO,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAajK;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,SAAa,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAa9G"}
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Updates under the workspace's control (D130). The agent asks the workspace which versions this
|
|
2
|
+
// computer should run; with "recommended" it installs the named agent from npm (which brings the
|
|
3
|
+
// matching hook, pinned exactly) and restarts; with "hold" it changes nothing. The hook entries in
|
|
4
|
+
// the agent settings are moved to the hook version the agent carries.
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, userHarnessFile, writeHarnessConfig, } from "@scopebond/hook";
|
|
8
|
+
const VERSION = /^[0-9]+\.[0-9]+\.[0-9]+$/;
|
|
9
|
+
export function agentVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
12
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function fetchClientVersion(connection, fetchImpl = fetch, timeoutMs = 10_000) {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetchImpl(new URL("/v1/client-version", connection.url).toString(), {
|
|
21
|
+
headers: { authorization: `Bearer ${connection.credential}` }, redirect: "error", signal: AbortSignal.timeout(timeoutMs),
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
return null;
|
|
25
|
+
const body = await res.json();
|
|
26
|
+
const ok = (v) => typeof v === "string" && VERSION.test(v) ? v : null;
|
|
27
|
+
return { policy: body.policy === "hold" ? "hold" : "recommended", hook: ok(body.hook), agent: ok(body.agent) };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Compare x.y.z versions: negative when a < b. */
|
|
34
|
+
export function compareVersions(a, b) {
|
|
35
|
+
const pa = a.split(".").map(Number), pb = b.split(".").map(Number);
|
|
36
|
+
for (let i = 0; i < 3; i++)
|
|
37
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0))
|
|
38
|
+
return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
/** The hook version a settings command runs, when it names one (an `npx` pin or a pinned copy). */
|
|
42
|
+
export function commandHookVersion(command) {
|
|
43
|
+
return /@scopebond\/hook@([0-9]+\.[0-9]+\.[0-9]+)/.exec(command)?.[1]
|
|
44
|
+
?? /[\\/]runtime[\\/]([0-9]+\.[0-9]+\.[0-9]+)[\\/]/.exec(command)?.[1]
|
|
45
|
+
?? null;
|
|
46
|
+
}
|
|
47
|
+
/** The command an agent-maintained settings entry runs: the hook version this agent carries,
|
|
48
|
+
* through npx, so it resolves Node at run time and an agent update never swaps files under it. */
|
|
49
|
+
export function maintainedHookCommand(harness, version = hookVersion()) {
|
|
50
|
+
return `npx -y @scopebond/hook@${version} ${harness}`;
|
|
51
|
+
}
|
|
52
|
+
/** Bring the user-level Scopebond hook entries to `version`, and repair any that cannot start.
|
|
53
|
+
* Entries of other tools are never touched. Returns the files changed. */
|
|
54
|
+
export function maintainHookEntries(harnesses, version = hookVersion(), moveVersions = true) {
|
|
55
|
+
const changed = [];
|
|
56
|
+
for (const harness of harnesses) {
|
|
57
|
+
const file = userHarnessFile(harness);
|
|
58
|
+
const ours = configuredHookCommands(file).filter((c) => isScopebondHookCommand(c));
|
|
59
|
+
if (!ours.length)
|
|
60
|
+
continue;
|
|
61
|
+
const broken = ours.some((c) => !hookCommandResolves(c));
|
|
62
|
+
const behind = moveVersions && ours.some((c) => { const v = commandHookVersion(c); return v !== null && compareVersions(v, version) < 0; });
|
|
63
|
+
if (!broken && !behind)
|
|
64
|
+
continue;
|
|
65
|
+
writeHarnessConfig(file, harness, maintainedHookCommand(harness, version));
|
|
66
|
+
changed.push({ harness, file, reason: broken ? "the hook command could not start" : `moved to hook ${version}` });
|
|
67
|
+
}
|
|
68
|
+
return changed;
|
|
69
|
+
}
|
|
70
|
+
/** Install `@scopebond/agent@version` globally with this computer's npm. */
|
|
71
|
+
export function installAgent(version, timeoutMs = 5 * 60_000) {
|
|
72
|
+
if (!VERSION.test(version))
|
|
73
|
+
return Promise.resolve({ ok: false, output: "not a version" });
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const win = process.platform === "win32";
|
|
76
|
+
// npm on Windows is a .cmd file, which Node only starts through a shell; the arguments are fixed and the version is validated.
|
|
77
|
+
const child = spawn(win ? "npm.cmd" : "npm", ["install", "-g", `@scopebond/agent@${version}`, "--no-fund", "--no-audit"], { shell: win, windowsHide: true });
|
|
78
|
+
let output = "";
|
|
79
|
+
child.stdout?.on("data", (d) => { output += d; });
|
|
80
|
+
child.stderr?.on("data", (d) => { output += d; });
|
|
81
|
+
const timer = setTimeout(() => { child.kill(); resolve({ ok: false, output: output + "\ntimed out" }); }, timeoutMs);
|
|
82
|
+
child.on("error", (error) => { clearTimeout(timer); resolve({ ok: false, output: String(error) }); });
|
|
83
|
+
child.on("close", (code) => { clearTimeout(timer); resolve({ ok: code === 0, output: output.slice(-2000) }); });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"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","sourcesContent":["// Updates under the workspace's control (D130). The agent asks the workspace which versions this\n// computer should run; with \"recommended\" it installs the named agent from npm (which brings the\n// matching hook, pinned exactly) and restarts; with \"hold\" it changes nothing. The hook entries in\n// the agent settings are moved to the hook version the agent carries.\n\nimport { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport {\n configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, userHarnessFile, writeHarnessConfig,\n type Harness, type HookConnection,\n} from \"@scopebond/hook\";\n\nconst VERSION = /^[0-9]+\\.[0-9]+\\.[0-9]+$/;\n\nexport function agentVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\")) as { version?: string };\n return typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\n } catch { return \"0.0.0\"; }\n}\n\nexport interface ClientVersion { policy: \"recommended\" | \"hold\"; hook: string | null; agent: string | null }\n\nexport async function fetchClientVersion(connection: Pick<HookConnection, \"url\" | \"credential\">, fetchImpl: typeof fetch = fetch, timeoutMs = 10_000): Promise<ClientVersion | null> {\n try {\n const res = await fetchImpl(new URL(\"/v1/client-version\", connection.url).toString(), {\n headers: { authorization: `Bearer ${connection.credential}` }, redirect: \"error\", signal: AbortSignal.timeout(timeoutMs),\n });\n if (!res.ok) return null;\n const body = await res.json() as Partial<ClientVersion>;\n const ok = (v: unknown) => typeof v === \"string\" && VERSION.test(v) ? v : null;\n return { policy: body.policy === \"hold\" ? \"hold\" : \"recommended\", hook: ok(body.hook), agent: ok(body.agent) };\n } catch { return null; }\n}\n\n/** Compare x.y.z versions: negative when a < b. */\nexport function compareVersions(a: string, b: string): number {\n const pa = a.split(\".\").map(Number), pb = b.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);\n return 0;\n}\n\n/** The hook version a settings command runs, when it names one (an `npx` pin or a pinned copy). */\nexport function commandHookVersion(command: string): string | null {\n return /@scopebond\\/hook@([0-9]+\\.[0-9]+\\.[0-9]+)/.exec(command)?.[1]\n ?? /[\\\\/]runtime[\\\\/]([0-9]+\\.[0-9]+\\.[0-9]+)[\\\\/]/.exec(command)?.[1]\n ?? null;\n}\n\n/** The command an agent-maintained settings entry runs: the hook version this agent carries,\n * through npx, so it resolves Node at run time and an agent update never swaps files under it. */\nexport function maintainedHookCommand(harness: Harness, version = hookVersion()): string {\n return `npx -y @scopebond/hook@${version} ${harness}`;\n}\n\n/** Bring the user-level Scopebond hook entries to `version`, and repair any that cannot start.\n * Entries of other tools are never touched. Returns the files changed. */\nexport function maintainHookEntries(harnesses: Harness[], version = hookVersion(), moveVersions = true): Array<{ harness: Harness; file: string; reason: string }> {\n const changed: Array<{ harness: Harness; file: string; reason: string }> = [];\n for (const harness of harnesses) {\n const file = userHarnessFile(harness);\n const ours = configuredHookCommands(file).filter((c) => isScopebondHookCommand(c));\n if (!ours.length) continue;\n const broken = ours.some((c) => !hookCommandResolves(c));\n const behind = moveVersions && ours.some((c) => { const v = commandHookVersion(c); return v !== null && compareVersions(v, version) < 0; });\n if (!broken && !behind) continue;\n writeHarnessConfig(file, harness, maintainedHookCommand(harness, version));\n changed.push({ harness, file, reason: broken ? \"the hook command could not start\" : `moved to hook ${version}` });\n }\n return changed;\n}\n\n/** Install `@scopebond/agent@version` globally with this computer's npm. */\nexport function installAgent(version: string, timeoutMs = 5 * 60_000): Promise<{ ok: boolean; output: string }> {\n if (!VERSION.test(version)) return Promise.resolve({ ok: false, output: \"not a version\" });\n return new Promise((resolve) => {\n const win = process.platform === \"win32\";\n // npm on Windows is a .cmd file, which Node only starts through a shell; the arguments are fixed and the version is validated.\n const child = spawn(win ? \"npm.cmd\" : \"npm\", [\"install\", \"-g\", `@scopebond/agent@${version}`, \"--no-fund\", \"--no-audit\"], { shell: win, windowsHide: true });\n let output = \"\";\n child.stdout?.on(\"data\", (d) => { output += d; });\n child.stderr?.on(\"data\", (d) => { output += d; });\n const timer = setTimeout(() => { child.kill(); resolve({ ok: false, output: output + \"\\ntimed out\" }); }, timeoutMs);\n child.on(\"error\", (error) => { clearTimeout(timer); resolve({ ok: false, output: String(error) }); });\n child.on(\"close\", (code) => { clearTimeout(timer); resolve({ ok: code === 0, output: output.slice(-2000) }); });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scopebond/agent",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The Scopebond Agent — one resident process per user that delivers this computer's signed records to its Scopebond workspace, keeps its rules and connection current, and reports its health, so nobody has to run a command to keep it working.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Avouro LLC",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"dist"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@scopebond/gateway": "
|
|
43
|
-
"@scopebond/
|
|
44
|
-
"@scopebond/
|
|
42
|
+
"@scopebond/gateway": "0.12.0",
|
|
43
|
+
"@scopebond/hook": "0.15.1",
|
|
44
|
+
"@scopebond/sdk": "0.1.3"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|