struere 0.3.5 → 0.3.7

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.
@@ -17480,6 +17480,332 @@ function getEnvLocal(deploymentUrl) {
17480
17480
  return `STRUERE_DEPLOYMENT_URL=${deploymentUrl}
17481
17481
  `;
17482
17482
  }
17483
+ function getClaudeMd(name) {
17484
+ const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
17485
+ return `# ${displayName} Agent
17486
+
17487
+ This is a Struere AI agent project. Struere is a framework for building production AI agents with built-in data management, event tracking, and job scheduling.
17488
+
17489
+ ## Project Structure
17490
+
17491
+ \`\`\`
17492
+ src/
17493
+ \u251C\u2500\u2500 agent.ts # Agent definition (system prompt, model, tools)
17494
+ \u251C\u2500\u2500 context.ts # Dynamic context injection per request
17495
+ \u251C\u2500\u2500 tools.ts # Custom tool definitions
17496
+ \u2514\u2500\u2500 workflows/ # Multi-step workflow definitions
17497
+ tests/
17498
+ \u2514\u2500\u2500 *.test.yaml # YAML-based conversation tests
17499
+ struere.json # Project configuration (agentId, team, slug)
17500
+ struere.config.ts # Framework settings (port, CORS, logging)
17501
+ \`\`\`
17502
+
17503
+ ## Agent Definition
17504
+
17505
+ Define your agent in \`src/agent.ts\`:
17506
+
17507
+ \`\`\`typescript
17508
+ import { defineAgent } from 'struere'
17509
+ import { tools } from './tools'
17510
+ import { context } from './context'
17511
+
17512
+ export default defineAgent({
17513
+ name: 'my-agent',
17514
+ version: '0.1.0',
17515
+ description: 'My AI Agent',
17516
+ model: {
17517
+ provider: 'anthropic',
17518
+ name: 'claude-sonnet-4-20250514',
17519
+ temperature: 0.7,
17520
+ maxTokens: 4096,
17521
+ },
17522
+ systemPrompt: \\\`You are a helpful assistant.
17523
+
17524
+ Current time: {{datetime}}
17525
+ Customer: {{entity.get({"id": "{{thread.metadata.customerId}}"})}}\\\`,
17526
+ tools,
17527
+ context,
17528
+ state: {
17529
+ storage: 'memory',
17530
+ ttl: 3600,
17531
+ },
17532
+ })
17533
+ \`\`\`
17534
+
17535
+ ## System Prompt Templates
17536
+
17537
+ System prompts support dynamic \`{{...}}\` templates that are resolved at runtime before the LLM call.
17538
+
17539
+ ### Available Variables
17540
+
17541
+ | Variable | Description |
17542
+ |----------|-------------|
17543
+ | \`{{organizationId}}\` | Current organization ID |
17544
+ | \`{{userId}}\` | Current user ID |
17545
+ | \`{{threadId}}\` | Conversation thread ID |
17546
+ | \`{{agentId}}\` | Agent ID |
17547
+ | \`{{agent.name}}\` | Agent name |
17548
+ | \`{{agent.slug}}\` | Agent slug |
17549
+ | \`{{thread.metadata.X}}\` | Thread metadata field X |
17550
+ | \`{{message}}\` | Current user message |
17551
+ | \`{{timestamp}}\` | Unix timestamp (ms) |
17552
+ | \`{{datetime}}\` | ISO 8601 datetime |
17553
+
17554
+ ### Function Calls
17555
+
17556
+ Call any agent tool directly in the system prompt:
17557
+
17558
+ \`\`\`
17559
+ {{entity.get({"id": "ent_123"})}}
17560
+ {{entity.query({"type": "customer", "limit": 5})}}
17561
+ {{event.query({"entityId": "ent_123", "limit": 10})}}
17562
+ \`\`\`
17563
+
17564
+ ### Nested Templates
17565
+
17566
+ Variables can be used inside function arguments:
17567
+
17568
+ \`\`\`
17569
+ {{entity.get({"id": "{{thread.metadata.customerId}}"})}}
17570
+ \`\`\`
17571
+
17572
+ ### Error Handling
17573
+
17574
+ Failed templates are replaced with inline errors:
17575
+ \`\`\`
17576
+ [TEMPLATE_ERROR: variableName not found]
17577
+ [TEMPLATE_ERROR: toolName - error message]
17578
+ \`\`\`
17579
+
17580
+ ## Custom Tools
17581
+
17582
+ Define tools in \`src/tools.ts\`:
17583
+
17584
+ \`\`\`typescript
17585
+ import { defineTools } from 'struere'
17586
+
17587
+ export const tools = defineTools([
17588
+ {
17589
+ name: 'search_products',
17590
+ description: 'Search the product catalog',
17591
+ parameters: {
17592
+ type: 'object',
17593
+ properties: {
17594
+ query: { type: 'string', description: 'Search query' },
17595
+ limit: { type: 'number', description: 'Max results' },
17596
+ },
17597
+ required: ['query'],
17598
+ },
17599
+ handler: async (params) => {
17600
+ const results = await searchProducts(params.query, params.limit ?? 10)
17601
+ return { products: results }
17602
+ },
17603
+ },
17604
+ ])
17605
+ \`\`\`
17606
+
17607
+ Custom tool handlers are executed in a sandboxed Cloudflare Worker environment. They can make HTTP requests to allowlisted domains:
17608
+ - api.openai.com, api.anthropic.com, api.stripe.com
17609
+ - api.sendgrid.com, api.twilio.com, hooks.slack.com
17610
+ - discord.com, api.github.com
17611
+
17612
+ ## Built-in Tools
17613
+
17614
+ Agents have access to these built-in tools for data management:
17615
+
17616
+ ### Entity Tools
17617
+
17618
+ | Tool | Description |
17619
+ |------|-------------|
17620
+ | \`entity.create\` | Create a new entity |
17621
+ | \`entity.get\` | Get entity by ID |
17622
+ | \`entity.query\` | Query entities by type/filters |
17623
+ | \`entity.update\` | Update entity data |
17624
+ | \`entity.delete\` | Soft-delete entity |
17625
+ | \`entity.link\` | Create entity relation |
17626
+ | \`entity.unlink\` | Remove entity relation |
17627
+
17628
+ Example entity operations:
17629
+ \`\`\`json
17630
+ // entity.create
17631
+ { "type": "customer", "data": { "name": "John", "email": "john@example.com" } }
17632
+
17633
+ // entity.query
17634
+ { "type": "customer", "filters": { "status": "active" }, "limit": 10 }
17635
+
17636
+ // entity.update
17637
+ { "id": "ent_123", "data": { "status": "vip" } }
17638
+ \`\`\`
17639
+
17640
+ ### Event Tools
17641
+
17642
+ | Tool | Description |
17643
+ |------|-------------|
17644
+ | \`event.emit\` | Emit a custom event |
17645
+ | \`event.query\` | Query event history |
17646
+
17647
+ Example event operations:
17648
+ \`\`\`json
17649
+ // event.emit
17650
+ { "entityId": "ent_123", "eventType": "order.placed", "payload": { "amount": 99.99 } }
17651
+
17652
+ // event.query
17653
+ { "entityId": "ent_123", "eventType": "order.*", "limit": 20 }
17654
+ \`\`\`
17655
+
17656
+ ### Job Tools
17657
+
17658
+ | Tool | Description |
17659
+ |------|-------------|
17660
+ | \`job.enqueue\` | Schedule a background job |
17661
+ | \`job.status\` | Get job status |
17662
+
17663
+ Example job operations:
17664
+ \`\`\`json
17665
+ // job.enqueue
17666
+ { "jobType": "send_email", "payload": { "to": "user@example.com" }, "scheduledFor": 1706745600000 }
17667
+
17668
+ // job.status
17669
+ { "id": "job_abc123" }
17670
+ \`\`\`
17671
+
17672
+ ## Context Function
17673
+
17674
+ Inject dynamic context per request in \`src/context.ts\`:
17675
+
17676
+ \`\`\`typescript
17677
+ import { defineContext } from 'struere'
17678
+
17679
+ export const context = defineContext(async (request) => {
17680
+ const { conversationId, userId, channel, state } = request
17681
+
17682
+ const userProfile = await fetchUserProfile(userId)
17683
+
17684
+ return {
17685
+ additionalContext: \\\`
17686
+ User: \${userProfile.name} (\${userProfile.tier} tier)
17687
+ Conversation: \${conversationId}
17688
+ Channel: \${channel}
17689
+ \\\`,
17690
+ variables: {
17691
+ userId,
17692
+ userTier: userProfile.tier,
17693
+ timestamp: new Date().toISOString(),
17694
+ },
17695
+ }
17696
+ })
17697
+ \`\`\`
17698
+
17699
+ ## Testing
17700
+
17701
+ Write YAML-based conversation tests in \`tests/\`:
17702
+
17703
+ \`\`\`yaml
17704
+ name: Order flow test
17705
+ description: Test the complete order flow
17706
+
17707
+ conversation:
17708
+ - role: user
17709
+ content: I want to order a pizza
17710
+ - role: assistant
17711
+ assertions:
17712
+ - type: contains
17713
+ value: size
17714
+ - type: toolCalled
17715
+ value: get_menu
17716
+
17717
+ - role: user
17718
+ content: Large pepperoni please
17719
+ - role: assistant
17720
+ assertions:
17721
+ - type: toolCalled
17722
+ value: entity.create
17723
+ \`\`\`
17724
+
17725
+ ### Assertion Types
17726
+
17727
+ | Type | Description |
17728
+ |------|-------------|
17729
+ | \`contains\` | Response contains substring |
17730
+ | \`matches\` | Response matches regex |
17731
+ | \`toolCalled\` | Specific tool was called |
17732
+ | \`noToolCalled\` | No tools were called |
17733
+
17734
+ Run tests with:
17735
+ \`\`\`bash
17736
+ bun run test
17737
+ \`\`\`
17738
+
17739
+ ## CLI Commands
17740
+
17741
+ | Command | Description |
17742
+ |---------|-------------|
17743
+ | \`struere dev\` | Start development mode (live sync to Convex) |
17744
+ | \`struere build\` | Validate agent configuration |
17745
+ | \`struere deploy\` | Deploy agent to production |
17746
+ | \`struere test\` | Run YAML conversation tests |
17747
+ | \`struere logs\` | View recent execution logs |
17748
+ | \`struere state\` | Inspect conversation thread state |
17749
+
17750
+ ## Thread Metadata
17751
+
17752
+ Set thread metadata when creating conversations to provide context:
17753
+
17754
+ \`\`\`typescript
17755
+ // Via API
17756
+ POST /v1/chat
17757
+ {
17758
+ "agentId": "agent_123",
17759
+ "message": "Hello",
17760
+ "metadata": {
17761
+ "customerId": "ent_customer_456",
17762
+ "channel": "web",
17763
+ "language": "en"
17764
+ }
17765
+ }
17766
+ \`\`\`
17767
+
17768
+ Access in system prompt:
17769
+ \`\`\`
17770
+ Customer: {{entity.get({"id": "{{thread.metadata.customerId}}"})}}
17771
+ Channel: {{thread.metadata.channel}}
17772
+ \`\`\`
17773
+
17774
+ ## Development Workflow
17775
+
17776
+ 1. **Edit agent configuration** in \`src/agent.ts\`
17777
+ 2. **Run \`bun run dev\`** to sync changes to Convex
17778
+ 3. **Test via API** or dashboard chat interface
17779
+ 4. **Write tests** in \`tests/*.test.yaml\`
17780
+ 5. **Deploy** with \`bun run deploy\`
17781
+
17782
+ ## API Endpoints
17783
+
17784
+ | Endpoint | Method | Description |
17785
+ |----------|--------|-------------|
17786
+ | \`/v1/chat\` | POST | Chat by agent ID |
17787
+ | \`/v1/agents/:slug/chat\` | POST | Chat by agent slug |
17788
+
17789
+ Authentication: Bearer token (API key from dashboard)
17790
+
17791
+ \`\`\`bash
17792
+ curl -X POST https://your-deployment.convex.cloud/v1/chat \\
17793
+ -H "Authorization: Bearer sk_live_..." \\
17794
+ -H "Content-Type: application/json" \\
17795
+ -d '{"agentId": "...", "message": "Hello"}'
17796
+ \`\`\`
17797
+
17798
+ ## Best Practices
17799
+
17800
+ 1. **System Prompts**: Use templates for dynamic data instead of hardcoding
17801
+ 2. **Tools**: Keep tool handlers focused and stateless
17802
+ 3. **Entities**: Model your domain data as entity types
17803
+ 4. **Events**: Emit events for audit trails and analytics
17804
+ 5. **Jobs**: Use jobs for async operations (emails, notifications)
17805
+ 6. **Testing**: Write tests for critical conversation flows
17806
+ 7. **Context**: Use context for user-specific personalization
17807
+ `;
17808
+ }
17483
17809
 
