@popoverinstall/cli 0.4.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/.claude-plugin/marketplace.json +23 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/daemon-entry.d.ts +14 -0
- package/dist/daemon-entry.d.ts.map +1 -0
- package/dist/daemon-entry.js +35 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/doctor.d.ts +12 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +199 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +398 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-client.d.ts +5 -0
- package/dist/ipc-client.d.ts.map +1 -0
- package/dist/ipc-client.js +38 -0
- package/dist/ipc-client.js.map +1 -0
- package/dist/login.d.ts +10 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +128 -0
- package/dist/login.js.map +1 -0
- package/dist/setup.d.ts +15 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +113 -0
- package/dist/setup.js.map +1 -0
- package/dist/ui.d.ts +16 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +39 -0
- package/dist/ui.js.map +1 -0
- package/package.json +46 -0
- package/plugin/.claude-plugin/plugin.json +19 -0
- package/plugin/.mcp.json +9 -0
- package/plugin/README.md +58 -0
- package/plugin/commands/team.md +63 -0
- package/plugin/hooks/hooks.json +99 -0
- package/plugin/mcp/index.mjs +286 -0
- package/plugin/scripts/_ipc.mjs +146 -0
- package/plugin/scripts/emit-event.mjs +27 -0
- package/plugin/scripts/ensure-daemon.mjs +116 -0
- package/plugin/scripts/roster.mjs +47 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "popover",
|
|
4
|
+
"description": "Talk to your teammates' Claude Code agents.",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Quolabs",
|
|
7
|
+
"url": "https://github.com/many-ideations/popover"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "popover",
|
|
12
|
+
"source": "./plugin",
|
|
13
|
+
"description": "See your teammates' active Claude Code agents and ask them questions. A read-only fork answers from the agent's full context without interrupting your teammate.",
|
|
14
|
+
"category": "collaboration",
|
|
15
|
+
"tags": [
|
|
16
|
+
"team",
|
|
17
|
+
"agents",
|
|
18
|
+
"mcp",
|
|
19
|
+
"collaboration"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quolabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# popover
|
|
2
|
+
|
|
3
|
+
See your teammates' active Claude Code agents, and ask them questions.
|
|
4
|
+
|
|
5
|
+
When a teammate's agent has already worked out why the retry logic lives where it does, you
|
|
6
|
+
should be able to ask it — without interrupting your teammate. popover shows you everyone's
|
|
7
|
+
running agents in the same repo, and answers from a read-only fork of the agent's full
|
|
8
|
+
context.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @popoverinstall/cli
|
|
14
|
+
popover login
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires **Node.js 20+** and **Claude Code 2.1.229+**.
|
|
18
|
+
|
|
19
|
+
`popover login` connects this machine to your team and registers the Claude Code plugin. It
|
|
20
|
+
prints a short code to confirm in your browser; the device token it stores is generated
|
|
21
|
+
locally and never transmitted.
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
Inside Claude Code, `/team` shows everyone's agents in the repo you are working in. From the
|
|
26
|
+
shell:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
popover team Print the team roster
|
|
30
|
+
popover ask <who> <question> Ask a teammate's agent directly
|
|
31
|
+
popover status This machine's sessions and connection
|
|
32
|
+
popover log Recent questions asked of your agents
|
|
33
|
+
popover doctor Diagnose setup problems
|
|
34
|
+
popover update Upgrade and restart the daemon
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Agents are only visible to each other **inside the same repo**, so the directory you run from
|
|
38
|
+
is what scopes the roster.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
Three pieces install together:
|
|
43
|
+
|
|
44
|
+
- **the CLI** — the `popover` command
|
|
45
|
+
- **the daemon** — a detached background process that publishes this machine's sessions and
|
|
46
|
+
answers teammates' questions. It stores state in `~/.popover/`.
|
|
47
|
+
- **the Claude Code plugin** — provides `/team` and the MCP tools agents use to reach each
|
|
48
|
+
other. Registered automatically on first login.
|
|
49
|
+
|
|
50
|
+
Answers are produced by a read-only fork of the teammate's agent, billed to their account.
|
|
51
|
+
Their session is never interrupted.
|
|
52
|
+
|
|
53
|
+
## Uninstall
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
popover uninstall # stop the daemon, unhook Claude Code
|
|
57
|
+
npm uninstall -g @popoverinstall/cli
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Credentials and logs are left in `~/.popover/`; delete the directory to remove those too.
|
|
61
|
+
|
|
62
|
+
## Links
|
|
63
|
+
|
|
64
|
+
- [Source](https://github.com/many-ideations/popover)
|
|
65
|
+
- [Issues](https://github.com/many-ideations/popover/issues)
|
|
66
|
+
|
|
67
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the daemon build lives.
|
|
3
|
+
*
|
|
4
|
+
* Node's own resolver goes first because it is correct under every layout — npm's nested
|
|
5
|
+
* global tree, a hoisted one, pnpm's store, and the workspace symlink in a dev checkout —
|
|
6
|
+
* without this function having to know which it is looking at. The relative candidates
|
|
7
|
+
* remain for the one case the resolver cannot help with: a source checkout that has been
|
|
8
|
+
* built but never `npm install`ed.
|
|
9
|
+
*
|
|
10
|
+
* Lives in its own module so `setup.ts` can use it without importing `index.ts`, which
|
|
11
|
+
* runs the CLI on import.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveDaemonEntry(): string | null;
|
|
14
|
+
//# sourceMappingURL=daemon-entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-entry.d.ts","sourceRoot":"","sources":["../src/daemon-entry.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAclD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const require_ = createRequire(import.meta.url);
|
|
7
|
+
/**
|
|
8
|
+
* Where the daemon build lives.
|
|
9
|
+
*
|
|
10
|
+
* Node's own resolver goes first because it is correct under every layout — npm's nested
|
|
11
|
+
* global tree, a hoisted one, pnpm's store, and the workspace symlink in a dev checkout —
|
|
12
|
+
* without this function having to know which it is looking at. The relative candidates
|
|
13
|
+
* remain for the one case the resolver cannot help with: a source checkout that has been
|
|
14
|
+
* built but never `npm install`ed.
|
|
15
|
+
*
|
|
16
|
+
* Lives in its own module so `setup.ts` can use it without importing `index.ts`, which
|
|
17
|
+
* runs the CLI on import.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveDaemonEntry() {
|
|
20
|
+
if (process.env.POPOVER_DAEMON_ENTRY && existsSync(process.env.POPOVER_DAEMON_ENTRY)) {
|
|
21
|
+
return process.env.POPOVER_DAEMON_ENTRY;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return require_.resolve("@popoverinstall/daemon/entry");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* not installed as a dependency — fall through to the checkout layout */
|
|
28
|
+
}
|
|
29
|
+
const candidates = [
|
|
30
|
+
path.join(HERE, "..", "..", "daemon", "dist", "index.js"),
|
|
31
|
+
path.join(HERE, "..", "node_modules", "@popoverinstall", "daemon", "dist", "index.js"),
|
|
32
|
+
];
|
|
33
|
+
return candidates.find((p) => existsSync(p)) ?? null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=daemon-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-entry.js","sourceRoot":"","sources":["../src/daemon-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACrF,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC;QACzD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC;KACvF,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACvD,CAAC"}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Minimum Claude Code version. Below this, `claude agents --json` and the fork flags we depend on are missing or differ. */
|
|
2
|
+
export declare const MIN_CLAUDE_VERSION: readonly [2, 1, 229];
|
|
3
|
+
/**
|
|
4
|
+
* Diagnose a machine end to end.
|
|
5
|
+
*
|
|
6
|
+
* Ordered so the first failure is the root cause: no point reporting "roster empty" when
|
|
7
|
+
* the real problem is that the CLI is not installed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function doctor(): Promise<number>;
|
|
10
|
+
export declare function claudeVersion(): Promise<string | null>;
|
|
11
|
+
export declare function versionAtLeast(version: string, min: readonly [number, number, number]): boolean;
|
|
12
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAUA,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,CAY5D;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
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { claudeHome, daemonAddress, loadCredentials } from "@popoverinstall/shared";
|
|
6
|
+
import { askDaemon } from "./ipc-client.js";
|
|
7
|
+
import { bad, dim, ok, warn } from "./ui.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
/** Minimum Claude Code version. Below this, `claude agents --json` and the fork flags we depend on are missing or differ. */
|
|
10
|
+
export const MIN_CLAUDE_VERSION = [2, 1, 229];
|
|
11
|
+
/**
|
|
12
|
+
* Diagnose a machine end to end.
|
|
13
|
+
*
|
|
14
|
+
* Ordered so the first failure is the root cause: no point reporting "roster empty" when
|
|
15
|
+
* the real problem is that the CLI is not installed.
|
|
16
|
+
*/
|
|
17
|
+
export async function doctor() {
|
|
18
|
+
let failures = 0;
|
|
19
|
+
const fail = (msg, hint) => {
|
|
20
|
+
bad(msg);
|
|
21
|
+
if (hint)
|
|
22
|
+
dim(hint);
|
|
23
|
+
failures += 1;
|
|
24
|
+
};
|
|
25
|
+
// 1. Claude Code
|
|
26
|
+
const version = await claudeVersion();
|
|
27
|
+
if (!version) {
|
|
28
|
+
fail("claude CLI not found on PATH", "Install Claude Code, or set POPOVER_CLAUDE_BIN.");
|
|
29
|
+
}
|
|
30
|
+
else if (!versionAtLeast(version, MIN_CLAUDE_VERSION)) {
|
|
31
|
+
fail(`claude ${version} is too old (need ${MIN_CLAUDE_VERSION.join(".")}+)`, "Earlier versions lack `claude agents --json` and the fork flags popover relies on.");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
ok(`claude ${version}`);
|
|
35
|
+
}
|
|
36
|
+
// 2. Plugin hooks
|
|
37
|
+
const hooksInstalled = pluginLooksInstalled();
|
|
38
|
+
if (hooksInstalled)
|
|
39
|
+
ok("popover plugin found");
|
|
40
|
+
else {
|
|
41
|
+
warn("popover plugin not detected in ~/.claude");
|
|
42
|
+
dim("Install it with: claude plugin install popover@popoverinstall");
|
|
43
|
+
dim("Without it, your sessions will not appear to your team.");
|
|
44
|
+
}
|
|
45
|
+
// 3. Credentials
|
|
46
|
+
const creds = loadCredentials();
|
|
47
|
+
if (!creds) {
|
|
48
|
+
fail("this machine is not signed in", "Run `popover login`.");
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
ok(`signed in (machine ${creds.machine_id.slice(0, 8)}…)`);
|
|
52
|
+
}
|
|
53
|
+
// 4. Daemon
|
|
54
|
+
const pong = await askDaemon({ t: "ping", id: "doctor" }, 2000);
|
|
55
|
+
if (!pong || pong.t !== "pong") {
|
|
56
|
+
fail("daemon is not running", `Start it with \`popover daemon start\`. Socket: ${daemonAddress()}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
ok(`daemon running (v${pong.version})`);
|
|
60
|
+
if (!pong.authenticated) {
|
|
61
|
+
fail("daemon is running but not authenticated", "Its token exchange failed. Check the backend, then `popover daemon restart`.");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 5. Backend round trip — the check that actually exercises the JWT path.
|
|
65
|
+
if (creds) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${creds.api_url}/api/device/session`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "content-type": "application/json" },
|
|
70
|
+
// No daemon_version: this is the CLI exercising the token path, not the daemon
|
|
71
|
+
// checking in. Sending one overwrote the recorded version with "doctor", so a
|
|
72
|
+
// machine's Daemon column read "doctor" for as long as nobody restarted it.
|
|
73
|
+
body: JSON.stringify({ device_token: creds.device_token }),
|
|
74
|
+
});
|
|
75
|
+
if (res.ok) {
|
|
76
|
+
ok(`backend reachable (${creds.api_url})`);
|
|
77
|
+
}
|
|
78
|
+
else if (res.status === 401) {
|
|
79
|
+
fail("this machine's access was revoked", "Run `popover login` to reconnect.");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
fail(`backend returned ${res.status}`, "Check SUPABASE_JWT_SECRET on the server.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
fail(`cannot reach ${creds.api_url}`, err instanceof Error ? err.message : String(err));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 6. git — how repos are identified, and therefore who can see whom.
|
|
90
|
+
if (await gitVersion()) {
|
|
91
|
+
ok("git found");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
fail("git not found on PATH", "popover matches agents by their git remote. Without git every session looks like a " +
|
|
95
|
+
"private directory, and no teammate will ever be visible.");
|
|
96
|
+
}
|
|
97
|
+
// 7. Roster. Scoped to this directory, exactly as `popover team` is.
|
|
98
|
+
if (pong?.t === "pong" && pong.authenticated) {
|
|
99
|
+
const roster = await askDaemon({ t: "roster", id: "doctor", refresh: true, cwd: process.cwd() }, 15000);
|
|
100
|
+
if (roster?.t === "roster.ok") {
|
|
101
|
+
// Reported from the daemon's own answer rather than recomputed here, so this says
|
|
102
|
+
// what `popover team` will actually do in this directory.
|
|
103
|
+
if (roster.scope?.kind === "repo")
|
|
104
|
+
ok(`this directory is ${roster.scope.repo}`);
|
|
105
|
+
else if (roster.scope?.kind === "all") {
|
|
106
|
+
warn("this directory is not a git repository");
|
|
107
|
+
dim("Agents here are private to this machine. Run popover from inside a clone.");
|
|
108
|
+
}
|
|
109
|
+
const others = roster.entries.filter((e) => !e.isSelf).length;
|
|
110
|
+
ok(`roster: ${roster.entries.length} agent(s), ${others} from teammates`);
|
|
111
|
+
if (roster.entries.length === 0) {
|
|
112
|
+
dim("No sessions visible yet. Open a Claude Code session and re-run.");
|
|
113
|
+
}
|
|
114
|
+
if (roster.hiddenOlderClients) {
|
|
115
|
+
warn(`${roster.hiddenOlderClients} teammate machine(s) are on an older popover`);
|
|
116
|
+
dim("They stay hidden until they run `popover update`.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (roster?.t === "error") {
|
|
120
|
+
fail(`roster failed: ${roster.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log("");
|
|
124
|
+
if (failures === 0)
|
|
125
|
+
ok("Everything looks healthy.");
|
|
126
|
+
else
|
|
127
|
+
bad(`${failures} problem(s) found.`);
|
|
128
|
+
return failures === 0 ? 0 : 1;
|
|
129
|
+
}
|
|
130
|
+
export async function claudeVersion() {
|
|
131
|
+
const bin = process.env.POPOVER_CLAUDE_BIN ?? "claude";
|
|
132
|
+
try {
|
|
133
|
+
const { stdout } = await execFileAsync(bin, ["--version"], {
|
|
134
|
+
timeout: 10_000,
|
|
135
|
+
windowsHide: true,
|
|
136
|
+
shell: process.platform === "win32",
|
|
137
|
+
});
|
|
138
|
+
return stdout.trim().split(/\s+/)[0] ?? null;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function gitVersion() {
|
|
145
|
+
try {
|
|
146
|
+
const { stdout } = await execFileAsync("git", ["--version"], {
|
|
147
|
+
timeout: 5_000,
|
|
148
|
+
windowsHide: true,
|
|
149
|
+
});
|
|
150
|
+
return stdout.trim() || null;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function versionAtLeast(version, min) {
|
|
157
|
+
const parts = version.split(".").map((p) => Number.parseInt(p, 10));
|
|
158
|
+
for (let i = 0; i < 3; i += 1) {
|
|
159
|
+
const got = parts[i] ?? 0;
|
|
160
|
+
const want = min[i];
|
|
161
|
+
if (got > want)
|
|
162
|
+
return true;
|
|
163
|
+
if (got < want)
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Look for the plugin in Claude Code's settings.
|
|
170
|
+
*
|
|
171
|
+
* Deliberately a soft check: plugins can be enabled at several scopes and loaded from
|
|
172
|
+
* places we cannot enumerate, so a negative result is a warning rather than a failure.
|
|
173
|
+
*/
|
|
174
|
+
function pluginLooksInstalled() {
|
|
175
|
+
const candidates = [
|
|
176
|
+
path.join(claudeHome(), "settings.json"),
|
|
177
|
+
path.join(process.cwd(), ".claude", "settings.json"),
|
|
178
|
+
path.join(process.cwd(), ".claude", "settings.local.json"),
|
|
179
|
+
];
|
|
180
|
+
for (const file of candidates) {
|
|
181
|
+
try {
|
|
182
|
+
if (!existsSync(file))
|
|
183
|
+
continue;
|
|
184
|
+
if (JSON.stringify(JSON.parse(readFileSync(file, "utf8"))).includes("popover")) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* unreadable settings are not proof of anything */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Deliberately no fallback. This used to answer "does ~/.popover/daemon.log exist?",
|
|
193
|
+
// which was a fair proxy when the installer registered the plugin and started the daemon
|
|
194
|
+
// in one step. Under npm the two are separate — `popover daemon start` works fine without
|
|
195
|
+
// `popover setup` — so that fallback reported the plugin as present in exactly the
|
|
196
|
+
// situation this check exists to catch.
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* This package's own version, read from the manifest npm installed beside us.
|
|
4
|
+
*
|
|
5
|
+
* `import.meta.url` is `<pkg>/dist/index.js` under every layout — global install, dev
|
|
6
|
+
* checkout, `npm link` — so `../package.json` is always this package's. Reading it beats a
|
|
7
|
+
* hardcoded constant that silently drifts from what was published.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VERSION: string;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAyBA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
|