@scopebond/agent 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/setup.js ADDED
@@ -0,0 +1,182 @@
1
+ // `scopebond-agent setup <workspace-url>`: one command from nothing to a connected computer.
2
+ // It checks Node, signs this person in with a code (which puts the hook in the user-level agent
3
+ // settings), installs the agent for this user so autostart has a stable path, turns autostart on
4
+ // and ends with `status`. Run again, it repairs what is missing and duplicates nothing: an
5
+ // existing connection to the same workspace is kept, the same agent version is not reinstalled,
6
+ // and the hook entry and autostart are put back only where they are gone.
7
+ import { spawn, spawnSync } from "node:child_process";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { createRequire } from "node:module";
10
+ import { homedir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { loadConnection, nodeTooOldLines, readDeliveryState } from "@scopebond/hook";
13
+ import { autostartHealth, enableAutostart, startNow } from "./autostart.js";
14
+ import { callAgent } from "./ipc.js";
15
+ import { compareVersions, ownNpm } from "./update.js";
16
+ import { repairHookEntries } from "./service.js";
17
+ /** What a setup run has to do, given what is already true. Pure, so a second run is provably a no-op
18
+ * (or a repair) rather than a second sign-in or install. */
19
+ export function setupPlan(state, origin, version, relogin = false) {
20
+ const steps = [];
21
+ if (relogin || state.connectedTo !== origin || state.credentialRefused)
22
+ steps.push("login");
23
+ const install = state.installedVersion === null || compareVersions(state.installedVersion, version) < 0;
24
+ if (install)
25
+ steps.push("install_agent");
26
+ if (install || !state.autostartOk)
27
+ steps.push("autostart");
28
+ return steps;
29
+ }
30
+ export function nodeSupported(version = process.versions.node) {
31
+ const [major, minor] = version.split(".").map(Number);
32
+ return major > 22 || (major === 22 && minor >= 13);
33
+ }
34
+ /** The folder npm puts global commands in: the prefix itself on Windows, <prefix>/bin elsewhere. */
35
+ export function globalBinDir(prefix, platform = process.platform) {
36
+ return platform === "win32" ? prefix : join(prefix, "bin");
37
+ }
38
+ /** Whether `dir` is on PATH (case-insensitive on Windows, ignoring a trailing separator). */
39
+ export function onPath(dir, pathEnv = process.env.PATH ?? process.env.Path ?? "", platform = process.platform) {
40
+ const norm = (p) => {
41
+ let t = p.trim();
42
+ while (t.endsWith("/") || t.endsWith("\\"))
43
+ t = t.slice(0, -1); // a loop, not a regex: PATH entries are untrusted input
44
+ return platform === "win32" ? t.toLowerCase().replace(/\//g, "\\") : t;
45
+ };
46
+ const target = norm(dir);
47
+ return pathEnv.split(platform === "win32" ? ";" : ":").some((p) => p && norm(p) === target);
48
+ }
49
+ /** The PowerShell line that adds a folder to this user's PATH for new windows. */
50
+ export function addToPathCommand(dir) {
51
+ return `[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";${dir}", "User")`;
52
+ }
53
+ function npm(args) {
54
+ const win = process.platform === "win32";
55
+ // This Node's own npm, without a shell; otherwise npm on PATH (a .cmd on Windows, started through a shell, each argument quoted).
56
+ const own = ownNpm();
57
+ const r = own
58
+ ? spawnSync(own[0], [...own[1], ...args], { encoding: "utf8", windowsHide: true, timeout: 5 * 60_000 })
59
+ : spawnSync(win ? "npm.cmd" : "npm", win ? args.map((a) => `"${a}"`) : args, { encoding: "utf8", shell: win, windowsHide: true, timeout: 5 * 60_000 });
60
+ return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}` };
61
+ }
62
+ function globalAgent() {
63
+ const prefix = npm(["prefix", "-g"]);
64
+ const root = npm(["root", "-g"]);
65
+ if (!root.ok)
66
+ return { prefix: null, cli: null, version: null };
67
+ const dir = join(root.out.trim().split(/\r?\n/).pop() ?? "", "@scopebond", "agent");
68
+ const cli = join(dir, "dist", "cli.js");
69
+ let version = null;
70
+ try {
71
+ version = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).version ?? null;
72
+ }
73
+ catch { /* not installed */ }
74
+ return { prefix: prefix.ok ? prefix.out.trim().split(/\r?\n/).pop() ?? null : null, cli: existsSync(cli) ? cli : null, version };
75
+ }
76
+ /** The bundled hook's CLI, which this agent depends on at an exact version. */
77
+ function hookCli() {
78
+ const index = createRequire(import.meta.url).resolve("@scopebond/hook");
79
+ return join(dirname(index), "cli.js");
80
+ }
81
+ export async function runSetup(o) {
82
+ const say = (line) => console.log(line);
83
+ if (!nodeSupported()) {
84
+ for (const line of nodeTooOldLines(process.versions.node))
85
+ console.error(line);
86
+ return 1;
87
+ }
88
+ const connection = loadConnection(o.dir);
89
+ let connectedTo = null;
90
+ try {
91
+ connectedTo = connection ? new URL(connection.url).origin : null;
92
+ }
93
+ catch {
94
+ connectedTo = null;
95
+ }
96
+ const global = globalAgent();
97
+ const state = {
98
+ connectedTo,
99
+ credentialRefused: readDeliveryState(o.dir).invalid_since != null,
100
+ installedVersion: global.version,
101
+ autostartOk: autostartHealth(o.dir).ok,
102
+ };
103
+ const steps = setupPlan(state, o.origin, o.version, o.relogin);
104
+ say(`Setting up Scopebond for this user (${steps.length ? steps.join(", ").replace(/_/g, " ") : "nothing missing"}).`);
105
+ // 1. Sign in with a code. The hook's own login prints the link and code and waits for approval.
106
+ if (steps.includes("login")) {
107
+ const flag = o.harness === "claude" ? "--claude" : `--${o.harness}`;
108
+ // From the home folder, so a project's own setup in the current folder can never take the sign-in;
109
+ // and a failed sign-in names this setup command, which also installs and starts the agent.
110
+ const again = `${process.platform === "win32" ? "npx.cmd" : "npx"} -y @scopebond/agent@${o.version} setup ${o.origin}${o.harness === "claude" ? "" : ` --${o.harness}`}`;
111
+ const r = spawnSync(process.execPath, [hookCli(), "login", o.origin, flag], { stdio: "inherit", cwd: homedir(), env: { ...process.env, SCOPEBOND_HOME: o.dir, SCOPEBOND_RETRY_COMMAND: again } });
112
+ if (r.status !== 0) {
113
+ console.error("Setup stopped: the sign-in did not finish. Run the same command again for a new code.");
114
+ return 1;
115
+ }
116
+ }
117
+ else {
118
+ say(`✓ Already connected to ${o.origin} (to sign in again anyway, add --relogin).`);
119
+ for (const fixed of repairHookEntries())
120
+ say(`✓ Put the Scopebond hook back in ${fixed.file}`);
121
+ }
122
+ // 2. The agent, installed for this user so autostart points at a path npm will not clear.
123
+ let cli = global.cli;
124
+ if (steps.includes("install_agent")) {
125
+ const spec = process.env.SCOPEBOND_AGENT_INSTALL_SPEC || `@scopebond/agent@${o.version}`;
126
+ say(`Installing the Scopebond Agent for this user: ${process.platform === "win32" ? "npm.cmd" : "npm"} install -g ${spec}`);
127
+ const installed = npm(["install", "-g", spec, "--no-fund", "--no-audit"]);
128
+ if (!installed.ok) {
129
+ console.error(`Setup stopped: npm could not install the agent.\n${installed.out.slice(-1500)}`);
130
+ return 1;
131
+ }
132
+ cli = globalAgent().cli;
133
+ if (!cli) {
134
+ console.error("Setup stopped: npm reported success but the agent is not in npm's global folder.");
135
+ return 1;
136
+ }
137
+ }
138
+ else
139
+ say(`✓ The Scopebond Agent ${global.version} is installed for this user.`);
140
+ const bin = global.prefix ? globalBinDir(global.prefix) : null;
141
+ if (bin && !onPath(bin)) {
142
+ say(`Note: npm's global folder ${bin} is not on PATH, so new terminals will not find ${o.me}.`);
143
+ say(process.platform === "win32"
144
+ ? ` Add it for your user in PowerShell, then open a new window: ${addToPathCommand(bin)}
145
+ (or: Start, "Edit environment variables for your account", Path, New)`
146
+ : ` Add it to PATH in your shell profile: export PATH="${bin}:$PATH"`);
147
+ }
148
+ // 3. Autostart, and the agent running now.
149
+ if (steps.includes("autostart") && cli) {
150
+ say(enableAutostart(o.dir, cli));
151
+ }
152
+ else
153
+ say("✓ The agent already starts with your sign-in.");
154
+ if (cli && !await callAgent(o.dir, "GET", "/status", undefined, 2_000)) {
155
+ const up = async (tries) => {
156
+ for (let i = 0; i < tries; i++) {
157
+ if (await callAgent(o.dir, "GET", "/status", undefined, 1_000))
158
+ return true;
159
+ await new Promise((r) => setTimeout(r, 500));
160
+ }
161
+ return false;
162
+ };
163
+ if (process.platform === "win32") {
164
+ // The headless console first; where it does not start, the launcher through a hidden cmd.exe.
165
+ if (!(startNow(o.dir, "win32", 0) && await up(12))) {
166
+ startNow(o.dir, "win32", 1);
167
+ await up(18);
168
+ }
169
+ }
170
+ else {
171
+ // No systemd user session (a container, WSL without systemd): run it detached until the next sign-in.
172
+ if (!await up(10) && process.platform === "linux")
173
+ spawn(process.execPath, [cli, "run"], { detached: true, stdio: "ignore", env: { ...process.env, SCOPEBOND_HOME: o.dir } }).unref();
174
+ await up(20);
175
+ }
176
+ }
177
+ // 4. Status, from the installed agent.
178
+ say("");
179
+ const status = spawnSync(process.execPath, [cli ?? process.argv[1], "status"], { stdio: "inherit", env: { ...process.env, SCOPEBOND_HOME: o.dir } });
180
+ return status.status === 0 ? 0 : 1;
181
+ }
182
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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","sourcesContent":["// `scopebond-agent setup <workspace-url>`: one command from nothing to a connected computer.\n// It checks Node, signs this person in with a code (which puts the hook in the user-level agent\n// settings), installs the agent for this user so autostart has a stable path, turns autostart on\n// and ends with `status`. Run again, it repairs what is missing and duplicates nothing: an\n// existing connection to the same workspace is kept, the same agent version is not reinstalled,\n// and the hook entry and autostart are put back only where they are gone.\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { loadConnection, nodeTooOldLines, readDeliveryState, type Harness } from \"@scopebond/hook\";\nimport { autostartHealth, enableAutostart, startNow } from \"./autostart.js\";\nimport { callAgent } from \"./ipc.js\";\nimport { compareVersions, ownNpm } from \"./update.js\";\nimport { repairHookEntries } from \"./service.js\";\n\nexport type SetupStep = \"login\" | \"install_agent\" | \"autostart\";\n\nexport interface SetupState {\n /** The workspace origin this computer's user-level connection points at, if any. */\n connectedTo: string | null;\n /** The workspace refused that connection's credential (it must sign in again). */\n credentialRefused: boolean;\n /** The agent version installed for this user with npm, if any. */\n installedVersion: string | null;\n /** Autostart is on and its launcher exists. */\n autostartOk: boolean;\n}\n\n/** What a setup run has to do, given what is already true. Pure, so a second run is provably a no-op\n * (or a repair) rather than a second sign-in or install. */\nexport function setupPlan(state: SetupState, origin: string, version: string, relogin = false): SetupStep[] {\n const steps: SetupStep[] = [];\n if (relogin || state.connectedTo !== origin || state.credentialRefused) steps.push(\"login\");\n const install = state.installedVersion === null || compareVersions(state.installedVersion, version) < 0;\n if (install) steps.push(\"install_agent\");\n if (install || !state.autostartOk) steps.push(\"autostart\");\n return steps;\n}\n\nexport function nodeSupported(version = process.versions.node): boolean {\n const [major, minor] = version.split(\".\").map(Number);\n return major > 22 || (major === 22 && minor >= 13);\n}\n\n/** The folder npm puts global commands in: the prefix itself on Windows, <prefix>/bin elsewhere. */\nexport function globalBinDir(prefix: string, platform: NodeJS.Platform = process.platform): string {\n return platform === \"win32\" ? prefix : join(prefix, \"bin\");\n}\n\n/** Whether `dir` is on PATH (case-insensitive on Windows, ignoring a trailing separator). */\nexport function onPath(dir: string, pathEnv = process.env.PATH ?? process.env.Path ?? \"\", platform: NodeJS.Platform = process.platform): boolean {\n const norm = (p: string) => {\n let t = p.trim();\n while (t.endsWith(\"/\") || t.endsWith(\"\\\\\")) t = t.slice(0, -1); // a loop, not a regex: PATH entries are untrusted input\n return platform === \"win32\" ? t.toLowerCase().replace(/\\//g, \"\\\\\") : t;\n };\n const target = norm(dir);\n return pathEnv.split(platform === \"win32\" ? \";\" : \":\").some((p) => p && norm(p) === target);\n}\n\n/** The PowerShell line that adds a folder to this user's PATH for new windows. */\nexport function addToPathCommand(dir: string): string {\n return `[Environment]::SetEnvironmentVariable(\"Path\", [Environment]::GetEnvironmentVariable(\"Path\", \"User\") + \";${dir}\", \"User\")`;\n}\n\nfunction npm(args: string[]): { ok: boolean; out: string } {\n const win = process.platform === \"win32\";\n // This Node's own npm, without a shell; otherwise npm on PATH (a .cmd on Windows, started through a shell, each argument quoted).\n const own = ownNpm();\n const r = own\n ? spawnSync(own[0], [...own[1], ...args], { encoding: \"utf8\", windowsHide: true, timeout: 5 * 60_000 })\n : spawnSync(win ? \"npm.cmd\" : \"npm\", win ? args.map((a) => `\"${a}\"`) : args, { encoding: \"utf8\", shell: win, windowsHide: true, timeout: 5 * 60_000 });\n return { ok: r.status === 0, out: `${r.stdout ?? \"\"}${r.stderr ?? \"\"}` };\n}\n\nfunction globalAgent(): { prefix: string | null; cli: string | null; version: string | null } {\n const prefix = npm([\"prefix\", \"-g\"]);\n const root = npm([\"root\", \"-g\"]);\n if (!root.ok) return { prefix: null, cli: null, version: null };\n const dir = join(root.out.trim().split(/\\r?\\n/).pop() ?? \"\", \"@scopebond\", \"agent\");\n const cli = join(dir, \"dist\", \"cli.js\");\n let version: string | null = null;\n try { version = (JSON.parse(readFileSync(join(dir, \"package.json\"), \"utf8\")) as { version?: string }).version ?? null; } catch { /* not installed */ }\n return { prefix: prefix.ok ? prefix.out.trim().split(/\\r?\\n/).pop() ?? null : null, cli: existsSync(cli) ? cli : null, version };\n}\n\n/** The bundled hook's CLI, which this agent depends on at an exact version. */\nfunction hookCli(): string {\n const index = createRequire(import.meta.url).resolve(\"@scopebond/hook\");\n return join(dirname(index), \"cli.js\");\n}\n\nexport interface SetupOptions { dir: string; origin: string; harness: Harness; relogin: boolean; version: string; me: string }\n\nexport async function runSetup(o: SetupOptions): Promise<number> {\n const say = (line: string) => console.log(line);\n if (!nodeSupported()) { for (const line of nodeTooOldLines(process.versions.node)) console.error(line); return 1; }\n const connection = loadConnection(o.dir);\n let connectedTo: string | null = null;\n try { connectedTo = connection ? new URL(connection.url).origin : null; } catch { connectedTo = null; }\n const global = globalAgent();\n const state: SetupState = {\n connectedTo,\n credentialRefused: readDeliveryState(o.dir).invalid_since != null,\n installedVersion: global.version,\n autostartOk: autostartHealth(o.dir).ok,\n };\n const steps = setupPlan(state, o.origin, o.version, o.relogin);\n say(`Setting up Scopebond for this user (${steps.length ? steps.join(\", \").replace(/_/g, \" \") : \"nothing missing\"}).`);\n\n // 1. Sign in with a code. The hook's own login prints the link and code and waits for approval.\n if (steps.includes(\"login\")) {\n const flag = o.harness === \"claude\" ? \"--claude\" : `--${o.harness}`;\n // From the home folder, so a project's own setup in the current folder can never take the sign-in;\n // and a failed sign-in names this setup command, which also installs and starts the agent.\n const again = `${process.platform === \"win32\" ? \"npx.cmd\" : \"npx\"} -y @scopebond/agent@${o.version} setup ${o.origin}${o.harness === \"claude\" ? \"\" : ` --${o.harness}`}`;\n const r = spawnSync(process.execPath, [hookCli(), \"login\", o.origin, flag], { stdio: \"inherit\", cwd: homedir(), env: { ...process.env, SCOPEBOND_HOME: o.dir, SCOPEBOND_RETRY_COMMAND: again } });\n if (r.status !== 0) { console.error(\"Setup stopped: the sign-in did not finish. Run the same command again for a new code.\"); return 1; }\n } else {\n say(`✓ Already connected to ${o.origin} (to sign in again anyway, add --relogin).`);\n for (const fixed of repairHookEntries()) say(`✓ Put the Scopebond hook back in ${fixed.file}`);\n }\n\n // 2. The agent, installed for this user so autostart points at a path npm will not clear.\n let cli = global.cli;\n if (steps.includes(\"install_agent\")) {\n const spec = process.env.SCOPEBOND_AGENT_INSTALL_SPEC || `@scopebond/agent@${o.version}`;\n say(`Installing the Scopebond Agent for this user: ${process.platform === \"win32\" ? \"npm.cmd\" : \"npm\"} install -g ${spec}`);\n const installed = npm([\"install\", \"-g\", spec, \"--no-fund\", \"--no-audit\"]);\n if (!installed.ok) { console.error(`Setup stopped: npm could not install the agent.\\n${installed.out.slice(-1500)}`); return 1; }\n cli = globalAgent().cli;\n if (!cli) { console.error(\"Setup stopped: npm reported success but the agent is not in npm's global folder.\"); return 1; }\n } else say(`✓ The Scopebond Agent ${global.version} is installed for this user.`);\n const bin = global.prefix ? globalBinDir(global.prefix) : null;\n if (bin && !onPath(bin)) {\n say(`Note: npm's global folder ${bin} is not on PATH, so new terminals will not find ${o.me}.`);\n say(process.platform === \"win32\"\n ? ` Add it for your user in PowerShell, then open a new window: ${addToPathCommand(bin)}\n (or: Start, \"Edit environment variables for your account\", Path, New)`\n : ` Add it to PATH in your shell profile: export PATH=\"${bin}:$PATH\"`);\n }\n\n // 3. Autostart, and the agent running now.\n if (steps.includes(\"autostart\") && cli) {\n say(enableAutostart(o.dir, cli));\n } else say(\"✓ The agent already starts with your sign-in.\");\n if (cli && !await callAgent(o.dir, \"GET\", \"/status\", undefined, 2_000)) {\n const up = async (tries: number) => {\n for (let i = 0; i < tries; i++) { if (await callAgent(o.dir, \"GET\", \"/status\", undefined, 1_000)) return true; await new Promise((r) => setTimeout(r, 500)); }\n return false;\n };\n if (process.platform === \"win32\") {\n // The headless console first; where it does not start, the launcher through a hidden cmd.exe.\n if (!(startNow(o.dir, \"win32\", 0) && await up(12))) { startNow(o.dir, \"win32\", 1); await up(18); }\n } else {\n // No systemd user session (a container, WSL without systemd): run it detached until the next sign-in.\n if (!await up(10) && process.platform === \"linux\") spawn(process.execPath, [cli, \"run\"], { detached: true, stdio: \"ignore\", env: { ...process.env, SCOPEBOND_HOME: o.dir } }).unref();\n await up(20);\n }\n }\n\n // 4. Status, from the installed agent.\n say(\"\");\n const status = spawnSync(process.execPath, [cli ?? process.argv[1], \"status\"], { stdio: \"inherit\", env: { ...process.env, SCOPEBOND_HOME: o.dir } });\n return status.status === 0 ? 0 : 1;\n}\n"]}
package/dist/update.d.ts CHANGED
@@ -12,7 +12,12 @@ export declare function compareVersions(a: string, b: string): number;
12
12
  export declare function commandHookVersion(command: string): string | null;
