@popoverinstall/cli 0.4.1 → 0.6.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/dist/aardvark.d.ts +30 -0
  3. package/dist/aardvark.d.ts.map +1 -0
  4. package/dist/aardvark.js +97 -0
  5. package/dist/aardvark.js.map +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +140 -52
  8. package/dist/index.js.map +1 -1
  9. package/dist/login.d.ts +8 -1
  10. package/dist/login.d.ts.map +1 -1
  11. package/dist/login.js +51 -23
  12. package/dist/login.js.map +1 -1
  13. package/dist/next-steps.d.ts +18 -0
  14. package/dist/next-steps.d.ts.map +1 -0
  15. package/dist/next-steps.js +73 -0
  16. package/dist/next-steps.js.map +1 -0
  17. package/dist/scene.d.ts +43 -0
  18. package/dist/scene.d.ts.map +1 -0
  19. package/dist/scene.js +276 -0
  20. package/dist/scene.js.map +1 -0
  21. package/dist/snapshot.d.ts +2 -0
  22. package/dist/snapshot.d.ts.map +1 -0
  23. package/dist/snapshot.js +828 -0
  24. package/dist/snapshot.js.map +1 -0
  25. package/dist/terminal.d.ts +58 -0
  26. package/dist/terminal.d.ts.map +1 -0
  27. package/dist/terminal.js +139 -0
  28. package/dist/terminal.js.map +1 -0
  29. package/dist/theme.d.ts +123 -0
  30. package/dist/theme.d.ts.map +1 -0
  31. package/dist/theme.js +257 -0
  32. package/dist/theme.js.map +1 -0
  33. package/dist/welcome.d.ts +23 -0
  34. package/dist/welcome.d.ts.map +1 -0
  35. package/dist/welcome.js +128 -0
  36. package/dist/welcome.js.map +1 -0
  37. package/package.json +5 -4
  38. package/plugin/.claude-plugin/plugin.json +1 -1
  39. package/plugin/README.md +11 -1
  40. package/plugin/commands/fork.md +118 -0
  41. package/plugin/commands/team.md +44 -20
  42. package/plugin/hooks/hooks.json +6 -0
  43. package/plugin/mcp/index.mjs +69 -0
  44. package/plugin/scripts/deliver-messages.mjs +76 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The first screen anyone sees: what the installer hands off to the moment it finishes.
