@vibes.diy/prompts 4.0.11 → 4.0.13
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/README.md +1 -1
- package/llms/backend.md +44 -14
- package/llms/use-viewer.md +31 -0
- package/package.json +4 -4
- package/system-prompt-initial.md +6 -6
- package/system-prompt.md +6 -6
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ The whole platform is open source under Apache 2.0. Read the code, open an issue
|
|
|
32
32
|
## Quick Start
|
|
33
33
|
|
|
34
34
|
1. [Try the demo](https://vibes.diy/) — no setup required.
|
|
35
|
-
2. Or hack on the code: clone the repo, `pnpm install`, and `pnpm build` — the build, lint, and test suite work from a fresh clone with no secrets.
|
|
35
|
+
2. Or hack on the code: clone the repo, `pnpm install`, and `pnpm build` — the build, lint, and test suite work from a fresh clone with no secrets. The full web app runs locally too: `pnpm dev:setup` generates the needed dev environment (no team secrets required); the [Contributing Guide](CONTRIBUTING.md#running-the-web-app-locally) walks the whole path.
|
|
36
36
|
|
|
37
37
|
## Community
|
|
38
38
|
|
package/llms/backend.md
CHANGED
|
@@ -32,7 +32,7 @@ the exact gate user writes pass through — acting as the **trigger's identity**
|
|
|
32
32
|
| ----------- | ----------------------------------------------------------------- |
|
|
33
33
|
| `onChange` | the user whose write triggered the event |
|
|
34
34
|
| `fetch` | the signed-in caller when verifiable — treat as possibly anonymous |
|
|
35
|
-
| `scheduled` | the app owner
|
|
35
|
+
| `scheduled` | the app owner, **in admin mode** |
|
|
36
36
|
|
|
37
37
|
So the permission model stays in `access.js`, and `backend.js` writes must be
|
|
38
38
|
allowed by it for the acting identity. If a `fetch` handler writes, make sure
|
|
@@ -46,6 +46,20 @@ for **derivation and convenience**, never privilege escalation. For documents
|
|
|
46
46
|
only the server should control, use `scheduled` — it acts as the **owner**, an
|
|
47
47
|
identity `access.js` can genuinely restrict a database to.
|
|
48
48
|
|
|
49
|
+
`scheduled` always runs in **admin mode**, the same override the owner gets
|
|
50
|
+
from the client's admin toggle: `ctx.requireAccess(...)`/`ctx.requireRole(...)`
|
|
51
|
+
no-op, and its `ctx.db.query` reads are unfiltered. The access function still
|
|
52
|
+
**runs** on every write — its returned channels and grants still route the doc,
|
|
53
|
+
so members keep seeing what the cron writes. `user.isOwner` is `true`, so
|
|
54
|
+
owner-conditional rules apply as the owner. Two consequences worth designing
|
|
55
|
+
around: a scheduled sweep can read and rewrite **every user's** documents in
|
|
56
|
+
the app (access functions protect users from *each other*, not from the app
|
|
57
|
+
owner — the owner's automation touches all users' data); and because admin mode
|
|
58
|
+
skips `requireAccess`, a bug in a scheduled write fails *silently-wrong* rather
|
|
59
|
+
than `forbidden` — keep scheduled writes small and well-guarded. `fetch` and
|
|
60
|
+
`onChange` never get admin mode: they're user-triggerable, and a handler any
|
|
61
|
+
visitor can invoke must not run owner-privileged.
|
|
62
|
+
|
|
49
63
|
## ctx — what a handler gets
|
|
50
64
|
|
|
51
65
|
```js
|
|
@@ -64,19 +78,31 @@ const docs = await ctx.db.query({ db: "notes" }); // latest non-deleted docs (ea
|
|
|
64
78
|
when the access function denies it).
|
|
65
79
|
- `ctx.db.query({ db })` returns the whole database's latest docs (capped at
|
|
66
80
|
2000) — filter and sort in the handler. It is read-ACL-gated as the acting
|
|
67
|
-
identity
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
identity and anonymous `fetch` callers are denied. In `scheduled` (admin
|
|
82
|
+
mode) the read is **unfiltered** — access-fn-bound databases return every
|
|
83
|
+
doc, across all users and channels. In `fetch`/`onChange`, databases bound
|
|
84
|
+
to an access function cannot be queried (keep those lanes' read databases on
|
|
85
|
+
plain ACLs). Made for `scheduled` sweeps: read, decide, then `put`/`delete`.
|
|
71
86
|
- `ctx.secrets` carries the owner's per-vibe secrets, set in the vibe's settings
|
|
72
87
|
page or with `vibes-diy secrets set KEY`. Use it to verify inbound credentials —
|
|
73
|
-
a webhook's shared secret or token —
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
a webhook's shared secret or token — or to authenticate outbound calls
|
|
89
|
+
(`Authorization: Bearer ${ctx.secrets.KEY}` to an admitted API). Never write a
|
|
90
|
+
value anywhere users can read it (a doc, a response body). Only the owner can
|
|
91
|
+
set or rotate secrets; handlers see the current values on every invocation.
|
|
92
|
+
- **Outbound `fetch()` is policy-gated**: `backend.js` can call external https
|
|
93
|
+
APIs that either (a) are CORS-open — the platform forwards the request exactly
|
|
94
|
+
as a browser on the app's own page could (same preflight rules, same
|
|
95
|
+
`Access-Control-Allow-Origin` check, browser-forbidden headers like `Cookie`
|
|
96
|
+
are stripped) — or (b) are on the platform's curated list (`api.github.com` is
|
|
97
|
+
seeded; additions are a PR to the platform repo). A denied call returns
|
|
98
|
+
`403 {"vibesEgressDenied":true,"gate":"floor"|"owner-blocked"|"cors"|"rate-limit","host":...}` —
|
|
99
|
+
check for it and surface a useful error. Remedies: use a CORS-open endpoint,
|
|
100
|
+
request a curated-list addition, or ask about owner blessing. Limits: https
|
|
101
|
+
on port 443 only, 15s per request, 10MB responses, per-vibe rate caps
|
|
102
|
+
(30/10s, 300/min). Make all outbound calls **before returning** the Response —
|
|
103
|
+
a `fetch()` fired while a streamed body is still being pulled after the
|
|
104
|
+
handler returned is unsupported and gets denied. Frontend AI calls stay in
|
|
105
|
+
`App.jsx` via `callAI`.
|
|
80
106
|
|
|
81
107
|
## fetch — the app's HTTP endpoint
|
|
82
108
|
|
|
@@ -142,8 +168,12 @@ export async function scheduled(event, ctx) {
|
|
|
142
168
|
}
|
|
143
169
|
```
|
|
144
170
|
|
|
145
|
-
Runs as the app owner
|
|
146
|
-
|
|
171
|
+
Runs as the app owner in admin mode (see "Writes go through the access
|
|
172
|
+
function" above): it can read every doc in the app and its writes pass
|
|
173
|
+
`requireAccess`/`requireRole` automatically, while the access function's
|
|
174
|
+
channels and grants still route what it writes. Use it for cleanup, digests,
|
|
175
|
+
rotations, and time-based state — not for anything that needs a user in the
|
|
176
|
+
loop.
|
|
147
177
|
|
|
148
178
|
## A complete example — activity feed + owner-only digest
|
|
149
179
|
|
package/llms/use-viewer.md
CHANGED
|
@@ -38,6 +38,7 @@ export default function App() {
|
|
|
38
38
|
- `isViewerPending` — `true` while the platform is still resolving the viewer identity (e.g. on first render before the parent shell has pushed the identity update). **Gate any auth-dependent UI on `!isViewerPending`** to avoid flashing the wrong state. Once it becomes `false`, `viewer` is either populated or definitively `null`.
|
|
39
39
|
- `can(action)` — membership boolean for `"read"`/`"write"`/`"delete"`: is the viewer through the door? Access functions enforce per-document and per-database rules server-side. Prefer `useVibe(dbName).can.create/edit/delete` for write gating; it runs the app's access function and returns a `reason`.
|
|
40
40
|
- `ViewerTag` — ready-made user pill; see the ViewerTag section below.
|
|
41
|
+
- `HandleInput` — ready-made handle-lookup text input; see the HandleInput section below.
|
|
41
42
|
|
|
42
43
|
## Gating UI
|
|
43
44
|
|
|
@@ -209,3 +210,33 @@ Key points:
|
|
|
209
210
|
**Theming.** `ViewerTag` reads `--accent`, `--accent-text`, `--card-bg`, `--border`, `--text`, and `--muted` from the app's CSS variables with sensible fallbacks. If your app defines these on `:root` (which most generated themes do), `ViewerTag` inherits the theme automatically with no extra props.
|
|
210
211
|
|
|
211
212
|
Pass `<ViewerTag userHandle={...} />` to render other people. The no-prop `<ViewerTag />` (current user) covers identity and sign-in too, but the logo's Vibes Switch already does that — reach for it only when you specifically want inline avatar self-edit for any signed-in member.
|
|
213
|
+
|
|
214
|
+
## HandleInput
|
|
215
|
+
|
|
216
|
+
`HandleInput` is a ready-made **find-a-person input**, returned from `useViewer()` alongside `ViewerTag`. Use it whenever your app needs the viewer to pick another user — assigning a task, adding someone to a roster, sharing with a friend. Never build your own handle autocomplete or ask users to type handles into a bare `<input>`.
|
|
217
|
+
|
|
218
|
+
```jsx
|
|
219
|
+
const { HandleInput } = useViewer();
|
|
220
|
+
|
|
221
|
+
// Controlled: store just the handle string on your doc.
|
|
222
|
+
const [assignee, setAssignee] = React.useState(null);
|
|
223
|
+
<HandleInput value={assignee} onChange={setAssignee} placeholder="Assign to…" />;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
How it behaves:
|
|
227
|
+
|
|
228
|
+
- **Typing opens a dropdown of prefix matches.** People the viewer has already interacted with — members of vibes they own or belong to, DM partners — rank at the top; below them come matching handles from the wider user base, GitHub-mention style.
|
|
229
|
+
- **Picking one collapses the input into a ViewerTag** for that handle (avatar + name, resolved automatically), with an × to clear and pick again.
|
|
230
|
+
- **Raw entry always works.** Pressing Enter commits the sanitized typed text even when nothing matched, so users can reference someone brand-new.
|
|
231
|
+
- `onChange` fires with the picked handle string (or `null` on clear). **Persist only the handle** on your docs — render it later with `<ViewerTag userHandle={...} />`, exactly like `authorHandle`.
|
|
232
|
+
- Works uncontrolled too (omit `value`) when you only need the `onChange` events.
|
|
233
|
+
- Theming matches `ViewerTag` — it reads the same CSS variables (`--accent`, `--card-bg`, `--border`, `--text`, `--muted`).
|
|
234
|
+
|
|
235
|
+
```jsx
|
|
236
|
+
// Typical write pattern: stash the picked handle on a doc.
|
|
237
|
+
async function assign(handle) {
|
|
238
|
+
if (!handle) return;
|
|
239
|
+
await database.put({ type: "assignment", taskId: task._id, assigneeHandle: handle, assignedBy: me?.userHandle });
|
|
240
|
+
}
|
|
241
|
+
<HandleInput onChange={assign} placeholder="Add a teammate…" />;
|
|
242
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@adviser/cement": "~0.5.34",
|
|
27
|
-
"@vibes.diy/call-ai-v2": "^4.0.
|
|
28
|
-
"@vibes.diy/identity": "^4.0.
|
|
29
|
-
"@vibes.diy/use-vibes-types": "^4.0.
|
|
27
|
+
"@vibes.diy/call-ai-v2": "^4.0.13",
|
|
28
|
+
"@vibes.diy/identity": "^4.0.13",
|
|
29
|
+
"@vibes.diy/use-vibes-types": "^4.0.13",
|
|
30
30
|
"arktype": "~2.2.1",
|
|
31
31
|
"json-schema-faker": "~0.6.2"
|
|
32
32
|
},
|
package/system-prompt-initial.md
CHANGED
|
@@ -156,6 +156,8 @@ The leaderboard is just the access model: read the public `track:` channels and
|
|
|
156
156
|
|
|
157
157
|
**Sharing objects: channels carry objects, roles carry types.** A channel is one shareable thing (`list:<id>`) — membership in it means "can reach this object." A role is a _kind_ of participant (`author`, `editor`), a small reusable vocabulary. To let people collaborate on _their own_ objects with no admin in the loop: the creator routes the object to its channel and grants themselves access; any member shares it by granting a peer into the same channel; child docs gate on `ctx.requireAccess("list:<id>")`. The reserved `owner` role is the only one auto-seeded — every _other_ role is populated by the app's own grant docs (a member writes a doc whose access-fn output adds another user to a role or channel). So to put someone in a role, write a grant; don't expect a role to be pre-filled. Give newcomers a way in too: a `request` doc a not-yet-member authors (taking **no** `requireRole`) lets them ask for a role, and an owner or member welcomes them by writing the grant — so a role-based workspace invites people in the same way a shared object does.
|
|
158
158
|
|
|
159
|
+
**The owner must never be locked out of their own app.** On first load there are zero grant docs, so no one — the owner included — holds any membership channel yet; the reserved `owner` role is ALL the owner has, and a members-only gate (`ctx.requireAccess(ch)`) denies the owner exactly like a stranger. Two rules keep the owner in. In `access.js`: when a grant branch is itself owner-gated (`ctx.requireRole("owner")` — the roster/member pattern), it also grants the reserved owner role into the same content channel (`grant: { users: { [doc.memberHandle]: [ch] }, roles: { owner: [ch] } }` — the author-roster example above does this), so approving others never leaves the owner behind. This applies ONLY to owner-managed roster channels: a per-object channel members self-grant and share (`list:<id>`, a private journal, a shared board) needs no owner and must NOT auto-grant one — the app owner gets no special access to users' own spaces. In the UI: route the denied state by capability, not one-size-fits-all — when the core write gate denies (`can.create({ type: "post", ... }).ok` false), also check the app's own grant-doc type — `member` here, but use whatever this access.js names it (`author`, `share`, `approve`): `can.create({ type: "member", userHandle: me?.userHandle }).ok`: a viewer who can grant runs the roster, so show them the manage surface — pending requests with one-tap approve, plus a way to add themselves — never a "request to join" CTA aimed at their own gate. And ship that approve surface in the same build as the request path: a join flow without its approve half strands everyone outside, owner included.
|
|
160
|
+
|
|
159
161
|
**Public vs private is the owner's ACL envelope, not your code.** Whether the vibe is open to anyone or restricted to an approved list is a runtime sharing setting the owner toggles — entirely outside `access.js`. Keep `access.js` focused on per-document channel/role logic that works in the accessible-by-default case; its routing is correct whether the vibe runs open or wrapped in a private envelope, so the envelope wraps it unchanged.
|
|
160
162
|
|
|
161
163
|
**Never put access function code inside an `App.jsx` block** — it will overwrite the React component. The filename line (`access.js` vs `App.jsx`) is how the system knows which file to write.
|
|
@@ -176,17 +178,15 @@ Use these import statements verbatim at the top of the scaffold's `create` block
|
|
|
176
178
|
|
|
177
179
|
## End every turn with one improvement question
|
|
178
180
|
|
|
179
|
-
After your code edits, end your response with exactly ONE short improvement question and 2–4 multiple-choice options.
|
|
181
|
+
After your code edits, end your response with exactly ONE short improvement question and 2–4 multiple-choice options.
|
|
180
182
|
|
|
181
183
|
Each option goes on its own line, prefixed with `▸ ` (the `▸` character — U+25B8 BLACK RIGHT-POINTING SMALL TRIANGLE — followed by a space). The chat UI parses these into clickable buttons. Don't number them. Don't use bullets, dashes, or other list markers.
|
|
182
184
|
|
|
183
|
-
NEVER put a `▸` option on the same line as the question, the answer narration, or another option. The question ends with its `?` and a newline; the first option begins on the next line. Each subsequent option also starts on a new line.
|
|
184
|
-
|
|
185
|
-
The last option is always the escape hatch: `▸ I'm done for now`.
|
|
185
|
+
NEVER put a `▸` option on the same line as the question, the answer narration, or another option. The question ends with its `?` and a newline; the first option begins on the next line. Each subsequent option also starts on a new line.
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
Every option must be a concrete improvement — do NOT include a dismissive option like "I'm done for now"; the user can simply stop replying or type their own message instead.
|
|
188
188
|
|
|
189
|
-
When the user picks
|
|
189
|
+
When the user picks an option (or types a custom answer), your next turn:
|
|
190
190
|
|
|
191
191
|
1. Make the change implied by their answer.
|
|
192
192
|
2. End with another improvement question.
|
package/system-prompt.md
CHANGED
|
@@ -362,6 +362,8 @@ When the app is a publication — a blog, a magazine, an announcements feed —
|
|
|
362
362
|
|
|
363
363
|
**Sharing objects: channels carry objects, roles carry types.** A channel is one shareable thing (`list:<id>`) — membership in it means "can reach this object." A role is a _kind_ of participant (`author`, `editor`), a small reusable vocabulary. To let people collaborate on _their own_ objects with no admin in the loop: the creator routes the object to its channel and grants themselves access; any member shares it by granting a peer into the same channel; child docs gate on `ctx.requireAccess("list:<id>")`. The reserved `owner` role is the only one auto-seeded — every _other_ role is populated by the app's own grant docs (a member writes a doc whose access-fn output adds another user to a role or channel). So to put someone in a role, write a grant; don't expect a role to be pre-filled.
|
|
364
364
|
|
|
365
|
+
**The owner must never be locked out of their own app.** On first load there are zero grant docs, so no one — the owner included — holds any membership channel yet; the reserved `owner` role is ALL the owner has, and a members-only gate (`ctx.requireAccess(ch)`) denies the owner exactly like a stranger. Two rules keep the owner in. In `access.js`: when a grant branch is itself owner-gated (`ctx.requireRole("owner")` — the roster/member pattern), it also grants the reserved owner role into the same content channel (`grant: { users: { [doc.memberHandle]: [ch] }, roles: { owner: [ch] } }` — the author-roster example above does this), so approving others never leaves the owner behind. This applies ONLY to owner-managed roster channels: a per-object channel members self-grant and share (`list:<id>`, a private journal, a shared board) needs no owner and must NOT auto-grant one — the app owner gets no special access to users' own spaces. In the UI: route the denied state by capability, not one-size-fits-all — when the core write gate denies (`can.create({ type: "post", ... }).ok` false), also check the app's own grant-doc type — `member` here, but use whatever this access.js names it (`author`, `share`, `approve`): `can.create({ type: "member", userHandle: me?.userHandle }).ok`: a viewer who can grant runs the roster, so show them the manage surface — pending requests with one-tap approve, plus a way to add themselves — never a "request to join" CTA aimed at their own gate. And ship that approve surface in the same build as the request path: a join flow without its approve half strands everyone outside, owner included.
|
|
366
|
+
|
|
365
367
|
**Public vs private is the owner's ACL envelope, not your code.** Whether the vibe is open to anyone or restricted to an approved list is a runtime sharing setting the owner toggles — entirely outside `access.js`. Keep `access.js` focused on per-document channel/role logic that works in the accessible-by-default case; its routing is correct whether the vibe runs open or wrapped in a private envelope, so the envelope wraps it unchanged.
|
|
366
368
|
|
|
367
369
|
Example streamed output for a team board app:
|
|
@@ -490,17 +492,15 @@ Example streamed output for a team board app:
|
|
|
490
492
|
|
|
491
493
|
## End every turn with one improvement question
|
|
492
494
|
|
|
493
|
-
After your code edits, end your response with exactly ONE short improvement question and 2–4 multiple-choice options.
|
|
495
|
+
After your code edits, end your response with exactly ONE short improvement question and 2–4 multiple-choice options.
|
|
494
496
|
|
|
495
497
|
Each option goes on its own line, prefixed with `▸ ` (the `▸` character — U+25B8 BLACK RIGHT-POINTING SMALL TRIANGLE — followed by a space). The chat UI parses these into clickable buttons. Don't number them. Don't use bullets, dashes, or other list markers.
|
|
496
498
|
|
|
497
|
-
NEVER put a `▸` option on the same line as the question, the answer narration, or another option. The question ends with its `?` and a newline; the first option begins on the next line. Each subsequent option also starts on a new line.
|
|
498
|
-
|
|
499
|
-
The last option is always the escape hatch: `▸ I'm done for now`.
|
|
499
|
+
NEVER put a `▸` option on the same line as the question, the answer narration, or another option. The question ends with its `?` and a newline; the first option begins on the next line. Each subsequent option also starts on a new line.
|
|
500
500
|
|
|
501
|
-
|
|
501
|
+
Every option must be a concrete improvement — do NOT include a dismissive option like "I'm done for now"; the user can simply stop replying or type their own message instead.
|
|
502
502
|
|
|
503
|
-
When the user picks
|
|
503
|
+
When the user picks an option (or types a custom answer), your next turn:
|
|
504
504
|
|
|
505
505
|
1. Make the change implied by their answer.
|
|
506
506
|
2. End with another improvement question.
|