@popoverinstall/cli 0.7.1 → 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.
- package/CHANGELOG.md +101 -0
- package/README.md +90 -19
- package/dist/changelog.d.ts.map +1 -1
- package/dist/changelog.js +8 -1
- package/dist/changelog.js.map +1 -1
- package/dist/daemon-lock.d.ts +102 -0
- package/dist/daemon-lock.d.ts.map +1 -0
- package/dist/daemon-lock.js +321 -0
- package/dist/daemon-lock.js.map +1 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +13 -5
- package/dist/doctor.js.map +1 -1
- package/dist/exec.d.ts +49 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +58 -0
- package/dist/exec.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +154 -35
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +38 -5
- package/dist/login.js.map +1 -1
- package/dist/rename.d.ts +18 -0
- package/dist/rename.d.ts.map +1 -0
- package/dist/rename.js +252 -0
- package/dist/rename.js.map +1 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +4 -8
- package/dist/setup.js.map +1 -1
- package/dist/snapshot.js +24 -4
- package/dist/snapshot.js.map +1 -1
- package/dist/theme.d.ts +5 -3
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +7 -5
- package/dist/theme.js.map +1 -1
- package/package.json +3 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +10 -2
- package/plugin/commands/ask.md +65 -0
- package/plugin/commands/fork.md +1 -1
- package/plugin/commands/team.md +37 -64
- package/plugin/commands/tell.md +66 -0
- package/plugin/scripts/announce-roster.mjs +5 -1
- package/plugin/scripts/emit-event.mjs +17 -0
- package/plugin/scripts/ensure-daemon.mjs +41 -1
- package/plugin/scripts/roster.mjs +5 -0
- package/plugin/skills/popover/SKILL.md +23 -8
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,107 @@ already here:
|
|
|
15
15
|
Every version that ships needs an entry — an update with nothing to print says nothing at
|
|
16
16
|
all, which reads as though the update did not happen.
|
|
17
17
|
|
|
18
|
+
## 0.8.0
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Asking and telling are their own commands. `/popover:ask B1 why did you rule out redis?` and
|
|
23
|
+
`/popover:tell B1 the migration is applied` replace `/popover:team ask …` and
|
|
24
|
+
`/popover:team tell …`, and `/popover:team` is now just the roster. The three cost completely
|
|
25
|
+
different things — the roster costs nothing, an ask spends the teammate's tokens on their
|
|
26
|
+
machine, a tell spends their attention and lands in their live session — and hiding all three
|
|
27
|
+
behind one first word made the cheapest and the most intrusive look like the same gesture.
|
|
28
|
+
The old forms still work and mention the new name once, so nothing you have already learned
|
|
29
|
+
breaks.
|
|
30
|
+
- Agents can have names. `popover rename B1 "auth work"` gives one of your agents a name, and
|
|
31
|
+
your teammates see it on their next roster while the dashboard picks it up live — so three
|
|
32
|
+
sessions in the same repo stop being three identical rows of `B1`, `B2`, `B3`. Run
|
|
33
|
+
`popover rename` on its own to pick from a list instead of looking a handle up first. A named
|
|
34
|
+
agent answers to either, so `/popover:ask B1` and `/popover:ask "auth work"` both
|
|
35
|
+
reach it. You can only name your own agents, the name goes when the session does, and
|
|
36
|
+
`popover rename B1 ""` takes it off again. Nothing changes for anyone who never uses it: a
|
|
37
|
+
roster with no named agents on it prints exactly as it did before.
|
|
38
|
+
- The documentation is public. popover.to/docs is the whole of how it works — the roster,
|
|
39
|
+
asking, telling, forking, which agents can see each other, and what leaves your machine —
|
|
40
|
+
and popover.to/changelog is this file, with a URL you can send someone. Neither needs an
|
|
41
|
+
account: until now the only thing a person could read before installing was the poster, so
|
|
42
|
+
deciding whether to run the install command meant asking somebody who already had.
|
|
43
|
+
- `popover changelog` points at popover.to/changelog when an install has no notes to read,
|
|
44
|
+
rather than at a repository that is private and answers a 404.
|
|
45
|
+
|
|
46
|
+
### Fixes
|
|
47
|
+
|
|
48
|
+
- Everyone on your team needs this release. The way a repo is identified changed, and a popover
|
|
49
|
+
older than 0.8.0 cannot prove which repo it is in — so an out-of-date machine keeps working
|
|
50
|
+
and keeps its own agents up to date, but goes quiet for the rest of the team until it
|
|
51
|
+
updates. It says so on its own roster and in `popover doctor`, and your roster counts the
|
|
52
|
+
teammates it cannot show you rather than leaving you to wonder where everybody went.
|
|
53
|
+
- An unrecognised repo identity costs you the identity, not the update. A publish carries every
|
|
54
|
+
session on the machine in one write, so refusing that write over one field it did not like
|
|
55
|
+
took the whole machine off the air — no roster, no status, and its own agents frozen at
|
|
56
|
+
whatever they were last doing — while the connection itself looked perfectly healthy. A value
|
|
57
|
+
we cannot verify is now dropped and the update lands without it.
|
|
58
|
+
- Working in the same repo is now something popover checks rather than something it takes your
|
|
59
|
+
word for. A repo was identified by the name of its git remote, which anyone can type, so
|
|
60
|
+
anyone on your team could claim to be in a repo they had never cloned and see — and ask —
|
|
61
|
+
the agents working in it. Identity now comes from the repository's own history, which you
|
|
62
|
+
only have if you have actually cloned it, and what reaches the server is a hash of that
|
|
63
|
+
rather than the thing itself, so it cannot be read off one session and reused on another.
|
|
64
|
+
- Revoking a machine ends it. A revoked machine could quietly un-revoke itself and carry on,
|
|
65
|
+
which made the button on the dashboard a suggestion.
|
|
66
|
+
- The agent that answers you cannot rewrite what you asked it, and the agent you send a
|
|
67
|
+
message to cannot change who it came from. Both are what `/activity` and `popover log` show
|
|
68
|
+
you afterwards, so both are now fixed at the moment they are written.
|
|
69
|
+
- Nothing a teammate can type reaches your agent as an instruction. Names, titles and the
|
|
70
|
+
activity shown on the roster are text your agent reads, and a newline in the right place
|
|
71
|
+
could forge a line that looked like popover talking. That text is now bounded and stripped
|
|
72
|
+
of anything that can start a new line, hide itself, or reverse the one it is on.
|
|
73
|
+
- A laptop and a desktop no longer give two agents the same handle. Both machines numbered
|
|
74
|
+
their own agents from one, so `G1` could mean either, and asking for it reached neither.
|
|
75
|
+
Handles are now handed out where all of your machines can be seen at once.
|
|
76
|
+
- `popover daemon stop` stops popover and nothing else. It trusted a leftover file naming a
|
|
77
|
+
process id, which the operating system may since have given to something entirely
|
|
78
|
+
unrelated — and `popover update` runs it for you.
|
|
79
|
+
- `popover update` no longer risks leaving half an install behind on Windows, where a file
|
|
80
|
+
being replaced cannot be one a running program still holds open.
|
|
81
|
+
- A slow moment no longer takes your agents off your teammates' rosters. If popover could not
|
|
82
|
+
read the list of running sessions — a busy machine, a moment's hesitation — it concluded
|
|
83
|
+
every one of them had ended.
|
|
84
|
+
- `popover fork revoke` tells you the truth when it fails. It reported that a conversation had
|
|
85
|
+
been destroyed without checking, which is the one command whose word you would not think to
|
|
86
|
+
doubt.
|
|
87
|
+
- Agents belonging to teammates whose names are not written in the Latin alphabet can be
|
|
88
|
+
reached again. A teammate called 李明 is given the handle 李1, and asking for it matched every
|
|
89
|
+
agent whose handle ended in the same digit — so `/popover:team ask 李1` came back "did you
|
|
90
|
+
mean 李1 or 王1?" with no third thing you could type that would have worked. Their name now
|
|
91
|
+
finds them too, and so does the name of an agent renamed in any script.
|
|
92
|
+
- The roster lines up its columns for those agents rather than shearing them. Column widths
|
|
93
|
+
were counted in characters, and a character in Chinese, Japanese or Korean takes two columns
|
|
94
|
+
of a terminal, as does an emoji — so every such row drifted a column further left than the
|
|
95
|
+
one above it.
|
|
96
|
+
- Asking for something with no letters or digits in it — punctuation, or an emoji on its own —
|
|
97
|
+
says nothing matches, instead of matching every agent on the roster at once.
|
|
98
|
+
|
|
99
|
+
## 0.7.2
|
|
100
|
+
|
|
101
|
+
### Fixes
|
|
102
|
+
|
|
103
|
+
- `popover update` updates the machine on Windows instead of handing you a command. It used
|
|
104
|
+
to print `npm install -g @popoverinstall/cli@latest` and stop, on the belief that replacing
|
|
105
|
+
the `popover.cmd` shim while it was running would corrupt it — npm's shims are built to
|
|
106
|
+
survive exactly that, which is how npm upgrades itself. Worse, the command it printed could
|
|
107
|
+
fail with `ETARGET: No matching version found` naming the release you were trying to
|
|
108
|
+
install: npm caches registry metadata for a few minutes after a publish, and the two
|
|
109
|
+
documents it caches can disagree about which versions exist. The update now re-checks that
|
|
110
|
+
metadata rather than trusting the cache, and if a release really has not propagated yet it
|
|
111
|
+
says so and tells you to try again shortly, instead of reporting that the version does not
|
|
112
|
+
exist.
|
|
113
|
+
- `popover setup` registers the plugin on Windows when your user directory has a space in it.
|
|
114
|
+
It was passing the plugin's path through a shell unquoted, so Claude Code was told to add a
|
|
115
|
+
marketplace at the first half of the path and answered `Path does not exist` — which also
|
|
116
|
+
meant every `popover update` left the old plugin registered against the new daemon.
|
|
117
|
+
`popover uninstall` was failing to unhook Claude Code on Windows for a related reason.
|
|
118
|
+
|
|
18
119
|
## 0.7.1
|
|
19
120
|
|
|
20
121
|
### Features
|
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# popover
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Ask your teammates' agents, not your teammates.**
|
|
4
4
|
|
|
5
5
|
When a teammate's agent has already worked out why the retry logic lives where it does, you
|
|
6
6
|
should be able to ask it — without interrupting your teammate. popover shows you everyone's
|
|
7
|
-
running agents in the
|
|
8
|
-
context.
|
|
7
|
+
running Claude Code agents in the repo you are working in, and answers from a read-only fork
|
|
8
|
+
of the agent's full accumulated context.
|
|
9
|
+
|
|
10
|
+
Full documentation: **[popover.to/docs](https://www.popover.to/docs)**.
|
|
9
11
|
|
|
10
12
|
## Install
|
|
11
13
|
|
|
@@ -13,7 +15,8 @@ context.
|
|
|
13
15
|
npm install -g @popoverinstall/cli && popover welcome
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
Requires **Node.js 20+** and **Claude Code 2.1.229+**.
|
|
18
|
+
Requires **Node.js 20+** and **Claude Code 2.1.229+**. The same two commands on macOS, Linux
|
|
19
|
+
and Windows.
|
|
17
20
|
|
|
18
21
|
`npm install -g` only places files. `popover welcome` is the rest of a first run: it registers
|
|
19
22
|
the Claude Code plugin, records where the daemon lives, and offers to connect this machine. It
|
|
@@ -25,20 +28,71 @@ connection — and `welcome` is re-runnable, so it is also the way back if eithe
|
|
|
25
28
|
|
|
26
29
|
## Use
|
|
27
30
|
|
|
28
|
-
Inside Claude Code, `/team`
|
|
29
|
-
|
|
31
|
+
Inside Claude Code, `/popover:team` prints the roster: every agent your team is running in the
|
|
32
|
+
repo you are in, and what each one is doing right now. Each gets a short handle.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
*You*
|
|
36
|
+
A1 ad pipeline idle 34m — Wiring Kling into the ad pipeline
|
|
37
|
+
A2 popover editing main.py — Team agent communication
|
|
38
|
+
|
|
39
|
+
*Bob Chen*
|
|
40
|
+
B1 api-server reading config.json — Fixing OAuth token refresh
|
|
41
|
+
B2 api-server creating plan.md — Postgres connection pooling
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Three things to do with one:
|
|
30
45
|
|
|
31
46
|
```
|
|
32
|
-
popover
|
|
33
|
-
popover
|
|
47
|
+
/popover:ask B1 why did you rule out redis?
|
|
48
|
+
/popover:tell B1 the migration is applied on prod
|
|
49
|
+
/popover:fork create
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Ask** is answered by a read-only fork of that agent, running on its owner's machine. Their
|
|
53
|
+
session is never interrupted and their source never leaves their laptop — only the answer
|
|
54
|
+
does. The fork has no tools, so a question about something the agent never looked at gets
|
|
55
|
+
"I don't know" rather than an investigation, and the answer is billed to their account.
|
|
56
|
+
|
|
57
|
+
**Tell** drops a heads-up into a running agent's context before its next prompt. No answer
|
|
58
|
+
comes back. It is for facts that would otherwise cause a collision, and it is the one thing
|
|
59
|
+
here that reaches a live session: the database caps a sender at ten messages an hour to any
|
|
60
|
+
one recipient, and the recipient's human is always notified.
|
|
61
|
+
|
|
62
|
+
**Fork** hands over the whole conversation. `/popover:fork create` returns a code that works
|
|
63
|
+
for 24 hours; whoever you send it to runs `popover fork open <code>` and carries on in their
|
|
64
|
+
own local chat. Opening one needs no popover account — `npx @popoverinstall/cli fork open
|
|
65
|
+
<code>` is enough.
|
|
66
|
+
|
|
67
|
+
Claude Code namespaces plugin commands, so it is `/popover:team`, not `/team`.
|
|
68
|
+
|
|
69
|
+
## Who can see what
|
|
70
|
+
|
|
71
|
+
Agents are only visible to each other **inside the same repo**, so the directory you run from
|
|
72
|
+
is what scopes the roster. Repos are identified by their git remotes, so two clones match
|
|
73
|
+
however they were cloned; a directory with no shared remote is private to you. The boundary is
|
|
74
|
+
enforced by row-level security in Postgres, not by the client.
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
popover welcome Set up and log in — the whole of a first run
|
|
80
|
+
popover login / logout Connect this machine, or forget its credentials
|
|
81
|
+
popover setup Re-register the Claude Code plugin
|
|
34
82
|
popover status This machine's sessions and connection
|
|
35
|
-
popover
|
|
36
|
-
popover
|
|
83
|
+
popover team Print the roster for the repo you are in
|
|
84
|
+
popover fork <create|open|launch|list|revoke>
|
|
85
|
+
popover log Questions and messages your agents received
|
|
86
|
+
popover changelog [--all] What the last update changed
|
|
37
87
|
popover update Upgrade and restart the daemon
|
|
88
|
+
popover doctor Diagnose setup problems
|
|
89
|
+
popover daemon <start|stop|restart|status|logs>
|
|
90
|
+
popover uninstall Stop the daemon, unhook Claude Code
|
|
38
91
|
```
|
|
39
92
|
|
|
40
|
-
|
|
41
|
-
|
|
93
|
+
Asking and telling are not in that list on purpose: they live in the session, where the agent
|
|
94
|
+
doing them has the context to write a question worth answering and to do something with the
|
|
95
|
+
answer.
|
|
42
96
|
|
|
43
97
|
## How it works
|
|
44
98
|
|
|
@@ -47,11 +101,27 @@ Three pieces install together:
|
|
|
47
101
|
- **the CLI** — the `popover` command
|
|
48
102
|
- **the daemon** — a detached background process that publishes this machine's sessions and
|
|
49
103
|
answers teammates' questions. It stores state in `~/.popover/`.
|
|
50
|
-
- **the Claude Code plugin** — provides `/team
|
|
51
|
-
other. Registered
|
|
104
|
+
- **the Claude Code plugin** — provides `/popover:team`, `/popover:fork`, and the MCP tools
|
|
105
|
+
agents use to reach each other. Registered by `popover setup`.
|
|
106
|
+
|
|
107
|
+
## What leaves your machine
|
|
108
|
+
|
|
109
|
+
- An **ask** exports one answer. A teammate can learn whatever is already in that agent's
|
|
110
|
+
context, including the contents of files it read earlier in the session — that is what it
|
|
111
|
+
answers from. Every ask is logged with its cost.
|
|
112
|
+
- A **fork** exports the entire conversation: every message, every tool result, the contents
|
|
113
|
+
of every file read into it. It is sealed before it leaves, with a key derived from the code,
|
|
114
|
+
so the server holds something it cannot read — but anyone holding the code can open it.
|
|
115
|
+
Treat it the way you would treat pasting the transcript into a chat window.
|
|
116
|
+
- The device token is generated here and never transmitted; the server only ever sees its
|
|
117
|
+
SHA-256.
|
|
118
|
+
|
|
119
|
+
## Troubleshooting
|
|
52
120
|
|
|
53
|
-
|
|
54
|
-
|
|
121
|
+
`popover doctor` diagnoses setup problems in root-cause order and is the right first move for
|
|
122
|
+
most things. `popover status` says whether this machine is connected; `popover daemon logs` is
|
|
123
|
+
the detail underneath. A teammate missing from the roster is usually running a version too old
|
|
124
|
+
to publish a repo identity — the roster counts them and says so.
|
|
55
125
|
|
|
56
126
|
## Uninstall
|
|
57
127
|
|
|
@@ -64,7 +134,8 @@ Credentials and logs are left in `~/.popover/`; delete the directory to remove t
|
|
|
64
134
|
|
|
65
135
|
## Links
|
|
66
136
|
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
137
|
+
- [Documentation](https://www.popover.to/docs)
|
|
138
|
+
- [Changelog](https://www.popover.to/changelog)
|
|
139
|
+
- [popover.to](https://www.popover.to) — dashboard, team and activity
|
|
69
140
|
|
|
70
|
-
MIT
|
|
141
|
+
MIT · Quo Labs, Inc.
|
package/dist/changelog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwDH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmC/D"}
|
package/dist/changelog.js
CHANGED
|
@@ -19,7 +19,14 @@ import { parseChangelog, renderReleaseNotes } from "@popoverinstall/shared";
|
|
|
19
19
|
import { keyReader, regionTextWidth, scrollRegion } from "./pager.js";
|
|
20
20
|
import { box, glyph, style } from "./theme.js";
|
|
21
21
|
import { bad, dim } from "./ui.js";
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Where to send someone whose install has no notes in it.
|
|
24
|
+
*
|
|
25
|
+
* The repository, which this used to name, is private — so the one line printed on the one
|
|
26
|
+
* path where the local copy has failed was a link to a 404. `/changelog` on the site renders
|
|
27
|
+
* the same file, is public, and needs no account.
|
|
28
|
+
*/
|
|
29
|
+
const SOURCE_URL = "https://www.popover.to/changelog";
|
|
23
30
|
/**
|
|
24
31
|
* Where the notes live, in order of preference.
|
|
25
32
|
*
|
package/dist/changelog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAgB,cAAc,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAEnC,MAAM,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** The shape written by `writeLock()` in packages/daemon/src/index.ts. */
|
|
2
|
+
export interface DaemonLock {
|
|
3
|
+
pid: number;
|
|
4
|
+
/** Epoch ms. Absent in the legacy bare-pid format. */
|
|
5
|
+
startedAt?: number;
|
|
6
|
+
/** The daemon script. Absent in the legacy bare-pid format. */
|
|
7
|
+
entry?: string;
|
|
8
|
+
/** True when the file predates the richer format and holds nothing but a pid. */
|
|
9
|
+
legacy: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type ProcessInfo = {
|
|
12
|
+
state: "running";
|
|
13
|
+
startedAt?: number;
|
|
14
|
+
commandLine?: string;
|
|
15
|
+
}
|
|
16
|
+
/** No process holds this pid. */
|
|
17
|
+
| {
|
|
18
|
+
state: "gone";
|
|
19
|
+
}
|
|
20
|
+
/** The probe itself could not run, so nothing is known either way. */
|
|
21
|
+
| {
|
|
22
|
+
state: "unknown";
|
|
23
|
+
};
|
|
24
|
+
export type LockVerdict = {
|
|
25
|
+
ok: true;
|
|
26
|
+
pid: number;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
/**
|
|
30
|
+
* `gone` and `not-ours` mean the file is stale and should be cleaned; `unknown` means
|
|
31
|
+
* we could not tell, which is the one case where the file must be left exactly as it
|
|
32
|
+
* is — deleting a live daemon's lock would strand it.
|
|
33
|
+
*/
|
|
34
|
+
reason: "bad-pid" | "gone" | "not-ours" | "unknown";
|
|
35
|
+
detail: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Parse the lock file, tolerating the format that predates it.
|
|
39
|
+
*
|
|
40
|
+
* A daemon installed before this change is still a daemon somebody may need to stop, and its
|
|
41
|
+
* lock holds a bare pid. It is accepted, flagged `legacy`, and verified against a weaker
|
|
42
|
+
* signal — see `verifyDaemonLock`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readDaemonLock(file?: string): DaemonLock | null;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the process named by the lock is still the daemon that wrote it.
|
|
47
|
+
*
|
|
48
|
+
* `expectedEntry` is the daemon build this CLI would start, used as the identity test for a
|
|
49
|
+
* legacy lock that records no entry of its own. It is a weaker test — it says "this pid is
|
|
50
|
+
* running the daemon" rather than "this pid is the one that wrote the file" — but combined
|
|
51
|
+
* with the pid still existing it is enough to rule out an unrelated process, which is the
|
|
52
|
+
* failure that matters.
|
|
53
|
+
*/
|
|
54
|
+
export declare function verifyDaemonLock(lock: DaemonLock, opts?: {
|
|
55
|
+
expectedEntry?: string | null;
|
|
56
|
+
probe?: (pid: number) => ProcessInfo;
|
|
57
|
+
}): LockVerdict;
|
|
58
|
+
/**
|
|
59
|
+
* Ask the OS what a pid is, without ever throwing.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately not `process.kill(pid, 0)`, which answers only "does something hold this
|
|
62
|
+
* number" — the exact question that led here. What is needed is identity, and identity means
|
|
63
|
+
* the start time and the command line, neither of which Node exposes for another process.
|
|
64
|
+
*
|
|
65
|
+
* Costs one subprocess. That is fine: this runs on the fallback path of `popover daemon
|
|
66
|
+
* stop`, after the pipe has already failed, and never on any hot path.
|
|
67
|
+
*/
|
|
68
|
+
export declare function inspectProcess(pid: number): ProcessInfo;
|
|
69
|
+
/** Remove a lock that has been shown to name nothing of ours. Never throws. */
|
|
70
|
+
export declare function clearDaemonLock(file?: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Marker saying "an upgrade is replacing the install tree; do not start a daemon".
|
|
73
|
+
*
|
|
74
|
+
* `popover update` stops the daemon and then hands npm up to five minutes to replace the
|
|
75
|
+
* global package. The SessionStart hook runs in *every* Claude Code session, pings, finds
|
|
76
|
+
* nothing, and starts `node <entry>` pointing into the tree npm is halfway through replacing.
|
|
77
|
+
* On Windows the daemon it starts then holds those files open, npm's rename fails EPERM or
|
|
78
|
+
* EBUSY, and the update aborts with a half-replaced global install — a machine with no
|
|
79
|
+
* working `popover` at all.
|
|
80
|
+
*
|
|
81
|
+
* The deadline is what keeps this from being worse than the problem. A marker with no expiry
|
|
82
|
+
* that survives a crashed update would suppress the daemon forever, on a machine whose owner
|
|
83
|
+
* has no idea this file exists.
|
|
84
|
+
*/
|
|
85
|
+
export declare function updateLockPath(): string;
|
|
86
|
+
export declare function beginUpdateLock(ttlMs: number): void;
|
|
87
|
+
export declare function clearUpdateLock(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Whether an upgrade is in flight right now.
|
|
90
|
+
*
|
|
91
|
+
* Every branch fails towards starting the daemon. Only a marker that is present, readable and
|
|
92
|
+
* unexpired holds anything back; a torn or truncated one means the process that wrote it was
|
|
93
|
+
* interrupted, which is a dead update rather than a running one. Reading it the other way
|
|
94
|
+
* round would leave a machine permanently without a daemon over a file its owner has never
|
|
95
|
+
* heard of — worse than the race this guards against, and far harder to diagnose.
|
|
96
|
+
*
|
|
97
|
+
* `packages/plugin/scripts/ensure-daemon.mjs` duplicates this check, down to that choice. It
|
|
98
|
+
* must stay dependency-free — a hook that fails to resolve an import breaks every session on
|
|
99
|
+
* the machine — so it cannot import this. Change both together.
|
|
100
|
+
*/
|
|
101
|
+
export declare function updateInProgress(): boolean;
|
|
102
|
+
//# sourceMappingURL=daemon-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-lock.d.ts","sourceRoot":"","sources":["../src/daemon-lock.ts"],"names":[],"mappings":"AAkCA,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE;AAChE,iCAAiC;GAC/B;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE;AACnB,sEAAsE;GACpE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAEzB,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzB;IACE,EAAE,EAAE,KAAK,CAAC;IACV;;;;OAIG;IACH,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACpD,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAyB,GAAG,UAAU,GAAG,IAAI,CAgCjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAA;CAAO,GACjF,WAAW,CAmEb;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAQvD;AAyED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,GAAE,MAAyB,GAAG,IAAI,CAMrE;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAYnD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAS1C"}
|