struere 0.4.2 → 0.4.4

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/cli/index.js CHANGED
@@ -243,8 +243,11 @@ async function syncOrganization(payload) {
243
243
  const error = await response.text();
244
244
  return { success: false, error };
245
245
  }
246
- const result = await response.json();
247
- return result;
246
+ const json = await response.json();
247
+ if (json.status === "success" && json.value) {
248
+ return json.value;
249
+ }
250
+ return { success: false, error: "Unexpected response format" };
248
251
  }
249
252
  async function getSyncState() {
250
253
  const credentials = loadCredentials();
@@ -293,8 +296,11 @@ async function deployAllAgents() {
293
296
  const error = await response.text();
294
297
  return { success: false, error };
295
298
  }
296
- const result = await response.json();
297
- return result;
299
+ const json = await response.json();
300
+ if (json.status === "success" && json.value) {
301
+ return json.value;
302
+ }
303
+ return { success: false, error: "Unexpected response format" };
298
304
  }
299
305
 
300
306
  // src/cli/commands/login.ts
@@ -664,10 +670,10 @@ function getIndexTs(type) {
664
670
  function getToolsIndexTs() {
665
671
  return `import { defineTools } from 'struere'
666
672
 
667
- export const tools = defineTools([
673
+ export default defineTools([
668
674
  {
669
675
  name: 'get_current_time',
670
- description: 'Get the current date and time',
676
+ description: 'Get the current date and time in a specific timezone',
671
677
  parameters: {
672
678
  type: 'object',
673
679
  properties: {
@@ -677,19 +683,59 @@ export const tools = defineTools([
677
683
  },
678
684
  },
679
685
  },
680
- handler: async (params) => {
681
- const timezone = (params.timezone as string) || 'UTC'
686
+ handler: async (args, context, fetch) => {
687
+ const timezone = (args.timezone as string) || 'UTC'
682
688
  const now = new Date()
683
689
  return {
684
690
  timestamp: now.toISOString(),
685
691
  formatted: now.toLocaleString('en-US', { timeZone: timezone }),
686
692
  timezone,
693
+ organizationId: context.organizationId,
687
694
  }
688
695
  },
689
696
  },
690
- ])
691
697
 
692
- export default tools
698
+ {
699
+ name: 'send_slack_message',
700
+ description: 'Send a message to a Slack channel via webhook',
701
+ parameters: {
702
+ type: 'object',
703
+ properties: {
704
+ message: {
705
+ type: 'string',
706
+ description: 'The message to send',
707
+ },
708
+ channel: {
709
+ type: 'string',
710
+ description: 'Channel name (for logging purposes)',
711
+ },
712
+ },
713
+ required: ['message'],
714
+ },
715
+ handler: async (args, context, fetch) => {
716
+ const webhookUrl = process.env.SLACK_WEBHOOK_URL
717
+ if (!webhookUrl) {
718
+ return { success: false, error: 'SLACK_WEBHOOK_URL not configured' }
719
+ }
720
+
721
+ const response = await fetch(webhookUrl, {
722
+ method: 'POST',
723
+ headers: { 'Content-Type': 'application/json' },
724
+ body: JSON.stringify({
725
+ text: args.message,
726
+ username: 'Struere Agent',
727
+ }),
728
+ })
729
+
730
+ return {
731
+ success: response.ok,
732
+ status: response.status,
733
+ actorId: context.actorId,
734
+ actorType: context.actorType,
735
+ }
736
+ },
737
+ },
738
+ ])
693
739
  `;
694
740
  }
695
741
  function getStruereJsonV2(orgId, orgSlug, orgName) {
@@ -723,123 +769,430 @@ function getPackageJsonV2(name) {
723
769
  }, null, 2);
724
770
  }
