1import { API_URL } from "https://esm.town/v/std/API_URL";
2import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
3import ValTown from "npm:@valtown/sdk";
28);
29
30// Example of using the API to create a new val with a single file
31// First create the val
32const valResponse = await fetch(
33 `${API_URL}/v2/vals`,
34 {
35 method: "POST",
36 body: JSON.stringify({
37 name: "new-val-api",
38 privacy: "public",
39 description: "This is an example of using the API to create a val.",
40 }),
41 headers: {
53const val = await valResponse.json();
54const fileResponse = await fetch(
55 `${API_URL}/v2/vals/${val.id}/files?path=main.tsx`,
56 {
57 method: "POST",
58 body: JSON.stringify({
59 content: "This is an example of using the API to create a file at the root.",
60 type: "file",
61 }),
1This is an example of using the API to create a file at the root.
5 <head>
6 <title>Rock Paper Scissors</title>
7 <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
8 <style>
9 body {
9- **File Browser**: Select specific files to include in the context window for more focused AI assistance
10- **Branch Management**: View, select, and create branches without leaving the app
11- **Cost Tracking**: See estimated API usage costs for each interaction
12- **Sound Notifications**: Get alerted when Claude finishes responding
13- **Mobile-Friendly**: Works on both desktop and mobile devices
15## How It Works
16
171. **Login**: Authenticate with your Val Town API token and Anthropic API key
182. **Select a Project**: Choose which Val Town project you want to work on
193. **Select Files**: Browse your project files and select which ones to include in the context window
25### Prerequisites
26
27- A Val Town account with API access
28- An Anthropic API key (Claude 3.7 Sonnet)
29
30### Setup
31
321. Visit the OpenTownie app
332. Enter your Val Town API token (with `projects:write` and `users:read` permissions)
343. Enter your Anthropic API key
354. Click "Login" to access your projects
36
48- React frontend with TypeScript
49- React Router
50- Hono API server backend
51- Web Audio API for sound notifications
52- AI SDK for Claude integration
53
54The application proxies requests to the Anthropic API and Val Town API, allowing Claude to view and edit your project files directly.
55
56## Privacy & Security
57
58- Your Val Town API token and Anthropic API key are stored locally in your browser
59- No data is stored on our servers
60- All communication with the APIs is done directly from your browser
61
97
98export async function sendDailyBriefing(chatId?: string, today?: DateTime) {
99 // Get API keys from environment
100 const apiKey = Deno.env.get("ANTHROPIC_API_KEY");
101 const telegramToken = Deno.env.get("TELEGRAM_TOKEN");
102
106 }
107
108 if (!apiKey) {
109 console.error("Anthropic API key is not configured.");
110 return;
111 }
122
123 // Initialize Anthropic client
124 const anthropic = new Anthropic({ apiKey });
125
126 // Initialize Telegram bot
162
163 // disabled title for now, it seemes unnecessary...
164 // await bot.api.sendMessage(chatId, `*${title}*`, { parse_mode: "Markdown" });
165
166 // Then send the main content
169
170 if (content.length <= MAX_LENGTH) {
171 await bot.api.sendMessage(chatId, content, { parse_mode: "Markdown" });
172 // Store the briefing in chat history
173 await storeChatMessage(
198 // Send each chunk as a separate message and store in chat history
199 for (const chunk of chunks) {
200 await bot.api.sendMessage(chatId, chunk, { parse_mode: "Markdown" });
201 // Store each chunk in chat history
202 await storeChatMessage(
53You'll need to set up some environment variables to make it run.
54
55- `ANTHROPIC_API_KEY` for LLM calls
56- You'll need to follow [these instructions](https://docs.val.town/integrations/telegram/) to make a telegram bot, and set `TELEGRAM_TOKEN`. You'll also need to get a `TELEGRAM_CHAT_ID` in order to have the bot remember chat contents.
57- For the Google Calendar integration you'll need `GOOGLE_CALENDAR_ACCOUNT_ID` and `GOOGLE_CALENDAR_CALENDAR_ID`. See [these instuctions](https://www.val.town/v/stevekrouse/pipedream) for details.
8## Hono
9
10This app uses [Hono](https://hono.dev/) as the API framework. You can think of Hono as a replacement for [ExpressJS](https://expressjs.com/) that works in serverless environments like Val Town or Cloudflare Workers. If you come from Python or Ruby, Hono is also a lot like [Flask](https://github.com/pallets/flask) or [Sinatra](https://github.com/sinatra/sinatra), respectively.
11
12## Serving assets to the frontend
20### `index.html`
21
22The most complicated part of this backend API is serving index.html. In this app (like most apps) we serve it at the root, ie `GET /`.
23
24We *bootstrap* `index.html` with some initial data from the server, so that it gets dynamically injected JSON data without having to make another round-trip request to the server to get that data on the frontend. This is a common pattern for client-side rendered apps.
25
26## CRUD API Routes
27
28This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.
29
30## Errors
31
32Hono and other API frameworks have a habit of swallowing up Errors. We turn off this default behavior by re-throwing errors, because we think most of the time you'll want to see the full stack trace instead of merely "Internal Server Error". You can customize how you want errors to appear.
8import { type Memory } from "../../shared/types.ts";
9
10const API_BASE = "/api/memories";
11const MEMORIES_PER_PAGE = 20;
12
71 setError(null);
72 try {
73 const response = await fetch(API_BASE);
74 if (!response.ok) {
75 throw new Error(`HTTP error! status: ${response.status}`);
100
101 try {
102 const response = await fetch(API_BASE, {
103 method: "POST",
104 headers: { "Content-Type": "application/json" },
123
124 try {
125 const response = await fetch(`${API_BASE}/${id}`, {
126 method: "DELETE",
127 });
155
156 try {
157 const response = await fetch(`${API_BASE}/${editingMemory.id}`, {
158 method: "PUT",
159 headers: { "Content-Type": "application/json" },
18});
19
20// --- API Routes for Memories ---
21
22// GET /api/memories - Retrieve all memories
23app.get("/api/memories", async (c) => {
24 const memories = await getAllMemories();
25 return c.json(memories);
26});
27
28// POST /api/memories - Create a new memory
29app.post("/api/memories", async (c) => {
30 const body = await c.req.json<Omit<Memory, "id">>();
31 if (!body.text) {
36});
37
38// PUT /api/memories/:id - Update an existing memory
39app.put("/api/memories/:id", async (c) => {
40 const id = c.req.param("id");
41 const body = await c.req.json<Partial<Omit<Memory, "id">>>();
58});
59
60// DELETE /api/memories/:id - Delete a memory
61app.delete("/api/memories/:id", async (c) => {
62 const id = c.req.param("id");
63 try {
75// --- Blob Image Serving Routes ---
76
77// GET /api/images/:filename - Serve images from blob storage
78app.get("/api/images/:filename", async (c) => {
79 const filename = c.req.param("filename");
80
12 type="image/svg+xml"
13 />
14 <link rel="preconnect" href="https://fonts.googleapis.com" />
15 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
16 <link
17 href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400..700&display=swap"
18 rel="stylesheet"
19 />