@popoverinstall/cli 0.7.2 → 0.8.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +90 -19
  3. package/dist/changelog.d.ts.map +1 -1
  4. package/dist/changelog.js +8 -1
  5. package/dist/changelog.js.map +1 -1
  6. package/dist/daemon-lock.d.ts +102 -0
  7. package/dist/daemon-lock.d.ts.map +1 -0
  8. package/dist/daemon-lock.js +321 -0
  9. package/dist/daemon-lock.js.map +1 -0
  10. package/dist/doctor.d.ts.map +1 -1
  11. package/dist/doctor.js +11 -0
  12. package/dist/doctor.js.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +107 -21
  15. package/dist/index.js.map +1 -1
  16. package/dist/login.d.ts.map +1 -1
  17. package/dist/login.js +38 -5
  18. package/dist/login.js.map +1 -1
  19. package/dist/next-steps.d.ts +1 -5
  20. package/dist/next-steps.d.ts.map +1 -1
  21. package/dist/next-steps.js +9 -5
  22. package/dist/next-steps.js.map +1 -1
  23. package/dist/rename.d.ts +18 -0
  24. package/dist/rename.d.ts.map +1 -0
  25. package/dist/rename.js +252 -0
  26. package/dist/rename.js.map +1 -0
  27. package/dist/snapshot.js +24 -4
  28. package/dist/snapshot.js.map +1 -1
  29. package/dist/terminal.d.ts.map +1 -1
  30. package/dist/terminal.js +21 -0
  31. package/dist/terminal.js.map +1 -1
  32. package/dist/theme.d.ts +5 -3
  33. package/dist/theme.d.ts.map +1 -1
  34. package/dist/theme.js +7 -5
  35. package/dist/theme.js.map +1 -1
  36. package/package.json +3 -3
  37. package/plugin/.claude-plugin/plugin.json +1 -1
  38. package/plugin/README.md +13 -4
  39. package/plugin/commands/ask.md +65 -0
  40. package/plugin/commands/fork.md +4 -3
  41. package/plugin/commands/team.md +37 -64
  42. package/plugin/commands/tell.md +66 -0
  43. package/plugin/scripts/announce-roster.mjs +21 -7
  44. package/plugin/scripts/emit-event.mjs +17 -0
  45. package/plugin/scripts/ensure-daemon.mjs +41 -1
  46. package/plugin/scripts/roster.mjs +5 -0
  47. package/plugin/skills/popover/SKILL.md +34 -11
