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/bin/struere.js
CHANGED
|
@@ -16904,8 +16904,11 @@ async function syncOrganization(payload) {
|
|
|
16904
16904
|
const error = await response.text();
|
|
16905
16905
|
return { success: false, error };
|
|
16906
16906
|
}
|
|
16907
|
-
const
|
|
16908
|
-
|
|
16907
|
+
const json = await response.json();
|
|
16908
|
+
if (json.status === "success" && json.value) {
|
|
16909
|
+
return json.value;
|
|
16910
|
+
}
|
|
16911
|
+
return { success: false, error: "Unexpected response format" };
|
|
16909
16912
|
}
|
|
16910
16913
|
async function getSyncState() {
|
|
16911
16914
|
const credentials = loadCredentials();
|
|
@@ -16954,8 +16957,11 @@ async function deployAllAgents() {
|
|
|
16954
16957
|
const error = await response.text();
|
|
16955
16958
|
return { success: false, error };
|
|
16956
16959
|
}
|
|
16957
|
-
const
|
|
16958
|
-
|
|
16960
|
+
const json = await response.json();
|
|
16961
|
+
if (json.status === "success" && json.value) {
|
|
16962
|
+
return json.value;
|
|
16963
|
+
}
|
|
16964
|
+
return { success: false, error: "Unexpected response format" };
|
|
16959
16965
|
}
|
|
16960
16966
|
|
|
16961
16967
|
// src/cli/commands/login.ts
|
|
@@ -17325,10 +17331,10 @@ function getIndexTs(type) {
|
|
|
17325
17331
|
function getToolsIndexTs() {
|
|
17326
17332
|
return `import { defineTools } from 'struere'
|
|
17327
17333
|
|
|
17328
|
-
export
|
|
17334
|
+
export default defineTools([
|
|
17329
17335
|
{
|
|
17330
17336
|
name: 'get_current_time',
|
|
17331
|
-
description: 'Get the current date and time',
|
|
17337
|
+
description: 'Get the current date and time in a specific timezone',
|
|
17332
17338
|
parameters: {
|
|
17333
17339
|
type: 'object',
|
|
17334
17340
|
properties: {
|
|
@@ -17338,19 +17344,59 @@ export const tools = defineTools([
|
|
|
17338
17344
|
},
|
|
17339
17345
|
},
|
|
17340
17346
|
},
|
|
17341
|
-
handler: async (
|
|
17342
|
-
const timezone = (
|
|
17347
|
+
handler: async (args, context, fetch) => {
|
|
17348
|
+
const timezone = (args.timezone as string) || 'UTC'
|
|
17343
17349
|
const now = new Date()
|
|
17344
17350
|
return {
|
|
17345
17351
|
timestamp: now.toISOString(),
|
|
17346
17352
|
formatted: now.toLocaleString('en-US', { timeZone: timezone }),
|
|
17347
17353
|
timezone,
|
|
17354
|
+
organizationId: context.organizationId,
|
|
17348
17355
|
}
|
|
17349
17356
|
},
|
|
17350
17357
|
},
|
|
17351
|
-
])
|
|
17352
17358
|
|
|
17353
|
-
|
|
17359
|
+
{
|
|
17360
|
+
name: 'send_slack_message',
|
|
17361
|
+
description: 'Send a message to a Slack channel via webhook',
|
|
17362
|
+
parameters: {
|
|
17363
|
+
type: 'object',
|
|
17364
|
+
properties: {
|
|
17365
|
+
message: {
|
|
17366
|
+
type: 'string',
|
|
17367
|
+
description: 'The message to send',
|
|
17368
|
+
},
|
|
17369
|
+
channel: {
|
|
17370
|
+
type: 'string',
|
|
17371
|
+
description: 'Channel name (for logging purposes)',
|
|
17372
|
+
},
|
|
17373
|
+
},
|
|
17374
|
+
required: ['message'],
|
|
17375
|
+
},
|
|
17376
|
+
handler: async (args, context, fetch) => {
|
|
17377
|
+
const webhookUrl = process.env.SLACK_WEBHOOK_URL
|
|
17378
|
+
if (!webhookUrl) {
|
|
17379
|
+
return { success: false, error: 'SLACK_WEBHOOK_URL not configured' }
|
|
17380
|
+
}
|
|
17381
|
+
|
|
17382
|
+
const response = await fetch(webhookUrl, {
|
|
17383
|
+
method: 'POST',
|
|
17384
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17385
|
+
body: JSON.stringify({
|
|
17386
|
+
text: args.message,
|
|
17387
|
+
username: 'Struere Agent',
|
|
17388
|
+
}),
|
|
17389
|
+
})
|
|
17390
|
+
|
|
17391
|
+
return {
|
|
17392
|
+
success: response.ok,
|
|
17393
|
+
status: response.status,
|
|
17394
|
+
actorId: context.actorId,
|
|
17395
|
+
actorType: context.actorType,
|
|
17396
|
+
}
|
|
17397
|
+
},
|
|
17398
|
+
},
|
|
17399
|
+
])
|
|
17354
17400
|
`;
|
|
17355
17401
|
}
|
|
17356
17402
|
function getStruereJsonV2(orgId, orgSlug, orgName) {
|
|
@@ -17384,123 +17430,430 @@ function getPackageJsonV2(name) {
|
|
|
17384
17430
|
}, null, 2);
|
|
17385
17431
|
}
|
|
17386
17432
|
function getClaudeMDV2(orgName) {
|
|
17387
|
-
return `# ${orgName} - Struere
|
|
17433
|
+
return `# ${orgName} - Struere Workspace
|
|
17388
17434
|
|
|
17389
|
-
This is a
|
|
17435
|
+
> **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
|
|
17436
|
+
|
|
17437
|
+
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.
|
|
17438
|
+
|
|
17439
|
+
## How It Works
|
|
17440
|
+
|
|
17441
|
+
\`\`\`
|
|
17442
|
+
\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
|
|
17443
|
+
\u2502 Your Project (this folder) \u2502
|
|
17444
|
+
\u2502 \u251C\u2500\u2500 agents/*.ts \u2192 Agent configs synced to Convex \u2502
|
|
17445
|
+
\u2502 \u251C\u2500\u2500 entity-types/*.ts \u2192 Schema definitions synced to Convex \u2502
|
|
17446
|
+
\u2502 \u251C\u2500\u2500 roles/*.ts \u2192 RBAC policies synced to Convex \u2502
|
|
17447
|
+
\u2502 \u2514\u2500\u2500 tools/index.ts \u2192 Custom tools (handlers run on CF Worker)\u2502
|
|
17448
|
+
\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
|
|
17449
|
+
\u2502 struere dev (watches & syncs)
|
|
17450
|
+
\u25BC
|
|
17451
|
+
\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
|
|
17452
|
+
\u2502 Convex (Real-time Backend) \u2502
|
|
17453
|
+
\u2502 \u2022 Stores agent configs, entities, events, jobs \u2502
|
|
17454
|
+
\u2502 \u2022 Runs LLM calls (Anthropic/OpenAI) \u2502
|
|
17455
|
+
\u2502 \u2022 Enforces RBAC on every operation \u2502
|
|
17456
|
+
\u2502 \u2022 Executes custom tools via Cloudflare Worker \u2502
|
|
17457
|
+
\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
|
|
17458
|
+
\u2502 HTTP API
|
|
17459
|
+
\u25BC
|
|
17460
|
+
\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
|
|
17461
|
+
\u2502 Clients \u2502
|
|
17462
|
+
\u2502 \u2022 Dashboard (chat UI, entity browser) \u2502
|
|
17463
|
+
\u2502 \u2022 Your app (REST API with Bearer token) \u2502
|
|
17464
|
+
\u2502 \u2022 WhatsApp/Webhooks \u2502
|
|
17465
|
+
\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
|
|
17466
|
+
\`\`\`
|
|
17390
17467
|
|
|
17391
17468
|
## Project Structure
|
|
17392
17469
|
|
|
17393
17470
|
\`\`\`
|
|
17394
|
-
agents/ # Agent definitions
|
|
17395
|
-
\u251C\u2500\u2500
|
|
17396
|
-
\u2514\u2500\u2500 index.ts # Re-exports
|
|
17471
|
+
agents/ # Agent definitions (synced to Convex)
|
|
17472
|
+
\u251C\u2500\u2500 my-agent.ts # One file per agent
|
|
17473
|
+
\u2514\u2500\u2500 index.ts # Re-exports (optional)
|
|
17397
17474
|
|
|
17398
|
-
entity-types/ #
|
|
17399
|
-
\u251C\u2500\u2500
|
|
17400
|
-
\u2514\u2500\u2500 index.ts
|
|
17475
|
+
entity-types/ # Data schemas (like DB tables)
|
|
17476
|
+
\u251C\u2500\u2500 customer.ts # Defines shape of "customer" entities
|
|
17477
|
+
\u2514\u2500\u2500 index.ts
|
|
17401
17478
|
|
|
17402
|
-
roles/ #
|
|
17403
|
-
\u251C\u2500\u2500 admin.ts #
|
|
17404
|
-
\
|
|
17479
|
+
roles/ # RBAC: who can do what
|
|
17480
|
+
\u251C\u2500\u2500 admin.ts # Full access
|
|
17481
|
+
\u251C\u2500\u2500 support.ts # Limited access
|
|
17482
|
+
\u2514\u2500\u2500 index.ts
|
|
17405
17483
|
|
|
17406
|
-
tools/ #
|
|
17407
|
-
\u2514\u2500\u2500 index.ts #
|
|
17484
|
+
tools/ # Custom tools shared by all agents
|
|
17485
|
+
\u2514\u2500\u2500 index.ts # defineTools([...])
|
|
17408
17486
|
|
|
17409
|
-
struere.json # Organization
|
|
17410
|
-
struere.config.ts #
|
|
17487
|
+
struere.json # Organization ID (don't edit)
|
|
17488
|
+
struere.config.ts # Local dev settings (port, CORS)
|
|
17411
17489
|
\`\`\`
|
|
17412
17490
|
|
|
17413
|
-
##
|
|
17491
|
+
## Configuration Files
|
|
17414
17492
|
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17493
|
+
### struere.json (auto-generated, don't edit)
|
|
17494
|
+
Links this project to your Convex organization:
|
|
17495
|
+
\`\`\`json
|
|
17496
|
+
{
|
|
17497
|
+
"version": "2.0",
|
|
17498
|
+
"organization": { "id": "org_xxx", "slug": "my-org", "name": "My Org" }
|
|
17499
|
+
}
|
|
17500
|
+
\`\`\`
|
|
17421
17501
|
|
|
17422
|
-
|
|
17502
|
+
### struere.config.ts (optional local settings)
|
|
17503
|
+
\`\`\`typescript
|
|
17504
|
+
import { defineConfig } from 'struere'
|
|
17505
|
+
export default defineConfig({
|
|
17506
|
+
port: 3000, // Local dev server port
|
|
17507
|
+
logging: { level: 'debug' }
|
|
17508
|
+
})
|
|
17509
|
+
\`\`\`
|
|
17510
|
+
|
|
17511
|
+
## CLI Commands
|
|
17512
|
+
|
|
17513
|
+
| Command | What it does |
|
|
17514
|
+
|---------|--------------|
|
|
17515
|
+
| \`struere dev\` | Watch files, sync to Convex on every save |
|
|
17516
|
+
| \`struere deploy\` | Copy dev config to production for all agents |
|
|
17517
|
+
| \`struere add agent <name>\` | Create agents/name.ts with template |
|
|
17518
|
+
| \`struere add entity-type <name>\` | Create entity-types/name.ts |
|
|
17519
|
+
| \`struere add role <name>\` | Create roles/name.ts |
|
|
17520
|
+
| \`struere status\` | Show what's synced vs local-only |
|
|
17423
17521
|
|
|
17424
|
-
|
|
17522
|
+
## Defining Agents
|
|
17425
17523
|
|
|
17524
|
+
Create \`agents/support.ts\`:
|
|
17426
17525
|
\`\`\`typescript
|
|
17427
17526
|
import { defineAgent } from 'struere'
|
|
17428
17527
|
|
|
17429
17528
|
export default defineAgent({
|
|
17430
|
-
name: "
|
|
17431
|
-
slug: "
|
|
17529
|
+
name: "Support Agent",
|
|
17530
|
+
slug: "support",
|
|
17432
17531
|
version: "0.1.0",
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17532
|
+
model: {
|
|
17533
|
+
provider: "anthropic", // or "openai", "google"
|
|
17534
|
+
name: "claude-sonnet-4-20250514",
|
|
17535
|
+
temperature: 0.7,
|
|
17536
|
+
maxTokens: 4096,
|
|
17537
|
+
},
|
|
17538
|
+
systemPrompt: \\\`You are a support agent for {{organizationName}}.
|
|
17539
|
+
Current time: {{currentTime}}
|
|
17540
|
+
|
|
17541
|
+
Available customers:
|
|
17542
|
+
{{#each entityTypes}}
|
|
17543
|
+
- {{this.name}}: {{this.description}}
|
|
17544
|
+
{{/each}}
|
|
17545
|
+
|
|
17546
|
+
Use entity.query to look up customer info before responding.\\\`,
|
|
17547
|
+
tools: [
|
|
17548
|
+
"entity.query",
|
|
17549
|
+
"entity.get",
|
|
17550
|
+
"entity.update",
|
|
17551
|
+
"event.emit",
|
|
17552
|
+
"send_email", // custom tool from tools/index.ts
|
|
17553
|
+
],
|
|
17436
17554
|
})
|
|
17437
17555
|
\`\`\`
|
|
17438
17556
|
|
|
17439
|
-
###
|
|
17557
|
+
### System Prompt Variables
|
|
17558
|
+
|
|
17559
|
+
| Variable | Value |
|
|
17560
|
+
|----------|-------|
|
|
17561
|
+
| \`{{currentTime}}\` | ISO 8601 timestamp |
|
|
17562
|
+
| \`{{organizationName}}\` | Your org name |
|
|
17563
|
+
| \`{{agentName}}\` | This agent's name |
|
|
17564
|
+
| \`{{entityTypes}}\` | Array of all entity types (for #each loops) |
|
|
17565
|
+
| \`{{roles}}\` | Array of all roles |
|
|
17440
17566
|
|
|
17567
|
+
## Defining Entity Types
|
|
17568
|
+
|
|
17569
|
+
Create \`entity-types/customer.ts\`:
|
|
17441
17570
|
\`\`\`typescript
|
|
17442
17571
|
import { defineEntityType } from 'struere'
|
|
17443
17572
|
|
|
17444
17573
|
export default defineEntityType({
|
|
17445
|
-
name: "
|
|
17446
|
-
slug: "
|
|
17574
|
+
name: "Customer",
|
|
17575
|
+
slug: "customer",
|
|
17447
17576
|
schema: {
|
|
17448
17577
|
type: "object",
|
|
17449
17578
|
properties: {
|
|
17450
17579
|
name: { type: "string" },
|
|
17451
17580
|
email: { type: "string", format: "email" },
|
|
17452
|
-
|
|
17581
|
+
plan: { type: "string", enum: ["free", "pro", "enterprise"] },
|
|
17582
|
+
metadata: { type: "object" },
|
|
17453
17583
|
},
|
|
17454
17584
|
required: ["name", "email"],
|
|
17455
17585
|
},
|
|
17456
|
-
searchFields: ["name", "email"],
|
|
17586
|
+
searchFields: ["name", "email"], // Fields indexed for search
|
|
17457
17587
|
})
|
|
17458
17588
|
\`\`\`
|
|
17459
17589
|
|
|
17460
|
-
|
|
17590
|
+
Entities are stored as:
|
|
17591
|
+
\`\`\`json
|
|
17592
|
+
{
|
|
17593
|
+
"_id": "ent_abc123",
|
|
17594
|
+
"type": "customer",
|
|
17595
|
+
"data": { "name": "John", "email": "john@example.com", "plan": "pro" },
|
|
17596
|
+
"status": "active",
|
|
17597
|
+
"createdAt": 1706745600000
|
|
17598
|
+
}
|
|
17599
|
+
\`\`\`
|
|
17600
|
+
|
|
17601
|
+
## Defining Roles (RBAC)
|
|
17461
17602
|
|
|
17603
|
+
Create \`roles/support.ts\`:
|
|
17462
17604
|
\`\`\`typescript
|
|
17463
17605
|
import { defineRole } from 'struere'
|
|
17464
17606
|
|
|
17465
17607
|
export default defineRole({
|
|
17466
|
-
name: "
|
|
17467
|
-
description: "
|
|
17608
|
+
name: "support",
|
|
17609
|
+
description: "Support staff with limited access",
|
|
17610
|
+
|
|
17611
|
+
// What actions are allowed/denied
|
|
17468
17612
|
policies: [
|
|
17469
|
-
{ resource: "
|
|
17613
|
+
{ resource: "customer", actions: ["list", "read"], effect: "allow", priority: 50 },
|
|
17614
|
+
{ resource: "customer", actions: ["delete"], effect: "deny", priority: 100 },
|
|
17470
17615
|
{ resource: "payment", actions: ["*"], effect: "deny", priority: 100 },
|
|
17471
17616
|
],
|
|
17617
|
+
|
|
17618
|
+
// Row-level security: only see assigned customers
|
|
17472
17619
|
scopeRules: [
|
|
17473
|
-
{
|
|
17620
|
+
{
|
|
17621
|
+
entityType: "customer",
|
|
17622
|
+
field: "data.assignedTo", // Field in entity data
|
|
17623
|
+
operator: "eq",
|
|
17624
|
+
value: "actor.userId" // Current user's ID
|
|
17625
|
+
},
|
|
17474
17626
|
],
|
|
17627
|
+
|
|
17628
|
+
// Column-level security: hide sensitive fields
|
|
17475
17629
|
fieldMasks: [
|
|
17476
|
-
{ entityType: "
|
|
17630
|
+
{ entityType: "customer", fieldPath: "data.ssn", maskType: "hide" },
|
|
17631
|
+
{ entityType: "customer", fieldPath: "data.creditCard", maskType: "redact" },
|
|
17477
17632
|
],
|
|
17478
17633
|
})
|
|
17479
17634
|
\`\`\`
|
|
17480
17635
|
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
|
|
17486
|
-
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17636
|
+
### RBAC Enforcement
|
|
17637
|
+
|
|
17638
|
+
Every tool call goes through permission checks:
|
|
17639
|
+
1. **Policy check**: Does this role allow the action on this resource?
|
|
17640
|
+
2. **Scope filter**: Query results filtered to rows user can access
|
|
17641
|
+
3. **Field mask**: Sensitive fields hidden/redacted in response
|
|
17642
|
+
|
|
17643
|
+
Deny policies override allow. Higher priority wins.
|
|
17644
|
+
|
|
17645
|
+
## Defining Custom Tools
|
|
17646
|
+
|
|
17647
|
+
Edit \`tools/index.ts\`:
|
|
17648
|
+
\`\`\`typescript
|
|
17649
|
+
import { defineTools } from 'struere'
|
|
17650
|
+
|
|
17651
|
+
export default defineTools([
|
|
17652
|
+
{
|
|
17653
|
+
name: "send_email",
|
|
17654
|
+
description: "Send an email to a recipient",
|
|
17655
|
+
parameters: {
|
|
17656
|
+
type: "object",
|
|
17657
|
+
properties: {
|
|
17658
|
+
to: { type: "string", description: "Recipient email" },
|
|
17659
|
+
subject: { type: "string", description: "Email subject" },
|
|
17660
|
+
body: { type: "string", description: "Email body" },
|
|
17661
|
+
},
|
|
17662
|
+
required: ["to", "subject", "body"],
|
|
17663
|
+
},
|
|
17664
|
+
// Handler runs on Cloudflare Worker (sandboxed)
|
|
17665
|
+
handler: async (args, context, fetch) => {
|
|
17666
|
+
// context = { organizationId, actorId, actorType }
|
|
17667
|
+
// fetch = sandboxed fetch (limited domains)
|
|
17668
|
+
|
|
17669
|
+
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
|
|
17670
|
+
method: "POST",
|
|
17671
|
+
headers: {
|
|
17672
|
+
"Authorization": \\\`Bearer \\\${process.env.SENDGRID_API_KEY}\\\`,
|
|
17673
|
+
"Content-Type": "application/json",
|
|
17674
|
+
},
|
|
17675
|
+
body: JSON.stringify({
|
|
17676
|
+
personalizations: [{ to: [{ email: args.to }] }],
|
|
17677
|
+
from: { email: "noreply@example.com" },
|
|
17678
|
+
subject: args.subject,
|
|
17679
|
+
content: [{ type: "text/plain", value: args.body }],
|
|
17680
|
+
}),
|
|
17681
|
+
})
|
|
17682
|
+
return { success: response.ok, status: response.status }
|
|
17683
|
+
},
|
|
17684
|
+
},
|
|
17685
|
+
|
|
17686
|
+
{
|
|
17687
|
+
name: "lookup_order",
|
|
17688
|
+
description: "Look up order by ID",
|
|
17689
|
+
parameters: {
|
|
17690
|
+
type: "object",
|
|
17691
|
+
properties: {
|
|
17692
|
+
orderId: { type: "string", description: "Order ID" },
|
|
17693
|
+
},
|
|
17694
|
+
required: ["orderId"],
|
|
17695
|
+
},
|
|
17696
|
+
handler: async (args, context, fetch) => {
|
|
17697
|
+
const res = await fetch(\\\`https://api.myshop.com/orders/\\\${args.orderId}\\\`, {
|
|
17698
|
+
headers: { "X-Org-Id": context.organizationId },
|
|
17699
|
+
})
|
|
17700
|
+
return await res.json()
|
|
17701
|
+
},
|
|
17702
|
+
},
|
|
17703
|
+
])
|
|
17704
|
+
\`\`\`
|
|
17705
|
+
|
|
17706
|
+
### Allowed Domains for Custom Tools
|
|
17707
|
+
|
|
17708
|
+
Custom tool handlers can only fetch from:
|
|
17709
|
+
- api.openai.com, api.anthropic.com
|
|
17710
|
+
- api.stripe.com, api.sendgrid.com, api.twilio.com
|
|
17711
|
+
- hooks.slack.com, discord.com, api.github.com
|
|
17712
|
+
|
|
17713
|
+
## Built-in Tools Reference
|
|
17714
|
+
|
|
17715
|
+
### Entity Tools
|
|
17716
|
+
|
|
17717
|
+
\`\`\`typescript
|
|
17718
|
+
// entity.create - Create new entity
|
|
17719
|
+
{ type: "customer", data: { name: "John", email: "j@example.com" }, status: "active" }
|
|
17720
|
+
|
|
17721
|
+
// entity.get - Get by ID
|
|
17722
|
+
{ id: "ent_abc123" }
|
|
17723
|
+
|
|
17724
|
+
// entity.query - Search/filter
|
|
17725
|
+
{ type: "customer", filters: { "data.plan": "pro" }, status: "active", limit: 50 }
|
|
17726
|
+
|
|
17727
|
+
// entity.update - Partial update
|
|
17728
|
+
{ id: "ent_abc123", data: { plan: "enterprise" } }
|
|
17729
|
+
|
|
17730
|
+
// entity.delete - Soft delete
|
|
17731
|
+
{ id: "ent_abc123" }
|
|
17732
|
+
|
|
17733
|
+
// entity.link - Create relation
|
|
17734
|
+
{ fromEntityId: "ent_abc", toEntityId: "ent_xyz", relationType: "assigned_to" }
|
|
17735
|
+
|
|
17736
|
+
// entity.unlink - Remove relation
|
|
17737
|
+
{ fromEntityId: "ent_abc", toEntityId: "ent_xyz", relationType: "assigned_to" }
|
|
17738
|
+
\`\`\`
|
|
17739
|
+
|
|
17740
|
+
### Event Tools
|
|
17741
|
+
|
|
17742
|
+
\`\`\`typescript
|
|
17743
|
+
// event.emit - Log an event
|
|
17744
|
+
{ eventType: "support.ticket.resolved", entityId: "ent_abc", payload: { rating: 5 } }
|
|
17745
|
+
|
|
17746
|
+
// event.query - Query event history
|
|
17747
|
+
{ eventType: "support.ticket.*", entityId: "ent_abc", limit: 20 }
|
|
17748
|
+
\`\`\`
|
|
17749
|
+
|
|
17750
|
+
Events are immutable audit logs. Use for analytics, debugging, compliance.
|
|
17751
|
+
|
|
17752
|
+
### Job Tools
|
|
17753
|
+
|
|
17754
|
+
\`\`\`typescript
|
|
17755
|
+
// job.enqueue - Schedule background work
|
|
17756
|
+
{
|
|
17757
|
+
jobType: "send_reminder",
|
|
17758
|
+
payload: { customerId: "ent_abc", message: "Your trial ends soon" },
|
|
17759
|
+
runAt: 1706832000000 // Unix timestamp (optional, runs immediately if omitted)
|
|
17760
|
+
}
|
|
17761
|
+
|
|
17762
|
+
// job.status - Check job status
|
|
17763
|
+
{ jobId: "job_xyz123" }
|
|
17764
|
+
// Returns: { status: "pending" | "running" | "completed" | "failed", result: {...} }
|
|
17765
|
+
\`\`\`
|
|
17766
|
+
|
|
17767
|
+
Jobs run asynchronously with retry logic. Use for: emails, notifications, data sync.
|
|
17768
|
+
|
|
17769
|
+
## Invoking Agents (API)
|
|
17770
|
+
|
|
17771
|
+
### Chat Endpoint
|
|
17772
|
+
\`\`\`bash
|
|
17773
|
+
curl -X POST https://your-convex-url.convex.cloud/v1/chat \\
|
|
17774
|
+
-H "Authorization: Bearer sk_live_xxx" \\
|
|
17775
|
+
-H "Content-Type: application/json" \\
|
|
17776
|
+
-d '{
|
|
17777
|
+
"agentId": "agent_abc123",
|
|
17778
|
+
"message": "What is the status of order #12345?",
|
|
17779
|
+
"threadId": "thread_xyz", // optional, creates new if omitted
|
|
17780
|
+
"metadata": { "customerId": "ent_cust_789" } // available in system prompt
|
|
17781
|
+
}'
|
|
17782
|
+
\`\`\`
|
|
17783
|
+
|
|
17784
|
+
### Response
|
|
17785
|
+
\`\`\`json
|
|
17786
|
+
{
|
|
17787
|
+
"threadId": "thread_xyz",
|
|
17788
|
+
"message": "Order #12345 is currently being shipped...",
|
|
17789
|
+
"usage": { "inputTokens": 150, "outputTokens": 89 }
|
|
17790
|
+
}
|
|
17791
|
+
\`\`\`
|
|
17792
|
+
|
|
17793
|
+
### By Slug (production)
|
|
17794
|
+
\`\`\`bash
|
|
17795
|
+
curl -X POST https://your-convex-url.convex.cloud/v1/agents/support/chat \\
|
|
17796
|
+
-H "Authorization: Bearer sk_live_xxx" \\
|
|
17797
|
+
-d '{"message": "Hello"}'
|
|
17798
|
+
\`\`\`
|
|
17496
17799
|
|
|
17497
17800
|
## Development Workflow
|
|
17498
17801
|
|
|
17499
|
-
1.
|
|
17500
|
-
2. Edit agents
|
|
17501
|
-
3.
|
|
17502
|
-
4. Test via
|
|
17503
|
-
5.
|
|
17802
|
+
1. \`struere dev\` - Start watching
|
|
17803
|
+
2. Edit files in agents/, entity-types/, roles/, tools/
|
|
17804
|
+
3. Save \u2192 auto-syncs to Convex (you'll see "Synced" message)
|
|
17805
|
+
4. Test via dashboard or curl
|
|
17806
|
+
5. \`struere deploy\` - Push to production
|
|
17807
|
+
|
|
17808
|
+
## Common Patterns
|
|
17809
|
+
|
|
17810
|
+
### Customer Support Agent
|
|
17811
|
+
\`\`\`typescript
|
|
17812
|
+
// agents/support.ts
|
|
17813
|
+
export default defineAgent({
|
|
17814
|
+
name: "Support",
|
|
17815
|
+
slug: "support",
|
|
17816
|
+
version: "0.1.0",
|
|
17817
|
+
systemPrompt: \\\`You help customers with their orders and account issues.
|
|
17818
|
+
|
|
17819
|
+
When a customer asks about an order, use entity.query to find it first.
|
|
17820
|
+
Always be polite and helpful. If you can't help, offer to escalate.\\\`,
|
|
17821
|
+
model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
|
|
17822
|
+
tools: ["entity.query", "entity.get", "entity.update", "event.emit"],
|
|
17823
|
+
})
|
|
17824
|
+
\`\`\`
|
|
17825
|
+
|
|
17826
|
+
### Scheduling Agent
|
|
17827
|
+
\`\`\`typescript
|
|
17828
|
+
// agents/scheduler.ts
|
|
17829
|
+
export default defineAgent({
|
|
17830
|
+
name: "Scheduler",
|
|
17831
|
+
slug: "scheduler",
|
|
17832
|
+
version: "0.1.0",
|
|
17833
|
+
systemPrompt: \\\`You help schedule appointments between teachers and students.
|
|
17834
|
+
|
|
17835
|
+
Check teacher availability before booking. Create session entities for confirmed bookings.
|
|
17836
|
+
Send confirmation via the send_notification custom tool.\\\`,
|
|
17837
|
+
model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
|
|
17838
|
+
tools: ["entity.create", "entity.query", "job.enqueue", "send_notification"],
|
|
17839
|
+
})
|
|
17840
|
+
\`\`\`
|
|
17841
|
+
|
|
17842
|
+
### Data Entry Agent
|
|
17843
|
+
\`\`\`typescript
|
|
17844
|
+
// agents/data-entry.ts
|
|
17845
|
+
export default defineAgent({
|
|
17846
|
+
name: "Data Entry",
|
|
17847
|
+
slug: "data-entry",
|
|
17848
|
+
version: "0.1.0",
|
|
17849
|
+
systemPrompt: \\\`You help users create and update records in the system.
|
|
17850
|
+
|
|
17851
|
+
When creating entities, validate the data matches the schema.
|
|
17852
|
+
Always confirm what was created/updated.\\\`,
|
|
17853
|
+
model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
|
|
17854
|
+
tools: ["entity.create", "entity.update", "entity.query"],
|
|
17855
|
+
})
|
|
17856
|
+
\`\`\`
|
|
17504
17857
|
`;
|
|
17505
17858
|
}
|
|
17506
17859
|
|
|
@@ -18032,10 +18385,55 @@ var devCommand = new Command("dev").description("Sync all resources to developme
|
|
|
18032
18385
|
const message = error instanceof Error ? error.message : String(error);
|
|
18033
18386
|
return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
|
|
18034
18387
|
};
|
|
18388
|
+
spinner.start("Checking remote state");
|
|
18389
|
+
const { state: remoteState, error: stateError } = await getSyncState();
|
|
18390
|
+
if (stateError) {
|
|
18391
|
+
if (isAuthError(new Error(stateError))) {
|
|
18392
|
+
spinner.fail("Session expired - re-authenticating...");
|
|
18393
|
+
clearCredentials();
|
|
18394
|
+
credentials = await performLogin();
|
|
18395
|
+
if (!credentials) {
|
|
18396
|
+
console.log(source_default.red("Authentication failed"));
|
|
18397
|
+
process.exit(1);
|
|
18398
|
+
}
|
|
18399
|
+
} else {
|
|
18400
|
+
spinner.fail("Failed to fetch remote state");
|
|
18401
|
+
console.log(source_default.red("Error:"), stateError);
|
|
18402
|
+
process.exit(1);
|
|
18403
|
+
}
|
|
18404
|
+
} else {
|
|
18405
|
+
spinner.succeed("Remote state fetched");
|
|
18406
|
+
}
|
|
18407
|
+
if (remoteState?.installedPacks && remoteState.installedPacks.length > 0) {
|
|
18408
|
+
console.log();
|
|
18409
|
+
console.log(source_default.cyan("Installed packs detected:"));
|
|
18410
|
+
for (const pack of remoteState.installedPacks) {
|
|
18411
|
+
console.log(source_default.gray(" \u2022"), `${pack.packId} v${pack.version}`, source_default.gray(`(${pack.entityTypeCount} types, ${pack.roleCount} roles)`));
|
|
18412
|
+
}
|
|
18413
|
+
console.log(source_default.gray(" Pack resources will be preserved during sync."));
|
|
18414
|
+
console.log();
|
|
18415
|
+
}
|
|
18416
|
+
if (remoteState?.agents && remoteState.agents.length > 0) {
|
|
18417
|
+
const localResources = await loadAllResources(cwd);
|
|
18418
|
+
const localSlugs = new Set(localResources.agents.map((a) => a.slug));
|
|
18419
|
+
const unmanagedAgents = remoteState.agents.filter((a) => !localSlugs.has(a.slug));
|
|
18420
|
+
if (unmanagedAgents.length > 0) {
|
|
18421
|
+
console.log(source_default.cyan("Existing agents not in local files:"));
|
|
18422
|
+
for (const agent of unmanagedAgents) {
|
|
18423
|
+
console.log(source_default.gray(" \u2022"), agent.name, source_default.gray(`(${agent.slug})`));
|
|
18424
|
+
}
|
|
18425
|
+
console.log(source_default.gray(" These agents will be preserved during sync."));
|
|
18426
|
+
console.log();
|
|
18427
|
+
}
|
|
18428
|
+
}
|
|
18035
18429
|
const performSync = async () => {
|
|
18036
18430
|
const resources = await loadAllResources(cwd);
|
|
18037
18431
|
const payload = extractSyncPayload(resources);
|
|
18038
|
-
const result = await syncOrganization(
|
|
18432
|
+
const result = await syncOrganization({
|
|
18433
|
+
...payload,
|
|
18434
|
+
preservePackResources: true,
|
|
18435
|
+
preserveUnmanagedAgents: true
|
|
18436
|
+
});
|
|
18039
18437
|
if (!result.success) {
|
|
18040
18438
|
throw new Error(result.error || "Sync failed");
|
|
18041
18439
|
}
|
|
@@ -18091,7 +18489,9 @@ var devCommand = new Command("dev").description("Sync all resources to developme
|
|
|
18091
18489
|
].filter((p) => existsSync5(p));
|
|
18092
18490
|
const watcher = import_chokidar.default.watch(watchPaths, {
|
|
18093
18491
|
ignoreInitial: true,
|
|
18094
|
-
ignored: /node_modules
|
|
18492
|
+
ignored: /node_modules/,
|
|
18493
|
+
persistent: true,
|
|
18494
|
+
usePolling: false
|
|
18095
18495
|
});
|
|
18096
18496
|
watcher.on("change", async (path) => {
|
|
18097
18497
|
const relativePath = path.replace(cwd, ".");
|
|
@@ -18147,10 +18547,15 @@ var devCommand = new Command("dev").description("Sync all resources to developme
|
|
|
18147
18547
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18148
18548
|
}
|
|
18149
18549
|
});
|
|
18150
|
-
|
|
18550
|
+
let isClosing = false;
|
|
18551
|
+
process.on("SIGINT", async () => {
|
|
18552
|
+
if (isClosing) {
|
|
18553
|
+
process.exit(0);
|
|
18554
|
+
}
|
|
18555
|
+
isClosing = true;
|
|
18151
18556
|
console.log();
|
|
18152
|
-
|
|
18153
|
-
|
|
18557
|
+
console.log(source_default.gray("Stopping..."));
|
|
18558
|
+
await watcher.close();
|
|
18154
18559
|
process.exit(0);
|
|
18155
18560
|
});
|
|
18156
18561
|
});
|
|
@@ -19100,7 +19505,7 @@ var statusCommand = new Command("status").description("Compare local vs remote s
|
|
|
19100
19505
|
// package.json
|
|
19101
19506
|
var package_default = {
|
|
19102
19507
|
name: "struere",
|
|
19103
|
-
version: "0.4.
|
|
19508
|
+
version: "0.4.4",
|
|
19104
19509
|
description: "Build, test, and deploy AI agents",
|
|
19105
19510
|
keywords: [
|
|
19106
19511
|
"ai",
|