@scopebond/agent 0.4.6 → 0.5.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 +35 -6
- package/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +4 -4
- package/dist/agent.js.map +1 -1
- package/dist/allowance-sender.d.ts +8 -0
- package/dist/allowance-sender.d.ts.map +1 -0
- package/dist/allowance-sender.js +80 -0
- package/dist/allowance-sender.js.map +1 -0
- package/dist/autostart.d.ts +13 -0
- package/dist/autostart.d.ts.map +1 -1
- package/dist/autostart.js +60 -10
- package/dist/autostart.js.map +1 -1
- package/dist/cli-main.d.ts +3 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +218 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.js +4 -185
- 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 +5 -3
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +7 -1
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +64 -12
- package/dist/ipc.js.map +1 -1
- package/dist/log-file.d.ts +6 -0
- package/dist/log-file.d.ts.map +1 -0
- package/dist/log-file.js +40 -0
- package/dist/log-file.js.map +1 -0
- package/dist/native-update.d.ts +51 -0
- package/dist/native-update.d.ts.map +1 -0
- package/dist/native-update.js +120 -0
- package/dist/native-update.js.map +1 -0
- package/dist/prompt.d.ts +11 -1
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +57 -23
- package/dist/prompt.js.map +1 -1
- package/dist/reconnect.d.ts +19 -0
- package/dist/reconnect.d.ts.map +1 -0
- package/dist/reconnect.js +60 -0
- package/dist/reconnect.js.map +1 -0
- package/dist/self.d.ts +5 -0
- package/dist/self.d.ts.map +1 -0
- package/dist/self.js +15 -0
- package/dist/self.js.map +1 -0
- package/dist/service.d.ts +50 -4
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +292 -18
- package/dist/service.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +4 -9
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts +16 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +47 -0
- package/dist/summary.js.map +1 -0
- package/dist/tray-model.d.ts +91 -0
- package/dist/tray-model.d.ts.map +1 -0
- package/dist/tray-model.js +159 -0
- package/dist/tray-model.js.map +1 -0
- package/dist/tray.d.ts.map +1 -1
- package/dist/tray.js +103 -29
- package/dist/tray.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +7 -4
- package/dist/update.js.map +1 -1
- package/package.json +4 -4
package/dist/setup.js
CHANGED
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
// and the hook entry and autostart are put back only where they are gone.
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import { createRequire } from "node:module";
|
|
10
9
|
import { homedir } from "node:os";
|
|
11
|
-
import {
|
|
12
|
-
import { loadConnection, nodeTooOldLines, readDeliveryState } from "@scopebond/hook";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { hookSelfCommand, loadConnection, nodeTooOldLines, readDeliveryState } from "@scopebond/hook";
|
|
13
12
|
import { autostartHealth, enableAutostart, startNow } from "./autostart.js";
|
|
14
13
|
import { callAgent } from "./ipc.js";
|
|
15
14
|
import { compareVersions, ownNpm } from "./update.js";
|
|
@@ -73,11 +72,6 @@ function globalAgent() {
|
|
|
73
72
|
catch { /* not installed */ }
|
|
74
73
|
return { prefix: prefix.ok ? prefix.out.trim().split(/\r?\n/).pop() ?? null : null, cli: existsSync(cli) ? cli : null, version };
|
|
75
74
|
}
|
|
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
75
|
export async function runSetup(o) {
|
|
82
76
|
const say = (line) => console.log(line);
|
|
83
77
|
if (!nodeSupported()) {
|
|
@@ -108,7 +102,8 @@ export async function runSetup(o) {
|
|
|
108
102
|
// From the home folder, so a project's own setup in the current folder can never take the sign-in;
|
|
109
103
|
// and a failed sign-in names this setup command, which also installs and starts the agent.
|
|
110
104
|
const again = `${process.platform === "win32" ? "npx.cmd" : "npx"} -y @scopebond/agent@${o.version} setup ${o.origin}${o.harness === "claude" ? "" : ` --${o.harness}`}`;
|
|
111
|
-
const
|
|
105
|
+
const [program, args] = hookSelfCommand(["login", o.origin, flag]);
|
|
106
|
+
const r = spawnSync(program, args, { stdio: "inherit", cwd: homedir(), env: { ...process.env, SCOPEBOND_HOME: o.dir, SCOPEBOND_RETRY_COMMAND: again } });
|
|
112
107
|
if (r.status !== 0) {
|
|
113
108
|
console.error("Setup stopped: the sign-in did not finish. Run the same command again for a new code.");
|
|
114
109
|
return 1;
|
package/dist/setup.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { hookSelfCommand, 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\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 [program, args] = hookSelfCommand([\"login\", o.origin, flag]);\n const r = spawnSync(program, args, { 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"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HookConnection } from "@scopebond/hook";
|
|
2
|
+
export interface ComputerSummary {
|
|
3
|
+
workspace_name: string | null;
|
|
4
|
+
environment_name: string | null;
|
|
5
|
+
computer_name: string | null;
|
|
6
|
+
computer_url: string | null;
|
|
7
|
+
review_url: string | null;
|
|
8
|
+
open_reviews: number;
|
|
9
|
+
stage: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function fetchComputerSummary(connection: HookConnection, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<ComputerSummary | null>;
|
|
12
|
+
/** Whether `url` is an https (or loopback http) page on the same origin as the workspace. */
|
|
13
|
+
export declare function sameOrigin(url: string, workspace: string): boolean;
|
|
14
|
+
/** Open a checked URL in the default browser. */
|
|
15
|
+
export declare function openInBrowser(url: string): void;
|
|
16
|
+
//# sourceMappingURL=summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAID,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,cAAc,EAAE,SAAS,GAAE,OAAO,KAAa,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAe1J;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAMlE;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAG/C"}
|
package/dist/summary.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SB391 (D143): the workspace's summary of this computer, for the tray. Optional: a workspace that does not implement
|
|
2
|
+
// `GET /v1/computer/summary` (a self-hosted gateway, the fake cloud) answers 404, and the tray leaves those rows out. The tray
|
|
3
|
+
// opens only links on the workspace this computer is connected to, never a URL from anywhere else.
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
const text = (v, max = 200) => (typeof v === "string" && v.length <= max ? v : null);
|
|
6
|
+
export async function fetchComputerSummary(connection, fetchImpl = fetch, timeoutMs = 5_000) {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetchImpl(`${connection.url.replace(/\/+$/, "")}/v1/computer/summary`, {
|
|
9
|
+
headers: { authorization: `Bearer ${connection.credential}` }, redirect: "error", signal: AbortSignal.timeout(timeoutMs),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok)
|
|
12
|
+
return null;
|
|
13
|
+
const body = await res.json();
|
|
14
|
+
const link = (v) => { const s = text(v, 2048); return s && sameOrigin(s, connection.url) ? s : null; };
|
|
15
|
+
return {
|
|
16
|
+
workspace_name: text(body.workspace_name), environment_name: text(body.environment_name), computer_name: text(body.computer_name),
|
|
17
|
+
computer_url: link(body.computer_url), review_url: link(body.review_url),
|
|
18
|
+
open_reviews: Number.isSafeInteger(body.open_reviews) && body.open_reviews >= 0 ? body.open_reviews : 0,
|
|
19
|
+
stage: text(body.stage, 40),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Whether `url` is an https (or loopback http) page on the same origin as the workspace. */
|
|
27
|
+
export function sameOrigin(url, workspace) {
|
|
28
|
+
try {
|
|
29
|
+
const a = new URL(url), b = new URL(workspace);
|
|
30
|
+
const safe = a.protocol === "https:" || (a.protocol === "http:" && /^(localhost|127\.0\.0\.1|\[::1\])$/.test(a.hostname));
|
|
31
|
+
return safe && a.origin === b.origin;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Open a checked URL in the default browser. */
|
|
38
|
+
export function openInBrowser(url) {
|
|
39
|
+
const [command, args] = process.platform === "win32" ? ["explorer.exe", [url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
40
|
+
try {
|
|
41
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
42
|
+
child.on("error", () => { });
|
|
43
|
+
child.unref();
|
|
44
|
+
}
|
|
45
|
+
catch { /* no browser */ }
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,+HAA+H;AAC/H,mGAAmG;AAEnG,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAa3C,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,GAAG,GAAG,GAAG,EAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAE7G,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,UAA0B,EAAE,YAA0B,KAAK,EAAE,SAAS,GAAG,KAAK;IACvH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,sBAAsB,EAAE;YACvF,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACzH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;QACzD,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChH,OAAO;YACL,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;YACjI,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;YACxE,YAAY,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,IAAK,IAAI,CAAC,YAAuB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAsB,CAAC,CAAC,CAAC,CAAC;YAC7H,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;SAC5B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,SAAiB;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,oCAAoC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1H,OAAO,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AAC3B,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvJ,IAAI,CAAC;QAAC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC;AAC5K,CAAC","sourcesContent":["// SB391 (D143): the workspace's summary of this computer, for the tray. Optional: a workspace that does not implement\n// `GET /v1/computer/summary` (a self-hosted gateway, the fake cloud) answers 404, and the tray leaves those rows out. The tray\n// opens only links on the workspace this computer is connected to, never a URL from anywhere else.\n\nimport { spawn } from \"node:child_process\";\nimport type { HookConnection } from \"@scopebond/hook\";\n\nexport interface ComputerSummary {\n workspace_name: string | null;\n environment_name: string | null;\n computer_name: string | null;\n computer_url: string | null;\n review_url: string | null;\n open_reviews: number;\n stage: string | null;\n}\n\nconst text = (v: unknown, max = 200): string | null => (typeof v === \"string\" && v.length <= max ? v : null);\n\nexport async function fetchComputerSummary(connection: HookConnection, fetchImpl: typeof fetch = fetch, timeoutMs = 5_000): Promise<ComputerSummary | null> {\n try {\n const res = await fetchImpl(`${connection.url.replace(/\\/+$/, \"\")}/v1/computer/summary`, {\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 Record<string, unknown>;\n const link = (v: unknown) => { const s = text(v, 2048); return s && sameOrigin(s, connection.url) ? s : null; };\n return {\n workspace_name: text(body.workspace_name), environment_name: text(body.environment_name), computer_name: text(body.computer_name),\n computer_url: link(body.computer_url), review_url: link(body.review_url),\n open_reviews: Number.isSafeInteger(body.open_reviews) && (body.open_reviews as number) >= 0 ? body.open_reviews as number : 0,\n stage: text(body.stage, 40),\n };\n } catch { return null; }\n}\n\n/** Whether `url` is an https (or loopback http) page on the same origin as the workspace. */\nexport function sameOrigin(url: string, workspace: string): boolean {\n try {\n const a = new URL(url), b = new URL(workspace);\n const safe = a.protocol === \"https:\" || (a.protocol === \"http:\" && /^(localhost|127\\.0\\.0\\.1|\\[::1\\])$/.test(a.hostname));\n return safe && a.origin === b.origin;\n } catch { return false; }\n}\n\n/** Open a checked URL in the default browser. */\nexport function openInBrowser(url: string): void {\n const [command, args] = process.platform === \"win32\" ? [\"explorer.exe\", [url]] : process.platform === \"darwin\" ? [\"open\", [url]] : [\"xdg-open\", [url]];\n try { const child = spawn(command, args, { detached: true, stdio: \"ignore\", windowsHide: true }); child.on(\"error\", () => {}); child.unref(); } catch { /* no browser */ }\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { StatusJson } from "@scopebond/hook";
|
|
2
|
+
import type { Health } from "./health.js";
|
|
3
|
+
/** The six icon states of the "S" tile (D143): no badge when protected; a badge with its own glyph otherwise. */
|
|
4
|
+
export type TrayState = "protected" | "working" | "offline" | "attention" | "problem" | "disconnected";
|
|
5
|
+
export interface TrayAction {
|
|
6
|
+
id: "send_now" | "update_now" | "check_now" | "repair" | "reconnect" | "open_workspace";
|
|
7
|
+
label: string;
|
|
8
|
+
/** The agent's local route that does it (POST). */
|
|
9
|
+
route: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TrayModel {
|
|
12
|
+
state: TrayState;
|
|
13
|
+
/** One line at the top, and the tooltip (Windows allows 63 characters in a classic tray tooltip). */
|
|
14
|
+
headline: string;
|
|
15
|
+
tooltip: string;
|
|
16
|
+
/** Rules, Delivery, Today, Version: each only when there is data (never "unknown"). */
|
|
17
|
+
rows: Array<{
|
|
18
|
+
label: string;
|
|
19
|
+
value: string;
|
|
20
|
+
}>;
|
|
21
|
+
/** The one fix when something is wrong (shown first, in place of the usual actions). */
|
|
22
|
+
fix: TrayAction | null;
|
|
23
|
+
/** Actions that apply now, in menu order. */
|
|
24
|
+
actions: TrayAction[];
|
|
25
|
+
/** A sign-in or similar step no button can do, in words. */
|
|
26
|
+
hint: string | null;
|
|
27
|
+
recent_blocks: RecentBlock[];
|
|
28
|
+
}
|
|
29
|
+
/** A recent block. `can_act`: the person may still allow it or ask an admin from the tray (D144); `acted`: what they did. */
|
|
30
|
+
export interface RecentBlock {
|
|
31
|
+
action_id: string | null;
|
|
32
|
+
summary: string;
|
|
33
|
+
at: string;
|
|
34
|
+
rule: string | null;
|
|
35
|
+
can_act?: boolean;
|
|
36
|
+
acted?: "allowed" | "asked" | null;
|
|
37
|
+
}
|
|
38
|
+
export interface TrayInput {
|
|
39
|
+
status: StatusJson;
|
|
40
|
+
health: Health;
|
|
41
|
+
now: number;
|
|
42
|
+
/** When this computer last came back from sleep or started (the agent sees the gap between its cycles). */
|
|
43
|
+
awakeSince: number;
|
|
44
|
+
/** A long step the agent is running (an update, a sign-in, a repair), or null. */
|
|
45
|
+
working: string | null;
|
|
46
|
+
rules: {
|
|
47
|
+
checked_at: number | null;
|
|
48
|
+
managed: boolean;
|
|
49
|
+
block: number;
|
|
50
|
+
monitor: number;
|
|
51
|
+
} | null;
|
|
52
|
+
today: {
|
|
53
|
+
actions: number;
|
|
54
|
+
blocked: number;
|
|
55
|
+
allowed_by_person: number;
|
|
56
|
+
} | null;
|
|
57
|
+
recentBlocks?: RecentBlock[];
|
|
58
|
+
version: {
|
|
59
|
+
agent: string;
|
|
60
|
+
hook: string;
|
|
61
|
+
policy: "recommended" | "hold" | "unknown";
|
|
62
|
+
recommendedAgent: string | null;
|
|
63
|
+
recommendedHook: string | null;
|
|
64
|
+
};
|
|
65
|
+
workspace: {
|
|
66
|
+
name: string | null;
|
|
67
|
+
environment: string | null;
|
|
68
|
+
computer_url: string | null;
|
|
69
|
+
} | null;
|
|
70
|
+
computerName: string;
|
|
71
|
+
/** Items about this computer waiting in the workspace's Review (from the workspace's summary). */
|
|
72
|
+
openReviews?: number;
|
|
73
|
+
/** Whether the agent can run the sign-in itself (`POST /reconnect`); until then the tray shows the command. */
|
|
74
|
+
canReconnect?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Records waiting this long while the computer is awake and online need attention (D143 §4.1). */
|
|
77
|
+
export declare const WAITING_ATTENTION_MS: number;
|
|
78
|
+
/** Offline (the workspace unreachable) this long needs attention. */
|
|
79
|
+
export declare const OFFLINE_ATTENTION_MS: number;
|
|
80
|
+
/** Rules not checked for this long while online need attention. */
|
|
81
|
+
export declare const RULES_STALE_MS: number;
|
|
82
|
+
/** A delivery error that means the workspace could not be reached (no answer at all), not a refusal. */
|
|
83
|
+
export declare function unreachable(error: string | null | undefined): boolean;
|
|
84
|
+
export declare function ago(at: number, now: number): string;
|
|
85
|
+
export declare function trayModel(input: TrayInput): TrayModel;
|
|
86
|
+
/** What "Check now" says afterwards, for five seconds (SB387): never silence. */
|
|
87
|
+
export declare function checkResult(selfCheck: {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
failed: string[];
|
|
90
|
+
} | null, error: string | null): string;
|
|
91
|
+
//# sourceMappingURL=tray-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tray-model.d.ts","sourceRoot":"","sources":["../src/tray-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// What the tray shows, as data (SB387, SB388). The tray (today a PowerShell script, later a native one) only draws this: one
|
|
2
|
+
// state, one headline, the rows that have data, the one fix when something is wrong, and the actions that apply now. It never
|
|
3
|
+
// works out health itself, so the tray, `status` and the workspace's computer page say the same thing.
|
|
4
|
+
const MIN = 60_000;
|
|
5
|
+
const HOUR = 60 * MIN;
|
|
6
|
+
/** Records waiting this long while the computer is awake and online need attention (D143 §4.1). */
|
|
7
|
+
export const WAITING_ATTENTION_MS = 15 * MIN;
|
|
8
|
+
/** Offline (the workspace unreachable) this long needs attention. */
|
|
9
|
+
export const OFFLINE_ATTENTION_MS = 4 * HOUR;
|
|
10
|
+
/** Rules not checked for this long while online need attention. */
|
|
11
|
+
export const RULES_STALE_MS = HOUR;
|
|
12
|
+
/** A delivery error that means the workspace could not be reached (no answer at all), not a refusal. */
|
|
13
|
+
export function unreachable(error) {
|
|
14
|
+
return !!error && !/HTTP \d{3}/.test(error) && /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|getaddrinfo|network|socket|could not reach/i.test(error);
|
|
15
|
+
}
|
|
16
|
+
export function ago(at, now) {
|
|
17
|
+
const s = Math.max(0, Math.round((now - at) / 1000));
|
|
18
|
+
if (s < 60)
|
|
19
|
+
return "just now";
|
|
20
|
+
const m = Math.round(s / 60);
|
|
21
|
+
if (m < 60)
|
|
22
|
+
return `${m} min ago`;
|
|
23
|
+
const h = Math.round(m / 60);
|
|
24
|
+
if (h < 36)
|
|
25
|
+
return `${h} h ago`;
|
|
26
|
+
return `${Math.round(h / 24)} days ago`;
|
|
27
|
+
}
|
|
28
|
+
function clock(at) {
|
|
29
|
+
const d = new Date(at);
|
|
30
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31
|
+
}
|
|
32
|
+
const newer = (want, have) => {
|
|
33
|
+
if (!want)
|
|
34
|
+
return false;
|
|
35
|
+
const a = want.split(".").map(Number), b = have.split(".").map(Number);
|
|
36
|
+
for (let i = 0; i < 3; i++)
|
|
37
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0))
|
|
38
|
+
return (a[i] ?? 0) > (b[i] ?? 0);
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
const ACTIONS = {
|
|
42
|
+
send_now: { id: "send_now", label: "Send records now", route: "/flush" },
|
|
43
|
+
update_now: { id: "update_now", label: "Update now", route: "/update" },
|
|
44
|
+
check_now: { id: "check_now", label: "Check now", route: "/check" },
|
|
45
|
+
repair: { id: "repair", label: "Repair", route: "/repair" },
|
|
46
|
+
reconnect: { id: "reconnect", label: "Reconnect…", route: "/reconnect" },
|
|
47
|
+
open_workspace: { id: "open_workspace", label: "Open workspace", route: "/open-workspace" },
|
|
48
|
+
};
|
|
49
|
+
export function trayModel(input) {
|
|
50
|
+
const { status, health, now } = input;
|
|
51
|
+
const d = status.delivery;
|
|
52
|
+
const pending = d.pending;
|
|
53
|
+
const oldestAt = d.oldest_pending_age_s === null ? null : now - d.oldest_pending_age_s * 1000;
|
|
54
|
+
// Sleep-aware (SB388): time the computer was asleep or off never counts as waiting.
|
|
55
|
+
const waitingMs = oldestAt === null ? 0 : now - Math.max(oldestAt, input.awakeSince);
|
|
56
|
+
const offline = d.connected && unreachable(d.last_error);
|
|
57
|
+
const offlineFor = offline ? now - Math.max(d.last_success_at ?? input.awakeSince, input.awakeSince) : 0;
|
|
58
|
+
const updateAvailable = input.version.policy === "recommended" && (newer(input.version.recommendedAgent, input.version.agent) || newer(input.version.recommendedHook, input.version.hook));
|
|
59
|
+
let state;
|
|
60
|
+
let headline;
|
|
61
|
+
let fix = null;
|
|
62
|
+
let hint = null;
|
|
63
|
+
if (input.working) {
|
|
64
|
+
state = "working";
|
|
65
|
+
headline = input.working;
|
|
66
|
+
}
|
|
67
|
+
else if (health.level === "red" && d.connection_refused_since !== null) {
|
|
68
|
+
state = "disconnected";
|
|
69
|
+
headline = "Not connected to your workspace: using this computer's own rules";
|
|
70
|
+
if (input.canReconnect)
|
|
71
|
+
fix = ACTIONS.reconnect;
|
|
72
|
+
else
|
|
73
|
+
hint = health.hint;
|
|
74
|
+
}
|
|
75
|
+
else if (health.level === "red") {
|
|
76
|
+
state = "problem";
|
|
77
|
+
headline = health.headline;
|
|
78
|
+
hint = health.hint;
|
|
79
|
+
if (health.fix?.route === "/repair")
|
|
80
|
+
fix = ACTIONS.repair;
|
|
81
|
+
}
|
|
82
|
+
else if (offline) {
|
|
83
|
+
state = offlineFor > OFFLINE_ATTENTION_MS ? "attention" : "offline";
|
|
84
|
+
headline = pending ? `Workspace unreachable: ${pending} record${pending === 1 ? "" : "s"} waiting` : "Workspace unreachable: still checking actions";
|
|
85
|
+
}
|
|
86
|
+
else if (pending > 0 && waitingMs > WAITING_ATTENTION_MS) {
|
|
87
|
+
state = "attention";
|
|
88
|
+
headline = `${pending} record${pending === 1 ? "" : "s"} waiting to send`;
|
|
89
|
+
fix = ACTIONS.send_now;
|
|
90
|
+
}
|
|
91
|
+
else if (health.level === "amber" && health.fix?.route === "/maintain") {
|
|
92
|
+
state = "attention";
|
|
93
|
+
headline = health.headline;
|
|
94
|
+
fix = ACTIONS.check_now;
|
|
95
|
+
}
|
|
96
|
+
else if (input.rules?.checked_at && d.connected && now - input.rules.checked_at > RULES_STALE_MS) {
|
|
97
|
+
state = "attention";
|
|
98
|
+
headline = `Rules not checked for ${ago(input.rules.checked_at, now).replace(" ago", "")}`;
|
|
99
|
+
fix = ACTIONS.check_now;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
state = "protected";
|
|
103
|
+
headline = "Protected";
|
|
104
|
+
}
|
|
105
|
+
const rows = [];
|
|
106
|
+
if (input.rules) {
|
|
107
|
+
const when = input.rules.checked_at ? ` · checked ${ago(input.rules.checked_at, now)}` : "";
|
|
108
|
+
const source = input.rules.managed ? "Up to date" : "Using this computer's own rules";
|
|
109
|
+
const counts = input.rules.block + input.rules.monitor > 0 ? ` · ${input.rules.block} block · ${input.rules.monitor} monitor` : "";
|
|
110
|
+
rows.push({ label: "Rules", value: `${input.rules.managed && input.rules.checked_at && now - input.rules.checked_at > RULES_STALE_MS ? `Not checked for ${ago(input.rules.checked_at, now).replace(" ago", "")}` : source}${when}${counts}` });
|
|
111
|
+
}
|
|
112
|
+
if (d.connected) {
|
|
113
|
+
const value = pending === 0
|
|
114
|
+
? (d.last_success_at ? `All sent · ${ago(d.last_success_at, now)}` : "Nothing to send yet")
|
|
115
|
+
: offline ? `${pending} waiting since ${oldestAt ? clock(oldestAt) : "earlier"} · offline`
|
|
116
|
+
: `${pending} waiting · sending`;
|
|
117
|
+
rows.push({ label: "Delivery", value });
|
|
118
|
+
}
|
|
119
|
+
if (input.today) {
|
|
120
|
+
const parts = [`${input.today.actions} action${input.today.actions === 1 ? "" : "s"}`, `${input.today.blocked} blocked`];
|
|
121
|
+
if (input.today.allowed_by_person)
|
|
122
|
+
parts.push(`${input.today.allowed_by_person} allowed by a person`);
|
|
123
|
+
if (input.openReviews)
|
|
124
|
+
parts.push(`${input.openReviews} in Review`);
|
|
125
|
+
rows.push({ label: "Today", value: parts.join(" · ") });
|
|
126
|
+
}
|
|
127
|
+
rows.push({
|
|
128
|
+
label: "Version",
|
|
129
|
+
value: updateAvailable ? `Update available (agent ${input.version.recommendedAgent ?? input.version.agent})`
|
|
130
|
+
: input.version.policy === "hold" ? `agent ${input.version.agent} · updates managed by your workspace`
|
|
131
|
+
: `Up to date (agent ${input.version.agent})`,
|
|
132
|
+
});
|
|
133
|
+
const actions = [];
|
|
134
|
+
if (pending > 0 && fix?.id !== "send_now" && state !== "disconnected")
|
|
135
|
+
actions.push(ACTIONS.send_now);
|
|
136
|
+
if (updateAvailable && !input.working)
|
|
137
|
+
actions.push(ACTIONS.update_now);
|
|
138
|
+
if (fix?.id !== "check_now")
|
|
139
|
+
actions.push(ACTIONS.check_now);
|
|
140
|
+
if (input.workspace?.computer_url)
|
|
141
|
+
actions.push(ACTIONS.open_workspace);
|
|
142
|
+
const where = [input.workspace?.name, input.workspace?.environment].filter(Boolean).join(" · ");
|
|
143
|
+
const label = state === "protected" ? `Protected${where ? ` · ${where}` : ""}` : headline;
|
|
144
|
+
const tooltip = `Scopebond — ${label}`;
|
|
145
|
+
return {
|
|
146
|
+
state, headline, fix, actions, hint, rows,
|
|
147
|
+
tooltip: tooltip.length > 63 ? `${tooltip.slice(0, 62)}…` : tooltip,
|
|
148
|
+
recent_blocks: (input.recentBlocks ?? []).slice(0, 3),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** What "Check now" says afterwards, for five seconds (SB387): never silence. */
|
|
152
|
+
export function checkResult(selfCheck, error) {
|
|
153
|
+
if (error)
|
|
154
|
+
return `Check could not finish: ${error}`.slice(0, 200);
|
|
155
|
+
if (!selfCheck)
|
|
156
|
+
return "Checked just now: this computer is not connected to a workspace, so only local checks ran";
|
|
157
|
+
return selfCheck.ok ? "Checked just now: all good" : `Check found a problem: ${selfCheck.failed[0] ?? "see status"}`;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=tray-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tray-model.js","sourceRoot":"","sources":["../src/tray-model.ts"],"names":[],"mappings":"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","sourcesContent":["// What the tray shows, as data (SB387, SB388). The tray (today a PowerShell script, later a native one) only draws this: one\n// state, one headline, the rows that have data, the one fix when something is wrong, and the actions that apply now. It never\n// works out health itself, so the tray, `status` and the workspace's computer page say the same thing.\n\nimport type { StatusJson } from \"@scopebond/hook\";\nimport type { Health } from \"./health.js\";\n\n/** The six icon states of the \"S\" tile (D143): no badge when protected; a badge with its own glyph otherwise. */\nexport type TrayState = \"protected\" | \"working\" | \"offline\" | \"attention\" | \"problem\" | \"disconnected\";\n\nexport interface TrayAction {\n id: \"send_now\" | \"update_now\" | \"check_now\" | \"repair\" | \"reconnect\" | \"open_workspace\";\n label: string;\n /** The agent's local route that does it (POST). */\n route: string;\n}\n\nexport interface TrayModel {\n state: TrayState;\n /** One line at the top, and the tooltip (Windows allows 63 characters in a classic tray tooltip). */\n headline: string;\n tooltip: string;\n /** Rules, Delivery, Today, Version: each only when there is data (never \"unknown\"). */\n rows: Array<{ label: string; value: string }>;\n /** The one fix when something is wrong (shown first, in place of the usual actions). */\n fix: TrayAction | null;\n /** Actions that apply now, in menu order. */\n actions: TrayAction[];\n /** A sign-in or similar step no button can do, in words. */\n hint: string | null;\n recent_blocks: RecentBlock[];\n}\n\n/** A recent block. `can_act`: the person may still allow it or ask an admin from the tray (D144); `acted`: what they did. */\nexport interface RecentBlock { action_id: string | null; summary: string; at: string; rule: string | null; can_act?: boolean; acted?: \"allowed\" | \"asked\" | null }\n\nexport interface TrayInput {\n status: StatusJson;\n health: Health;\n now: number;\n /** When this computer last came back from sleep or started (the agent sees the gap between its cycles). */\n awakeSince: number;\n /** A long step the agent is running (an update, a sign-in, a repair), or null. */\n working: string | null;\n rules: { checked_at: number | null; managed: boolean; block: number; monitor: number } | null;\n today: { actions: number; blocked: number; allowed_by_person: number } | null;\n recentBlocks?: RecentBlock[];\n version: { agent: string; hook: string; policy: \"recommended\" | \"hold\" | \"unknown\"; recommendedAgent: string | null; recommendedHook: string | null };\n workspace: { name: string | null; environment: string | null; computer_url: string | null } | null;\n computerName: string;\n /** Items about this computer waiting in the workspace's Review (from the workspace's summary). */\n openReviews?: number;\n /** Whether the agent can run the sign-in itself (`POST /reconnect`); until then the tray shows the command. */\n canReconnect?: boolean;\n}\n\nconst MIN = 60_000;\nconst HOUR = 60 * MIN;\n/** Records waiting this long while the computer is awake and online need attention (D143 §4.1). */\nexport const WAITING_ATTENTION_MS = 15 * MIN;\n/** Offline (the workspace unreachable) this long needs attention. */\nexport const OFFLINE_ATTENTION_MS = 4 * HOUR;\n/** Rules not checked for this long while online need attention. */\nexport const RULES_STALE_MS = HOUR;\n\n/** A delivery error that means the workspace could not be reached (no answer at all), not a refusal. */\nexport function unreachable(error: string | null | undefined): boolean {\n return !!error && !/HTTP \\d{3}/.test(error) && /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|getaddrinfo|network|socket|could not reach/i.test(error);\n}\n\nexport function ago(at: number, now: number): string {\n const s = Math.max(0, Math.round((now - at) / 1000));\n if (s < 60) return \"just now\";\n const m = Math.round(s / 60);\n if (m < 60) return `${m} min ago`;\n const h = Math.round(m / 60);\n if (h < 36) return `${h} h ago`;\n return `${Math.round(h / 24)} days ago`;\n}\n\nfunction clock(at: number): string {\n const d = new Date(at);\n return `${String(d.getHours()).padStart(2, \"0\")}:${String(d.getMinutes()).padStart(2, \"0\")}`;\n}\n\nconst newer = (want: string | null, have: string): boolean => {\n if (!want) return false;\n const a = want.split(\".\").map(Number), b = have.split(\".\").map(Number);\n for (let i = 0; i < 3; i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);\n return false;\n};\n\nconst ACTIONS: Record<TrayAction[\"id\"], TrayAction> = {\n send_now: { id: \"send_now\", label: \"Send records now\", route: \"/flush\" },\n update_now: { id: \"update_now\", label: \"Update now\", route: \"/update\" },\n check_now: { id: \"check_now\", label: \"Check now\", route: \"/check\" },\n repair: { id: \"repair\", label: \"Repair\", route: \"/repair\" },\n reconnect: { id: \"reconnect\", label: \"Reconnect…\", route: \"/reconnect\" },\n open_workspace: { id: \"open_workspace\", label: \"Open workspace\", route: \"/open-workspace\" },\n};\n\nexport function trayModel(input: TrayInput): TrayModel {\n const { status, health, now } = input;\n const d = status.delivery;\n const pending = d.pending;\n const oldestAt = d.oldest_pending_age_s === null ? null : now - d.oldest_pending_age_s * 1000;\n // Sleep-aware (SB388): time the computer was asleep or off never counts as waiting.\n const waitingMs = oldestAt === null ? 0 : now - Math.max(oldestAt, input.awakeSince);\n const offline = d.connected && unreachable(d.last_error);\n const offlineFor = offline ? now - Math.max(d.last_success_at ?? input.awakeSince, input.awakeSince) : 0;\n const updateAvailable = input.version.policy === \"recommended\" && (newer(input.version.recommendedAgent, input.version.agent) || newer(input.version.recommendedHook, input.version.hook));\n\n let state: TrayState;\n let headline: string;\n let fix: TrayAction | null = null;\n let hint: string | null = null;\n if (input.working) {\n state = \"working\"; headline = input.working;\n } else if (health.level === \"red\" && d.connection_refused_since !== null) {\n state = \"disconnected\"; headline = \"Not connected to your workspace: using this computer's own rules\";\n if (input.canReconnect) fix = ACTIONS.reconnect; else hint = health.hint;\n } else if (health.level === \"red\") {\n state = \"problem\"; headline = health.headline; hint = health.hint;\n if (health.fix?.route === \"/repair\") fix = ACTIONS.repair;\n } else if (offline) {\n state = offlineFor > OFFLINE_ATTENTION_MS ? \"attention\" : \"offline\";\n headline = pending ? `Workspace unreachable: ${pending} record${pending === 1 ? \"\" : \"s\"} waiting` : \"Workspace unreachable: still checking actions\";\n } else if (pending > 0 && waitingMs > WAITING_ATTENTION_MS) {\n state = \"attention\"; headline = `${pending} record${pending === 1 ? \"\" : \"s\"} waiting to send`; fix = ACTIONS.send_now;\n } else if (health.level === \"amber\" && health.fix?.route === \"/maintain\") {\n state = \"attention\"; headline = health.headline; fix = ACTIONS.check_now;\n } else if (input.rules?.checked_at && d.connected && now - input.rules.checked_at > RULES_STALE_MS) {\n state = \"attention\"; headline = `Rules not checked for ${ago(input.rules.checked_at, now).replace(\" ago\", \"\")}`; fix = ACTIONS.check_now;\n } else {\n state = \"protected\"; headline = \"Protected\";\n }\n\n const rows: TrayModel[\"rows\"] = [];\n if (input.rules) {\n const when = input.rules.checked_at ? ` · checked ${ago(input.rules.checked_at, now)}` : \"\";\n const source = input.rules.managed ? \"Up to date\" : \"Using this computer's own rules\";\n const counts = input.rules.block + input.rules.monitor > 0 ? ` · ${input.rules.block} block · ${input.rules.monitor} monitor` : \"\";\n rows.push({ label: \"Rules\", value: `${input.rules.managed && input.rules.checked_at && now - input.rules.checked_at > RULES_STALE_MS ? `Not checked for ${ago(input.rules.checked_at, now).replace(\" ago\", \"\")}` : source}${when}${counts}` });\n }\n if (d.connected) {\n const value = pending === 0\n ? (d.last_success_at ? `All sent · ${ago(d.last_success_at, now)}` : \"Nothing to send yet\")\n : offline ? `${pending} waiting since ${oldestAt ? clock(oldestAt) : \"earlier\"} · offline`\n : `${pending} waiting · sending`;\n rows.push({ label: \"Delivery\", value });\n }\n if (input.today) {\n const parts = [`${input.today.actions} action${input.today.actions === 1 ? \"\" : \"s\"}`, `${input.today.blocked} blocked`];\n if (input.today.allowed_by_person) parts.push(`${input.today.allowed_by_person} allowed by a person`);\n if (input.openReviews) parts.push(`${input.openReviews} in Review`);\n rows.push({ label: \"Today\", value: parts.join(\" · \") });\n }\n rows.push({\n label: \"Version\",\n value: updateAvailable ? `Update available (agent ${input.version.recommendedAgent ?? input.version.agent})`\n : input.version.policy === \"hold\" ? `agent ${input.version.agent} · updates managed by your workspace`\n : `Up to date (agent ${input.version.agent})`,\n });\n\n const actions: TrayAction[] = [];\n if (pending > 0 && fix?.id !== \"send_now\" && state !== \"disconnected\") actions.push(ACTIONS.send_now);\n if (updateAvailable && !input.working) actions.push(ACTIONS.update_now);\n if (fix?.id !== \"check_now\") actions.push(ACTIONS.check_now);\n if (input.workspace?.computer_url) actions.push(ACTIONS.open_workspace);\n\n const where = [input.workspace?.name, input.workspace?.environment].filter(Boolean).join(\" · \");\n const label = state === \"protected\" ? `Protected${where ? ` · ${where}` : \"\"}` : headline;\n const tooltip = `Scopebond — ${label}`;\n return {\n state, headline, fix, actions, hint, rows,\n tooltip: tooltip.length > 63 ? `${tooltip.slice(0, 62)}…` : tooltip,\n recent_blocks: (input.recentBlocks ?? []).slice(0, 3),\n };\n}\n\n/** What \"Check now\" says afterwards, for five seconds (SB387): never silence. */\nexport function checkResult(selfCheck: { ok: boolean; failed: string[] } | null, error: string | null): string {\n if (error) return `Check could not finish: ${error}`.slice(0, 200);\n if (!selfCheck) return \"Checked just now: this computer is not connected to a workspace, so only local checks ran\";\n return selfCheck.ok ? \"Checked just now: all good\" : `Check found a problem: ${selfCheck.failed[0] ?? \"see status\"}`;\n}\n"]}
|
package/dist/tray.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tray.d.ts","sourceRoot":"","sources":["../src/tray.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAIvD,6GAA6G;AAC7G,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoIjE;AAED,sHAAsH;AACtH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAc,GAAG,YAAY,GAAG,IAAI,CAQnF;AAID,uHAAuH;AACvH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAE,OAAO,KAAa,GAAG,OAAO,CAW7G"}
|