13
13
  /** The command an agent-maintained settings entry runs: the hook version this agent carries,
14
14
  * through npx, so it resolves Node at run time and an agent update never swaps files under it. */
15
+ /** The hook command upkeep writes. The hook this agent carries is pinned by its absolute path (fast,
16
+ * and it needs neither the registry nor npx on the coding tool's PATH); another version, or a pin that
17
+ * cannot be made, falls back to the portable npx form. */
15
18
  export declare function maintainedHookCommand(harness: Harness, version?: string): string;
19
+ /** This Node's own npm, run directly (no shell, and never another Node's npm first on PATH), or null. */
20
+ export declare function ownNpm(): [string, string[]] | null;
16
21
  /** Bring the user-level Scopebond hook entries to `version`, and repair any that cannot start.
17
22
  * Entries of other tools are never touched. Returns the files changed. */
18
23
  export declare function maintainHookEntries(harnesses: Harness[], version?: string, moveVersions?: boolean): Array<{
@@ -1 +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"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,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;;2DAE2D;AAC3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,SAAgB,GAAG,MAAM,CASvF;AAED,yGAAyG;AACzG,wBAAgB,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAGlD;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,CAgB9G"}
package/dist/update.js CHANGED
@@ -3,8 +3,10 @@
3
3
  // matching hook, pinned exactly) and restarts; with "hold" it changes nothing. The hook entries in
