@popoverinstall/cli 0.8.1 → 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 (53) hide show
  1. package/CHANGELOG.md +147 -78
  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/repo-scan.d.ts +130 -0
  20. package/dist/repo-scan.d.ts.map +1 -0
  21. package/dist/repo-scan.js +281 -0
  22. package/dist/repo-scan.js.map +1 -0
  23. package/dist/repos.d.ts +180 -0
  24. package/dist/repos.d.ts.map +1 -0
  25. package/dist/repos.js +1002 -0
  26. package/dist/repos.js.map +1 -0
  27. package/dist/snapshot.d.ts +35 -0
  28. package/dist/snapshot.d.ts.map +1 -1
  29. package/dist/snapshot.js +16 -16
  30. package/dist/snapshot.js.map +1 -1
  31. package/dist/vaults.d.ts +276 -0
  32. package/dist/vaults.d.ts.map +1 -0
  33. package/dist/vaults.js +1224 -0
  34. package/dist/vaults.js.map +1 -0
  35. package/package.json +47 -47
  36. package/plugin/.claude-plugin/plugin.json +19 -19
  37. package/plugin/.mcp.json +9 -9
  38. package/plugin/README.md +84 -77
  39. package/plugin/commands/ask.md +65 -65
  40. package/plugin/commands/fork.md +119 -119
  41. package/plugin/commands/repos.md +107 -0
  42. package/plugin/commands/team.md +60 -60
  43. package/plugin/commands/tell.md +66 -66
  44. package/plugin/commands/vault.md +173 -0
  45. package/plugin/hooks/hooks.json +111 -111
  46. package/plugin/mcp/index.mjs +585 -355
  47. package/plugin/scripts/_ipc.mjs +146 -146
  48. package/plugin/scripts/announce-roster.mjs +141 -141
  49. package/plugin/scripts/deliver-messages.mjs +77 -77
  50. package/plugin/scripts/emit-event.mjs +44 -44
  51. package/plugin/scripts/ensure-daemon.mjs +156 -156
  52. package/plugin/scripts/roster.mjs +52 -52
  53. package/plugin/skills/popover/SKILL.md +175 -168
