@popoverinstall/cli 0.8.0 → 0.9.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 (60) hide show
  1. package/CHANGELOG.md +196 -69
  2. package/LICENSE +21 -21
  3. package/README.md +142 -141
  4. package/dist/config-command.d.ts +2 -0
  5. package/dist/config-command.d.ts.map +1 -0
  6. package/dist/config-command.js +80 -0
  7. package/dist/config-command.js.map +1 -0
  8. package/dist/cursor-hooks.d.ts +18 -0
  9. package/dist/cursor-hooks.d.ts.map +1 -0
  10. package/dist/cursor-hooks.js +105 -0
  11. package/dist/cursor-hooks.js.map +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +46 -25
  14. package/dist/index.js.map +1 -1
  15. package/dist/keys.d.ts +10 -0
  16. package/dist/keys.d.ts.map +1 -0
  17. package/dist/keys.js +44 -0
  18. package/dist/keys.js.map +1 -0
  19. package/dist/next-steps.d.ts +1 -5
  20. package/dist/next-steps.d.ts.map +1 -1
  21. package/dist/next-steps.js +9 -5
  22. package/dist/next-steps.js.map +1 -1
  23. package/dist/repo-scan.d.ts +130 -0
  24. package/dist/repo-scan.d.ts.map +1 -0
  25. package/dist/repo-scan.js +281 -0
  26. package/dist/repo-scan.js.map +1 -0
  27. package/dist/repos.d.ts +180 -0
  28. package/dist/repos.d.ts.map +1 -0
  29. package/dist/repos.js +1002 -0
  30. package/dist/repos.js.map +1 -0
  31. package/dist/snapshot.d.ts +35 -0
  32. package/dist/snapshot.d.ts.map +1 -1
  33. package/dist/snapshot.js +16 -16
  34. package/dist/snapshot.js.map +1 -1
  35. package/dist/terminal.d.ts.map +1 -1
  36. package/dist/terminal.js +21 -0
  37. package/dist/terminal.js.map +1 -1
  38. package/dist/vaults.d.ts +276 -0
  39. package/dist/vaults.d.ts.map +1 -0
  40. package/dist/vaults.js +1224 -0
  41. package/dist/vaults.js.map +1 -0
  42. package/package.json +47 -47
  43. package/plugin/.claude-plugin/plugin.json +19 -19
  44. package/plugin/.mcp.json +9 -9
  45. package/plugin/README.md +84 -76
  46. package/plugin/commands/ask.md +65 -65
  47. package/plugin/commands/fork.md +119 -118
  48. package/plugin/commands/repos.md +107 -0
  49. package/plugin/commands/team.md +60 -60
  50. package/plugin/commands/tell.md +66 -66
  51. package/plugin/commands/vault.md +173 -0
  52. package/plugin/hooks/hooks.json +111 -111
  53. package/plugin/mcp/index.mjs +585 -355
  54. package/plugin/scripts/_ipc.mjs +146 -146
  55. package/plugin/scripts/announce-roster.mjs +141 -131
  56. package/plugin/scripts/deliver-messages.mjs +77 -77
  57. package/plugin/scripts/emit-event.mjs +44 -44
  58. package/plugin/scripts/ensure-daemon.mjs +156 -156
  59. package/plugin/scripts/roster.mjs +52 -52
  60. package/plugin/skills/popover/SKILL.md +175 -160
