@scopebond/agent 0.5.6 → 0.6.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/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/allowance-sender.js +10 -4
- package/dist/allowance-sender.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 +60 -6
- 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 +9 -4
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +15 -9
- package/dist/ipc.js.map +1 -1
- package/dist/log-file.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/prompt.d.ts.map +1 -1
- package/dist/prompt.js +1 -0
- package/dist/prompt.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 +42 -13
- 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 +4 -4
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"]}
|
package/dist/allowance-sender.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function sendAllowancesAndRequests(dir, connection, fetchImpl = fet
|
|
|
37
37
|
for (const a of readAllowances(dir)) {
|
|
38
38
|
if (a.created_by !== "person" || a.sent_at)
|
|
39
39
|
continue;
|
|
40
|
-
const
|
|
40
|
+
const record = without(a, "sent_at", "uses");
|
|
41
41
|
const outcome = await post("/v1/allowances", record, ALLOWANCE_DOMAIN);
|
|
42
42
|
if (outcome !== "later")
|
|
43
43
|
allowanceOutcomes.set(a.id, outcome);
|
|
@@ -50,8 +50,7 @@ export async function sendAllowancesAndRequests(dir, connection, fetchImpl = fet
|
|
|
50
50
|
return a;
|
|
51
51
|
if (outcome === "sent") {
|
|
52
52
|
sent.allowances += 1;
|
|
53
|
-
|
|
54
|
-
return { ...rest, sent_at: at };
|
|
53
|
+
return { ...without(a, "reason"), sent_at: at };
|
|
55
54
|
} // the workspace keeps the text
|
|
56
55
|
return { ...a, sent_at: at };
|
|
57
56
|
}), now);
|
|
@@ -60,7 +59,7 @@ export async function sendAllowancesAndRequests(dir, connection, fetchImpl = fet
|
|
|
60
59
|
for (const r of readRequests(dir)) {
|
|
61
60
|
if (r.sent_at)
|
|
62
61
|
continue;
|
|
63
|
-
const
|
|
62
|
+
const record = without(r, "sent_at");
|
|
64
63
|
const outcome = await post("/v1/requests", record, REQUEST_DOMAIN);
|
|
65
64
|
if (outcome !== "later")
|
|
66
65
|
requestOutcomes.set(r.id, outcome);
|
|
@@ -77,4 +76,11 @@ export async function sendAllowancesAndRequests(dir, connection, fetchImpl = fet
|
|
|
77
76
|
}
|
|
78
77
|
return sent;
|
|
79
78
|
}
|
|
79
|
+
/** A copy of `value` without the named fields. */
|
|
80
|
+
function without(value, ...keys) {
|
|
81
|
+
const copy = { ...value };
|
|
82
|
+
for (const key of keys)
|
|
83
|
+
delete copy[key];
|
|
84
|
+
return copy;
|
|
85
|
+
}
|
|
80
86
|
//# sourceMappingURL=allowance-sender.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"allowance-sender.js","sourceRoot":"","sources":["../src/allowance-sender.ts"],"names":[],"mappings":"AAAA,4HAA4H;AAC5H,2HAA2H;AAC3H,yHAAyH;AACzH,6HAA6H;AAC7H,6HAA6H;AAC7H,6HAA6H;AAE7H,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAuB,MAAM,iBAAiB,CAAC;AAEpH,MAAM,CAAC,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAC3D,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAW,EAAE,UAAsD,EAAE,YAA0B,KAAK,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACpK,MAAM,IAAI,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,gEAAgE;IACvG,MAAM,EAAE,QAAQ,EAAE,GAAG,oBAAoB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,KAAK,EAAE,IAAY,EAAE,MAA+B,EAAE,MAAc,EAAyC,EAAE;QAC1H,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACvG,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;gBACpE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;gBACtE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,UAAU,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC9H,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,EAAE;gBAAE,OAAO,MAAM,CAAC;YAC1B,OAAO,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QACxE,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,OAAO,CAAC;QAAC,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA8B,CAAC;IAChE,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO;YAAE,SAAS;QACrD,MAAM,
|
|
1
|
+
{"version":3,"file":"allowance-sender.js","sourceRoot":"","sources":["../src/allowance-sender.ts"],"names":[],"mappings":"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","sourcesContent":["// D144 (SB411): the agent tells the workspace about allowances a person made on this computer and the requests they sent to\n// an admin. Each record is signed by the key this computer enrolled with (the same key that countersigns its receipts), so\n// the workspace can show it was made here. Sent once; one the workspace refuses for good (400) is not sent again; one it\n// could not take (offline, 5xx) waits for the next cycle. A workspace without these calls (404) leaves them on the computer.\n// Each signature covers a domain line and the canonical record, so neither can pass as a receipt signed by the same key. The\n// files are re-read before each write and only what this send changed is applied, so a hook writing meanwhile loses nothing.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { canonical } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { readAllowances, readRequests, writeAllowances, writeRequests, type HookConnection } from \"@scopebond/hook\";\n\nexport const ALLOWANCE_DOMAIN = \"scopebond:allowance/v1\\n\";\nexport const REQUEST_DOMAIN = \"scopebond:request/v1\\n\";\n\nexport async function sendAllowancesAndRequests(dir: string, connection: Pick<HookConnection, \"url\" | \"credential\">, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<{ allowances: number; requests: number }> {\n const sent = { allowances: 0, requests: 0 };\n const keyFile = join(dir, \"attester.key\");\n if (!existsSync(keyFile)) return sent; // never makes a key: an unenrolled computer has nothing to send\n const { attester } = loadOrCreateAttester({ file: keyFile });\n const post = async (path: string, record: Record<string, unknown>, domain: string): Promise<\"sent\" | \"refused\" | \"later\"> => {\n try {\n const body = { record, kid: attester.kid, signature: await attester.sign(domain + canonical(record)) };\n const res = await fetchImpl(new URL(path, connection.url).toString(), {\n method: \"POST\", redirect: \"error\", signal: AbortSignal.timeout(10_000),\n headers: { authorization: `Bearer ${connection.credential}`, \"content-type\": \"application/json\" }, body: JSON.stringify(body),\n });\n if (res.ok) return \"sent\";\n return res.status === 400 || res.status === 409 ? \"refused\" : \"later\";\n } catch { return \"later\"; }\n };\n\n const at = new Date(now).toISOString();\n const allowanceOutcomes = new Map<string, \"sent\" | \"refused\">();\n for (const a of readAllowances(dir)) {\n if (a.created_by !== \"person\" || a.sent_at) continue;\n const record = without(a, \"sent_at\", \"uses\");\n const outcome = await post(\"/v1/allowances\", record, ALLOWANCE_DOMAIN);\n if (outcome !== \"later\") allowanceOutcomes.set(a.id, outcome);\n }\n if (allowanceOutcomes.size) {\n // Applied to the file as it is now (the hook may have counted a use or added an allowance while this was sending).\n writeAllowances(dir, readAllowances(dir).map((a) => {\n const outcome = allowanceOutcomes.get(a.id);\n if (!outcome || a.sent_at) return a;\n if (outcome === \"sent\") { sent.allowances += 1; return { ...without(a, \"reason\"), sent_at: at }; } // the workspace keeps the text\n return { ...a, sent_at: at };\n }), now);\n }\n\n const requestOutcomes = new Map<string, \"sent\" | \"refused\">();\n for (const r of readRequests(dir)) {\n if (r.sent_at) continue;\n const record = without(r, \"sent_at\");\n const outcome = await post(\"/v1/requests\", record, REQUEST_DOMAIN);\n if (outcome !== \"later\") requestOutcomes.set(r.id, outcome);\n }\n if (requestOutcomes.size) {\n writeRequests(dir, readRequests(dir).map((r) => {\n const outcome = requestOutcomes.get(r.id);\n if (!outcome || r.sent_at) return r;\n if (outcome === \"sent\") sent.requests += 1;\n return { ...r, sent_at: at };\n }), now);\n }\n return sent;\n}\n\n/** A copy of `value` without the named fields. */\nfunction without<T extends object, K extends keyof T>(value: T, ...keys: K[]): Omit<T, K> {\n const copy = { ...value };\n for (const key of keys) delete copy[key];\n return copy;\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":"AAAA,2HAA2H;AAC3H,6HAA6H;AAC7H,0HAA0H;AAC1H,4HAA4H;AAC5H,4HAA4H;AAC5H,yHAAyH;AAEzH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,CAAC,MAAM,aAAa,GAAG,+EAA+E,CAAC;AAC7G,MAAM,CAAC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAC9C,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;AAEtC;;6CAE6C;AAC7C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,8BAA8B,EAAE,MAAM,CAAC,GAAG,CAAC;AAErG;0FAC0F;AAC1F,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAEjC;;;;wHAIwH;AACxH,MAAM,UAAU,eAAe,CAAC,CAA2D;IACzF,OAAO;;;;UAIC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;SAChB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;SACd,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;mBAsBJ,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,aAAa,CAAC;;;;;;;;;;CAUjD,CAAC;AACF,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,CAAoD;IAClF,MAAM,GAAG,GAAG,4DAA4D,CAAC,CAAC,MAAM,GAAG,CAAC;IACpF,OAAO;QACL,CAAC,aAAa,EAAE,QAAQ,EAAE,YAAY,CAAC;QACvC,CAAC,gBAAgB,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC;QACvC,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC;QAClC,CAAC,cAAc,EAAE,QAAQ,EAAE,uBAAuB,CAAC;QACnD,CAAC,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC;QACrC,CAAC,iBAAiB,EAAE,QAAQ,EAAE,GAAG,CAAC;QAClC,CAAC,sBAAsB,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAAE,CAAC;QAC1F,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC;QAC9B,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;IAC9C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,6GAA6G;AAC7G,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,CAA2D;IAC5G,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC5C,sHAAsH;IACtH,aAAa,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qHAAqH;AACrH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,CAAkD,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ;IAC1H,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC;IACxC,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACvF,YAAY,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7G,CAAC;IACD,OAAO,UAAU,YAAY,6CAA6C,CAAC;AAC7E,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ;IACvE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC;QAAC,YAAY,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IACzG,OAAO,WAAW,YAAY,wBAAwB,CAAC;AACzD,CAAC","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,9 +304,15 @@ 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
|
-
let on
|
|
315
|
+
let on;
|
|
262
316
|
if (platform === "win32") {
|
|
263
317
|
try {
|
|
264
318
|
execFileSync("reg", ["query", RUN_KEY, "/v", RUN_VALUE], { stdio: "ignore" });
|