@scopebond/agent 0.5.5 → 0.6.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/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +10 -3
- package/dist/agent.js.map +1 -1
- package/dist/apps-entry.d.ts +46 -0
- package/dist/apps-entry.d.ts.map +1 -0
- package/dist/apps-entry.js +118 -0
- package/dist/apps-entry.js.map +1 -0
- package/dist/autostart.d.ts +11 -5
- package/dist/autostart.d.ts.map +1 -1
- package/dist/autostart.js +59 -5
- package/dist/autostart.js.map +1 -1
- package/dist/awake.d.ts +31 -0
- package/dist/awake.d.ts.map +1 -0
- package/dist/awake.js +57 -0
- package/dist/awake.js.map +1 -0
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli-main.js +28 -1
- package/dist/cli-main.js.map +1 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/ipc.d.ts +6 -2
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +11 -7
- package/dist/ipc.js.map +1 -1
- package/dist/native-update.d.ts +83 -7
- package/dist/native-update.d.ts.map +1 -1
- package/dist/native-update.js +230 -33
- package/dist/native-update.js.map +1 -1
- package/dist/selfcheck.d.ts.map +1 -1
- package/dist/selfcheck.js +4 -1
- package/dist/selfcheck.js.map +1 -1
- package/dist/service.d.ts +20 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +141 -44
- package/dist/service.js.map +1 -1
- package/dist/setup.d.ts +12 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +41 -12
- package/dist/setup.js.map +1 -1
- package/dist/tray-model.d.ts +2 -0
- package/dist/tray-model.d.ts.map +1 -1
- package/dist/tray-model.js +2 -0
- package/dist/tray-model.js.map +1 -1
- package/dist/update.d.ts +56 -12
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +209 -38
- package/dist/update.js.map +1 -1
- package/dist/upkeep.d.ts +16 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +55 -0
- package/dist/upkeep.js.map +1 -0
- package/package.json +3 -3
package/dist/agent.d.ts
CHANGED
|
@@ -6,9 +6,13 @@ export interface CycleOptions {
|
|
|
6
6
|
now?: () => number;
|
|
7
7
|
/** Per-request timeout for the workspace's rules check, in milliseconds. */
|
|
8
8
|
timeoutMs?: number;
|
|
9
|
+
/** How long one cycle sends before it moves on to the rules check (default one minute); the rest is sent next cycle. */
|
|
10
|
+
deliveryMs?: number;
|
|
9
11
|
/** Per-request timeout for each delivery request, its answer included (default 30 s). */
|
|
10
12
|
deliveryTimeoutMs?: number;
|
|
11
13
|
}
|
|
14
|
+
/** One cycle sends for at most this long, so a long queue never holds up the rules check and the credential renewal. */
|
|
15
|
+
export declare const CYCLE_DELIVERY_MS = 60000;
|
|
12
16
|
export interface CycleResult {
|
|
13
17
|
at: number;
|
|
14
18
|
connected: boolean;
|
|
@@ -19,6 +23,8 @@ export interface CycleResult {
|
|
|
19
23
|
missingHookEntries: Harness[];
|
|
20
24
|
/** A request from the workspace's computer page, carried on this cycle's rules check. */
|
|
21
25
|
requested: "flush" | "self_check" | null;
|
|
26
|
+
/** Records still waiting because this cycle's sending time ran out (not because the workspace refused them). */
|
|
27
|
+
more: boolean;
|
|
22
28
|
}
|
|
23
29
|
/** The agents on this computer whose user-level settings should hold the Scopebond hook. */
|
|
24
30
|
export declare function expectedHarnesses(): Harness[];
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAChD,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,OAAO,EAAE,CAAC;IAC9B,yFAAyF;IACzF,SAAS,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAChD,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wHAAwH;IACxH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wHAAwH;AACxH,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,OAAO,EAAE,CAAC;IAC9B,yFAAyF;IACzF,SAAS,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;IACzC,gHAAgH;IAChH,IAAI,EAAE,OAAO,CAAC;CACf;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,IAAI,OAAO,EAAE,CAE7C;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,OAAO,EAAwB,GAAG,OAAO,EAAE,CAExF;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAoD1E;AAED,mGAAmG;AACnG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAOpE"}
|
package/dist/agent.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { createCloudExporter } from "@scopebond/gateway";
|
|
9
|
-
import { SqliteCloudOutbox } from "@scopebond/gateway/node";
|
|
9
|
+
import { CHAIN_HEADS_FILE, SqliteCloudOutbox, chainHeadRecorder } from "@scopebond/gateway/node";
|
|
10
10
|
import { createSigner } from "@scopebond/sdk";
|
|
11
11
|
import { LOSSLESS_OUTBOX, OUTBOX_FILE, buildStatusJson, cursorDetected, codexDetected, hookVersion, ingestUrl, isHarnessConfigured, loadConnection, policyBuilds, recordDeliveryAttempt, repairDeliveryAt, sequenceProofFor, summaryOptions, syncPolicy, userHarnessFile, } from "@scopebond/hook";
|
|
12
|
+
/** One cycle sends for at most this long, so a long queue never holds up the rules check and the credential renewal. */
|
|
13
|
+
export const CYCLE_DELIVERY_MS = 60_000;
|
|
12
14
|
/** The agents on this computer whose user-level settings should hold the Scopebond hook. */
|
|
13
15
|
export function expectedHarnesses() {
|
|
14
16
|
return ["claude", ...(cursorDetected() ? ["cursor"] : []), ...(codexDetected() ? ["codex"] : [])];
|
|
@@ -20,7 +22,7 @@ export function missingHookEntries(harnesses = expectedHarnesses()) {
|
|
|
20
22
|
export async function runCycle(options) {
|
|
21
23
|
const now = options.now ?? Date.now;
|
|
22
24
|
const dir = options.dir;
|
|
23
|
-
const result = { at: now(), connected: false, delivered: 0, pending: 0, deliveryError: null, rules: "skipped", missingHookEntries: [], requested: null };
|
|
25
|
+
const result = { at: now(), connected: false, delivered: 0, pending: 0, deliveryError: null, rules: "skipped", missingHookEntries: [], requested: null, more: false };
|
|
24
26
|
try {
|
|
25
27
|
result.missingHookEntries = missingHookEntries();
|
|
26
28
|
}
|
|
@@ -43,11 +45,16 @@ export async function runCycle(options) {
|
|
|
43
45
|
url: ingestUrl(connection), credential: connection.credential, outbox,
|
|
44
46
|
flushMs: 24 * 60 * 60 * 1000, fetch: options.fetchImpl, now, summaries, requestTimeoutMs: options.deliveryTimeoutMs ?? 30_000,
|
|
45
47
|
...(sequenceProof ? { sequenceProof } : {}),
|
|
48
|
+
// The chain head each answer carries is kept beside the hook's receipts (chain-heads.json).
|
|
49
|
+
onChainHead: chainHeadRecorder(join(dir, CHAIN_HEADS_FILE)),
|
|
46
50
|
});
|
|
47
51
|
try {
|
|
48
52
|
const lastSuccess = exporter.status().lastSuccessAt;
|
|
49
|
-
|
|
53
|
+
const started = Date.now();
|
|
54
|
+
const deliveryMs = options.deliveryMs ?? CYCLE_DELIVERY_MS;
|
|
55
|
+
await exporter.flush({ maxMs: deliveryMs });
|
|
50
56
|
const status = exporter.status();
|
|
57
|
+
result.more = status.pending > 0 && status.lastError === null && Date.now() - started >= deliveryMs;
|
|
51
58
|
recordDeliveryAttempt(dir, status, now(), lastSuccess);
|
|
52
59
|
result.pending = status.pending;
|
|
53
60
|
result.delivered = Math.max(0, before - status.pending);
|
package/dist/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["// The Scopebond Agent's work, one cycle at a time. The hook still decides every action on its\n// own (enforcement never depends on the agent); the agent keeps everything around it working:\n// it delivers what the hook queued, keeps the workspace rules and the connection current, and\n// knows when the agent settings lost their hook entry. Every step is best-effort and bounded;\n// a cycle never throws.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createCloudExporter } from \"@scopebond/gateway\";\nimport { SqliteCloudOutbox } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport {\n LOSSLESS_OUTBOX, OUTBOX_FILE, buildStatusJson, cursorDetected, codexDetected, hookVersion, ingestUrl,\n isHarnessConfigured, loadConnection, policyBuilds, recordDeliveryAttempt, repairDeliveryAt, sequenceProofFor, summaryOptions, syncPolicy, userHarnessFile,\n type Harness, type StatusJson, type SyncOutcome,\n} from \"@scopebond/hook\";\n\nexport interface CycleOptions {\n /** The Scopebond home this agent serves (the user's ~/.scopebond by default). */\n dir: string;\n fetchImpl?: typeof fetch;\n now?: () => number;\n /** Per-request timeout for the workspace's rules check, in milliseconds. */\n timeoutMs?: number;\n /** Per-request timeout for each delivery request, its answer included (default 30 s). */\n deliveryTimeoutMs?: number;\n}\n\nexport interface CycleResult {\n at: number;\n connected: boolean;\n delivered: number;\n pending: number;\n deliveryError: string | null;\n rules: SyncOutcome[\"state\"] | \"skipped\";\n missingHookEntries: Harness[];\n /** A request from the workspace's computer page, carried on this cycle's rules check. */\n requested: \"flush\" | \"self_check\" | null;\n}\n\n/** The agents on this computer whose user-level settings should hold the Scopebond hook. */\nexport function expectedHarnesses(): Harness[] {\n return [\"claude\", ...(cursorDetected() ? [\"cursor\" as const] : []), ...(codexDetected() ? [\"codex\" as const] : [])];\n}\n\n/** Agent settings that lost their Scopebond hook entry (an update or a reset can remove it). */\nexport function missingHookEntries(harnesses: Harness[] = expectedHarnesses()): Harness[] {\n return harnesses.filter((h) => existsSync(userHarnessFile(h)) && !isHarnessConfigured(userHarnessFile(h)));\n}\n\nexport async function runCycle(options: CycleOptions): Promise<CycleResult> {\n const now = options.now ?? Date.now;\n const dir = options.dir;\n const result: CycleResult = { at: now(), connected: false, delivered: 0, pending: 0, deliveryError: null, rules: \"skipped\", missingHookEntries: [], requested: null };\n try { result.missingHookEntries = missingHookEntries(); } catch { /* reported as none */ }\n const connection = loadConnection(dir);\n if (!connection) return result;\n result.connected = true;\n\n // 1. Deliver everything the hook queued, in batches, until the queue is empty or the workspace refuses.\n try {\n const outbox = new SqliteCloudOutbox(join(dir, OUTBOX_FILE), LOSSLESS_OUTBOX);\n // First, what the queue never got: evaluations a hook process was stopped in the middle of (closed as outcome-unknown\n // records) and receipts whose queue write failed. Best effort; never stops delivery.\n await repairDeliveryAt(dir, outbox, now());\n const before = outbox.status().pending;\n // The record numbers are signed with the same enrolled key that signs the receipts and summaries.\n const summaries = summaryOptions(dir);\n const sequenceProof = sequenceProofFor(connection, summaries?.attester);\n const exporter = createCloudExporter({\n url: ingestUrl(connection), credential: connection.credential, outbox,\n flushMs: 24 * 60 * 60 * 1000, fetch: options.fetchImpl, now, summaries, requestTimeoutMs: options.deliveryTimeoutMs ?? 30_000,\n ...(sequenceProof ? { sequenceProof } : {}),\n });\n try {\n const lastSuccess = exporter.status().lastSuccessAt;\n await exporter.flush();\n const status = exporter.status();\n recordDeliveryAttempt(dir, status, now(), lastSuccess);\n result.pending = status.pending;\n result.delivered = Math.max(0, before - status.pending);\n result.deliveryError = status.lastError;\n } finally { exporter.stop(); }\n } catch (error) {\n result.deliveryError = (error as Error).message;\n }\n\n // 2. The rules check: the workspace's rules, the queue report, and credential renewal when due.\n try {\n const keyFile = join(dir, \"agent.key\");\n if (existsSync(keyFile)) {\n const agentKid = createSigner({ privateKeyPem: readFileSync(keyFile, \"utf8\") }).kid;\n const outcome = await syncPolicy(dir, { agentKid, hookVersion: hookVersion(), policyBuilds, fetchImpl: options.fetchImpl, timeoutMs: options.timeoutMs ?? 10_000, onRequest: (kind) => { result.requested = kind; } });\n result.rules = outcome.state;\n }\n } catch { result.rules = \"unavailable\"; }\n return result;\n}\n\n/** The machine-readable status (scopebond.status.v1) for this computer, as the hook reports it. */\nexport function computerStatus(dir: string, now?: number): StatusJson {\n const expected = expectedHarnesses();\n const configured = (h: Harness) => expected.includes(h) && existsSync(userHarnessFile(h)) && isHarnessConfigured(userHarnessFile(h));\n return buildStatusJson({\n version: hookVersion(), activeDir: dir, candidateDirs: [dir], hasPolicy: existsSync(join(dir, \"policy.json\")),\n agents: { claude: configured(\"claude\"), cursor: configured(\"cursor\"), codex: configured(\"codex\") }, now,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["// The Scopebond Agent's work, one cycle at a time. The hook still decides every action on its\n// own (enforcement never depends on the agent); the agent keeps everything around it working:\n// it delivers what the hook queued, keeps the workspace rules and the connection current, and\n// knows when the agent settings lost their hook entry. Every step is best-effort and bounded;\n// a cycle never throws.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { createCloudExporter } from \"@scopebond/gateway\";\nimport { CHAIN_HEADS_FILE, SqliteCloudOutbox, chainHeadRecorder } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport {\n LOSSLESS_OUTBOX, OUTBOX_FILE, buildStatusJson, cursorDetected, codexDetected, hookVersion, ingestUrl,\n isHarnessConfigured, loadConnection, policyBuilds, recordDeliveryAttempt, repairDeliveryAt, sequenceProofFor, summaryOptions, syncPolicy, userHarnessFile,\n type Harness, type StatusJson, type SyncOutcome,\n} from \"@scopebond/hook\";\n\nexport interface CycleOptions {\n /** The Scopebond home this agent serves (the user's ~/.scopebond by default). */\n dir: string;\n fetchImpl?: typeof fetch;\n now?: () => number;\n /** Per-request timeout for the workspace's rules check, in milliseconds. */\n timeoutMs?: number;\n /** How long one cycle sends before it moves on to the rules check (default one minute); the rest is sent next cycle. */\n deliveryMs?: number;\n /** Per-request timeout for each delivery request, its answer included (default 30 s). */\n deliveryTimeoutMs?: number;\n}\n\n/** One cycle sends for at most this long, so a long queue never holds up the rules check and the credential renewal. */\nexport const CYCLE_DELIVERY_MS = 60_000;\n\nexport interface CycleResult {\n at: number;\n connected: boolean;\n delivered: number;\n pending: number;\n deliveryError: string | null;\n rules: SyncOutcome[\"state\"] | \"skipped\";\n missingHookEntries: Harness[];\n /** A request from the workspace's computer page, carried on this cycle's rules check. */\n requested: \"flush\" | \"self_check\" | null;\n /** Records still waiting because this cycle's sending time ran out (not because the workspace refused them). */\n more: boolean;\n}\n\n/** The agents on this computer whose user-level settings should hold the Scopebond hook. */\nexport function expectedHarnesses(): Harness[] {\n return [\"claude\", ...(cursorDetected() ? [\"cursor\" as const] : []), ...(codexDetected() ? [\"codex\" as const] : [])];\n}\n\n/** Agent settings that lost their Scopebond hook entry (an update or a reset can remove it). */\nexport function missingHookEntries(harnesses: Harness[] = expectedHarnesses()): Harness[] {\n return harnesses.filter((h) => existsSync(userHarnessFile(h)) && !isHarnessConfigured(userHarnessFile(h)));\n}\n\nexport async function runCycle(options: CycleOptions): Promise<CycleResult> {\n const now = options.now ?? Date.now;\n const dir = options.dir;\n const result: CycleResult = { at: now(), connected: false, delivered: 0, pending: 0, deliveryError: null, rules: \"skipped\", missingHookEntries: [], requested: null, more: false };\n try { result.missingHookEntries = missingHookEntries(); } catch { /* reported as none */ }\n const connection = loadConnection(dir);\n if (!connection) return result;\n result.connected = true;\n\n // 1. Deliver everything the hook queued, in batches, until the queue is empty or the workspace refuses.\n try {\n const outbox = new SqliteCloudOutbox(join(dir, OUTBOX_FILE), LOSSLESS_OUTBOX);\n // First, what the queue never got: evaluations a hook process was stopped in the middle of (closed as outcome-unknown\n // records) and receipts whose queue write failed. Best effort; never stops delivery.\n await repairDeliveryAt(dir, outbox, now());\n const before = outbox.status().pending;\n // The record numbers are signed with the same enrolled key that signs the receipts and summaries.\n const summaries = summaryOptions(dir);\n const sequenceProof = sequenceProofFor(connection, summaries?.attester);\n const exporter = createCloudExporter({\n url: ingestUrl(connection), credential: connection.credential, outbox,\n flushMs: 24 * 60 * 60 * 1000, fetch: options.fetchImpl, now, summaries, requestTimeoutMs: options.deliveryTimeoutMs ?? 30_000,\n ...(sequenceProof ? { sequenceProof } : {}),\n // The chain head each answer carries is kept beside the hook's receipts (chain-heads.json).\n onChainHead: chainHeadRecorder(join(dir, CHAIN_HEADS_FILE)),\n });\n try {\n const lastSuccess = exporter.status().lastSuccessAt;\n const started = Date.now();\n const deliveryMs = options.deliveryMs ?? CYCLE_DELIVERY_MS;\n await exporter.flush({ maxMs: deliveryMs });\n const status = exporter.status();\n result.more = status.pending > 0 && status.lastError === null && Date.now() - started >= deliveryMs;\n recordDeliveryAttempt(dir, status, now(), lastSuccess);\n result.pending = status.pending;\n result.delivered = Math.max(0, before - status.pending);\n result.deliveryError = status.lastError;\n } finally { exporter.stop(); }\n } catch (error) {\n result.deliveryError = (error as Error).message;\n }\n\n // 2. The rules check: the workspace's rules, the queue report, and credential renewal when due.\n try {\n const keyFile = join(dir, \"agent.key\");\n if (existsSync(keyFile)) {\n const agentKid = createSigner({ privateKeyPem: readFileSync(keyFile, \"utf8\") }).kid;\n const outcome = await syncPolicy(dir, { agentKid, hookVersion: hookVersion(), policyBuilds, fetchImpl: options.fetchImpl, timeoutMs: options.timeoutMs ?? 10_000, onRequest: (kind) => { result.requested = kind; } });\n result.rules = outcome.state;\n }\n } catch { result.rules = \"unavailable\"; }\n return result;\n}\n\n/** The machine-readable status (scopebond.status.v1) for this computer, as the hook reports it. */\nexport function computerStatus(dir: string, now?: number): StatusJson {\n const expected = expectedHarnesses();\n const configured = (h: Harness) => expected.includes(h) && existsSync(userHarnessFile(h)) && isHarnessConfigured(userHarnessFile(h));\n return buildStatusJson({\n version: hookVersion(), activeDir: dir, candidateDirs: [dir], hasPolicy: existsSync(join(dir, \"policy.json\")),\n agents: { claude: configured(\"claude\"), cursor: configured(\"cursor\"), codex: configured(\"codex\") }, now,\n });\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const UNINSTALL_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ScopebondAgent";
|
|
2
|
+
export declare const UNINSTALL_SCRIPT = "uninstall-agent.ps1";
|
|
3
|
+
export declare const DISPLAY_NAME = "Scopebond Agent";
|
|
4
|
+
export declare const PUBLISHER = "Avouro LLC";
|
|
5
|
+
/** A PowerShell single-quoted string. PowerShell ends such a string at any of its single quotes, the ASCII one and the
|
|
6
|
+
* typographic ones (U+2018, U+2019, U+201A, U+201B), and reads each one doubled as itself; a profile folder named
|
|
7
|
+
* O’Brien must not end the string early. */
|
|
8
|
+
export declare const psQuote: (text: string) => string;
|
|
9
|
+
/** The byte-order mark the script is written with: without it, Windows PowerShell 5.1 reads the script in the ANSI code
|
|
10
|
+
* page and a non-ASCII path (a profile folder named Jörg) no longer names the folder. */
|
|
11
|
+
export declare const UTF8_BOM = "\uFEFF";
|
|
12
|
+
/** The script the entry's Uninstall runs. Paths reach PowerShell only as quoted data. It says "removed" only when the
|
|
13
|
+
* agent's own uninstall ran and succeeded and npm removed the package; otherwise it says what did not happen and exits 1.
|
|
14
|
+
* - The agent is not there (npm uninstall -g ran first): its own uninstall cannot run, so autostart, the hook entries and
|
|
15
|
+
* the workspace may still need it; npm's uninstall still runs and the entry is still removed, but the exit code is 1.
|
|
16
|
+
* - The agent's uninstall fails: the package stays, so `scopebond-agent.cmd uninstall` can be run again to see why. */
|
|
17
|
+
export declare function uninstallScript(o: {
|
|
18
|
+
node: string;
|
|
19
|
+
cli: string;
|
|
20
|
+
npm: string;
|
|
21
|
+
key?: string;
|
|
22
|
+
}): string;
|
|
23
|
+
/** The registry values of the entry: [name, type, data]. */
|
|
24
|
+
export declare function appsEntryValues(o: {
|
|
25
|
+
version: string;
|
|
26
|
+
script: string;
|
|
27
|
+
home: string;
|
|
28
|
+
}): Array<[string, "REG_SZ" | "REG_DWORD", string]>;
|
|
29
|
+
/** npm beside the Node that runs the agent (where the Node installer puts it), else npm.cmd on PATH. */
|
|
30
|
+
export declare function npmBeside(node: string): string;
|
|
31
|
+
/** Write the uninstall script (UTF-8 with a byte-order mark) into the Scopebond folder; returns its path. */
|
|
32
|
+
export declare function writeUninstallScript(home: string, o: {
|
|
33
|
+
node: string;
|
|
34
|
+
cli: string;
|
|
35
|
+
npm: string;
|
|
36
|
+
key?: string;
|
|
37
|
+
}): string;
|
|
38
|
+
/** Write the entry and its script for this user. Windows only; returns a one-line description, or null elsewhere. */
|
|
39
|
+
export declare function writeAppsEntry(home: string, o: {
|
|
40
|
+
version: string;
|
|
41
|
+
cli: string;
|
|
42
|
+
node?: string;
|
|
43
|
+
}, platform?: NodeJS.Platform): string | null;
|
|
44
|
+
/** Remove the entry and its script. Windows only; never throws. */
|
|
45
|
+
export declare function removeAppsEntry(home: string, platform?: NodeJS.Platform): string | null;
|
|
46
|
+
//# sourceMappingURL=apps-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apps-entry.d.ts","sourceRoot":"","sources":["../src/apps-entry.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,aAAa,kFAAkF,CAAC;AAC7G,eAAO,MAAM,gBAAgB,wBAAwB,CAAC;AACtD,eAAO,MAAM,YAAY,oBAAoB,CAAC;AAC9C,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;6CAE6C;AAC7C,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,WAAgE,CAAC;AAErG;0FAC0F;AAC1F,eAAO,MAAM,QAAQ,WAAW,CAAC;AAEjC;;;;wHAIwH;AACxH,wBAAgB,eAAe,CAAC,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAwCnG;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,QAAQ,GAAG,WAAW,EAAE,MAAM,CAAC,CAAC,CAarI;AAED,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAKtH;AAED,qHAAqH;AACrH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,kBAAmB,GAAG,MAAM,GAAG,IAAI,CAQ3I;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,kBAAmB,GAAG,MAAM,GAAG,IAAI,CAKxF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Scopebond in Windows Settings → Apps → Installed apps, for an agent installed with npm. The signed installer has its own
|
|
2
|
+
// entry (Windows writes it for the MSI); an npm install had none, so a person looking for Scopebond in Settings did not find
|
|
3
|
+
// it. `setup` and `autostart on` write a per-user entry (no administrator rights) whose Uninstall runs a small PowerShell
|
|
4
|
+
// script: the agent's own `uninstall` (autostart off, the agent stopped, the hook taken out of the coding agents' settings,
|
|
5
|
+
// the workspace told), then `npm uninstall -g @scopebond/agent`. `uninstall` removes the entry. The Scopebond folder (keys,
|
|
6
|
+
// the connection, records not yet sent) stays, so installing again is the same computer; `uninstall --purge` deletes it.
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
export const UNINSTALL_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ScopebondAgent";
|
|
11
|
+
export const UNINSTALL_SCRIPT = "uninstall-agent.ps1";
|
|
12
|
+
export const DISPLAY_NAME = "Scopebond Agent";
|
|
13
|
+
export const PUBLISHER = "Avouro LLC";
|
|
14
|
+
/** A PowerShell single-quoted string. PowerShell ends such a string at any of its single quotes, the ASCII one and the
|
|
15
|
+
* typographic ones (U+2018, U+2019, U+201A, U+201B), and reads each one doubled as itself; a profile folder named
|
|
16
|
+
* O’Brien must not end the string early. */
|
|
17
|
+
export const psQuote = (text) => `'${text.replace(/['\u2018\u2019\u201A\u201B]/g, "$&$&")}'`;
|
|
18
|
+
/** The byte-order mark the script is written with: without it, Windows PowerShell 5.1 reads the script in the ANSI code
|
|
19
|
+
* page and a non-ASCII path (a profile folder named Jörg) no longer names the folder. */
|
|
20
|
+
export const UTF8_BOM = "\uFEFF";
|
|
21
|
+
/** The script the entry's Uninstall runs. Paths reach PowerShell only as quoted data. It says "removed" only when the
|
|
22
|
+
* agent's own uninstall ran and succeeded and npm removed the package; otherwise it says what did not happen and exits 1.
|
|
23
|
+
* - The agent is not there (npm uninstall -g ran first): its own uninstall cannot run, so autostart, the hook entries and
|
|
24
|
+
* the workspace may still need it; npm's uninstall still runs and the entry is still removed, but the exit code is 1.
|
|
25
|
+
* - The agent's uninstall fails: the package stays, so `scopebond-agent.cmd uninstall` can be run again to see why. */
|
|
26
|
+
export function uninstallScript(o) {
|
|
27
|
+
return `# Scopebond Agent, installed with npm: what Windows Settings -> Apps -> Scopebond Agent -> Uninstall runs.
|
|
28
|
+
# The Scopebond folder (keys, connection, records not yet sent) stays; delete it with: scopebond-agent uninstall --purge
|
|
29
|
+
$ErrorActionPreference = 'Continue'
|
|
30
|
+
Write-Host 'Removing the Scopebond Agent...'
|
|
31
|
+
$node = ${psQuote(o.node)}
|
|
32
|
+
$cli = ${psQuote(o.cli)}
|
|
33
|
+
$npm = ${psQuote(o.npm)}
|
|
34
|
+
$problems = @()
|
|
35
|
+
$agentRan = $false
|
|
36
|
+
if (-not (Test-Path -LiteralPath $cli)) {
|
|
37
|
+
$problems += "The agent is not at $cli, so its own uninstall did not run: autostart, the hook in the coding agents' settings and the workspace may still need it. Install it again (npm.cmd install -g @scopebond/agent) and run: scopebond-agent.cmd uninstall"
|
|
38
|
+
} else {
|
|
39
|
+
try {
|
|
40
|
+
& $node --disable-warning=ExperimentalWarning $cli uninstall
|
|
41
|
+
if ($LASTEXITCODE -eq 0) { $agentRan = $true }
|
|
42
|
+
else { $problems += "The agent's own uninstall stopped with exit code $LASTEXITCODE. The agent is still installed; run it again to see why: scopebond-agent.cmd uninstall" }
|
|
43
|
+
} catch {
|
|
44
|
+
$problems += "The agent's own uninstall could not start ($($_.Exception.Message)). Run: scopebond-agent.cmd uninstall"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if ($agentRan -or -not (Test-Path -LiteralPath $cli)) {
|
|
48
|
+
try {
|
|
49
|
+
& $npm uninstall -g @scopebond/agent
|
|
50
|
+
if ($LASTEXITCODE -ne 0) { $problems += "npm.cmd uninstall -g @scopebond/agent stopped with exit code $LASTEXITCODE." }
|
|
51
|
+
} catch {
|
|
52
|
+
$problems += "npm.cmd uninstall -g @scopebond/agent could not start ($($_.Exception.Message))."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
& reg.exe delete ${psQuote(o.key ?? UNINSTALL_KEY)} /f 2>$null | Out-Null
|
|
56
|
+
if ($problems.Count -eq 0) {
|
|
57
|
+
Write-Host 'The Scopebond Agent is removed.'
|
|
58
|
+
if (-not $env:SCOPEBOND_NO_PAUSE) { Start-Sleep -Seconds 3 }
|
|
59
|
+
exit 0
|
|
60
|
+
}
|
|
61
|
+
Write-Host 'The Scopebond Agent is not fully removed:'
|
|
62
|
+
foreach ($problem in $problems) { Write-Host " - $problem" }
|
|
63
|
+
if (-not $env:SCOPEBOND_NO_PAUSE) { Start-Sleep -Seconds 15 }
|
|
64
|
+
exit 1
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
/** The registry values of the entry: [name, type, data]. */
|
|
68
|
+
export function appsEntryValues(o) {
|
|
69
|
+
const run = `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${o.script}"`;
|
|
70
|
+
return [
|
|
71
|
+
["DisplayName", "REG_SZ", DISPLAY_NAME],
|
|
72
|
+
["DisplayVersion", "REG_SZ", o.version],
|
|
73
|
+
["Publisher", "REG_SZ", PUBLISHER],
|
|
74
|
+
["URLInfoAbout", "REG_SZ", "https://scopebond.com"],
|
|
75
|
+
["InstallLocation", "REG_SZ", o.home],
|
|
76
|
+
["UninstallString", "REG_SZ", run],
|
|
77
|
+
["QuietUninstallString", "REG_SZ", `${run.replace("-File", "-WindowStyle Hidden -File")}`],
|
|
78
|
+
["NoModify", "REG_DWORD", "1"],
|
|
79
|
+
["NoRepair", "REG_DWORD", "1"],
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
/** npm beside the Node that runs the agent (where the Node installer puts it), else npm.cmd on PATH. */
|
|
83
|
+
export function npmBeside(node) {
|
|
84
|
+
const beside = join(dirname(node), "npm.cmd");
|
|
85
|
+
return existsSync(beside) ? beside : "npm.cmd";
|
|
86
|
+
}
|
|
87
|
+
/** Write the uninstall script (UTF-8 with a byte-order mark) into the Scopebond folder; returns its path. */
|
|
88
|
+
export function writeUninstallScript(home, o) {
|
|
89
|
+
const script = join(home, UNINSTALL_SCRIPT);
|
|
90
|
+
// Readable and writable by this user only (on Windows the folder's own access list decides; elsewhere the mode does).
|
|
91
|
+
writeFileSync(script, UTF8_BOM + uninstallScript(o), { encoding: "utf8", mode: 0o600 });
|
|
92
|
+
return script;
|
|
93
|
+
}
|
|
94
|
+
/** Write the entry and its script for this user. Windows only; returns a one-line description, or null elsewhere. */
|
|
95
|
+
export function writeAppsEntry(home, o, platform = process.platform) {
|
|
96
|
+
if (platform !== "win32")
|
|
97
|
+
return null;
|
|
98
|
+
const node = o.node ?? process.execPath;
|
|
99
|
+
const script = writeUninstallScript(home, { node, cli: o.cli, npm: npmBeside(node) });
|
|
100
|
+
for (const [name, type, data] of appsEntryValues({ version: o.version, script, home })) {
|
|
101
|
+
execFileSync("reg", ["add", UNINSTALL_KEY, "/v", name, "/t", type, "/d", data, "/f"], { stdio: "ignore" });
|
|
102
|
+
}
|
|
103
|
+
return `listed ${DISPLAY_NAME} in Settings -> Apps (uninstall removes it)`;
|
|
104
|
+
}
|
|
105
|
+
/** Remove the entry and its script. Windows only; never throws. */
|
|
106
|
+
export function removeAppsEntry(home, platform = process.platform) {
|
|
107
|
+
if (platform !== "win32")
|
|
108
|
+
return null;
|
|
109
|
+
rmSync(join(home, UNINSTALL_SCRIPT), { force: true });
|
|
110
|
+
try {
|
|
111
|
+
execFileSync("reg", ["delete", UNINSTALL_KEY, "/f"], { stdio: "ignore" });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return `removed ${DISPLAY_NAME} from Settings -> Apps`;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=apps-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apps-entry.js","sourceRoot":"","sources":["../src/apps-entry.ts"],"names":[],"mappings":"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","sourcesContent":["// Scopebond in Windows Settings → Apps → Installed apps, for an agent installed with npm. The signed installer has its own\n// entry (Windows writes it for the MSI); an npm install had none, so a person looking for Scopebond in Settings did not find\n// it. `setup` and `autostart on` write a per-user entry (no administrator rights) whose Uninstall runs a small PowerShell\n// script: the agent's own `uninstall` (autostart off, the agent stopped, the hook taken out of the coding agents' settings,\n// the workspace told), then `npm uninstall -g @scopebond/agent`. `uninstall` removes the entry. The Scopebond folder (keys,\n// the connection, records not yet sent) stays, so installing again is the same computer; `uninstall --purge` deletes it.\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport const UNINSTALL_KEY = \"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\ScopebondAgent\";\nexport const UNINSTALL_SCRIPT = \"uninstall-agent.ps1\";\nexport const DISPLAY_NAME = \"Scopebond Agent\";\nexport const PUBLISHER = \"Avouro LLC\";\n\n/** A PowerShell single-quoted string. PowerShell ends such a string at any of its single quotes, the ASCII one and the\n * typographic ones (U+2018, U+2019, U+201A, U+201B), and reads each one doubled as itself; a profile folder named\n * O’Brien must not end the string early. */\nexport const psQuote = (text: string) => `'${text.replace(/['\\u2018\\u2019\\u201A\\u201B]/g, \"$&$&\")}'`;\n\n/** The byte-order mark the script is written with: without it, Windows PowerShell 5.1 reads the script in the ANSI code\n * page and a non-ASCII path (a profile folder named Jörg) no longer names the folder. */\nexport const UTF8_BOM = \"\\uFEFF\";\n\n/** The script the entry's Uninstall runs. Paths reach PowerShell only as quoted data. It says \"removed\" only when the\n * agent's own uninstall ran and succeeded and npm removed the package; otherwise it says what did not happen and exits 1.\n * - The agent is not there (npm uninstall -g ran first): its own uninstall cannot run, so autostart, the hook entries and\n * the workspace may still need it; npm's uninstall still runs and the entry is still removed, but the exit code is 1.\n * - The agent's uninstall fails: the package stays, so `scopebond-agent.cmd uninstall` can be run again to see why. */\nexport function uninstallScript(o: { node: string; cli: string; npm: string; key?: string }): string {\n return `# Scopebond Agent, installed with npm: what Windows Settings -> Apps -> Scopebond Agent -> Uninstall runs.\n# The Scopebond folder (keys, connection, records not yet sent) stays; delete it with: scopebond-agent uninstall --purge\n$ErrorActionPreference = 'Continue'\nWrite-Host 'Removing the Scopebond Agent...'\n$node = ${psQuote(o.node)}\n$cli = ${psQuote(o.cli)}\n$npm = ${psQuote(o.npm)}\n$problems = @()\n$agentRan = $false\nif (-not (Test-Path -LiteralPath $cli)) {\n $problems += \"The agent is not at $cli, so its own uninstall did not run: autostart, the hook in the coding agents' settings and the workspace may still need it. Install it again (npm.cmd install -g @scopebond/agent) and run: scopebond-agent.cmd uninstall\"\n} else {\n try {\n & $node --disable-warning=ExperimentalWarning $cli uninstall\n if ($LASTEXITCODE -eq 0) { $agentRan = $true }\n else { $problems += \"The agent's own uninstall stopped with exit code $LASTEXITCODE. The agent is still installed; run it again to see why: scopebond-agent.cmd uninstall\" }\n } catch {\n $problems += \"The agent's own uninstall could not start ($($_.Exception.Message)). Run: scopebond-agent.cmd uninstall\"\n }\n}\nif ($agentRan -or -not (Test-Path -LiteralPath $cli)) {\n try {\n & $npm uninstall -g @scopebond/agent\n if ($LASTEXITCODE -ne 0) { $problems += \"npm.cmd uninstall -g @scopebond/agent stopped with exit code $LASTEXITCODE.\" }\n } catch {\n $problems += \"npm.cmd uninstall -g @scopebond/agent could not start ($($_.Exception.Message)).\"\n }\n}\n& reg.exe delete ${psQuote(o.key ?? UNINSTALL_KEY)} /f 2>$null | Out-Null\nif ($problems.Count -eq 0) {\n Write-Host 'The Scopebond Agent is removed.'\n if (-not $env:SCOPEBOND_NO_PAUSE) { Start-Sleep -Seconds 3 }\n exit 0\n}\nWrite-Host 'The Scopebond Agent is not fully removed:'\nforeach ($problem in $problems) { Write-Host \" - $problem\" }\nif (-not $env:SCOPEBOND_NO_PAUSE) { Start-Sleep -Seconds 15 }\nexit 1\n`;\n}\n\n/** The registry values of the entry: [name, type, data]. */\nexport function appsEntryValues(o: { version: string; script: string; home: string }): Array<[string, \"REG_SZ\" | \"REG_DWORD\", string]> {\n const run = `powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"${o.script}\"`;\n return [\n [\"DisplayName\", \"REG_SZ\", DISPLAY_NAME],\n [\"DisplayVersion\", \"REG_SZ\", o.version],\n [\"Publisher\", \"REG_SZ\", PUBLISHER],\n [\"URLInfoAbout\", \"REG_SZ\", \"https://scopebond.com\"],\n [\"InstallLocation\", \"REG_SZ\", o.home],\n [\"UninstallString\", \"REG_SZ\", run],\n [\"QuietUninstallString\", \"REG_SZ\", `${run.replace(\"-File\", \"-WindowStyle Hidden -File\")}`],\n [\"NoModify\", \"REG_DWORD\", \"1\"],\n [\"NoRepair\", \"REG_DWORD\", \"1\"],\n ];\n}\n\n/** npm beside the Node that runs the agent (where the Node installer puts it), else npm.cmd on PATH. */\nexport function npmBeside(node: string): string {\n const beside = join(dirname(node), \"npm.cmd\");\n return existsSync(beside) ? beside : \"npm.cmd\";\n}\n\n/** Write the uninstall script (UTF-8 with a byte-order mark) into the Scopebond folder; returns its path. */\nexport function writeUninstallScript(home: string, o: { node: string; cli: string; npm: string; key?: string }): string {\n const script = join(home, UNINSTALL_SCRIPT);\n // Readable and writable by this user only (on Windows the folder's own access list decides; elsewhere the mode does).\n writeFileSync(script, UTF8_BOM + uninstallScript(o), { encoding: \"utf8\", mode: 0o600 });\n return script;\n}\n\n/** Write the entry and its script for this user. Windows only; returns a one-line description, or null elsewhere. */\nexport function writeAppsEntry(home: string, o: { version: string; cli: string; node?: string }, platform = process.platform): string | null {\n if (platform !== \"win32\") return null;\n const node = o.node ?? process.execPath;\n const script = writeUninstallScript(home, { node, cli: o.cli, npm: npmBeside(node) });\n for (const [name, type, data] of appsEntryValues({ version: o.version, script, home })) {\n execFileSync(\"reg\", [\"add\", UNINSTALL_KEY, \"/v\", name, \"/t\", type, \"/d\", data, \"/f\"], { stdio: \"ignore\" });\n }\n return `listed ${DISPLAY_NAME} in Settings -> Apps (uninstall removes it)`;\n}\n\n/** Remove the entry and its script. Windows only; never throws. */\nexport function removeAppsEntry(home: string, platform = process.platform): string | null {\n if (platform !== \"win32\") return null;\n rmSync(join(home, UNINSTALL_SCRIPT), { force: true });\n try { execFileSync(\"reg\", [\"delete\", UNINSTALL_KEY, \"/f\"], { stdio: \"ignore\" }); } catch { return null; }\n return `removed ${DISPLAY_NAME} from Settings -> Apps`;\n}\n"]}
|
package/dist/autostart.d.ts
CHANGED
|
@@ -27,13 +27,19 @@ export declare function refreshLauncher(scopebondHome: string, cli: string, node
|
|
|
27
27
|
* headless console host does not start (seen on Windows Server). macOS and Linux start it themselves
|
|
28
28
|
* when autostart is turned on (RunAtLoad, enable --now). */
|
|
29
29
|
export declare function startCommands(launcher: string, platform?: NodeJS.Platform): Array<[string, string[]]>;
|
|
30
|
-
/**
|
|
31
|
-
export
|
|
30
|
+
/** Runs `reg.exe` with these arguments; whether it succeeded. Tests pass their own. */
|
|
31
|
+
export type RegRunner = (args: string[]) => boolean;
|
|
32
|
+
export declare const TRAY_RUN_VALUE = "Scopebond";
|
|
33
|
+
/** The launcher's Run value is not used beside the native tray. Whether there was one to remove. */
|
|
34
|
+
export declare function retireLauncherRunValue(reg?: RegRunner): boolean;
|
|
35
|
+
/** Start the agent now with the `attempt`-th way (0 first), detached from this terminal. Returns whether it tried one.
|
|
36
|
+
* Beside the native tray, the one way is to start the tray (it starts the agent; a second tray gives way to the first). */
|
|
37
|
+
export declare function startNow(scopebondHome: string, platform?: NodeJS.Platform, attempt?: number, tray?: string | null): boolean;
|
|
32
38
|
/** Turn autostart on for this user. Returns a one-line description of what changed. */
|
|
33
|
-
export declare function enableAutostart(scopebondHome: string, cli: string, node?: string, platform?: NodeJS.Platform): string;
|
|
34
|
-
export declare function disableAutostart(scopebondHome: string, platform?: NodeJS.Platform): string;
|
|
39
|
+
export declare function enableAutostart(scopebondHome: string, cli: string, node?: string, platform?: NodeJS.Platform, tray?: string | null, reg?: RegRunner): string;
|
|
40
|
+
export declare function disableAutostart(scopebondHome: string, platform?: NodeJS.Platform, tray?: string | null, reg?: RegRunner): string;
|
|
35
41
|
/** Whether autostart is on and its launcher can start the agent now. */
|
|
36
|
-
export declare function autostartHealth(scopebondHome: string, platform?: NodeJS.Platform): {
|
|
42
|
+
export declare function autostartHealth(scopebondHome: string, platform?: NodeJS.Platform, tray?: string | null, reg?: RegRunner): {
|
|
37
43
|
on: boolean;
|
|
38
44
|
ok: boolean;
|
|
39
45
|
detail: string;
|
package/dist/autostart.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autostart.d.ts","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"autostart.d.ts","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,KAAK,wBAAwB,CAAC;AAO3C,8HAA8H;AAC9H,eAAO,MAAM,aAAa,wBAAwB,CAAC;AAEnD;gGACgG;AAChG,eAAO,MAAM,aAAa,oCAAoC,CAAC;AAE/D,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,OAAO,CAErG;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,MAAM,CAE/F;AAED,6HAA6H;AAC7H,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAyClF;AAED,qGAAqG;AACrG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAsB/D;AAOD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAexE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CActD;AAED,wBAAgB,cAAc,CAAC,IAAI,SAAY;;;EAK9C;AAED;;sCAEsC;AACtC,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,SAAmB,EAAE,QAAQ,kBAAmB,GAAG,OAAO,CAOjI;AAaD;;;6DAG6D;AAC7D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAIvH;AAMD,uFAAuF;AACvF,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;AAIpD,eAAO,MAAM,cAAc,cAAc,CAAC;AAM1C,oGAAoG;AACpG,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,SAAkB,GAAG,OAAO,CAEvE;AAED;4HAC4H;AAC5H,wBAAgB,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,kBAAmB,EAAE,OAAO,SAAI,EAAE,IAAI,GAAE,MAAM,GAAG,IAAwB,GAAG,OAAO,CAmB1I;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,SAAmB,EAAE,QAAQ,kBAAmB,EAAE,IAAI,GAAE,MAAM,GAAG,IAAwB,EAAE,GAAG,GAAE,SAAkB,GAAG,MAAM,CAwBlM;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,kBAAmB,EAAE,IAAI,GAAE,MAAM,GAAG,IAAwB,EAAE,GAAG,GAAE,SAAkB,GAAG,MAAM,CAuB7J;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,kBAAmB,EAAE,IAAI,GAAE,MAAM,GAAG,IAAwB,EAAE,GAAG,GAAE,SAAkB,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAiBlM"}
|
package/dist/autostart.js
CHANGED
|
@@ -10,6 +10,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync }
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { dirname, join, win32 } from "node:path";
|
|
12
12
|
import { isSingleExecutable } from "@scopebond/hook";
|
|
13
|
+
import { nativeTrayPath } from "./native-update.js";
|
|
13
14
|
export const LABEL = "com.scopebond.agent";
|
|
14
15
|
const RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
15
16
|
const RUN_VALUE = "ScopebondAgent";
|
|
@@ -180,8 +181,40 @@ export function startCommands(launcher, platform = process.platform) {
|
|
|
180
181
|
// Passed verbatim (startNow sets windowsVerbatimArguments): Node's own quoting follows other rules than cmd's.
|
|
181
182
|
return [["conhost.exe", ["--headless", "cmd.exe", cmdRun(launcher)]], ["cmd.exe", [cmdRun(launcher)]]];
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
const runReg = (args) => {
|
|
185
|
+
try {
|
|
186
|
+
execFileSync("reg", args, { stdio: "ignore", windowsHide: true });
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
export const TRAY_RUN_VALUE = "Scopebond";
|
|
194
|
+
const MACHINE_RUN_KEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
195
|
+
const trayFor = (platform) => (platform === "win32" ? nativeTrayPath() : null);
|
|
196
|
+
/** The tray's Run value set for every user (an install with ALLUSERS=1): only whoever installed it changes it. */
|
|
197
|
+
const trayForEveryone = (reg) => reg(["query", MACHINE_RUN_KEY, "/v", TRAY_RUN_VALUE]);
|
|
198
|
+
/** The launcher's Run value is not used beside the native tray. Whether there was one to remove. */
|
|
199
|
+
export function retireLauncherRunValue(reg = runReg) {
|
|
200
|
+
return reg(["query", RUN_KEY, "/v", RUN_VALUE]) && reg(["delete", RUN_KEY, "/v", RUN_VALUE, "/f"]);
|
|
201
|
+
}
|
|
202
|
+
/** Start the agent now with the `attempt`-th way (0 first), detached from this terminal. Returns whether it tried one.
|
|
203
|
+
* Beside the native tray, the one way is to start the tray (it starts the agent; a second tray gives way to the first). */
|
|
204
|
+
export function startNow(scopebondHome, platform = process.platform, attempt = 0, tray = trayFor(platform)) {
|
|
205
|
+
if (tray) {
|
|
206
|
+
if (attempt > 0)
|
|
207
|
+
return false;
|
|
208
|
+
try {
|
|
209
|
+
const child = spawn(tray, [], { detached: true, stdio: "ignore", windowsHide: true });
|
|
210
|
+
child.on("error", () => { });
|
|
211
|
+
child.unref();
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
185
218
|
const launcher = launcherPath(scopebondHome, platform);
|
|
186
219
|
const command = startCommands(launcher, platform)[attempt];
|
|
187
220
|
if (!command || !existsSync(launcher))
|
|
@@ -197,7 +230,15 @@ export function startNow(scopebondHome, platform = process.platform, attempt = 0
|
|
|
197
230
|
}
|
|
198
231
|
}
|
|
199
232
|
/** Turn autostart on for this user. Returns a one-line description of what changed. */
|
|
200
|
-
export function enableAutostart(scopebondHome, cli, node = process.execPath, platform = process.platform) {
|
|
233
|
+
export function enableAutostart(scopebondHome, cli, node = process.execPath, platform = process.platform, tray = trayFor(platform), reg = runReg) {
|
|
234
|
+
if (tray) {
|
|
235
|
+
retireLauncherRunValue(reg);
|
|
236
|
+
if (trayForEveryone(reg))
|
|
237
|
+
return "the Scopebond tray starts for every user of this computer and keeps the agent running";
|
|
238
|
+
if (!reg(["add", RUN_KEY, "/v", TRAY_RUN_VALUE, "/t", "REG_SZ", "/d", `"${noQuotes(tray)}"`, "/f"]))
|
|
239
|
+
throw new Error(`could not add ${TRAY_RUN_VALUE} to ${RUN_KEY}`);
|
|
240
|
+
return `added ${TRAY_RUN_VALUE} to ${RUN_KEY}: the Scopebond tray starts with your sign-in and keeps the agent running`;
|
|
241
|
+
}
|
|
201
242
|
const launcher = writeLauncher(scopebondHome, node, cli, platform);
|
|
202
243
|
const paths = autostartPaths();
|
|
203
244
|
if (platform === "win32") {
|
|
@@ -224,9 +265,16 @@ export function enableAutostart(scopebondHome, cli, node = process.execPath, pla
|
|
|
224
265
|
}
|
|
225
266
|
return `wrote and enabled ${paths.linuxUnit}`;
|
|
226
267
|
}
|
|
227
|
-
export function disableAutostart(scopebondHome, platform = process.platform) {
|
|
268
|
+
export function disableAutostart(scopebondHome, platform = process.platform, tray = trayFor(platform), reg = runReg) {
|
|
228
269
|
const paths = autostartPaths();
|
|
229
270
|
rmSync(launcherPath(scopebondHome, platform), { force: true });
|
|
271
|
+
if (tray) {
|
|
272
|
+
const launcherValue = retireLauncherRunValue(reg);
|
|
273
|
+
const trayValue = reg(["query", RUN_KEY, "/v", TRAY_RUN_VALUE]) && reg(["delete", RUN_KEY, "/v", TRAY_RUN_VALUE, "/f"]);
|
|
274
|
+
if (trayValue || launcherValue)
|
|
275
|
+
return `removed ${trayValue ? TRAY_RUN_VALUE : RUN_VALUE} from ${RUN_KEY}`;
|
|
276
|
+
return trayForEveryone(reg) ? "Scopebond starts for every user of this computer; whoever installed it changes that" : "autostart was not on";
|
|
277
|
+
}
|
|
230
278
|
if (platform === "win32") {
|
|
231
279
|
try {
|
|
232
280
|
execFileSync("reg", ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"], { stdio: "ignore" });
|
|
@@ -256,7 +304,13 @@ export function disableAutostart(scopebondHome, platform = process.platform) {
|
|
|
256
304
|
return `removed ${paths.linuxUnit}`;
|
|
257
305
|
}
|
|
258
306
|
/** Whether autostart is on and its launcher can start the agent now. */
|
|
259
|
-
export function autostartHealth(scopebondHome, platform = process.platform) {
|
|
307
|
+
export function autostartHealth(scopebondHome, platform = process.platform, tray = trayFor(platform), reg = runReg) {
|
|
308
|
+
if (tray) {
|
|
309
|
+
const on = reg(["query", RUN_KEY, "/v", TRAY_RUN_VALUE]) || trayForEveryone(reg);
|
|
310
|
+
return on
|
|
311
|
+
? { on, ok: true, detail: "starts with sign-in (the Scopebond tray keeps the agent running)" }
|
|
312
|
+
: { on, ok: false, detail: "the Scopebond tray does not start with sign-in (turn on Start with Windows in its menu, or run: scopebond-agent.exe autostart on)" };
|
|
313
|
+
}
|
|
260
314
|
const paths = autostartPaths();
|
|
261
315
|
let on = false;
|
|
262
316
|
if (platform === "win32") {
|
package/dist/autostart.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"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","sourcesContent":["// Start the agent when the person signs in to the computer, per user, with no administrator\n// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux.\n//\n// Each of them starts a small launcher script in the Scopebond home, not Node directly. The\n// launcher finds Node and the agent when it runs (the recorded paths first, then the system's),\n// so upgrading Node or switching versions never leaves an autostart entry pointing at nothing.\n// On Windows it runs under `conhost --headless`, so no window opens at sign-in.\n\nimport { execFileSync, spawn } from \"node:child_process\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, win32 } from \"node:path\";\nimport { isSingleExecutable } from \"@scopebond/hook\";\n\nexport const LABEL = \"com.scopebond.agent\";\nconst RUN_KEY = \"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\";\nconst RUN_VALUE = \"ScopebondAgent\";\n\nconst xml = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\nconst noQuotes = (s: string) => s.replace(/\"/g, \"\");\n\n/** The agent's log, which the agent writes itself (`SCOPEBOND_AGENT_LOG`) when a Windows launcher or a handover starts it. */\nexport const AGENT_LOG_ENV = \"SCOPEBOND_AGENT_LOG\";\n\n/** Written into every Windows launcher this version makes. A launcher without it redirects the agent's output into\n * agent.log itself, which keeps a replacement agent from starting (see `spawnReplacement`). */\nexport const LAUNCHER_MARK = \"rem Scopebond Agent launcher 2:\";\n\n/** Whether a launcher's text is this version's (an older one is rewritten once no agent runs under it). */\nexport function launcherIsCurrent(text: string, platform: NodeJS.Platform = process.platform): boolean {\n return platform !== \"win32\" || text.includes(LAUNCHER_MARK);\n}\n\nexport function launcherPath(home: string, platform: NodeJS.Platform = process.platform): string {\n return join(home, platform === \"win32\" ? \"agent-launch.cmd\" : \"agent-launch.sh\");\n}\n\n/** The Windows launcher. With `cli` empty, `node` is the single executable: it is started as it is, with no Node to find. */\nexport function windowsLauncher(node: string, cli: string, logFile: string): string {\n const log = `\"%~dp0${win32.basename(noQuotes(logFile))}\"`;\n const single = cli === \"\";\n return [\n \"@echo off\",\n // cmd.exe reads a batch file in the console code page: switch to UTF-8 first, so a profile folder\n // with non-ASCII letters in the paths below reads as written.\n \"chcp 65001 >nul\",\n `${LAUNCHER_MARK} finds Node and the agent each time, so a Node upgrade never stops it.`,\n \"setlocal\",\n // The agent writes its own log. A `>> agent.log` here would hold the file shared for reading only, the old agent's\n // children inherit that handle, and a replacement started during an update could then never open it: cmd skips the\n // whole command line when a redirect fails. The log sits beside this launcher; %~dp0 keeps it right whatever the folder is called.\n `set \"${AGENT_LOG_ENV}=%~dp0${win32.basename(noQuotes(logFile))}\"`,\n `set \"NODE=${noQuotes(node)}\"`,\n ...(single ? [`if not exist \"%NODE%\" exit /b 1`] : [\n `if not exist \"%NODE%\" set \"NODE=\"`,\n `if not defined NODE for /f \"delims=\" %%i in ('where node 2^>nul') do if not defined NODE set \"NODE=%%i\"`,\n `set \"CLI=${noQuotes(cli)}\"`,\n `if not exist \"%CLI%\" for /f \"delims=\" %%i in ('npm.cmd root -g 2^>nul') do set \"CLI=%%i\\\\@scopebond\\\\agent\\\\dist\\\\cli.js\"`,\n `if not defined NODE exit /b 1`,\n ]),\n // Restart on failure, as launchd (KeepAlive) and systemd (Restart=on-failure) do: a crash restarts the agent after\n // 30 seconds, up to 50 times; a clean exit (stop, autostart off, an update handing over) ends the launcher.\n \"set /a TRIES=0\",\n \":run\",\n // (call) sets the error level to 1 first, so a command that never ran counts as a failure, not a clean stop.\n \"(call)\",\n single ? `\"%NODE%\" run >nul 2>&1` : `\"%NODE%\" --disable-warning=ExperimentalWarning \"%CLI%\" run >nul 2>&1`,\n `set \"CODE=%ERRORLEVEL%\"`,\n // An update's handover names the agent to wait for; a restart after a crash has nothing to wait for.\n `set \"SCOPEBOND_AGENT_AFTER_PID=\"`,\n \"if %CODE% EQU 0 exit /b 0\",\n \"set /a TRIES+=1\",\n `echo %DATE% %TIME% the agent stopped with exit code %CODE%; restarting in 30 seconds (attempt %TRIES% of 50) >> ${log}`,\n \"if %TRIES% GEQ 50 exit /b 1\",\n // ping waits without a console to read from (timeout.exe refuses to run under a headless console).\n \"ping -n 31 127.0.0.1 >nul\",\n \"goto run\",\n \"\",\n ].join(\"\\r\\n\");\n}\n\n/** The macOS/Linux launcher. With `cli` empty, `node` is the single executable, started as it is. */\nexport function posixLauncher(node: string, cli: string): string {\n const q = (s: string) => `'${s.replace(/'/g, \"'\\\\''\")}'`;\n if (cli === \"\") return `#!/bin/sh\n# Scopebond Agent launcher: the single executable.\nexec ${q(node)} run\n`;\n return `#!/bin/sh\n# Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.\nNODE=${q(node)}\nif [ ! -x \"$NODE\" ]; then\n [ -s \"$HOME/.nvm/nvm.sh\" ] && . \"$HOME/.nvm/nvm.sh\" >/dev/null 2>&1\n NODE=\"$(command -v node 2>/dev/null)\"\n for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do\n [ -n \"$NODE\" ] && break\n [ -x \"$candidate\" ] && NODE=\"$candidate\"\n done\nfi\nCLI=${q(cli)}\n[ -f \"$CLI\" ] || CLI=\"$(npm root -g 2>/dev/null)/@scopebond/agent/dist/cli.js\"\n[ -n \"$NODE\" ] || exit 1\nexec \"$NODE\" --disable-warning=ExperimentalWarning \"$CLI\" run\n`;\n}\n\n/** The Run value: the launcher under a headless console host, so nothing appears on screen. */\n/** `cmd /s /c \"\"<launcher>\"\"`: cmd drops only the outer quotes and runs the quoted path as it is, so a\n * profile folder with a space and a `(`, `)` or `&` (\"John (Work)\") still starts the launcher. */\nconst cmdRun = (launcher: string) => `/d /s /c \"\"${noQuotes(launcher)}\"\"`;\n\nexport function windowsRunCommand(launcher: string): string {\n return `conhost.exe --headless cmd.exe ${cmdRun(launcher)}`;\n}\n\nexport function macLaunchAgent(launcher: string, logFile: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>/bin/sh</string><string>${xml(launcher)}</string></array>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>StandardOutPath</key><string>${xml(logFile)}</string>\n <key>StandardErrorPath</key><string>${xml(logFile)}</string>\n</dict>\n</plist>\n`;\n}\n\nexport function linuxUserUnit(launcher: string): string {\n const q = (s: string) => `\"${s.replace(/([\"\\\\])/g, \"\\\\$1\")}\"`;\n return `[Unit]\nDescription=Scopebond Agent (delivers this computer's records to its Scopebond workspace)\nAfter=network-online.target\n\n[Service]\nExecStart=/bin/sh ${q(launcher)}\nRestart=on-failure\nRestartSec=30\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function autostartPaths(home = homedir()) {\n return {\n macPlist: join(home, \"Library\", \"LaunchAgents\", `${LABEL}.plist`),\n linuxUnit: join(home, \".config\", \"systemd\", \"user\", \"scopebond-agent.service\"),\n };\n}\n\n/** Rewrite an older Windows launcher as this version's, keeping autostart as it is. Only safe once no cmd.exe runs the old\n * one (cmd reads a batch file line by line from where it left off): the handover calls it after the old agent has exited.\n * Returns whether it rewrote one. */\nexport function refreshLauncher(scopebondHome: string, cli: string, node = process.execPath, platform = process.platform): boolean {\n const launcher = launcherPath(scopebondHome, platform);\n let text: string;\n try { text = readFileSync(launcher, \"utf8\"); } catch { return false; }\n if (launcherIsCurrent(text, platform)) return false;\n writeLauncher(scopebondHome, node, cli, platform);\n return true;\n}\n\nfunction writeLauncher(scopebondHome: string, node: string, cli: string, platform: NodeJS.Platform): string {\n // The single executable is both Node and the agent: the launcher starts it with `run`.\n if (isSingleExecutable()) { node = process.execPath; cli = \"\"; }\n mkdirSync(scopebondHome, { recursive: true });\n const launcher = launcherPath(scopebondHome, platform);\n const log = join(scopebondHome, \"agent.log\");\n writeFileSync(launcher, platform === \"win32\" ? windowsLauncher(node, cli, log) : posixLauncher(node, cli), { mode: 0o700 });\n if (platform !== \"win32\") chmodSync(launcher, 0o700);\n return launcher;\n}\n\n/** The ways to start the agent now on Windows, in order: the launcher under a headless console (no window,\n * as sign-in does), then the launcher through cmd.exe with its window hidden, for a session where the\n * headless console host does not start (seen on Windows Server). macOS and Linux start it themselves\n * when autostart is turned on (RunAtLoad, enable --now). */\nexport function startCommands(launcher: string, platform: NodeJS.Platform = process.platform): Array<[string, string[]]> {\n if (platform !== \"win32\") return [];\n // Passed verbatim (startNow sets windowsVerbatimArguments): Node's own quoting follows other rules than cmd's.\n return [[\"conhost.exe\", [\"--headless\", \"cmd.exe\", cmdRun(launcher)]], [\"cmd.exe\", [cmdRun(launcher)]]];\n}\n\n/** Start the agent now with the `attempt`-th way (0 first), detached from this terminal. Returns whether it tried one. */\nexport function startNow(scopebondHome: string, platform = process.platform, attempt = 0): boolean {\n const launcher = launcherPath(scopebondHome, platform);\n const command = startCommands(launcher, platform)[attempt];\n if (!command || !existsSync(launcher)) return false;\n try {\n const child = spawn(command[0], command[1], { detached: true, stdio: \"ignore\", windowsHide: true, windowsVerbatimArguments: true });\n child.on(\"error\", () => { /* the next way, or the next sign-in */ });\n child.unref();\n return true;\n } catch { return false; }\n}\n\n/** Turn autostart on for this user. Returns a one-line description of what changed. */\nexport function enableAutostart(scopebondHome: string, cli: string, node = process.execPath, platform = process.platform): string {\n const launcher = writeLauncher(scopebondHome, node, cli, platform);\n const paths = autostartPaths();\n if (platform === \"win32\") {\n execFileSync(\"reg\", [\"add\", RUN_KEY, \"/v\", RUN_VALUE, \"/t\", \"REG_SZ\", \"/d\", windowsRunCommand(launcher), \"/f\"], { stdio: \"ignore\" });\n return `added ${RUN_VALUE} to ${RUN_KEY} (launcher ${launcher})`;\n }\n if (platform === \"darwin\") {\n mkdirSync(dirname(paths.macPlist), { recursive: true });\n writeFileSync(paths.macPlist, macLaunchAgent(launcher, join(scopebondHome, \"agent.log\")));\n try { execFileSync(\"launchctl\", [\"load\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* loads at next sign-in */ }\n return `wrote ${paths.macPlist}`;\n }\n mkdirSync(dirname(paths.linuxUnit), { recursive: true });\n writeFileSync(paths.linuxUnit, linuxUserUnit(launcher));\n try { execFileSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { stdio: \"ignore\" }); execFileSync(\"systemctl\", [\"--user\", \"enable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); }\n catch { return `wrote ${paths.linuxUnit} (enable it with: systemctl --user enable --now scopebond-agent)`; }\n return `wrote and enabled ${paths.linuxUnit}`;\n}\n\nexport function disableAutostart(scopebondHome: string, platform = process.platform): string {\n const paths = autostartPaths();\n rmSync(launcherPath(scopebondHome, platform), { force: true });\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"delete\", RUN_KEY, \"/v\", RUN_VALUE, \"/f\"], { stdio: \"ignore\" }); } catch { return \"autostart was not on\"; }\n return `removed ${RUN_VALUE} from ${RUN_KEY}`;\n }\n if (platform === \"darwin\") {\n if (!existsSync(paths.macPlist)) return \"autostart was not on\";\n try { execFileSync(\"launchctl\", [\"unload\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* not loaded */ }\n rmSync(paths.macPlist, { force: true });\n return `removed ${paths.macPlist}`;\n }\n if (!existsSync(paths.linuxUnit)) return \"autostart was not on\";\n try { execFileSync(\"systemctl\", [\"--user\", \"disable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); } catch { /* not enabled */ }\n rmSync(paths.linuxUnit, { force: true });\n return `removed ${paths.linuxUnit}`;\n}\n\n/** Whether autostart is on and its launcher can start the agent now. */\nexport function autostartHealth(scopebondHome: string, platform = process.platform): { on: boolean; ok: boolean; detail: string } {\n const paths = autostartPaths();\n let on = false;\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"query\", RUN_KEY, \"/v\", RUN_VALUE], { stdio: \"ignore\" }); on = true; } catch { on = false; }\n } else on = existsSync(platform === \"darwin\" ? paths.macPlist : paths.linuxUnit);\n // The fix as the person types it: PowerShell blocks the plain scopebond-agent script shim.\n const fix = `${platform === \"win32\" ? \"scopebond-agent.cmd\" : \"scopebond-agent\"} autostart on`;\n if (!on) return { on, ok: false, detail: `the agent does not start with sign-in (run: ${fix})` };\n if (!existsSync(launcherPath(scopebondHome, platform))) return { on, ok: false, detail: `the autostart launcher is missing (run: ${fix})` };\n return { on, ok: true, detail: \"starts with sign-in\" };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../src/autostart.ts"],"names":[],"mappings":"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","sourcesContent":["// Start the agent when the person signs in to the computer, per user, with no administrator\n// rights: a Run entry on Windows, a LaunchAgent on macOS, a systemd user unit on Linux.\n//\n// Each of them starts a small launcher script in the Scopebond home, not Node directly. The\n// launcher finds Node and the agent when it runs (the recorded paths first, then the system's),\n// so upgrading Node or switching versions never leaves an autostart entry pointing at nothing.\n// On Windows it runs under `conhost --headless`, so no window opens at sign-in.\n\nimport { execFileSync, spawn } from \"node:child_process\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, win32 } from \"node:path\";\nimport { isSingleExecutable } from \"@scopebond/hook\";\nimport { nativeTrayPath } from \"./native-update.js\";\n\nexport const LABEL = \"com.scopebond.agent\";\nconst RUN_KEY = \"HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\";\nconst RUN_VALUE = \"ScopebondAgent\";\n\nconst xml = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\nconst noQuotes = (s: string) => s.replace(/\"/g, \"\");\n\n/** The agent's log, which the agent writes itself (`SCOPEBOND_AGENT_LOG`) when a Windows launcher or a handover starts it. */\nexport const AGENT_LOG_ENV = \"SCOPEBOND_AGENT_LOG\";\n\n/** Written into every Windows launcher this version makes. A launcher without it redirects the agent's output into\n * agent.log itself, which keeps a replacement agent from starting (see `spawnReplacement`). */\nexport const LAUNCHER_MARK = \"rem Scopebond Agent launcher 2:\";\n\n/** Whether a launcher's text is this version's (an older one is rewritten once no agent runs under it). */\nexport function launcherIsCurrent(text: string, platform: NodeJS.Platform = process.platform): boolean {\n return platform !== \"win32\" || text.includes(LAUNCHER_MARK);\n}\n\nexport function launcherPath(home: string, platform: NodeJS.Platform = process.platform): string {\n return join(home, platform === \"win32\" ? \"agent-launch.cmd\" : \"agent-launch.sh\");\n}\n\n/** The Windows launcher. With `cli` empty, `node` is the single executable: it is started as it is, with no Node to find. */\nexport function windowsLauncher(node: string, cli: string, logFile: string): string {\n const log = `\"%~dp0${win32.basename(noQuotes(logFile))}\"`;\n const single = cli === \"\";\n return [\n \"@echo off\",\n // cmd.exe reads a batch file in the console code page: switch to UTF-8 first, so a profile folder\n // with non-ASCII letters in the paths below reads as written.\n \"chcp 65001 >nul\",\n `${LAUNCHER_MARK} finds Node and the agent each time, so a Node upgrade never stops it.`,\n \"setlocal\",\n // The agent writes its own log. A `>> agent.log` here would hold the file shared for reading only, the old agent's\n // children inherit that handle, and a replacement started during an update could then never open it: cmd skips the\n // whole command line when a redirect fails. The log sits beside this launcher; %~dp0 keeps it right whatever the folder is called.\n `set \"${AGENT_LOG_ENV}=%~dp0${win32.basename(noQuotes(logFile))}\"`,\n `set \"NODE=${noQuotes(node)}\"`,\n ...(single ? [`if not exist \"%NODE%\" exit /b 1`] : [\n `if not exist \"%NODE%\" set \"NODE=\"`,\n `if not defined NODE for /f \"delims=\" %%i in ('where node 2^>nul') do if not defined NODE set \"NODE=%%i\"`,\n `set \"CLI=${noQuotes(cli)}\"`,\n `if not exist \"%CLI%\" for /f \"delims=\" %%i in ('npm.cmd root -g 2^>nul') do set \"CLI=%%i\\\\@scopebond\\\\agent\\\\dist\\\\cli.js\"`,\n `if not defined NODE exit /b 1`,\n ]),\n // Restart on failure, as launchd (KeepAlive) and systemd (Restart=on-failure) do: a crash restarts the agent after\n // 30 seconds, up to 50 times; a clean exit (stop, autostart off, an update handing over) ends the launcher.\n \"set /a TRIES=0\",\n \":run\",\n // (call) sets the error level to 1 first, so a command that never ran counts as a failure, not a clean stop.\n \"(call)\",\n single ? `\"%NODE%\" run >nul 2>&1` : `\"%NODE%\" --disable-warning=ExperimentalWarning \"%CLI%\" run >nul 2>&1`,\n `set \"CODE=%ERRORLEVEL%\"`,\n // An update's handover names the agent to wait for; a restart after a crash has nothing to wait for.\n `set \"SCOPEBOND_AGENT_AFTER_PID=\"`,\n \"if %CODE% EQU 0 exit /b 0\",\n \"set /a TRIES+=1\",\n `echo %DATE% %TIME% the agent stopped with exit code %CODE%; restarting in 30 seconds (attempt %TRIES% of 50) >> ${log}`,\n \"if %TRIES% GEQ 50 exit /b 1\",\n // ping waits without a console to read from (timeout.exe refuses to run under a headless console).\n \"ping -n 31 127.0.0.1 >nul\",\n \"goto run\",\n \"\",\n ].join(\"\\r\\n\");\n}\n\n/** The macOS/Linux launcher. With `cli` empty, `node` is the single executable, started as it is. */\nexport function posixLauncher(node: string, cli: string): string {\n const q = (s: string) => `'${s.replace(/'/g, \"'\\\\''\")}'`;\n if (cli === \"\") return `#!/bin/sh\n# Scopebond Agent launcher: the single executable.\nexec ${q(node)} run\n`;\n return `#!/bin/sh\n# Scopebond Agent launcher: finds Node and the agent each time, so a Node upgrade never stops it.\nNODE=${q(node)}\nif [ ! -x \"$NODE\" ]; then\n [ -s \"$HOME/.nvm/nvm.sh\" ] && . \"$HOME/.nvm/nvm.sh\" >/dev/null 2>&1\n NODE=\"$(command -v node 2>/dev/null)\"\n for candidate in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do\n [ -n \"$NODE\" ] && break\n [ -x \"$candidate\" ] && NODE=\"$candidate\"\n done\nfi\nCLI=${q(cli)}\n[ -f \"$CLI\" ] || CLI=\"$(npm root -g 2>/dev/null)/@scopebond/agent/dist/cli.js\"\n[ -n \"$NODE\" ] || exit 1\nexec \"$NODE\" --disable-warning=ExperimentalWarning \"$CLI\" run\n`;\n}\n\n/** The Run value: the launcher under a headless console host, so nothing appears on screen. */\n/** `cmd /s /c \"\"<launcher>\"\"`: cmd drops only the outer quotes and runs the quoted path as it is, so a\n * profile folder with a space and a `(`, `)` or `&` (\"John (Work)\") still starts the launcher. */\nconst cmdRun = (launcher: string) => `/d /s /c \"\"${noQuotes(launcher)}\"\"`;\n\nexport function windowsRunCommand(launcher: string): string {\n return `conhost.exe --headless cmd.exe ${cmdRun(launcher)}`;\n}\n\nexport function macLaunchAgent(launcher: string, logFile: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key><string>${LABEL}</string>\n <key>ProgramArguments</key>\n <array><string>/bin/sh</string><string>${xml(launcher)}</string></array>\n <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>\n <key>StandardOutPath</key><string>${xml(logFile)}</string>\n <key>StandardErrorPath</key><string>${xml(logFile)}</string>\n</dict>\n</plist>\n`;\n}\n\nexport function linuxUserUnit(launcher: string): string {\n const q = (s: string) => `\"${s.replace(/([\"\\\\])/g, \"\\\\$1\")}\"`;\n return `[Unit]\nDescription=Scopebond Agent (delivers this computer's records to its Scopebond workspace)\nAfter=network-online.target\n\n[Service]\nExecStart=/bin/sh ${q(launcher)}\nRestart=on-failure\nRestartSec=30\n\n[Install]\nWantedBy=default.target\n`;\n}\n\nexport function autostartPaths(home = homedir()) {\n return {\n macPlist: join(home, \"Library\", \"LaunchAgents\", `${LABEL}.plist`),\n linuxUnit: join(home, \".config\", \"systemd\", \"user\", \"scopebond-agent.service\"),\n };\n}\n\n/** Rewrite an older Windows launcher as this version's, keeping autostart as it is. Only safe once no cmd.exe runs the old\n * one (cmd reads a batch file line by line from where it left off): the handover calls it after the old agent has exited.\n * Returns whether it rewrote one. */\nexport function refreshLauncher(scopebondHome: string, cli: string, node = process.execPath, platform = process.platform): boolean {\n const launcher = launcherPath(scopebondHome, platform);\n let text: string;\n try { text = readFileSync(launcher, \"utf8\"); } catch { return false; }\n if (launcherIsCurrent(text, platform)) return false;\n writeLauncher(scopebondHome, node, cli, platform);\n return true;\n}\n\nfunction writeLauncher(scopebondHome: string, node: string, cli: string, platform: NodeJS.Platform): string {\n // The single executable is both Node and the agent: the launcher starts it with `run`.\n if (isSingleExecutable()) { node = process.execPath; cli = \"\"; }\n mkdirSync(scopebondHome, { recursive: true });\n const launcher = launcherPath(scopebondHome, platform);\n const log = join(scopebondHome, \"agent.log\");\n writeFileSync(launcher, platform === \"win32\" ? windowsLauncher(node, cli, log) : posixLauncher(node, cli), { mode: 0o700 });\n if (platform !== \"win32\") chmodSync(launcher, 0o700);\n return launcher;\n}\n\n/** The ways to start the agent now on Windows, in order: the launcher under a headless console (no window,\n * as sign-in does), then the launcher through cmd.exe with its window hidden, for a session where the\n * headless console host does not start (seen on Windows Server). macOS and Linux start it themselves\n * when autostart is turned on (RunAtLoad, enable --now). */\nexport function startCommands(launcher: string, platform: NodeJS.Platform = process.platform): Array<[string, string[]]> {\n if (platform !== \"win32\") return [];\n // Passed verbatim (startNow sets windowsVerbatimArguments): Node's own quoting follows other rules than cmd's.\n return [[\"conhost.exe\", [\"--headless\", \"cmd.exe\", cmdRun(launcher)]], [\"cmd.exe\", [cmdRun(launcher)]]];\n}\n\n// The signed Windows install with its native tray (`scopebond-tray.exe` beside the agent): the installer adds a Run value\n// `Scopebond` that starts the tray, and the tray starts the agent and keeps it running. Autostart then means that value,\n// not the launcher's `ScopebondAgent`, which would start a second agent (refused by its lock) at every sign-in.\n\n/** Runs `reg.exe` with these arguments; whether it succeeded. Tests pass their own. */\nexport type RegRunner = (args: string[]) => boolean;\nconst runReg: RegRunner = (args) => {\n try { execFileSync(\"reg\", args, { stdio: \"ignore\", windowsHide: true }); return true; } catch { return false; }\n};\nexport const TRAY_RUN_VALUE = \"Scopebond\";\nconst MACHINE_RUN_KEY = \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\";\nconst trayFor = (platform: NodeJS.Platform) => (platform === \"win32\" ? nativeTrayPath() : null);\n/** The tray's Run value set for every user (an install with ALLUSERS=1): only whoever installed it changes it. */\nconst trayForEveryone = (reg: RegRunner) => reg([\"query\", MACHINE_RUN_KEY, \"/v\", TRAY_RUN_VALUE]);\n\n/** The launcher's Run value is not used beside the native tray. Whether there was one to remove. */\nexport function retireLauncherRunValue(reg: RegRunner = runReg): boolean {\n return reg([\"query\", RUN_KEY, \"/v\", RUN_VALUE]) && reg([\"delete\", RUN_KEY, \"/v\", RUN_VALUE, \"/f\"]);\n}\n\n/** Start the agent now with the `attempt`-th way (0 first), detached from this terminal. Returns whether it tried one.\n * Beside the native tray, the one way is to start the tray (it starts the agent; a second tray gives way to the first). */\nexport function startNow(scopebondHome: string, platform = process.platform, attempt = 0, tray: string | null = trayFor(platform)): boolean {\n if (tray) {\n if (attempt > 0) return false;\n try {\n const child = spawn(tray, [], { detached: true, stdio: \"ignore\", windowsHide: true });\n child.on(\"error\", () => { /* the next sign-in starts it */ });\n child.unref();\n return true;\n } catch { return false; }\n }\n const launcher = launcherPath(scopebondHome, platform);\n const command = startCommands(launcher, platform)[attempt];\n if (!command || !existsSync(launcher)) return false;\n try {\n const child = spawn(command[0], command[1], { detached: true, stdio: \"ignore\", windowsHide: true, windowsVerbatimArguments: true });\n child.on(\"error\", () => { /* the next way, or the next sign-in */ });\n child.unref();\n return true;\n } catch { return false; }\n}\n\n/** Turn autostart on for this user. Returns a one-line description of what changed. */\nexport function enableAutostart(scopebondHome: string, cli: string, node = process.execPath, platform = process.platform, tray: string | null = trayFor(platform), reg: RegRunner = runReg): string {\n if (tray) {\n retireLauncherRunValue(reg);\n if (trayForEveryone(reg)) return \"the Scopebond tray starts for every user of this computer and keeps the agent running\";\n if (!reg([\"add\", RUN_KEY, \"/v\", TRAY_RUN_VALUE, \"/t\", \"REG_SZ\", \"/d\", `\"${noQuotes(tray)}\"`, \"/f\"])) throw new Error(`could not add ${TRAY_RUN_VALUE} to ${RUN_KEY}`);\n return `added ${TRAY_RUN_VALUE} to ${RUN_KEY}: the Scopebond tray starts with your sign-in and keeps the agent running`;\n }\n const launcher = writeLauncher(scopebondHome, node, cli, platform);\n const paths = autostartPaths();\n if (platform === \"win32\") {\n execFileSync(\"reg\", [\"add\", RUN_KEY, \"/v\", RUN_VALUE, \"/t\", \"REG_SZ\", \"/d\", windowsRunCommand(launcher), \"/f\"], { stdio: \"ignore\" });\n return `added ${RUN_VALUE} to ${RUN_KEY} (launcher ${launcher})`;\n }\n if (platform === \"darwin\") {\n mkdirSync(dirname(paths.macPlist), { recursive: true });\n writeFileSync(paths.macPlist, macLaunchAgent(launcher, join(scopebondHome, \"agent.log\")));\n try { execFileSync(\"launchctl\", [\"load\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* loads at next sign-in */ }\n return `wrote ${paths.macPlist}`;\n }\n mkdirSync(dirname(paths.linuxUnit), { recursive: true });\n writeFileSync(paths.linuxUnit, linuxUserUnit(launcher));\n try { execFileSync(\"systemctl\", [\"--user\", \"daemon-reload\"], { stdio: \"ignore\" }); execFileSync(\"systemctl\", [\"--user\", \"enable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); }\n catch { return `wrote ${paths.linuxUnit} (enable it with: systemctl --user enable --now scopebond-agent)`; }\n return `wrote and enabled ${paths.linuxUnit}`;\n}\n\nexport function disableAutostart(scopebondHome: string, platform = process.platform, tray: string | null = trayFor(platform), reg: RegRunner = runReg): string {\n const paths = autostartPaths();\n rmSync(launcherPath(scopebondHome, platform), { force: true });\n if (tray) {\n const launcherValue = retireLauncherRunValue(reg);\n const trayValue = reg([\"query\", RUN_KEY, \"/v\", TRAY_RUN_VALUE]) && reg([\"delete\", RUN_KEY, \"/v\", TRAY_RUN_VALUE, \"/f\"]);\n if (trayValue || launcherValue) return `removed ${trayValue ? TRAY_RUN_VALUE : RUN_VALUE} from ${RUN_KEY}`;\n return trayForEveryone(reg) ? \"Scopebond starts for every user of this computer; whoever installed it changes that\" : \"autostart was not on\";\n }\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"delete\", RUN_KEY, \"/v\", RUN_VALUE, \"/f\"], { stdio: \"ignore\" }); } catch { return \"autostart was not on\"; }\n return `removed ${RUN_VALUE} from ${RUN_KEY}`;\n }\n if (platform === \"darwin\") {\n if (!existsSync(paths.macPlist)) return \"autostart was not on\";\n try { execFileSync(\"launchctl\", [\"unload\", \"-w\", paths.macPlist], { stdio: \"ignore\" }); } catch { /* not loaded */ }\n rmSync(paths.macPlist, { force: true });\n return `removed ${paths.macPlist}`;\n }\n if (!existsSync(paths.linuxUnit)) return \"autostart was not on\";\n try { execFileSync(\"systemctl\", [\"--user\", \"disable\", \"--now\", \"scopebond-agent.service\"], { stdio: \"ignore\" }); } catch { /* not enabled */ }\n rmSync(paths.linuxUnit, { force: true });\n return `removed ${paths.linuxUnit}`;\n}\n\n/** Whether autostart is on and its launcher can start the agent now. */\nexport function autostartHealth(scopebondHome: string, platform = process.platform, tray: string | null = trayFor(platform), reg: RegRunner = runReg): { on: boolean; ok: boolean; detail: string } {\n if (tray) {\n const on = reg([\"query\", RUN_KEY, \"/v\", TRAY_RUN_VALUE]) || trayForEveryone(reg);\n return on\n ? { on, ok: true, detail: \"starts with sign-in (the Scopebond tray keeps the agent running)\" }\n : { on, ok: false, detail: \"the Scopebond tray does not start with sign-in (turn on Start with Windows in its menu, or run: scopebond-agent.exe autostart on)\" };\n }\n const paths = autostartPaths();\n let on = false;\n if (platform === \"win32\") {\n try { execFileSync(\"reg\", [\"query\", RUN_KEY, \"/v\", RUN_VALUE], { stdio: \"ignore\" }); on = true; } catch { on = false; }\n } else on = existsSync(platform === \"darwin\" ? paths.macPlist : paths.linuxUnit);\n // The fix as the person types it: PowerShell blocks the plain scopebond-agent script shim.\n const fix = `${platform === \"win32\" ? \"scopebond-agent.cmd\" : \"scopebond-agent\"} autostart on`;\n if (!on) return { on, ok: false, detail: `the agent does not start with sign-in (run: ${fix})` };\n if (!existsSync(launcherPath(scopebondHome, platform))) return { on, ok: false, detail: `the autostart launcher is missing (run: ${fix})` };\n return { on, ok: true, detail: \"starts with sign-in\" };\n}\n"]}
|