17484
17810
  // src/cli/utils/scaffold.ts
17485
17811
  function ensureDir(filePath) {
@@ -17519,7 +17845,8 @@ function scaffoldAgentFiles(cwd, projectName) {
17519
17845
  "src/tools.ts": getToolsTs(),
17520
17846
  "src/workflows/.gitkeep": "",
17521
17847
  "tests/basic.test.yaml": getBasicTestYaml(),
17522
- ".env.example": getEnvExample()
17848
+ ".env.example": getEnvExample(),
17849
+ ".claude.md": getClaudeMd(projectName)
17523
17850
  };
17524
17851
  for (const [relativePath, content] of Object.entries(files)) {
17525
17852
  const fullPath = join3(cwd, relativePath);
@@ -17627,12 +17954,12 @@ var initCommand = new Command("init").description("Initialize a new Struere proj
17627
17954
  }
17628
17955
  }
17629
17956
  if (!selectedAgent) {
17630
- const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
17957
+ const displayName2 = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
17631
17958
  spinner.start("Creating agent");
17632
17959
  const { agentId, error: createError } = await createAgent({
17633
- name: displayName,
17960
+ name: displayName2,
17634
17961
  slug: projectName,
17635
- description: `${displayName} Agent`
17962
+ description: `${displayName2} Agent`
17636
17963
  });
17637
17964
  if (createError || !agentId) {
17638
17965
  spinner.fail("Failed to create agent");
@@ -17640,7 +17967,7 @@ var initCommand = new Command("init").description("Initialize a new Struere proj
17640
17967
  console.log(source_default.red("Error:"), createError || "Unknown error");
17641
17968
  process.exit(1);
17642
17969
  }
17643
- selectedAgent = { id: agentId, name: displayName, slug: projectName };
17970
+ selectedAgent = { id: agentId, name: displayName2, slug: projectName };
17644
17971
  deploymentUrl = `https://${projectName}-dev.struere.dev`;
17645
17972
  spinner.succeed(`Created agent "${projectName}"`);
17646
17973
  } else {
@@ -17691,10 +18018,43 @@ var initCommand = new Command("init").description("Initialize a new Struere proj
17691
18018
  }
17692
18019
  }
17693
18020
  console.log();
18021
+ spinner.start("Installing dependencies");
18022
+ const installResult = Bun.spawnSync(["bun", "install"], {
18023
+ cwd,
18024
+ stdout: "pipe",
18025
+ stderr: "pipe"
18026
+ });
18027
+ if (installResult.exitCode === 0) {
18028
+ spinner.succeed("Dependencies installed");
18029
+ } else {
18030
+ spinner.warn("Could not install dependencies automatically");
18031
+ console.log(source_default.gray(" Run"), source_default.cyan("bun install"), source_default.gray("manually"));
18032
+ }
18033
+ spinner.start("Syncing initial config to Convex");
18034
+ const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
18035
+ const defaultConfig = {
18036
+ name: displayName,
18037
+ version: "0.1.0",
18038
+ systemPrompt: `You are ${displayName}, a helpful AI assistant. You help users with their questions and tasks.`,
18039
+ model: {
18040
+ provider: "anthropic",
18041
+ name: "claude-sonnet-4-20250514",
18042
+ temperature: 0.7,
18043
+ maxTokens: 4096
18044
+ },
18045
+ tools: []
18046
+ };
18047
+ const syncResult = await syncToConvex(selectedAgent.id, defaultConfig);
18048
+ if (syncResult.success) {
18049
+ spinner.succeed("Initial config synced");
18050
+ } else {
18051
+ spinner.warn("Could not sync initial config");
18052
+ console.log(source_default.gray(" Run"), source_default.cyan("struere dev"), source_default.gray("to sync manually"));
18053
+ }
18054
+ console.log();
17694
18055
  console.log(source_default.green("Success!"), "Project initialized");
17695
18056
  console.log();
17696
18057
  console.log(source_default.gray("Next steps:"));
17697
- console.log(source_default.gray(" $"), source_default.cyan("bun install"));
17698
18058
  console.log(source_default.gray(" $"), source_default.cyan("struere dev"));
17699
18059
  console.log();
17700
18060
  });
