@euqns/nudge-mcp 1.22.0 → 1.25.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.
@@ -0,0 +1,136 @@
1
+ // Process state files: what a running runner or agent companion knows about
2
+ // itself, written to ~/.nudge/state/<label>.json so the CLI (`nudge-mcp
3
+ // service status|install`) can report it without scraping logs.
4
+ //
5
+ // Both long-lived processes write one file at startup — the companion with its
6
+ // listener port and pairing URL, the runner with its registration — and the
7
+ // CLI reads it back. Freshness is decided by the reader, never trusted from
8
+ // the file: a state is live only when its pid is still alive AND it was
9
+ // written after the current boot, so a URL left behind by a process that died
10
+ // last week is never shown as the current pairing.
11
+ import fs from "node:fs/promises";
12
+ import os from "node:os";
13
+ import path from "node:path";
14
+ import { CREDENTIALS_DIR } from "./credentials.js";
15
+ export const SERVICE_KINDS = ["runner", "agent"];
16
+ export const SERVICE_MANAGER_ENV = "NUDGE_SERVICE_MANAGER";
17
+ export const SERVICE_LABEL_ENV = "NUDGE_SERVICE_LABEL";
18
+ export const STATE_DIR = path.join(CREDENTIALS_DIR, "state");
19
+ /** Only [a-z0-9-] survive; anything else becomes a dash. `--instance "Mac 2"` → `mac-2`. */
20
+ export function sanitizeInstance(raw) {
21
+ return raw
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, "-")
24
+ .replace(/^-+|-+$/g, "");
25
+ }
26
+ export function serviceLabel(kind, instance) {
27
+ const suffix = instance ? sanitizeInstance(instance) : "";
28
+ return suffix ? `com.nudge.${kind}.${suffix}` : `com.nudge.${kind}`;
29
+ }
30
+ /** The `--instance` a label was built from, or null for the default service.
31
+ * `com.nudge.runner.mac-2` → `mac-2`. */
32
+ export function instanceFromLabel(label) {
33
+ const m = /^com\.nudge\.(?:runner|agent)\.(.+)$/.exec(label);
34
+ return m ? m[1] : null;
35
+ }
36
+ /** systemd unit names can't start with the reverse-DNS prefix comfortably;
37
+ * `com.nudge.runner` → `nudge-runner`, `com.nudge.runner.mac-2` → `nudge-runner-mac-2`. */
38
+ export function unitName(label) {
39
+ return label.replace(/^com\.nudge\./, "nudge-").replace(/\./g, "-");
40
+ }
41
+ export function stateFilePath(label, dir = STATE_DIR) {
42
+ return path.join(dir, `${label}.json`);
43
+ }
44
+ export function serviceInfoFromEnv(env = process.env) {
45
+ const manager = env[SERVICE_MANAGER_ENV];
46
+ const label = env[SERVICE_LABEL_ENV];
47
+ if ((manager === "launchd" || manager === "systemd") && label) {
48
+ return { manager, label };
49
+ }
50
+ return null;
51
+ }
52
+ export async function writeProcessState(state, dir = STATE_DIR) {
53
+ const file = stateFilePath(state.label, dir);
54
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
55
+ // The pairing URL embeds the companion's bearer token — owner-only, like
56
+ // every other file under ~/.nudge.
57
+ const tmp = `${file}.${process.pid}.tmp`;
58
+ await fs.writeFile(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
59
+ await fs.chmod(tmp, 0o600);
60
+ // Atomic replace so a concurrent reader never sees a half-written file.
61
+ await fs.rename(tmp, file);
62
+ return file;
63
+ }
64
+ export async function readProcessState(label, dir = STATE_DIR) {
65
+ try {
66
+ const parsed = JSON.parse(await fs.readFile(stateFilePath(label, dir), "utf8"));
67
+ if ((parsed.kind !== "runner" && parsed.kind !== "agent") ||
68
+ typeof parsed.pid !== "number" ||
69
+ typeof parsed.startedAt !== "number" ||
70
+ typeof parsed.label !== "string") {
71
+ return null;
72
+ }
73
+ return parsed;
74
+ }
75
+ catch (err) {
76
+ if (err.code === "ENOENT")
77
+ return null;
78
+ // A corrupt file is "no report", not a crash of the status command.
79
+ return null;
80
+ }
81
+ }
82
+ /** Is a process with this pid alive? EPERM means it exists but isn't ours. */
83
+ export function isPidAlive(pid) {
84
+ if (!Number.isInteger(pid) || pid <= 0)
85
+ return false;
86
+ try {
87
+ process.kill(pid, 0);
88
+ return true;
89
+ }
90
+ catch (err) {
91
+ return err.code === "EPERM";
92
+ }
93
+ }
94
+ /** ms timestamp of the current boot. Anything a process wrote before this was
95
+ * written by a process that a reboot has certainly killed, whatever its pid
96
+ * says now (pids are recycled). */
97
+ export function bootTime(now = Date.now()) {
98
+ return now - os.uptime() * 1000;
99
+ }
100
+ /** A state is live when its process is still running and it was written in
101
+ * this boot session. Both checks matter: a recycled pid can be alive while the
102
+ * file is from last week, and a file from this boot can outlive a crash. */
103
+ export function isStateLive(state, deps = {}) {
104
+ const now = deps.now ?? Date.now();
105
+ const boot = deps.bootTime ?? bootTime(now);
106
+ const pidAlive = deps.pidAlive ?? isPidAlive;
107
+ // A little slack: os.uptime() is coarse and the clock may step after boot.
108
+ return state.startedAt >= boot - 60_000 && pidAlive(state.pid);
109
+ }
110
+ /**
111
+ * Wait for a process to report in AFTER `since` (ms). Used by `service
112
+ * install`: only a state written by the process that install just started
113
+ * counts, so a URL or registration left by an older process is never shown.
114
+ */
115
+ export async function waitForProcessState(label, opts) {
116
+ const timeoutMs = opts.timeoutMs ?? 30_000;
117
+ const pollMs = opts.pollMs ?? 300;
118
+ const now = opts.now ?? Date.now;
119
+ const read = opts.read ?? ((l) => readProcessState(l, opts.dir));
120
+ const isLive = opts.isLive ?? ((s) => isStateLive(s));
121
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
122
+ const deadline = now() + timeoutMs;
123
+ for (;;) {
124
+ const state = await read(label);
125
+ // Allow a couple of seconds of skew between "install started" and the
126
+ // child's own clock read — same machine, but the check must not be
127
+ // flaky on the happy path.
128
+ if (state && state.startedAt >= opts.since - 2_000 && isLive(state)) {
129
+ return state;
130
+ }
131
+ if (now() >= deadline)
132
+ return null;
133
+ await sleep(pollMs);
134
+ }
135
+ }
136
+ //# sourceMappingURL=service-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-state.js","sourceRoot":"","sources":["../src/service-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { instanceFromLabel, isPidAlive, isStateLive, readProcessState, sanitizeInstance, serviceInfoFromEnv, serviceLabel, stateFilePath, unitName, waitForProcessState, writeProcessState, } from "./service-state.js";
7
+ const NOW = 1_800_000_000_000;
8
+ const agentState = (patch = {}) => ({
9
+ kind: "agent",
10
+ label: "com.nudge.agent",
11
+ pid: process.pid,
12
+ startedAt: NOW - 5_000,
13
+ updatedAt: NOW - 5_000,
14
+ version: "1.24.0",
15
+ hostname: "mac-mini",
16
+ cwd: "/Users/dev/repo",
17
+ supervised: true,
18
+ service: { manager: "launchd", label: "com.nudge.agent" },
19
+ port: 47653,
20
+ appUrl: "https://app.example",
21
+ pairingUrl: "https://app.example/#nudge-agent=FRESH",
22
+ providers: ["Codex", "Claude Code"],
23
+ ...patch,
24
+ });
25
+ test("labels: one per kind, instances get a sanitized suffix and round-trip", () => {
26
+ assert.equal(serviceLabel("runner"), "com.nudge.runner");
27
+ assert.equal(serviceLabel("agent"), "com.nudge.agent");
28
+ assert.equal(serviceLabel("runner", "Mac Mini #2"), "com.nudge.runner.mac-mini-2");
29
+ assert.equal(sanitizeInstance("--weird--"), "weird");
30
+ assert.equal(unitName("com.nudge.runner"), "nudge-runner");
31
+ assert.equal(unitName("com.nudge.runner.mac-2"), "nudge-runner-mac-2");
32
+ assert.equal(instanceFromLabel("com.nudge.runner"), null);
33
+ assert.equal(instanceFromLabel("com.nudge.runner.mac-2"), "mac-2");
34
+ });
35
+ test("service identity is read from the env the service file stamps in", () => {
36
+ assert.deepEqual(serviceInfoFromEnv({ NUDGE_SERVICE_MANAGER: "launchd", NUDGE_SERVICE_LABEL: "com.nudge.runner" }), { manager: "launchd", label: "com.nudge.runner" });
37
+ assert.equal(serviceInfoFromEnv({ NUDGE_SERVICE_MANAGER: "launchd" }), null);
38
+ assert.equal(serviceInfoFromEnv({ NUDGE_SERVICE_MANAGER: "cron", NUDGE_SERVICE_LABEL: "x" }), null);
39
+ assert.equal(serviceInfoFromEnv({}), null);
40
+ });
41
+ test("state files round-trip, are owner-only, and reject garbage", async () => {
42
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "nudge-state-"));
43
+ try {
44
+ const state = agentState();
45
+ const file = await writeProcessState(state, dir);
46
+ assert.equal(file, stateFilePath("com.nudge.agent", dir));
47
+ assert.deepEqual(await readProcessState("com.nudge.agent", dir), state);
48
+ if (process.platform !== "win32") {
49
+ assert.equal((await fs.stat(file)).mode & 0o777, 0o600);
50
+ }
51
+ // No leftover temp file from the atomic replace.
52
+ assert.deepEqual(await fs.readdir(dir), ["com.nudge.agent.json"]);
53
+ assert.equal(await readProcessState("com.nudge.runner", dir), null);
54
+ await fs.writeFile(stateFilePath("com.nudge.runner", dir), "{not json");
55
+ assert.equal(await readProcessState("com.nudge.runner", dir), null);
56
+ await fs.writeFile(stateFilePath("com.nudge.runner", dir), JSON.stringify({ kind: "runner", pid: "x" }));
57
+ assert.equal(await readProcessState("com.nudge.runner", dir), null);
58
+ }
59
+ finally {
60
+ await fs.rm(dir, { recursive: true, force: true });
61
+ }
62
+ });
63
+ test("liveness needs a living pid AND a start after the current boot", () => {
64
+ const alive = () => true;
65
+ const dead = () => false;
66
+ const boot = NOW - 60 * 60_000;
67
+ assert.equal(isStateLive({ pid: 1, startedAt: NOW - 1000 }, { now: NOW, bootTime: boot, pidAlive: alive }), true);
68
+ assert.equal(isStateLive({ pid: 1, startedAt: NOW - 1000 }, { now: NOW, bootTime: boot, pidAlive: dead }), false);
69
+ // Written before this boot: the pid may have been recycled, the URL is stale.
70
+ assert.equal(isStateLive({ pid: 1, startedAt: boot - 5 * 60_000 }, { now: NOW, bootTime: boot, pidAlive: alive }), false);
71
+ // Real pids: ours is alive, an absurd one is not.
72
+ assert.equal(isPidAlive(process.pid), true);
73
+ assert.equal(isPidAlive(2 ** 22 - 1), false);
74
+ assert.equal(isPidAlive(0), false);
75
+ });
76
+ test("waitForProcessState ignores reports from before the install and returns the fresh one", async () => {
77
+ const since = NOW;
78
+ const stale = agentState({ startedAt: since - 60_000, pairingUrl: "https://app.example/#nudge-agent=OLD" });
79
+ const fresh = agentState({ startedAt: since + 1_500 });
80
+ let reads = 0;
81
+ const read = async () => {
82
+ reads += 1;
83
+ return reads < 3 ? stale : fresh;
84
+ };
85
+ let clock = since;
86
+ const got = await waitForProcessState("com.nudge.agent", {
87
+ since,
88
+ timeoutMs: 10_000,
89
+ pollMs: 100,
90
+ read,
91
+ isLive: () => true,
92
+ sleep: async () => {
93
+ clock += 100;
94
+ },
95
+ now: () => clock,
96
+ });
97
+ assert.equal(got?.kind === "agent" ? got.pairingUrl : null, "https://app.example/#nudge-agent=FRESH");
98
+ assert.equal(reads, 3);
99
+ });
100
+ test("waitForProcessState gives up on a stale or dead report", async () => {
101
+ const since = NOW;
102
+ let clock = since;
103
+ const tick = async () => {
104
+ clock += 1_000;
105
+ };
106
+ // Only a stale report ever appears.
107
+ const stale = agentState({ startedAt: since - 60_000 });
108
+ assert.equal(await waitForProcessState("com.nudge.agent", {
109
+ since, timeoutMs: 3_000, pollMs: 1_000, read: async () => stale, isLive: () => true, sleep: tick, now: () => clock,
110
+ }), null);
111
+ // A fresh timestamp from a process that already died is not a live pairing.
112
+ clock = since;
113
+ const dead = agentState({ startedAt: since + 500 });
114
+ assert.equal(await waitForProcessState("com.nudge.agent", {
115
+ since, timeoutMs: 3_000, pollMs: 1_000, read: async () => dead, isLive: () => false, sleep: tick, now: () => clock,
116
+ }), null);
117
+ });
118
+ //# sourceMappingURL=service-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-state.test.js","sourceRoot":"","sources":["../src/service-state.test.ts"],"names":[],"mappings":"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"}