3
+ *
4
+ * The aardvark, a box saying what this is, and a single question — log in? — that Enter
5
+ * answers. Everything past that point is the existing device flow in `login.ts`; this
6
+ * module owns the framing and the one keypress, not the protocol.
7
+ *
8
+ * Two things it must never do:
9
+ *
10
+ * 1. **Block a non-interactive install.** The installer only hands off when it has a
11
+ * terminal, but `popover welcome` is a real command and can be piped or run in CI. With no
12
+ * TTY it prints the same next steps the installer used to print and exits 0.
13
+ * 2. **Fail the install.** A machine with popover on it and no login is a fine outcome — the
14
+ * user runs `popover login` later. Every exit here is 0 unless the login itself was
15
+ * attempted and failed.
16
+ */
17
+ /** Re-exported from where they now live, alongside the send-off that uses them. */
18
+ export { BRAND, TAGLINE, TAGLINE_SECONDARY } from "./next-steps.js";
19
+ export interface WelcomeOptions {
20
+ apiUrl: string;
21
+ }
22
+ export declare function welcome({ apiUrl }: WelcomeOptions): Promise<number>;
23
+ //# sourceMappingURL=welcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../src/welcome.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,mFAAmF;AACnF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDzE"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The first screen anyone sees: what the installer hands off to the moment it finishes.
3
+ *
4
+ * The aardvark, a box saying what this is, and a single question — log in? — that Enter
5
+ * answers. Everything past that point is the existing device flow in `login.ts`; this
6
+ * module owns the framing and the one keypress, not the protocol.
7
+ *
8
+ * Two things it must never do:
9
+ *
10
+ * 1. **Block a non-interactive install.** The installer only hands off when it has a
11
+ * terminal, but `popover welcome` is a real command and can be piped or run in CI. With no
12
+ * TTY it prints the same next steps the installer used to print and exits 0.
13
+ * 2. **Fail the install.** A machine with popover on it and no login is a fine outcome — the
14
+ * user runs `popover login` later. Every exit here is 0 unless the login itself was
15
+ * attempted and failed.
16
+ */
17
+ import { loadCredentials } from "@popoverinstall/shared";
18
+ import { aardvarkLines } from "./aardvark.js";
19
+ import { sceneLines } from "./scene.js";
20
+ import { login } from "./login.js";
21
+ import { BRAND, TAGLINE, TAGLINE_SECONDARY, nextSteps } from "./next-steps.js";
22
+ import { badge, box, glyph, line, step, style, trunk, wrap } from "./theme.js";
23
+ /** Re-exported from where they now live, alongside the send-off that uses them. */
24
+ export { BRAND, TAGLINE, TAGLINE_SECONDARY } from "./next-steps.js";
25
+ export async function welcome({ apiUrl }) {
26
+ const alreadyConnected = Boolean(loadCredentials());
27
+ console.log("");
28
+ // The full scene where there is room for it, the aardvark on its own where there is not.
29
+ for (const row of sceneLines() ?? aardvarkLines())
30
+ console.log(row);
31
+ // Wrapped before styling, and to the window rather than to a fixed number: the second
32
+ // line is long enough that an 80-column terminal is the tight case, not the roomy one.
33
+ const room = Math.max(28, (process.stdout.columns ?? 80) - 8);
34
+ box({
35
+ title: badge(`Welcome to ${BRAND}`),
36
+ lines: [
37
+ "",
38
+ ...wrap(TAGLINE, room).map(style.bold),
39
+ ...wrap(TAGLINE_SECONDARY, room).map(style.dim),
40
+ "",
41
+ ],
42
+ // Keeps the box at least as wide as the aardvark above it, so the two read as one block.
43
+ minWidth: 36,
44
+ });
45
+ if (alreadyConnected) {
46
+ trunk();
47
+ step("This machine is already connected.");
48
+ nextSteps();
49
+ return 0;
50
+ }
51
+ // No terminal to ask into: say what to run and get out of the way.
52
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
53
+ trunk();
54
+ step(`Log in to ${BRAND}:`);
55
+ line(style.bold("popover login"));
56
+ nextSteps();
57
+ return 0;
58
+ }
59
+ trunk();
60
+ const answer = await ask(`Log in to ${BRAND} ${style.dim("(opens browser)")}`);
61
+ trunk();
62
+ if (answer === "skip") {
63
+ step("Skipped. Run this when you are ready:");
64
+ line(style.bold("popover login"));
65
+ nextSteps();
66
+ return 0;
67
+ }
68
+ const code = await login(apiUrl, { framed: true });
69
+ if (code === 0)
70
+ nextSteps();
71
+ return code;
72
+ }
73
+ /**
74
+ * The one question in the flow.
75
+ *
76
+ * Enter is the answer — the prompt is phrased as the action rather than a yes/no, so a bare
77
+ * Enter means "do it". `s`, `n` and `q` skip, which covers "skip", "no" and "quit" without a
78
+ * menu. Ctrl-C, Escape and a closed stdin all resolve to a skip rather than throwing,
79
+ * because the installer is the parent process and a half-finished login is not a failure.
80
+ *
81
+ * Read raw rather than through readline, for two reasons. A single keypress is what "just
82
+ * hit enter" means — with readline, `s` would need an Enter after it. And readline owns the
83
+ * cursor on the line it prompted on: it repositions after the echoed newline, so the next
84
+ * thing printed lands on the prompt line instead of below it.
85
+ */
86
+ function ask(question) {
87
+ step(style.bold(question));
88
+ // The one instruction on screen, so it is the one thing not dimmed: bright white against a
89
+ // flow that is otherwise deliberately quiet.
90
+ const hint = `${style.white("press enter")}${style.dim(" to continue")}` +
91
+ `${style.dim(` ${glyph.dot} s to skip`)}`;
92
+ process.stdout.write(`${style.dim(glyph.trunk)} ${hint} `);
93
+ const stdin = process.stdin;
94
+ const wasRaw = stdin.isRaw;
95
+ return new Promise((resolve) => {
96
+ let settled = false;
97
+ const done = (result) => {
98
+ if (settled)
99
+ return;
100
+ settled = true;
101
+ stdin.removeListener("data", onData);
102
+ stdin.removeListener("end", onEnd);
103
+ stdin.setRawMode?.(wasRaw ?? false);
104
+ stdin.pause();
105
+ // Raw mode swallowed the echo of whatever ended this, so close the line ourselves.
106
+ console.log("");
107
+ resolve(result);
108
+ };
109
+ const onData = (chunk) => {
110
+ for (const key of chunk) {
111
+ if (key === "\r" || key === "\n")
112
+ return done("go");
113
+ // Ctrl-C, Ctrl-D and Escape: in raw mode these arrive as bytes, not as signals.
114
+ if (key === "\x03" || key === "\x04" || key === "\x1b")
115
+ return done("skip");
116
+ if (/[nsq]/i.test(key))
117
+ return done("skip");
118
+ }
119
+ };
120
+ const onEnd = () => done("skip");
121
+ stdin.setRawMode?.(true);
122
+ stdin.setEncoding("utf8");
123
+ stdin.resume();
124
+ stdin.on("data", onData);
125
+ stdin.on("end", onEnd);
126
+ });
127
+ }
128
+ //# sourceMappingURL=welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../src/welcome.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popoverinstall/cli",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "See your teammates' active Claude Code agents and ask them questions.",
5
5
  "keywords": [
6
6
  "claude",
@@ -33,14 +33,15 @@
33
33
  "dist",
34
34
  "plugin",
35
35
  ".claude-plugin",
36
- "README.md"
36
+ "README.md",
37
+ "CHANGELOG.md"
37
38
  ],
