World Vibe Web
The distributed app store for vibe-coded projects. Browse, discover, and install open-source apps from across GitHub.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Real gifts for what someone loves, with the reason. Taste links from Qloo, an agent on Cloudflare Workers AI, real product cards from Channel3, experience links from Tavily, PayPal sandbox guest checkout. Qloo hackathon entry, built by AI agents (Claude Code).
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Real gifts for what someone loves, with the reason.
The reason on every gift idea that Qloo links to a favourite is a contract: it must name the favourite that Qloo links most strongly to the idea and use a tag Qloo gives to both, or the server replaces it with a fixed sentence built from that link: "Qloo tags both X and Y as ...", or, when Qloo's data gives the two nothing in common, only "Qloo links X to Y." (the model is not asked about such an idea, so it cannot invent a quality). An idea Qloo links to none of the favourites says "Fits their overall taste" and claims nothing more. Under the reason the card shows Qloo's evidence: how Qloo splits its link to the idea between the favourites it names (shares that add up to 100%, not a chance of a match) and the tags it gives both. Every run in which the model is asked for reasons lists "N of M reasons kept" in the agent's lookups (M counts the ideas that have a tag in common; the other ideas get the fixed sentence without a model call), so you can check the contract yourself.
Type three to five things a person loves (a book, a band, a shop, a place) and a budget. Qloo reads their taste, an AI agent finds real products with real prices, every idea says "Because they love X" (or "Fits their overall taste" when Qloo links it to none of them), and you can pay in the PayPal sandbox as a guest. No account, and Tasteloom does not store the favourites you type.
Try it: https://tasteloom.kylne700.workers.dev/?images=on (free, no sign-in; Run a live example searches a fixed example set like any other search, within the same daily limits, and Replay the saved example costs no API calls; ?images=on shows product photos, which load from the shops' own servers and are off by default for privacy).
Project page and demo video: https://devpost.com/software/tasteloom.
Test logs: unit tests (2026-10-30), end-to-end run with a PayPal sandbox order (2026-10-22, live), end-to-end run of the screens on the current build (2026-10-30, Qloo and the model faked), Qloo contract check.
The saved example, with product photos off (the default). The page says it itself: the products and prices were live on the day it was recorded, and its taste matches and reasons are stand-ins.
This is an entry to the Qloo hackathon. It was built by AI agents (Claude Code), with a person setting the goals and owning the accounts the app uses.
Who it is for: anyone with a budget and no product in mind who knows a person only in fragments: a favourite book, band, shop or place.
Who could pay: the shop, not the gift-giver. A retailer or gifting marketplace with a gift-finder page could add a reason to each pick. We have not spoken to one and have no price. What we can state is the cost of one run, in the Limits table below.
Qloo is the only thing in the app that decides what fits a person's taste.
GET /search turns each favourite into a Qloo entity. A type hint (book, artist, brand, place and so on) is sent as types=urn:entity:<type>.GET /v2/insights is called once per gift category (brand, book, artist, place, destination) with signal.interests.entities (the ids of every favourite from step 1, in every call), filter.type and feature.explainability=true. It returns entities the person is likely to like and, for each one, which of the favourites drove it (query.explainability), strongest first. Anything the person typed is dropped from the results.One live run makes about 9 Qloo calls with four favourites (4 searches and 5 insight calls, one per gift category; measured 8 to 10 on 2026-10-22). The raw Qloo answers are used inside the request and not stored. npm run contract:qloo checks the real API against the shapes the code expects (5 calls).
An AI model runs a loop in which it chooses tool calls; the server decides whether each call is allowed.
@cf/qwen/qwen3-30b-a3b-fp8 on Cloudflare Workers AI, reached through a Worker binding (no API key). Set AI_AGENT_MODEL and AI_WRITER_MODEL to try another.qloo_search, qloo_insights, find_products.create_order, get_order, capture_order.One live run makes 5 to 6 model calls and uses up to about 110 neurons (Cloudflare's unit for Workers AI; 85 to 110 measured on 2026-10-22, and on 2026-10-24 a run plus its checkout used 8 model calls and 79 neurons in total). The checkout part of the live test (3 order creations and 1 capture) used 5 model calls and 15 neurons.
?images=on starts with them on), because a shop's image server sees your IP.Not sure what they love? That is the second way to start, in its own box under the favourites: Not sure what they love? Send them a link and let them tell you, with an Ask them button. It shows one link to send, with Copy, and Share where the browser has it. The link is the same for everyone: no name, no email, no account.
/ask: one line ("A friend wants to know what you love lately. Nothing is stored: your answer travels inside the link you send back."), the same favourites input (3 to 5, an optional type hint) and Make my reply link. The page does not mention a gift, a budget or a shop./#loves= plus base64url of UTF-8 JSON. Everything after the # stays in the browser, which never sends it to a server. It is encoded, not encrypted: anyone who holds the link can read the answer, and the messenger that carries it keeps its own copy.#... part is removed from the address bar with history.replaceState, and one line says "Filled from their reply". The reply carries no budget and starts nothing: you set the budget and press Find gift ideas as usual.Your own taste link. The home page has one small link, Make your own taste link (/ask?for=me), for the person on the receiving end. It is the same page with different words: the same favourites input, the same reply link (/#loves= plus the same encoding and caps) and nothing stored. Its result says "Send this to whoever buys you gifts. They see what you love, not what you will get." The ?for=me part only picks the wording (public/own.js, loaded only then, so a friend's page never carries it) and is not part of the answer. Whoever opens that link gets exactly what a reply link gives: the favourites filled in, and they set the budget and press Find gift ideas.
A reply is untrusted input, decoded whole or not at all: at most 5 items, each at most 80 characters after trimming, only the known type hints, control characters turned into spaces, no duplicates, at most 2048 characters of fragment. Anything else is ignored with one plain line, "That reply link could not be read, so nothing was filled in.", and your own favourites stay as they were.
Stored: nothing. The server never receives the answer, the /ask page makes no request after it loads, and it writes nothing to browser storage or cookies (a filled-in reply on the giver's screen is kept like anything typed there, in sessionStorage for that tab, as the Privacy section says). Sent: nothing, until the giver presses Find gift ideas; then the favourites go where they go today ("What is sent where"). npm run e2e -- ask checks this in a browser behind a recording proxy that refuses every billed route: no request carries the reply link, and /ask sends none of its own.
| Service | What it receives | Why |
|---|---|---|
| Qloo | the favourites as typed (and the type hint), then Qloo's own entity ids | to read taste |
| Cloudflare Workers AI | the favourites and budget, the entities Qloo returned, the tags Qloo gives both an idea and the favourite it links most strongly (up to four per idea), product titles and prices, and at checkout the ids of the picked items and the order's id, status and total | the model that drives the tools and writes the reasons |
| Channel3 | product searches built from Qloo's suggestions and the model's wording, with a price ceiling | real products and prices |
| Tavily | searches for an experience idea (a place or destination Qloo suggested) and price checks of a product on its shop's domain | experience links, informational price checks |
| PayPal sandbox | the order (items, total) and the buyer's return | the test payment; no real money moves |
Compare button, only when you press it: your favourites and your budget go to Cloudflare Workers AI, to the model Tasteloom writes its ideas with, with no Qloo data; nothing else is sent and the favourites are not stored.
Tasteloom does not store the favourites you type (the Privacy section lists the little it keeps). The answers of Channel3 and Tavily searches (product cards, experience links) are stored for 12 hours under a hash of the search, so the same search within 12 hours is answered from that store and not sent again. The other services keep what they receive under their own terms, which this project does not control.
| Service | Live when | Otherwise |
|---|---|---|
| Qloo | QLOO_API_KEY is set | demo data: a stand-in that returns the documented response shape through the same parser |
| AI model | the AI binding exists (Cloudflare Workers) | demo data: a stand-in speaking the same tool-calling format (unit tests, plain Node) |
| Tavily | TAVILY_API_KEY is set | off: the experience and price-check steps are skipped |
| Channel3 | CHANNEL3_API_KEY is set | off: no products |
| PayPal | PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET are set | off: no checkout (sandbox only either way) |
While a stand-in is active the page shows a Demo data badge and a note naming it. With the Qloo key set and the AI binding present, as in the deployed demo, both are live and no badge shows. FORCE_MOCK=qloo,ai forces the stand-ins. With no Qloo key npm run contract:qloo stops with "KEY MISSING" and exit code 2.
The demo is free and has no sign-in. Daily caps, counted per UTC day, keep it inside free plans. They are kept in one Durable Object so parallel requests cannot pass a cap; every billed call is reserved before it is made, and when the counters cannot be reached the call is refused.
| What is counted | Per visitor (IP, or IPv6 /64) | All visitors | One live run uses (measured) |
|---|---|---|---|
| Gift-idea runs | 8 | 100 | 1 |
| Qloo calls | 60 | 600 | about 9 (8 to 10) |
| Model calls | 80 | 600 | 5 to 6 (the checkout part of the live test, 3 order creations and 1 capture, used 5 more) |
| Model neurons | 1,500 | 8,000 | up to 110 (that checkout part used 15 more) |
| Channel3 searches | 24 | 25 or more (see below) | 4 to 6 (1 or 2 when the same search was made in the last 12 hours) |
| Tavily searches | 20 | 25 or more (see below) | 4 to 5 (1 when repeated within 12 hours) |
| Sandbox orders | 8 | 100 | 1 per payment |
| Returns from PayPal that still had work to do | 20 | 150 | 1 or 2 per payment |
| Clicks on Compare: same AI, no Qloo (see Compare in the app) | 8 | 40 | not part of a run; the model call each click makes is also counted under Model calls and Model neurons |
| Sandbox spend | $2,000 |
The Channel3 and Tavily numbers are the small ones on purpose: each service gives 1,000 free credits a month (its docs, read 2026-10-22; the balances in the dashboards were not re-read for this text), the demo has to keep working until judging ends (Nov 5, 2026, 11:45 pm ET, which is Nov 6, 04:45 UTC), and a fifth of the credits is held back. The 25 in the table is the default in the code; the deployed shared pool for these two can be higher, set at deploy time (see the end of this section). One visitor can still make about four ordinary runs a day (4 to 6 shop searches and 4 to 5 web searches each, as measured). A run that tries more suggestions uses more: the caps then pause shop or web search for the rest of the day and the run still returns its Qloo ideas. The other caps keep a day inside the free plans of Cloudflare (Workers AI neurons, 1,000 KV writes a day: the caps above allow about 850 at the very most; the saved example and an unchanged order write nothing) and give each visitor several runs.
What the page shows when a cap is reached. The services that only add to a run (shop search, web search, the model) pause on their own and the run goes on; the ones a run cannot do without (a run, Qloo) refuse it before it starts, so a refused run has spent no Qloo or model call.
Cloudflare's own limit sits behind ours. The Workers AI pricing page (read 2026-10-22) says: "Our free allocation allows anyone to use a total of 10,000 Neurons per day at no charge" and "If you exceed any one of the above limits, further operations will fail with an error." Our neuron cap (8,000) stops first; if Cloudflare refuses a call anyway, the model tier is skipped in the same way.
The caps are settings, not code: LIMIT_IP_* and LIMIT_GLOBAL_* (IDEAS, QLOO, AI, NEURONS, TAVILY, CHANNEL3, ORDERS, RETURNS, PLAIN), LIMIT_DAILY_CAP_USD, LIMIT_MAX_ITEMS and LIMIT_MAX_BUDGET_USD. The deployed values are all written in the vars of wrangler.jsonc (a test keeps them equal to the defaults in src/config.ts), so changing one for good means editing that file and deploying again. A single deploy can also override a value without touching the files: wrangler deploy --var LIMIT_GLOBAL_CHANNEL3:40 --var LIMIT_GLOBAL_TAVILY:40; the next deploy goes back to the file unless it repeats the flags. /api/status shows the shared caps (caps) next to today's usage (usage), so a deploy's values can be read back.
Needs Node 22.18 or newer and a Cloudflare login (npx wrangler login): the Workers AI binding runs on Cloudflare even in local development. Cloudflare's configuration docs say: "Using Workers AI always accesses your Cloudflare account in order to run AI models and will incur usage charges even in local development."
npm install
# keys go in ../.env.local (one folder above this repository), one KEY=value per line:
# QLOO_API_KEY TAVILY_API_KEY CHANNEL3_API_KEY PAYPAL_CLIENT_ID PAYPAL_CLIENT_SECRET
npm run dev # http://localhost:8791
npm run dev first runs scripts/dev-vars.mjs, which copies only those five keys from ../.env.local into .dev.vars (the file wrangler dev reads). .dev.vars is git-ignored and written with mode 600, and the script prints key names, never values. Keys are used only on the server: they are never sent to the browser, never logged, and error messages are redacted. npm run dev also sets DEV_CACHE=1, which caches Qloo and model answers while developing (Channel3 and Tavily answers are always cached for 12 hours; Qloo answers are not cached in production).
Optional settings (plain environment variables): AI_AGENT_MODEL, AI_WRITER_MODEL, FORCE_MOCK, PUBLIC_BASE_URL, and the LIMIT_* settings above (already set in wrangler.jsonc).
Deploying is not part of this repository's checks. A deployment needs the five keys as Worker secrets (wrangler secret put NAME asks for the value at a prompt or reads it from stdin, so it never appears in the command line, shell history or output). wrangler.jsonc already holds the AI binding and the spend meter, a SQLite-backed Durable Object (migration v1; Workers Free runs only this kind). The KV binding has no id: Cloudflare's docs say Wrangler creates a KV namespace without one at deploy time and writes the id back to the config file.
| Command | What it does |
|---|---|
npm run dev | local Worker with static assets on port 8791 |
npm test | unit tests (money rules, adapters on documented fixtures, malformed model JSON, spend counters, contrast of the stylesheet, static checks) |
npm run typecheck | tsc --noEmit on src/ |
npm run build | wrangler deploy --dry-run: bundles the Worker without deploying |
npm run e2e | real Chromium against wrangler dev: the full flow with a PayPal sandbox guest payment to COMPLETED, then a tour of every screen and error state at 1280 and 390 px (key screens also at 768 and 320 px), then the ask-them check (the /ask page and the reply link, at 1280 and 390 px); the screens go to evidence/screens/ (git-ignored). Everything captured from live services (run data, live screenshots, the dev server's log and state) goes to a folder outside the repository, tasteloom-live in the temp folder or TASTELOOM_LIVE_DIR. No run rewrites a tracked file: npm run e2e -- --evidence (both phases) refreshes evidence/e2e.txt. npm run e2e -- tour runs the tour alone and makes no billed call; npm run e2e -- ask runs the ask-them check alone and makes none either (a recording proxy lets through only static files, the status route and the saved example). FORCE_MOCK=qloo,ai node scripts/e2e.mjs tour ask makes the dev server use the Qloo and model stand-ins even when keys are present, and prints the server's mock list at the start. TOUR_BROWSER=firefox node scripts/e2e.mjs tour runs it in Firefox (/usr/bin/firefox, no downloads; shots go to evidence/debug/firefox/) |
npm run contract:qloo | cheap live check of the Qloo API against the shapes the code expects |
npm run scan | secret and privacy scan of the tracked files, built assets and git history; it only prints, and npm run scan -- --write also saves the report as evidence/scan.txt |
APPROVED with the same amount and currency; the request id makes a repeat harmless, and opening the return link twice does not capture twice.No accounts, no cookies. What you type is sent to the server to build your ideas, and the favourites are not stored; the section "What is sent where" lists which services receive it. The server keeps only the saved product cards and the budget (for server-side pricing, 2 hours; the saved example is not stored at all), orders (7 days), cached Channel3 and Tavily answers (12 hours, keyed by a hash of the search) and daily counters keyed by a hash of the day and your IP (IPv6: its /64 network), cleared the next UTC day. The browser keeps your inputs, finished run and picks in sessionStorage (this tab only) so the return from PayPal's cancel page restores them. The page ships no third-party logos or fonts; product images load from the shops' CDNs only after you turn on the image switch.
Plain HTML, CSS and ES modules in public/ (no framework, no build step). One warm palette with a single accent, three screens with a step indicator, every state designed (loading, empty, errors, limits, cancelled, failed capture, receipt), visible focus rings, keyboard use end to end, responsive from 320 px phones to desktop (every state is screenshot at 1280 and 390 px, the key screens also at 768 and 320 px), and a stylesheet whose colours are tested for WCAG contrast. npm run e2e checks accessibility basics, focus rings, layout shift and horizontal overflow in a real browser.
The measured comparison behind this panel used 20 made-up people, 4 gift ideas each and one run per condition. Method, every count, the limits and what is not verified are in docs/proof.md. Ideas that are merch of a favourite the person already loves: 73 of 76 from the model on its own, 66 of 80 from a bigger model on its own, 3 of 80 from Tasteloom (partly by design: it drops what you typed). Ideas with identical text to another person's idea: 17 of 80 for Tasteloom, 0 of 76 and 0 of 80 for the models on their own (a count made after we saw the data). That is mixed, not a win for Tasteloom; the proof page shows the other similarity checks. After a change designed after we saw those repeats on the same 20 people, one re-run gave 4 of 80 repeated ideas. It cost 42% more model compute per person (20 people, one run), was slower, and does not show the same result for other people.
Under the results, Compare: same AI, no Qloo asks the model Tasteloom writes with for four gift ideas from the favourites alone: no Qloo, shop or web lookup, only the favourites and the budget are sent to Workers AI, and the favourites are not stored. Ideas whose text names a favourite are tagged by a whole-word name match (full name; last name too for a favourite typed as a person); albums or characters are not detected, so the count is a hint.
It also works on the saved example (the plain ideas are asked live, the example is a recording). Each click is counted once by LIMIT_IP_PLAIN (8 a day per visitor) and LIMIT_GLOBAL_PLAIN (40 a day for everyone) on top of the model's own limits; at a limit the panel says so, and with no model connected it shows a fixed demo answer.
Part of the difference is by design: Tasteloom does not offer the favourites themselves as ideas. The prompt follows the template of the project's measured comparison (a type it did not use gets its own label); the name rule is a port of its rule with the same test cases (test/plain.test.ts).
npm run contract:qloo).evidence/first-build/ holds the files from 2026-10-16 and 2026-10-17 (the two reviews, the security test runs and four before/after pictures, two of which still show the Nebius steps), from the first build, when the model ran on Nebius; the model is now Cloudflare Workers AI.A loom weaves separate threads into one cloth. Tasteloom takes separate threads of taste (a book, a band, a shop, a city) and weaves them into gifts, keeping each thread visible in the "because" line.
MIT, see LICENSE.