725
771
  function getClaudeMDV2(orgName) {
726
- return `# ${orgName} - Struere Project
772
+ return `# ${orgName} - Struere Workspace
773
+
774
+ > **This is a workspace project**, not the Struere framework source code. You define agents, entity types, roles, and custom tools here. The CLI syncs them to Convex. Framework source: github.com/struere/struere
727
775
 
728
- This is a Struere organization project. Struere is a framework for building production AI agents with built-in data management, RBAC permissions, and job scheduling.
776
+ Struere is a framework for building production AI agents with Convex as the real-time backend. Agents can manage entities (business data), emit events, and schedule background jobs\u2014all with built-in RBAC permissions.
777
+
778
+ ## How It Works
779
+
780
+ \`\`\`
781
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
782
+ \u2502 Your Project (this folder) \u2502
783
+ \u2502 \u251C\u2500\u2500 agents/*.ts \u2192 Agent configs synced to Convex \u2502
784
+ \u2502 \u251C\u2500\u2500 entity-types/*.ts \u2192 Schema definitions synced to Convex \u2502
785
+ \u2502 \u251C\u2500\u2500 roles/*.ts \u2192 RBAC policies synced to Convex \u2502
786
+ \u2502 \u2514\u2500\u2500 tools/index.ts \u2192 Custom tools (handlers run on CF Worker)\u2502
787
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
788
+ \u2502 struere dev (watches & syncs)
789
+ \u25BC
790
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
791
+ \u2502 Convex (Real-time Backend) \u2502
792
+ \u2502 \u2022 Stores agent configs, entities, events, jobs \u2502
793
+ \u2502 \u2022 Runs LLM calls (Anthropic/OpenAI) \u2502
794
+ \u2502 \u2022 Enforces RBAC on every operation \u2502
795
+ \u2502 \u2022 Executes custom tools via Cloudflare Worker \u2502
796
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
797
+ \u2502 HTTP API
798
+ \u25BC
799
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
800
+ \u2502 Clients \u2502
801
+ \u2502 \u2022 Dashboard (chat UI, entity browser) \u2502
802
+ \u2502 \u2022 Your app (REST API with Bearer token) \u2502
803
+ \u2502 \u2022 WhatsApp/Webhooks \u2502
804
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
805
+ \`\`\`
729
806
 
730
807
  ## Project Structure
731
808
 
732
809
  \`\`\`
733
- agents/ # Agent definitions
734
- \u251C\u2500\u2500 scheduler.ts # Example agent
735
- \u2514\u2500\u2500 index.ts # Re-exports all agents
810
+ agents/ # Agent definitions (synced to Convex)
811
+ \u251C\u2500\u2500 my-agent.ts # One file per agent
812
+ \u2514\u2500\u2500 index.ts # Re-exports (optional)
736
813
 
737
- entity-types/ # Entity type schemas
738
- \u251C\u2500\u2500 teacher.ts # Example entity type
739
- \u2514\u2500\u2500 index.ts # Re-exports all entity types
814
+ entity-types/ # Data schemas (like DB tables)
815
+ \u251C\u2500\u2500 customer.ts # Defines shape of "customer" entities
816
+ \u2514\u2500\u2500 index.ts
740
817
 
741
- roles/ # Role + permission definitions
742
- \u251C\u2500\u2500 admin.ts # Example role with policies
743
- \u2514\u2500\u2500 index.ts # Re-exports all roles
818
+ roles/ # RBAC: who can do what
819
+ \u251C\u2500\u2500 admin.ts # Full access
820
+ \u251C\u2500\u2500 support.ts # Limited access
821
+ \u2514\u2500\u2500 index.ts
744
822
 
745
- tools/ # Shared custom tools
746
- \u2514\u2500\u2500 index.ts # Custom tool definitions
823
+ tools/ # Custom tools shared by all agents
824
+ \u2514\u2500\u2500 index.ts # defineTools([...])
747
825
 
748
- struere.json # Organization configuration
749
- struere.config.ts # Framework settings
826
+ struere.json # Organization ID (don't edit)
827
+ struere.config.ts # Local dev settings (port, CORS)
750
828
  \`\`\`
751
829
 
752
- ## CLI Commands
830
+ ## Configuration Files
753
831
 
754
- | Command | Description |
755
- |---------|-------------|
756
- | \`struere dev\` | Watch and sync all resources to Convex |
757
- | \`struere deploy\` | Deploy all agents to production |
758
- | \`struere add <type> <name>\` | Scaffold new agent/entity-type/role |
759
- | \`struere status\` | Compare local vs remote state |
832
+ ### struere.json (auto-generated, don't edit)
833
+ Links this project to your Convex organization:
834
+ \`\`\`json
835
+ {
836
+ "version": "2.0",
837
+ "organization": { "id": "org_xxx", "slug": "my-org", "name": "My Org" }
838
+ }
839
+ \`\`\`
840
+
841
+ ### struere.config.ts (optional local settings)
842
+ \`\`\`typescript
843
+ import { defineConfig } from 'struere'
844
+ export default defineConfig({
845
+ port: 3000, // Local dev server port
846
+ logging: { level: 'debug' }
847
+ })
848
+ \`\`\`
760
849
 
761
- ## Defining Resources
850
+ ## CLI Commands
762
851
 
763
- ### Agents (\`agents/*.ts\`)
852
+ | Command | What it does |
853
+ |---------|--------------|
854
+ | \`struere dev\` | Watch files, sync to Convex on every save |
855
+ | \`struere deploy\` | Copy dev config to production for all agents |
856
+ | \`struere add agent <name>\` | Create agents/name.ts with template |
857
+ | \`struere add entity-type <name>\` | Create entity-types/name.ts |
858
+ | \`struere add role <name>\` | Create roles/name.ts |
859
+ | \`struere status\` | Show what's synced vs local-only |
764
860
 
861
+ ## Defining Agents
862
+
863
+ Create \`agents/support.ts\`:
765
864
  \`\`\`typescript
766
865
  import { defineAgent } from 'struere'
767
866
 
768
867
  export default defineAgent({
769
- name: "Scheduler",
770
- slug: "scheduler",
868
+ name: "Support Agent",
869
+ slug: "support",
771
870
  version: "0.1.0",
772
- systemPrompt: "You are a scheduling assistant...",
773
- model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
774
- tools: ["entity.create", "entity.query", "event.emit"],
871
+ model: {
872
+ provider: "anthropic", // or "openai", "google"
873
+ name: "claude-sonnet-4-20250514",
874
+ temperature: 0.7,
875
+ maxTokens: 4096,
876
+ },
877
+ systemPrompt: \\\`You are a support agent for {{organizationName}}.
878
+ Current time: {{currentTime}}
879
+
880
+ Available customers:
881
+ {{#each entityTypes}}
882
+ - {{this.name}}: {{this.description}}
883
+ {{/each}}
884
+
885
+ Use entity.query to look up customer info before responding.\\\`,
886
+ tools: [
887
+ "entity.query",
888
+ "entity.get",
889
+ "entity.update",
890
+ "event.emit",
891
+ "send_email", // custom tool from tools/index.ts
892
+ ],
775
893
  })
776
894
  \`\`\`
777
895
 
778
- ### Entity Types (\`entity-types/*.ts\`)
896
+ ### System Prompt Variables
897
+
898
+ | Variable | Value |
899
+ |----------|-------|
900
+ | \`{{currentTime}}\` | ISO 8601 timestamp |
901
+ | \`{{organizationName}}\` | Your org name |
902
+ | \`{{agentName}}\` | This agent's name |
903
+ | \`{{entityTypes}}\` | Array of all entity types (for #each loops) |
904
+ | \`{{roles}}\` | Array of all roles |
779
905
 
906
+ ## Defining Entity Types
907
+
908
+ Create \`entity-types/customer.ts\`:
780
909
  \`\`\`typescript
781
910
  import { defineEntityType } from 'struere'
782
911
 
783
912
  export default defineEntityType({
784
- name: "Teacher",
785
- slug: "teacher",
913
+ name: "Customer",
914
+ slug: "customer",
786
915
  schema: {
787
916
  type: "object",
788
917
  properties: {
789
918
  name: { type: "string" },
790
919
  email: { type: "string", format: "email" },
791
- hourlyRate: { type: "number" },
920
+ plan: { type: "string", enum: ["free", "pro", "enterprise"] },
921
+ metadata: { type: "object" },
792
922
  },
793
923
  required: ["name", "email"],
794
924
  },
795
- searchFields: ["name", "email"],
925
+ searchFields: ["name", "email"], // Fields indexed for search
796
926
  })
797
927
  \`\`\`
798
928
 
799
- ### Roles (\`roles/*.ts\`)
929
+ Entities are stored as:
930
+ \`\`\`json
931
+ {
932
+ "_id": "ent_abc123",
933
+ "type": "customer",
934
+ "data": { "name": "John", "email": "john@example.com", "plan": "pro" },
935
+ "status": "active",
936
+ "createdAt": 1706745600000
937
+ }
938
+ \`\`\`
939
+
940
+ ## Defining Roles (RBAC)
800
941
 
942
+ Create \`roles/support.ts\`:
801
943
  \`\`\`typescript
802
944
  import { defineRole } from 'struere'
803
945
 
804
946
  export default defineRole({
805
- name: "teacher",
806
- description: "Tutors who conduct sessions",
947
+ name: "support",
948
+ description: "Support staff with limited access",
949
+
950
+ // What actions are allowed/denied
807
951
  policies: [
808
- { resource: "session", actions: ["list", "read", "update"], effect: "allow", priority: 50 },
952
+ { resource: "customer", actions: ["list", "read"], effect: "allow", priority: 50 },
953
+ { resource: "customer", actions: ["delete"], effect: "deny", priority: 100 },
809
954
  { resource: "payment", actions: ["*"], effect: "deny", priority: 100 },
810
955
  ],
956
+
957
+ // Row-level security: only see assigned customers
811
958
  scopeRules: [
812
- { entityType: "session", field: "data.teacherId", operator: "eq", value: "actor.userId" },
959
+ {
960
+ entityType: "customer",
961
+ field: "data.assignedTo", // Field in entity data
962
+ operator: "eq",
963
+ value: "actor.userId" // Current user's ID
964
+ },
813
965
  ],
966
+
967
+ // Column-level security: hide sensitive fields
814
968
  fieldMasks: [
815
- { entityType: "session", fieldPath: "data.paymentId", maskType: "hide" },
969
+ { entityType: "customer", fieldPath: "data.ssn", maskType: "hide" },
970
+ { entityType: "customer", fieldPath: "data.creditCard", maskType: "redact" },
816
971
  ],
817
972
  })
818
973
  \`\`\`
819
974
 
820
- ## Built-in Tools
821
-
822
- | Tool | Description |
823
- |------|-------------|
824
- | \`entity.create\` | Create a new entity |
825
- | \`entity.get\` | Get entity by ID |
826
- | \`entity.query\` | Query entities by type/filters |
827
- | \`entity.update\` | Update entity data |
828
- | \`entity.delete\` | Soft-delete entity |
829
- | \`entity.link\` | Create entity relation |
830
- | \`entity.unlink\` | Remove entity relation |
831
- | \`event.emit\` | Emit custom event |
832
- | \`event.query\` | Query events |
833
- | \`job.enqueue\` | Schedule background job |
834
- | \`job.status\` | Get job status |
975
+ ### RBAC Enforcement
976
+
977
+ Every tool call goes through permission checks:
978
+ 1. **Policy check**: Does this role allow the action on this resource?
979
+ 2. **Scope filter**: Query results filtered to rows user can access
980
+ 3. **Field mask**: Sensitive fields hidden/redacted in response
981
+
982
+ Deny policies override allow. Higher priority wins.
983
+
984
+ ## Defining Custom Tools
985
+
986
+ Edit \`tools/index.ts\`:
987
+ \`\`\`typescript
988
+ import { defineTools } from 'struere'
989
+
990
+ export default defineTools([
991
+ {
992
+ name: "send_email",
993
+ description: "Send an email to a recipient",
994
+ parameters: {
995
+ type: "object",
996
+ properties: {
997
+ to: { type: "string", description: "Recipient email" },
998
+ subject: { type: "string", description: "Email subject" },
999
+ body: { type: "string", description: "Email body" },
1000
+ },
1001
+ required: ["to", "subject", "body"],
1002
+ },
1003
+ // Handler runs on Cloudflare Worker (sandboxed)
1004
+ handler: async (args, context, fetch) => {
1005
+ // context = { organizationId, actorId, actorType }
1006
+ // fetch = sandboxed fetch (limited domains)
1007
+
1008
+ const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
1009
+ method: "POST",
1010
+ headers: {
1011
+ "Authorization": \\\`Bearer \\\${process.env.SENDGRID_API_KEY}\\\`,
1012
+ "Content-Type": "application/json",
1013
+ },
1014
+ body: JSON.stringify({
1015
+ personalizations: [{ to: [{ email: args.to }] }],
1016
+ from: { email: "noreply@example.com" },
1017
+ subject: args.subject,
1018
+ content: [{ type: "text/plain", value: args.body }],
1019
+ }),
1020
+ })
1021
+ return { success: response.ok, status: response.status }
1022
+ },
1023
+ },
1024
+
1025
+ {
1026
+ name: "lookup_order",
1027
+ description: "Look up order by ID",
1028
+ parameters: {
1029
+ type: "object",
1030
+ properties: {
1031
+ orderId: { type: "string", description: "Order ID" },
1032
+ },
1033
+ required: ["orderId"],
1034
+ },
1035
+ handler: async (args, context, fetch) => {
1036
+ const res = await fetch(\\\`https://api.myshop.com/orders/\\\${args.orderId}\\\`, {
1037
+ headers: { "X-Org-Id": context.organizationId },
1038
+ })
1039
+ return await res.json()
1040
+ },
1041
+ },
1042
+ ])
1043
+ \`\`\`
1044
+
1045
+ ### Allowed Domains for Custom Tools
1046
+
1047
+ Custom tool handlers can only fetch from:
1048
+ - api.openai.com, api.anthropic.com
1049
+ - api.stripe.com, api.sendgrid.com, api.twilio.com
1050
+ - hooks.slack.com, discord.com, api.github.com
1051
+
1052
+ ## Built-in Tools Reference
1053
+
1054
+ ### Entity Tools
1055
+
1056
+ \`\`\`typescript
1057
+ // entity.create - Create new entity
1058
+ { type: "customer", data: { name: "John", email: "j@example.com" }, status: "active" }
1059
+
1060
+ // entity.get - Get by ID
1061
+ { id: "ent_abc123" }
1062
+
1063
+ // entity.query - Search/filter
1064
+ { type: "customer", filters: { "data.plan": "pro" }, status: "active", limit: 50 }
1065
+
1066
+ // entity.update - Partial update
1067
+ { id: "ent_abc123", data: { plan: "enterprise" } }
1068
+
1069
+ // entity.delete - Soft delete
1070
+ { id: "ent_abc123" }
1071
+
1072
+ // entity.link - Create relation
1073
+ { fromEntityId: "ent_abc", toEntityId: "ent_xyz", relationType: "assigned_to" }
1074
+
1075
+ // entity.unlink - Remove relation
1076
+ { fromEntityId: "ent_abc", toEntityId: "ent_xyz", relationType: "assigned_to" }
1077
+ \`\`\`
1078
+
1079
+ ### Event Tools
1080
+
1081
+ \`\`\`typescript
1082
+ // event.emit - Log an event
1083
+ { eventType: "support.ticket.resolved", entityId: "ent_abc", payload: { rating: 5 } }
1084
+
1085
+ // event.query - Query event history
1086
+ { eventType: "support.ticket.*", entityId: "ent_abc", limit: 20 }
1087
+ \`\`\`
1088
+
1089
+ Events are immutable audit logs. Use for analytics, debugging, compliance.
1090
+
1091
+ ### Job Tools
1092
+
1093
+ \`\`\`typescript
1094
+ // job.enqueue - Schedule background work
1095
+ {
1096
+ jobType: "send_reminder",
1097
+ payload: { customerId: "ent_abc", message: "Your trial ends soon" },
1098
+ runAt: 1706832000000 // Unix timestamp (optional, runs immediately if omitted)
1099
+ }
1100
+
1101
+ // job.status - Check job status
1102
+ { jobId: "job_xyz123" }
1103
+ // Returns: { status: "pending" | "running" | "completed" | "failed", result: {...} }
1104
+ \`\`\`
1105
+
1106
+ Jobs run asynchronously with retry logic. Use for: emails, notifications, data sync.
1107
+
1108
+ ## Invoking Agents (API)
1109
+
1110
+ ### Chat Endpoint
1111
+ \`\`\`bash
1112
+ curl -X POST https://your-convex-url.convex.cloud/v1/chat \\
1113
+ -H "Authorization: Bearer sk_live_xxx" \\
1114
+ -H "Content-Type: application/json" \\
1115
+ -d '{
1116
+ "agentId": "agent_abc123",
1117
+ "message": "What is the status of order #12345?",
1118
+ "threadId": "thread_xyz", // optional, creates new if omitted
1119
+ "metadata": { "customerId": "ent_cust_789" } // available in system prompt
1120
+ }'
1121
+ \`\`\`
1122
+
1123
+ ### Response
1124
+ \`\`\`json
1125
+ {
1126
+ "threadId": "thread_xyz",
1127
+ "message": "Order #12345 is currently being shipped...",
1128
+ "usage": { "inputTokens": 150, "outputTokens": 89 }
1129
+ }
1130
+ \`\`\`
1131
+
1132
+ ### By Slug (production)
1133
+ \`\`\`bash
1134
+ curl -X POST https://your-convex-url.convex.cloud/v1/agents/support/chat \\
1135
+ -H "Authorization: Bearer sk_live_xxx" \\
1136
+ -d '{"message": "Hello"}'
1137
+ \`\`\`
835
1138
 
836
1139
  ## Development Workflow
837
1140
 
838
- 1. Run \`struere dev\` to start watching for changes
839
- 2. Edit agents, entity types, or roles
840
- 3. Changes are automatically synced to Convex
841
- 4. Test via API or dashboard
842
- 5. Run \`struere deploy\` when ready for production
1141
+ 1. \`struere dev\` - Start watching
1142
+ 2. Edit files in agents/, entity-types/, roles/, tools/
1143
+ 3. Save \u2192 auto-syncs to Convex (you'll see "Synced" message)
1144
+ 4. Test via dashboard or curl
1145
+ 5. \`struere deploy\` - Push to production
1146
+
1147
+ ## Common Patterns
1148
+
1149
+ ### Customer Support Agent
1150
+ \`\`\`typescript
1151
+ // agents/support.ts
1152
+ export default defineAgent({
1153
+ name: "Support",
1154
+ slug: "support",
1155
+ version: "0.1.0",
1156
+ systemPrompt: \\\`You help customers with their orders and account issues.
1157
+
1158
+ When a customer asks about an order, use entity.query to find it first.
1159
+ Always be polite and helpful. If you can't help, offer to escalate.\\\`,
1160
+ model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
1161
+ tools: ["entity.query", "entity.get", "entity.update", "event.emit"],
1162
+ })
1163
+ \`\`\`
1164
+
1165
+ ### Scheduling Agent
1166
+ \`\`\`typescript
1167
+ // agents/scheduler.ts
1168
+ export default defineAgent({
1169
+ name: "Scheduler",
1170
+ slug: "scheduler",
1171
+ version: "0.1.0",
1172
+ systemPrompt: \\\`You help schedule appointments between teachers and students.
1173
+
1174
+ Check teacher availability before booking. Create session entities for confirmed bookings.
1175
+ Send confirmation via the send_notification custom tool.\\\`,
1176
+ model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
1177
+ tools: ["entity.create", "entity.query", "job.enqueue", "send_notification"],
1178
+ })
1179
+ \`\`\`
1180
+
1181
+ ### Data Entry Agent
1182
+ \`\`\`typescript
1183
+ // agents/data-entry.ts
1184
+ export default defineAgent({
1185
+ name: "Data Entry",
1186
+ slug: "data-entry",
1187
+ version: "0.1.0",
1188
+ systemPrompt: \\\`You help users create and update records in the system.
1189
+
1190
+ When creating entities, validate the data matches the schema.
1191
+ Always confirm what was created/updated.\\\`,
1192
+ model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
1193
+ tools: ["entity.create", "entity.update", "entity.query"],
1194
+ })
1195
+ \`\`\`
843
1196
  `;
844
1197
  }