38
39
  "scripts": {
39
40
  "build": "tsc -b",
40
41
  "prepack": "node ../../scripts/stage-plugin.mjs && tsc -b"
41
42
  },
42
43
  "dependencies": {
43
- "@popoverinstall/daemon": "0.4.1",
44
- "@popoverinstall/shared": "0.4.1"
44
+ "@popoverinstall/daemon": "0.6.0",
45
+ "@popoverinstall/shared": "0.6.0"
45
46
  }
46
47
  }
@@ -3,7 +3,7 @@
3
3
  "name": "popover",
4
4
  "displayName": "Popover",
5
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.1",
6
+ "version": "0.6.0",
7
7
  "author": {
8
8
  "name": "Quolabs"
9
9
  },
package/plugin/README.md CHANGED
@@ -5,12 +5,22 @@ Installed into Claude Code; see the repo root README for the whole system.
5
5
  ```
6
6
  .claude-plugin/plugin.json metadata only
7
7
  commands/team.md the /team command
8
+ commands/fork.md the /fork command
8
9
  hooks/hooks.json status feed (auto-discovered)
9
10
  .mcp.json bundled MCP server (auto-discovered)
10
- mcp/index.mjs team_list + team_ask, zero dependencies
11
+ mcp/index.mjs team_list + team_ask + team_tell, zero dependencies
11
12
  scripts/ hook handlers, zero dependencies
12
13
  ```
13
14
 
15
+ `/team` reaches the daemon through the MCP server. `/fork` deliberately does not: it shells
16
+ out to the `popover` binary instead, because opening a fork has to work for someone who is
17
+ not signed in and may have no daemon running at all. That also means it adds nothing to the
18
+ IPC protocol, and cannot regress asks, tells, or the roster.
19
+
20
+ It is the one command that needs to know which session it is in, which it gets from
21
+ `${CLAUDE_SESSION_ID}` — substituted into the command body by Claude Code, so there is no
22
+ guessing at "the newest transcript in this directory".
23
+
14
24
  ## Three things that will bite you
15
25
 
16
26
  **Do not declare `hooks` or `mcpServers` in `plugin.json`.** Both `hooks/hooks.json` and
