@vibes.diy/prompts 14.3.175 → 14.3.176

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/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. Bring your own key, and explain how
22
-
23
- The owner gets a key from Google, once:
24
-
25
- 1. Open the Google Cloud console and create or select a project.
26
- 2. Go to APIs & Services, then Library, and enable **YouTube Data API v3**.
27
- 3. Go to Credentials, then Create credentials, then API key.
28
- 4. Restrict the key to the YouTube Data API v3.
29
- 5. Set it on the vibe as `YOUTUBE_API_KEY` — the vibe's settings page, or
30
- `vibes-diy secrets set YOUTUBE_API_KEY <the key>`.
31
-
32
- The app carries those five steps in its own UI, because the person reading them
33
- is the person who has to do them.
34
-
35
- When the key is missing, the app shows the owner the setup steps for a YouTube Data API v3 key instead of an error.
36
-
37
- The backend answers `{ ok: false, reason: "missing-key" }` when
38
- `ctx.secrets.YOUTUBE_API_KEY` is absent, and the client renders the numbered
39
- steps, naming "YouTube Data API v3" and `YOUTUBE_API_KEY` in so many words so
40
- the owner knows what to enable and what to call it.
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` — show the setup steps from section 1 again.
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 { Button, Field, Input, Row } from "@vibes.diy/pouf";
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
- const SETUP_STEPS = [
518
- "Open the Google Cloud console and create or select a project.",
519
- "In APIs & Services, enable YouTube Data API v3.",
520
- "In Credentials, create an API key and restrict it to YouTube Data API v3.",
521
- "Set it on this vibe as YOUTUBE_API_KEY in the settings page.",
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
- <div>
556
- <Row gap={2} align="top">
557
- <Field label="Channel">{(id, describedBy) => <Input id={id} describedBy={describedBy} value={input} onChange={setInput} placeholder="@handle or channel URL" />}</Field>
558
- <Button type="button" onClick={connect}>Connect channel</Button>
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
- {problem?.reason === "missing-key" && (
562
- <section>
563
- <h2>Add a YouTube Data API v3 key</h2>
564
- <ol>
565
- {SETUP_STEPS.map((step) => (
566
- <li key={step}>{step}</li>
567
- ))}
568
- </ol>
569
- <p>The key is stored as YOUTUBE_API_KEY and stays on the server.</p>
570
- </section>
571
- )}
572
- {problem?.reason === "no-snapshot-yet" && <p>The first daily snapshot has not been saved yet.</p>}
573
- {problem && !["missing-key", "no-snapshot-yet"].includes(problem.reason) && <p role="alert">{problem.message}</p>}
574
-
575
- {channel && (
576
- <section>
577
- <h2>{channel.title}</h2>
578
- <p>{channel.subscribersHidden ? "Subscribers hidden by the channel" : `${channel.subscribers} subscribers`}</p>
579
- <small>YouTube rounds public subscriber counts.</small>
580
- <p>{channel.views} total views across {channel.videoCount} videos</p>
581
- {source === "snapshot" && <small>As of {asOf} — live numbers are refreshed for the channel owner.</small>}
582
- </section>
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.175",
3
+ "version": "14.3.176",
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.175",
43
- "@vibes.diy/call-ai-v2": "14.3.175",
44
- "@vibes.diy/identity": "14.3.175",
45
- "@vibes.diy/use-vibes-types": "14.3.175",
42
+ "@vibes.diy/api-types": "14.3.176",
43
+ "@vibes.diy/call-ai-v2": "14.3.176",
44
+ "@vibes.diy/identity": "14.3.176",
45
+ "@vibes.diy/use-vibes-types": "14.3.176",
46
46
  "arktype": "~2.2.8",
47
47
  "json-schema-faker": "~0.6.3"
48
48
  },