@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
package/dist/setup.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { daemonEntryPointerPath, popoverHome } from "@popoverinstall/shared";
|
|
7
|
+
import { resolveDaemonEntry } from "./daemon-entry.js";
|
|
8
|
+
import { MIN_CLAUDE_VERSION, claudeVersion, versionAtLeast } from "./doctor.js";
|
|
9
|
+
import { bad, dim, info, ok } from "./ui.js";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
/**
|
|
13
|
+
* Connect this npm install to Claude Code.
|
|
14
|
+
*
|
|
15
|
+
* `npm install -g` can place files, but it cannot register a Claude Code plugin or tell the
|
|
16
|
+
* plugin's hooks where the daemon ended up. Those are the two things this does, and they
|
|
17
|
+
* are why installing is two commands rather than one. A `postinstall` script would hide it,
|
|
18
|
+
* but `--ignore-scripts` is common, corporate registries strip scripts, and writing to
|
|
19
|
+
* `~/.claude` from an install hook is not something a package should do unannounced.
|
|
20
|
+
*
|
|
21
|
+
* Safe to re-run: every step is idempotent, and `update` re-runs it on every upgrade.
|
|
22
|
+
*/
|
|
23
|
+
export async function setup({ force = false } = {}) {
|
|
24
|
+
const version = await claudeVersion();
|
|
25
|
+
if (!version) {
|
|
26
|
+
bad("claude CLI not found on PATH.");
|
|
27
|
+
dim("Install Claude Code first, or set POPOVER_CLAUDE_BIN to point at it.");
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
if (!versionAtLeast(version, MIN_CLAUDE_VERSION)) {
|
|
31
|
+
bad(`claude ${version} is too old (need ${MIN_CLAUDE_VERSION.join(".")}+).`);
|
|
32
|
+
dim("Earlier versions lack `claude agents --json` and the fork flags popover relies on.");
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const marketplace = resolveMarketplaceRoot();
|
|
36
|
+
if (!marketplace) {
|
|
37
|
+
bad("Could not find the bundled Claude Code plugin.");
|
|
38
|
+
dim("This install looks incomplete — reinstall with: npm install -g @popoverinstall/cli");
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (!force && (await pluginRegistered())) {
|
|
42
|
+
ok("Claude Code plugin already registered.");
|
|
43
|
+
return writePointer();
|
|
44
|
+
}
|
|
45
|
+
// Remove before add so a re-run replaces a stale registration rather than failing on a
|
|
46
|
+
// name that already exists. Both are best-effort: a first run has nothing to remove.
|
|
47
|
+
info("Registering the Claude Code plugin…");
|
|
48
|
+
await claude(["plugin", "marketplace", "remove", "popover"]).catch(() => { });
|
|
49
|
+
try {
|
|
50
|
+
await claude(["plugin", "marketplace", "add", marketplace]);
|
|
51
|
+
await claude(["plugin", "install", "popover@popoverinstall", "--scope", "user", "-y"]);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
bad(`Could not register the plugin: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
ok("Claude Code plugin registered.");
|
|
58
|
+
return writePointer();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Record where the daemon lives, for the plugin's SessionStart hook.
|
|
62
|
+
*
|
|
63
|
+
* Claude Code installs a plugin by *copying* it into its own cache, so nothing the hook can
|
|
64
|
+
* compute from its own location points back at the npm tree. This file is the only link
|
|
65
|
+
* between the two, and a missing one means sessions silently never publish — so failing to
|
|
66
|
+
* write it is a hard failure, not a warning.
|
|
67
|
+
*/
|
|
68
|
+
function writePointer() {
|
|
69
|
+
const entry = resolveDaemonEntry();
|
|
70
|
+
if (!entry) {
|
|
71
|
+
bad("Could not find the daemon build.");
|
|
72
|
+
dim("Reinstall with: npm install -g @popoverinstall/cli");
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
mkdirSync(popoverHome(), { recursive: true });
|
|
76
|
+
writeFileSync(daemonEntryPointerPath(), `${entry}\n`);
|
|
77
|
+
ok(`daemon → ${entry}`);
|
|
78
|
+
dim("Run `popover login` to connect this machine, then `/team` inside Claude Code.");
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The directory holding `.claude-plugin/marketplace.json`.
|
|
83
|
+
*
|
|
84
|
+
* Published, that is this package's own root: `scripts/stage-plugin.mjs` copies the plugin
|
|
85
|
+
* to `<pkg>/plugin` and writes the manifest beside it at pack time. In a dev checkout
|
|
86
|
+
* neither exists, so fall back to the repo root, whose manifest sources `./packages/plugin`
|
|
87
|
+
* directly.
|
|
88
|
+
*/
|
|
89
|
+
function resolveMarketplaceRoot() {
|
|
90
|
+
const candidates = [
|
|
91
|
+
path.join(HERE, ".."), // <pkg>/ — published layout
|
|
92
|
+
path.join(HERE, "..", "..", ".."), // repo root — dev checkout
|
|
93
|
+
];
|
|
94
|
+
return candidates.find((d) => existsSync(path.join(d, ".claude-plugin", "marketplace.json"))) ?? null;
|
|
95
|
+
}
|
|
96
|
+
async function pluginRegistered() {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await claude(["plugin", "list"]);
|
|
99
|
+
return /\bpopover\b/.test(stdout);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function claude(args) {
|
|
106
|
+
const bin = process.env.POPOVER_CLAUDE_BIN ?? "claude";
|
|
107
|
+
return execFileAsync(bin, args, {
|
|
108
|
+
timeout: 60_000,
|
|
109
|
+
windowsHide: true,
|
|
110
|
+
shell: process.platform === "win32",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const c: {
|
|
2
|
+
green: (s: string) => string;
|
|
3
|
+
red: (s: string) => string;
|
|
4
|
+
yellow: (s: string) => string;
|
|
5
|
+
dim: (s: string) => string;
|
|
6
|
+
bold: (s: string) => string;
|
|
7
|
+
cyan: (s: string) => string;
|
|
8
|
+
};
|
|
9
|
+
export declare const ok: (s: string) => void;
|
|
10
|
+
export declare const bad: (s: string) => void;
|
|
11
|
+
export declare const warn: (s: string) => void;
|
|
12
|
+
export declare const info: (s: string) => void;
|
|
13
|
+
export declare const dim: (s: string) => void;
|
|
14
|
+
/** Open a URL in the user's browser. Best effort — we always print it too. */
|
|
15
|
+
export declare function openBrowser(url: string): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,CAAC;eAFqB,MAAM;aAAN,MAAM;gBAAN,MAAM;aAAN,MAAM;cAAN,MAAM;cAAN,MAAM;CASxC,CAAC;AAEF,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,SAAwC,CAAC;AACrE,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,SAAsC,CAAC;AACpE,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,SAAyC,CAAC;AACxE,eAAO,MAAM,IAAI,GAAI,GAAG,MAAM,SAA0B,CAAC;AACzD,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,SAAiC,CAAC;AAE/D,8EAA8E;AAC9E,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB5D"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
2
|
+
const wrap = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
3
|
+
export const c = {
|
|
4
|
+
green: wrap("32"),
|
|
5
|
+
red: wrap("31"),
|
|
6
|
+
yellow: wrap("33"),
|
|
7
|
+
dim: wrap("2"),
|
|
8
|
+
bold: wrap("1"),
|
|
9
|
+
cyan: wrap("36"),
|
|
10
|
+
};
|
|
11
|
+
export const ok = (s) => console.log(`${c.green("✓")} ${s}`);
|
|
12
|
+
export const bad = (s) => console.log(`${c.red("✗")} ${s}`);
|
|
13
|
+
export const warn = (s) => console.log(`${c.yellow("!")} ${s}`);
|
|
14
|
+
export const info = (s) => console.log(` ${s}`);
|
|
15
|
+
export const dim = (s) => console.log(c.dim(` ${s}`));
|
|
16
|
+
/** Open a URL in the user's browser. Best effort — we always print it too. */
|
|
17
|
+
export async function openBrowser(url) {
|
|
18
|
+
const { spawn } = await import("node:child_process");
|
|
19
|
+
const [command, args] = process.platform === "win32"
|
|
20
|
+
? // `start` is a cmd builtin; the empty string is the window title, which cmd
|
|
21
|
+
// otherwise steals from a quoted URL.
|
|
22
|
+
["cmd.exe", ["/c", "start", "", url]]
|
|
23
|
+
: process.platform === "darwin"
|
|
24
|
+
? ["open", [url]]
|
|
25
|
+
: ["xdg-open", [url]];
|
|
26
|
+
try {
|
|
27
|
+
const child = spawn(command, [...args], {
|
|
28
|
+
detached: true,
|
|
29
|
+
stdio: "ignore",
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
});
|
|
32
|
+
child.on("error", () => { });
|
|
33
|
+
child.unref();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* the printed URL is the fallback */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE/D,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE1F,MAAM,CAAC,MAAM,CAAC,GAAG;IACf,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;IACjB,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC;IACf,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC;IAClB,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;IACd,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;IACf,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACzD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/D,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACrD,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GACnB,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC1B,CAAC,CAAC,4EAA4E;YAC5E,sCAAsC;YACrC,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAW;QAClD,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ;YAC7B,CAAC,CAAE,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAW;YAC5B,CAAC,CAAE,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAW,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;YACtC,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5B,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@popoverinstall/cli",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "See your teammates' active Claude Code agents and ask them questions.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"agents",
|
|
9
|
+
"team",
|
|
10
|
+
"collaboration",
|
|
11
|
+
"cli"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://github.com/many-ideations/popover",
|
|
16
|
+
"bugs": "https://github.com/many-ideations/popover/issues",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/many-ideations/popover.git",
|
|
20
|
+
"directory": "packages/cli"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"bin": {
|
|
30
|
+
"popover": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"plugin",
|
|
35
|
+
".claude-plugin",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -b",
|
|
40
|
+
"prepack": "node ../../scripts/stage-plugin.mjs && tsc -b"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@popoverinstall/daemon": "0.4.0",
|
|
44
|
+
"@popoverinstall/shared": "0.4.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
|
+
"name": "popover",
|
|
4
|
+
"displayName": "Popover",
|
|
5
|
+
"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.",
|
|
6
|
+
"version": "0.4.0",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Quolabs"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/many-ideations/popover",
|
|
11
|
+
"repository": "https://github.com/many-ideations/popover",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"team",
|
|
15
|
+
"collaboration",
|
|
16
|
+
"agents",
|
|
17
|
+
"mcp"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/plugin/.mcp.json
ADDED
package/plugin/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# popover plugin
|
|
2
|
+
|
|
3
|
+
Installed into Claude Code; see the repo root README for the whole system.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
.claude-plugin/plugin.json metadata only
|
|
7
|
+
commands/team.md the /team command
|
|
8
|
+
hooks/hooks.json status feed (auto-discovered)
|
|
9
|
+
.mcp.json bundled MCP server (auto-discovered)
|
|
10
|
+
mcp/index.mjs team_list + team_ask, zero dependencies
|
|
11
|
+
scripts/ hook handlers, zero dependencies
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Three things that will bite you
|
|
15
|
+
|
|
16
|
+
**Do not declare `hooks` or `mcpServers` in `plugin.json`.** Both `hooks/hooks.json` and
|
|
17
|
+
`.mcp.json` at the plugin root are auto-discovered. Naming them in the manifest loads them
|
|
18
|
+
twice and the plugin refuses to start with `Duplicate hooks file detected`. This is why no
|
|
19
|
+
first-party plugin declares them either. `claude plugin validate --strict` does **not**
|
|
20
|
+
catch it — only a real install does.
|
|
21
|
+
|
|
22
|
+
**Quote any frontmatter value starting with `[`.** `argument-hint: [a] [b]` is invalid
|
|
23
|
+
YAML, and the failure is silent: every frontmatter field is dropped, including
|
|
24
|
+
`allowed-tools`. `claude plugin validate` does catch this one.
|
|
25
|
+
|
|
26
|
+
**Installing copies the plugin into `~/.claude/plugins/cache/<mkt>/<plugin>/<version>/`.**
|
|
27
|
+
It runs from there, severed from the tree it was built in, so *every path relative to a
|
|
28
|
+
script in this directory resolves inside that cache directory*. This is why
|
|
29
|
+
`resolveDaemonEntry()` in `scripts/ensure-daemon.mjs` tries `~/.popover/runtime` first: the
|
|
30
|
+
relative candidates below it only ever match in a monorepo checkout, and on a real install
|
|
31
|
+
they all miss. A daemon reachable only by relative path is a daemon that never starts, and
|
|
32
|
+
the symptom is remote — `/popover:team` reporting an empty roster, with nothing in any log.
|
|
33
|
+
It also means edits here do nothing until you reinstall.
|
|
34
|
+
|
|
35
|
+
## Why the scripts have no dependencies
|
|
36
|
+
|
|
37
|
+
The plugin is installed from a marketplace into a cache directory where dependency
|
|
38
|
+
installation is conditional and can fail. A hook that cannot resolve an import would break
|
|
39
|
+
every tool call in the session, and an MCP server that cannot start would take `/team`
|
|
40
|
+
with it. So `scripts/*.mjs` and `mcp/index.mjs` import nothing outside Node's stdlib and
|
|
41
|
+
duplicate ~15 lines of `@popoverinstall/shared` on purpose.
|
|
42
|
+
|
|
43
|
+
## Local development
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run build # from the repo root
|
|
47
|
+
claude plugin marketplace add "$(pwd)" # absolute; a bare `.` is rejected
|
|
48
|
+
claude plugin install popover@popoverinstall -y
|
|
49
|
+
claude plugin uninstall popover # to undo
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Teammates do not do this — they run the installer (`curl -fsSL <host>/install | sh`),
|
|
53
|
+
which is the only path that also delivers the daemon. See the repo root README.
|
|
54
|
+
|
|
55
|
+
The hook scripts *talk to* the daemon over `POPOVER_DAEMON_ADDR`, falling back to
|
|
56
|
+
`\\.\pipe\popover-<user>` on Windows and `~/.popover/daemon.sock` elsewhere. They *launch* it
|
|
57
|
+
from `POPOVER_DAEMON_ENTRY` or `~/.popover/runtime/daemon/dist/index.js` — see the third
|
|
58
|
+
gotcha above.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: See your team's active Claude Code agents in this repo and ask one of them a question
|
|
3
|
+
# Quoted because an unquoted value starting with `[` is parsed as a YAML flow sequence,
|
|
4
|
+
# which fails and silently drops EVERY field here, allowed-tools included.
|
|
5
|
+
argument-hint: "[agent-handle] [question]"
|
|
6
|
+
allowed-tools: mcp__plugin_popover_popover__team_list, mcp__plugin_popover_popover__team_ask, AskUserQuestion
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Team agents
|
|
10
|
+
|
|
11
|
+
The user ran `/popover:team`. Arguments, which may be empty: **$ARGUMENTS**
|
|
12
|
+
|
|
13
|
+
Only agents working in **this repo** are visible. A teammate running Claude Code in a
|
|
14
|
+
different repository is neither listed nor askable, so an empty roster means nobody else is
|
|
15
|
+
working *here* — not that the team is idle. Say it that way if it comes up.
|
|
16
|
+
|
|
17
|
+
Follow exactly one of these three paths.
|
|
18
|
+
|
|
19
|
+
## 1. No arguments — show the roster
|
|
20
|
+
|
|
21
|
+
Call `mcp__plugin_popover_popover__team_list`, then print what it returns **verbatim** in a
|
|
22
|
+
fenced block. Do not reformat it, re-sort it, or add agents that are not in it.
|
|
23
|
+
|
|
24
|
+
Then add one short line telling the user they can ask any of these agents a question, e.g.
|
|
25
|
+
`/popover:team B1 why did you rule out redis?`
|
|
26
|
+
|
|
27
|
+
Stop there. Do not ask a question on the user's behalf.
|
|
28
|
+
|
|
29
|
+
## 2. A handle and a question — ask it
|
|
30
|
+
|
|
31
|
+
If the arguments name an agent (a handle like `B1`, a person's name, or a repo) followed
|
|
32
|
+
by a question, call `mcp__plugin_popover_popover__team_ask` with:
|
|
33
|
+
|
|
34
|
+
- `target`: the handle or name they used, exactly as typed
|
|
35
|
+
- `question`: the rest of their message, as a self-contained question — the answering
|
|
36
|
+
agent has none of this conversation's context, so expand pronouns and vague references
|
|
37
|
+
("that bug" → "the token refresh bug in the auth service")
|
|
38
|
+
|
|
39
|
+
When the answer comes back, show it clearly attributed:
|
|
40
|
+
|
|
41
|
+
> **B1** (Bob Chen, api-server): …answer…
|
|
42
|
+
|
|
43
|
+
Then say how it bears on what you are working on, if it does. If the tool returns an
|
|
44
|
+
error, relay it plainly — do not retry a different agent unless the user asks.
|
|
45
|
+
|
|
46
|
+
## 3. A question with no clear target — pick one
|
|
47
|
+
|
|
48
|
+
Call `mcp__plugin_popover_popover__team_list` first. Then:
|
|
49
|
+
|
|
50
|
+
- If exactly one agent plausibly fits, ask it and say which one you chose and why.
|
|
51
|
+
- If several fit, call `AskUserQuestion` with up to 4 of them as options (label = the
|
|
52
|
+
handle, description = owner, repo, and what it is doing). Then ask the chosen one.
|
|
53
|
+
- If none fit, say so and show the roster instead.
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
- Never invent a handle. Only use ones `team_list` returned. A handle from earlier in this
|
|
58
|
+
conversation may since have gone out of scope; if `team_ask` says it does not match,
|
|
59
|
+
relay that rather than guessing at another agent.
|
|
60
|
+
- Asking costs the *teammate* money and runs on *their* machine. One question per
|
|
61
|
+
request; do not fan out to several agents unless explicitly asked.
|
|
62
|
+
- The answer comes from a read-only copy of their session. Their live session is not
|
|
63
|
+
interrupted and cannot be modified by this.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Publishes this machine's Claude Code session activity to the popover daemon.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node",
|
|
10
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/ensure-daemon.mjs"],
|
|
11
|
+
"async": true,
|
|
12
|
+
"timeout": 30
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"UserPromptSubmit": [
|
|
18
|
+
{
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node",
|
|
23
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
24
|
+
"async": true,
|
|
25
|
+
"timeout": 15
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PreToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "*",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "node",
|
|
37
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
38
|
+
"async": true,
|
|
39
|
+
"timeout": 15
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"PostToolUse": [
|
|
45
|
+
{
|
|
46
|
+
"matcher": "*",
|
|
47
|
+
"hooks": [
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "node",
|
|
51
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
52
|
+
"async": true,
|
|
53
|
+
"timeout": 15
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"Notification": [
|
|
59
|
+
{
|
|
60
|
+
"matcher": "agent_needs_input",
|
|
61
|
+
"hooks": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
66
|
+
"async": true,
|
|
67
|
+
"timeout": 15
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"Stop": [
|
|
73
|
+
{
|
|
74
|
+
"hooks": [
|
|
75
|
+
{
|
|
76
|
+
"type": "command",
|
|
77
|
+
"command": "node",
|
|
78
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
79
|
+
"async": true,
|
|
80
|
+
"timeout": 15
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"SessionEnd": [
|
|
86
|
+
{
|
|
87
|
+
"hooks": [
|
|
88
|
+
{
|
|
89
|
+
"type": "command",
|
|
90
|
+
"command": "node",
|
|
91
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
|
|
92
|
+
"async": true,
|
|
93
|
+
"timeout": 5
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}
|