@vibes.diy/prompts 14.3.160 → 14.3.162
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/llms/backend.md +125 -27
- package/llms/connections.js +1 -1
- package/llms/connections.js.map +1 -1
- package/llms/connections.md +6 -1
- package/llms/github.d.ts +2 -0
- package/llms/github.js +21 -0
- package/llms/github.js.map +1 -0
- package/llms/github.md +564 -0
- package/llms/index.d.ts +2 -1
- package/llms/index.js +3 -0
- package/llms/index.js.map +1 -1
- package/package.json +5 -5
package/llms/backend.md
CHANGED
|
@@ -100,11 +100,13 @@ visitor can invoke must not run owner-privileged.
|
|
|
100
100
|
|
|
101
101
|
### Taking on the authorization job: `config.fetch.unfilteredReads`
|
|
102
102
|
|
|
103
|
-
Because the `fetch` lane runs as the calling user, a
|
|
104
|
-
reads
|
|
105
|
-
|
|
106
|
-
`
|
|
107
|
-
|
|
103
|
+
Because the `fetch` lane runs as the calling user, a signed-in caller's
|
|
104
|
+
`fetch` handler reads exactly what that caller could read from their own page —
|
|
105
|
+
an access-fn-bound database comes back filtered to their channels, like
|
|
106
|
+
`onChange` reads for its triggering user. A caller with no session reads the
|
|
107
|
+
databases the opt-in below names. Some routes need exactly that: a calendar
|
|
108
|
+
feed at `GET /_api/faves.ics?t=<token>` is fetched by a calendar app that carries
|
|
109
|
+
no login, and the docs it serves are private to one user. For those, `backend.js`
|
|
108
110
|
can opt in — per database, `fetch` lane only:
|
|
109
111
|
|
|
110
112
|
```js
|
|
@@ -155,8 +157,10 @@ const res = await ctx.fetch("https://api.example.com/x", { headers }); // outbou
|
|
|
155
157
|
await ctx.notify({ toHandle: "alice", body: "Your turn", dedupeKey: "game-7-turn-3", path: "#game-7" }); // one participant; `path` optional; throws on failure
|
|
156
158
|
ctx.log("info", "tick finished", { written: 3 }); // diagnostics — read back with: vibes-diy app logs owner/slug
|
|
157
159
|
const gh = await ctx.github.fetch("/repos/acme/site/issues", { method: "POST", body: { title: "Filed from the vibe" } }); // connected GitHub repo
|
|
160
|
+
const routes = await ctx.github.routes(); // [{ repo, accountLogin, binderHandle, installationId }] — who connected which repo; no GitHub call
|
|
158
161
|
```
|
|
159
162
|
|
|
163
|
+
- An app that works with somebody's GitHub repo loads the `github` skill: the connect link, `config.github`, `ctx.github.routes()`, deliveries and a complete example.
|
|
160
164
|
- `ctx.github.fetch(path, init)` calls the GitHub REST API **as the GitHub App
|
|
161
165
|
installation the owner connected to this vibe**. It is the only GitHub surface
|
|
162
166
|
a backend has: `path` is a GitHub API path rooted at `/` (`/repos/…`, plus
|
|
@@ -182,6 +186,58 @@ const gh = await ctx.github.fetch("/repos/acme/site/issues", { method: "POST", b
|
|
|
182
186
|
`permission_not_granted` — the installation lacks that permission, or
|
|
183
187
|
`upstream_error`); branch on `err.reason`, and tell the user which one it was.
|
|
184
188
|
|
|
189
|
+
- **Connecting a repository is a link, not a call.** The page links to the
|
|
190
|
+
PLATFORM's `/github/install?vibe=<owner>/<slug>` with an **absolute** URL in a
|
|
191
|
+
**new tab** — never a root-relative `/github/install` (the app is served from
|
|
192
|
+
its own host, where that path is not the platform's) and never `fetch` (the
|
|
193
|
+
next stop is github.com, and the consent page refuses to load in a frame).
|
|
194
|
+
`buildGithubInstallUrl()` from `use-vibes` returns that absolute address for
|
|
195
|
+
the running vibe — on `https://vibes.diy`, or on the PR preview the vibe runs
|
|
196
|
+
under — so the page never writes the host or its own `<owner>/<slug>`:
|
|
197
|
+
|
|
198
|
+
```jsx
|
|
199
|
+
import { buildGithubInstallUrl } from "use-vibes";
|
|
200
|
+
|
|
201
|
+
<a href={buildGithubInstallUrl()} target="_blank" rel="noopener">
|
|
202
|
+
Connect a GitHub repository
|
|
203
|
+
</a>;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The owner goes straight to GitHub to pick repositories. By default only the
|
|
207
|
+
owner may connect one; to let **visitors** connect their own repositories (a
|
|
208
|
+
PR reviewer, a repo dashboard anyone can use), declare it in `backend.js`:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
export const config = {
|
|
212
|
+
github: { bind: "anyone", why: "Reviews the pull requests you open and leaves comments on them." },
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`bind` is `"owner"` (the default) or `"anyone"`; with `"anyone"`, `why` is
|
|
217
|
+
required — one static sentence, 280 characters at most — because the platform
|
|
218
|
+
shows it on the consent page a visitor reads before going to GitHub. Write it
|
|
219
|
+
inline as `github: { … }` — a shorthand `{ github }` or a method is refused —
|
|
220
|
+
and a malformed `config.github` refuses the push. A visitor always passes
|
|
221
|
+
through that consent page: an install that skips it connects nothing. Repositories a visitor connects are
|
|
222
|
+
bound to this vibe like the owner's, so `ctx.github.fetch` and the
|
|
223
|
+
`/_api/github-webhook` deliveries cover them too; a repository someone else
|
|
224
|
+
already connected stays theirs.
|
|
225
|
+
- `ctx.github.routes()` lists the repos connected to THIS vibe and who
|
|
226
|
+
connected each one, as
|
|
227
|
+
`[{ repo, accountLogin, binderHandle, installationId }]`: `repo` is
|
|
228
|
+
`owner/name` (lowercase, as stored), `accountLogin` is the GitHub account the
|
|
229
|
+
App is installed on, `binderHandle` is the vibes.diy handle of the person who
|
|
230
|
+
connected it (`undefined` when that account has no handle), and
|
|
231
|
+
`installationId` is a number. It reads the platform's own records and makes
|
|
232
|
+
no GitHub call, so it is cheap enough to run on every request. An unconnected
|
|
233
|
+
vibe gets `[]`, never an error. This is how ONE vibe serves many people's
|
|
234
|
+
repos: key per-repo settings by `repo`, and let only the person whose handle
|
|
235
|
+
is `binderHandle` change them (compare it to `ctx.userInfo`).
|
|
236
|
+
`/installation/repositories` answers the same question from GitHub: with
|
|
237
|
+
repos from several installations it returns
|
|
238
|
+
`{ total_count, repositories }` holding only the repos connected to this
|
|
239
|
+
vibe, and ignores `per_page`/`page`.
|
|
240
|
+
|
|
185
241
|
- `{ db }` names the Fireproof database (same names `App.jsx` uses with
|
|
186
242
|
`useFireproof`). It is **required** — except inside `onChange`, where the
|
|
187
243
|
database that triggered the event is the default.
|
|
@@ -252,14 +308,15 @@ const gh = await ctx.github.fetch("/repos/acme/site/issues", { method: "POST", b
|
|
|
252
308
|
array operation (`.filter()`, `.map()`, spread) returns a plain array with no
|
|
253
309
|
`next`. On the last page the property is absent, not undefined — `"next" in
|
|
254
310
|
page` is `false`.
|
|
255
|
-
- Both reads are read-gated as the acting identity, through the same gate
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
311
|
+
- Both reads are read-gated as the acting identity, through the same gate. In
|
|
312
|
+
`onChange` and in a signed-in `fetch` a bound database reads **filtered to the
|
|
313
|
+
acting user's channels** — the same per-reader filter the client's own reads
|
|
314
|
+
use, so the handler sees exactly what that user could see: the triggering
|
|
315
|
+
user's neighbouring docs for dedup, rank and "don't repeat what's already
|
|
316
|
+
there" checks, or the calling user's own docs for a page's `apiFetch` route,
|
|
317
|
+
and nothing privileged. A doc outside those channels reads as `null` from
|
|
318
|
+
`get`, the same as one that isn't there. A `fetch` caller with no session
|
|
319
|
+
reads the databases `config.fetch.unfilteredReads` names. In
|
|
263
320
|
`scheduled` (admin mode) reads are
|
|
264
321
|
**unfiltered** — access-fn-bound databases return every doc, across all users
|
|
265
322
|
and channels. Made for `scheduled` sweeps: read, decide, then `put`/`delete`.
|
|
@@ -707,9 +764,23 @@ stays the signal for a bug to fix.
|
|
|
707
764
|
A connected GitHub repository's deliveries arrive here too, as a `POST` to
|
|
708
765
|
`/_api/github-webhook`, already verified by the platform, with the event name
|
|
709
766
|
in `x-vibes-github-event`, GitHub's delivery id in `x-vibes-github-delivery`
|
|
710
|
-
(key writes by it — a delivery can arrive twice),
|
|
711
|
-
|
|
712
|
-
|
|
767
|
+
(key writes by it — a delivery can arrive twice), the handle of the person
|
|
768
|
+
who connected that repo in `x-vibes-github-binder` (left off when they have no
|
|
769
|
+
handle), and an untrusted third-party body; a `4xx` ends the delivery, and a
|
|
770
|
+
throw marks it failed (GitHub does not retry it). `x-vibes-github-binder` is
|
|
771
|
+
how a vibe shared by many people knows whose repo an event belongs to, and so
|
|
772
|
+
whose settings apply. The platform sets it, the same as
|
|
773
|
+
`x-vibes-github-event`, so you can trust it: any `x-vibes-*` header a caller
|
|
774
|
+
sends is deleted before the platform adds its own. It matches the
|
|
775
|
+
`binderHandle` that `ctx.github.routes()` returns for that repo.
|
|
776
|
+
|
|
777
|
+
A delivery **runs as the person who connected the repo**: `ctx.userInfo` is
|
|
778
|
+
them, `ctx.db` writes pass `access.js` as them (`user.backend === "fetch"`,
|
|
779
|
+
`user.userHandle` equal to `x-vibes-github-binder`), reads see their channels,
|
|
780
|
+
and `ctx.callAI` runs on their credits. So a review bot keeps each person's
|
|
781
|
+
records in that person's channel, and every repo's activity is paid for by
|
|
782
|
+
the person who connected it. The consent page tells them so before they
|
|
783
|
+
connect.
|
|
713
784
|
|
|
714
785
|
`/_api` opens to every caller once the app is published. Before its first
|
|
715
786
|
publish, and after the owner unpublishes it, the routes answer the app's own
|
|
@@ -774,21 +845,43 @@ where the first stopped.
|
|
|
774
845
|
|
|
775
846
|
### `backend.js`
|
|
776
847
|
```js
|
|
777
|
-
export const config = {
|
|
848
|
+
export const config = {
|
|
849
|
+
fetch: {
|
|
850
|
+
unfilteredReads: {
|
|
851
|
+
dbs: ["rsvps"],
|
|
852
|
+
why: "/admin/normalize serves only the app owner, checked against ctx.appInfo.ownerHandle before it reads",
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
const STATE_ID = "state:normalize";
|
|
858
|
+
|
|
859
|
+
function json(data, status) {
|
|
860
|
+
return new Response(JSON.stringify(data), { status: status ?? 200, headers: { "content-type": "application/json" } });
|
|
861
|
+
}
|
|
778
862
|
|
|
779
863
|
export async function fetch(request, ctx) {
|
|
780
864
|
const url = new URL(request.url);
|
|
781
|
-
if (url.pathname !== "/admin/normalize") return
|
|
782
|
-
if (ctx.
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
865
|
+
if (url.pathname !== "/admin/normalize") return json({ ok: false, reason: "not found" }, 404);
|
|
866
|
+
if (ctx.userInfo?.userHandle !== ctx.appInfo.ownerHandle) return json({ ok: false, reason: "owner only" }, 403);
|
|
867
|
+
// Progress lives in one state document read by id, so a second run picks up where the first stopped.
|
|
868
|
+
const state = await ctx.db.get(STATE_ID, { db: "rsvps" });
|
|
869
|
+
if (state?.done === true) return json({ ok: true, fixed: 0 });
|
|
870
|
+
let after = state?.after ?? undefined;
|
|
871
|
+
let fixed = 0;
|
|
872
|
+
do {
|
|
873
|
+
const page = await ctx.db.query({ db: "rsvps", field: "kind", key: "rsvp", limit: 100, after });
|
|
874
|
+
for (const doc of page) {
|
|
875
|
+
if (typeof doc.name === "string" && doc.name !== doc.name.trim()) {
|
|
876
|
+
await ctx.db.put({ ...doc, name: doc.name.trim() }, { db: "rsvps", id: doc._id });
|
|
877
|
+
fixed += 1;
|
|
878
|
+
}
|
|
787
879
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
});
|
|
880
|
+
after = page.next; // absent on the last page
|
|
881
|
+
await ctx.db.put({ kind: "normalizeState", after: after ?? null, done: after === undefined }, { db: "rsvps", id: STATE_ID });
|
|
882
|
+
} while (after);
|
|
883
|
+
ctx.log("info", "normalized rsvp names", { fixed });
|
|
884
|
+
return json({ ok: true, fixed });
|
|
792
885
|
}
|
|
793
886
|
```
|
|
794
887
|
|
|
@@ -926,6 +1019,11 @@ export async function onChange(event, ctx) {
|
|
|
926
1019
|
}
|
|
927
1020
|
```
|
|
928
1021
|
|
|
1022
|
+
`fetch` reads the same way for a signed-in caller: when the page calls
|
|
1023
|
+
`apiFetch`, the handler runs as the visitor and a bound database returns the
|
|
1024
|
+
docs in their channels — the same set their page already shows them — so a
|
|
1025
|
+
route can sum, rank or reshape the caller's own docs server-side.
|
|
1026
|
+
|
|
929
1027
|
### Jobs that finish even when a run crashes
|
|
930
1028
|
|
|
931
1029
|
**A job that survives a crash carries its status, its claim and its lease on the document.**
|
package/llms/connections.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const connectionsConfig = {
|
|
2
2
|
name: "connections",
|
|
3
3
|
label: "Connections",
|
|
4
|
-
description: "Connect a visitor's own GitHub, Threads or Instagram account with one click and read it from backend.js — config.connections, useConnection, @vibes.diy/social",
|
|
4
|
+
description: "Connect a visitor's own GitHub profile, Threads or Instagram account with one click and read it from backend.js — config.connections, useConnection, @vibes.diy/social",
|
|
5
5
|
cues: [
|
|
6
6
|
"connect my",
|
|
7
7
|
"sign in with github",
|
package/llms/connections.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../jsr/llms/connections.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../jsr/llms/connections.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,WAAW,EACT,wKAAwK;IAC1K,IAAI,EAAE;QACJ,YAAY;QACZ,qBAAqB;QACrB,wBAAwB;QACxB,sBAAsB;QACtB,cAAc;QACd,YAAY;QACZ,WAAW;QACX,UAAU;QACV,cAAc;QACd,oBAAoB;QACpB,WAAW;QACX,SAAS;QACT,gBAAgB;KACjB;IACD,YAAY,EAAE,WAAW;IACzB,UAAU,EAAE,eAAe;CAC5B,CAAC"}
|
package/llms/connections.md
CHANGED
|
@@ -23,6 +23,11 @@ A request that names one of the providers below and wants **their** data is a
|
|
|
23
23
|
connection. A request for public data nobody has to log in for (a public feed,
|
|
24
24
|
a public profile) is an ordinary `ctx.fetch` and needs no connection.
|
|
25
25
|
|
|
26
|
+
A request about somebody's GitHub **repos** — pull requests, issues, commits,
|
|
27
|
+
files, a bot that answers GitHub events — connects the repo through the
|
|
28
|
+
vibes.diy GitHub App instead: load the `github` skill. The GitHub row below is
|
|
29
|
+
the visitor's profile.
|
|
30
|
+
|
|
26
31
|
## The three moving parts
|
|
27
32
|
|
|
28
33
|
**Declare the provider in `config.connections`, render the Connect button from
|
|
@@ -66,7 +71,7 @@ refuses the call, not because posting is forbidden but because nobody agreed to
|
|
|
66
71
|
it.
|
|
67
72
|
|
|
68
73
|
**GitHub is read-only on this lane** — there is no GitHub write scope to
|
|
69
|
-
declare. Instagram views and plays (the `insights` edge) are not available
|
|
74
|
+
declare; reading and writing a repo is the `github` skill's GitHub App lane. Instagram views and plays (the `insights` edge) are not available
|
|
70
75
|
either. Build the app around what the table gives you; do not add a token field
|
|
71
76
|
for the rest.
|
|
72
77
|
|
package/llms/github.d.ts
ADDED
package/llms/github.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const githubConfig = {
|
|
2
|
+
name: "github",
|
|
3
|
+
label: "GitHub repos",
|
|
4
|
+
description: "Work with somebody's GitHub repo — a pull request bot, a code reviewer, an issue sorter, a repo dashboard, anything reading files, commits or a README or answering GitHub webhooks — through the vibes.diy GitHub App: a Connect link to /github/install, config.github in backend.js, ctx.github.fetch for repo calls and the /github-webhook route for events (a visitor's GitHub profile alone is connections)",
|
|
5
|
+
cues: [
|
|
6
|
+
"my repo",
|
|
7
|
+
"github repo",
|
|
8
|
+
"pull request",
|
|
9
|
+
"pull requests",
|
|
10
|
+
"PR",
|
|
11
|
+
"PRs",
|
|
12
|
+
"code review",
|
|
13
|
+
"issues",
|
|
14
|
+
"readme",
|
|
15
|
+
"commits",
|
|
16
|
+
"github bot",
|
|
17
|
+
"webhook from github",
|
|
18
|
+
"github webhook",
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../jsr/llms/github.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,oZAAoZ;IACtZ,IAAI,EAAE;QACJ,SAAS;QACT,aAAa;QACb,cAAc;QACd,eAAe;QACf,IAAI;QACJ,KAAK;QACL,aAAa;QACb,QAAQ;QACR,QAAQ;QACR,SAAS;QACT,YAAY;QACZ,qBAAqB;QACrB,gBAAgB;KACjB;CACF,CAAC"}
|
package/llms/github.md
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
# GitHub — connect a repo to your app
|
|
2
|
+
|
|
3
|
+
An app that works with somebody's GitHub repo — answers its pull requests,
|
|
4
|
+
sorts its issues, reads its files, shows its commits — connects that repo in
|
|
5
|
+
one click through the **vibes.diy GitHub App**. The person presses Connect,
|
|
6
|
+
picks repos on GitHub's own screen, and lands back in your app. From then on
|
|
7
|
+
`backend.js` reads and writes those repos with `ctx.github.fetch`, and GitHub's
|
|
8
|
+
events for them arrive at your `/_api/github-webhook` route.
|
|
9
|
+
|
|
10
|
+
**The platform's GitHub App holds the credential, so the app carries no GitHub key of its own.**
|
|
11
|
+
The whole integration is a link, a declaration and `ctx.github.fetch`; the
|
|
12
|
+
person's consent and the credential both live on the platform. (An app built
|
|
13
|
+
without this skill asked its owner to register a GitHub OAuth App and paste its
|
|
14
|
+
client secret into the vibe's secrets — the GitHub App replaces every step of
|
|
15
|
+
that.)
|
|
16
|
+
|
|
17
|
+
## Which lane — signal words
|
|
18
|
+
|
|
19
|
+
| The ask | Lane | What you write |
|
|
20
|
+
| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| "my repo", "my GitHub repo", pull requests, PRs, code review, issues, commits, files, a README, "a GitHub bot", "when someone opens a PR" | **The repo lane — this skill** | the `/github/install` link, `config.github`, `ctx.github.fetch`, the `/github-webhook` route |
|
|
22
|
+
| the visitor's GitHub **profile** — their name, avatar, bio next to their entries | **Connections** (`connections` skill) | `config.connections` with `provider: "github"`, `scopes: ["read:user"]`, and `useConnection` |
|
|
23
|
+
| public GitHub data nobody signs in for — stars or releases of a public repo, a public user's repos | **Plain `ctx.fetch`** | `ctx.fetch("https://api.github.com/repos/nodejs/node")` in `backend.js`, no connect step |
|
|
24
|
+
|
|
25
|
+
Repos, pull requests and issues always ride the repo lane, even when the ask
|
|
26
|
+
also says "sign in with GitHub": the Connections lane carries the profile, and
|
|
27
|
+
the GitHub App carries the repos. An app can use both side by side.
|
|
28
|
+
|
|
29
|
+
## The three pieces
|
|
30
|
+
|
|
31
|
+
### 1. Declare it — `backend.js`
|
|
32
|
+
|
|
33
|
+
**`backend.js` declares `config.github` with `bind` and a `why` sentence** the
|
|
34
|
+
visitor reads on the platform's consent page before GitHub opens:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
export const config = {
|
|
38
|
+
github: {
|
|
39
|
+
bind: "anyone",
|
|
40
|
+
why: "Leaves a short note on each new pull request in the repos you pick.",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `bind: "anyone"` lets every signed-in visitor connect their **own** repos —
|
|
46
|
+
the shape for a tool other people use (a reviewer, a triager, a board anyone
|
|
47
|
+
brings their repos to). `bind: "owner"` (the default) keeps connecting to the
|
|
48
|
+
app's owner — the shape for one team's own repo.
|
|
49
|
+
- `why` is one static sentence, 280 characters at most, written to the visitor
|
|
50
|
+
in the app's own voice ("Sorts the issues people open in your repos with a
|
|
51
|
+
label."). It is required with `"anyone"`; write it for `"owner"` too, so a
|
|
52
|
+
remix that opens up to visitors already says what it does.
|
|
53
|
+
- A malformed `config.github` refuses the push, with a message naming the field.
|
|
54
|
+
|
|
55
|
+
### 2. Connect it — `App.jsx`
|
|
56
|
+
|
|
57
|
+
**The Connect button is a link to `buildGithubInstallUrl()` from `use-vibes`, opened in a new tab**
|
|
58
|
+
with `target="_blank" rel="noopener"`. `buildGithubInstallUrl()` takes no
|
|
59
|
+
arguments and returns the platform's absolute
|
|
60
|
+
`/github/install?vibe=<ownerHandle>/<appSlug>` address for the app that is
|
|
61
|
+
running — on `https://vibes.diy`, or on the preview the app runs under — so the
|
|
62
|
+
link stays right in every copy and remix of the app. The app runs on its own
|
|
63
|
+
subdomain, so a path starting at `/github/install` would point at the app
|
|
64
|
+
itself, and the new tab gives the platform's consent page a whole window of its
|
|
65
|
+
own:
|
|
66
|
+
|
|
67
|
+
```jsx
|
|
68
|
+
import { buildGithubInstallUrl } from "use-vibes";
|
|
69
|
+
|
|
70
|
+
<a href={buildGithubInstallUrl()} target="_blank" rel="noopener">Connect a GitHub repo</a>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The app's owner goes straight to GitHub. A visitor first sees the platform's
|
|
74
|
+
consent page quoting your `why`; its Continue button carries the visitor on to
|
|
75
|
+
GitHub's install screen, where they pick the repos. **The platform builds every
|
|
76
|
+
step after `/github/install` itself, so the app's one link is
|
|
77
|
+
`buildGithubInstallUrl()`** — GitHub's own install page is reached only through
|
|
78
|
+
that door. When the new tab finishes, the person comes back to the app's tab,
|
|
79
|
+
so re-read the repo list when the window regains focus.
|
|
80
|
+
Connecting needs a signed-in visitor, so a signed-out visitor gets the sign-in
|
|
81
|
+
ask (`requestLogin` from `useViewer`) in that spot.
|
|
82
|
+
|
|
83
|
+
### 3. Call and listen — `backend.js`
|
|
84
|
+
|
|
85
|
+
**Every call to a connected repo goes through `ctx.github.fetch`** — GitHub's
|
|
86
|
+
REST API, called as the GitHub App installation that covers that repo:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
const res = await ctx.github.fetch(`/repos/${repo}/pulls/${number}/files?per_page=100`);
|
|
90
|
+
const files = await res.json();
|
|
91
|
+
await ctx.github.fetch(`/repos/${repo}/pulls/${number}/reviews`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
body: { commit_id: sha, event: "COMMENT", body: "Looks tidy." },
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`ctx.github.fetch` attaches the App's credential itself, so a repo call carries only its path, method and body.
|
|
98
|
+
`path` starts at `/`: any `/repos/{owner}/{repo}/…` path for a repo connected to
|
|
99
|
+
this app, plus `/installation/repositories`. Methods are GET, POST, PUT, PATCH
|
|
100
|
+
and DELETE; an object `body` is sent as JSON. It resolves a normal `Response`
|
|
101
|
+
(GitHub's own 404 or 422 included — read `res.ok` and `res.status`), and it
|
|
102
|
+
rejects with an `Error` whose `err.reason` names what happened:
|
|
103
|
+
`not_connected` (no repo connected yet), `unmodeled_endpoint` (a path outside
|
|
104
|
+
those, or a repo this app has not connected), `permission_not_granted`,
|
|
105
|
+
`unsupported_method`, `body_too_large`, `upstream_error`. Branch on
|
|
106
|
+
`err.reason` and tell the person which one it was.
|
|
107
|
+
|
|
108
|
+
**`ctx.github.routes()` lists the connected repos and who connected each** —
|
|
109
|
+
`[{ repo, accountLogin, binderHandle, installationId }]`, spending no GitHub
|
|
110
|
+
call. It is how a route answers "which of these repos are mine?": keep the rows
|
|
111
|
+
whose `binderHandle` is `ctx.userInfo.userHandle`. Check a repo name the page
|
|
112
|
+
sends against that list before building a path from it.
|
|
113
|
+
|
|
114
|
+
**A delivery arrives as `POST /github-webhook` with the event name in `x-vibes-github-event`**
|
|
115
|
+
(`pull_request`, `issues`, `push`, …) and GitHub's delivery id in
|
|
116
|
+
`x-vibes-github-delivery`. The platform has already verified it came from
|
|
117
|
+
GitHub, so the headers are trustworthy and the body is GitHub's payload.
|
|
118
|
+
|
|
119
|
+
- **A delivery runs as the person who connected the repo.** `ctx.userInfo` is
|
|
120
|
+
them, and its `ctx.db.put` writes pass `access.js` as them, with
|
|
121
|
+
`user.backend === "fetch"` — so a delivery can keep its own records in that
|
|
122
|
+
person's channel, and the page shows them live.
|
|
123
|
+
- **Claim each unit of work before spending on it.** GitHub can deliver the
|
|
124
|
+
same event twice, and a pull request is pushed to many times. Put a record
|
|
125
|
+
whose `_id` names the work — the repo, the number and the head commit, or the
|
|
126
|
+
delivery id — with `ifRev: null`; the first delivery writes it, and a repeat
|
|
127
|
+
meets `err.code === "conflict"` and answers `200` having done nothing.
|
|
128
|
+
- A `4xx` answer ends the delivery; a thrown handler marks it failed.
|
|
129
|
+
|
|
130
|
+
**Automatic work starts with the repo's team: read `author_association` off the delivery.**
|
|
131
|
+
A delivery runs as the person who connected the repo, so the work it takes on
|
|
132
|
+
by itself spends their credits (`ctx.callAI` included) and writes under their
|
|
133
|
+
name, and on a public repo anyone can open a pull request. GitHub stamps every
|
|
134
|
+
pull request, issue and comment with its author's place in the repo, so the
|
|
135
|
+
delivery already carries the answer, with no extra call:
|
|
136
|
+
`payload.pull_request.author_association`, `payload.issue.author_association`,
|
|
137
|
+
`payload.comment.author_association`. `OWNER`, `MEMBER` and `COLLABORATOR` are
|
|
138
|
+
the repo's team; every other value (`CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`,
|
|
139
|
+
`FIRST_TIMER`, `NONE`, `MANNEQUIN`) is anyone.
|
|
140
|
+
|
|
141
|
+
- **Each repo gets a `who` setting, `"team"` or `"anyone"`, starting at
|
|
142
|
+
`"team"`.** The person who connected the repo saves it, and the page shows it
|
|
143
|
+
as two plain choices: **People on the repo's team** and **Anyone who opens a
|
|
144
|
+
pull request** (for an issue bot, **Anyone who opens an issue**). An app whose
|
|
145
|
+
whole job is greeting newcomers starts at `"anyone"`, with the same two
|
|
146
|
+
choices.
|
|
147
|
+
- **Work from beyond the chosen circle waits for its person.** The delivery
|
|
148
|
+
records it in their channel, so it shows up live on their page, with a
|
|
149
|
+
one-tap **Review it now**: an `apiFetch` route, so the work runs as them and
|
|
150
|
+
the pull request hears from the app the moment they press it.
|
|
151
|
+
- Issue and comment bots read the same stamp off `payload.issue` and
|
|
152
|
+
`payload.comment`, and honour the same setting.
|
|
153
|
+
|
|
154
|
+
**Opening the app and spending on the work are separate dials.** An app can be
|
|
155
|
+
open for anyone to visit and read, while its costly work (a `ctx.callAI`
|
|
156
|
+
review, a comment posted to GitHub) runs only for the people who chose it. Set
|
|
157
|
+
each dial on its own:
|
|
158
|
+
|
|
159
|
+
| Dial | Who it decides | Where it lives |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| Open the app and read its records | visitors | publishing (apps are public by default) and `access.js` channels and grants |
|
|
162
|
+
| Connect a repo | signed-in visitors | `config.github.bind` |
|
|
163
|
+
| Set off the costly work | pull request, issue and comment authors, and page callers | the repo's `who` setting on deliveries; a `ctx.userInfo` check against `binderHandle` on every route that spends |
|
|
164
|
+
|
|
165
|
+
So a review bot stays published and public, with a landing page that explains
|
|
166
|
+
it and a shared board that anyone can browse, while a stranger's pull request
|
|
167
|
+
or an anonymous page visit sets off no AI call. A route that only reads (a
|
|
168
|
+
public board, the page's own listing) spends nothing and can answer anyone. A
|
|
169
|
+
route that spends (`ctx.callAI`, a POST through `ctx.github.fetch`) first
|
|
170
|
+
checks that `ctx.userInfo?.userHandle` is the `binderHandle` of the repo it
|
|
171
|
+
touches, and answers everyone else with a plain `403` reason. Keep the app
|
|
172
|
+
open; put the gate on the spend.
|
|
173
|
+
|
|
174
|
+
## A complete example — pull request size notes
|
|
175
|
+
|
|
176
|
+
Each new pull request in a connected repo gets a short review comment saying
|
|
177
|
+
how many lines it changes; past a size the person picks, the note adds where to
|
|
178
|
+
start reading. Anyone can connect their own repos, set their own size, and see
|
|
179
|
+
their notes land live. Pull requests from the repo's team get their note right
|
|
180
|
+
away; one from anyone else waits on the person's page with **Review it now**,
|
|
181
|
+
until they switch the repo to **Anyone who opens a pull request**.
|
|
182
|
+
|
|
183
|
+
### `backend.js`
|
|
184
|
+
```js app=size-notes
|
|
185
|
+
export const config = {
|
|
186
|
+
github: {
|
|
187
|
+
bind: "anyone",
|
|
188
|
+
why: "Leaves a short note on each new pull request in the repos you pick, saying how big the change is.",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const DB = "sizeNotes";
|
|
193
|
+
const DEFAULT_BIG_AT = 400;
|
|
194
|
+
const PR_ACTIONS = ["opened", "reopened", "synchronize"];
|
|
195
|
+
// The author_association values GitHub gives the people on a repo's team.
|
|
196
|
+
const TEAM = ["OWNER", "MEMBER", "COLLABORATOR"];
|
|
197
|
+
const DEFAULT_WHO = "team";
|
|
198
|
+
|
|
199
|
+
function json(data, status) {
|
|
200
|
+
return new Response(JSON.stringify(data), { status: status ?? 200, headers: { "content-type": "application/json" } });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function settingsId(repo) {
|
|
204
|
+
return `settings:${repo.toLowerCase()}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The repos the signed-in caller connected to this app.
|
|
208
|
+
async function myRepos(ctx) {
|
|
209
|
+
const me = ctx.userInfo?.userHandle;
|
|
210
|
+
if (me === undefined) return [];
|
|
211
|
+
const routes = await ctx.github.routes();
|
|
212
|
+
return routes.filter((r) => r.binderHandle === me);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function writeNote(ctx, repo, pr, bigAt) {
|
|
216
|
+
const res = await ctx.github.fetch(`/repos/${repo}/pulls/${pr.number}/files?per_page=100`);
|
|
217
|
+
if (res.ok === false) return { status: "failed", reason: `GitHub answered ${res.status} for the file list` };
|
|
218
|
+
const files = await res.json();
|
|
219
|
+
const lines = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
|
220
|
+
const largest = [...files].sort((a, b) => b.changes - a.changes)[0];
|
|
221
|
+
const body =
|
|
222
|
+
lines >= bigAt && largest !== undefined
|
|
223
|
+
? `This pull request changes ${lines} lines across ${files.length} files. A good place to start reading: \`${largest.filename}\`.`
|
|
224
|
+
: `This pull request changes ${lines} lines across ${files.length} files.`;
|
|
225
|
+
const posted = await ctx.github.fetch(`/repos/${repo}/pulls/${pr.number}/reviews`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
body: { commit_id: pr.head.sha, event: "COMMENT", body },
|
|
228
|
+
});
|
|
229
|
+
if (posted.ok === false) return { status: "failed", reason: `GitHub answered ${posted.status} to the note` };
|
|
230
|
+
const review = await posted.json();
|
|
231
|
+
return { status: "done", lines, files: files.length, reviewUrl: review.html_url };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function noteId(repo, pr) {
|
|
235
|
+
return `note:${repo.toLowerCase()}#${pr.number}@${pr.head.sha}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function noteRecord(repo, pr, binderHandle, status) {
|
|
239
|
+
return { type: "note", repo, number: pr.number, title: pr.title, url: pr.html_url, author: pr.user?.login, binderHandle, status, at: Date.now() };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The person's `who` setting decides whose pull requests get a note by themselves.
|
|
243
|
+
function takesOn(settings, pr) {
|
|
244
|
+
const who = settings?.who ?? DEFAULT_WHO;
|
|
245
|
+
return who === "anyone" || TEAM.includes(pr.author_association);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function finishNote(ctx, id, repo, pr, bigAt) {
|
|
249
|
+
const outcome = await writeNote(ctx, repo, pr, bigAt).catch((err) => ({
|
|
250
|
+
status: "failed",
|
|
251
|
+
reason: err.reason === "permission_not_granted" ? "the GitHub App needs permission to review pull requests" : String(err.reason ?? err.message),
|
|
252
|
+
}));
|
|
253
|
+
const claimed = await ctx.db.get(id, { db: DB });
|
|
254
|
+
await ctx.db.put({ ...claimed, ...outcome }, { db: DB, id, ifRev: claimed._rev });
|
|
255
|
+
return { ok: outcome.status === "done", ...outcome };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function noteOnPull(ctx, repo, pr, binderHandle) {
|
|
259
|
+
const settings = await ctx.db.get(settingsId(repo), { db: DB });
|
|
260
|
+
// From the team, or the person opened it to anyone: the note runs now. Otherwise it waits for them.
|
|
261
|
+
const status = takesOn(settings, pr) ? "running" : "waiting";
|
|
262
|
+
// One record per pull request and head commit: the first delivery claims it, a repeat steps aside.
|
|
263
|
+
const id = noteId(repo, pr);
|
|
264
|
+
try {
|
|
265
|
+
await ctx.db.put(noteRecord(repo, pr, binderHandle, status), { db: DB, id, ifRev: null });
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (err.code === "conflict") return { ok: true, skipped: "already-claimed" };
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
if (status === "waiting") return { ok: true, waiting: pr.author_association };
|
|
271
|
+
return finishNote(ctx, id, repo, pr, settings?.bigAt ?? DEFAULT_BIG_AT);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// "Review it now": the person who connected the repo takes on a waiting pull request, as themselves.
|
|
275
|
+
async function reviewNow(request, ctx) {
|
|
276
|
+
const input = await request.json();
|
|
277
|
+
const mine = await myRepos(ctx);
|
|
278
|
+
const bound = mine.find((r) => r.repo.toLowerCase() === String(input.repo).toLowerCase());
|
|
279
|
+
if (bound === undefined) return json({ ok: false, reason: "Connect this repo first, and its pull requests open here." }, 404);
|
|
280
|
+
const res = await ctx.github.fetch(`/repos/${bound.repo}/pulls/${Number(input.number)}`);
|
|
281
|
+
if (res.ok === false) return json({ ok: false, reason: `GitHub answered ${res.status} for that pull request` }, 422);
|
|
282
|
+
const pr = await res.json();
|
|
283
|
+
const id = noteId(bound.repo, pr);
|
|
284
|
+
const held = await ctx.db.get(id, { db: DB });
|
|
285
|
+
if (held !== null && held.status !== "waiting") return json({ ok: true, skipped: "already-claimed" });
|
|
286
|
+
const record = held === null ? noteRecord(bound.repo, pr, bound.binderHandle, "running") : { ...held, status: "running" };
|
|
287
|
+
try {
|
|
288
|
+
await ctx.db.put(record, { db: DB, id, ifRev: held === null ? null : held._rev });
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (err.code === "conflict") return json({ ok: true, skipped: "already-claimed" });
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
const settings = await ctx.db.get(settingsId(bound.repo), { db: DB });
|
|
294
|
+
const outcome = await finishNote(ctx, id, bound.repo, pr, settings?.bigAt ?? DEFAULT_BIG_AT);
|
|
295
|
+
return json(outcome, outcome.ok ? 200 : 422);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function onDelivery(request, ctx) {
|
|
299
|
+
const event = request.headers.get("x-vibes-github-event");
|
|
300
|
+
if (event !== "pull_request") return json({ ok: true, ignored: event });
|
|
301
|
+
const payload = await request.json();
|
|
302
|
+
if (PR_ACTIONS.includes(payload.action) === false) return json({ ok: true, ignored: payload.action });
|
|
303
|
+
const repo = payload.repository?.full_name;
|
|
304
|
+
const pr = payload.pull_request;
|
|
305
|
+
if (typeof repo !== "string" || pr === undefined) return json({ ok: false, reason: "this delivery carries no pull request" }, 400);
|
|
306
|
+
// The delivery runs as the person who connected this repo.
|
|
307
|
+
const binderHandle = ctx.userInfo?.userHandle;
|
|
308
|
+
if (binderHandle === undefined) return json({ ok: false, reason: "this delivery arrived without its person" }, 422);
|
|
309
|
+
ctx.log("info", "pull request", {
|
|
310
|
+
repo,
|
|
311
|
+
number: pr.number,
|
|
312
|
+
author: pr.author_association,
|
|
313
|
+
delivery: request.headers.get("x-vibes-github-delivery"),
|
|
314
|
+
});
|
|
315
|
+
const outcome = await noteOnPull(ctx, repo, pr, binderHandle);
|
|
316
|
+
return json(outcome, outcome.ok || outcome.skipped ? 200 : 422);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function listRepos(ctx) {
|
|
320
|
+
const rows = [];
|
|
321
|
+
for (const r of await myRepos(ctx)) {
|
|
322
|
+
const settings = await ctx.db.get(settingsId(r.repo), { db: DB });
|
|
323
|
+
rows.push({ repo: r.repo, bigAt: settings?.bigAt ?? DEFAULT_BIG_AT, who: settings?.who ?? DEFAULT_WHO });
|
|
324
|
+
}
|
|
325
|
+
return json({ ok: true, repos: rows });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function saveSettings(request, ctx) {
|
|
329
|
+
const input = await request.json();
|
|
330
|
+
const mine = await myRepos(ctx);
|
|
331
|
+
const bound = mine.find((r) => r.repo.toLowerCase() === String(input.repo).toLowerCase());
|
|
332
|
+
if (bound === undefined) return json({ ok: false, reason: "Connect this repo first, and its settings open here." }, 404);
|
|
333
|
+
const bigAt = Math.max(1, Math.round(Number(input.bigAt) || DEFAULT_BIG_AT));
|
|
334
|
+
const who = input.who === "anyone" ? "anyone" : "team";
|
|
335
|
+
const existing = await ctx.db.get(settingsId(bound.repo), { db: DB });
|
|
336
|
+
await ctx.db.put(
|
|
337
|
+
{ type: "settings", repo: bound.repo, binderHandle: bound.binderHandle, bigAt, who },
|
|
338
|
+
{ db: DB, id: settingsId(bound.repo), ifRev: existing === null ? null : existing._rev }
|
|
339
|
+
);
|
|
340
|
+
return json({ ok: true, bigAt, who });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function fetch(request, ctx) {
|
|
344
|
+
const route = `${request.method} ${new URL(request.url).pathname}`;
|
|
345
|
+
const run = async () => {
|
|
346
|
+
if (route === "POST /github-webhook") return onDelivery(request, ctx);
|
|
347
|
+
if (route === "GET /repos") return listRepos(ctx);
|
|
348
|
+
if (route === "POST /settings") return saveSettings(request, ctx);
|
|
349
|
+
if (route === "POST /review") return reviewNow(request, ctx);
|
|
350
|
+
return json({ ok: false, reason: "not found" }, 404);
|
|
351
|
+
};
|
|
352
|
+
return run().catch((err) => {
|
|
353
|
+
ctx.log("error", "route failed", { route, reason: err.reason ?? err.code, message: String(err.message ?? err) });
|
|
354
|
+
return json({ ok: false, reason: err.reason ?? "error" }, err.reason === "not_connected" ? 409 : 500);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### `access.js`
|
|
360
|
+
```js app=size-notes
|
|
361
|
+
// Notes and settings are written by backend.js: settings by POST /settings, notes by the GitHub
|
|
362
|
+
// delivery (which runs as the person who connected the repo) and by POST /review. Each lands in
|
|
363
|
+
// that person's channel.
|
|
364
|
+
export default function (doc, oldDoc, user, ctx) {
|
|
365
|
+
if (doc.type === "note" || doc.type === "settings") {
|
|
366
|
+
ctx.requireBackend("fetch");
|
|
367
|
+
const binder = doc.binderHandle;
|
|
368
|
+
if (typeof binder !== "string" || binder === "") throw { forbidden: "missing binderHandle" };
|
|
369
|
+
// write-once: a record stays with the person who connected its repo
|
|
370
|
+
if (oldDoc && oldDoc.binderHandle !== binder) throw { forbidden: "binderHandle is fixed" };
|
|
371
|
+
if (binder !== user.userHandle) throw { forbidden: "not your repo" };
|
|
372
|
+
const ch = `repo-of:${binder}`;
|
|
373
|
+
return { channels: [ch], grant: { users: { [binder]: [ch] } } };
|
|
374
|
+
}
|
|
375
|
+
throw { forbidden: "unknown document type" };
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### `App.jsx`
|
|
380
|
+
```jsx app=size-notes
|
|
381
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
382
|
+
import { apiFetch, buildGithubInstallUrl, useViewer } from "use-vibes";
|
|
383
|
+
import { useFireproof } from "use-fireproof";
|
|
384
|
+
import { Badge, Button, Field, Heading, Input, RowCard, Skeleton, Stack, Text } from "@vibes.diy/pouf";
|
|
385
|
+
import { RadioGroup } from "@vibes.diy/pouf/interactive";
|
|
386
|
+
|
|
387
|
+
const TONE = { done: "up", failed: "warn", running: "info", waiting: "info" };
|
|
388
|
+
const WHO_CHOICES = [
|
|
389
|
+
{ value: "team", label: "People on the repo's team" },
|
|
390
|
+
{ value: "anyone", label: "Anyone who opens a pull request" },
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
function badgeText(n) {
|
|
394
|
+
if (n.status === "done") return `${n.lines} lines`;
|
|
395
|
+
if (n.status === "failed") return n.reason;
|
|
396
|
+
if (n.status === "waiting") return `From ${n.author ?? "someone new"}`;
|
|
397
|
+
return "Writing…";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function NoteRow({ note }) {
|
|
401
|
+
const [busy, setBusy] = useState(false);
|
|
402
|
+
|
|
403
|
+
// Runs as the person who connected the repo; the record turns to "done" live when the note lands.
|
|
404
|
+
async function reviewNow() {
|
|
405
|
+
setBusy(true);
|
|
406
|
+
await apiFetch("/review", {
|
|
407
|
+
method: "POST",
|
|
408
|
+
headers: { "content-type": "application/json" },
|
|
409
|
+
body: JSON.stringify({ repo: note.repo, number: note.number }),
|
|
410
|
+
});
|
|
411
|
+
setBusy(false);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<RowCard>
|
|
416
|
+
<a className="underline" href={note.reviewUrl ?? note.url} target="_blank" rel="noreferrer">
|
|
417
|
+
#{note.number} {note.title}
|
|
418
|
+
</a>
|
|
419
|
+
<Badge tone={TONE[note.status] ?? "info"}>{badgeText(note)}</Badge>
|
|
420
|
+
{note.status === "waiting" && (
|
|
421
|
+
<Button type="button" size="sm" loading={busy} onClick={reviewNow}>
|
|
422
|
+
Review it now
|
|
423
|
+
</Button>
|
|
424
|
+
)}
|
|
425
|
+
</RowCard>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// The live ledger: each note record is written by the delivery itself, so new ones appear as
|
|
430
|
+
// pull requests open.
|
|
431
|
+
function RecentNotes({ repo }) {
|
|
432
|
+
const { useLiveQuery } = useFireproof("sizeNotes");
|
|
433
|
+
const { docs } = useLiveQuery("repo", { key: repo });
|
|
434
|
+
const notes = docs.filter((d) => d.type === "note").sort((a, b) => b.at - a.at).slice(0, 10);
|
|
435
|
+
if (notes.length === 0) return <Text muted>Open a pull request and its note shows up here and on GitHub.</Text>;
|
|
436
|
+
return (
|
|
437
|
+
<Stack gap={2}>
|
|
438
|
+
{notes.map((n) => (
|
|
439
|
+
<NoteRow key={n._id} note={n} />
|
|
440
|
+
))}
|
|
441
|
+
</Stack>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function RepoPanel({ row }) {
|
|
446
|
+
const [bigAt, setBigAt] = useState(String(row.bigAt));
|
|
447
|
+
const [who, setWho] = useState(row.who);
|
|
448
|
+
const [message, setMessage] = useState("");
|
|
449
|
+
|
|
450
|
+
async function save() {
|
|
451
|
+
setMessage("Saving…");
|
|
452
|
+
const res = await apiFetch("/settings", {
|
|
453
|
+
method: "POST",
|
|
454
|
+
headers: { "content-type": "application/json" },
|
|
455
|
+
body: JSON.stringify({ repo: row.repo, bigAt: Number(bigAt), who }),
|
|
456
|
+
});
|
|
457
|
+
const body = await res.json();
|
|
458
|
+
setMessage(res.ok ? "Saved. The next pull request uses it." : body.reason);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<Stack gap={3}>
|
|
463
|
+
<Heading level={2}>
|
|
464
|
+
<a href={`https://github.com/${row.repo}`} target="_blank" rel="noreferrer">
|
|
465
|
+
{row.repo}
|
|
466
|
+
</a>
|
|
467
|
+
</Heading>
|
|
468
|
+
<Field label="Suggest where to start reading at this many changed lines">
|
|
469
|
+
{(id, describedBy) => <Input id={id} describedBy={describedBy} value={bigAt} onChange={setBigAt} />}
|
|
470
|
+
</Field>
|
|
471
|
+
<RadioGroup label="Whose pull requests get a note right away" value={who} onChange={setWho} options={WHO_CHOICES} />
|
|
472
|
+
<Button type="button" size="sm" onClick={save}>
|
|
473
|
+
Save
|
|
474
|
+
</Button>
|
|
475
|
+
{message !== "" && <Text size="sm" muted>{message}</Text>}
|
|
476
|
+
<RecentNotes repo={row.repo} />
|
|
477
|
+
</Stack>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export default function App() {
|
|
482
|
+
const { viewer, isViewerPending, requestLogin } = useViewer();
|
|
483
|
+
const [state, setState] = useState({ loading: true, repos: [] });
|
|
484
|
+
|
|
485
|
+
const load = useCallback(async () => {
|
|
486
|
+
const res = await apiFetch("/repos");
|
|
487
|
+
const body = await res.json();
|
|
488
|
+
setState({ loading: false, repos: body.repos ?? [] });
|
|
489
|
+
}, []);
|
|
490
|
+
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (isViewerPending === false) load();
|
|
493
|
+
}, [isViewerPending, viewer, load]);
|
|
494
|
+
|
|
495
|
+
// Connecting finishes in the tab the link opened; re-read the list when the person comes back.
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
window.addEventListener("focus", load);
|
|
498
|
+
return () => window.removeEventListener("focus", load);
|
|
499
|
+
}, [load]);
|
|
500
|
+
|
|
501
|
+
return (
|
|
502
|
+
<main className="min-h-screen bg-background text-foreground font-sans mx-auto max-w-2xl p-6">
|
|
503
|
+
<Stack gap={5}>
|
|
504
|
+
<Heading level={1}>Pull request size notes</Heading>
|
|
505
|
+
<Text>
|
|
506
|
+
Connect a GitHub repo and each new pull request gets a short note on how big it is, with a place to start reading when it runs long. Your team's pull
|
|
507
|
+
requests get theirs right away, and the rest are one tap away here.
|
|
508
|
+
</Text>
|
|
509
|
+
{viewer && (
|
|
510
|
+
<a className="inline-block rounded-md bg-foreground px-4 py-2 font-bold text-background" href={buildGithubInstallUrl()} target="_blank" rel="noopener">
|
|
511
|
+
Connect a GitHub repo
|
|
512
|
+
</a>
|
|
513
|
+
)}
|
|
514
|
+
{isViewerPending === false && !viewer && (
|
|
515
|
+
<Button type="button" onClick={requestLogin}>
|
|
516
|
+
Sign in to connect a repo
|
|
517
|
+
</Button>
|
|
518
|
+
)}
|
|
519
|
+
{state.loading ? <Skeleton /> : state.repos.map((row) => <RepoPanel key={row.repo} row={row} />)}
|
|
520
|
+
</Stack>
|
|
521
|
+
</main>
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
`GET /repos`, `POST /settings` and the delivery each run as a signed-in
|
|
527
|
+
person, so their `ctx.db` reads of `sizeNotes` come back filtered to that
|
|
528
|
+
person's own channels — the same records their page sees — with no extra
|
|
529
|
+
config. Before writing, a route still checks the repo against the ones
|
|
530
|
+
`ctx.github.routes()` says the caller connected. A pull request from beyond
|
|
531
|
+
the repo's `who` setting is claimed as a `waiting` record, and **Review it
|
|
532
|
+
now** calls `POST /review`, which runs the same note as the person who pressed
|
|
533
|
+
it. The page reads its notes
|
|
534
|
+
through `useLiveQuery`, which shows each person the records in their own
|
|
535
|
+
channel.
|
|
536
|
+
|
|
537
|
+
## Good to know
|
|
538
|
+
|
|
539
|
+
- **The page calls its routes with `apiFetch`** from `use-vibes`, so the route
|
|
540
|
+
runs as the signed-in visitor and `ctx.userInfo` says whose repos to list.
|
|
541
|
+
- **The repo's team comes first.** Every app that acts on pull requests,
|
|
542
|
+
issues or comments by itself reads `author_association` and starts each repo
|
|
543
|
+
at `who: "team"` (`OWNER`, `MEMBER`, `COLLABORATOR`), with a two-choice
|
|
544
|
+
control that opens it to anyone. Everything else waits on the connector's
|
|
545
|
+
page with **Review it now**, so their credits and their name go where they
|
|
546
|
+
choose.
|
|
547
|
+
- **The app stays open; the spend has its own gate.** Who can visit and read
|
|
548
|
+
the app is publishing plus `access.js`. Who sets off AI calls and GitHub
|
|
549
|
+
posts is the repo's `who` setting on deliveries and a `binderHandle` check on
|
|
550
|
+
every spending route. Keep the app published and gate the spend.
|
|
551
|
+
- **What the App may do today:** read repository contents, and write to pull
|
|
552
|
+
requests — reviews, review comments, and comments on a pull request's
|
|
553
|
+
conversation. Build on those. Other writes (labels, issue comments, check
|
|
554
|
+
runs) may answer `permission_not_granted`; record that reason on the
|
|
555
|
+
person's record and show it, so they see exactly what happened.
|
|
556
|
+
- **One repo connects to one app.** A repo already connected to another app
|
|
557
|
+
answers the person on the connect screen with "Already connected" — say so on
|
|
558
|
+
your page near the Connect button, so it reads as expected.
|
|
559
|
+
- **Repo names are case-insensitive on GitHub.** Lower-case them in `_id`s and
|
|
560
|
+
comparisons, as the example does, and keep GitHub's spelling for display.
|
|
561
|
+
- **Disconnecting happens on GitHub**, in the person's installed GitHub Apps
|
|
562
|
+
settings.
|
|
563
|
+
- More on `ctx` — `ctx.db`, `ctx.log`, `ifRev`, `apiFetch` — lives in the
|
|
564
|
+
`backend` skill.
|
package/llms/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { useVibeConfig } from "./use-vibe.js";
|
|
|
16
16
|
export { createVibeConfig } from "./create-vibe.js";
|
|
17
17
|
export { accessConfig } from "./access.js";
|
|
18
18
|
export { connectionsConfig } from "./connections.js";
|
|
19
|
+
export { githubConfig } from "./github.js";
|
|
19
20
|
export { youtubeConfig } from "./youtube.js";
|
|
20
21
|
export { blueskyConfig } from "./bluesky.js";
|
|
21
22
|
export { spotifyConfig } from "./spotify.js";
|
|
@@ -57,5 +58,5 @@ export { poufCalendarConfig } from "./pouf-calendar.js";
|
|
|
57
58
|
export { poufTestimonialsConfig } from "./pouf-testimonials.js";
|
|
58
59
|
export type { LlmConfig } from "./types.js";
|
|
59
60
|
export { cueMatches, cueMatchedSkills, type CueCatalogEntry } from "./cues.js";
|
|
60
|
-
export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
|
|
61
|
+
export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
|
|
61
62
|
export { mapsConfig } from "./maps.js";
|
package/llms/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { useVibeConfig } from "./use-vibe.js";
|
|
|
17
17
|
import { createVibeConfig } from "./create-vibe.js";
|
|
18
18
|
import { accessConfig } from "./access.js";
|
|
19
19
|
import { connectionsConfig } from "./connections.js";
|
|
20
|
+
import { githubConfig } from "./github.js";
|
|
20
21
|
import { youtubeConfig } from "./youtube.js";
|
|
21
22
|
import { blueskyConfig } from "./bluesky.js";
|
|
22
23
|
import { spotifyConfig } from "./spotify.js";
|
|
@@ -74,6 +75,7 @@ export { useVibeConfig } from "./use-vibe.js";
|
|
|
74
75
|
export { createVibeConfig } from "./create-vibe.js";
|
|
75
76
|
export { accessConfig } from "./access.js";
|
|
76
77
|
export { connectionsConfig } from "./connections.js";
|
|
78
|
+
export { githubConfig } from "./github.js";
|
|
77
79
|
export { youtubeConfig } from "./youtube.js";
|
|
78
80
|
export { blueskyConfig } from "./bluesky.js";
|
|
79
81
|
export { spotifyConfig } from "./spotify.js";
|
|
@@ -134,6 +136,7 @@ export const allConfigs = [
|
|
|
134
136
|
boardGamesConfig,
|
|
135
137
|
accessConfig,
|
|
136
138
|
connectionsConfig,
|
|
139
|
+
githubConfig,
|
|
137
140
|
youtubeConfig,
|
|
138
141
|
blueskyConfig,
|
|
139
142
|
spotifyConfig,
|
package/llms/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.162",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"exports": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@adviser/cement": "~0.5.34",
|
|
42
|
-
"@vibes.diy/api-types": "14.3.
|
|
43
|
-
"@vibes.diy/call-ai-v2": "14.3.
|
|
44
|
-
"@vibes.diy/identity": "14.3.
|
|
45
|
-
"@vibes.diy/use-vibes-types": "14.3.
|
|
42
|
+
"@vibes.diy/api-types": "14.3.162",
|
|
43
|
+
"@vibes.diy/call-ai-v2": "14.3.162",
|
|
44
|
+
"@vibes.diy/identity": "14.3.162",
|
|
45
|
+
"@vibes.diy/use-vibes-types": "14.3.162",
|
|
46
46
|
"arktype": "~2.2.6",
|
|
47
47
|
"json-schema-faker": "~0.6.3"
|
|
48
48
|
},
|