@vibes.diy/prompts 14.3.175 → 14.3.177
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 +5 -1
- package/llms/spotify.md +149 -72
- package/llms/stripe-subscriptions.md +196 -117
- package/llms/stripe.md +130 -93
- package/llms/use-vibe.md +77 -0
- package/llms/youtube.md +147 -66
- package/package.json +5 -5
package/llms/youtube.md
CHANGED
|
@@ -18,26 +18,37 @@ developer console to mint one: that is the flow connections exists to replace.
|
|
|
18
18
|
The console trip below is for the **owner** of this app, once, and for nobody
|
|
19
19
|
else.
|
|
20
20
|
|
|
21
|
-
## 1.
|
|
22
|
-
|
|
23
|
-
The owner gets a key from Google, once
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
## 1. The setup item: the owner's YouTube Data API key
|
|
22
|
+
|
|
23
|
+
The owner gets a key from Google, once. The Setup surface the item lands on —
|
|
24
|
+
the owner-only entry in the app's navigation, the verdict from `/setup`, the
|
|
25
|
+
re-read on focus that unlocks the dashboard in place — is the use-vibe docs'
|
|
26
|
+
*Some apps need setup before they work*; this recipe contributes one item to
|
|
27
|
+
it. The YouTube item is the API key: the backend answers missing-key while ctx.secrets.YOUTUBE_API_KEY is absent, and its steps link the Google Cloud console's YouTube Data API v3 library page and its Credentials page by URL and name YOUTUBE_API_KEY in so many words.
|
|
28
|
+
The steps are written for someone who has never opened the console, so each
|
|
29
|
+
one is a link to the exact page and uses the words that page uses:
|
|
30
|
+
|
|
31
|
+
1. Open the **YouTube Data API v3** page in the Google Cloud console:
|
|
32
|
+
<https://console.cloud.google.com/apis/library/youtube.googleapis.com>
|
|
33
|
+
(sign in with a Google account; create a project when it asks) and press
|
|
34
|
+
**Enable**.
|
|
35
|
+
2. Open **Credentials**: <https://console.cloud.google.com/apis/credentials>.
|
|
36
|
+
Press **Create credentials**, choose **API key**, and copy the key. Then
|
|
37
|
+
open the key, and under **API restrictions** choose **Restrict key**, tick
|
|
38
|
+
**YouTube Data API v3**, and save.
|
|
39
|
+
3. Open this vibe's **Settings** tab on vibes.diy:
|
|
40
|
+
`https://vibes.diy/vibes/mine/<ownerHandle>/<appSlug>/settings` (that is
|
|
41
|
+
**My Vibes** → this vibe → **Settings**). Under **Secrets**, enter the key
|
|
42
|
+
`YOUTUBE_API_KEY`, paste the value, and save. From a terminal the same
|
|
43
|
+
thing is `npx vibes-diy secrets set YOUTUBE_API_KEY --vibe <ownerHandle>/<appSlug>`.
|
|
44
|
+
4. Come back to the app. The dashboard works the moment the key is saved; no
|
|
45
|
+
redeploy.
|
|
46
|
+
|
|
47
|
+
Those steps are the item's copy on the Setup surface, with the vibe's real
|
|
48
|
+
handle and slug filled in from the `ownerHandle` and `appSlug` the `/setup`
|
|
49
|
+
route hands over. In the consumer flow, a dashboard whose key is not set
|
|
50
|
+
reads *Channel stats are not open yet*; the owner's Setup entry is where the
|
|
51
|
+
steps are, and later it is where a rotated key goes.
|
|
41
52
|
|
|
42
53
|
Only the owner can set or rotate a secret, and handlers see the current value on
|
|
43
54
|
every invocation — so the dashboard starts working the moment the key lands, with
|
|
@@ -241,7 +252,8 @@ Google answers a refusal with `error.errors[0].reason`. Classify on it and pass
|
|
|
241
252
|
the reason through:
|
|
242
253
|
|
|
243
254
|
- `quotaExceeded` — "Daily quota used; resets at midnight Pacific."
|
|
244
|
-
- `keyInvalid` or `API_KEY_INVALID` —
|
|
255
|
+
- `keyInvalid` or `API_KEY_INVALID` — say so, and point the owner at the Setup
|
|
256
|
+
entry: the item reads *Done* because a value is present, and the value is wrong.
|
|
245
257
|
- `accessNotConfigured` — "Enable YouTube Data API v3 on this key's project."
|
|
246
258
|
- anything else — show the API's own `error.message`.
|
|
247
259
|
|
|
@@ -399,6 +411,15 @@ export async function fetch(request, ctx) {
|
|
|
399
411
|
|
|
400
412
|
const isOwner = ctx.userInfo?.userHandle != null && ctx.userInfo.userHandle === ctx.appInfo.ownerHandle;
|
|
401
413
|
|
|
414
|
+
// The setup verdict: ready for everyone, the item (and the handle + slug the Setup screen links
|
|
415
|
+
// with) for the owner alone. No live call, so it sits in front of the owner gate.
|
|
416
|
+
if (url.pathname === "/setup") {
|
|
417
|
+
const items = [{ id: "youtube-key", label: "YouTube Data API v3 key", done: Boolean(ctx.secrets.YOUTUBE_API_KEY) }];
|
|
418
|
+
const ready = items.every((item) => item.done);
|
|
419
|
+
if (isOwner) return json({ ok: true, ready, owner: isOwner, items, ownerHandle: ctx.appInfo.ownerHandle, appSlug: ctx.appInfo.appSlug });
|
|
420
|
+
return json({ ok: true, ready, owner: isOwner });
|
|
421
|
+
}
|
|
422
|
+
|
|
402
423
|
// Anyone who is not the owner is answered from what the tick already stored, and the channel they
|
|
403
424
|
// asked for is resolved against the tracked docs' aliases. Nothing here reaches googleapis.com, so
|
|
404
425
|
// a stranger with a refresh button cannot spend the owner's daily quota.
|
|
@@ -510,25 +531,76 @@ export async function scheduled(event, ctx) {
|
|
|
510
531
|
|
|
511
532
|
### `App.jsx`
|
|
512
533
|
```jsx app=youtube-dashboard
|
|
513
|
-
import React, { useState } from "react";
|
|
534
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
514
535
|
import { useFireproof } from "use-fireproof";
|
|
515
|
-
import {
|
|
536
|
+
import { apiFetch } from "use-vibes";
|
|
537
|
+
import { Badge, Button, Field, Heading, Input, Row, Stack, Text } from "@vibes.diy/pouf";
|
|
538
|
+
|
|
539
|
+
// The setup verdict, read on mount and again whenever the app comes back into view: the owner
|
|
540
|
+
// sets the key in another tab and returns, and the dashboard unlocks without a reload.
|
|
541
|
+
function useSetup() {
|
|
542
|
+
const [setup, setSetup] = useState(null); // { ready, owner, items?, ownerHandle?, appSlug? }
|
|
543
|
+
const latest = useRef(0);
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
// Mount, focus and visibility reads overlap; only the latest one may land, or a slow stale
|
|
546
|
+
// answer overwrites a fresh one.
|
|
547
|
+
const load = () => {
|
|
548
|
+
const n = ++latest.current;
|
|
549
|
+
apiFetch("/setup").then((r) => r.json()).catch(() => ({ ready: false, owner: false })).then((s) => { if (n === latest.current) setSetup(s); });
|
|
550
|
+
};
|
|
551
|
+
load();
|
|
552
|
+
const onVisible = () => { if (document.visibilityState === "visible") load(); };
|
|
553
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
554
|
+
window.addEventListener("focus", load);
|
|
555
|
+
return () => { document.removeEventListener("visibilitychange", onVisible); window.removeEventListener("focus", load); };
|
|
556
|
+
}, []);
|
|
557
|
+
return setup;
|
|
558
|
+
}
|
|
516
559
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
560
|
+
// The Setup surface, the owner's alone: one item, every step a link to the exact page in that
|
|
561
|
+
// page's own words, with this vibe's real handle and slug filled in.
|
|
562
|
+
function SetupScreen({ setup }) {
|
|
563
|
+
const vibe = `${setup.ownerHandle}/${setup.appSlug}`;
|
|
564
|
+
const settingsUrl = `https://vibes.diy/vibes/mine/${vibe}/settings`;
|
|
565
|
+
const key = setup.items.find((item) => item.id === "youtube-key");
|
|
566
|
+
return (
|
|
567
|
+
<Stack>
|
|
568
|
+
<Heading>Setup</Heading>
|
|
569
|
+
<Row gap={2}>
|
|
570
|
+
<Text>{key.label}</Text>
|
|
571
|
+
<Badge tone={key.done ? "up" : "info"}>{key.done ? "Done" : "To do"}</Badge>
|
|
572
|
+
</Row>
|
|
573
|
+
<ol>
|
|
574
|
+
<li>
|
|
575
|
+
Open the <a href="https://console.cloud.google.com/apis/library/youtube.googleapis.com" target="_blank" rel="noreferrer">YouTube Data API v3 page</a> in the Google Cloud console
|
|
576
|
+
(use your Google account; create a project when it asks) and press <b>Enable</b>.
|
|
577
|
+
</li>
|
|
578
|
+
<li>
|
|
579
|
+
Open <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noreferrer">Credentials</a>, press <b>Create credentials</b>, choose <b>API key</b>, and copy it.
|
|
580
|
+
Then open the key, and under <b>API restrictions</b> choose <b>Restrict key</b>, tick <b>YouTube Data API v3</b>, and save.
|
|
581
|
+
</li>
|
|
582
|
+
<li>
|
|
583
|
+
Open <a href={settingsUrl} target="_blank" rel="noreferrer">this vibe's Settings tab</a> (My Vibes → this vibe → Settings).
|
|
584
|
+
Under <b>Secrets</b>, enter the key <code>YOUTUBE_API_KEY</code>, paste the value, and save.
|
|
585
|
+
From a terminal instead: <code>npx vibes-diy secrets set YOUTUBE_API_KEY --vibe {vibe}</code>.
|
|
586
|
+
</li>
|
|
587
|
+
<li>Come back here: the dashboard works as soon as the key is saved. The key stays on the server.</li>
|
|
588
|
+
</ol>
|
|
589
|
+
</Stack>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
523
592
|
|
|
524
593
|
export default function App() {
|
|
525
594
|
const { useLiveQuery, database } = useFireproof("youtube");
|
|
526
595
|
const { docs: snapshots } = useLiveQuery("type", { key: "snapshot" });
|
|
596
|
+
const setup = useSetup();
|
|
597
|
+
const [screen, setScreen] = useState("dashboard");
|
|
527
598
|
const [input, setInput] = useState("");
|
|
528
599
|
const [channel, setChannel] = useState(null);
|
|
529
600
|
const [problem, setProblem] = useState(null);
|
|
530
601
|
const [source, setSource] = useState(null);
|
|
531
602
|
const [asOf, setAsOf] = useState(null);
|
|
603
|
+
const ready = setup?.ready === true;
|
|
532
604
|
|
|
533
605
|
const connect = async () => {
|
|
534
606
|
const res = await fetch(`/_api/channel?q=${encodeURIComponent(input)}`);
|
|
@@ -552,46 +624,55 @@ export default function App() {
|
|
|
552
624
|
const history = snapshots.slice().sort((a, b) => a.day.localeCompare(b.day));
|
|
553
625
|
|
|
554
626
|
return (
|
|
555
|
-
<
|
|
556
|
-
<Row gap={2}
|
|
557
|
-
<
|
|
558
|
-
|
|
627
|
+
<Stack>
|
|
628
|
+
<Row gap={2}>
|
|
629
|
+
<Button variant="quiet" onClick={() => setScreen("dashboard")}>Dashboard</Button>
|
|
630
|
+
{/* The Setup entry: the owner's alone, highlighted while the item is still to do, quiet once done. */}
|
|
631
|
+
{setup?.owner && (
|
|
632
|
+
<Button variant={ready ? "quiet" : "solid"} onClick={() => setScreen("setup")}>
|
|
633
|
+
Setup{ready ? "" : " · to do"}
|
|
634
|
+
</Button>
|
|
635
|
+
)}
|
|
559
636
|
</Row>
|
|
560
637
|
|
|
561
|
-
{
|
|
562
|
-
<
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
638
|
+
{screen === "setup" && setup?.owner ? (
|
|
639
|
+
<SetupScreen setup={setup} />
|
|
640
|
+
) : (
|
|
641
|
+
<Stack>
|
|
642
|
+
{ready ? (
|
|
643
|
+
<Row gap={2} align="top">
|
|
644
|
+
<Field label="Channel">{(id, describedBy) => <Input id={id} describedBy={describedBy} value={input} onChange={setInput} placeholder="@handle or channel URL" />}</Field>
|
|
645
|
+
<Button type="button" onClick={connect}>Connect channel</Button>
|
|
646
|
+
</Row>
|
|
647
|
+
) : (
|
|
648
|
+
<Text muted>Channel stats are not open yet.</Text>
|
|
649
|
+
)}
|
|
650
|
+
|
|
651
|
+
{problem?.reason === "no-snapshot-yet" && <Text>The first daily snapshot has not been saved yet.</Text>}
|
|
652
|
+
{problem && problem.reason !== "no-snapshot-yet" && <Text role="alert">{problem.message}</Text>}
|
|
653
|
+
|
|
654
|
+
{channel && (
|
|
655
|
+
<section>
|
|
656
|
+
<h2>{channel.title}</h2>
|
|
657
|
+
<p>{channel.subscribersHidden ? "Subscribers hidden by the channel" : `${channel.subscribers} subscribers`}</p>
|
|
658
|
+
<small>YouTube rounds public subscriber counts.</small>
|
|
659
|
+
<p>{channel.views} total views across {channel.videoCount} videos</p>
|
|
660
|
+
{source === "snapshot" && <small>As of {asOf} — live numbers are refreshed for the channel owner.</small>}
|
|
661
|
+
</section>
|
|
662
|
+
)}
|
|
663
|
+
|
|
664
|
+
<section>
|
|
665
|
+
<h3>Not available with an API key</h3>
|
|
666
|
+
<p>Watch time, revenue, impressions, traffic sources and demographics come from YouTube Analytics with OAuth.</p>
|
|
667
|
+
</section>
|
|
668
|
+
|
|
669
|
+
<section>
|
|
670
|
+
<h3>Daily snapshots</h3>
|
|
671
|
+
{history.length === 0 ? <p>First snapshot saves within the hour.</p> : <Chart rows={history} />}
|
|
672
|
+
</section>
|
|
673
|
+
</Stack>
|
|
583
674
|
)}
|
|
584
|
-
|
|
585
|
-
<section>
|
|
586
|
-
<h3>Not available with an API key</h3>
|
|
587
|
-
<p>Watch time, revenue, impressions, traffic sources and demographics come from YouTube Analytics with OAuth.</p>
|
|
588
|
-
</section>
|
|
589
|
-
|
|
590
|
-
<section>
|
|
591
|
-
<h3>Daily snapshots</h3>
|
|
592
|
-
{history.length === 0 ? <p>First snapshot saves within the hour.</p> : <Chart rows={history} />}
|
|
593
|
-
</section>
|
|
594
|
-
</div>
|
|
675
|
+
</Stack>
|
|
595
676
|
);
|
|
596
677
|
}
|
|
597
678
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "14.3.
|
|
3
|
+
"version": "14.3.177",
|
|
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.177",
|
|
43
|
+
"@vibes.diy/call-ai-v2": "14.3.177",
|
|
44
|
+
"@vibes.diy/identity": "14.3.177",
|
|
45
|
+
"@vibes.diy/use-vibes-types": "14.3.177",
|
|
46
46
|
"arktype": "~2.2.8",
|
|
47
47
|
"json-schema-faker": "~0.6.3"
|
|
48
48
|
},
|