@@ -18735,9 +19095,17 @@ var whoamiCommand = new Command("whoami").description("Show current logged in us
18735
19095
  // package.json
18736
19096
  var package_default = {
18737
19097
  name: "struere",
18738
- version: "0.3.5",
19098
+ version: "0.3.7",
18739
19099
  description: "Build, test, and deploy AI agents",
18740
- keywords: ["ai", "agents", "llm", "anthropic", "openai", "framework", "cli"],
19100
+ keywords: [
19101
+ "ai",
19102
+ "agents",
19103
+ "llm",
19104
+ "anthropic",
19105
+ "openai",
19106
+ "framework",
19107
+ "cli"
19108
+ ],
18741
19109
  author: "struere",
18742
19110
  license: "MIT",
18743
19111
  publishConfig: {
@@ -18764,7 +19132,9 @@ var package_default = {
18764
19132
  types: "./dist/index.d.ts"
18765
19133
  }
18766
19134
  },
18767
- files: ["dist"],
19135
+ files: [
19136
+ "dist"
19137
+ ],
18768
19138
  scripts: {
18769
19139
  build: "bun build ./src/cli/index.ts --outdir ./dist/cli --target bun --external commander --external chalk --external ora --external chokidar --external yaml && bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/bin/struere.ts --outdir ./dist/bin --target bun && tsc --emitDeclarationOnly && chmod +x ./dist/bin/struere.js",
18770
19140
  dev: "tsc --watch",
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC,eAAO,MAAM,WAAW,SA0KpB,CAAA;AAEJ,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYpE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC,eAAO,MAAM,WAAW,SA4NpB,CAAA;AAEJ,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYpE;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C"}
package/dist/cli/index.js CHANGED
@@ -819,6 +819,332 @@ function getEnvLocal(deploymentUrl) {
819
819
  return `STRUERE_DEPLOYMENT_URL=${deploymentUrl}
820
820
  `;
821
821
  }
822
+ function getClaudeMd(name) {
823
+ const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
824
+ return `# ${displayName} Agent
825
+
826
+ This is a Struere AI agent project. Struere is a framework for building production AI agents with built-in data management, event tracking, and job scheduling.
827
+
828
+ ## Project Structure
829
+
830
+ \`\`\`
831
+ src/
832
+ \u251C\u2500\u2500 agent.ts # Agent definition (system prompt, model, tools)
833
+ \u251C\u2500\u2500 context.ts # Dynamic context injection per request
834
+ \u251C\u2500\u2500 tools.ts # Custom tool definitions
835
+ \u2514\u2500\u2500 workflows/ # Multi-step workflow definitions
836
+ tests/
837
+ \u2514\u2500\u2500 *.test.yaml # YAML-based conversation tests
838
+ struere.json # Project configuration (agentId, team, slug)
839
+ struere.config.ts # Framework settings (port, CORS, logging)
840
+ \`\`\`
841
+
842
+ ## Agent Definition
843
+
844
+ Define your agent in \`src/agent.ts\`:
845
+
846
+ \`\`\`typescript
847
+ import { defineAgent } from 'struere'
848
+ import { tools } from './tools'
849
+ import { context } from './context'
850
+
851
+ export default defineAgent({
852
+ name: 'my-agent',
853
+ version: '0.1.0',
854
+ description: 'My AI Agent',
855
+ model: {
856
+ provider: 'anthropic',
857
+ name: 'claude-sonnet-4-20250514',
858
+ temperature: 0.7,
859
+ maxTokens: 4096,
860
+ },
861
+ systemPrompt: \\\`You are a helpful assistant.
862
+
863
+ Current time: {{datetime}}
864
+ Customer: {{entity.get({"id": "{{thread.metadata.customerId}}"})}}\\\`,
865
+ tools,
866
+ context,
867
+ state: {
868
+ storage: 'memory',
869
+ ttl: 3600,
870
+ },
871
+ })
872
+ \`\`\`
873
+
874
+ ## System Prompt Templates
875
+
876
+ System prompts support dynamic \`{{...}}\` templates that are resolved at runtime before the LLM call.
877
+
878
+ ### Available Variables
879
+
880
+ | Variable | Description |
881
+ |----------|-------------|
882
+ | \`{{organizationId}}\` | Current organization ID |
883
+ | \`{{userId}}\` | Current user ID |
884
+ | \`{{threadId}}\` | Conversation thread ID |
885
+ | \`{{agentId}}\` | Agent ID |
886
+ | \`{{agent.name}}\` | Agent name |
887
+ | \`{{agent.slug}}\` | Agent slug |
888
+ | \`{{thread.metadata.X}}\` | Thread metadata field X |
889
+ | \`{{message}}\` | Current user message |
890
+ | \`{{timestamp}}\` | Unix timestamp (ms) |
891
+ | \`{{datetime}}\` | ISO 8601 datetime |
892
+
893
+ ### Function Calls
894
+
895
+ Call any agent tool directly in the system prompt:
896
+
897
+ \`\`\`
898
+ {{entity.get({"id": "ent_123"})}}
899
+ {{entity.query({"type": "customer", "limit": 5})}}
900
+ {{event.query({"entityId": "ent_123", "limit": 10})}}
901
+ \`\`\`
902
+
903
+ ### Nested Templates
904
+
905
+ Variables can be used inside function arguments:
906
+
907
+ \`\`\`
908
+ {{entity.get({"id": "{{thread.metadata.customerId}}"})}}
909
+ \`\`\`
910
+
911
+ ### Error Handling
912
+
913
+ Failed templates are replaced with inline errors:
914
+ \`\`\`
915
+ [TEMPLATE_ERROR: variableName not found]
916
+ [TEMPLATE_ERROR: toolName - error message]
917
+ \`\`\`
918
+
919
+ ## Custom Tools
920
+
921
+ Define tools in \`src/tools.ts\`:
922
+
923
+ \`\`\`typescript
924
+ import { defineTools } from 'struere'
925
+
926
+ export const tools = defineTools([
927
+ {
928
+ name: 'search_products',
929
+ description: 'Search the product catalog',
930
+ parameters: {
931
+ type: 'object',
932
+ properties: {
933
+ query: { type: 'string', description: 'Search query' },
934
+ limit: { type: 'number', description: 'Max results' },
935
+ },
936
+ required: ['query'],
937
+ },
938
+ handler: async (params) => {
939
+ const results = await searchProducts(params.query, params.limit ?? 10)
940
+ return { products: results }
941
+ },
942
+ },
943
+ ])
944
+ \`\`\`
945
+
946
+ Custom tool handlers are executed in a sandboxed Cloudflare Worker environment. They can make HTTP requests to allowlisted domains:
947
+ - api.openai.com, api.anthropic.com, api.stripe.com
948
+ - api.sendgrid.com, api.twilio.com, hooks.slack.com
949
+ - discord.com, api.github.com
950
+
951
+ ## Built-in Tools
952
+
953
+ Agents have access to these built-in tools for data management:
954
+
955
+ ### Entity Tools
956
+
957
+ | Tool | Description |
958
+ |------|-------------|
959
+ | \`entity.create\` | Create a new entity |
960
+ | \`entity.get\` | Get entity by ID |
961
+ | \`entity.query\` | Query entities by type/filters |
962
+ | \`entity.update\` | Update entity data |
963
+ | \`entity.delete\` | Soft-delete entity |
964
+ | \`entity.link\` | Create entity relation |
965
+ | \`entity.unlink\` | Remove entity relation |
966
+
967
+ Example entity operations:
968
+ \`\`\`json
969
+ // entity.create
970
+ { "type": "customer", "data": { "name": "John", "email": "john@example.com" } }
971
+
972
+ // entity.query
973
+ { "type": "customer", "filters": { "status": "active" }, "limit": 10 }
974
+
975
+ // entity.update
976
+ { "id": "ent_123", "data": { "status": "vip" } }
977
+ \`\`\`
978
+
979
+ ### Event Tools
980
+
981
+ | Tool | Description |
982
+ |------|-------------|
983
+ | \`event.emit\` | Emit a custom event |
984
+ | \`event.query\` | Query event history |
985
+
986
+ Example event operations:
987
+ \`\`\`json
988
+ // event.emit
989
+ { "entityId": "ent_123", "eventType": "order.placed", "payload": { "amount": 99.99 } }
990
+
991
+ // event.query
992
+ { "entityId": "ent_123", "eventType": "order.*", "limit": 20 }
993
+ \`\`\`
994
+
995
+ ### Job Tools
996
+
997
+ | Tool | Description |
998
+ |------|-------------|
999
+ | \`job.enqueue\` | Schedule a background job |
1000
+ | \`job.status\` | Get job status |
1001
+
1002
+ Example job operations:
1003
+ \`\`\`json
1004
+ // job.enqueue
1005
+ { "jobType": "send_email", "payload": { "to": "user@example.com" }, "scheduledFor": 1706745600000 }
1006
+
1007
+ // job.status
1008
+ { "id": "job_abc123" }
1009
+ \`\`\`
1010
+
1011
+ ## Context Function
1012
+
1013
+ Inject dynamic context per request in \`src/context.ts\`:
1014
+
1015
+ \`\`\`typescript
1016
+ import { defineContext } from 'struere'
1017
+
1018
+ export const context = defineContext(async (request) => {
1019
+ const { conversationId, userId, channel, state } = request
1020
+
1021
+ const userProfile = await fetchUserProfile(userId)
1022
+
1023
+ return {
1024
+ additionalContext: \\\`
1025
+ User: \${userProfile.name} (\${userProfile.tier} tier)
1026
+ Conversation: \${conversationId}
1027
+ Channel: \${channel}
1028
+ \\\`,
1029
+ variables: {
1030
+ userId,
1031
+ userTier: userProfile.tier,
1032
+ timestamp: new Date().toISOString(),
1033
+ },
1034
+ }
1035
+ })
1036
+ \`\`\`
1037
+
1038
+ ## Testing
1039
+
1040
+ Write YAML-based conversation tests in \`tests/\`:
1041
+
1042
+ \`\`\`yaml
1043
+ name: Order flow test
1044
+ description: Test the complete order flow
1045
+
1046
+ conversation:
1047
+ - role: user
1048
+ content: I want to order a pizza
1049
+ - role: assistant
1050
+ assertions:
1051
+ - type: contains
1052
+ value: size
1053
+ - type: toolCalled
1054
+ value: get_menu
1055
+
1056
+ - role: user
1057
+ content: Large pepperoni please
1058
+ - role: assistant
1059
+ assertions:
1060
+ - type: toolCalled
1061
+ value: entity.create
1062
+ \`\`\`
1063
+
1064
+ ### Assertion Types
1065
+
1066
+ | Type | Description |
1067
+ |------|-------------|
1068
+ | \`contains\` | Response contains substring |
1069
+ | \`matches\` | Response matches regex |
1070
+ | \`toolCalled\` | Specific tool was called |
1071
+ | \`noToolCalled\` | No tools were called |
1072
+
1073
+ Run tests with:
1074
+ \`\`\`bash
1075
+ bun run test
1076
+ \`\`\`
1077
+
1078
+ ## CLI Commands
1079
+
1080
+ | Command | Description |
1081
+ |---------|-------------|
1082
+ | \`struere dev\` | Start development mode (live sync to Convex) |
1083
+ | \`struere build\` | Validate agent configuration |
1084
+ | \`struere deploy\` | Deploy agent to production |
1085
+ | \`struere test\` | Run YAML conversation tests |
1086
+ | \`struere logs\` | View recent execution logs |
1087
+ | \`struere state\` | Inspect conversation thread state |
1088
+
1089
+ ## Thread Metadata
1090
+
1091
+ Set thread metadata when creating conversations to provide context:
1092
+
1093
+ \`\`\`typescript
1094
+ // Via API
1095
+ POST /v1/chat
1096
+ {
1097
+ "agentId": "agent_123",
1098
+ "message": "Hello",
1099
+ "metadata": {
1100
+ "customerId": "ent_customer_456",
1101
+ "channel": "web",
1102
+ "language": "en"
1103
+ }
1104
+ }
1105
+ \`\`\`
1106
+
1107
+ Access in system prompt:
1108
+ \`\`\`
1109
+ Customer: {{entity.get({"id": "{{thread.metadata.customerId}}"})}}
1110
+ Channel: {{thread.metadata.channel}}
1111
+ \`\`\`
1112
+
1113
+ ## Development Workflow
1114
+
1115
+ 1. **Edit agent configuration** in \`src/agent.ts\`
1116
+ 2. **Run \`bun run dev\`** to sync changes to Convex
1117
+ 3. **Test via API** or dashboard chat interface
1118
+ 4. **Write tests** in \`tests/*.test.yaml\`
1119
+ 5. **Deploy** with \`bun run deploy\`
1120
+
1121
+ ## API Endpoints
1122
+
1123
+ | Endpoint | Method | Description |
1124
+ |----------|--------|-------------|
1125
+ | \`/v1/chat\` | POST | Chat by agent ID |
1126
+ | \`/v1/agents/:slug/chat\` | POST | Chat by agent slug |
1127
+
1128
+ Authentication: Bearer token (API key from dashboard)
1129
+
1130
+ \`\`\`bash
1131
+ curl -X POST https://your-deployment.convex.cloud/v1/chat \\
1132
+ -H "Authorization: Bearer sk_live_..." \\
1133
+ -H "Content-Type: application/json" \\
1134
+ -d '{"agentId": "...", "message": "Hello"}'
1135
+ \`\`\`
1136
+
1137
+ ## Best Practices
1138
+
1139
+ 1. **System Prompts**: Use templates for dynamic data instead of hardcoding
1140
+ 2. **Tools**: Keep tool handlers focused and stateless
1141
+ 3. **Entities**: Model your domain data as entity types
1142
+ 4. **Events**: Emit events for audit trails and analytics
1143
+ 5. **Jobs**: Use jobs for async operations (emails, notifications)
1144
+ 6. **Testing**: Write tests for critical conversation flows
1145
+ 7. **Context**: Use context for user-specific personalization
1146
+ `;
1147
+ }
822
1148
 
823
1149
  // src/cli/utils/scaffold.ts
824
1150
  function ensureDir(filePath) {
@@ -858,7 +1184,8 @@ function scaffoldAgentFiles(cwd, projectName) {
858
1184
  "src/tools.ts": getToolsTs(),
859
1185
  "src/workflows/.gitkeep": "",
860
1186
  "tests/basic.test.yaml": getBasicTestYaml(),
861
- ".env.example": getEnvExample()
1187
+ ".env.example": getEnvExample(),
1188
+ ".claude.md": getClaudeMd(projectName)
862
1189
  };
863
1190
  for (const [relativePath, content] of Object.entries(files)) {
864
1191
  const fullPath = join3(cwd, relativePath);
@@ -966,12 +1293,12 @@ var initCommand = new Command2("init").description("Initialize a new Struere pro
966
1293
  }
967
1294
  }
968
1295
  if (!selectedAgent) {
969
- const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1296
+ const displayName2 = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
970
1297
  spinner.start("Creating agent");
971
1298
  const { agentId, error: createError } = await createAgent({
972
- name: displayName,
1299
+ name: displayName2,
973
1300
  slug: projectName,
974
- description: `${displayName} Agent`
1301
+ description: `${displayName2} Agent`
975
1302
  });
976
1303
  if (createError || !agentId) {
977
1304
  spinner.fail("Failed to create agent");
@@ -979,7 +1306,7 @@ var initCommand = new Command2("init").description("Initialize a new Struere pro
979
1306
  console.log(chalk2.red("Error:"), createError || "Unknown error");
980
1307
  process.exit(1);
981
1308
  }
982
- selectedAgent = { id: agentId, name: displayName, slug: projectName };
1309
+ selectedAgent = { id: agentId, name: displayName2, slug: projectName };
983
1310
  deploymentUrl = `https://${projectName}-dev.struere.dev`;
984
1311
  spinner.succeed(`Created agent "${projectName}"`);
985
1312
  } else {
@@ -1030,10 +1357,43 @@ var initCommand = new Command2("init").description("Initialize a new Struere pro
1030
1357
  }
1031
1358
  }
1032
1359
  console.log();
1360
+ spinner.start("Installing dependencies");
1361
+ const installResult = Bun.spawnSync(["bun", "install"], {
1362
+ cwd,
1363
+ stdout: "pipe",
1364
+ stderr: "pipe"
1365
+ });
1366
+ if (installResult.exitCode === 0) {
1367
+ spinner.succeed("Dependencies installed");
1368
+ } else {
1369
+ spinner.warn("Could not install dependencies automatically");
1370
+ console.log(chalk2.gray(" Run"), chalk2.cyan("bun install"), chalk2.gray("manually"));
1371
+ }
1372
+ spinner.start("Syncing initial config to Convex");
1373
+ const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1374
+ const defaultConfig = {
1375
+ name: displayName,
1376
+ version: "0.1.0",
1377
+ systemPrompt: `You are ${displayName}, a helpful AI assistant. You help users with their questions and tasks.`,
1378
+ model: {
1379
+ provider: "anthropic",
1380
+ name: "claude-sonnet-4-20250514",
1381
+ temperature: 0.7,
1382
+ maxTokens: 4096
1383
+ },
1384
+ tools: []
1385
+ };
1386
+ const syncResult = await syncToConvex(selectedAgent.id, defaultConfig);
1387
+ if (syncResult.success) {
1388
+ spinner.succeed("Initial config synced");
1389
+ } else {
1390
+ spinner.warn("Could not sync initial config");
1391
+ console.log(chalk2.gray(" Run"), chalk2.cyan("struere dev"), chalk2.gray("to sync manually"));
1392
+ }
1393
+ console.log();
1033
1394
  console.log(chalk2.green("Success!"), "Project initialized");
1034
1395
  console.log();
1035
1396
  console.log(chalk2.gray("Next steps:"));
1036
- console.log(chalk2.gray(" $"), chalk2.cyan("bun install"));
1037
1397
  console.log(chalk2.gray(" $"), chalk2.cyan("struere dev"));
1038
1398
  console.log();
1039
1399
  });
@@ -2100,9 +2460,17 @@ var whoamiCommand = new Command11("whoami").description("Show current logged in
2100
2460
  // package.json
2101
2461
  var package_default = {
2102
2462
  name: "struere",
2103
- version: "0.3.5",
2463
+ version: "0.3.7",
2104
2464
  description: "Build, test, and deploy AI agents",
2105
- keywords: ["ai", "agents", "llm", "anthropic", "openai", "framework", "cli"],
2465
+ keywords: [
2466
+ "ai",
2467
+ "agents",
2468
+ "llm",
2469
+ "anthropic",
2470
+ "openai",
2471
+ "framework",
2472
+ "cli"
2473
+ ],
2106
2474
  author: "struere",
2107
2475
  license: "MIT",
2108
2476
  publishConfig: {
@@ -2129,7 +2497,9 @@ var package_default = {
2129
2497
  types: "./dist/index.d.ts"
2130
2498
  }
2131
2499
  },
2132
- files: ["dist"],
2500
+ files: [
2501
+ "dist"
2502
+ ],
2133
2503
  scripts: {
2134
2504
  build: "bun build ./src/cli/index.ts --outdir ./dist/cli --target bun --external commander --external chalk --external ora --external chokidar --external yaml && bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/bin/struere.ts --outdir ./dist/bin --target bun && tsc --emitDeclarationOnly && chmod +x ./dist/bin/struere.js",
2135
2505
  dev: "tsc --watch",
@@ -9,4 +9,5 @@ export declare function getEnvExample(): string;
9
9
  export declare function getGitignore(): string;
10
10
  export declare function getStruereJson(agentId: string, team: string, slug: string, name: string): string;
11
11
  export declare function getEnvLocal(deploymentUrl: string): string;
12
+ export declare function getClaudeMd(name: string): string;
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuBnD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAsBpC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAgBzC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoC/C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAkBrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAoDnC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAmBzC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAatC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAahG;AAED,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGzD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/templates/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuBnD;AAED,wBAAgB,WAAW,IAAI,MAAM,CAsBpC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAgBzC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoC/C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAkBrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAoDnC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAmBzC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAatC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAahG;AAED,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyUhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/scaffold.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAeD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAexF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CA+BrF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CA8BnF;AAsBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/scaffold.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAeD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAexF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CAgCrF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CA+BnF;AAsBD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD"}
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "struere",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Build, test, and deploy AI agents",
5
- "keywords": ["ai", "agents", "llm", "anthropic", "openai", "framework", "cli"],
5
+ "keywords": [
6
+ "ai",
7
+ "agents",
8
+ "llm",
9
+ "anthropic",
10
+ "openai",
11
+ "framework",
12
+ "cli"
13
+ ],
6
14
  "author": "struere",
7
15
  "license": "MIT",
8
16
  "publishConfig": {
@@ -29,7 +37,9 @@
29
37
  "types": "./dist/index.d.ts"
30
38
  }
31
39
  },
32
- "files": ["dist"],
40
+ "files": [
41
+ "dist"
42
+ ],
33
43
  "scripts": {
34
44
  "build": "bun build ./src/cli/index.ts --outdir ./dist/cli --target bun --external commander --external chalk --external ora --external chokidar --external yaml && bun build ./src/index.ts --outdir ./dist --target node && bun build ./src/bin/struere.ts --outdir ./dist/bin --target bun && tsc --emitDeclarationOnly && chmod +x ./dist/bin/struere.js",
35
45
  "dev": "tsc --watch",