stevensDemo.cursorrules10 matches
20### 2. HTTP Vals
2122- Create web APIs and endpoints
23- Handle HTTP requests and responses
24- Example structure:
66- Generate code in TypeScript
67- Add appropriate TypeScript types and interfaces for all data structures
68- Prefer official SDKs or libraries than writing API calls directly
69- Ask the user to supply API or library documentation if you are at all unsure about it
70- **Never bake in secrets into the code** - always use environment variables
71- Include comments explaining complex logic (avoid commenting obvious operations)
190- For AI-generated images, use: `https://maxm-imggenurl.web.val.run/the-description-of-your-image`
191- **Storage:** DO NOT use the Deno KV module for storage
192- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
193- **Weather Data:** Use open-meteo for weather data (doesn't require API keys) unless otherwise specified
194- **View Source:** Add a view source link with `import.meta.url.replace("esm.town", "val.town")` and include `target="_top"` attribute
195- **Error Debugging:** Add `<script src="https://esm.town/v/std/catch"></script>` to HTML to capture client-side errors
196- **Error Handling:** Only use try...catch when there's a clear local resolution; avoid catches that merely log or return 500s - let errors bubble up with full context
197- **Environment Variables:** Use `Deno.env.get('keyname')` and minimize their use - prefer APIs without keys
198- **Imports:** Use `https://esm.sh` for npm and Deno dependencies to ensure compatibility on server and browser
199- **Storage Strategy:** Only use backend storage if explicitly required; prefer simple static client-side sites
230231### Backend (Hono) Best Practices
232- Hono is the recommended API framework (similar to Express, Flask, or Sinatra)
233- Main entry point should be `backend/index.ts`
234- **Static asset serving:** Use the utility functions to read and serve project files:
251});
252```
253- Create RESTful API routes for CRUD operations
254- Be careful with error handling as Hono tends to swallow errors
255- Always include this snippet at the top-level Hono app to re-throwing errors to see full stack traces:
268- Use React 18.2.0 consistently in all imports and the `@jsxImportSource` pragma
269- Follow the React component pattern from the example project
270- Handle API calls properly with proper error catching
271272### Database Patterns
299- For files in the project, use `readFile` helpers
3003015. **API Design:**
302- `fetch` handler is the entry point for HTTP vals
303- Run the Hono app with `export default app.fetch // This is the entry point for HTTP vals`
stevensDemoApp.tsx8 matches
10import { NotebookView } from "./NotebookView.tsx";
1112const API_BASE = "/api/memories";
13const MEMORIES_PER_PAGE = 20; // Increased from 7 to 20 memories per page
149091// Fetch avatar image
92fetch("/api/images/stevens.jpg")
93.then((response) => {
94if (response.ok) return response.blob();
104105// Fetch wood background
106fetch("/api/images/wood.jpg")
107.then((response) => {
108if (response.ok) return response.blob();
133setError(null);
134try {
135const response = await fetch(API_BASE);
136if (!response.ok) {
137throw new Error(`HTTP error! status: ${response.status}`);
176177try {
178const response = await fetch(API_BASE, {
179method: "POST",
180headers: { "Content-Type": "application/json" },
199200try {
201const response = await fetch(`${API_BASE}/${id}`, {
202method: "DELETE",
203});
231232try {
233const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
234method: "PUT",
235headers: { "Content-Type": "application/json" },
606<div className="font-pixel text-[#f8f1e0]">
607<style jsx>{`
608@import url("https://fonts.googleapis.com/css2?family=Pixelify+Sans&display=swap");
609610@tailwind base;
12app.get("/frontend/**/*", c => serveFile(c.req.path, import.meta.url));
1314// Add your API routes here
15// app.get("/api/data", c => c.json({ hello: "world" }));
1617// Unwrap and rethrow Hono errors as the original error
vtProjectSearchstyles.tsx4 matches
782}
783784.api-info {
785margin-top: 10px;
786}
787788.api-info summary {
789cursor: pointer;
790color: var(--primary-color);
792}
793794.api-docs {
795background-color: var(--code-bg);
796padding: 10px 15px;
801}
802803.api-docs code {
804display: inline-block;
805background-color: white;
vtProjectSearchcomponents.tsx8 matches
1063<link rel="icon" href="https://fav.farm/👀" />
1064<meta name="viewport" content="width=device-width, initial-scale=1" />
1065<link rel="preconnect" href="https://fonts.googleapis.com" />
1066<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
1067<link
1068href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap"
1069rel="stylesheet"
1070/>
1080<a href="https://val.town" className="valtown-link" style={{ marginLeft: "auto" }}>Return to Val Town</a>
1081</h1>
1082<div className="api-info">
1083<details>
1084<summary>API Access</summary>
1085<div className="api-docs">
1086<p>
1087You can access search results via JSON API by adding <code>format=json</code> to your query:
1088</p>
1089{searchTerm
1241<div className="search-examples">
1242<a href="?q=fetch" className="example-link">fetch</a>
1243<a href="?q=api" className="example-link">api</a>
1244<a href="?q=database" className="example-link">database</a>
1245<a href="?q=image" className="example-link">image</a>
1396<div className="search-examples">
1397<a href="?q=fetch" className="example-link">fetch</a>
1398<a href="?q=api" className="example-link">api</a>
1399<a href="?q=database" className="example-link">database</a>
1400<a href="?q=image" className="example-link">image</a>
telegramBotStarterindex.ts1 match
30// This is a no-op if nothing's changed
31if (!isEndpointSet) {
32await bot.api.setWebhook(req.url, {
33secret_token: SECRET_TOKEN,
34});
x_posts_collect_mainmain.tsx59 matches
10const [query, setQuery] = React.useState("");
11const [targetAccount, setTargetAccount] = React.useState("");
12const [apiKey, setApiKey] = React.useState("");
13const [searchType, setSearchType] = React.useState("Latest");
14const [sinceDate, setSinceDate] = React.useState("");
39const checkAuth = async () => {
40try {
41const response = await fetch("/api/me");
42if (response.ok) {
43const userData = await response.json();
97setAuthError(null);
98try {
99const response = await fetch("/api/login", {
100method: "POST",
101headers: { "Content-Type": "application/json" },
120setAuthError(null);
121try {
122const response = await fetch("/api/register", {
123method: "POST",
124headers: { "Content-Type": "application/json" },
138const handleLogout = async () => {
139try {
140await fetch("/api/logout");
141setUser(null);
142// Clear sensitive data on logout
143setApiKey("");
144setTweets([]);
145setError(null);
154// No need to check for user here, useEffect handles it
155try {
156const response = await fetch("/api/searches");
157if (response.ok) {
158const searches = await response.json();
186if (!searchName) return;
187188const response = await fetch("/api/searches/save", {
189method: "POST",
190headers: { "Content-Type": "application/json" },
214if (!confirm("Are you sure you want to delete this saved search?")) return;
215try {
216const response = await fetch(`/api/searches/${searchId}/delete`, {
217method: "POST", // Using POST due to potential Val Town limitations
218});
242search.until_date,
243);
244// Ensure API key is present before running saved search
245if (!apiKey.trim()) {
246setError(
247"Please enter your SocialData API Key before running a saved search.",
248);
249return;
261return;
262}
263if (!apiKey.trim()) {
264setError("Please enter your SocialData API Key.");
265return;
266}
289params.append("cursor", currentCursor);
290}
291// *** Pass API key in header ***
292const response = await fetch(`/api/search?${params.toString()}`, {
293headers: { "X-Api-Key": apiKey }, // Pass API key here
294});
295306const errorData = await response
307.json()
308.catch(() => ({ message: `API Error: ${response.statusText}` }));
309throw new Error(
310errorData.message
311|| `API Error: ${response.statusText} on page ${pageNum}`,
312);
313}
318if (data.status === "error") {
319throw new Error(
320data.message || `API returned an error on page ${pageNum}`,
321);
322}
353}
354},
355[apiKey, user], // Add user dependency
356);
357367return;
368}
369if (!apiKey.trim()) {
370setError("Please enter your SocialData API Key.");
371return;
372}
562rel: "noopener noreferrer",
563},
564"SocialData API",
565),
566" | ",
616),
617),
618// --- API Key Input ---
619React.createElement(
620"div",
625React.createElement(
626"label",
627{ htmlFor: "apiKey", className: "label" },
628"API Key",
629),
630React.createElement("input", {
631type: "password",
632id: "apiKey",
633value: apiKey,
634onChange: (e) => setApiKey(e.target.value),
635placeholder: "Enter SocialData API Key",
636className: "input",
637}),
1088rel: "noopener noreferrer",
1089},
1090"SocialData API",
1091),
1092" | ",
1194}
11951196// --- API Endpoints ---
11971198// Register Endpoint
1199if (url.pathname === "/api/register" && request.method === "POST") {
1200try {
1201const { username, email, password } = await request.json();
12471248// Login Endpoint
1249if (url.pathname === "/api/login" && request.method === "POST") {
1250try {
1251const { email, password } = await request.json();
12901291// Get Current User Endpoint
1292if (url.pathname === "/api/me" && request.method === "GET") {
1293try {
1294const user = await getUserFromRequest(request);
13251326// Logout Endpoint
1327if (url.pathname === "/api/logout" && request.method === "GET") {
1328// Changed to GET for simplicity, POST is often preferred
1329return Response.json(
13411342// Save Search Endpoint
1343if (url.pathname === "/api/searches/save" && request.method === "POST") {
1344if (!user)
1345return Response.json({ error: "Not authenticated" }, { status: 401 });
13951396// Get Saved Searches Endpoint
1397if (url.pathname === "/api/searches" && request.method === "GET") {
1398if (!user)
1399return Response.json({ error: "Not authenticated" }, { status: 401 });
1415// Delete Saved Search Endpoint
1416if (
1417url.pathname.startsWith("/api/searches/")
1418&& url.pathname.endsWith("/delete")
1419&& (request.method === "POST" || request.method === "DELETE")
1447}
14481449// Search API Proxy Endpoint
1450if (url.pathname === "/api/search" && request.method === "GET") {
1451// *** LOGIN CHECK ADDED for the proxy endpoint ***
1452if (!user) {
1457}
14581459const apiKey = request.headers.get("X-Api-Key"); // Get API key from header sent by frontend
1460if (!apiKey) {
1461return Response.json(
1462{ status: "error", message: "API Key missing in request" },
1463{ status: 400 },
1464); // Changed status to 400
1476}
14771478const apiUrl = new URL("https://api.socialdata.tools/twitter/search");
1479apiUrl.searchParams.set("query", query);
1480apiUrl.searchParams.set("type", type);
1481if (cursor) apiUrl.searchParams.set("cursor", cursor);
14821483try {
1484const apiResponse = await fetch(apiUrl.toString(), {
1485headers: {
1486Authorization: `Bearer ${apiKey}`, // Use the API key from the request header
1487Accept: "application/json",
1488},
1491let responseData;
1492try {
1493responseData = await apiResponse.json();
1494} catch (e) {
1495const text = await apiResponse.text();
1496console.error("Non-JSON response from SocialData:", text);
1497return Response.json(
1498{
1499status: "error",
1500message: `SocialData API non-JSON response: ${apiResponse.statusText}`,
1501},
1502{ status: apiResponse.status },
1503);
1504}
15051506if (!apiResponse.ok || responseData.status === "error") {
1507console.error(
1508`SocialData API Error (${apiResponse.status}):`,
1509responseData,
1510);
1514status: "error",
1515message: responseData?.message
1516|| `SocialData API Error: ${apiResponse.statusText}`,
1517},
1518{ status: apiResponse.status },
1519);
1520}
9const [query, setQuery] = React.useState("");
10const [targetAccount, setTargetAccount] = React.useState("");
11const [apiKey, setApiKey] = React.useState("");
12const [searchType, setSearchType] = React.useState("Latest");
13const [sinceDate, setSinceDate] = React.useState("");
35const checkAuth = async () => {
36try {
37const response = await fetch("/api/me"); // サーバーに現在のユーザー情報を問い合わせる
38if (response.ok) {
39const userData = await response.json(); // 応答がOKならユーザーデータを取得
57if (!user) return; // ユーザーがログインしていない場合は何もしない(※useEffectの後に移動したのでこのチェックは不要かも)
58try {
59const response = await fetch("/api/searches"); // 保存済み検索APIを叩く
60if (response.ok) {
61const searches = await response.json();
9495try {
96const response = await fetch("/api/login", {
97// ログインAPIを叩く
98method: "POST",
99headers: { "Content-Type": "application/json" },
125126try {
127const response = await fetch("/api/register", {
128// 登録APIを叩く
129method: "POST",
130headers: { "Content-Type": "application/json" },
148const handleLogout = async () => {
149try {
150await fetch("/api/logout"); // ログアウトAPIを叩く
151setUser(null); // ユーザー情報をnullにする
152// setSavedSearches([]); // useEffectでuserが変わると自動でクリアされるので不要
178if (!searchName) return; // キャンセルされたら何もしない
179180const response = await fetch("/api/searches/save", {
181// 保存APIを叩く
182method: "POST",
183headers: { "Content-Type": "application/json" },
212213try {
214const response = await fetch(`/api/searches/${searchId}/delete`, {
215method: "POST", // Val Townの制限でDELETEが使えない場合があるのでPOSTを使う
216});
279const fetchAllTweets = React.useCallback(
280async (fullQuery, type, targetCount) => {
281if (!apiKey.trim()) {
282setError("Please enter your SocialData API Key.");
283return;
284}
306params.append("cursor", currentCursor);
307}
308const response = await fetch(`/api/search?${params.toString()}`, {
309headers: { "X-Api-Key": apiKey },
310});
311if (!isFetchingRef.current) break;
313const errorData = await response
314.json()
315.catch(() => ({ message: `API Error: ${response.statusText}` }));
316throw new Error(
317errorData.message ||
318`API Error: ${response.statusText} on page ${pageNum}`
319);
320}
323if (data.status === "error") {
324throw new Error(
325data.message || `API returned an error on page ${pageNum}`
326);
327}
356}
357},
358[apiKey]
359);
360365return;
366}
367if (!apiKey.trim()) {
368setError("Please enter your SocialData API Key.");
369return;
370}
596)
597),
598// API Key Section
599React.createElement(
600"div",
605React.createElement(
606"label",
607{ htmlFor: "apiKey", className: "label" },
608"API Key"
609),
610React.createElement("input", {
611type: "password",
612id: "apiKey",
613value: apiKey,
614onChange: (e) => setApiKey(e.target.value),
615placeholder: "Enter SocialData API Key",
616className: "input",
617})
1081rel: "noopener noreferrer",
1082},
1083"SocialData API"
1084),
1085" | ",
12011202// テーブル確認用エンドポイント
1203if (url.pathname === "/api/check-table") {
1204try {
1205const result = await sqlite.execute(`
12221223// ヘルパー関数テスト用エンドポイント(単純化)
1224if (url.pathname === "/api/test-helpers") {
1225try {
1226// パスワードハッシュ化のテスト
12471248// ユーザー登録エンドポイント
1249if (url.pathname === "/api/register") {
1250if (request.method !== "POST") {
1251return Response.json({ error: "Method not allowed" }, { status: 405 });
13381339// ユーザーログインエンドポイント
1340if (url.pathname === "/api/login") {
1341if (request.method !== "POST") {
1342return Response.json({ error: "Method not allowed" }, { status: 405 });
14091410// 現在のユーザー情報取得エンドポイント
1411if (url.pathname === "/api/me") {
1412try {
1413// ユーザー情報を取得
14461447// ログアウトエンドポイント
1448if (url.pathname === "/api/logout") {
1449return Response.json(
1450{ success: true },
14601461// 検索条件保存エンドポイント
1462if (url.pathname === "/api/searches/save") {
1463if (request.method !== "POST") {
1464return Response.json({ error: "Method not allowed" }, { status: 405 });
15451546// 保存済み検索一覧取得エンドポイント
1547if (url.pathname === "/api/searches") {
1548try {
1549// ユーザー情報を取得
1575// 保存済み検索削除エンドポイント
1576if (
1577url.pathname.startsWith("/api/searches/") &&
1578url.pathname.includes("/delete")
1579) {
1625}
16261627// 既存のAPI検索エンドポイント
1628if (url.pathname === "/api/search") {
1629const apiKey = request.headers.get("X-Api-Key");
1630if (!apiKey) {
1631return Response.json(
1632{ status: "error", message: "API Key missing" },
1633{ status: 401 }
1634);
1643);
1644}
1645const apiUrl = new URL("https://api.socialdata.tools/twitter/search");
1646apiUrl.searchParams.set("query", query);
1647apiUrl.searchParams.set("type", type);
1648if (cursor) apiUrl.searchParams.set("cursor", cursor);
16491650try {
1651const apiResponse = await fetch(apiUrl.toString(), {
1652headers: {
1653Authorization: `Bearer ${apiKey}`,
1654Accept: "application/json",
1655},
1657let responseData;
1658try {
1659responseData = await apiResponse.json();
1660} catch (e) {
1661const text = await apiResponse.text();
1662console.error("Non-JSON response:", text);
1663return Response.json(
1664{
1665status: "error",
1666message: `API non-JSON response: ${apiResponse.statusText}`,
1667},
1668{
1669status: apiResponse.status,
1670}
1671);
1672}
1673if (!apiResponse.ok || responseData.status === "error") {
1674console.error(`API Error (${apiResponse.status}):`, responseData);
1675return Response.json(
1676{
1677status: "error",
1678message:
1679responseData?.message || `API Error: ${apiResponse.statusText}`,
1680},
1681{ status: apiResponse.status }
1682);
1683}
stevensDemosendDailyBrief.ts8 matches
9798export async function sendDailyBriefing(chatId?: string, today?: DateTime) {
99// Get API keys from environment
100const apiKey = Deno.env.get("ANTHROPIC_API_KEY");
101const telegramToken = Deno.env.get("TELEGRAM_TOKEN");
102106}
107108if (!apiKey) {
109console.error("Anthropic API key is not configured.");
110return;
111}
122123// Initialize Anthropic client
124const anthropic = new Anthropic({ apiKey });
125126// Initialize Telegram bot
162163// disabled title for now, it seemes unnecessary...
164// await bot.api.sendMessage(chatId, `*${title}*`, { parse_mode: "Markdown" });
165166// Then send the main content
169170if (content.length <= MAX_LENGTH) {
171await bot.api.sendMessage(chatId, content, { parse_mode: "Markdown" });
172// Store the briefing in chat history
173await storeChatMessage(
198// Send each chunk as a separate message and store in chat history
199for (const chunk of chunks) {
200await bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
201// Store each chunk in chat history
202await storeChatMessage(