@popoverinstall/cli 0.7.2 → 0.8.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.
@@ -12,6 +12,16 @@ import { daemonAddress, popoverHome, readStdin, request, sendFireAndForget } fro
12
12
 
13
13
  const HERE = path.dirname(fileURLToPath(import.meta.url));
14
14
 
15
+ /*
16
+ * A fork has nothing to do here, and doing it is actively harmful.
17
+ *
18
+ * The daemon spawns forks itself, so a daemon demonstrably exists — and registering the
19
+ * fork's session id would put a ghost agent on the team roster for every ask answered. Same
20
+ * guard, same reason, as emit-event.mjs; see its header for what the ghost then does to
21
+ * attribution.
22
+ */
23
+ if (process.env.CLAUDE_CODE_ENTRYPOINT === "popover-fork") process.exit(0);
24
+
15
25
  async function main() {
16
26
  const raw = await readStdin().catch(() => "");
17
27
  let payload = null;
@@ -22,7 +32,12 @@ async function main() {
22
32
  }
23
33
 
24
34
  const alive = await ping();
25
- if (!alive) {
35
+ // `updateInProgress()` is the interlock, not an optimisation. `popover update` stops the
36
+ // daemon and hands npm up to five minutes to replace the global install; a daemon started
37
+ // in that window runs out of the tree being replaced and holds its files open, so npm's
38
+ // rename fails EPERM/EBUSY on Windows and the update aborts half-done. Every session on the
39
+ // machine runs this hook, so without the check one of them wins that race almost every time.
40
+ if (!alive && !updateInProgress()) {
26
41
  const entry = resolveDaemonEntry();
27
42
  if (entry) {
28
43
  launch(entry);
@@ -95,6 +110,31 @@ function resolveDaemonEntry() {
95
110
  return null;
96
111
  }
97
112
 
113
+ /**
114
+ * Whether `popover update` is replacing the install tree right now.
115
+ *
116
+ * Written by `beginUpdateLock` in packages/cli/src/daemon-lock.ts, which holds the readable
117
+ * version of why this exists. Duplicated here rather than imported for the same reason
118
+ * `resolveDaemonEntry` duplicates its path: this file must resolve no imports beyond the ones
119
+ * it already has, because a hook that throws breaks every session on the machine. Keep the
120
+ * two in step.
121
+ *
122
+ * Every branch fails towards starting the daemon. A marker with no deadline, or one whose
123
+ * deadline has passed, is a crashed update rather than a running one, and refusing to start
124
+ * on that basis would leave a machine permanently without a daemon and nothing on screen to
125
+ * explain it. Only a marker that is present, readable and unexpired holds us back.
126
+ */
127
+ function updateInProgress() {
128
+ try {
129
+ const file = path.join(popoverHome(), "update.lock");
130
+ if (!existsSync(file)) return false;
131
+ const deadline = Number(JSON.parse(readFileSync(file, "utf8")).deadline);
132
+ return Number.isFinite(deadline) && Date.now() < deadline;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
98
138
  function launch(entry) {
99
139
  try {
100
140
  // Detached with stdio to a log file: the daemon must outlive this hook, this session,
@@ -18,6 +18,11 @@ const reply = await request(
18
18
  id: "cli",
19
19
  refresh: true,
20
20
  ...(callerSessionId() ? { fromSessionId: callerSessionId() } : {}),
21
+ // Fallback for the window where the daemon has not yet resolved this session's repo —
22
+ // the same hint the MCP server sends. Without it an unplaceable caller falls through to
23
+ // pure recency across every session on the machine, so `/popover:team` run in one repo
24
+ // could print the roster for another, under a heading claiming it was this one.
25
+ cwd: process.cwd(),
21
26
  },
22
27
  { timeoutMs: 8000 },
23
28
  );
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: popover
3
- description: Reach a teammate's Claude Code agent working in this same repo. Use when a question is about what a colleague decided, already ruled out, or is changing right now — why the code is the way it is when the repo does not say, whether work you are about to start is already in flight, or why something you did not touch is broken. Also covers handing this whole conversation to a teammate as a fork.
3
+ description: "Judgment for reaching a teammate's Claude Code agent *without being asked to* — when it is worth spending their tokens, and which of /popover:team, /popover:ask, /popover:tell and /popover:fork fits. Nothing to invoke by hand: use it when a question turns on what a colleague decided, ruled out, or is changing right now — why the code is the way it is when the repo does not say, whether work about to start is already in flight, or why something nobody touched is broken."
4
4
  ---
5
5
 
6
6
  # popover
@@ -10,10 +10,21 @@ accumulated context you do not have: the alternatives they rejected, the constra
10
10
  forced an awkward shape, what they changed twenty minutes ago and have not pushed. popover
11
11
  lets you reach those agents.
12
12
 
13
- The reason this skill exists is that the moment to use popover rarely announces itself. The
13
+ **This skill is not a command, and there is nothing here for a user to invoke.** The four
14
+ commands are the things a user types:
15
+
16
+ | | |
17
+ | --- | --- |
18
+ | `/popover:team` | who is working in this repo right now |
19
+ | `/popover:ask` | one question to one agent, and you wait for the answer |
20
+ | `/popover:tell` | a heads-up into a teammate's live session; nothing comes back |
21
+ | `/popover:fork` | hand this whole conversation to a teammate |
22
+
23
+ This file is the judgment around them: when reaching for one is worth it, which one fits, and
24
+ what each one spends. It exists because the moment to use popover rarely announces itself. The
14
25
  user asks why something is the way it is, and the honest answer is that the repo does not
15
- record it — but a colleague's agent worked it out this morning. Nobody will type
16
- `/popover:team` for you. Noticing is your job.
26
+ record it — but a colleague's agent worked it out this morning. Nobody will type `/popover:ask`
27
+ for you. Noticing is your job.
17
28
 
18
29
  ## The three tools
19
30
 
@@ -139,7 +150,11 @@ The tools return errors as text; relay them rather than retrying with different
139
150
 
140
151
  ## The user-facing commands
141
152
 
142
- `/popover:team` and `/popover:fork` carry the full flows for when the user drives this
143
- explicitly, including how to present a roster and how to take delivery of a fork. Do not
144
- restate their steps here or work around them when the user invokes a command, follow it.
145
- This skill is for the case where nobody invoked anything and you noticed the moment yourself.
153
+ The four commands carry the full flows for when the user drives this explicitly — how to
154
+ present a roster, how to word an ask, how to take delivery of a fork. Do not restate their
155
+ steps here or work around them: **when the user invokes a command, follow that command.** This
156
+ skill is for the other case, where nobody invoked anything and you noticed the moment yourself.
157
+
158
+ When you act on your own, prefer naming the command you are standing in for — "I'll run the
159
+ equivalent of `/popover:ask B1`" — so the user learns the surface they can drive directly next
160
+ time.