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
90 value={customPath}
91 onChange={handlePathChange}
92 placeholder="Path (e.g., /api/data)"
93 />
94 </div>
8 const { isAuthenticated, authenticate, error } = useAuth();
9 const [tokenValue, setTokenValue] = useState("");
10 const [apiKey, setApiKey] = useState("");
11 // const [invalid, setInvalid] = useState(""); // TODO
12
13 const handleSubmit = (e) => {
14 e.preventDefault();
15 authenticate(tokenValue, apiKey);
16 }
17
36 >
37 <div>
38 <label htmlFor="valtown-token" className="label">Val Town API Token</label>
39 <input
40 type="password"
48 </div>
49 <div>
50 <label htmlFor="anthropic-api-key" className="label">Anthropic API Key</label>
51 <input
52 type="password"
53 id="anthropic-api-key"
54 name="anthropic-key"
55 value={apiKey}
56 onChange={e => {
57 setApiKey(e.target.value);
58 }}
59 />
11app.get("*", async (c, next) => {
12 const path = c.req.path;
13 if (path.startsWith("/api/") || c.req.header("Accept")?.includes("application/json")) {
14 return next();
15 }
21});
22
23app.route("/api", backend);
24app.get("/frontend/*", c => {
25 return serveFile(c.req.path, import.meta.url);
48 </h2>
49 <ol>
50 <li>Login with your Val Town API token</li>
51 <li>Add your Anthropic API key</li>
52 <li>Select a project to work on</li>
53 <li>Chat with Claude about your code</li>
86 </div>
87 <h3>Cost Tracking</h3>
88 <p>See estimated API usage costs for each interaction</p>
89 </div>
90 </section>
99 <ul>
100 <li>React frontend with TypeScript</li>
101 <li>Hono API server backend</li>
102 <li>Web Audio API for sound notifications</li>
103 <li>AI SDK for Claude integration</li>
104 </ul>
105 <p >
106 The application proxies requests to the Anthropic API and Val Town API, allowing Claude to view and edit your project files directly.
107 </p>
108 <div >
63 refetch: () => void;
64}) {
65 const { token, anthropicApiKey } = useAuth();
66 const [images, setImages] = useState<(string|null)[]>([]);
67 const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
81 project,
82 branchId,
83 anthropicApiKey,
84 bearerToken: token,
85 selectedFiles,
1import { searchWithSerpApi } from "https://esm.town/v/charmaine/searchWithSerpApi";
2import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
3
4// Customize your search parameters
5const KEYWORDS = "\"val town\" OR \"val.town\" OR \"val.run\"";
6const DISCORD_API_KEY = Deno.env.get("mentionsDiscord");
7const SERP_API_KEY = Deno.env.get("SERP_API_KEY");
8
9// Set isProd = false for testing and = true for production
13
14export async function redditAlert({ lastRunAt }: Interval) {
15 if (!SERP_API_KEY || !DISCORD_API_KEY) {
16 console.error("Missing SERP_API_KEY or Discord webhook URL. Exiting.");
17 return;
18 }
19
20 // Determine the time frame for the search
21 // Details on as_qdr: https://serpapi.com/advanced-google-query-parameters#api-parameters-advanced-search-query-parameters-as-qdr
22 const timeFrame = isProd
23 ? lastRunAt
27
28 try {
29 const response = await searchWithSerpApi({
30 query: KEYWORDS,
31 site: "reddit.com",
32 apiKey: SERP_API_KEY,
33 as_qdr: timeFrame,
34 });
62 if (isProd) {
63 await discordWebhook({
64 url: DISCORD_API_KEY,
65 content,
66 });
1console.log(Deno.env.get("SERP_API_KEY"));
72. Send notifications to your preferred platform (Discord, Slack, email, etc.)
8
9Reddit does not have an API that allows users to scrape data, so we are doing this with the Google Search API, [Serp](https://serpapi.com/).
10
11---
29
30---
31### 3. Get a SerpApi Key
32This template requires a [SerpApi](https://serpapi.com/) key to search Reddit posts via Google search results.
33
341. **Get a SerpApi key**:
35 - Sign up at [SerpApi](https://serpapi.com/) to create an account.
36 - Generate an API key from your account dashboard.
37
382. **Add the SerpApi key to your environment variables**:
39 - Go to your [Val Town environment variables](https://www.val.town/settings/environment-variables).
40 - Add a new key:
41 - Key: `SERP_API_KEY`
42 - Value: Your SERP API key.
43
44Without this key, the val will not function correctly.
75
76### NOTE: Usage Limits
77- **SerpApi:** Free SerpApi accounts have monthly call limits.
188 rel="noopener noreferrer"
189 >
190 OpenAI’s in-painting API
191 </a>{" "}
192 expects.