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/bin/struere.js +481 -76
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/index.js +481 -76
- package/dist/cli/templates/index.d.ts.map +1 -1
- package/dist/cli/utils/convex.d.ts +17 -1
- package/dist/cli/utils/convex.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/define/context.d.ts +0 -3
- package/dist/define/context.d.ts.map +0 -1
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
|
|
247
|
-
|
|
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
|
|
297
|
-
|
|
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
|
|
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 (
|
|
681
|
-
const timezone = (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
735
|
-
\u2514\u2500\u2500 index.ts # Re-exports
|
|
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/ #
|
|
738
|
-
\u251C\u2500\u2500
|
|
739
|
-
\u2514\u2500\u2500 index.ts
|
|
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/ #
|
|
742
|
-
\u251C\u2500\u2500 admin.ts #
|
|
743
|
-
\
|
|
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/ #
|
|
746
|
-
\u2514\u2500\u2500 index.ts #
|
|
823
|
+
tools/ # Custom tools shared by all agents
|
|
824
|
+
\u2514\u2500\u2500 index.ts # defineTools([...])
|
|
747
825
|
|
|
748
|
-
struere.json # Organization
|
|
749
|
-
struere.config.ts #
|
|
826
|
+
struere.json # Organization ID (don't edit)
|
|
827
|
+
struere.config.ts # Local dev settings (port, CORS)
|
|
750
828
|
\`\`\`
|
|
751
829
|
|
|
752
|
-
##
|
|
830
|
+
## Configuration Files
|
|
753
831
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
##
|
|
850
|
+
## CLI Commands
|
|
762
851
|
|
|
763
|
-
|
|
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: "
|
|
770
|
-
slug: "
|
|
868
|
+
name: "Support Agent",
|
|
869
|
+
slug: "support",
|
|
771
870
|
version: "0.1.0",
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
###
|
|
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: "
|
|
785
|
-
slug: "
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
806
|
-
description: "
|
|
947
|
+
name: "support",
|
|
948
|
+
description: "Support staff with limited access",
|
|
949
|
+
|
|
950
|
+
// What actions are allowed/denied
|
|
807
951
|
policies: [
|
|
808
|
-
{ resource: "
|
|
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
|
-
{
|
|
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: "
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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.
|
|
839
|
-
2. Edit agents
|
|
840
|
-
3.
|
|
841
|
-
4. Test via
|
|
842
|
-
5.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1495
|
-
|
|
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.
|
|
2878
|
+
version: "0.4.4",
|
|
2474
2879
|
description: "Build, test, and deploy AI agents",
|
|
2475
2880
|
keywords: [
|
|
2476
2881
|
"ai",
|