@@ -0,0 +1,118 @@
1
+ ---
2
+ description: Share this conversation as a fork a teammate can carry on, or open one you were sent
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: "[create|open] [code]"
6
+ allowed-tools: Bash(popover fork *), AskUserQuestion
7
+ ---
8
+
9
+ # Fork this conversation
10
+
11
+ The user ran `/popover:fork`. Arguments, which may be empty: **$ARGUMENTS**
12
+
13
+ This session's id is `${CLAUDE_SESSION_ID}`. You will need it for `create`.
14
+
15
+ A fork is not an ask. An ask sends a teammate's question to a read-only copy of someone's
16
+ session and brings back one answer. A fork hands over **the whole conversation**, frozen at
17
+ the moment it was made, and the person who opens it gets their own local chat that continues
18
+ from there. Nothing they do in it comes back here.
19
+
20
+ The first word of the arguments chooses what to do. Follow exactly one of these paths.
21
+
22
+ ## 1. `create` — freeze this conversation and get a code
23
+
24
+ Run:
25
+
26
+ ```
27
+ popover fork create --session ${CLAUDE_SESSION_ID} --json
28
+ ```
29
+
30
+ On `ok: true`, show the user the code on its own line so it is easy to copy, then tell them,
31
+ briefly and in this order:
32
+
33
+ - Who it is for and how they open it: `popover fork open <code>`, or
34
+ `npx @popoverinstall/cli fork open <code>` if they do not have popover yet.
35
+ - That the code stops working in 24 hours, but a chat someone has already opened does not —
36
+ it is theirs from then on.
37
+ - That it is frozen at `last_turn_at`, so nothing said after this point is included. If they
38
+ keep working and want the newer state shared, they run `create` again for a new code.
39
+ - That this sends the entire conversation, including the contents of every file read into
40
+ it. Say it plainly, once. It is a much larger disclosure than an ask and the user should
41
+ make it deliberately.
42
+
43
+ On `ok: false`, relay `message` and the `hints` verbatim. Do not retry with different flags.
44
+
45
+ ## 2. `open <code>` — take delivery of a fork
46
+
47
+ Run:
48
+
49
+ ```
50
+ popover fork open <the code they typed> --json
51
+ ```
52
+
53
+ On `ok: false`, relay `message` and `hints` and stop.
54
+
55
+ On `ok: true`, tell them what arrived: who shared it, the title if there is one,
56
+ `entry_count` messages, when it was frozen, and which repo it came from. Then surface
57
+ whatever the output flags:
58
+
59
+ - `repo_match: "different"` — say which repo it came from and which one they are in, and
60
+ that the conversation will refer to files they may not have.
61
+ - `repo_match: "unknown"` — they are not in a git repository, so paths in it may not resolve.
62
+ - `version_skew` — relay it as a note, not a blocker.
63
+ - `claude_problem` — this one *is* a blocker. Relay it and stop.
64
+
65
+ Then explain why this needs a window of its own, and offer the choice.
66
+
67
+ **A fork cannot open inside this chat.** A running Claude Code session cannot become a
68
+ different session — one chat holds one conversation, and this one already has its own. So
69
+ the fork starts as a second session in a new window. If this conversation has real context
70
+ in it already, say that plainly; if the user only just started here, one short line is
71
+ enough.
72
+
73
+ Then call `AskUserQuestion` with exactly these two options, so Esc cancels:
74
+
75
+ - **Open a new window** — "Start the fork in its own terminal window"
76
+ - **Print the command** — "Show the command and let me run it myself"
77
+
78
+ If they choose to open a new window, run:
79
+
80
+ ```
81
+ popover fork launch <stage_id> --json
82
+ ```
83
+
84
+ Then report what happened: `opened: true` means a window is on its way, and it is worth
85
+ adding that the fork may take a moment to appear. `opened: false` is not a failure — it
86
+ happens over SSH, in a container, and anywhere there is no window to open — so show them
87
+ `command` and the `cwd` to run it in.
88
+
89
+ If they choose to print the command, run the same thing with `--here` instead of no flag,
90
+ and show them the `command` it prints. `--here` writes the fork without trying to open
91
+ anything.
92
+
93
+ If they press Esc, stop. Nothing has been written to their machine yet, and there is nothing
94
+ to clean up.
95
+
96
+ ## 3. No arguments, or anything else
97
+
98
+ Say what the two halves do in two lines, and stop:
99
+
100
+ `/popover:fork create` · `/popover:fork open PQRS-2345-BCDF-7892`
101
+
102
+ Do not create a fork on the user's behalf just because they ran the command with no
103
+ arguments. Sharing a conversation is a disclosure, and it should always be something they
104
+ asked for in as many words.
105
+
106
+ ## Rules
107
+
108
+ - Never invent or guess at a code. If what they typed is rejected, relay that and let them
109
+ paste it again — a code is 16 characters in four groups and is easy to truncate.
110
+ - Never print a code the user has not seen; it is the entire credential and the decryption
111
+ key for the conversation behind it.
112
+ - A fork is one-way. If the user seems to want an answer rather than to hand over the whole
113
+ conversation, say that `/popover:team ask` is the cheaper thing and offer it instead.
114
+ - Do not run `create` and then immediately `open` the result to "test" it. Every open counts
115
+ against what the sharer sees, and the fork is already verified by the command exiting
116
+ cleanly.
117
+ - `popover fork revoke <code>` destroys a snapshot early. It cannot recall a copy someone
118
+ has already opened; if the user is worried about that, say so rather than reassuring them.
@@ -1,9 +1,9 @@
1
1
  ---