4
4
  // the agent settings are moved to the hook version the agent carries.
5
5
  import { spawn } from "node:child_process";
6
- import { readFileSync } from "node:fs";
7
- import { configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, userHarnessFile, writeHarnessConfig, } from "@scopebond/hook";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import { dirname, join } from "node:path";
9
+ import { absoluteHookCommand, ensureDurableRuntime, configuredHookCommands, hookCommandResolves, hookVersion, isScopebondHookCommand, userHarnessFile, writeHarnessConfig, } from "@scopebond/hook";
8
10
  const VERSION = /^[0-9]+\.[0-9]+\.[0-9]+$/;
9
11
  export function agentVersion() {
10
12
  try {
@@ -46,9 +48,26 @@ export function commandHookVersion(command) {
46
48
  }
47
49
  /** The command an agent-maintained settings entry runs: the hook version this agent carries,
48
50
  * through npx, so it resolves Node at run time and an agent update never swaps files under it. */
51
+ /** The hook command upkeep writes. The hook this agent carries is pinned by its absolute path (fast,
52
+ * and it needs neither the registry nor npx on the coding tool's PATH); another version, or a pin that
53
+ * cannot be made, falls back to the portable npx form. */
49
54
  export function maintainedHookCommand(harness, version = hookVersion()) {
55
+ if (version === hookVersion()) {
56
+ try {
57
+ const cli = join(dirname(createRequire(import.meta.url).resolve("@scopebond/hook")), "cli.js");
58
+ const pin = ensureDurableRuntime(cli, version);
59
+ if (pin.cli)
60
+ return absoluteHookCommand(pin.cli, harness);
61
+ }
62
+ catch { /* fall back below */ }
63
+ }
50
64
  return `npx -y @scopebond/hook@${version} ${harness}`;
51
65
  }
66
+ /** This Node's own npm, run directly (no shell, and never another Node's npm first on PATH), or null. */
67
+ export function ownNpm() {
68
+ const cli = join(dirname(process.execPath), process.platform === "win32" ? "" : "../lib", "node_modules", "npm", "bin", "npm-cli.js");
69
+ return existsSync(cli) ? [process.execPath, [cli]] : null;
70
+ }
52
71
  /** Bring the user-level Scopebond hook entries to `version`, and repair any that cannot start.
53
72
  * Entries of other tools are never touched. Returns the files changed. */
54
73
  export function maintainHookEntries(harnesses, version = hookVersion(), moveVersions = true) {
@@ -73,8 +92,11 @@ export function installAgent(version, timeoutMs = 5 * 60_000) {
73
92
  return Promise.resolve({ ok: false, output: "not a version" });
74
93
  return new Promise((resolve) => {
75
94
  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 });
95
+ const args = ["install", "-g", `@scopebond/agent@${version}`, "--no-fund", "--no-audit"];
96
+ // This Node's own npm when it can be found; otherwise npm on PATH (a .cmd on Windows, which Node only
97
+ // starts through a shell; the arguments are fixed and the version is validated).
98
+ const own = ownNpm();
99
+ const child = own ? spawn(own[0], [...own[1], ...args], { windowsHide: true }) : spawn(win ? "npm.cmd" : "npm", args, { shell: win, windowsHide: true });
78
100
  let output = "";
79
101
  child.stdout?.on("data", (d) => { output += d; });
80
102
  child.stderr?.on("data", (d) => { output += d; });
@@ -1 +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"]}
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 { existsSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport {\n absoluteHookCommand, ensureDurableRuntime,\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. */\n/** The hook command upkeep writes. The hook this agent carries is pinned by its absolute path (fast,\n * and it needs neither the registry nor npx on the coding tool's PATH); another version, or a pin that\n * cannot be made, falls back to the portable npx form. */\nexport function maintainedHookCommand(harness: Harness, version = hookVersion()): string {\n if (version === hookVersion()) {\n try {\n const cli = join(dirname(createRequire(import.meta.url).resolve(\"@scopebond/hook\")), \"cli.js\");\n const pin = ensureDurableRuntime(cli, version);\n if (pin.cli) return absoluteHookCommand(pin.cli, harness);\n } catch { /* fall back below */ }\n }\n return `npx -y @scopebond/hook@${version} ${harness}`;\n}\n\n/** This Node's own npm, run directly (no shell, and never another Node's npm first on PATH), or null. */\nexport function ownNpm(): [string, string[]] | null {\n const cli = join(dirname(process.execPath), process.platform === \"win32\" ? \"\" : \"../lib\", \"node_modules\", \"npm\", \"bin\", \"npm-cli.js\");\n return existsSync(cli) ? [process.execPath, [cli]] : null;\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 const args = [\"install\", \"-g\", `@scopebond/agent@${version}`, \"--no-fund\", \"--no-audit\"];\n // This Node's own npm when it can be found; otherwise npm on PATH (a .cmd on Windows, which Node only\n // starts through a shell; the arguments are fixed and the version is validated).\n const own = ownNpm();\n const child = own ? spawn(own[0], [...own[1], ...args], { windowsHide: true }) : spawn(win ? \"npm.cmd\" : \"npm\", args, { 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,7 +1,7 @@
1
1
  {
2
2
  "name": "@scopebond/agent",
3
- "version": "0.3.1",
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.",
3
+ "version": "0.4.0",
4
+ "description": "Free, open-source Scopebond Agent companion: deliver queued records, maintain rules and connections, and report computer health to the separate proprietary Scopebond Cloud workspace.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Avouro LLC",
7
7
  "homepage": "https://github.com/avouro-com/scopebond/tree/main/packages/agent#readme",
@@ -27,7 +27,7 @@
27
27
  "main": "dist/index.js",
28
28
  "types": "dist/index.d.ts",
29
29
  "bin": {
30
- "scopebond-agent": "dist/cli.js"
30
+ "scopebond-agent": "dist/bin.js"
31
31
  },
32
32
  "exports": {
33
33
  ".": {
@@ -39,13 +39,16 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@scopebond/gateway": "0.13.0",
43
- "@scopebond/hook": "0.16.0",
42
+ "@scopebond/gateway": "0.14.0",
43
+ "@scopebond/hook": "0.17.0",
44
44
  "@scopebond/sdk": "0.1.4"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
+ "devDependencies": {
50
+ "@scopebond/fake-cloud": "0.1.0"
51
+ },
49
52
  "scripts": {
50
53
  "build": "tsc -p tsconfig.json",
51
54
  "test": "tsc -p tsconfig.json && node --test test/*.test.mjs"