845
1198
 
@@ -1374,10 +1727,55 @@ var devCommand = new Command3("dev").description("Sync all resources to developm
1374
1727
  const message = error instanceof Error ? error.message : String(error);
1375
1728
  return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
1376
1729
  };
1730
+ spinner.start("Checking remote state");
1731
+ const { state: remoteState, error: stateError } = await getSyncState();
1732
+ if (stateError) {
1733
+ if (isAuthError(new Error(stateError))) {
1734
+ spinner.fail("Session expired - re-authenticating...");
1735
+ clearCredentials();
1736
+ credentials = await performLogin();
1737
+ if (!credentials) {
1738
+ console.log(chalk3.red("Authentication failed"));
1739
+ process.exit(1);
1740
+ }
1741
+ } else {
1742
+ spinner.fail("Failed to fetch remote state");
1743
+ console.log(chalk3.red("Error:"), stateError);
1744
+ process.exit(1);
1745
+ }
1746
+ } else {
1747
+ spinner.succeed("Remote state fetched");
1748
+ }
1749
+ if (remoteState?.installedPacks && remoteState.installedPacks.length > 0) {
1750
+ console.log();
1751
+ console.log(chalk3.cyan("Installed packs detected:"));
1752
+ for (const pack of remoteState.installedPacks) {
1753
+ console.log(chalk3.gray(" \u2022"), `${pack.packId} v${pack.version}`, chalk3.gray(`(${pack.entityTypeCount} types, ${pack.roleCount} roles)`));
1754
+ }
1755
+ console.log(chalk3.gray(" Pack resources will be preserved during sync."));
1756
+ console.log();
1757
+ }
1758
+ if (remoteState?.agents && remoteState.agents.length > 0) {
1759
+ const localResources = await loadAllResources(cwd);
1760
+ const localSlugs = new Set(localResources.agents.map((a) => a.slug));
1761
+ const unmanagedAgents = remoteState.agents.filter((a) => !localSlugs.has(a.slug));
1762
+ if (unmanagedAgents.length > 0) {
1763
+ console.log(chalk3.cyan("Existing agents not in local files:"));
1764
+ for (const agent of unmanagedAgents) {
1765
+ console.log(chalk3.gray(" \u2022"), agent.name, chalk3.gray(`(${agent.slug})`));
1766
+ }
1767
+ console.log(chalk3.gray(" These agents will be preserved during sync."));
1768
+ console.log();
1769
+ }
1770
+ }
1377
1771
  const performSync = async () => {
1378
1772
  const resources = await loadAllResources(cwd);
1379
1773
  const payload = extractSyncPayload(resources);
1380
- const result = await syncOrganization(payload);
1774
+ const result = await syncOrganization({
1775
+ ...payload,
1776
+ preservePackResources: true,
1777
+ preserveUnmanagedAgents: true
1778
+ });
1381
1779
  if (!result.success) {
1382
1780
  throw new Error(result.error || "Sync failed");
1383
1781
  }
@@ -1433,7 +1831,9 @@ var devCommand = new Command3("dev").description("Sync all resources to developm
1433
1831
  ].filter((p) => existsSync5(p));
1434
1832
  const watcher = chokidar.watch(watchPaths, {
1435
1833
  ignoreInitial: true,
1436
- ignored: /node_modules/
1834
+ ignored: /node_modules/,
1835
+ persistent: true,
1836
+ usePolling: false
1437
1837
  });
1438
1838
  watcher.on("change", async (path) => {
1439
1839
  const relativePath = path.replace(cwd, ".");
@@ -1489,10 +1889,15 @@ var devCommand = new Command3("dev").description("Sync all resources to developm
1489
1889
  console.log(chalk3.red("Error:"), error instanceof Error ? error.message : String(error));
1490
1890
  }
1491
1891
  });
1492
- process.on("SIGINT", () => {
1892
+ let isClosing = false;
1893
+ process.on("SIGINT", async () => {
1894
+ if (isClosing) {
1895
+ process.exit(0);
1896
+ }
1897
+ isClosing = true;
1493
1898
  console.log();
1494
- watcher.close();
1495
- console.log(chalk3.gray("Stopped"));
1899
+ console.log(chalk3.gray("Stopping..."));
1900
+ await watcher.close();
1496
1901
  process.exit(0);
1497
1902
  });
1498
1903
  });
@@ -2470,7 +2875,7 @@ var statusCommand = new Command13("status").description("Compare local vs remote
2470
2875
  // package.json
2471
2876
  var package_default = {
2472
2877
  name: "struere",
2473
- version: "0.4.2",
2878
+ version: "0.4.4",
2474
2879
  description: "Build, test, and deploy AI agents",
2475
2880
  keywords: [
2476
2881
  "ai",