@standardagents/cli 0.8.1

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 ADDED
@@ -0,0 +1,1328 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import path3 from 'path';
4
+ import fs from 'fs';
5
+ import { parse, modify, applyEdits } from 'jsonc-parser';
6
+ import chalk from 'chalk';
7
+
8
+ function findWranglerConfig(cwd) {
9
+ const jsonc = path3.join(cwd, "wrangler.jsonc");
10
+ const json = path3.join(cwd, "wrangler.json");
11
+ if (fs.existsSync(jsonc)) {
12
+ return jsonc;
13
+ }
14
+ if (fs.existsSync(json)) {
15
+ return json;
16
+ }
17
+ return null;
18
+ }
19
+ function readWranglerConfig(filePath) {
20
+ const text = fs.readFileSync(filePath, "utf-8");
21
+ const config = parse(text);
22
+ return { config, text };
23
+ }
24
+ function updateWranglerConfig(filePath, text, updates) {
25
+ let result = text;
26
+ if (updates.durable_objects) {
27
+ const edits = modify(result, ["durable_objects"], updates.durable_objects, {});
28
+ result = applyEdits(result, edits);
29
+ }
30
+ if (updates.migrations) {
31
+ const edits = modify(result, ["migrations"], updates.migrations, {});
32
+ result = applyEdits(result, edits);
33
+ }
34
+ if (updates.env) {
35
+ for (const [envName, envConfig] of Object.entries(updates.env)) {
36
+ if (envConfig.durable_objects) {
37
+ const edits = modify(result, ["env", envName, "durable_objects"], envConfig.durable_objects, {});
38
+ result = applyEdits(result, edits);
39
+ }
40
+ if (envConfig.migrations) {
41
+ const edits = modify(result, ["env", envName, "migrations"], envConfig.migrations, {});
42
+ result = applyEdits(result, edits);
43
+ }
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ function writeWranglerConfig(filePath, content) {
49
+ fs.writeFileSync(filePath, content, "utf-8");
50
+ }
51
+ var logger = {
52
+ success: (message) => {
53
+ console.log(chalk.green("\u2713"), message);
54
+ },
55
+ error: (message) => {
56
+ console.log(chalk.red("\u2717"), message);
57
+ },
58
+ warning: (message) => {
59
+ console.log(chalk.yellow("\u26A0"), message);
60
+ },
61
+ info: (message) => {
62
+ console.log(chalk.blue("\u2139"), message);
63
+ },
64
+ log: (message) => {
65
+ console.log(message);
66
+ }
67
+ };
68
+
69
+ // src/commands/init.ts
70
+ async function init(options = {}) {
71
+ var _a, _b, _c, _d, _e;
72
+ const cwd = process.cwd();
73
+ logger.info("Initializing Standard Agents configuration...");
74
+ const configPath = findWranglerConfig(cwd);
75
+ if (!configPath) {
76
+ logger.error("No wrangler.jsonc or wrangler.json found in current directory");
77
+ logger.log("\nTo use Standard Agents, you need a Cloudflare Workers project with wrangler configuration.");
78
+ logger.log("\nExample wrangler.jsonc:");
79
+ logger.log(`
80
+ {
81
+ "name": "my-agent",
82
+ "main": "server/index.ts",
83
+ "compatibility_date": "2025-08-13",
84
+ "compatibility_flags": ["nodejs_compat"],
85
+
86
+ "durable_objects": {
87
+ "bindings": [
88
+ {
89
+ "name": "AGENT_BUILDER_THREAD",
90
+ "class_name": "DurableThread"
91
+ },
92
+ {
93
+ "name": "AGENT_BUILDER",
94
+ "class_name": "DurableAgentBuilder"
95
+ }
96
+ ]
97
+ },
98
+
99
+ "migrations": [
100
+ {
101
+ "tag": "v1",
102
+ "new_sqlite_classes": ["DurableThread", "DurableAgentBuilder"]
103
+ }
104
+ ]
105
+ }
106
+ `);
107
+ process.exit(1);
108
+ }
109
+ logger.success(`Found configuration: ${path3.relative(cwd, configPath)}`);
110
+ const { config, text } = readWranglerConfig(configPath);
111
+ const hasAgentBuilderThread = (_b = (_a = config.durable_objects) == null ? void 0 : _a.bindings) == null ? void 0 : _b.some(
112
+ (binding) => binding.name === "AGENT_BUILDER_THREAD"
113
+ );
114
+ const hasAgentBuilder = (_d = (_c = config.durable_objects) == null ? void 0 : _c.bindings) == null ? void 0 : _d.some(
115
+ (binding) => binding.name === "AGENT_BUILDER"
116
+ );
117
+ if (hasAgentBuilderThread && hasAgentBuilder && !options.force) {
118
+ logger.success("Standard Agents is already configured!");
119
+ logger.info("Use --force to overwrite existing configuration");
120
+ return;
121
+ }
122
+ const updates = {};
123
+ updates.durable_objects = config.durable_objects || { bindings: [] };
124
+ if (options.force) {
125
+ updates.durable_objects.bindings = updates.durable_objects.bindings.filter(
126
+ (binding) => binding.name !== "AGENT_BUILDER_THREAD" && binding.name !== "AGENT_BUILDER"
127
+ );
128
+ }
129
+ if (!hasAgentBuilderThread || options.force) {
130
+ updates.durable_objects.bindings.push({
131
+ name: "AGENT_BUILDER_THREAD",
132
+ class_name: "DurableThread"
133
+ });
134
+ logger.info("Added AGENT_BUILDER_THREAD binding");
135
+ }
136
+ if (!hasAgentBuilder || options.force) {
137
+ updates.durable_objects.bindings.push({
138
+ name: "AGENT_BUILDER",
139
+ class_name: "DurableAgentBuilder"
140
+ });
141
+ logger.info("Added AGENT_BUILDER binding");
142
+ }
143
+ if (!config.migrations || config.migrations.length === 0) {
144
+ updates.migrations = [
145
+ {
146
+ tag: "v1",
147
+ new_sqlite_classes: ["DurableThread", "DurableAgentBuilder"]
148
+ }
149
+ ];
150
+ logger.info("Added Durable Object migrations");
151
+ }
152
+ if ((_e = config.env) == null ? void 0 : _e.test) {
153
+ updates.env = updates.env || {};
154
+ updates.env.test = config.env.test;
155
+ if (!updates.env.test.durable_objects) {
156
+ updates.env.test.durable_objects = { bindings: [] };
157
+ }
158
+ const hasTestThread = updates.env.test.durable_objects.bindings.some(
159
+ (binding) => binding.name === "AGENT_BUILDER_THREAD"
160
+ );
161
+ const hasTestBuilder = updates.env.test.durable_objects.bindings.some(
162
+ (binding) => binding.name === "AGENT_BUILDER"
163
+ );
164
+ if (!hasTestThread) {
165
+ updates.env.test.durable_objects.bindings.push({
166
+ name: "AGENT_BUILDER_THREAD",
167
+ class_name: "DurableThread"
168
+ });
169
+ logger.info("Added test environment AGENT_BUILDER_THREAD binding");
170
+ }
171
+ if (!hasTestBuilder) {
172
+ updates.env.test.durable_objects.bindings.push({
173
+ name: "AGENT_BUILDER",
174
+ class_name: "DurableAgentBuilder"
175
+ });
176
+ logger.info("Added test environment AGENT_BUILDER binding");
177
+ }
178
+ if (!updates.env.test.migrations) {
179
+ updates.env.test.migrations = [
180
+ {
181
+ tag: "v1",
182
+ new_sqlite_classes: ["DurableThread", "DurableAgentBuilder"]
183
+ }
184
+ ];
185
+ logger.info("Added test environment Durable Object migrations");
186
+ }
187
+ }
188
+ const updatedText = updateWranglerConfig(configPath, text, updates);
189
+ writeWranglerConfig(configPath, updatedText);
190
+ logger.success("Configuration updated successfully!");
191
+ logger.log("\nNext steps:");
192
+ logger.log("1. Create your agent definitions in agents/agents/");
193
+ logger.log("2. Start your development server");
194
+ }
195
+ var TOOLS_CLAUDE_MD = `# Custom Tools
196
+
197
+ This directory contains custom tools that your AI agents can call during execution.
198
+
199
+ ## What Are Tools?
200
+
201
+ Tools are functions that extend your agent's capabilities beyond text generation. They allow agents to:
202
+ - Fetch external data (APIs, databases)
203
+ - Perform calculations
204
+ - Execute side effects (send emails, create records)
205
+ - Chain to other prompts or agents
206
+
207
+ ## Creating a Tool
208
+
209
+ Create a new file in this directory following the naming convention:
210
+
211
+ \`\`\`
212
+ agents/tools/
213
+ \u251C\u2500\u2500 my_tool.ts # \u2713 snake_case recommended
214
+ \u251C\u2500\u2500 another_tool.ts # \u2713 Valid
215
+ \u2514\u2500\u2500 CamelCaseTool.ts # \u26A0 Works but triggers warning
216
+ \`\`\`
217
+
218
+ ### Tool File Structure
219
+
220
+ Each tool file should export a default function created with \`defineTool\`:
221
+
222
+ \`\`\`typescript
223
+ import { defineTool } from '@standardagents/builder';
224
+ import { z } from 'zod';
225
+
226
+ // With arguments
227
+ export default defineTool(
228
+ 'Description of what this tool does',
229
+ z.object({
230
+ param1: z.string().describe('Description of param1'),
231
+ param2: z.number().optional().describe('Optional parameter'),
232
+ }),
233
+ async (flow, args) => {
234
+ // Tool implementation
235
+ const result = await doSomething(args.param1, args.param2);
236
+
237
+ return {
238
+ status: 'success',
239
+ result: JSON.stringify(result)
240
+ };
241
+ }
242
+ );
243
+
244
+ // Without arguments
245
+ export default defineTool(
246
+ 'Simple tool with no parameters',
247
+ async (flow) => {
248
+ return {
249
+ status: 'success',
250
+ result: 'Done!'
251
+ };
252
+ }
253
+ );
254
+ \`\`\`
255
+
256
+ ### FlowState Object
257
+
258
+ The \`flow\` parameter provides execution context:
259
+
260
+ \`\`\`typescript
261
+ interface FlowState {
262
+ env: Env; // Cloudflare bindings (KV, R2, etc.)
263
+ storage: DurableObjectStorage; // Thread's SQLite storage
264
+ threadId: string; // Current thread ID
265
+ agentId: string; // Current agent ID
266
+ currentSide: 'a' | 'b'; // Which side is executing
267
+ turnCount: number; // Current turn number
268
+ context: Record<string, any>; // Arbitrary state data
269
+ // ... and more
270
+ }
271
+ \`\`\`
272
+
273
+ ### Return Value
274
+
275
+ Tools must return a ToolResult object:
276
+
277
+ \`\`\`typescript
278
+ interface ToolResult {
279
+ status: 'success' | 'error';
280
+ result?: string; // Success data
281
+ error?: string; // Error message
282
+ }
283
+ \`\`\`
284
+
285
+ ## Tool Discovery
286
+
287
+ Tools are **auto-discovered** at runtime. No manual registration needed!
288
+
289
+ 1. Vite plugin scans this directory on startup
290
+ 2. Generates virtual module with dynamic imports
291
+ 3. Tools become available to all agents
292
+ 4. HMR (Hot Module Replacement) works in development
293
+
294
+ ## Examples
295
+
296
+ ### API Fetch Tool
297
+
298
+ \`\`\`typescript
299
+ import { defineTool } from '@standardagents/builder';
300
+ import { z } from 'zod';
301
+
302
+ export default defineTool(
303
+ 'Fetch weather data for a city',
304
+ z.object({
305
+ city: z.string().describe('City name'),
306
+ units: z.enum(['metric', 'imperial']).optional(),
307
+ }),
308
+ async (flow, args) => {
309
+ try {
310
+ const response = await fetch(
311
+ \`https://api.weather.com/\${args.city}\`
312
+ );
313
+ const data = await response.json();
314
+
315
+ return {
316
+ status: 'success',
317
+ result: JSON.stringify(data),
318
+ };
319
+ } catch (error) {
320
+ return {
321
+ status: 'error',
322
+ error: error.message,
323
+ };
324
+ }
325
+ }
326
+ );
327
+ \`\`\`
328
+
329
+ ### Thread Storage Tool
330
+
331
+ \`\`\`typescript
332
+ import { defineTool } from '@standardagents/builder';
333
+ import { z } from 'zod';
334
+
335
+ export default defineTool(
336
+ 'Get custom data from thread storage',
337
+ z.object({
338
+ key: z.string().describe('Storage key to look up'),
339
+ }),
340
+ async (flow, args) => {
341
+ try {
342
+ // Use thread's SQLite storage
343
+ const result = await flow.storage.sql.exec(
344
+ \`SELECT value FROM custom_data WHERE key = ?\`,
345
+ args.key
346
+ ).toArray();
347
+
348
+ if (result.length === 0) {
349
+ return {
350
+ status: 'error',
351
+ error: 'Data not found',
352
+ };
353
+ }
354
+
355
+ return {
356
+ status: 'success',
357
+ result: JSON.stringify(result[0]),
358
+ };
359
+ } catch (error) {
360
+ return {
361
+ status: 'error',
362
+ error: error.message,
363
+ };
364
+ }
365
+ }
366
+ );
367
+ \`\`\`
368
+
369
+ ## Best Practices
370
+
371
+ 1. **Descriptive Names**: Use clear, action-oriented names (e.g., \`fetch_weather\`, not \`weather\`)
372
+ 2. **Detailed Descriptions**: The description helps the LLM understand when to use the tool
373
+ 3. **Zod Descriptions**: Add \`.describe()\` to all schema fields for better LLM understanding
374
+ 4. **Error Handling**: Always wrap in try/catch and return proper error status
375
+ 5. **Type Safety**: Use Zod for runtime validation and TypeScript inference
376
+ 6. **Keep It Simple**: Each tool should do one thing well
377
+ 7. **Stateless**: Don't rely on global state; use FlowState for context
378
+
379
+ ## Debugging
380
+
381
+ - Check console output for tool execution logs
382
+ - Tool errors appear in the logs table
383
+ - Use \`console.log\` within tools (visible in dev mode)
384
+ - Check message history to see tool calls and responses
385
+
386
+ ## Testing
387
+
388
+ Tools can be tested independently:
389
+
390
+ \`\`\`typescript
391
+ import myTool from './my_tool';
392
+
393
+ const [description, schema, handler] = myTool;
394
+
395
+ // Mock FlowState
396
+ const mockFlow = {
397
+ env: mockEnv,
398
+ storage: mockStorage,
399
+ threadId: 'test-123',
400
+ // ...
401
+ };
402
+
403
+ const result = await handler(mockFlow, { param1: 'test' });
404
+ console.log(result);
405
+ \`\`\`
406
+
407
+ ## Limitations
408
+
409
+ - No nested directories (tools must be directly in this folder)
410
+ - Tool execution is **sequential** (no parallel execution)
411
+ - Tool results must be JSON-serializable strings
412
+ - Maximum execution time limited by Workers CPU time
413
+
414
+ ## Related
415
+
416
+ - **Hooks**: \`agents/hooks/CLAUDE.md\` - Lifecycle hooks
417
+ - **APIs**: \`agents/api/CLAUDE.md\` - Thread-specific endpoints
418
+ - **Documentation**: Project root \`CLAUDE.md\` for architecture overview
419
+ `;
420
+ var HOOKS_CLAUDE_MD = `# Lifecycle Hooks
421
+
422
+ This directory contains lifecycle hooks that intercept and modify agent execution at key points.
423
+
424
+ ## What Are Hooks?
425
+
426
+ Hooks are optional functions that run at specific points during agent execution:
427
+ - **Before** LLM requests (prefilter messages)
428
+ - **After** LLM responses (post-process messages)
429
+ - At other lifecycle events (message creation, tool execution, etc.)
430
+
431
+ Unlike tools (which agents explicitly call), hooks run **automatically** when their trigger point occurs.
432
+
433
+ ## Using defineHook for Type Safety
434
+
435
+ All hooks should use the \`defineHook\` utility for strict typing:
436
+
437
+ \`\`\`typescript
438
+ import { defineHook } from '@standardagents/builder';
439
+
440
+ export default defineHook('filter_messages', async (state, rows) => {
441
+ // TypeScript knows exactly what state and rows are!
442
+ return rows;
443
+ });
444
+ \`\`\`
445
+
446
+ The \`defineHook\` function provides:
447
+ - **Strict typing** for hook parameters based on the hook name
448
+ - **IntelliSense** support in your editor
449
+ - **Type checking** to catch errors before runtime
450
+ - **Better documentation** through type hints
451
+
452
+ ## Available Hooks
453
+
454
+ ### \`filter_messages\`
455
+
456
+ **When**: Before message history is transformed to chat completion format
457
+ **Purpose**: Filter or modify SQL row data with access to ALL database columns
458
+
459
+ \`\`\`typescript
460
+ import { defineHook, type MessageRow } from '@standardagents/builder';
461
+
462
+ export default defineHook('filter_messages', async (state, rows) => {
463
+ // rows contains ALL columns from messages table:
464
+ // id, role, content, name, tool_calls, tool_call_id, log_id,
465
+ // created_at, request_sent_at, response_completed_at, status,
466
+ // silent, tool_status
467
+
468
+ // Your filtering logic here
469
+ return rows;
470
+ });
471
+ \`\`\`
472
+
473
+ **Common Use Cases**:
474
+ - Filter out failed tool messages (\`tool_status = 'error'\`)
475
+ - Remove messages older than a certain time
476
+ - Filter by status (pending, completed, failed)
477
+ - Access columns not available in chat format
478
+ - Filter based on database-specific metadata
479
+
480
+ **Example - Filter Out Failed Tools**:
481
+ \`\`\`typescript
482
+ export default defineHook('filter_messages', async (state, rows) => {
483
+ // Remove tool messages that failed execution
484
+ return rows.filter(row => {
485
+ if (row.role === 'tool' && row.tool_status === 'error') {
486
+ return false;
487
+ }
488
+ return true;
489
+ });
490
+ });
491
+ \`\`\`
492
+
493
+ **Important**: This hook runs **before** messages are transformed to Message objects and receives raw SQL data. Use this when you need access to database columns like \`tool_status\`, \`silent\`, or \`status\`.
494
+
495
+ ### \`prefilter_llm_history\`
496
+
497
+ **When**: Immediately after message transformation, before sending to LLM
498
+ **Purpose**: Modify, filter, or limit message history in chat completion format
499
+
500
+ \`\`\`typescript
501
+ import { defineHook } from '@standardagents/builder';
502
+
503
+ export default defineHook('prefilter_llm_history', async (state, messages) => {
504
+ // messages are in chat completion format (already transformed from SQL rows)
505
+ // Available fields: role, content, tool_calls, tool_call_id, name
506
+
507
+ // Your filtering logic here
508
+ return messages;
509
+ });
510
+ \`\`\`
511
+
512
+ **Common Use Cases**:
513
+ - Limit conversation history to last N messages
514
+ - Remove old tool messages to reduce token usage
515
+ - Summarize old messages before sending
516
+ - Add dynamic context based on message patterns
517
+ - Filter out sensitive information
518
+
519
+ **Example - Limit to Last 10 Messages**:
520
+ \`\`\`typescript
521
+ import { defineHook } from '@standardagents/builder';
522
+
523
+ export default defineHook('prefilter_llm_history', async (state, messages) => {
524
+ // Keep all system messages
525
+ const systemMessages = messages.filter(m => m.role === 'system');
526
+
527
+ // Take only last 10 non-system messages
528
+ const otherMessages = messages
529
+ .filter(m => m.role !== 'system')
530
+ .slice(-10);
531
+
532
+ return [...systemMessages, ...otherMessages];
533
+ });
534
+ \`\`\`
535
+
536
+ **Example - Remove Old Tool Messages**:
537
+ \`\`\`typescript
538
+ import { defineHook } from '@standardagents/builder';
539
+
540
+ export default defineHook('prefilter_llm_history', async (state, messages) => {
541
+ // Keep messages from last 5 turns, remove old tool messages
542
+ const recentThreshold = messages.length - 10;
543
+
544
+ return messages.filter((m, index) => {
545
+ if (m.role === 'tool' && index < recentThreshold) {
546
+ return false; // Remove old tool messages
547
+ }
548
+ return true;
549
+ });
550
+ });
551
+ \`\`\`
552
+
553
+ **Important**: The **filtered messages are logged**, so you can see exactly what was sent to the LLM in the logs table.
554
+
555
+ ### \`post_process_message\`
556
+
557
+ **When**: After receiving a response from the LLM
558
+ **Purpose**: Modify or enhance the assistant's message
559
+
560
+ \`\`\`typescript
561
+ import { defineHook } from '@standardagents/builder';
562
+
563
+ export default defineHook('post_process_message', async (state, message) => {
564
+ // Your post-processing logic here
565
+ return message;
566
+ });
567
+ \`\`\`
568
+
569
+ **Common Use Cases**:
570
+ - Format or clean up LLM output
571
+ - Add metadata or tracking information
572
+ - Inject additional context into responses
573
+ - Transform tool call formats
574
+
575
+ **Example - Add Metadata**:
576
+ \`\`\`typescript
577
+ import { defineHook } from '@standardagents/builder';
578
+
579
+ export default defineHook('post_process_message', async (state, message) => {
580
+ if (message.content) {
581
+ message.content += \`\\n\\n_Generated at turn \${state.turnCount}_\`;
582
+ }
583
+ return message;
584
+ });
585
+ \`\`\`
586
+
587
+ **Note**: This hook is currently defined but **not yet invoked** in FlowEngine. It will be activated in a future update.
588
+
589
+ ### Message Lifecycle Hooks
590
+
591
+ The following hooks run for **ANY** message being created or updated in the database, whether it's an agent message, tool message, user message, or system message.
592
+
593
+ #### \`before_create_message\`
594
+
595
+ **When**: Immediately before a message is inserted into the database
596
+ **Purpose**: Modify message data before it's stored
597
+
598
+ \`\`\`typescript
599
+ import { defineHook } from '@standardagents/builder';
600
+
601
+ export default defineHook('before_create_message', async (state, message) => {
602
+ // Your modification logic here
603
+ return message;
604
+ });
605
+ \`\`\`
606
+
607
+ **Message Structure**:
608
+ \`\`\`typescript
609
+ {
610
+ id: string;
611
+ role: string;
612
+ content: string | null;
613
+ tool_calls?: string | null;
614
+ tool_call_id?: string | null;
615
+ name?: string | null;
616
+ created_at: number;
617
+ status?: string;
618
+ silent?: boolean;
619
+ }
620
+ \`\`\`
621
+
622
+ **Common Use Cases**:
623
+ - Add prefixes or metadata to message content
624
+ - Modify message based on agent configuration
625
+ - Add tracking IDs or identifiers
626
+ - Transform message format before storage
627
+
628
+ **Example - Add Metadata**:
629
+ \`\`\`typescript
630
+ import { defineHook } from '@standardagents/builder';
631
+
632
+ export default defineHook('before_create_message', async (state, message) => {
633
+ // Add agent ID to all messages
634
+ if (message.content && message.role === 'assistant') {
635
+ message.name = state.agentConfig.title;
636
+ }
637
+ return message;
638
+ });
639
+ \`\`\`
640
+
641
+ #### \`after_create_message\`
642
+
643
+ **When**: Immediately after a message is inserted into the database
644
+ **Purpose**: Perform actions based on newly created messages
645
+
646
+ \`\`\`typescript
647
+ import { defineHook } from '@standardagents/builder';
648
+
649
+ export default defineHook('after_create_message', async (state, message) => {
650
+ // Your post-creation logic here
651
+ // No return value needed
652
+ });
653
+ \`\`\`
654
+
655
+ **Common Use Cases**:
656
+ - Log messages to external systems
657
+ - Trigger webhooks or notifications
658
+ - Update analytics or metrics
659
+ - Send real-time updates to monitoring services
660
+
661
+ **Example - Log to External Service**:
662
+ \`\`\`typescript
663
+ import { defineHook } from '@standardagents/builder';
664
+
665
+ export default defineHook('after_create_message', async (state, message) => {
666
+ // Log all assistant messages to analytics
667
+ if (message.role === 'assistant' && message.content) {
668
+ try {
669
+ await fetch('https://analytics.example.com/message', {
670
+ method: 'POST',
671
+ body: JSON.stringify({
672
+ threadId: state.threadId,
673
+ messageId: message.id,
674
+ contentLength: message.content.length,
675
+ })
676
+ });
677
+ } catch (error) {
678
+ console.error('Analytics logging failed:', error);
679
+ }
680
+ }
681
+ });
682
+ \`\`\`
683
+
684
+ #### \`before_update_message\`
685
+
686
+ **When**: Immediately before a message is updated in the database
687
+ **Purpose**: Modify update data before it's applied
688
+
689
+ \`\`\`typescript
690
+ import { defineHook } from '@standardagents/builder';
691
+
692
+ export default defineHook('before_update_message', async (state, messageId, updates) => {
693
+ // Your modification logic here
694
+ return updates;
695
+ });
696
+ \`\`\`
697
+
698
+ **Common Use Cases**:
699
+ - Validate or sanitize updated content
700
+ - Add completion timestamps
701
+ - Transform status values
702
+ - Inject metadata into updates
703
+
704
+ **Example - Add Completion Timestamp**:
705
+ \`\`\`typescript
706
+ import { defineHook } from '@standardagents/builder';
707
+
708
+ export default defineHook('before_update_message', async (state, messageId, updates) => {
709
+ // Add custom completion timestamp for completed messages
710
+ if (updates.status === 'completed' && !updates.response_completed_at) {
711
+ updates.response_completed_at = Date.now() * 1000; // microseconds
712
+ }
713
+ return updates;
714
+ });
715
+ \`\`\`
716
+
717
+ #### \`after_update_message\`
718
+
719
+ **When**: Immediately after a message is updated in the database
720
+ **Purpose**: Perform actions based on message updates
721
+
722
+ \`\`\`typescript
723
+ import { defineHook } from '@standardagents/builder';
724
+
725
+ export default defineHook('after_update_message', async (state, messageId, updates) => {
726
+ // Your post-update logic here
727
+ // No return value needed
728
+ });
729
+ \`\`\`
730
+
731
+ **Common Use Cases**:
732
+ - Track message status changes
733
+ - Trigger notifications on completion
734
+ - Update external systems
735
+ - Log state transitions
736
+
737
+ **Example - Notify on Failure**:
738
+ \`\`\`typescript
739
+ import { defineHook } from '@standardagents/builder';
740
+
741
+ export default defineHook('after_update_message', async (state, messageId, updates) => {
742
+ // Send notification when message is marked as failed
743
+ if (updates.status === 'failed') {
744
+ console.log(\`[Alert] Message \${messageId} failed in thread \${state.threadId}\`);
745
+ }
746
+ });
747
+ \`\`\`
748
+
749
+ **Important**:
750
+ - \`before_*\` hooks must return the modified data object
751
+ - \`after_*\` hooks don't need to return anything
752
+ - All 4 hooks run for ANY message operation (agent, tool, user, system messages)
753
+ - Updates passed to update hooks contain only the fields being updated
754
+
755
+ ## Creating a Hook
756
+
757
+ 1. Create a file named exactly as the hook (e.g., \`prefilter_llm_history.ts\`)
758
+ 2. Use \`defineHook\` to ensure correct typing
759
+ 3. Return the modified data (or original if no changes)
760
+
761
+ \`\`\`typescript
762
+ // agents/hooks/prefilter_llm_history.ts
763
+ import { defineHook } from '@standardagents/builder';
764
+
765
+ export default defineHook('prefilter_llm_history', async (state, messages) => {
766
+ console.log(\`Processing \${messages.length} messages\`);
767
+
768
+ // Your logic here
769
+
770
+ return messages; // Return modified or original
771
+ });
772
+ \`\`\`
773
+
774
+ ## Hook Lifecycle
775
+
776
+ 1. **Discovery**: Vite plugin scans this directory on startup
777
+ 2. **Virtual Module**: Generates \`virtual:agent-hooks\` with lazy imports
778
+ 3. **Lazy Loading**: Hooks are loaded on first use (not at startup)
779
+ 4. **Caching**: Once loaded, hooks are cached for performance
780
+ 5. **HMR**: Changes trigger hot reload in development
781
+
782
+ ## Error Handling
783
+
784
+ Hooks are designed to be **safe**:
785
+ - If hook file doesn't exist \u2192 Silently skipped (no error)
786
+ - If hook throws error \u2192 Logged to console, original data returned
787
+ - If hook returns invalid data \u2192 Original data used as fallback
788
+
789
+ This ensures hooks never break agent execution.
790
+
791
+ ## FlowState Object
792
+
793
+ All hooks receive the FlowState context:
794
+
795
+ \`\`\`typescript
796
+ interface FlowState {
797
+ threadId: string; // Current thread ID
798
+ flowId: string; // Unique execution ID
799
+ agentConfig: Agent; // Full agent configuration
800
+ currentSide: 'a' | 'b'; // Which side is executing (dual_ai)
801
+ turnCount: number; // Current turn number
802
+ stopped: boolean; // Whether execution should stop
803
+ messageHistory: Message[]; // Full conversation history
804
+ env: Env; // Cloudflare bindings
805
+ storage: DurableObjectStorage; // Thread's SQLite storage
806
+ context: Record<string, any>; // Arbitrary state data
807
+ // ... and more
808
+ }
809
+ \`\`\`
810
+
811
+ Use this to make context-aware decisions in your hooks.
812
+
813
+ ## Best Practices
814
+
815
+ 1. **Keep Hooks Fast**: They run on every execution, avoid heavy operations
816
+ 2. **Always Return Data**: Never return \`undefined\` or \`null\`
817
+ 3. **Log Thoughtfully**: Use \`console.log\` sparingly (visible in logs)
818
+ 4. **Handle Errors**: Wrap risky operations in try/catch
819
+ 5. **Document Behavior**: Add comments explaining what your hook does
820
+ 6. **Test Thoroughly**: Hooks affect ALL agent executions
821
+
822
+ ## Debugging
823
+
824
+ - Check console output for hook loading/execution logs
825
+ - Hook errors are logged with \`[Hooks] \u2717\` prefix
826
+ - Inspect logs table to see filtered messages (for prefilter hook)
827
+ - Use \`console.log\` within hooks for debugging
828
+
829
+ ## Performance Considerations
830
+
831
+ Hooks run on **every turn**, so:
832
+ - Avoid expensive operations (heavy computation, slow APIs)
833
+ - Cache results when possible
834
+ - Consider using FlowState context to skip unnecessary work
835
+ - Use async operations efficiently
836
+
837
+ ## Message Data & Tool Status
838
+
839
+ The \`filter_messages\` hook receives SQL row data with ALL columns from the messages table:
840
+
841
+ \`\`\`typescript
842
+ interface MessageRow {
843
+ id: string;
844
+ role: 'system' | 'user' | 'assistant' | 'tool';
845
+ content: string | null;
846
+ name: string | null;
847
+ tool_calls: string | null; // JSON string of tool calls
848
+ tool_call_id: string | null; // For role='tool' messages
849
+ log_id: string | null; // Reference to logs table
850
+ created_at: number; // Microseconds timestamp
851
+ request_sent_at: number | null; // When request was sent to LLM
852
+ response_completed_at: number | null; // When response completed
853
+ status: 'pending' | 'completed' | 'failed' | null;
854
+ silent: number | null; // 1 if message should be hidden, 0/null otherwise
855
+ tool_status: 'success' | 'error' | null; // Status of tool execution (tool messages only)
856
+ }
857
+ \`\`\`
858
+
859
+ The \`tool_status\` column is automatically set when tool messages are created:
860
+ - \`'success'\` - Tool executed successfully
861
+ - \`'error'\` - Tool execution failed or threw an error
862
+ - \`null\` - Not a tool message (role !== 'tool')
863
+
864
+ ## Testing
865
+
866
+ Example test for a hook:
867
+
868
+ \`\`\`typescript
869
+ import { defineHook } from '@standardagents/builder';
870
+ import prefilterHook from './prefilter_llm_history';
871
+
872
+ const mockState = {
873
+ turnCount: 5,
874
+ currentSide: 'a',
875
+ // ... other FlowState fields
876
+ };
877
+
878
+ const mockMessages = [
879
+ { role: 'system', content: 'You are helpful' },
880
+ { role: 'user', content: 'Hello' },
881
+ { role: 'assistant', content: 'Hi there!' },
882
+ // ... more messages
883
+ ];
884
+
885
+ const result = await prefilterHook(mockState, mockMessages);
886
+ console.log('Filtered:', result.length, 'messages');
887
+ \`\`\`
888
+
889
+ ## Hook File Naming
890
+
891
+ **Critical**: Hook files must be named **exactly** as expected:
892
+ - \u2713 \`filter_messages.ts\`
893
+ - \u2713 \`prefilter_llm_history.ts\`
894
+ - \u2713 \`post_process_message.ts\`
895
+ - \u2713 \`before_create_message.ts\`
896
+ - \u2713 \`after_create_message.ts\`
897
+ - \u2713 \`before_update_message.ts\`
898
+ - \u2713 \`after_update_message.ts\`
899
+ - \u2717 \`filterMessages.ts\` (wrong case)
900
+ - \u2717 \`prefilterLLMHistory.ts\` (wrong case)
901
+ - \u2717 \`before-create-message.ts\` (wrong separator)
902
+
903
+ The file name determines which hook point it intercepts.
904
+
905
+ ## Available Hooks
906
+
907
+ Currently implemented hooks:
908
+ - \`filter_messages\` - Filter SQL row data before transformation to chat format (access to all DB columns including tool_status)
909
+ - \`prefilter_llm_history\` - Filter messages before sending to LLM (after transformation to chat format)
910
+ - \`before_create_message\` - Before inserting message into database
911
+ - \`after_create_message\` - After message is created in database
912
+ - \`before_update_message\` - Before updating message in database
913
+ - \`after_update_message\` - After message is updated in database
914
+ - \`after_tool_call_success\` - After successful tool execution (can modify result or return null to remove)
915
+ - \`after_tool_call_failure\` - After failed tool execution (can modify error or return null to remove)
916
+
917
+ ## Related
918
+
919
+ - **Tools**: \`agents/tools/CLAUDE.md\` - Custom tools
920
+ - **APIs**: \`agents/api/CLAUDE.md\` - Thread endpoints
921
+ - **Documentation**: Project root \`CLAUDE.md\` for architecture
922
+ `;
923
+ var API_CLAUDE_MD = `# Thread-Specific API Endpoints
924
+
925
+ This directory contains custom API endpoints that operate on specific threads.
926
+
927
+ ## What Are Thread Endpoints?
928
+
929
+ Thread endpoints are API routes that:
930
+ - Automatically receive a specific thread's Durable Object instance
931
+ - Can access thread-local SQLite storage
932
+ - Perform operations in the context of a conversation
933
+ - Use file-based routing for automatic discovery
934
+
935
+ ## File-Based Routing
936
+
937
+ Create files following this naming convention:
938
+
939
+ \`\`\`
940
+ agents/api/
941
+ \u251C\u2500\u2500 summary.get.ts # GET /agents/api/threads/:id/summary
942
+ \u251C\u2500\u2500 export.post.ts # POST /agents/api/threads/:id/export
943
+ \u251C\u2500\u2500 metadata.put.ts # PUT /agents/api/threads/:id/metadata
944
+ \u2514\u2500\u2500 archive.delete.ts # DELETE /agents/api/threads/:id/archive
945
+ \`\`\`
946
+
947
+ **Pattern**: \`{name}.{method}.ts\`
948
+
949
+ **Methods**: \`get\`, \`post\`, \`put\`, \`delete\`, \`patch\`
950
+
951
+ **URL**: \`/agents/api/threads/:id/{name}\`
952
+
953
+ ## Creating a Thread Endpoint
954
+
955
+ Use \`defineThreadEndpoint\` from \`@standardagents/builder\`:
956
+
957
+ \`\`\`typescript
958
+ import { defineThreadEndpoint } from '@standardagents/builder';
959
+
960
+ export default defineThreadEndpoint(async (thread, request, env) => {
961
+ // thread is the DurableObject stub for the requested thread
962
+ // request is the incoming Request object
963
+ // env is the Cloudflare environment with bindings
964
+
965
+ // Access thread's storage directly
966
+ const messages = await thread.getMessages();
967
+
968
+ // Perform operations
969
+ const summary = generateSummary(messages);
970
+
971
+ // Return response
972
+ return new Response(JSON.stringify({ summary }), {
973
+ headers: { 'Content-Type': 'application/json' }
974
+ });
975
+ });
976
+ \`\`\`
977
+
978
+ ## Thread Object
979
+
980
+ The \`thread\` parameter is a DurableObject stub with RPC methods:
981
+
982
+ \`\`\`typescript
983
+ interface DurableThread {
984
+ // Get messages from thread's SQLite storage
985
+ getMessages(limit?: number, offset?: number): Promise<Message[]>;
986
+
987
+ // Get execution logs
988
+ getLogs(limit?: number, offset?: number): Promise<Log[]>;
989
+
990
+ // Process a new message (starts agent execution)
991
+ processMessage(content: string, role?: string): Promise<Response>;
992
+
993
+ // Get thread metadata
994
+ getThreadMeta(threadId: string): Promise<ThreadMetadata>;
995
+
996
+ // Direct storage access (advanced)
997
+ storage: DurableObjectStorage;
998
+ }
999
+ \`\`\`
1000
+
1001
+ ## Examples
1002
+
1003
+ ### GET Summary Endpoint
1004
+
1005
+ \`\`\`typescript
1006
+ // agents/api/summary.get.ts
1007
+ import { defineThreadEndpoint } from '@standardagents/builder';
1008
+
1009
+ export default defineThreadEndpoint(async (thread, request, env) => {
1010
+ // Fetch messages from thread
1011
+ const messages = await thread.getMessages();
1012
+
1013
+ // Generate summary
1014
+ const userMessages = messages.filter(m => m.role === 'user');
1015
+ const assistantMessages = messages.filter(m => m.role === 'assistant');
1016
+
1017
+ return new Response(JSON.stringify({
1018
+ total_messages: messages.length,
1019
+ user_messages: userMessages.length,
1020
+ assistant_messages: assistantMessages.length,
1021
+ first_message: messages[0]?.content,
1022
+ last_message: messages[messages.length - 1]?.content,
1023
+ }), {
1024
+ headers: { 'Content-Type': 'application/json' }
1025
+ });
1026
+ });
1027
+ \`\`\`
1028
+
1029
+ **Usage**: \`GET /agents/api/threads/{threadId}/summary\`
1030
+
1031
+ ### POST Export Endpoint
1032
+
1033
+ \`\`\`typescript
1034
+ // agents/api/export.post.ts
1035
+ import { defineThreadEndpoint } from '@standardagents/builder';
1036
+
1037
+ export default defineThreadEndpoint(async (thread, request, env) => {
1038
+ const { format } = await request.json();
1039
+
1040
+ // Get messages
1041
+ const messages = await thread.getMessages();
1042
+
1043
+ // Export based on format
1044
+ let content: string;
1045
+ let contentType: string;
1046
+
1047
+ if (format === 'json') {
1048
+ content = JSON.stringify(messages, null, 2);
1049
+ contentType = 'application/json';
1050
+ } else if (format === 'markdown') {
1051
+ content = messages
1052
+ .map(m => \`**\${m.role}**: \${m.content}\`)
1053
+ .join('\\n\\n');
1054
+ contentType = 'text/markdown';
1055
+ } else {
1056
+ return new Response('Invalid format', { status: 400 });
1057
+ }
1058
+
1059
+ return new Response(content, {
1060
+ headers: {
1061
+ 'Content-Type': contentType,
1062
+ 'Content-Disposition': \`attachment; filename="thread-export.\${format}"\`
1063
+ }
1064
+ });
1065
+ });
1066
+ \`\`\`
1067
+
1068
+ **Usage**: \`POST /agents/api/threads/{threadId}/export\`
1069
+
1070
+ ### Direct Storage Access
1071
+
1072
+ \`\`\`typescript
1073
+ // agents/api/stats.get.ts
1074
+ import { defineThreadEndpoint } from '@standardagents/builder';
1075
+
1076
+ export default defineThreadEndpoint(async (thread, request, env) => {
1077
+ // Access SQLite storage directly
1078
+ const storage = (thread as any).storage;
1079
+
1080
+ const cursor = await storage.sql.exec(\`
1081
+ SELECT
1082
+ COUNT(*) as count,
1083
+ role,
1084
+ AVG(LENGTH(content)) as avg_length
1085
+ FROM messages
1086
+ GROUP BY role
1087
+ \`);
1088
+
1089
+ const stats = cursor.toArray();
1090
+
1091
+ return new Response(JSON.stringify({ stats }), {
1092
+ headers: { 'Content-Type': 'application/json' }
1093
+ });
1094
+ });
1095
+ \`\`\`
1096
+
1097
+ ## Request Handling
1098
+
1099
+ ### Reading Request Body
1100
+
1101
+ \`\`\`typescript
1102
+ export default defineThreadEndpoint(async (thread, request, env) => {
1103
+ // JSON
1104
+ const body = await request.json();
1105
+
1106
+ // FormData
1107
+ const formData = await request.formData();
1108
+
1109
+ // Text
1110
+ const text = await request.text();
1111
+
1112
+ // ...
1113
+ });
1114
+ \`\`\`
1115
+
1116
+ ### Query Parameters
1117
+
1118
+ \`\`\`typescript
1119
+ export default defineThreadEndpoint(async (thread, request, env) => {
1120
+ const url = new URL(request.url);
1121
+ const limit = url.searchParams.get('limit') || '10';
1122
+
1123
+ const messages = await thread.getMessages(parseInt(limit));
1124
+
1125
+ // ...
1126
+ });
1127
+ \`\`\`
1128
+
1129
+ ### Headers
1130
+
1131
+ \`\`\`typescript
1132
+ export default defineThreadEndpoint(async (thread, request, env) => {
1133
+ const authHeader = request.headers.get('Authorization');
1134
+
1135
+ if (!authHeader) {
1136
+ return new Response('Unauthorized', { status: 401 });
1137
+ }
1138
+
1139
+ // ...
1140
+ });
1141
+ \`\`\`
1142
+
1143
+ ## Error Handling
1144
+
1145
+ Always wrap in try/catch for robust error handling:
1146
+
1147
+ \`\`\`typescript
1148
+ export default defineThreadEndpoint(async (thread, request, env) => {
1149
+ try {
1150
+ // Your endpoint logic
1151
+ const result = await doSomething();
1152
+
1153
+ return new Response(JSON.stringify(result), {
1154
+ headers: { 'Content-Type': 'application/json' }
1155
+ });
1156
+ } catch (error) {
1157
+ console.error('Endpoint error:', error);
1158
+
1159
+ return new Response(
1160
+ JSON.stringify({
1161
+ error: error.message
1162
+ }),
1163
+ {
1164
+ status: 500,
1165
+ headers: { 'Content-Type': 'application/json' }
1166
+ }
1167
+ );
1168
+ }
1169
+ });
1170
+ \`\`\`
1171
+
1172
+ ## Environment Bindings
1173
+
1174
+ Access Cloudflare bindings via \`env\`:
1175
+
1176
+ \`\`\`typescript
1177
+ export default defineThreadEndpoint(async (thread, request, env) => {
1178
+ // Durable Objects
1179
+ const agentBuilder = env.AGENT_BUILDER.get(env.AGENT_BUILDER.idFromName("singleton"));
1180
+ const threadData = await agentBuilder.getThread(threadId);
1181
+
1182
+ // KV (if configured)
1183
+ const cache = await env.MY_KV.get('cache-key');
1184
+
1185
+ // R2 (if configured)
1186
+ const object = await env.MY_BUCKET.get('file.txt');
1187
+
1188
+ // ...
1189
+ });
1190
+ \`\`\`
1191
+
1192
+ ## Auto-Discovery
1193
+
1194
+ Thread endpoints are **auto-discovered**:
1195
+
1196
+ 1. Vite plugin scans this directory on startup
1197
+ 2. Generates virtual module with dynamic imports
1198
+ 3. Routes registered in the main router
1199
+ 4. HMR works in development
1200
+
1201
+ No manual registration needed!
1202
+
1203
+ ## URL Structure
1204
+
1205
+ All thread endpoints follow this pattern:
1206
+
1207
+ \`\`\`
1208
+ /agents/api/threads/:id/{endpoint-name}
1209
+ \`\`\`
1210
+
1211
+ Examples:
1212
+ - \`GET /agents/api/threads/abc-123/summary\`
1213
+ - \`POST /agents/api/threads/abc-123/export\`
1214
+ - \`PUT /agents/api/threads/abc-123/metadata\`
1215
+
1216
+ The \`:id\` is automatically used to fetch the correct Durable Object.
1217
+
1218
+ ## Built-In Endpoints
1219
+
1220
+ Standard Agents includes built-in thread endpoints (these are in the framework, not this directory):
1221
+
1222
+ - \`GET /agents/api/threads/:id/messages\` - Get message history
1223
+ - \`POST /agents/api/threads/:id/messages\` - Send a message
1224
+ - \`DELETE /agents/api/threads/:id\` - Delete thread
1225
+ - \`GET /agents/api/threads/:id/logs\` - Get execution logs
1226
+
1227
+ Your custom endpoints extend these built-in routes.
1228
+
1229
+ ## Best Practices
1230
+
1231
+ 1. **Descriptive Names**: Use clear endpoint names (e.g., \`summary\`, \`export\`)
1232
+ 2. **Proper HTTP Methods**: Use GET for reads, POST for creates, etc.
1233
+ 3. **Error Handling**: Always wrap in try/catch
1234
+ 4. **Type Safety**: Use TypeScript interfaces for request/response
1235
+ 5. **Performance**: Be mindful of SQLite query performance
1236
+ 6. **Authentication**: Add auth checks if endpoints are sensitive
1237
+ 7. **CORS**: Add CORS headers if accessed from browser
1238
+
1239
+ ## Testing
1240
+
1241
+ Test endpoints with curl or any HTTP client:
1242
+
1243
+ \`\`\`bash
1244
+ # GET summary
1245
+ curl http://localhost:8787/agents/api/threads/abc-123/summary
1246
+
1247
+ # POST export
1248
+ curl -X POST http://localhost:8787/agents/api/threads/abc-123/export \\
1249
+ -H "Content-Type: application/json" \\
1250
+ -d '{"format": "json"}'
1251
+ \`\`\`
1252
+
1253
+ ## Limitations
1254
+
1255
+ - No nested directories (endpoints must be directly in this folder)
1256
+ - Thread ID must be in URL path (handled automatically)
1257
+ - No support for multiple path parameters beyond thread ID
1258
+ - Response must be a Web API \`Response\` object
1259
+
1260
+ ## Related
1261
+
1262
+ - **Tools**: \`agents/tools/CLAUDE.md\` - Custom tools
1263
+ - **Hooks**: \`agents/hooks/CLAUDE.md\` - Lifecycle hooks
1264
+ - **Built-in APIs**: \`packages/builder/src/api/\` - Framework endpoints
1265
+ - **Documentation**: Project root \`CLAUDE.md\` for architecture
1266
+ `;
1267
+ async function scaffold() {
1268
+ const cwd = process.cwd();
1269
+ logger.info("Scaffolding Standard Agents directories...");
1270
+ const directories = [
1271
+ {
1272
+ path: path3.join(cwd, "agents", "tools"),
1273
+ claudeMd: TOOLS_CLAUDE_MD,
1274
+ name: "tools"
1275
+ },
1276
+ {
1277
+ path: path3.join(cwd, "agents", "hooks"),
1278
+ claudeMd: HOOKS_CLAUDE_MD,
1279
+ name: "hooks"
1280
+ },
1281
+ {
1282
+ path: path3.join(cwd, "agents", "api"),
1283
+ claudeMd: API_CLAUDE_MD,
1284
+ name: "api"
1285
+ }
1286
+ ];
1287
+ let created = 0;
1288
+ let skipped = 0;
1289
+ for (const dir of directories) {
1290
+ if (!fs.existsSync(dir.path)) {
1291
+ fs.mkdirSync(dir.path, { recursive: true });
1292
+ logger.success(`Created directory: agents/${dir.name}`);
1293
+ created++;
1294
+ } else {
1295
+ logger.info(`Directory already exists: agents/${dir.name}`);
1296
+ }
1297
+ const claudeMdPath = path3.join(dir.path, "CLAUDE.md");
1298
+ if (!fs.existsSync(claudeMdPath)) {
1299
+ fs.writeFileSync(claudeMdPath, dir.claudeMd, "utf-8");
1300
+ logger.success(`Created documentation: agents/${dir.name}/CLAUDE.md`);
1301
+ created++;
1302
+ } else {
1303
+ logger.info(`Documentation already exists: agents/${dir.name}/CLAUDE.md (not overwriting)`);
1304
+ skipped++;
1305
+ }
1306
+ }
1307
+ logger.log("");
1308
+ if (created > 0) {
1309
+ logger.success(`Scaffolding complete! Created ${created} file(s).`);
1310
+ }
1311
+ if (skipped > 0) {
1312
+ logger.info(`Skipped ${skipped} existing file(s).`);
1313
+ }
1314
+ logger.log("\nNext steps:");
1315
+ logger.log("1. Read the CLAUDE.md files in each directory for detailed documentation");
1316
+ logger.log("2. Create your first tool in agents/tools/");
1317
+ logger.log("3. Add lifecycle hooks in agents/hooks/ (optional)");
1318
+ logger.log("4. Create custom thread endpoints in agents/api/ (optional)");
1319
+ }
1320
+
1321
+ // src/index.ts
1322
+ var program = new Command();
1323
+ program.name("agentbuilder").description("CLI tool for AgentBuilder initialization").version("0.0.0");
1324
+ program.command("init").description("Initialize AgentBuilder configuration in wrangler.jsonc").option("--force", "Overwrite existing configuration").action(init);
1325
+ program.command("scaffold").description("Create agentbuilder directories (tools, hooks, api) with documentation").action(scaffold);
1326
+ program.parse();
1327
+ //# sourceMappingURL=index.js.map
1328
+ //# sourceMappingURL=index.js.map