dreamer 0.0.0-sandbox-20251210004407 → 0.0.0-sandbox-20260105024945
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/dist/index.js +717 -292
- package/dist/template/.claude/may_edit.py +59 -0
- package/dist/template/.claude/settings.json +25 -0
- package/dist/template/.vscode/tasks.json +19 -0
- package/dist/template/CLAUDE-DATA-PLANNING.md +106 -0
- package/dist/template/CLAUDE.md +560 -0
- package/dist/template/agent.yaml.example +36 -0
- package/dist/template/agent.yaml.template +36 -0
- package/dist/template/build.ts +108 -0
- package/dist/template/drizzle.config.json +5 -0
- package/dist/template/examples/ReadContainerSize.tsx +52 -0
- package/dist/template/examples/components/LoadingIcon.tsx +22 -0
- package/dist/template/sdk-helpers/standalone-wrapper.tsx +43 -0
- package/dist/template/src/App.tsx +89 -0
- package/dist/template/src/globals.css +158 -0
- package/dist/template/src/schema.ts +6 -0
- package/dist/template/src/server.ts +52 -0
- package/package.json +4 -8
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# High-level Architecture
|
|
2
|
+
This application consists of a react frontend located in `src/App.tsx` and a backend
|
|
3
|
+
located in `src/server.ts`. The server is responsible for calling tools and persisting data.
|
|
4
|
+
The frontend calls functions on the server and does not persist any data of its own. It must
|
|
5
|
+
not use browser storage like `localStorage`; all persistent state should live in the server's
|
|
6
|
+
KV store. The frontend also must not call any remote APIs other than those defined in
|
|
7
|
+
`src/server.ts`.
|
|
8
|
+
|
|
9
|
+
The frontend has an SDK and helpers available via the `@dev-agents/sdk-client` package.
|
|
10
|
+
|
|
11
|
+
The server has an SDK and helpers available to it via the `@dev-agents/sdk-server` package. The server also has access to tools defined in `sdk-helpers/tools.ts` (these are
|
|
12
|
+
NOT available to the frontend).
|
|
13
|
+
|
|
14
|
+
Only edit files in the `src` directory.
|
|
15
|
+
|
|
16
|
+
# Server
|
|
17
|
+
The server is running in bun environment. It is composed of entry-points, which can
|
|
18
|
+
call tools, and persist data using the KV-store.
|
|
19
|
+
|
|
20
|
+
## Entry-points
|
|
21
|
+
The server defines its entry points using the `serverFunction` and `serverGetterFunction` helpers from `@dev-agents/sdk-server`.
|
|
22
|
+
These are functions that can be called from the frontend. Parameter schemas cannot use `Type.Optional`; if a
|
|
23
|
+
field may be omitted, use `Type.Union` with `Type.Null()` instead (e.g.,
|
|
24
|
+
`foo: Type.Union([Type.String(), Type.Null()])`).
|
|
25
|
+
|
|
26
|
+
- `serverGetterFunction` - creates functions for read functions, like getting the value from the KV-store.
|
|
27
|
+
Specify keys that should cause a refetch on this function in the `keyDependencies` array so that the client
|
|
28
|
+
can be reactive to state changes. Do not do things with side-effects in these functions. These are analagous
|
|
29
|
+
to HTTP GET methods.
|
|
30
|
+
- `serverFunction` - creates a server function for mutations, things that have side-effects. These are analagous
|
|
31
|
+
to HTTP POST methods.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
e.g. a server function for setting the user's city in a weather application that can be called from the frontend:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export const setUserLocation = serverFunction({
|
|
38
|
+
description: "Sets the user's location",
|
|
39
|
+
// The type of the parameter the client sends.
|
|
40
|
+
params: Type.Object({
|
|
41
|
+
location: Type.String({ minLength: 1, description: "The user's location as a geocodable string e.g. San Francisco, CA" }),
|
|
42
|
+
}),
|
|
43
|
+
execute: async (sdk: ServerSdk, { location }) => {
|
|
44
|
+
await await sdk.setValue("userLocation", location);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
e.g., a server function for returning the weather in the user's location that can be called from the frontend:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
export const getWeather = serverGetterFunction({
|
|
53
|
+
description: "Gets the weather at the user's location as previously provided by setUserLocation",
|
|
54
|
+
// The type of the parameter the client sends.
|
|
55
|
+
params: Type.Object({}),
|
|
56
|
+
// Have clients refetch when the "userLocation" Key's value changes.
|
|
57
|
+
keyDependencies: ["userLocation"],
|
|
58
|
+
execute: async (sdk: ServerSdk, { location }) => {
|
|
59
|
+
const userCity = await sdk.getValue<string>("userLocation");
|
|
60
|
+
const weather = await weather_api_service_getcurrentweather(
|
|
61
|
+
sdk,
|
|
62
|
+
{ location: userCity },
|
|
63
|
+
Type.Object({
|
|
64
|
+
tempFahrenheit: Type.Number(),
|
|
65
|
+
isRaining: Type.Boolean()
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return weather;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
All server functions can be called as tools by the user's sidekick or other agents. Therefore it is important to use the description field for the function and any params to explain exactly what the function does and how to call it. For example if you provide an "addNote" function which requires content in markdown format, make sure you specify that it needs markdown in the description.
|
|
75
|
+
|
|
76
|
+
`main` is a special entry point (and optional) to be used for executing scheduled background work
|
|
77
|
+
(if the user asks for that).
|
|
78
|
+
|
|
79
|
+
e.g., a main function that can be run periodically to create a weather bulletin:
|
|
80
|
+
```ts
|
|
81
|
+
export const main = serverFunction({
|
|
82
|
+
// Main entry point does not take any parameters.
|
|
83
|
+
params: Type.Object({}),
|
|
84
|
+
execute: async (sdk: ServerSdk) => {
|
|
85
|
+
const cities = ["San Francisco", "New York", "London"];
|
|
86
|
+
const weatherReports = [];
|
|
87
|
+
|
|
88
|
+
for (const city of cities) {
|
|
89
|
+
const weather = await weather_api_service_getcurrentweather(
|
|
90
|
+
sdk,
|
|
91
|
+
{ location: city },
|
|
92
|
+
Type.Object({
|
|
93
|
+
tempFahrenheit: Type.Number(),
|
|
94
|
+
isRaining: Type.Boolean(),
|
|
95
|
+
description: Type.String()
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
weatherReports.push({ city, ...weather });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Create a bulletin to notify the user
|
|
102
|
+
await create_agent_bulletin(
|
|
103
|
+
sdk,
|
|
104
|
+
{
|
|
105
|
+
shortMessage: `Weather update for ${cities.length} cities`,
|
|
106
|
+
attachments: weatherReports.map(report => ({
|
|
107
|
+
type: "markdown",
|
|
108
|
+
content: `**${report.city}**: ${report.tempFahrenheit}°F - ${report.description}`
|
|
109
|
+
})),
|
|
110
|
+
duration: "read_once"
|
|
111
|
+
},
|
|
112
|
+
Type.Object({})
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### TRIGGERS
|
|
119
|
+
|
|
120
|
+
The triggers defining when agent functions are called are defined in `agent.yaml` in a section called `triggers:`
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
triggers:
|
|
124
|
+
# Cron trigger - called automatically on schedule (defaults to 'main' entrypoint)
|
|
125
|
+
- type: cron
|
|
126
|
+
defaultSchedule: 0 */2 * * * # Every 2 hours
|
|
127
|
+
entrypoint: checkUpdates # Optional - defaults to 'main'
|
|
128
|
+
name: "Check for Updates" # Optional - custom name for the trigger
|
|
129
|
+
|
|
130
|
+
# Input trigger - called manually with input data
|
|
131
|
+
- type: input
|
|
132
|
+
contentTypes: ["text/url", "text/markdown"]
|
|
133
|
+
entrypoint: processText
|
|
134
|
+
name: "Process URLs and Markdown" # Optional - custom name for the trigger
|
|
135
|
+
|
|
136
|
+
# More examples
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Trigger Types:**
|
|
141
|
+
- **`cron`**: Automatically called on schedule (computed in user's local timezone). If no `entrypoint` is specified, defaults to `main`. Optional `name` field provides a custom display name for the trigger.
|
|
142
|
+
- **`input`**: Manually called via API or share sheet with optional input data. Requires an `entrypoint` to be specified. Optional `name` field provides a custom display name for the trigger.
|
|
143
|
+
|
|
144
|
+
**Common MIME Types for Input Triggers:**
|
|
145
|
+
- `text/url` or `text/uri-list` - URLs/links
|
|
146
|
+
- `text/plain` - Plain text
|
|
147
|
+
- `text/markdown` - Markdown
|
|
148
|
+
- `text/html` - HTML content
|
|
149
|
+
|
|
150
|
+
### Input Trigger Arguments
|
|
151
|
+
|
|
152
|
+
Input triggers receive arguments in this structure:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
|
|
156
|
+
export const processText = serverFunction({
|
|
157
|
+
description: "Process text and other input content",
|
|
158
|
+
params: Type.Object({}),
|
|
159
|
+
execute: async (sdk: ServerSdk, params: InputTriggerParams) => {
|
|
160
|
+
if (params.message) { // The message provides context about how to handle the content
|
|
161
|
+
console.log("User instructions:", params.message);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Process each attachment
|
|
165
|
+
for (const attachment of params.attachments || []) {
|
|
166
|
+
if (attachment.contentType === "text/url") {
|
|
167
|
+
console.log("URL:", attachment.data);
|
|
168
|
+
} else if (attachment.contentType.startsWith("text/markdown")) {
|
|
169
|
+
console.log("Markdown:", attachment.data);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Testing Functions and Triggers
|
|
177
|
+
|
|
178
|
+
#### Server Functions
|
|
179
|
+
Use the `agent-code call-server` command to test server functions:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Call a server function with no parameters
|
|
183
|
+
agent-code call-server myFunction '{}'
|
|
184
|
+
|
|
185
|
+
# Call a server function with parameters
|
|
186
|
+
agent-code call-server addUser '{"name": "John", "email": "john@example.com"}'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Testing Triggers
|
|
190
|
+
|
|
191
|
+
**Cron triggers** run automatically on schedule, or you can test them manually with `call-server`
|
|
192
|
+
|
|
193
|
+
**Input triggers** can be tested using the same `call-server` command:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Test an input trigger function with message and attachments
|
|
197
|
+
agent-code call-server handleInput '{
|
|
198
|
+
"message": "Add this to my work reading list",
|
|
199
|
+
"attachments": [
|
|
200
|
+
{
|
|
201
|
+
"name": "Example Web Page",
|
|
202
|
+
"contentType": "text/uri-list",
|
|
203
|
+
"data": "https://example.com"
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## KV Store
|
|
212
|
+
The server can persist data using a simple key-value store accessed through the SDK:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
// Store data
|
|
216
|
+
await sdk.setValue("user_preferences", { theme: "dark", language: "en" });
|
|
217
|
+
|
|
218
|
+
// Retrieve data with type safety
|
|
219
|
+
const preferences = await sdk.getValue<UserPreferences>("user_preferences");
|
|
220
|
+
|
|
221
|
+
// Store arrays or complex objects
|
|
222
|
+
const todos = await sdk.getValue<Todo[]>("todos") || [];
|
|
223
|
+
todos.push(newTodo);
|
|
224
|
+
await sdk.setValue("todos", todos);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The KV store is scoped per agent and per user. Data persists between function calls and user sessions.
|
|
228
|
+
|
|
229
|
+
## Tools
|
|
230
|
+
Tools are external APIs and services that your server can call. They are defined in `sdk-helpers/tools.ts` and automatically generated based on your account's available integrations.
|
|
231
|
+
|
|
232
|
+
If you need to call an external API, before using a generic function like `fetch`, you should first read `sdk-helpers/tools.ts` to see if a tool already exists for the API.
|
|
233
|
+
|
|
234
|
+
### Special Tools
|
|
235
|
+
|
|
236
|
+
#### Agent Bulletins
|
|
237
|
+
Use `create_agent_bulletin` to notify users of important information:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
await create_agent_bulletin(
|
|
241
|
+
sdk,
|
|
242
|
+
{
|
|
243
|
+
shortMessage: "Brief notification title",
|
|
244
|
+
attachments: [
|
|
245
|
+
{ type: "markdown", content: "**Bold** text and [links](https://example.com)" },
|
|
246
|
+
{ type: "url", content: "https://example.com" }
|
|
247
|
+
],
|
|
248
|
+
duration: "read_once" // Currently the only option
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Tool Selection
|
|
253
|
+
|
|
254
|
+
Explore tools with the CLI. When deciding which tools to use and how to call them, run tools directly to see real outputs. This helps you pick arguments and use the output.
|
|
255
|
+
|
|
256
|
+
For accessing external data with tools, you must consult `CLAUDE-DATA-PLANNING.md` for detailed instructions on creating a data plan.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Example: Inspect a tool's output to refine arguments and schemas
|
|
260
|
+
agent-code call-tool gmail_getgmailmessages '{"labelIds":["INBOX"],"maxResults":5,"pageToken":""}'
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Notes:
|
|
264
|
+
- The params argument must be a single JSON-stringified object (quote appropriately for your shell).
|
|
265
|
+
- For tools with no parameters, specify an empty json object, i.e. `{}`.
|
|
266
|
+
- If a tool clearly does mututations, do not test without asking the user.
|
|
267
|
+
- Use what you learn from step 1 to define precise `Type` schemas for tool outputs in your server functions.
|
|
268
|
+
- You must only use `Type` and related exports from `@dev-agents/sdk-shared`. DO NOT use `@sinclair/typebox` directly.
|
|
269
|
+
|
|
270
|
+
## Calling LLMs
|
|
271
|
+
As part of the SDK, you have access to a general-purpose LLM for via `sdk.callLLM`. You should us this method for
|
|
272
|
+
tasks like transforming text, classifying user input and so on. For example, if a tool has free-text or unknown output type,
|
|
273
|
+
you can stringify and pass to to the LLM with an output schema to extract structured information.
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { yelp_get_most_popular_restaurants } from "@sdk-helpers/tools";
|
|
277
|
+
|
|
278
|
+
const result: unknown = yelp_get_most_popular_restaurants(sdk, { city: "Toronto" });
|
|
279
|
+
|
|
280
|
+
const { italian, chinese } = sdk.callLLM(
|
|
281
|
+
`Extract the names of italian and chinese restaraunts from this list: ${JSON.stringify(result)}`,
|
|
282
|
+
// Desired structured output schema
|
|
283
|
+
Type.Object({
|
|
284
|
+
italian: Type.Array(Type.String()),
|
|
285
|
+
chinese: Type.Array(Type.String())
|
|
286
|
+
}));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### The sidekick
|
|
290
|
+
The sidekick is a powerful system agent which builds up in its memory a detailed view of the user, their personal connections, preferences
|
|
291
|
+
and information over time. It also has access to a wide range of tools. Any time you need to perform inference with the personal context of the user in mind, you should use the sidekick via `sdk.sidekick` or `sdk.sidekickWithSchema`. For example, if you are building an agent which gets information related to the user's city you could ask the sidekick to provide a default value for that city using its existing knowledge of the user.
|
|
292
|
+
|
|
293
|
+
# Client
|
|
294
|
+
The frontend is a React application that communicates with the server through typed function calls. It does not directly call external APIs or persist data - all data operations go through the server. The frontend must not have any routes.
|
|
295
|
+
|
|
296
|
+
The client has no routing - it's a single page application. Use React state and conditional rendering to handle different views.
|
|
297
|
+
|
|
298
|
+
## UI Reactivity and State Management
|
|
299
|
+
|
|
300
|
+
The UI should be reactive and performant. Follow these patterns:
|
|
301
|
+
|
|
302
|
+
### Optimistic Updates
|
|
303
|
+
When performing mutations, immediately update the UI before the server responds:
|
|
304
|
+
```tsx
|
|
305
|
+
const { mutateAsync: addTodo, isLoading } = useServerFunctionMutation<typeof addTodo>("addTodo");
|
|
306
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
307
|
+
|
|
308
|
+
const handleAdd = async (text: string) => {
|
|
309
|
+
const optimisticTodo = { id: Date.now(), text, completed: false };
|
|
310
|
+
|
|
311
|
+
// Optimistically add to UI
|
|
312
|
+
setTodos(prev => [...prev, optimisticTodo]);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const realTodo = await addTodo({ text });
|
|
316
|
+
// Replace optimistic version with server response
|
|
317
|
+
setTodos(prev => prev.map(t => t.id === optimisticTodo.id ? realTodo : t));
|
|
318
|
+
} catch (error) {
|
|
319
|
+
// Revert optimistic update on failure
|
|
320
|
+
setTodos(prev => prev.filter(t => t.id !== optimisticTodo.id));
|
|
321
|
+
// Show error to user
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
### State Management Guidelines
|
|
326
|
+
* Local UI State: Use React state for UI-only concerns (expanded panels, form inputs, modal visibility, loading states)
|
|
327
|
+
* Server State: Use server functions for data that persists or affects multiple sessions
|
|
328
|
+
* Never: Store transient UI state on the server (expanded accordions, active tabs, etc.)
|
|
329
|
+
|
|
330
|
+
## Hooks
|
|
331
|
+
The client SDK provides several hooks for interacting with server functions:
|
|
332
|
+
|
|
333
|
+
### useServerGetter
|
|
334
|
+
For reactive read operations that automatically refetch when dependencies change. Use this for data that you want to display reactively.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import { useServerGetterFunction } from "@dev-agents/sdk-client";
|
|
338
|
+
import type { getWeather } from "./server";
|
|
339
|
+
|
|
340
|
+
function WeatherDisplay() {
|
|
341
|
+
// This will automatically refetch when the "userLocation" key changes
|
|
342
|
+
// (if the server function declares it as a keyDependency)
|
|
343
|
+
const {
|
|
344
|
+
data: weather,
|
|
345
|
+
isLoading,
|
|
346
|
+
error
|
|
347
|
+
} = useServerGetterFunction<typeof getWeather>("getWeather");
|
|
348
|
+
|
|
349
|
+
if (isLoading) return <div>Loading weather...</div>;
|
|
350
|
+
if (error) return <div>Error loading weather: {String(error)}</div>;
|
|
351
|
+
if (!weather) return <div>No weather data</div>;
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div className="weather-card">
|
|
355
|
+
<h3>Current Weather</h3>
|
|
356
|
+
<p>Temperature: {weather.tempFahrenheit}°F</p>
|
|
357
|
+
<p>{weather.isRaining ? "It's raining" : "No rain"}</p>
|
|
358
|
+
</div>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### useServerFunctionMutation
|
|
364
|
+
For calling server functions.
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import { useServerFunctionMutation } from "@dev-agents/sdk-client";
|
|
368
|
+
import type { createTodo } from "./server";
|
|
369
|
+
|
|
370
|
+
function AddTodo() {
|
|
371
|
+
const [text, setText] = useState("");
|
|
372
|
+
|
|
373
|
+
const {
|
|
374
|
+
mutateAsync: createTodo,
|
|
375
|
+
isLoading
|
|
376
|
+
} = useServerFunctionMutation<typeof createTodo>("createTodo");
|
|
377
|
+
|
|
378
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
379
|
+
e.preventDefault();
|
|
380
|
+
await createTodo({ text });
|
|
381
|
+
setText("");
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<form onSubmit={handleSubmit}>
|
|
386
|
+
<input
|
|
387
|
+
value={text}
|
|
388
|
+
onChange={(e) => setText(e.target.value)}
|
|
389
|
+
disabled={isLoading}
|
|
390
|
+
/>
|
|
391
|
+
<button type="submit" disabled={isLoading}>
|
|
392
|
+
{isLoading ? "Adding..." : "Add Todo"}
|
|
393
|
+
</button>
|
|
394
|
+
</form>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
You can also use it to query data:
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
const { data, isLoading, mutate: getTodos } = useServerFunctionMutation<typeof getTodos>("getTodos");
|
|
403
|
+
const { mutate: addTodo, isLoading: isAdding } = useServerFunctionMutation<typeof addTodo>("addTodo");
|
|
404
|
+
|
|
405
|
+
// Refetch the data when the addTodo function is done, or when the component mounts.
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (!isAdding) {
|
|
408
|
+
getTodos();
|
|
409
|
+
}
|
|
410
|
+
}, [isAdding]);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
## Typing Server Function Calls
|
|
415
|
+
Always import the types from your server file and use them with the hooks:
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
// Import types (not the actual functions)
|
|
419
|
+
import type {
|
|
420
|
+
getTodos as _getTodos,
|
|
421
|
+
createTodo as _createTodo,
|
|
422
|
+
updateTodo as _updateTodo
|
|
423
|
+
} from "./server";
|
|
424
|
+
|
|
425
|
+
// Use in hooks with string names
|
|
426
|
+
const { mutateAsync: create } = useServerFunctionMutation<typeof _createTodo>("createTodo");
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Use TypeScript's type system to ensure parameter and return types match between client and server.
|
|
430
|
+
|
|
431
|
+
## User interface
|
|
432
|
+
|
|
433
|
+
### UI Types
|
|
434
|
+
|
|
435
|
+
The user interface defined in App.tsx will be rendered by the system either as a widget on a dashboard or as a full screen app (treat feed_item the same as full screen).
|
|
436
|
+
You can tell which mode is being rendered using `renderContext.type`
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
interface RenderContext {
|
|
440
|
+
type: "widget" | "app" | "feed_item";
|
|
441
|
+
data?: unknown;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export default function App({ renderContext }: { renderContext: RenderContext }) {
|
|
445
|
+
...
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
When rendering as a widget, keep in mind that the containing iframe, which can vary in dimensions, will be quite small - typically 300x300 px. Show only the most salient information and use compact text sizes etc. Content in widgets can scroll vertically but only the upper part is typically visible to the user, so make sure widget content starts at the very top of the container, and avoid scrolling unless necessary.
|
|
449
|
+
|
|
450
|
+
When rendering in "app" mode you are being displayed full screen but must still use responsive design as App.tsx renders across mobile and desktop devices. Do make sure that all critical features of your experience are available.
|
|
451
|
+
|
|
452
|
+
Do not add taglines or a description what the product does within the UI. Only include the app name in the main view if it is an important label. Space is at a premium, use it effectively. Keep the UI focused on content and functionality. Use traditional app structures like toolbars, sidebars, and icon buttons.
|
|
453
|
+
|
|
454
|
+
### Examples
|
|
455
|
+
|
|
456
|
+
Worked examples of various UI scenarios have been provided for you to consult in `examples/`, here's what's in there should you need it:
|
|
457
|
+
- ReadContainerSize.tsx : shows how App.tsx can track its own dimensions using ResizeObserver when it needs to know its size across different rendering contexts, while emphasizing that responsive design should be the preferred approach.
|
|
458
|
+
|
|
459
|
+
- examples/components/ : A set of standard system components, including:
|
|
460
|
+
- LoadingIcon.tsx - use this whenever content is loading. Prefer it over text or a custom icon.
|
|
461
|
+
|
|
462
|
+
### Colors
|
|
463
|
+
|
|
464
|
+
The app needs to render nicely in dark mode and light mode. Theme appropriate tailwind CSS is loaded in the window so you should strongly prefer to use semantic colors. Tailwind color modifiers available include:
|
|
465
|
+
|
|
466
|
+
--background
|
|
467
|
+
--foreground
|
|
468
|
+
--card
|
|
469
|
+
--card-foreground
|
|
470
|
+
--popover
|
|
471
|
+
--popover-foreground
|
|
472
|
+
--primary
|
|
473
|
+
--primary-foreground
|
|
474
|
+
--secondary
|
|
475
|
+
--secondary-foreground
|
|
476
|
+
--muted
|
|
477
|
+
--muted-foreground
|
|
478
|
+
--accent
|
|
479
|
+
--accent-foreground
|
|
480
|
+
--destructive
|
|
481
|
+
--destructive-foreground
|
|
482
|
+
--border
|
|
483
|
+
|
|
484
|
+
Strongly prefer semantic colors. Use explicit colors only when the user specifically asks for them. To set the foreground color for text you'd add the class `text-foreground`.
|
|
485
|
+
|
|
486
|
+
Do not include a dark/light mode switcher in the UI you build - this is set by the system.
|
|
487
|
+
|
|
488
|
+
### Icons
|
|
489
|
+
When creating UI, do not use Emoji as decoration or interface icons.
|
|
490
|
+
|
|
491
|
+
Unless otherwise specified, import and use icons in the Lucide set for a consistent appearance.
|
|
492
|
+
|
|
493
|
+
### Components
|
|
494
|
+
|
|
495
|
+
A set of standard system components is included in `examples/components`. Use these when possible.
|
|
496
|
+
|
|
497
|
+
Unless otherwise specified by the user, import and use components from shadcn, with the default theme provided in `globals.css`.
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# Building
|
|
502
|
+
The project includes a build script that compiles both frontend and server code:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Build everything
|
|
506
|
+
bun run build
|
|
507
|
+
|
|
508
|
+
# Build only server
|
|
509
|
+
bun run build --server-only
|
|
510
|
+
|
|
511
|
+
# Build only frontend
|
|
512
|
+
bun run build --frontend-only
|
|
513
|
+
|
|
514
|
+
# Build standalone frontend
|
|
515
|
+
bun run build --standalone
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The build process:
|
|
519
|
+
- Compiles TypeScript server code to `dist/server/`
|
|
520
|
+
- Builds React frontend with Tailwind CSS to `dist/frontend/`
|
|
521
|
+
- Keeps SDK imports external (they're provided by the runtime)
|
|
522
|
+
- Bundles all other dependencies
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# Development Steps
|
|
526
|
+
1. Create a data-plan for any required external data by following the steps in `CLAUDE-DATA-PLANNING.md`.
|
|
527
|
+
2. Implement server functions.
|
|
528
|
+
3. Verify server code typechecks and builds with `bun run typecheck` and `bun run build`
|
|
529
|
+
4. Implement client app to the specifications, using the server functions for getting / mutating data from
|
|
530
|
+
KV and tools. Make liberal use of console.log messages in server and client code to help you and the developer you are pairing with to understand what's going on. Logs add virtually no overhead at runtime so err on the side of lots of logging.
|
|
531
|
+
5. You can pull logs of recent agent runs to verify that things are working as expected using `agent-code logs`. `agent-code logs --run <runId>` gets logs from a specific run, you can find run IDs with `agent-code runs`. Note that you may have to ask the user to interact with the agentic app and then run `agent-code logs` to see the results. `console.log` invocations in both UI and server functions will show up in these logs.
|
|
532
|
+
|
|
533
|
+
### Data Sources
|
|
534
|
+
If the task requires getting information from the web, and is not information that can be obtained from another tool, first use `agent-code call-tool` with any web search or web crawling tools to try to identify a good, source or query to use in the implementation for reliably getting this information. Keep trying until you find a useable source of information, DO NOT give up and hardcode information in the app.
|
|
535
|
+
|
|
536
|
+
DO NOT EVER use mock or simulated data in the application unless instructed to do so by the user.
|
|
537
|
+
|
|
538
|
+
DO NOT EVER use APIs that require API keys, unless instructed to do so. Good sources will be websites that you can crawl an extract information from.
|
|
539
|
+
|
|
540
|
+
If you are going to use `fetch` from a URL you MUST test the fetch results first yourself by running the appropriate `curl` command in the terminal.
|
|
541
|
+
|
|
542
|
+
Repeat the steps for discovering an appropriate data source until you have found one that will work to build into the app.
|
|
543
|
+
|
|
544
|
+
## Final Steps and Verification
|
|
545
|
+
1. **Type Check**: Run `bun run typecheck` to verify all TypeScript types are correct (do not use any other comamand or script for typechecking)
|
|
546
|
+
2. **Build**: Run `bun run build` to ensure the project compiles successfully
|
|
547
|
+
3. **Client-Server Verification**: Verify that:
|
|
548
|
+
- Client is only calling functions defined by the server
|
|
549
|
+
- Function names match exactly between client and server
|
|
550
|
+
- Client properly uses generic typing with types imported from server
|
|
551
|
+
4. **Push the code**: Once complete use `agent-code push` to deploy the changes.
|
|
552
|
+
|
|
553
|
+
# Product requirements doc (PRD.md)
|
|
554
|
+
|
|
555
|
+
There is a file called `PRD.md` in the root directory. Unless you have been provided detailed instructions to the contrary read it and follow the instructions therein.
|
|
556
|
+
|
|
557
|
+
As you make changes based on the user's instructions, you must keep `PRD.md` up to date - imagine that you have to recreate the experience from that doc only. Make it clear and comprehensive and maintain the same structure. DO NOT use this as a checklist for implementation—it should stand on its own and accurately reflects the product that was built. Always push the code with `agent-code push` before updating PRD.md
|
|
558
|
+
|
|
559
|
+
# Toolchain error handling.
|
|
560
|
+
IMPORTANT - any time you see an error running agent-code or bun commands that is not related to the code you are editing (e.g. authentication issues, internal service errors) DO NOT try to debug and work around it. Just tell the user what went wrong. Of course, if a problem can be addressed by changing the code (e.g. typecheck errors, imports etc) go ahead and fix.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Agent Configuration Template
|
|
2
|
+
# This file shows the structure of agent.yaml with its configuration options
|
|
3
|
+
|
|
4
|
+
agentId: your-agent-id-here
|
|
5
|
+
name: Your Agent Name
|
|
6
|
+
hasUi: true
|
|
7
|
+
|
|
8
|
+
# UI Views configuration options
|
|
9
|
+
views:
|
|
10
|
+
- type: app
|
|
11
|
+
- type: widget
|
|
12
|
+
name: Single Note
|
|
13
|
+
identifier: note-widget
|
|
14
|
+
constraints:
|
|
15
|
+
minWidth: 120
|
|
16
|
+
minHeight: 120
|
|
17
|
+
maxWidth: 480
|
|
18
|
+
maxHeight: 480
|
|
19
|
+
- type: feed
|
|
20
|
+
|
|
21
|
+
# Optional triggers configuration
|
|
22
|
+
triggers:
|
|
23
|
+
- type: cron
|
|
24
|
+
defaultSchedule: "0 */2 * * *" # Every 2 hours
|
|
25
|
+
entrypoint: "main" # Optional - defaults to 'main'
|
|
26
|
+
name: "Check for Updates" # Optional - custom name for the trigger
|
|
27
|
+
|
|
28
|
+
- type: input
|
|
29
|
+
contentTypes: ["text/uri-list", "text/markdown"]
|
|
30
|
+
entrypoint: "processText"
|
|
31
|
+
name: "Process URLs and Markdown" # Optional - custom name for the trigger
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Optional tools configuration
|
|
35
|
+
allowedTools: []
|
|
36
|
+
requiredTools: []
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Agent Configuration Template
|
|
2
|
+
# This file shows the structure of agent.yaml with its configuration options
|
|
3
|
+
|
|
4
|
+
agentId: your-agent-id-here
|
|
5
|
+
name: Your Agent Name
|
|
6
|
+
hasUi: true
|
|
7
|
+
|
|
8
|
+
# UI Views configuration options
|
|
9
|
+
views:
|
|
10
|
+
- type: app
|
|
11
|
+
- type: widget
|
|
12
|
+
name: Single Note
|
|
13
|
+
identifier: note-widget
|
|
14
|
+
constraints:
|
|
15
|
+
minWidth: 120
|
|
16
|
+
minHeight: 120
|
|
17
|
+
maxWidth: 480
|
|
18
|
+
maxHeight: 480
|
|
19
|
+
- type: feed
|
|
20
|
+
|
|
21
|
+
# Optional triggers configuration
|
|
22
|
+
triggers:
|
|
23
|
+
- type: cron
|
|
24
|
+
defaultSchedule: "0 */2 * * *" # Every 2 hours
|
|
25
|
+
entrypoint: "main" # Optional - defaults to 'main'
|
|
26
|
+
name: "Check for Updates" # Optional - custom name for the trigger
|
|
27
|
+
|
|
28
|
+
- type: input
|
|
29
|
+
contentTypes: ["text/url", "text/markdown"]
|
|
30
|
+
entrypoint: "processText"
|
|
31
|
+
name: "Process URLs and Markdown" # Optional - custom name for the trigger
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Optional tools configuration
|
|
35
|
+
allowedTools: []
|
|
36
|
+
requiredTools: []
|