@@ -0,0 +1,180 @@
1
+ import { type LocalRepo } from "./repo-scan.js";
2
+ /**
3
+ * `popover repos` — which repositories your teams treat as one product.
4
+ *
5
+ * Two agents can see each other when they share a team and share a repo. A team's repo list
6
+ * is what lets "share a repo" mean `backend`, `app` and `firmware` rather than one clone —
7
+ * see docs/team-repos.md. This command is the half of that a person drives from a terminal:
8
+ *
9
+ * popover repos what this repo is on, and what your teams have listed
10
+ * popover repos add [team] register this repo with a team you administer
11
+ * popover repos rm [team] remove it
12
+ *
13
+ * ## Why there is no repo argument
14
+ *
15
+ * Because a name is not a claim. The list stores a possession-derived key, and the server
16
+ * resolves that key from the caller's *own published sessions*; naming a repository you have
17
+ * never held produces nothing at all (§2). So possession comes from the working directory,
18
+ * and the only thing this command sends is a remote it found there.
19
+ *
20
+ * ## Why this never touches Postgres
21
+ *
22
+ * Every other read the CLI does could in principle go straight to Supabase under the daemon's
23
+ * token, governed by RLS. Registration cannot: it has to be accompanied by a GitHub ownership
24
+ * check that needs the admin's OAuth token, which Clerk holds and only the web app can reach.
25
+ * `register_team_repo` is service-role only and refuses an `authenticated` caller outright, so
26
+ * there is deliberately no path from here to the table. Hence the three HTTP routes below, and
27
+ * hence the fact that the failures worth explaining at length are all *its* failures.
28
+ *
29
+ * ## Removal is here, but not in the plugin command
30
+ *
31
+ * Removing a repo cuts other people's agents out of scope at the next gate evaluation. It is
32
+ * one line to type and there is no undo but re-adding, so it stays a thing a human typed.
33
+ * `/popover:repos` reports and adds, and says why it will not remove.
34
+ */
35
+ /**
36
+ * A team the caller belongs to.
37
+ *
38
+ * `admins` is optional and the CLI treats it as a nicety rather than a guarantee. It is what
39
+ * turns "you are not an admin" into "ask Ana or Priya", which is the difference between a
40
+ * refusal somebody can act on and one that just stops them — but a backend that does not send
41
+ * it must not break the command, so every use of it degrades to naming the team alone.
42
+ */
43
+ export interface TeamView {
44
+ id: string;
45
+ name: string;
46
+ isAdmin: boolean;
47
+ admins?: string[];
48
+ }
49
+ /**
50
+ * A repo the caller has sessions in, and the teams that list it.
51
+ *
52
+ * `lastSeen` is null for a repo the caller does not hold — an entry that is on a team's list
53
+ * but has never been worked in from this account. Rendered as such rather than hidden, because
54
+ * "Quolabs lists four repos and you have two of them" is the fact somebody is looking for.
55
+ */
56
+ export interface RepoView {
57
+ remote: string;
58
+ lastSeen: string | null;
59
+ teams: string[];
60
+ }
61
+ export interface ReposView {
62
+ teams: TeamView[];
63
+ repos: RepoView[];
64
+ }
65
+ /**
66
+ * Why the server refused to register a repo.
67
+ *
68
+ * Six values, and each one gets its own sentence in `refusal()` below. Collapsing any two of
69
+ * them into "could not add that repo" would be the whole difference between a person fixing
70
+ * this in thirty seconds and giving up: they are respectively a permissions problem in
71
+ * popover, a missing GitHub link, a missing GitHub *scope*, a permissions problem on GitHub,
72
+ * a repository host we cannot check at all, and an organization that has not approved us.
73
+ *
74
+ * An unknown seventh value from a newer backend is relayed verbatim rather than mapped to the
75
+ * nearest of these, because a wrong explanation is worse than an unexplained refusal.
76
+ */
77
+ export type AddRefusal = "not_admin" | "github_not_linked" | "github_scope_missing" | "not_repo_admin" | "not_github" | "github_org_restricted";
78
+ export declare function reposCommand(argv: string[]): Promise<number>;
79
+ export interface AddPlan {
80
+ /** What to POST, and under which remote. */
81
+ add: Array<{
82
+ repo: LocalRepo;
83
+ remote: string;
84
+ }>;
85
+ /** Already on this team's list. Not an error; the command says so and exits 0. */
86
+ already: LocalRepo[];
87
+ /** Held here, but with no key on the server, so there is nothing to register. */
88
+ unpublished: LocalRepo[];
89
+ }
90
+ /**
91
+ * Sort the repos in front of us into the three things that can be true of them.
92
+ *
93
+ * The subtle part is *which remote* gets registered for a clone that has several. A fork has
94
+ * `origin = alice/backend` and `upstream = acme/backend` and publishes a key for each, so the
95
+ * remote to register is the first one the server confirms a key for — not blindly the primary,
96
+ * which would produce a refusal ("no key for alice/backend") that is true, unhelpful, and
97
+ * avoidable with information we already hold.
98
+ *
99
+ * When the server confirms none of them the repo is `unpublished`, which is checked here
100
+ * rather than discovered from a 4xx, because only this side knows whether the cause is a
101
+ * shallow clone.
102
+ */
103
+ export declare function planAdd(here: LocalRepo[], team: TeamView, view: ReposView): AddPlan;
104
+ /**
105
+ * Which teams a typed argument could mean.
106
+ *
107
+ * Matched by id, then by exact name, then by prefix — the same widening a person expects from
108
+ * `popover rename B1`. Case-insensitive, because a team called "Quolabs" is typed `quolabs`.
109
+ * Returns every match rather than picking one: two teams whose names share a prefix is a real
110
+ * arrangement, and silently registering a repo with the wrong one is not recoverable by the
111
+ * person who did it.
112
+ */
113
+ export declare function resolveTeam(teams: TeamView[], typed: string): TeamView[];
114
+ export interface RefusalContext {
115
+ team?: TeamView;
116
+ remote?: string;
117
+ verb?: "add" | "remove";
118
+ }
119
+ export interface RefusalText {
120
+ headline: string;
121
+ hints: string[];
122
+ }
123
+ /**
124
+ * One actionable sentence per reason, plus what to do about it.
125
+ *
126
+ * Written out longhand rather than generated, because the differences between them are the
127
+ * whole point and a template would flatten exactly the two that are easiest to confuse:
128
+ * `github_not_linked` and `github_scope_missing`. Most people who sign up for popover sign up
129
+ * *with GitHub*, which links an account with Clerk's default scopes and no `repo` — so the
130
+ * second is the common case, and telling that person "you have no GitHub account" sends them
131
+ * to link a second one and get the same refusal again.
132
+ */
133
+ export declare function refusal(reason: AddRefusal, ctx?: RefusalContext): RefusalText;
134
+ /**
135
+ * Turn an API failure into text, mapping an unknown reason to something honest.
136
+ *
137
+ * Exported for the tests rather than for other callers: the three branches below all exist to
138
+ * stop a non-refusal being rendered as a refusal, and each of them is one careless edit away
139
+ * from telling somebody to go and change permissions they have.
140
+ */
141
+ export declare function refusalFor(result: ApiFailure, remote: string, team: TeamView, verb?: "add" | "remove", repo?: LocalRepo): RefusalText;
142
+ export interface ApiFailure {
143
+ ok: false;
144
+ status: number;
145
+ /** The `reason` from a 403 body, when there was one. */
146
+ reason?: string;
147
+ /** The `error` code from any of our own error bodies. */
148
+ error?: string;
149
+ /**
150
+ * A sentence the server wrote, when it sent one.
151
+ *
152
+ * Relayed rather than rewritten for the failures this file cannot phrase better than the
153
+ * route can — a GitHub outage carries its own detail, and a newer backend may know about
154
+ * something this build does not. Never used for the six `reason` refusals, whose wording is
155
+ * the point and lives in `refusal()`.
156
+ */
157
+ message?: string;
158
+ /**
159
+ * Something answered with a success status and a body that is not what this route returns.
160
+ *
161
+ * Its own flag rather than a status, because the status is 200 and reporting "failed (200)"
162
+ * is a sentence nobody can act on. In practice this is an auth wall that redirects rather
163
+ * than refusing: `https://popover.to/api/teams/repos` 307s to a sign-in page, `fetch`
164
+ * follows it, and the HTML that comes back is a perfectly successful response to a request
165
+ * we did not make. Left unchecked it surfaced as a TypeError about `.map`.
166
+ */
167
+ malformed?: true;
168
+ /**
169
+ * Whether our API refused this, or something in front of it did.
170
+ *
171
+ * The same test `popover fork` makes, for the same reason: a 401 from the route means the
172
+ * device token is no longer good and the fix is `popover login`, while a 401 from an auth
173
+ * wall between here and the app — a protected preview deployment, an SSO proxy, a captive
174
+ * portal — means nothing of the sort, and sending someone to re-authenticate popover over it
175
+ * wastes their time on the wrong problem. Our routes always answer with a JSON `error`
176
+ * string; a wall answers with something else.
177
+ */
178
+ ours: boolean;
179
+ }
180
+ //# sourceMappingURL=repos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repos.d.ts","sourceRoot":"","sources":["../src/repos.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,SAAS,EAGf,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,mBAAmB,GACnB,sBAAsB,GACtB,gBAAgB,GAChB,YAAY,GACZ,uBAAuB,CAAC;AAe5B,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8ClE;AA2UD,MAAM,WAAW,OAAO;IACtB,4CAA4C;IAC5C,GAAG,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,kFAAkF;IAClF,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,iFAAiF;IACjF,WAAW,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAqBnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAWxE;AAUD,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,GAAE,cAAmB,GAAG,WAAW,CA8EjF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,IAAI,GAAE,KAAK,GAAG,QAAgB,EAC9B,IAAI,CAAC,EAAE,SAAS,GACf,WAAW,CAmEb;AAQD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB;;;;;;;;;OASG;IACH,IAAI,EAAE,OAAO,CAAC;CACf"}