2
- description: See your team's active Claude Code agents in this repo and ask one of them a question
2
+ description: See your team's active Claude Code agents in this repo, ask one a question, or tell one something
3
3
  # Quoted because an unquoted value starting with `[` is parsed as a YAML flow sequence,
4
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
5
+ argument-hint: "[ask|tell] [agent-handle] [question or message]"
6
+ allowed-tools: mcp__plugin_popover_popover__team_list, mcp__plugin_popover_popover__team_ask, mcp__plugin_popover_popover__team_tell, AskUserQuestion
7
7
  ---
8
8
 
9
9
  # Team agents
@@ -11,25 +11,24 @@ allowed-tools: mcp__plugin_popover_popover__team_list, mcp__plugin_popover_popov
11
11
  The user ran `/popover:team`. Arguments, which may be empty: **$ARGUMENTS**
12
12
 
13
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
14
+ different repository is neither listed nor reachable, so an empty roster means nobody else is
15
15
  working *here* — not that the team is idle. Say it that way if it comes up.
16
16
 
17
- Follow exactly one of these three paths.
17
+ The first word of the arguments chooses what to do. Follow exactly one of these paths.
18
18
 
19
19
  ## 1. No arguments — show the roster
20
20
 
21
21
  Call `mcp__plugin_popover_popover__team_list`, then print what it returns **verbatim** in a
22
22
  fenced block. Do not reformat it, re-sort it, or add agents that are not in it.