@@ -0,0 +1,321 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { daemonLockPath, popoverHome } from "@popoverinstall/shared";
5
+ /**
6
+ * Reading the daemon's lock file, and refusing to act on it when it is stale.
7
+ *
8
+ * The lock is advisory. A daemon is running if and only if it answers on the pipe; the file
9
+ * exists so that a daemon which has stopped answering can still be signalled. That makes
10
+ * every use of it an act of trust in a number written by a process that may be long gone,
11
+ * and this module exists to make that trust conditional.
12
+ *
13
+ * Two ways it used to be misplaced, both reproduced:
14
+ *
15
+ * 1. **A recycled pid.** A daemon killed without its graceful path — `taskkill /F`, Task
16
+ * Manager, a power cut — leaves the file behind, and Windows reissues pids aggressively.
17
+ * `popover daemon stop` then sent SIGTERM to whatever now holds that number, and
18
+ * `popover update` runs the same path, so this was not a rare route into someone else's
19
+ * process.
20
+ * 2. **A zero-byte lock.** A crash mid-write, or a full disk, leaves an empty file.
21
+ * `Number("")` is `0` and `Number.isFinite(0)` is `true`, so the pid check passed and
22
+ * `process.kill(0, "SIGTERM")` went out — which on macOS and Linux signals the *caller's
23
+ * entire process group*, killing the user's shell.
24
+ *
25
+ * So: the pid must be a positive integer, and the process wearing it must still look like the
26
+ * one that wrote the file — same start time, running the same script. When that cannot be
27
+ * established, nothing is signalled. Refusing to act is always recoverable; signalling the
28
+ * wrong process is not.
29
+ */
30
+ /** How far the OS-reported start time may differ from the recorded one. */
31
+ const START_TIME_TOLERANCE_MS = 5_000;
32
+ /**
33
+ * Parse the lock file, tolerating the format that predates it.
34
+ *
35
+ * A daemon installed before this change is still a daemon somebody may need to stop, and its
36
+ * lock holds a bare pid. It is accepted, flagged `legacy`, and verified against a weaker
37
+ * signal — see `verifyDaemonLock`.
38
+ */
39
+ export function readDaemonLock(file = daemonLockPath()) {
40
+ let raw;
41
+ try {
42
+ raw = readFileSync(file, "utf8").trim();
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ // Includes the zero-byte case, which is the whole reason this returns null rather than
48
+ // letting `Number("")` become a pid of 0.
49
+ if (!raw)
50
+ return null;
51
+ if (raw.startsWith("{")) {
52
+ try {
53
+ const parsed = JSON.parse(raw);
54
+ const pid = Number(parsed.pid);
55
+ if (!Number.isInteger(pid))
56
+ return null;
57
+ return {
58
+ pid,
59
+ ...(Number.isFinite(Number(parsed.startedAt)) && Number(parsed.startedAt) > 0
60
+ ? { startedAt: Number(parsed.startedAt) }
61
+ : {}),
62
+ ...(parsed.entry ? { entry: String(parsed.entry) } : {}),
63
+ legacy: false,
64
+ };
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ const pid = Number(raw);
71
+ if (!Number.isInteger(pid))
72
+ return null;
73
+ return { pid, legacy: true };
74
+ }
75
+ /**
76
+ * Whether the process named by the lock is still the daemon that wrote it.
77
+ *
78
+ * `expectedEntry` is the daemon build this CLI would start, used as the identity test for a
79
+ * legacy lock that records no entry of its own. It is a weaker test — it says "this pid is
80
+ * running the daemon" rather than "this pid is the one that wrote the file" — but combined
81
+ * with the pid still existing it is enough to rule out an unrelated process, which is the
82
+ * failure that matters.
83
+ */
84
+ export function verifyDaemonLock(lock, opts = {}) {
85
+ // `pid > 0` rather than `Number.isFinite`: 0 is a real, finite number and a catastrophic
86
+ // signal target, and a negative pid signals a process group on POSIX.
87
+ if (!Number.isInteger(lock.pid) || lock.pid <= 0) {
88
+ return { ok: false, reason: "bad-pid", detail: `the lock names pid ${lock.pid}` };
89
+ }
90
+ const probe = opts.probe ?? inspectProcess;
91
+ const info = probe(lock.pid);
92
+ if (info.state === "gone") {
93
+ return { ok: false, reason: "gone", detail: `no process is running as pid ${lock.pid}` };
94
+ }
95
+ if (info.state === "unknown") {
96
+ return {
97
+ ok: false,
98
+ reason: "unknown",
99
+ detail: `this machine would not say what pid ${lock.pid} is`,
100
+ };
101
+ }
102
+ // The start time is the strongest available evidence against a recycled pid: a reissued
103
+ // number belongs to a process that started later, by definition.
104
+ if (lock.startedAt !== undefined) {
105
+ if (info.startedAt === undefined) {
106
+ return {
107
+ ok: false,
108
+ reason: "unknown",
109
+ detail: `could not read the start time of pid ${lock.pid}`,
110
+ };
111
+ }
112
+ if (Math.abs(info.startedAt - lock.startedAt) > START_TIME_TOLERANCE_MS) {
113
+ return {
114
+ ok: false,
115
+ reason: "not-ours",
116
+ detail: `pid ${lock.pid} started at a different time than the daemon that wrote the lock`,
117
+ };
118
+ }
119
+ }
120
+ const wanted = lock.entry ?? opts.expectedEntry ?? null;
121
+ if (wanted) {
122
+ if (info.commandLine === undefined) {
123
+ return {
124
+ ok: false,
125
+ reason: "unknown",
126
+ detail: `could not read the command line of pid ${lock.pid}`,
127
+ };
128
+ }
129
+ if (!commandLineNames(info.commandLine, wanted)) {
130
+ return {
131
+ ok: false,
132
+ reason: "not-ours",
133
+ detail: `pid ${lock.pid} is not running the popover daemon`,
134
+ };
135
+ }
136
+ }
137
+ else if (lock.legacy) {
138
+ // Nothing recorded and nothing to compare against. A legacy lock plus no known daemon
139
+ // build is exactly the situation this module exists to refuse.
140
+ return {
141
+ ok: false,
142
+ reason: "unknown",
143
+ detail: "the lock predates this version and there is no daemon build to check it against",
144
+ };
145
+ }
146
+ return { ok: true, pid: lock.pid };
147
+ }
148
+ /**
149
+ * Whether a command line is running a given script.
150
+ *
151
+ * Compared on the path as text rather than by resolving it: the daemon recorded
152
+ * `process.argv[1]` and the OS reports the same string back, so an exact substring match is
153
+ * both sufficient and free of filesystem calls that could themselves fail. Windows gets a
154
+ * case-insensitive comparison with separators normalised, because a path can come back with
155
+ * either slash and either case and still be the same file.
156
+ */
157
+ function commandLineNames(commandLine, entry) {
158
+ if (process.platform !== "win32")
159
+ return commandLine.includes(entry);
160
+ const norm = (s) => s.replace(/\\/g, "/").toLowerCase();
161
+ return norm(commandLine).includes(norm(entry));
162
+ }
163
+ /**
164
+ * Ask the OS what a pid is, without ever throwing.
165
+ *
166
+ * Deliberately not `process.kill(pid, 0)`, which answers only "does something hold this
167
+ * number" — the exact question that led here. What is needed is identity, and identity means
168
+ * the start time and the command line, neither of which Node exposes for another process.
169
+ *
170
+ * Costs one subprocess. That is fine: this runs on the fallback path of `popover daemon
171
+ * stop`, after the pipe has already failed, and never on any hot path.
172
+ */
173
+ export function inspectProcess(pid) {
174
+ try {
175
+ return process.platform === "win32" ? inspectWindows(pid) : inspectPosix(pid);
176
+ }
177
+ catch {
178
+ // A probe that could not run tells us nothing, and "nothing" must not read as "gone" —
179
+ // that would send us on to clean the lock of a daemon that is alive and well.
180
+ return { state: "unknown" };
181
+ }
182
+ }
183
+ function inspectPosix(pid) {
184
+ let out;
185
+ try {
186
+ out = execFileSync("ps", ["-o", "lstart=,args=", "-p", String(pid)], {
187
+ encoding: "utf8",
188
+ timeout: 8_000,
189
+ stdio: ["ignore", "pipe", "ignore"],
190
+ });
191
+ }
192
+ catch (err) {
193
+ // `ps` exits non-zero with no output when the pid does not exist, which is an answer.
194
+ // Anything else — no `ps`, a timeout — is not.
195
+ const status = err.status;
196
+ if (status === 1)
197
+ return { state: "gone" };
198
+ return { state: "unknown" };
199
+ }
200
+ const line = out.split("\n").find((l) => l.trim().length > 0);
201
+ if (!line)
202
+ return { state: "gone" };
203
+ // `lstart` is `%a %b %e %H:%M:%S %Y`, space-padded to a fixed width, so the date and the
204
+ // command are separable without knowing anything about the command.
205
+ const m = /^\s*(\w{3}\s+\w{3}\s+[\d ]?\d\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.*)$/.exec(line);
206
+ if (!m)
207
+ return { state: "running", commandLine: line.trim() };
208
+ const at = Date.parse(m[1].replace(/\s+/g, " "));
209
+ return {
210
+ state: "running",
211
+ ...(Number.isFinite(at) ? { startedAt: at } : {}),
212
+ commandLine: m[2].trim(),
213
+ };
214
+ }
215
+ function inspectWindows(pid) {
216
+ // One CIM query, because `Get-Process` reports a start time but not a command line and the
217
+ // command line is what identifies the script. Emitted as JSON so nothing has to be parsed
218
+ // out of a localised table.
219
+ const script = `$ErrorActionPreference='Stop';` +
220
+ `$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}";` +
221
+ `if ($null -eq $p) { exit 3 };` +
222
+ `$o = [ordered]@{ ` +
223
+ `start = [int64]([DateTimeOffset]$p.CreationDate).ToUnixTimeMilliseconds(); ` +
224
+ `cmd = [string]$p.CommandLine };` +
225
+ `[Console]::Out.Write(($o | ConvertTo-Json -Compress))`;
226
+ let out;
227
+ try {
228
+ out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { encoding: "utf8", timeout: 8_000, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
229
+ }
230
+ catch (err) {
231
+ // Exit 3 is our own "no such process"; every other failure is the probe, not the answer.
232
+ if (err.status === 3)
233
+ return { state: "gone" };
234
+ return { state: "unknown" };
235
+ }
236
+ try {
237
+ const parsed = JSON.parse(out.trim());
238
+ const at = Number(parsed.start);
239
+ return {
240
+ state: "running",
241
+ ...(Number.isFinite(at) && at > 0 ? { startedAt: at } : {}),
242
+ ...(parsed.cmd ? { commandLine: String(parsed.cmd) } : {}),
243
+ };
244
+ }
245
+ catch {
246
+ return { state: "unknown" };
247
+ }
248
+ }
249
+ /** Remove a lock that has been shown to name nothing of ours. Never throws. */
250
+ export function clearDaemonLock(file = daemonLockPath()) {
251
+ try {
252
+ rmSync(file, { force: true });
253
+ }
254
+ catch {
255
+ /* the next start overwrites it anyway */
256
+ }
257
+ }
258
+ // ---------------------------------------------------------------------------
259
+ // The update interlock
260
+ // ---------------------------------------------------------------------------
261
+ /**
262
+ * Marker saying "an upgrade is replacing the install tree; do not start a daemon".
263
+ *
264
+ * `popover update` stops the daemon and then hands npm up to five minutes to replace the
265
+ * global package. The SessionStart hook runs in *every* Claude Code session, pings, finds
266
+ * nothing, and starts `node <entry>` pointing into the tree npm is halfway through replacing.
267
+ * On Windows the daemon it starts then holds those files open, npm's rename fails EPERM or
268
+ * EBUSY, and the update aborts with a half-replaced global install — a machine with no
269
+ * working `popover` at all.
270
+ *
271
+ * The deadline is what keeps this from being worse than the problem. A marker with no expiry
272
+ * that survives a crashed update would suppress the daemon forever, on a machine whose owner
273
+ * has no idea this file exists.
274
+ */
275
+ export function updateLockPath() {
276
+ return path.join(popoverHome(), "update.lock");
277
+ }
278
+ export function beginUpdateLock(ttlMs) {
279
+ try {
280
+ mkdirSync(popoverHome(), { recursive: true });
281
+ writeFileSync(updateLockPath(), `${JSON.stringify({ v: 1, pid: process.pid, deadline: Date.now() + ttlMs })}\n`, "utf8");
282
+ }
283
+ catch {
284
+ // Best effort. Failing to write it makes the race possible again, which is where we
285
+ // started; failing the update over it would be worse.
286
+ }
287
+ }
288
+ export function clearUpdateLock() {
289
+ try {
290
+ rmSync(updateLockPath(), { force: true });
291
+ }
292
+ catch {
293
+ /* a stale marker expires on its own */
294
+ }
295
+ }
296
+ /**
297
+ * Whether an upgrade is in flight right now.
298
+ *
299
+ * Every branch fails towards starting the daemon. Only a marker that is present, readable and
300
+ * unexpired holds anything back; a torn or truncated one means the process that wrote it was
301
+ * interrupted, which is a dead update rather than a running one. Reading it the other way
302
+ * round would leave a machine permanently without a daemon over a file its owner has never
303
+ * heard of — worse than the race this guards against, and far harder to diagnose.
304
+ *
305
+ * `packages/plugin/scripts/ensure-daemon.mjs` duplicates this check, down to that choice. It
306
+ * must stay dependency-free — a hook that fails to resolve an import breaks every session on
307
+ * the machine — so it cannot import this. Change both together.
308
+ */
309
+ export function updateInProgress() {
310
+ try {
311
+ const file = updateLockPath();
312
+ if (!existsSync(file))
313
+ return false;
314
+ const deadline = Number(JSON.parse(readFileSync(file, "utf8")).deadline);
315
+ return Number.isFinite(deadline) && Date.now() < deadline;
316
+ }
317
+ catch {
318
+ return false;
319
+ }
320
+ }
321
+ //# sourceMappingURL=daemon-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-lock.js","sourceRoot":"","sources":["../src/daemon-lock.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAWA,6HAA6H;AAC7H,eAAO,MAAM,kBAAkB,sBAAuB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CA0H9C;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ5D;AAcD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAS/F"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAWA,6HAA6H;AAC7H,eAAO,MAAM,kBAAkB,sBAAuB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAuI9C;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ5D;AAcD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAS/F"}
package/dist/doctor.js CHANGED
@@ -61,6 +61,17 @@ export async function doctor() {
61
61
  if (!pong.authenticated) {
62
62
  fail("daemon is running but not authenticated", "Its token exchange failed. Check the backend, then `popover daemon restart`.");
63
63
  }
64
+ // A blocked client is a failure, not a suggestion: the backend is refusing its writes,
65
+ // so this machine's agents are invisible to the team no matter how healthy everything
66
+ // else here looks. Reported before the roster check below, which would otherwise show
67
+ // an empty team and read as "nobody is around".
68
+ if (pong.update?.blocked) {
69
+ fail("this build is too old to publish to your team", `The backend refuses its writes, so your agents are invisible to teammates. Run \`${pong.update.command}\`.`);
70
+ }
71
+ else if (pong.update) {
72
+ warn(`a newer release is available (${pong.update.current} → ${pong.update.latest})`);
73
+ dim(`Run \`${pong.update.command}\`.`);
74
+ }
64
75
  }
65
76
  // 5. Backend round trip — the check that actually exercises the JWT path.
66
77
  if (creds) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAe,MAAM,wBAAwB,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,6HAA6H;AAC7H,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAU,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,IAAa,EAAE,EAAE;QAC1C,GAAG,CAAC,GAAG,CAAC,CAAC;QACT,IAAI,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,QAAQ,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC;IAEF,iBAAiB;IACjB,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,CAAC,8BAA8B,EAAE,iDAAiD,CAAC,CAAC;IAC1F,CAAC;SAAM,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,CAAC;QACxD,IAAI,CACF,UAAU,OAAO,qBAAqB,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EACtE,oFAAoF,CACrF,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,kBAAkB;IAClB,MAAM,cAAc,GAAG,oBAAoB,EAAE,CAAC;IAC9C,IAAI,cAAc;QAAE,EAAE,CAAC,sBAAsB,CAAC,CAAC;SAC1C,CAAC;QACJ,IAAI,CAAC,0CAA0C,CAAC,CAAC;QACjD,GAAG,CAAC,wDAAwD,CAAC,CAAC;QAC9D,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACjE,CAAC;IAED,iBAAiB;IACjB,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,CAAC,+BAA+B,EAAE,sBAAsB,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,sBAAsB,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,YAAY;IACZ,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC,uBAAuB,EAAE,mDAAmD,aAAa,EAAE,EAAE,CAAC,CAAC;IACtG,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,oBAAoB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CACF,yCAAyC,EACzC,8EAA8E,CAC/E,CAAC;QACJ,CAAC;QACD,uFAAuF;QACvF,sFAAsF;QACtF,sFAAsF;QACtF,gDAAgD;QAChD,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACzB,IAAI,CACF,+CAA+C,EAC/C,oFAAoF,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,CAC7G,CAAC;QACJ,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,IAAI,CAAC,iCAAiC,IAAI,CAAC,MAAM,CAAC,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACtF,GAAG,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,KAAK,CAAC,OAAO,qBAAqB,EAAE;gBAC7D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,+EAA+E;gBAC/E,8EAA8E;gBAC9E,4EAA4E;gBAC5E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;aAC3D,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,EAAE,CAAC,sBAAsB,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;YAC7C,CAAC;iBAAM,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC9B,IAAI,CAAC,mCAAmC,EAAE,mCAAmC,CAAC,CAAC;YACjF,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,oBAAoB,GAAG,CAAC,MAAM,EAAE,EAAE,0CAA0C,CAAC,CAAC;YACrF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CACF,gBAAgB,KAAK,CAAC,OAAO,EAAE,EAC/B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,IAAI,MAAM,UAAU,EAAE,EAAE,CAAC;QACvB,EAAE,CAAC,WAAW,CAAC,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,IAAI,CACF,uBAAuB,EACvB,qFAAqF;YACnF,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,IAAI,IAAI,EAAE,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,SAAS,CAC5B,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,EAChE,KAAK,CACN,CAAC;QACF,IAAI,MAAM,EAAE,CAAC,KAAK,WAAW,EAAE,CAAC;YAC9B,kFAAkF;YAClF,0DAA0D;YAC1D,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM;gBAAE,EAAE,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;iBAC3E,IAAI,MAAM,CAAC,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,CAAC;gBACtC,IAAI,CAAC,wCAAwC,CAAC,CAAC;gBAC/C,GAAG,CAAC,2EAA2E,CAAC,CAAC;YACnF,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;YAC9D,EAAE,CAAC,WAAW,MAAM,CAAC,OAAO,CAAC,MAAM,cAAc,MAAM,iBAAiB,CAAC,CAAC;YAC1E,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChC,GAAG,CAAC,iEAAiE,CAAC,CAAC;YACzE,CAAC;YACD,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAC9B,IAAI,CAAC,GAAG,MAAM,CAAC,kBAAkB,8CAA8C,CAAC,CAAC;gBACjF,GAAG,CAAC,mDAAmD,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC;YACjC,IAAI,CAAC,kBAAkB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,IAAI,QAAQ,KAAK,CAAC;QAAE,EAAE,CAAC,2BAA2B,CAAC,CAAC;;QAC/C,GAAG,CAAC,GAAG,QAAQ,oBAAoB,CAAC,CAAC;IAC1C,OAAO,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,QAAQ,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE;YAC3D,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,GAAsC;IACpF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;QACrB,IAAI,GAAG,GAAG,IAAI;YAAE,OAAO,IAAI,CAAC;QAC5B,IAAI,GAAG,GAAG,IAAI;YAAE,OAAO,KAAK,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAS,oBAAoB;IAC3B,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,eAAe,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,qBAAqB,CAAC;KAC3D,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/E,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;IACD,qFAAqF;IACrF,yFAAyF;IACzF,0FAA0F;IAC1F,mFAAmF;IACnF,wCAAwC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA6BA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAsCA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
package/dist/index.js CHANGED
@@ -5,11 +5,13 @@ import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { PACKAGE_NAME, clearCredentials, daemonEntryPointerPath, daemonLogPath, defaultApiUrl, installCommand, loadCredentials, parseChangelog, popoverHome, releasesInstalled, renderReleaseNotes, } from "@popoverinstall/shared";
7
7
  import { changelog } from "./changelog.js";
8
+ import { beginUpdateLock, clearDaemonLock, clearUpdateLock, readDaemonLock, updateInProgress, verifyDaemonLock, } from "./daemon-lock.js";
8
9
  import { resolveDaemonEntry } from "./daemon-entry.js";
9
10
  import { doctor } from "./doctor.js";
10
11
  import { execTool } from "./exec.js";
11
12
  import { askDaemon, daemonIsRunning } from "./ipc-client.js";
12
13
  import { login } from "./login.js";
14
+ import { rename } from "./rename.js";
13
15
  import { setup } from "./setup.js";
14
16
  import { forkCommand } from "./snapshot.js";
15
17
  import { bad, c, dim, info, ok, warn } from "./ui.js";
@@ -36,7 +38,9 @@ async function main() {
36
38
  return welcome({ apiUrl: flag(rest, "--api") ?? defaultApiUrl() });
37
39
  case "logout":
38
40
  clearCredentials();
39
- ok("Signed out. The daemon will stop publishing after it restarts.");
41
+ // No longer "after it restarts": a running daemon re-reads this file on every publish
42
+ // tick and drops its connection when it goes, so signing out takes effect in seconds.
43
+ ok("Signed out. The daemon stops publishing within a few seconds.");
40
44
  dim("Revoke the machine at the dashboard to cut off access immediately.");
41
45
  return 0;
42
46
  case "update":
@@ -49,6 +53,8 @@ async function main() {
49
53
  return doctor();
50
54
  case "team":
51
55
  return team();
56
+ case "rename":
57
+ return rename(rest);
52
58
  case "daemon":
53
59
  return daemonCommand(rest[0] ?? "status");
54
60
  case "fork":
@@ -71,7 +77,7 @@ async function main() {
71
77
  case "ask":
72
78
  case "tell":
73
79
  bad(`\`popover ${command}\` has moved into the session.`);
74
- dim(`Run /popover:team ${command} <agent> <${command === "ask" ? "question" : "message"}> in Claude Code instead.`);
80
+ dim(`Run /popover:${command} <agent> <${command === "ask" ? "question" : "message"}> in Claude Code instead.`);
75
81
  dim("An agent there has the context to write a question worth answering — and to");
76
82
  dim("do something with the answer.");
77
83
  return 1;
@@ -135,7 +141,7 @@ function usage() {
135
141
  ${c.bold("popover")} — connect this machine, and see what your team's agents are doing
136
142
  ${next ? `\n${next}` : ""}
137
143
  Asking and telling happen inside a session, where the agent doing them has the
138
- context to be worth reading: ${c.bold("/popover:team ask B1 …")} and ${c.bold("/popover:team tell B1 …")}
144
+ context to be worth reading: ${c.bold("/popover:ask B1 …")} and ${c.bold("/popover:tell B1 …")}
139
145
 
140
146
  ${c.bold("popover login")} [--api URL] Connect this machine to your team
141
147
  ${c.bold("popover logout")} Forget this machine's credentials
@@ -145,6 +151,7 @@ ${next ? `\n${next}` : ""}
145
151
  ${c.bold("popover changelog")} [--all] What the last update changed
146
152
  ${c.bold("popover status")} Show this machine's sessions and connection
147
153
  ${c.bold("popover team")} Print the team roster
154
+ ${c.bold("popover rename")} [handle] Name an agent, so your team can tell them apart
148
155
  ${c.bold("popover fork")} <create|open|launch|list|revoke>
149
156
  ${c.bold("popover doctor")} Diagnose setup problems
150
157
  ${c.bold("popover daemon")} <start|stop|restart|status|logs>
@@ -177,15 +184,51 @@ ${next ? `\n${next}` : ""}
177
184
  * `setup()` has to run afterwards. Claude Code caches plugins by version, so without
178
185
  * re-registering, the user keeps running the previous plugin against the new daemon — a
179
186
  * version skew that produces no error, just quietly stale behaviour.
187
+ *
188
+ * 3. **Something else will try to start the daemon while npm is mid-install.** Stopping the
189
+ * daemon is necessary but not sufficient: the plugin's SessionStart hook runs in every
190
+ * Claude Code session on the machine, and all it knows is that no daemon is answering. It
191
+ * would then spawn one out of the tree npm is replacing, whose open file handles make
192
+ * npm's rename fail EPERM/EBUSY on Windows and abort the update half-done. The marker
193
+ * written below is the interlock; `updateInProgress()` is what the hook and `daemon start`
194
+ * both check.
195
+ *
196
+ * Its scope is exactly the window it guards — from before the stop, because the race opens
197
+ * the instant the daemon stops answering, to the moment npm is done with the tree. It is
198
+ * released before anything restarts the daemon, or the interlock would suppress our own
199
+ * restart, and it is released in a `finally` with a deadline on top, so neither a thrown
200
+ * npm, a Ctrl-C, nor a power cut can leave a machine that refuses to start its daemon.
180
201
  */
202
+ const UPDATE_LOCK_TTL_MS = 360_000; // the npm timeout below, with room to spare
181
203
  async function update() {
182
204
  // Asked before stopping it, because the daemon is the only thing that knows which
183
205
  // version the backend publishes.
184
206
  const pong = await askDaemon({ t: "ping", id: "update" }, 2_000);
185
207
  const expected = pong?.t === "pong" ? pong.update?.latest : undefined;
186
208
  info(`Updating ${PACKAGE_NAME} (${VERSION} → ${expected ?? "latest"})`);
187
- await daemonCommand("stop").catch(() => 0);
209
+ let failure;
210
+ beginUpdateLock(UPDATE_LOCK_TTL_MS);
188
211
  try {
212
+ const stopped = await daemonCommand("stop").catch(() => 1);
213
+ // A refused stop is not a stopped daemon. `daemon stop` deliberately signals nothing
214
+ // when it cannot verify the lock's pid is ours — which includes any case where the
215
+ // process probe itself is unavailable or slow, and on Windows that is a real
216
+ // possibility (no PowerShell on PATH, constrained language mode, a timed-out probe).
217
+ // Carrying on hands npm an install tree the daemon is still holding open, and npm's
218
+ // rename fails EPERM/EBUSY partway through, leaving a half-replaced global install —
219
+ // the precise failure the update interlock exists to prevent, arrived at from the one
220
+ // direction the interlock does not cover.
221
+ //
222
+ // Liveness is the real test, not the exit code: if nothing answers a ping then the
223
+ // daemon is gone whatever the lock file claimed, and the update is safe to run.
224
+ if (stopped !== 0) {
225
+ const stillAlive = await askDaemon({ t: "ping", id: "update" }, 1_500);
226
+ if (stillAlive?.t === "pong") {
227
+ bad("The daemon is still running and could not be stopped, so nothing was installed.");
228
+ dim("Stop it by hand or reboot, then run `popover update` again.");
229
+ return 1;
230
+ }
231
+ }
189
232
  // `execTool`, not `execFile`: on Windows `npm` is a `.cmd` that Node will not spawn
190
233
  // directly, and the update used to die on `spawn npm ENOENT` before reaching the
191
234
  // registry. See packages/cli/src/exec.ts.
@@ -197,6 +240,15 @@ async function update() {
197
240
  process.stdout.write(stdout);
198
241
  }
199
242
  catch (err) {
243
+ failure = err;
244
+ }
245
+ finally {
246
+ // npm is finished with the install tree either way, so nothing is gained by holding the
247
+ // interlock over the restart below — and holding it would block that restart.
248
+ clearUpdateLock();
249
+ }
250
+ if (failure) {
251
+ const err = failure;
200
252
  process.stdout.write(err.stdout ?? "");
201
253
  const output = `${err.stdout ?? ""}${err.stderr ?? ""}`;
202
254
  // A release that the registry will not serve yet: publishing is a separate step from the
@@ -383,6 +435,13 @@ async function daemonCommand(sub) {
383
435
  ok("Daemon is already running.");
384
436
  return 0;
385
437
  }
438
+ // An upgrade is replacing the tree the daemon runs out of. Starting one now would hold
439
+ // those files open and make npm's rename fail — see `update()`. `popover update` clears
440
+ // this before its own restart, so this can never suppress that.
441
+ if (updateInProgress()) {
442
+ warn("An update is in progress; not starting the daemon until it finishes.");
443
+ return 0;
444
+ }
386
445
  const entry = resolveDaemonEntry();
387
446
  if (!entry) {
388
447
  bad("Could not find the daemon build.");
@@ -424,30 +483,57 @@ async function daemonCommand(sub) {
424
483
  warn("The daemon accepted the stop but is still running; signalling it.");
425
484
  }
426
485
  // Fallback: a daemon too old to know this request, or one that is wedged.
427
- const lock = path.join(popoverHome(), "daemon.lock");
428
- if (!existsSync(lock)) {
429
- warn("No daemon lock file; it may not be running.");
486
+ //
487
+ // Everything below treats the lock file as a claim to be checked rather than an
488
+ // instruction to be followed. It is written by a process that may have died without
489
+ // removing it, and pids are reused — so a lock that cannot be tied to a running popover
490
+ // daemon is reported and cleaned, never signalled. See packages/cli/src/daemon-lock.ts.
491
+ const lock = readDaemonLock();
492
+ if (!lock) {
493
+ warn("No readable daemon lock file; it may not be running.");
494
+ clearDaemonLock();
430
495
  return 0;
431
496
  }
432
- const pid = Number(readFileSync(lock, "utf8").trim());
433
- if (!Number.isFinite(pid)) {
434
- bad("Lock file is unreadable.");
435
- return 1;
436
- }
437
- try {
438
- process.kill(pid, "SIGTERM");
439
- ok(`Signalled daemon (pid ${pid}).`);
440
- return 0;
497
+ const verdict = verifyDaemonLock(lock, { expectedEntry: resolveDaemonEntry() });
498
+ if (verdict.ok) {
499
+ try {
500
+ process.kill(verdict.pid, "SIGTERM");
501
+ ok(`Signalled daemon (pid ${verdict.pid}).`);
502
+ return 0;
503
+ }
504
+ catch {
505
+ // It exited between the check and the signal, which is a stop by any other name.
506
+ warn("Daemon was not running.");
507
+ clearDaemonLock();
508
+ return 0;
509
+ }
441
510
  }
442
- catch {
443
- warn("Daemon was not running.");
444
- return 0;
511
+ if (verdict.reason === "unknown") {
512
+ // The one case where doing nothing is the answer: we could not establish what that
513
+ // pid is, and a wrong guess signals a process that has nothing to do with popover.
514
+ // The lock is left alone too — it may belong to a daemon that is perfectly alive.
515
+ bad(`Could not confirm that pid ${lock.pid} is the popover daemon (${verdict.detail}).`);
516
+ dim("Nothing was signalled. Stop it by hand if you are sure, or reboot.");
517
+ return 1;
445
518
  }
519
+ warn(`Ignoring a stale daemon lock — ${verdict.detail}.`);
520
+ clearDaemonLock();
521
+ return 0;
446
522
  }
447
- case "restart":
448
- await daemonCommand("stop");
523
+ case "restart": {
524
+ const stopped = await daemonCommand("stop");
449
525
  await sleep(1200);
526
+ // Starting unconditionally after a refused stop is how you end up with two daemons
527
+ // racing for one socket. `stop` refuses when it cannot confirm the locked pid is
528
+ // ours, so check whether anything is actually still answering before starting
529
+ // another — the ping is the truth, the lock is only a hint.
530
+ if (stopped !== 0 && (await daemonIsRunning())) {
531
+ bad("The daemon is still running and could not be stopped, so it was not restarted.");
532
+ dim("Stop it by hand or reboot, then run `popover daemon start`.");
533
+ return 1;
534
+ }
450
535
  return daemonCommand("start");
536
+ }
451
537
  case "status":
452
538
  if (await daemonIsRunning()) {
453
539
  ok("Daemon is running.");