@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/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
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "popover": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/mcp/index.mjs"],
6
+ "timeout": 120000
7
+ }
8
+ }
9
+ }
@@ -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
+ }