package/README.md CHANGED
@@ -1,141 +1,142 @@
1
- # popover
2
-
3
- **Ask your teammates' agents, not your teammates.**
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 Claude Code agents in the repo you are working in, and answers from a read-only fork
8
- of the agent's full accumulated context.
9
-
10
- Full documentation: **[popover.to/docs](https://www.popover.to/docs)**.
11
-
12
- ## Install
13
-
14
- ```bash
15
- npm install -g @popoverinstall/cli && popover welcome
16
- ```
17
-
18
- Requires **Node.js 20+** and **Claude Code 2.1.229+**. The same two commands on macOS, Linux
19
- and Windows.
20
-
21
- `npm install -g` only places files. `popover welcome` is the rest of a first run: it registers
22
- the Claude Code plugin, records where the daemon lives, and offers to connect this machine. It
23
- prints a short code to confirm in your browser; the device token it stores is generated locally
24
- and never transmitted.
25
-
26
- Both halves are available on their own — `popover setup` for the wiring, `popover login` for the
27
- connection — and `welcome` is re-runnable, so it is also the way back if either needs redoing.
28
-
29
- ## Use
30
-
31
- Inside Claude Code, `/popover:team` prints the roster: every agent your team is running in the
32
- repo you are in, and what each one is doing right now. Each gets a short handle.
33
-
34
- ```
35
- *You*
36
- A1 ad pipeline idle 34m — Wiring Kling into the ad pipeline
37
- A2 popover editing main.py — Team agent communication
38
-
39
- *Bob Chen*
40
- B1 api-server reading config.json — Fixing OAuth token refresh
41
- B2 api-server creating plan.md — Postgres connection pooling
42
- ```
43
-
44
- Three things to do with one:
45
-
46
- ```
47
- /popover:ask B1 why did you rule out redis?
48
- /popover:tell B1 the migration is applied on prod
49
- /popover:fork create
50
- ```
51
-
52
- **Ask** is answered by a read-only fork of that agent, running on its owner's machine. Their
53
- session is never interrupted and their source never leaves their laptop — only the answer
54
- does. The fork has no tools, so a question about something the agent never looked at gets
55
- "I don't know" rather than an investigation, and the answer is billed to their account.
56
-
57
- **Tell** drops a heads-up into a running agent's context before its next prompt. No answer
58
- comes back. It is for facts that would otherwise cause a collision, and it is the one thing
59
- here that reaches a live session: the database caps a sender at ten messages an hour to any
60
- one recipient, and the recipient's human is always notified.
61
-
62
- **Fork** hands over the whole conversation. `/popover:fork create` returns a code that works
63
- for 24 hours; whoever you send it to runs `popover fork open <code>` and carries on in their
64
- own local chat. Opening one needs no popover account — `npx @popoverinstall/cli fork open
65
- <code>` is enough.
66
-
67
- Claude Code namespaces plugin commands, so it is `/popover:team`, not `/team`.
68
-
69
- ## Who can see what
70
-
71
- Agents are only visible to each other **inside the same repo**, so the directory you run from
72
- is what scopes the roster. Repos are identified by their git remotes, so two clones match
73
- however they were cloned; a directory with no shared remote is private to you. The boundary is
74
- enforced by row-level security in Postgres, not by the client.
75
-
76
- ## Commands
77
-
78
- ```
79
- popover welcome Set up and log in — the whole of a first run
80
- popover login / logout Connect this machine, or forget its credentials
81
- popover setup Re-register the Claude Code plugin
82
- popover status This machine's sessions and connection
83
- popover team Print the roster for the repo you are in
84
- popover fork <create|open|launch|list|revoke>
85
- popover log Questions and messages your agents received
86
- popover changelog [--all] What the last update changed
87
- popover update Upgrade and restart the daemon
88
- popover doctor Diagnose setup problems
89
- popover daemon <start|stop|restart|status|logs>
90
- popover uninstall Stop the daemon, unhook Claude Code
91
- ```
92
-
93
- Asking and telling are not in that list on purpose: they live in the session, where the agent
94
- doing them has the context to write a question worth answering and to do something with the
95
- answer.
96
-
97
- ## How it works
98
-
99
- Three pieces install together:
100
-
101
- - **the CLI** — the `popover` command
102
- - **the daemon** — a detached background process that publishes this machine's sessions and
103
- answers teammates' questions. It stores state in `~/.popover/`.
104
- - **the Claude Code plugin** provides `/popover:team`, `/popover:fork`, and the MCP tools
105
- agents use to reach each other. Registered by `popover setup`.
106
-
107
- ## What leaves your machine
108
-
109
- - An **ask** exports one answer. A teammate can learn whatever is already in that agent's
110
- context, including the contents of files it read earlier in the session that is what it
111
- answers from. Every ask is logged with its cost.
112
- - A **fork** exports the entire conversation: every message, every tool result, the contents
113
- of every file read into it. It is sealed before it leaves, with a key derived from the code,
114
- so the server holds something it cannot read but anyone holding the code can open it.
115
- Treat it the way you would treat pasting the transcript into a chat window.
116
- - The device token is generated here and never transmitted; the server only ever sees its
117
- SHA-256.
118
-
119
- ## Troubleshooting
120
-
121
- `popover doctor` diagnoses setup problems in root-cause order and is the right first move for
122
- most things. `popover status` says whether this machine is connected; `popover daemon logs` is
123
- the detail underneath. A teammate missing from the roster is usually running a version too old
124
- to publish a repo identity the roster counts them and says so.
125
-
126
- ## Uninstall
127
-
128
- ```bash
129
- popover uninstall # stop the daemon, unhook Claude Code
130
- npm uninstall -g @popoverinstall/cli
131
- ```
132
-
133
- Credentials and logs are left in `~/.popover/`; delete the directory to remove those too.
134
-
135
- ## Links
136
-
137
- - [Documentation](https://www.popover.to/docs)
138
- - [Changelog](https://www.popover.to/changelog)
139
- - [popover.to](https://www.popover.to) — dashboard, team and activity
140
-
141
- MIT · Quo Labs, Inc.
1
+ # popover
2
+
3
+ **Ask your teammates' agents, not your teammates.**
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 Claude Code agents in the repo you are working in, and answers from a read-only fork
8
+ of the agent's full accumulated context.
9
+
10
+ Full documentation: **[popover.to/docs](https://www.popover.to/docs)**.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g @popoverinstall/cli && popover welcome
16
+ ```
17
+
18
+ Requires **Node.js 20+** and **Claude Code 2.1.229+**. The same two commands on macOS, Linux
19
+ and Windows.
20
+
21
+ `npm install -g` only places files. `popover welcome` is the rest of a first run: it registers
22
+ the Claude Code plugin, records where the daemon lives, and offers to connect this machine. It
23
+ prints a short code to confirm in your browser; the device token it stores is generated locally
24
+ and never transmitted.
25
+
26
+ Both halves are available on their own — `popover setup` for the wiring, `popover login` for the
27
+ connection — and `welcome` is re-runnable, so it is also the way back if either needs redoing.
28
+
29
+ ## Use
30
+
31
+ Inside Claude Code, `/popover:team` prints the roster: every agent your team is running in the
32
+ repo you are in, and what each one is doing right now. Each gets a short handle.
33
+
34
+ ```
35
+ *You*
36
+ A1 ad pipeline idle 34m — Wiring Kling into the ad pipeline
37
+ A2 popover editing main.py — Team agent communication
38
+
39
+ *Bob Chen*
40
+ B1 api-server reading config.json — Fixing OAuth token refresh
41
+ B2 api-server creating plan.md — Postgres connection pooling
42
+ ```
43
+
44
+ Three things to do with one:
45
+
46
+ ```
47
+ /popover:ask B1 why did you rule out redis?
48
+ /popover:tell B1 the migration is applied on prod
49
+ /popover:fork create
50
+ ```
51
+
52
+ **Ask** is answered by a read-only fork of that agent, running on its owner's machine. Their
53
+ session is never interrupted and their source never leaves their laptop — only the answer
54
+ does. The fork has no tools, so a question about something the agent never looked at gets
55
+ "I don't know" rather than an investigation, and the answer is billed to their account.
56
+
57
+ **Tell** drops a heads-up into a running agent's context before its next prompt. No answer
58
+ comes back. It is for facts that would otherwise cause a collision, and it is the one thing
59
+ here that reaches a live session: the database caps a sender at ten messages an hour to any
60
+ one recipient, and the recipient's human is always notified.
61
+
62
+ **Fork** hands over the whole conversation. `/popover:fork create` returns a code that works
63
+ for 24 hours; whoever you send it to runs `popover fork open <code>` and carries on in their
64
+ own local chat. Opening one needs no popover account — `npx @popoverinstall/cli fork open
65
+ <code>` is enough.
66
+
67
+ Claude Code namespaces plugin commands, so it is `/popover:team`, not `/team`.
68
+
69
+ ## Who can see what
70
+
71
+ Agents are only visible to each other **inside the same repo**, so the directory you run from
72
+ is what scopes the roster. Repos are identified by their git remotes, so two clones match
73
+ however they were cloned; a directory with no shared remote is private to you. The boundary is
74
+ enforced by row-level security in Postgres, not by the client.
75
+
76
+ ## Commands
77
+
78
+ ```
79
+ popover welcome Set up and log in — the whole of a first run
80
+ popover login / logout Connect this machine, or forget its credentials
81
+ popover setup Re-register the Claude Code plugin
82
+ popover status This machine's sessions and connection
83
+ popover team Print the roster for the repo you are in
84
+ popover rename <who> [name] Name one of your own agents, or clear its name
85
+ popover fork <create|open|launch|list|revoke>
86
+ popover log Questions and messages your agents received
87
+ popover changelog [--all] What the last update changed
88
+ popover update Upgrade and restart the daemon
89
+ popover doctor Diagnose setup problems
90
+ popover daemon <start|stop|restart|status|logs>
91
+ popover uninstall Stop the daemon, unhook Claude Code
92
+ ```
93
+
94
+ Asking and telling are not in that list on purpose: they live in the session, where the agent
95
+ doing them has the context to write a question worth answering and to do something with the
96
+ answer.
97
+
98
+ ## How it works
99
+
100
+ Three pieces install together:
101
+
102
+ - **the CLI** — the `popover` command
103
+ - **the daemon** a detached background process that publishes this machine's sessions and
104
+ answers teammates' questions. It stores state in `~/.popover/`.
105
+ - **the Claude Code plugin** provides `/popover:team`, `/popover:fork`, and the MCP tools
106
+ agents use to reach each other. Registered by `popover setup`.
107
+
108
+ ## What leaves your machine
109
+
110
+ - An **ask** exports one answer. A teammate can learn whatever is already in that agent's
111
+ context, including the contents of files it read earlier in the session — that is what it
112
+ answers from. Every ask is logged with its cost.
113
+ - A **fork** exports the entire conversation: every message, every tool result, the contents
114
+ of every file read into it. It is sealed before it leaves, with a key derived from the code,
115
+ so the server holds something it cannot read but anyone holding the code can open it.
116
+ Treat it the way you would treat pasting the transcript into a chat window.
117
+ - The device token is generated here and never transmitted; the server only ever sees its
118
+ SHA-256.
119
+
120
+ ## Troubleshooting
121
+
122
+ `popover doctor` diagnoses setup problems in root-cause order and is the right first move for
123
+ most things. `popover status` says whether this machine is connected; `popover daemon logs` is
124
+ the detail underneath. A teammate missing from the roster is usually running a version too old
125
+ to publish a repo identity — the roster counts them and says so.
126
+
127
+ ## Uninstall
128
+
129
+ ```bash
130
+ popover uninstall # stop the daemon, unhook Claude Code
131
+ npm uninstall -g @popoverinstall/cli
132
+ ```
133
+
134
+ Credentials and logs are left in `~/.popover/`; delete the directory to remove those too.
135
+
136
+ ## Links
137
+
138
+ - [Documentation](https://www.popover.to/docs)
139
+ - [Changelog](https://www.popover.to/changelog)
140
+ - [popover.to](https://www.popover.to) — dashboard, team and activity
141
+
142
+ MIT · Quo Labs, Inc.
@@ -0,0 +1,2 @@
1
+ export declare function configCommand(args: string[]): number;
2
+ //# sourceMappingURL=config-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-command.d.ts","sourceRoot":"","sources":["../src/config-command.ts"],"names":[],"mappings":"AAmDA,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAqDpD"}
@@ -0,0 +1,80 @@
1
+ import { DEFAULT_CONFIG, WorkspaceScanSchema, configPath, loadConfig, saveConfig, } from "@popoverinstall/shared";
2
+ import { bad, c, dim, info, ok } from "./ui.js";
3
+ const SETTINGS = [
4
+ {
5
+ name: "workspace-scan",
6
+ key: "workspaceScan",
7
+ describe: "Whether a directory that is not itself a repo, but contains repos, publishes them",
8
+ values: WorkspaceScanSchema.options,
9
+ validate: (raw) => WorkspaceScanSchema.safeParse(raw).success,
10
+ effect: (value) => value === "children"
11
+ ? "An agent in a folder above several repos is visible to teammates in each of them."
12
+ : "An agent in a folder that is not itself a repo is private to you again.",
13
+ },
14
+ ];
15
+ export function configCommand(args) {
16
+ const [name, value] = args;
17
+ if (!name || name === "--help" || name === "-h" || name === "help") {
18
+ return list();
19
+ }
20
+ const setting = SETTINGS.find((s) => s.name === name);
21
+ if (!setting) {
22
+ bad(`No setting called \`${name}\`.`);
23
+ dim(`Known settings: ${SETTINGS.map((s) => s.name).join(", ")}`);
24
+ return 1;
25
+ }
26
+ // No value: report this one rather than assuming the user meant to clear it. A settings
27
+ // command where `config foo` unsets `foo` is a settings command that loses data to a
28
+ // typo.
29
+ if (value === undefined) {
30
+ const current = loadConfig()[setting.key];
31
+ info(`${c.bold(setting.name)} ${current}`);
32
+ dim(`${setting.describe}.`);
33
+ dim(`Values: ${setting.values.join(", ")} (default ${DEFAULT_CONFIG[setting.key]})`);
34
+ return 0;
35
+ }
36
+ if (!setting.validate(value)) {
37
+ bad(`\`${value}\` is not a valid ${setting.name}.`);
38
+ dim(`Values: ${setting.values.join(", ")}`);
39
+ return 1;
40
+ }
41
+ const before = loadConfig()[setting.key];
42
+ if (before === value) {
43
+ // Said plainly rather than reported as a change, because "it was already that" and "I
44
+ // just changed it" should not read identically when someone is checking their work.
45
+ ok(`${setting.name} is already ${value}.`);
46
+ return 0;
47
+ }
48
+ try {
49
+ saveConfig({ [setting.key]: value });
50
+ }
51
+ catch (err) {
52
+ bad(`Could not write ${configPath()}.`);
53
+ dim(err instanceof Error ? err.message : String(err));
54
+ return 1;
55
+ }
56
+ ok(`${setting.name}: ${before} → ${value}`);
57
+ dim(setting.effect(value));
58
+ // The daemon re-reads this file rather than caching it, which is what makes the promise
59
+ // below true. See `loadConfig` for why it is deliberately not memoized.
60
+ dim("The daemon picks this up within a minute; no restart needed.");
61
+ return 0;
62
+ }
63
+ function list() {
64
+ const current = loadConfig();
65
+ console.log(`
66
+ ${c.bold("popover config")} — settings for this machine
67
+
68
+ ${c.dim(configPath())}
69
+ `);
70
+ for (const setting of SETTINGS) {
71
+ info(`${c.bold(setting.name.padEnd(16))} ${current[setting.key]}`);
72
+ dim(`${setting.describe}.`);
73
+ dim(`Values: ${setting.values.join(", ")} (default ${DEFAULT_CONFIG[setting.key]})`);
74
+ console.log();
75
+ }
76
+ dim("popover config <setting> show one");
77
+ dim("popover config <setting> <value> change it");
78
+ return 0;
79
+ }
80
+ //# sourceMappingURL=config-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-command.js","sourceRoot":"","sources":["../src/config-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Add popover's Cursor hooks to `~/.cursor/hooks.json`, without touching anything already
3
+ * there.
4
+ *
5
+ * Unlike Claude Code's plugin-scoped hook file, `~/.cursor/hooks.json` is one file shared
6
+ * by every tool a user has configured Cursor hooks for — so this always reads the existing
7
+ * file, appends only popover's own entries (skipping any it already added, so re-running
8
+ * `setup`/`update` is idempotent), and never removes or replaces anything else in it.
9
+ *
10
+ * Whether Cursor actually *runs* every entry in a multi-entry event array, as opposed to
11
+ * only the first, is not yet confirmed — see the note above `setup()`. This writes the
12
+ * structurally-correct merge regardless; that question only affects whether Cursor honors
13
+ * it, not whether this code is right to write it this way.
14
+ *
15
+ * A no-op, not an error, if Cursor isn't installed on this machine (no `~/.cursor`).
16
+ */
17
+ export declare function installCursorHooks(): number;
18
+ //# sourceMappingURL=cursor-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-hooks.d.ts","sourceRoot":"","sources":["../src/cursor-hooks.ts"],"names":[],"mappings":"AA8CA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAmD3C"}
@@ -0,0 +1,105 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { bad, dim, info, ok } from "./ui.js";
6
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
7
+ /**
8
+ * Which Cursor hook event runs which of our scripts.
9
+ *
10
+ * A single source of truth kept in code rather than a template JSON file: unlike Claude
11
+ * Code's `${CLAUDE_PLUGIN_ROOT}`-templated `hooks/hooks.json`, Cursor's hook config has no
12
+ * confirmed placeholder-substitution mechanism of its own, so *we* have to resolve the
13
+ * scripts' absolute path and merge it in ourselves — a JS map does that more directly than
14
+ * a JSON file that would still need string substitution before use.
15
+ *
16
+ * `stop` carries two entries deliberately: detection (status) and tell delivery
17
+ * (followup_message), confirmed as the right event for both empirically — see the plan.
18
+ */
19
+ const EVENT_SCRIPTS = {
20
+ sessionStart: ["ensure-daemon.mjs"],
21
+ beforeSubmitPrompt: ["cursor-emit-event.mjs"],
22
+ preToolUse: ["cursor-emit-event.mjs"],
23
+ postToolUse: ["cursor-emit-event.mjs"],
24
+ stop: ["cursor-emit-event.mjs", "cursor-deliver-messages.mjs"],
25
+ sessionEnd: ["cursor-emit-event.mjs"],
26
+ };
27
+ function cursorHooksPath() {
28
+ return path.join(os.homedir(), ".cursor", "hooks.json");
29
+ }
30
+ /**
31
+ * The directory holding the plugin's `scripts/`, resolved the same way
32
+ * `resolveMarketplaceRoot` in setup.ts finds the plugin root — published layout first,
33
+ * dev-checkout fallback second.
34
+ */
35
+ function resolveScriptsDir() {
36
+ const candidates = [
37
+ path.join(HERE, "..", "plugin", "scripts"), // <pkg>/plugin/scripts — published layout
38
+ path.join(HERE, "..", "..", "..", "plugin", "scripts"), // repo root — dev checkout
39
+ ];
40
+ return candidates.find((d) => existsSync(d)) ?? null;
41
+ }
42
+ /**
43
+ * Add popover's Cursor hooks to `~/.cursor/hooks.json`, without touching anything already
44
+ * there.
45
+ *
46
+ * Unlike Claude Code's plugin-scoped hook file, `~/.cursor/hooks.json` is one file shared
47
+ * by every tool a user has configured Cursor hooks for — so this always reads the existing
48
+ * file, appends only popover's own entries (skipping any it already added, so re-running
49
+ * `setup`/`update` is idempotent), and never removes or replaces anything else in it.
50
+ *
51
+ * Whether Cursor actually *runs* every entry in a multi-entry event array, as opposed to
52
+ * only the first, is not yet confirmed — see the note above `setup()`. This writes the
53
+ * structurally-correct merge regardless; that question only affects whether Cursor honors
54
+ * it, not whether this code is right to write it this way.
55
+ *
56
+ * A no-op, not an error, if Cursor isn't installed on this machine (no `~/.cursor`).
57
+ */
58
+ export function installCursorHooks() {
59
+ const cursorHome = path.join(os.homedir(), ".cursor");
60
+ if (!existsSync(cursorHome)) {
61
+ dim("Cursor not found on this machine; skipping its hooks.");
62
+ return 0;
63
+ }
64
+ const scriptsDir = resolveScriptsDir();
65
+ if (!scriptsDir) {
66
+ bad("Could not find the bundled Cursor hook scripts.");
67
+ dim("This install looks incomplete — reinstall with: npm install -g @popoverinstall/cli");
68
+ return 1;
69
+ }
70
+ const file = cursorHooksPath();
71
+ let config = { version: 1, hooks: {} };
72
+ if (existsSync(file)) {
73
+ try {
74
+ config = JSON.parse(readFileSync(file, "utf8"));
75
+ }
76
+ catch (err) {
77
+ bad(`Could not parse ${file} — leaving it untouched.`);
78
+ dim(err instanceof Error ? err.message : String(err));
79
+ return 1;
80
+ }
81
+ }
82
+ config.hooks ??= {};
83
+ config.version ??= 1;
84
+ let added = 0;
85
+ for (const [event, scripts] of Object.entries(EVENT_SCRIPTS)) {
86
+ const existing = (config.hooks[event] ??= []);
87
+ for (const script of scripts) {
88
+ const command = `node "${path.join(scriptsDir, script)}"`;
89
+ const alreadyPresent = existing.some((h) => typeof h === "object" && h !== null && h.command === command);
90
+ if (alreadyPresent)
91
+ continue;
92
+ existing.push({ command, timeout: script === "ensure-daemon.mjs" ? 30 : 5 });
93
+ added += 1;
94
+ }
95
+ }
96
+ if (added === 0) {
97
+ ok("Cursor hooks already installed.");
98
+ return 0;
99
+ }
100
+ mkdirSync(cursorHome, { recursive: true });
101
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`, "utf8");
102
+ info(`Added ${added} Cursor hook${added === 1 ? "" : "s"} to ${file}.`);
103
+ return 0;
104
+ }
105
+ //# sourceMappingURL=cursor-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-hooks.js","sourceRoot":"","sources":["../src/cursor-hooks.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAsCA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAyCA;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,MAEb,CAAC"}
package/dist/index.js CHANGED
@@ -12,9 +12,12 @@ import { execTool } from "./exec.js";
12
12
  import { askDaemon, daemonIsRunning } from "./ipc-client.js";
13
13
  import { login } from "./login.js";
14
14
  import { rename } from "./rename.js";
15
+ import { reposCommand } from "./repos.js";
16
+ import { configCommand } from "./config-command.js";
15
17
  import { setup } from "./setup.js";
16
18
  import { forkCommand } from "./snapshot.js";
17
19
  import { bad, c, dim, info, ok, warn } from "./ui.js";
20
+ import { vaultCommand } from "./vaults.js";
18
21
  import { welcome } from "./welcome.js";
19
22
  /**
20
23
  * This package's own version, read from the manifest npm installed beside us.
@@ -55,10 +58,25 @@ async function main() {
55
58
  return team();
56
59
  case "rename":
57
60
  return rename(rest);
61
+ // Deliberately takes no repo argument at any depth: which repo it means comes from the
62
+ // working directory, because registering one is a claim of possession rather than a
63
+ // name. See packages/cli/src/repos.ts.
64
+ case "repos":
65
+ return reposCommand(rest);
66
+ // Machine-local settings, not team ones. The only key today changes who can see an
67
+ // agent, so it needs a way to be read and turned off that is not hand-editing JSON.
68
+ case "config":
69
+ return configCommand(rest);
58
70
  case "daemon":
59
71
  return daemonCommand(rest[0] ?? "status");
60
72
  case "fork":
61
73
  return forkCommand(rest);
74
+ // A vault is the other half of `fork`, and the difference is worth knowing before you
75
+ // reach for either: a fork goes to one named person, expires in a day, and is carried on
76
+ // as their own session; a vault is published to a team, never expires, is never carried
77
+ // on, and is readable by the service. See packages/cli/src/vaults.ts and docs/vaults.md.
78
+ case "vault":
79
+ return vaultCommand(rest);
62
80
  case "log":
63
81
  return showLog(Number(flag(rest, "--lines") ?? 20));
64
82
  case "uninstall":
@@ -128,35 +146,38 @@ function gettingStarted() {
128
146
  ? ` ${c.green(marker)} ${c.dim(name)}${c.dim(what)}`
129
147
  : ` ${c.bold(marker)} ${c.bold(name)}${what}`;
130
148
  });
131
- return ` ${c.bold("Finish installing")} — npm placed the files, these connect them:
132
-
133
- ${lines.join("\n")}
134
-
135
- ${c.dim("Then run")} ${c.cyan("/popover:team")} ${c.dim("inside Claude Code to see your teammates' agents.")}
149
+ return ` ${c.bold("Finish installing")} — npm placed the files, these connect them:
150
+
151
+ ${lines.join("\n")}
152
+
153
+ ${c.dim("Then run")} ${c.cyan("/popover:team")} ${c.dim("inside Claude Code to see your teammates' agents.")}
136
154
  `;
137
155
  }
138
156
  function usage() {
139
157
  const next = gettingStarted();
140
- console.log(`
141
- ${c.bold("popover")} — connect this machine, and see what your team's agents are doing
142
- ${next ? `\n${next}` : ""}
143
- Asking and telling happen inside a session, where the agent doing them has the
144
- context to be worth reading: ${c.bold("/popover:ask B1 …")} and ${c.bold("/popover:tell B1 …")}
145
-
146
- ${c.bold("popover login")} [--api URL] Connect this machine to your team
147
- ${c.bold("popover logout")} Forget this machine's credentials
148
- ${c.bold("popover setup")} [--force] Re-register the Claude Code plugin
149
- ${c.bold("popover welcome")} Set up and log in — the whole of a first run
150
- ${c.bold("popover update")} Upgrade to the newest version and restart the daemon
151
- ${c.bold("popover changelog")} [--all] What the last update changed
152
- ${c.bold("popover status")} Show this machine's sessions and connection
153
- ${c.bold("popover team")} Print the team roster
154
- ${c.bold("popover rename")} [handle] Name an agent, so your team can tell them apart
155
- ${c.bold("popover fork")} <create|open|launch|list|revoke>
156
- ${c.bold("popover doctor")} Diagnose setup problems
157
- ${c.bold("popover daemon")} <start|stop|restart|status|logs>
158
- ${c.bold("popover log")} [--lines N] Recent questions and messages your agents received
159
- ${c.bold("popover uninstall")} Remove popover from this machine
158
+ console.log(`
159
+ ${c.bold("popover")} — connect this machine, and see what your team's agents are doing
160
+ ${next ? `\n${next}` : ""}
161
+ Asking and telling happen inside a session, where the agent doing them has the
162
+ context to be worth reading: ${c.bold("/popover:ask B1 …")} and ${c.bold("/popover:tell B1 …")}
163
+
164
+ ${c.bold("popover login")} [--api URL] Connect this machine to your team
165
+ ${c.bold("popover logout")} Forget this machine's credentials
166
+ ${c.bold("popover setup")} [--force] Re-register the Claude Code plugin
167
+ ${c.bold("popover welcome")} Set up and log in — the whole of a first run
168
+ ${c.bold("popover update")} Upgrade to the newest version and restart the daemon
169
+ ${c.bold("popover changelog")} [--all] What the last update changed
170
+ ${c.bold("popover status")} Show this machine's sessions and connection
171
+ ${c.bold("popover team")} Print the team roster
172
+ ${c.bold("popover rename")} [handle] Name an agent, so your team can tell them apart
173
+ ${c.bold("popover repos")} <add|rm> Which repos your team treats as one product
174
+ ${c.bold("popover config")} [key] [value] Settings for this machine
175
+ ${c.bold("popover fork")} <create|open|launch|list|revoke>
176
+ ${c.bold("popover vault")} <create|list|ask|note|archive>
177
+ ${c.bold("popover doctor")} Diagnose setup problems
178
+ ${c.bold("popover daemon")} <start|stop|restart|status|logs>
179
+ ${c.bold("popover log")} [--lines N] Recent questions and messages your agents received
180
+ ${c.bold("popover uninstall")} Remove popover from this machine
160
181
  `);
161
182
  }
162
183
  /**