23
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?`
24
+ Then add one short line showing both forms:
25
+ `/popover:team ask B1 why did you rule out redis?` · `/popover:team tell B1 the migration is applied`
26
26
 
27
- Stop there. Do not ask a question on the user's behalf.
27
+ Stop there. Do not ask or tell anything on the user's behalf.
28
28
 
29
- ## 2. A handle and a question — ask it
29
+ ## 2. `ask <handle> <question>` — ask it and wait
30
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:
31
+ Call `mcp__plugin_popover_popover__team_ask` with:
33
32
 
34
33
  - `target`: the handle or name they used, exactly as typed
35
34
  - `question`: the rest of their message, as a self-contained question — the answering
@@ -43,21 +42,46 @@ When the answer comes back, show it clearly attributed:
43
42
  Then say how it bears on what you are working on, if it does. If the tool returns an
44
43
  error, relay it plainly — do not retry a different agent unless the user asks.
45
44
 
46
- ## 3. A question with no clear target pick one
45
+ ## 3. `tell <handle> <message>` send it, expect nothing back
46
+
47
+ Call `mcp__plugin_popover_popover__team_tell` with:
48
+
49
+ - `target`: the handle or name they used, exactly as typed
50
+ - `message`: the rest of their message, made self-contained the same way — it arrives with
51
+ no context of its own, so name the thing rather than referring to it
52
+
53
+ There is no answer. Confirm what was sent and to whom, in one line, and stop:
54
+
55
+ > Told **B1** (Bob Chen): the repo_keys migration is applied on prod.
56
+
57
+ A tell is for facts about shared state that would otherwise cause a collision. If the user
58
+ is trying to give another agent instructions or hand it work, say that a tell does not do
59
+ that — the receiving agent is told to treat it as information rather than a directive — and
60
+ offer to ask instead.
61
+
62
+ ## 4. A question or message with no clear target — pick one
47
63
 
48
64
  Call `mcp__plugin_popover_popover__team_list` first. Then:
49
65
 
50
- - If exactly one agent plausibly fits, ask it and say which one you chose and why.
66
+ - If exactly one agent plausibly fits, use it and say which one you chose and why.
51
67
  - 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.
68
+ handle, description = owner, repo, and what it is doing). Then use the chosen one.
53
69
  - If none fit, say so and show the roster instead.
54
70
 
71
+ If it is unclear whether the user meant to ask or to tell, prefer **ask** — it interrupts
72
+ nobody and costs only the answering machine some tokens, whereas a tell puts text into a
73
+ colleague's context and notifies them.
74
+
55
75
  ## Rules
56
76
 
57
77
  - 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.
78
+ conversation may since have gone out of scope; if the tool says it does not match, relay
79
+ that rather than guessing at another agent.
80
+ - Asking costs the *teammate* money and runs on *their* machine. Telling costs their
81
+ attention and notifies them. One per request either way; do not fan out to several agents
82
+ unless explicitly asked.
83
+ - An answer comes from a read-only copy of their session. Their live session is not
84
+ interrupted and cannot be modified by an ask.
85
+ - A tell **is** delivered into their live session, before its next prompt. That is the one
86
+ thing in popover that reaches a running agent, so treat it as something the user should have
87
+ meant to do.
@@ -23,6 +23,12 @@
23
23
  "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/emit-event.mjs"],
24
24
  "async": true,
25
25
  "timeout": 15
26
+ },
27
+ {
28
+ "type": "command",
29
+ "command": "node",
30
+ "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/deliver-messages.mjs"],
31
+ "timeout": 5
26
32
  }
27
33
  ]
28
34
  }
@@ -23,6 +23,7 @@ const SERVER_INFO = {
23
23
  ).version,
24
24
  };
25
25
 
26
+
26
27
  const TOOLS = [
27
28
  {
28
29
  name: "team_list",
@@ -73,6 +74,40 @@ const TOOLS = [
73
74
  },
74
75
  annotations: { readOnlyHint: true, openWorldHint: true, title: "Ask a teammate's agent" },
75
76
  },
77
+ {
78
+ name: "team_tell",
79
+ description:
80
+ "Send a short heads-up to a teammate's agent working in this repo. Unlike team_ask " +
81
+ "there is no answer: the message is delivered into that agent's context before its " +
82
+ "next prompt, and nothing comes back. Use it for facts about shared state that would " +
83
+ "otherwise cause a collision — \"the migration is applied on prod\", \"I am rewriting " +
84
+ "globals.css, leave it alone\". Do not use it to give another agent instructions or " +
85
+ "assign it work: the receiving agent is told to treat your message as information " +
86
+ "from outside its conversation, not as a directive, and its user is notified that you " +
87
+ "sent it. Only agents in the same repository can be told, and you are limited to 10 " +
88
+ "messages an hour to any one agent.",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ target: {
93
+ type: "string",
94
+ description:
95
+ "Which agent to tell: a handle from team_list such as 'B1', or a teammate's " +
96
+ "name, or a repo name.",
97
+ },
98
+ message: {
99
+ type: "string",
100
+ description:
101
+ "The heads-up, in one or two sentences. It arrives with no context of its own, " +
102
+ "so name the thing you are talking about rather than referring to it.",
103
+ },
104
+ },
105
+ required: ["target", "message"],
106
+ additionalProperties: false,
107
+ },
108
+ // Not read-only: this writes into someone else's context and notifies a person.
109
+ annotations: { readOnlyHint: false, openWorldHint: true, title: "Tell a teammate's agent" },
110
+ },
76
111
  ];
77
112
 
78
113
  // ---------------------------------------------------------------------------
@@ -157,6 +192,39 @@ async function callTeamAsk(args) {
157
192
  return text(`Answer from ${attribution}:\n\n${answer.answer}${cost}`);
158
193
  }
159
194
 
195
+ async function callTeamTell(args) {
196
+ const target = typeof args?.target === "string" ? args.target.trim() : "";
197
+ const message = typeof args?.message === "string" ? args.message.trim() : "";
198
+
199
+ if (!target) return errorText("`target` is required — call team_list to see the options.");
200
+ if (!message) return errorText("`message` is required.");
201
+
202
+ const reply = await request(
203
+ {
204
+ t: "tell",
205
+ id: rpcId(),
206
+ req: {
207
+ target,
208
+ message,
209
+ ...(callerSessionId() ? { fromSessionId: callerSessionId() } : {}),
210
+ },
211
+ cwd: process.cwd(),
212
+ },
213
+ { timeoutMs: 15000 },
214
+ );
215
+
216
+ if (!reply) return errorText(daemonDownMessage());
217
+ if (reply.t === "error") return errorText(explain(reply));
218
+ if (reply.t !== "tell.ok") return errorText("The popover daemon returned an unexpected response.");
219
+
220
+ // Says delivered-to, never read-by: a sender is deliberately told nothing about what
221
+ // happened to their message afterwards.
222
+ return text(
223
+ `Sent to ${reply.handle} (${reply.ownerName}). It will reach that agent before its next ` +
224
+ `prompt; there is no reply.`,
225
+ );
226
+ }
227
+
160
228
  function explain(errorReply) {
161
229
  switch (errorReply.code) {
162
230
  // The daemon knows whether credentials are missing or merely unusable; relaying its
@@ -247,6 +315,7 @@ async function handle(message) {
247
315
  try {
248
316
  if (name === "team_list") return reply(id, await callTeamList());
249
317
  if (name === "team_ask") return reply(id, await callTeamAsk(params?.arguments ?? {}));
318
+ if (name === "team_tell") return reply(id, await callTeamTell(params?.arguments ?? {}));
250
319
  return replyError(id, -32602, `Unknown tool: ${name}`);
251
320
  } catch (err) {
252
321
  // A thrown handler must not kill the server; report it as a failed tool call.
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // Delivers tells into a live session.
3
+ //
4
+ // This is the one hook in the plugin that writes to stdout on purpose. Every other one is
5
+ // registered `async: true` and forbidden from printing, because for UserPromptSubmit stdout
6
+ // is injected into the model's context — see the header of emit-event.mjs. That is precisely
7
+ // the channel a tell needs, so this hook is synchronous and its output is the message.
8
+ //
9
+ // Which makes it the riskiest script here, and it is written accordingly:
10
+ //
11
+ // - It runs before every prompt the user submits, so it must be fast and must fail open.
12
+ // A missed heads-up is survivable; a prompt that hangs waiting on a dead daemon is not.
13
+ // - It prints only what the daemon hands back. The envelope is rendered on this machine
14
+ // from columns the database filled in, so a sender can neither forge the attribution nor
15
+ // strip the framing off their own message.
16
+ // - It stays silent inside a fork. Forks run the plugin's hooks like any other session, and
17
+ // a fork answering a teammate's question must never be handed a third party's text — its
18
+ // output goes back to whoever asked.
19
+
20
+ import { callerSessionId, request } from "./_ipc.mjs";
21
+
22
+ // The fork runner sets this when it spawns a read-only answer session.
23
+ if (process.env.CLAUDE_CODE_ENTRYPOINT === "popover-fork") process.exit(0);
24
+
25
+ try {
26
+ // The payload carries session_id; the environment is the fallback, and one of the two is
27
+ // always present in a real session. Without a session there is no inbox to drain.
28
+ let sessionId = callerSessionId();
29
+ try {
30
+ const raw = await readStdinQuickly();
31
+ if (raw) sessionId = JSON.parse(raw)?.session_id || sessionId;
32
+ } catch {
33
+ // A malformed payload is not a reason to skip delivery; the env var still identifies us.
34
+ }
35
+
36
+ if (sessionId) {
37
+ const reply = await request(
38
+ { t: "pending", id: "hook", fromSessionId: sessionId, limit: 3 },
39
+ { timeoutMs: 300 },
40
+ );
41
+
42
+ if (reply?.t === "pending.ok" && reply.rendered) {
43
+ // The whole point of the script. Everything above exists to make this line safe.
44
+ process.stdout.write(`${reply.rendered}\n`);
45
+ }
46
+ }
47
+ } catch {
48
+ // Fail open, always. Whatever went wrong, the user's prompt must still go through.
49
+ }
50
+
51
+ process.exit(0);
52
+
53
+ /**
54
+ * Read the hook payload, but never wait long for it.
55
+ *
56
+ * `readStdin` in _ipc.mjs allows two seconds, which is right for a fire-and-forget reporter
57
+ * and far too long for something sitting between a keystroke and the model.
58
+ */
59
+ function readStdinQuickly(timeoutMs = 150) {
60
+ return new Promise((resolve) => {
61
+ let data = "";
62
+ const timer = setTimeout(() => resolve(data), timeoutMs);
63
+ process.stdin.setEncoding("utf8");
64
+ process.stdin.on("data", (chunk) => {
65
+ data += chunk;
66
+ });
67
+ process.stdin.on("end", () => {
68
+ clearTimeout(timer);
69
+ resolve(data);
70
+ });
71
+ process.stdin.on("error", () => {
72
+ clearTimeout(timer);
73
+ resolve(data);
74
+ });
75
+ });
76
+ }