feat: add resend tools, MCP collection endpoint, and UI refinements
- Add @tpmjs/tools-resend with email API tools and blocks.yml entries - Add MCP route for collection skill discovery - Add InstallationSection component for collections - Refactor collection pages to use shared components and simplify layouts - Update skills questions API, rate limiting, and API key handling - Add tpmjs-tool-creator skill for Claude - Update video feature scenes and fix lint issues - Update .gitignore with IDE and temp file exclusions
This commit is contained in:
parent
f3a46045ba
commit
a52d32c367
40 changed files with 3310 additions and 1694 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -9941,6 +9941,556 @@ blocks:
|
|||
description: "Current status of the removal request"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
# ─── Resend Email API ──────────────────────────────────────────────────────
|
||||
|
||||
ops.resendSendEmail:
|
||||
type: utility
|
||||
description: "Send a single email via the Resend API."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /emails endpoint"
|
||||
inputs:
|
||||
- name: from
|
||||
type: string
|
||||
description: "Sender email address"
|
||||
- name: to
|
||||
type: string | string[]
|
||||
description: "Recipient email address(es)"
|
||||
- name: subject
|
||||
type: string
|
||||
description: "Email subject line"
|
||||
- name: html
|
||||
type: string
|
||||
optional: true
|
||||
description: "HTML body content"
|
||||
- name: text
|
||||
type: string
|
||||
optional: true
|
||||
description: "Plain text body content"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Resend email ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendSendBatchEmails:
|
||||
type: utility
|
||||
description: "Send a batch of emails in a single API call via Resend."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /emails/batch endpoint"
|
||||
inputs:
|
||||
- name: emails
|
||||
type: array
|
||||
description: "Array of email objects to send"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of created email IDs"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendGetEmail:
|
||||
type: utility
|
||||
description: "Retrieve details of a sent email by its ID."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /emails/{id} endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "The email ID to retrieve"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Email details including status and content"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendUpdateEmail:
|
||||
type: utility
|
||||
description: "Update a scheduled email's delivery time."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API PATCH /emails/{id} endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "The email ID to update"
|
||||
- name: scheduled_at
|
||||
type: string
|
||||
description: "New scheduled time in ISO 8601 format"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Updated email ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCancelEmail:
|
||||
type: utility
|
||||
description: "Cancel a scheduled email that has not been sent yet."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /emails/{id}/cancel endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "The email ID to cancel"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Cancelled email ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListEmails:
|
||||
type: utility
|
||||
description: "List sent emails with cursor-based pagination."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /emails endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
- name: after
|
||||
type: string
|
||||
optional: true
|
||||
description: "Cursor for forward pagination"
|
||||
- name: before
|
||||
type: string
|
||||
optional: true
|
||||
description: "Cursor for backward pagination"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of email summaries"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCreateDomain:
|
||||
type: utility
|
||||
description: "Add a new sending domain to your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /domains endpoint"
|
||||
inputs:
|
||||
- name: name
|
||||
type: string
|
||||
description: "Domain name to add"
|
||||
- name: region
|
||||
type: string
|
||||
optional: true
|
||||
description: "Region for the domain"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Domain details with DNS records"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendGetDomain:
|
||||
type: utility
|
||||
description: "Retrieve details and DNS records for a specific domain."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /domains/{id} endpoint"
|
||||
inputs:
|
||||
- name: domain_id
|
||||
type: string
|
||||
description: "The domain ID to retrieve"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Domain details including verification status"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendUpdateDomain:
|
||||
type: utility
|
||||
description: "Update tracking and TLS settings for an existing domain."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API PATCH /domains/{id} endpoint"
|
||||
inputs:
|
||||
- name: domain_id
|
||||
type: string
|
||||
description: "The domain ID to update"
|
||||
- name: click_tracking
|
||||
type: boolean
|
||||
optional: true
|
||||
description: "Enable or disable click tracking"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Updated domain ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendDeleteDomain:
|
||||
type: utility
|
||||
description: "Delete a sending domain from your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API DELETE /domains/{id} endpoint"
|
||||
inputs:
|
||||
- name: domain_id
|
||||
type: string
|
||||
description: "The domain ID to delete"
|
||||
outputs:
|
||||
- name: deleted
|
||||
type: boolean
|
||||
description: "Whether deletion succeeded"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListDomains:
|
||||
type: utility
|
||||
description: "List all sending domains with their verification status."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /domains endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of domain objects"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendVerifyDomain:
|
||||
type: utility
|
||||
description: "Trigger DNS verification for a sending domain."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /domains/{id}/verify endpoint"
|
||||
inputs:
|
||||
- name: domain_id
|
||||
type: string
|
||||
description: "The domain ID to verify"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Verified domain ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCreateApiKey:
|
||||
type: utility
|
||||
description: "Create a new Resend API key."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /api-keys endpoint"
|
||||
inputs:
|
||||
- name: name
|
||||
type: string
|
||||
description: "Name for the API key"
|
||||
- name: permission
|
||||
type: string
|
||||
optional: true
|
||||
description: "Permission level"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Created API key ID"
|
||||
- name: token
|
||||
type: string
|
||||
description: "The API key token"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListApiKeys:
|
||||
type: utility
|
||||
description: "List all API keys in your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /api-keys endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of API key objects"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendDeleteApiKey:
|
||||
type: utility
|
||||
description: "Delete an API key from your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API DELETE /api-keys/{id} endpoint"
|
||||
inputs:
|
||||
- name: api_key_id
|
||||
type: string
|
||||
description: "The API key ID to delete"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Deletion confirmation"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCreateContact:
|
||||
type: utility
|
||||
description: "Create a new contact with email and optional properties."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /contacts endpoint"
|
||||
inputs:
|
||||
- name: email
|
||||
type: string
|
||||
description: "Contact email address"
|
||||
- name: first_name
|
||||
type: string
|
||||
optional: true
|
||||
description: "Contact first name"
|
||||
- name: last_name
|
||||
type: string
|
||||
optional: true
|
||||
description: "Contact last name"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Created contact ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendGetContact:
|
||||
type: utility
|
||||
description: "Retrieve a contact by ID or email address."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /contacts/{id} endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Contact ID or email address"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Contact details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendUpdateContact:
|
||||
type: utility
|
||||
description: "Update a contact's name, subscription status, or properties."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API PATCH /contacts/{id} endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Contact ID or email address"
|
||||
- name: first_name
|
||||
type: string
|
||||
optional: true
|
||||
description: "Updated first name"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Updated contact ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendDeleteContact:
|
||||
type: utility
|
||||
description: "Delete a contact by ID or email address."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API DELETE /contacts/{id} endpoint"
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Contact ID or email to delete"
|
||||
outputs:
|
||||
- name: deleted
|
||||
type: boolean
|
||||
description: "Whether deletion succeeded"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListContacts:
|
||||
type: utility
|
||||
description: "List contacts with optional segment filtering and pagination."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /contacts endpoint"
|
||||
inputs:
|
||||
- name: segment_id
|
||||
type: string
|
||||
optional: true
|
||||
description: "Filter by segment ID"
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of contact objects"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCreateBroadcast:
|
||||
type: utility
|
||||
description: "Create a broadcast email draft to send to a segment."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /broadcasts endpoint"
|
||||
inputs:
|
||||
- name: segment_id
|
||||
type: string
|
||||
description: "Segment ID to target"
|
||||
- name: from
|
||||
type: string
|
||||
description: "Sender address"
|
||||
- name: subject
|
||||
type: string
|
||||
description: "Broadcast subject line"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Created broadcast ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListBroadcasts:
|
||||
type: utility
|
||||
description: "List all broadcasts with status and scheduling details."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /broadcasts endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of broadcast objects"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendSendBroadcast:
|
||||
type: utility
|
||||
description: "Send or schedule a previously created broadcast."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /broadcasts/{id}/send endpoint"
|
||||
inputs:
|
||||
- name: broadcast_id
|
||||
type: string
|
||||
description: "The broadcast ID to send"
|
||||
- name: scheduled_at
|
||||
type: string
|
||||
optional: true
|
||||
description: "ISO 8601 datetime to schedule"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Sent broadcast ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendDeleteBroadcast:
|
||||
type: utility
|
||||
description: "Delete a draft broadcast that has not been sent."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API DELETE /broadcasts/{id} endpoint"
|
||||
inputs:
|
||||
- name: broadcast_id
|
||||
type: string
|
||||
description: "The broadcast ID to delete"
|
||||
outputs:
|
||||
- name: deleted
|
||||
type: boolean
|
||||
description: "Whether deletion succeeded"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendCreateAudience:
|
||||
type: utility
|
||||
description: "Create a new audience for organizing contacts."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API POST /audiences endpoint"
|
||||
inputs:
|
||||
- name: name
|
||||
type: string
|
||||
description: "Audience name"
|
||||
outputs:
|
||||
- name: id
|
||||
type: string
|
||||
description: "Created audience ID"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendListAudiences:
|
||||
type: utility
|
||||
description: "List all audiences in your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /audiences endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max results to return"
|
||||
outputs:
|
||||
- name: data
|
||||
type: array
|
||||
description: "Array of audience objects"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendGetAudience:
|
||||
type: utility
|
||||
description: "Retrieve details of a specific audience."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API GET /audiences/{id} endpoint"
|
||||
inputs:
|
||||
- name: audience_id
|
||||
type: string
|
||||
description: "The audience ID to retrieve"
|
||||
outputs:
|
||||
- name: result
|
||||
type: object
|
||||
description: "Audience details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.resendDeleteAudience:
|
||||
type: utility
|
||||
description: "Delete an audience from your Resend account."
|
||||
path: "resend"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call Resend API DELETE /audiences/{id} endpoint"
|
||||
inputs:
|
||||
- name: audience_id
|
||||
type: string
|
||||
description: "The audience ID to delete"
|
||||
outputs:
|
||||
- name: deleted
|
||||
type: boolean
|
||||
description: "Whether deletion succeeded"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
# =============================================================================
|
||||
# VALIDATORS - Which validators to run against each block
|
||||
# =============================================================================
|
||||
|
|
|
|||
66
packages/tools/official/resend/block.ts
Normal file
66
packages/tools/official/resend/block.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import {
|
||||
cancelEmail,
|
||||
createApiKey,
|
||||
createAudience,
|
||||
createBroadcast,
|
||||
createContact,
|
||||
createDomain,
|
||||
deleteApiKey,
|
||||
deleteAudience,
|
||||
deleteBroadcast,
|
||||
deleteContact,
|
||||
deleteDomain,
|
||||
getAudience,
|
||||
getContact,
|
||||
getDomain,
|
||||
getEmail,
|
||||
listApiKeys,
|
||||
listAudiences,
|
||||
listBroadcasts,
|
||||
listContacts,
|
||||
listDomains,
|
||||
listEmails,
|
||||
sendBatchEmails,
|
||||
sendBroadcast,
|
||||
sendEmail,
|
||||
updateContact,
|
||||
updateDomain,
|
||||
updateEmail,
|
||||
verifyDomain,
|
||||
} from './src/index.js';
|
||||
|
||||
export const block = {
|
||||
name: 'resend',
|
||||
tools: {
|
||||
sendEmail,
|
||||
sendBatchEmails,
|
||||
getEmail,
|
||||
updateEmail,
|
||||
cancelEmail,
|
||||
listEmails,
|
||||
createDomain,
|
||||
getDomain,
|
||||
updateDomain,
|
||||
deleteDomain,
|
||||
listDomains,
|
||||
verifyDomain,
|
||||
createApiKey,
|
||||
listApiKeys,
|
||||
deleteApiKey,
|
||||
createContact,
|
||||
getContact,
|
||||
updateContact,
|
||||
deleteContact,
|
||||
listContacts,
|
||||
createBroadcast,
|
||||
listBroadcasts,
|
||||
sendBroadcast,
|
||||
deleteBroadcast,
|
||||
createAudience,
|
||||
listAudiences,
|
||||
getAudience,
|
||||
deleteAudience,
|
||||
},
|
||||
};
|
||||
|
||||
export default block;
|
||||
2
packages/tools/official/resend/index.ts
Normal file
2
packages/tools/official/resend/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './src/index.js';
|
||||
export { default } from './src/index.js';
|
||||
166
packages/tools/official/resend/package.json
Normal file
166
packages/tools/official/resend/package.json
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
{
|
||||
"name": "@tpmjs/tools-resend",
|
||||
"version": "0.1.0",
|
||||
"description": "Resend email API tools for AI agents. Send emails, manage domains, contacts, broadcasts, audiences, and API keys.",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"tpmjs",
|
||||
"resend",
|
||||
"email",
|
||||
"transactional",
|
||||
"agent"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tpmjs/tpmjs.git",
|
||||
"directory": "packages/tools/official/resend"
|
||||
},
|
||||
"homepage": "https://tpmjs.com",
|
||||
"license": "MIT",
|
||||
"tpmjs": {
|
||||
"category": "ops",
|
||||
"frameworks": [
|
||||
"vercel-ai"
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "sendEmail",
|
||||
"description": "Send a single email via the Resend API."
|
||||
},
|
||||
{
|
||||
"name": "sendBatchEmails",
|
||||
"description": "Send a batch of emails in a single API call via Resend."
|
||||
},
|
||||
{
|
||||
"name": "getEmail",
|
||||
"description": "Retrieve details of a specific sent email by ID."
|
||||
},
|
||||
{
|
||||
"name": "updateEmail",
|
||||
"description": "Update a scheduled email by changing its scheduled time."
|
||||
},
|
||||
{
|
||||
"name": "cancelEmail",
|
||||
"description": "Cancel a scheduled email that has not been sent yet."
|
||||
},
|
||||
{
|
||||
"name": "listEmails",
|
||||
"description": "List sent emails with optional cursor-based pagination."
|
||||
},
|
||||
{
|
||||
"name": "createDomain",
|
||||
"description": "Add a new sending domain to your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "getDomain",
|
||||
"description": "Retrieve details and DNS records for a specific domain."
|
||||
},
|
||||
{
|
||||
"name": "updateDomain",
|
||||
"description": "Update tracking and TLS settings for a domain."
|
||||
},
|
||||
{
|
||||
"name": "deleteDomain",
|
||||
"description": "Delete a sending domain from your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "listDomains",
|
||||
"description": "List all sending domains in your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "verifyDomain",
|
||||
"description": "Trigger DNS verification for a sending domain."
|
||||
},
|
||||
{
|
||||
"name": "createApiKey",
|
||||
"description": "Create a new API key with specified permissions."
|
||||
},
|
||||
{
|
||||
"name": "listApiKeys",
|
||||
"description": "List all API keys in your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "deleteApiKey",
|
||||
"description": "Delete an API key from your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "createContact",
|
||||
"description": "Create a new contact with email and optional properties."
|
||||
},
|
||||
{
|
||||
"name": "getContact",
|
||||
"description": "Retrieve a contact by their ID or email address."
|
||||
},
|
||||
{
|
||||
"name": "updateContact",
|
||||
"description": "Update a contact's name, subscription status, or properties."
|
||||
},
|
||||
{
|
||||
"name": "deleteContact",
|
||||
"description": "Delete a contact by their ID or email address."
|
||||
},
|
||||
{
|
||||
"name": "listContacts",
|
||||
"description": "List contacts with optional segment filtering and pagination."
|
||||
},
|
||||
{
|
||||
"name": "createBroadcast",
|
||||
"description": "Create a new broadcast email to send to a segment."
|
||||
},
|
||||
{
|
||||
"name": "listBroadcasts",
|
||||
"description": "List all broadcasts with cursor-based pagination."
|
||||
},
|
||||
{
|
||||
"name": "sendBroadcast",
|
||||
"description": "Send or schedule a previously created broadcast."
|
||||
},
|
||||
{
|
||||
"name": "deleteBroadcast",
|
||||
"description": "Delete a draft broadcast that has not been sent."
|
||||
},
|
||||
{
|
||||
"name": "createAudience",
|
||||
"description": "Create a new audience for organizing contacts."
|
||||
},
|
||||
{
|
||||
"name": "listAudiences",
|
||||
"description": "List all audiences in your Resend account."
|
||||
},
|
||||
{
|
||||
"name": "getAudience",
|
||||
"description": "Retrieve details of a specific audience by ID."
|
||||
},
|
||||
{
|
||||
"name": "deleteAudience",
|
||||
"description": "Delete an audience from your Resend account."
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "6.0.49"
|
||||
}
|
||||
}
|
||||
875
packages/tools/official/resend/src/index.ts
Normal file
875
packages/tools/official/resend/src/index.ts
Normal file
|
|
@ -0,0 +1,875 @@
|
|||
/**
|
||||
* @tpmjs/tools-resend — Resend Email API Tools for AI Agents
|
||||
*
|
||||
* Full access to the Resend email API: send emails, manage domains, contacts,
|
||||
* broadcasts, audiences, and API keys.
|
||||
*
|
||||
* @requires RESEND_API_KEY environment variable
|
||||
*/
|
||||
|
||||
import { jsonSchema, tool } from 'ai';
|
||||
|
||||
const BASE_URL = 'https://api.resend.com';
|
||||
|
||||
// ─── Client Infrastructure ──────────────────────────────────────────────────
|
||||
|
||||
function getApiKey(): string {
|
||||
const key = process.env.RESEND_API_KEY;
|
||||
if (!key) {
|
||||
throw new Error(
|
||||
'RESEND_API_KEY environment variable is required. Get your API key from https://resend.com/api-keys'
|
||||
);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const key = getApiKey();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${key}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const options: RequestInit = { method, headers };
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(`${BASE_URL}${path}`, options);
|
||||
|
||||
if (!response.ok) {
|
||||
await handleApiError(response);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
if (!text) {
|
||||
return {} as T;
|
||||
}
|
||||
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
async function handleApiError(response: Response): Promise<never> {
|
||||
let errorMessage: string;
|
||||
try {
|
||||
const errorData = (await response.json()) as { message?: string; error?: string };
|
||||
errorMessage = errorData.message || errorData.error || `HTTP ${response.status}`;
|
||||
} catch {
|
||||
errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||
}
|
||||
|
||||
switch (response.status) {
|
||||
case 400:
|
||||
throw new Error(`Bad request: ${errorMessage}`);
|
||||
case 401:
|
||||
throw new Error('Authentication failed: Invalid Resend API key. Check RESEND_API_KEY.');
|
||||
case 403:
|
||||
throw new Error(`Access forbidden: ${errorMessage}`);
|
||||
case 404:
|
||||
throw new Error(`Not found: ${errorMessage}`);
|
||||
case 422:
|
||||
throw new Error(`Validation error: ${errorMessage}`);
|
||||
case 429:
|
||||
throw new Error(`Rate limit exceeded: ${errorMessage}`);
|
||||
default:
|
||||
throw new Error(`Resend API error (${response.status}): ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryString(params: Record<string, unknown>): string {
|
||||
const entries = Object.entries(params).filter(
|
||||
([, v]) => v !== undefined && v !== null && v !== ''
|
||||
);
|
||||
if (entries.length === 0) return '';
|
||||
return (
|
||||
'?' +
|
||||
entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join('&')
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Emails ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SendEmailInput {
|
||||
from: string;
|
||||
to: string | string[];
|
||||
subject: string;
|
||||
html?: string;
|
||||
text?: string;
|
||||
cc?: string | string[];
|
||||
bcc?: string | string[];
|
||||
reply_to?: string | string[];
|
||||
headers?: Record<string, string>;
|
||||
attachments?: Array<{ content?: string; filename: string; path?: string; content_type?: string }>;
|
||||
tags?: Array<{ name: string; value: string }>;
|
||||
scheduled_at?: string;
|
||||
topic_id?: string;
|
||||
}
|
||||
|
||||
export interface PaginationInput {
|
||||
limit?: number;
|
||||
after?: string;
|
||||
before?: string;
|
||||
}
|
||||
|
||||
export const sendEmail = tool({
|
||||
description:
|
||||
'Send a single email via the Resend API. Requires from, to, and subject. Provide html and/or text body content.',
|
||||
inputSchema: jsonSchema<SendEmailInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: {
|
||||
type: 'string',
|
||||
description: 'Sender email address. Supports "Name <email>" format.',
|
||||
},
|
||||
to: {
|
||||
oneOf: [
|
||||
{ type: 'string', description: 'Single recipient email address.' },
|
||||
{
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Array of recipient emails (max 50).',
|
||||
},
|
||||
],
|
||||
description: 'Recipient email address(es).',
|
||||
},
|
||||
subject: { type: 'string', description: 'Email subject line.' },
|
||||
html: { type: 'string', description: 'HTML body content.' },
|
||||
text: { type: 'string', description: 'Plain text body content.' },
|
||||
cc: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
description: 'CC recipient(s).',
|
||||
},
|
||||
bcc: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
description: 'BCC recipient(s).',
|
||||
},
|
||||
reply_to: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
description: 'Reply-to address(es).',
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
description: 'Custom email headers as key-value pairs.',
|
||||
},
|
||||
attachments: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: { type: 'string', description: 'Base64-encoded content.' },
|
||||
filename: { type: 'string', description: 'Attachment filename.' },
|
||||
path: { type: 'string', description: 'URL to fetch attachment from.' },
|
||||
content_type: { type: 'string', description: 'MIME type.' },
|
||||
},
|
||||
required: ['filename'],
|
||||
},
|
||||
description: 'File attachments (max 40MB total).',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name.' },
|
||||
value: { type: 'string', description: 'Tag value.' },
|
||||
},
|
||||
required: ['name', 'value'],
|
||||
},
|
||||
description: 'Tags for categorizing emails.',
|
||||
},
|
||||
scheduled_at: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime or natural language (e.g. "in 1 min").',
|
||||
},
|
||||
topic_id: { type: 'string', description: 'Topic ID for subscription management.' },
|
||||
},
|
||||
required: ['from', 'to', 'subject'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: SendEmailInput) {
|
||||
return apiRequest<{ id: string }>('POST', '/emails', input);
|
||||
},
|
||||
});
|
||||
|
||||
export interface SendBatchInput {
|
||||
emails: SendEmailInput[];
|
||||
}
|
||||
|
||||
export const sendBatchEmails = tool({
|
||||
description:
|
||||
'Send a batch of emails in a single API call via Resend. Each email has the same parameters as sendEmail.',
|
||||
inputSchema: jsonSchema<SendBatchInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
emails: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
from: { type: 'string', description: 'Sender email address.' },
|
||||
to: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
description: 'Recipient email address(es).',
|
||||
},
|
||||
subject: { type: 'string', description: 'Email subject line.' },
|
||||
html: { type: 'string', description: 'HTML body content.' },
|
||||
text: { type: 'string', description: 'Plain text body content.' },
|
||||
},
|
||||
required: ['from', 'to', 'subject'],
|
||||
},
|
||||
description: 'Array of email objects to send.',
|
||||
},
|
||||
},
|
||||
required: ['emails'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: SendBatchInput) {
|
||||
return apiRequest<{ data: Array<{ id: string }> }>('POST', '/emails/batch', input.emails);
|
||||
},
|
||||
});
|
||||
|
||||
export interface IdInput {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const getEmail = tool({
|
||||
description:
|
||||
'Retrieve details of a specific sent email by its ID, including status, recipients, and content.',
|
||||
inputSchema: jsonSchema<IdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'The email ID to retrieve.' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: IdInput) {
|
||||
return apiRequest<Record<string, unknown>>('GET', `/emails/${input.id}`);
|
||||
},
|
||||
});
|
||||
|
||||
export interface UpdateEmailInput {
|
||||
id: string;
|
||||
scheduled_at: string;
|
||||
}
|
||||
|
||||
export const updateEmail = tool({
|
||||
description:
|
||||
'Update a scheduled email by changing its scheduled delivery time. Only works on emails not yet sent.',
|
||||
inputSchema: jsonSchema<UpdateEmailInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'The email ID to update.' },
|
||||
scheduled_at: { type: 'string', description: 'New scheduled time in ISO 8601 format.' },
|
||||
},
|
||||
required: ['id', 'scheduled_at'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: UpdateEmailInput) {
|
||||
const { id, ...body } = input;
|
||||
return apiRequest<{ object: string; id: string }>('PATCH', `/emails/${id}`, body);
|
||||
},
|
||||
});
|
||||
|
||||
export const cancelEmail = tool({
|
||||
description:
|
||||
'Cancel a scheduled email that has not been sent yet. Returns the cancelled email ID.',
|
||||
inputSchema: jsonSchema<IdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'The email ID to cancel.' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: IdInput) {
|
||||
return apiRequest<{ object: string; id: string }>('POST', `/emails/${input.id}/cancel`);
|
||||
},
|
||||
});
|
||||
|
||||
export const listEmails = tool({
|
||||
description:
|
||||
'List sent emails with optional cursor-based pagination. Returns email summaries with status.',
|
||||
inputSchema: jsonSchema<PaginationInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results to return (1-100, default 20).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: PaginationInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/emails${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Domains ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateDomainInput {
|
||||
name: string;
|
||||
region?: string;
|
||||
custom_return_path?: string;
|
||||
open_tracking?: boolean;
|
||||
click_tracking?: boolean;
|
||||
tls?: string;
|
||||
}
|
||||
|
||||
export const createDomain = tool({
|
||||
description:
|
||||
'Add a new sending domain to your Resend account. Returns the domain ID and DNS records to configure.',
|
||||
inputSchema: jsonSchema<CreateDomainInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'The domain name to add (e.g. "example.com").' },
|
||||
region: {
|
||||
type: 'string',
|
||||
description: 'Region: us-east-1, eu-west-1, sa-east-1, or ap-northeast-1.',
|
||||
},
|
||||
custom_return_path: {
|
||||
type: 'string',
|
||||
description: 'Subdomain for Return-Path (default: "send").',
|
||||
},
|
||||
open_tracking: { type: 'boolean', description: 'Enable open rate tracking.' },
|
||||
click_tracking: { type: 'boolean', description: 'Enable click tracking in HTML emails.' },
|
||||
tls: { type: 'string', description: 'TLS mode: "opportunistic" or "enforced".' },
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateDomainInput) {
|
||||
return apiRequest<Record<string, unknown>>('POST', '/domains', input);
|
||||
},
|
||||
});
|
||||
|
||||
export interface DomainIdInput {
|
||||
domain_id: string;
|
||||
}
|
||||
|
||||
export const getDomain = tool({
|
||||
description:
|
||||
'Retrieve details and DNS records for a specific domain including verification status.',
|
||||
inputSchema: jsonSchema<DomainIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain_id: { type: 'string', description: 'The domain ID to retrieve.' },
|
||||
},
|
||||
required: ['domain_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: DomainIdInput) {
|
||||
return apiRequest<Record<string, unknown>>('GET', `/domains/${input.domain_id}`);
|
||||
},
|
||||
});
|
||||
|
||||
export interface UpdateDomainInput {
|
||||
domain_id: string;
|
||||
click_tracking?: boolean;
|
||||
open_tracking?: boolean;
|
||||
tls?: string;
|
||||
}
|
||||
|
||||
export const updateDomain = tool({
|
||||
description: 'Update tracking, TLS, and capability settings for an existing domain.',
|
||||
inputSchema: jsonSchema<UpdateDomainInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain_id: { type: 'string', description: 'The domain ID to update.' },
|
||||
click_tracking: { type: 'boolean', description: 'Enable or disable click tracking.' },
|
||||
open_tracking: { type: 'boolean', description: 'Enable or disable open tracking.' },
|
||||
tls: { type: 'string', description: 'TLS mode: "opportunistic" or "enforced".' },
|
||||
},
|
||||
required: ['domain_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: UpdateDomainInput) {
|
||||
const { domain_id, ...body } = input;
|
||||
return apiRequest<{ object: string; id: string }>('PATCH', `/domains/${domain_id}`, body);
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteDomain = tool({
|
||||
description: 'Delete a sending domain from your Resend account permanently.',
|
||||
inputSchema: jsonSchema<DomainIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain_id: { type: 'string', description: 'The domain ID to delete.' },
|
||||
},
|
||||
required: ['domain_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: DomainIdInput) {
|
||||
return apiRequest<{ object: string; id: string; deleted: boolean }>(
|
||||
'DELETE',
|
||||
`/domains/${input.domain_id}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const listDomains = tool({
|
||||
description: 'List all sending domains in your Resend account with their verification status.',
|
||||
inputSchema: jsonSchema<PaginationInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results to return (1-100, default 20).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: PaginationInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/domains${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const verifyDomain = tool({
|
||||
description:
|
||||
'Trigger DNS verification for a sending domain. Check domain status after DNS records are configured.',
|
||||
inputSchema: jsonSchema<DomainIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain_id: { type: 'string', description: 'The domain ID to verify.' },
|
||||
},
|
||||
required: ['domain_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: DomainIdInput) {
|
||||
return apiRequest<{ object: string; id: string }>('POST', `/domains/${input.domain_id}/verify`);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── API Keys ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateApiKeyInput {
|
||||
name: string;
|
||||
permission?: string;
|
||||
domain_id?: string;
|
||||
}
|
||||
|
||||
export const createApiKey = tool({
|
||||
description: 'Create a new Resend API key with specified name and permission level.',
|
||||
inputSchema: jsonSchema<CreateApiKeyInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name for the API key (max 50 characters).' },
|
||||
permission: { type: 'string', description: 'Permission: "full_access" or "sending_access".' },
|
||||
domain_id: { type: 'string', description: 'Restrict to a domain (only for sending_access).' },
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateApiKeyInput) {
|
||||
return apiRequest<{ id: string; token: string }>('POST', '/api-keys', input);
|
||||
},
|
||||
});
|
||||
|
||||
export const listApiKeys = tool({
|
||||
description: 'List all API keys in your Resend account with their names and creation dates.',
|
||||
inputSchema: jsonSchema<PaginationInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results to return (1-100).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: PaginationInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/api-keys${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface ApiKeyIdInput {
|
||||
api_key_id: string;
|
||||
}
|
||||
|
||||
export const deleteApiKey = tool({
|
||||
description: 'Delete an API key from your Resend account permanently.',
|
||||
inputSchema: jsonSchema<ApiKeyIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
api_key_id: { type: 'string', description: 'The API key ID to delete.' },
|
||||
},
|
||||
required: ['api_key_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ApiKeyIdInput) {
|
||||
return apiRequest<Record<string, unknown>>('DELETE', `/api-keys/${input.api_key_id}`);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Contacts ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateContactInput {
|
||||
email: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
unsubscribed?: boolean;
|
||||
properties?: Record<string, string>;
|
||||
}
|
||||
|
||||
export const createContact = tool({
|
||||
description:
|
||||
'Create a new contact with an email address and optional name, properties, and subscription settings.',
|
||||
inputSchema: jsonSchema<CreateContactInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', description: 'Contact email address.' },
|
||||
first_name: { type: 'string', description: 'Contact first name.' },
|
||||
last_name: { type: 'string', description: 'Contact last name.' },
|
||||
unsubscribed: {
|
||||
type: 'boolean',
|
||||
description: 'If true, contact is unsubscribed from all broadcasts.',
|
||||
},
|
||||
properties: {
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
description: 'Custom key-value properties for the contact.',
|
||||
},
|
||||
},
|
||||
required: ['email'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateContactInput) {
|
||||
return apiRequest<{ object: string; id: string }>('POST', '/contacts', input);
|
||||
},
|
||||
});
|
||||
|
||||
export const getContact = tool({
|
||||
description:
|
||||
'Retrieve a contact by their ID or email address, including name, properties, and subscription status.',
|
||||
inputSchema: jsonSchema<IdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Contact ID or email address.' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: IdInput) {
|
||||
return apiRequest<Record<string, unknown>>('GET', `/contacts/${encodeURIComponent(input.id)}`);
|
||||
},
|
||||
});
|
||||
|
||||
export interface UpdateContactInput {
|
||||
id: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
unsubscribed?: boolean;
|
||||
properties?: Record<string, string>;
|
||||
}
|
||||
|
||||
export const updateContact = tool({
|
||||
description: "Update a contact's name, subscription status, or custom properties.",
|
||||
inputSchema: jsonSchema<UpdateContactInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Contact ID or email address.' },
|
||||
first_name: { type: 'string', description: 'Updated first name.' },
|
||||
last_name: { type: 'string', description: 'Updated last name.' },
|
||||
unsubscribed: { type: 'boolean', description: 'Updated subscription status.' },
|
||||
properties: {
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'string' },
|
||||
description: 'Updated custom properties.',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: UpdateContactInput) {
|
||||
const { id, ...body } = input;
|
||||
return apiRequest<{ object: string; id: string }>(
|
||||
'PATCH',
|
||||
`/contacts/${encodeURIComponent(id)}`,
|
||||
body
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteContact = tool({
|
||||
description: 'Delete a contact by their ID or email address permanently.',
|
||||
inputSchema: jsonSchema<IdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Contact ID or email address to delete.' },
|
||||
},
|
||||
required: ['id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: IdInput) {
|
||||
return apiRequest<{ object: string; contact: string; deleted: boolean }>(
|
||||
'DELETE',
|
||||
`/contacts/${encodeURIComponent(input.id)}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface ListContactsInput extends PaginationInput {
|
||||
segment_id?: string;
|
||||
}
|
||||
|
||||
export const listContacts = tool({
|
||||
description: 'List contacts with optional segment filtering and cursor-based pagination.',
|
||||
inputSchema: jsonSchema<ListContactsInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
segment_id: { type: 'string', description: 'Filter contacts by segment ID.' },
|
||||
limit: { type: 'number', description: 'Max results to return (1-100, default 20).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ListContactsInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/contacts${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Broadcasts ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateBroadcastInput {
|
||||
segment_id: string;
|
||||
from: string;
|
||||
subject: string;
|
||||
reply_to?: string | string[];
|
||||
html?: string;
|
||||
text?: string;
|
||||
name?: string;
|
||||
topic_id?: string;
|
||||
}
|
||||
|
||||
export const createBroadcast = tool({
|
||||
description:
|
||||
'Create a new broadcast email draft to send to a segment. Use sendBroadcast to dispatch it.',
|
||||
inputSchema: jsonSchema<CreateBroadcastInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
segment_id: { type: 'string', description: 'Segment ID to send the broadcast to.' },
|
||||
from: { type: 'string', description: 'Sender address. Supports "Name <email>" format.' },
|
||||
subject: { type: 'string', description: 'Broadcast email subject line.' },
|
||||
reply_to: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
||||
description: 'Reply-to address(es).',
|
||||
},
|
||||
html: { type: 'string', description: 'HTML content. Supports Contact Property templating.' },
|
||||
text: { type: 'string', description: 'Plain text content.' },
|
||||
name: { type: 'string', description: 'Friendly name for internal reference.' },
|
||||
topic_id: { type: 'string', description: 'Topic ID for subscription management.' },
|
||||
},
|
||||
required: ['segment_id', 'from', 'subject'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateBroadcastInput) {
|
||||
return apiRequest<{ id: string }>('POST', '/broadcasts', input);
|
||||
},
|
||||
});
|
||||
|
||||
export const listBroadcasts = tool({
|
||||
description: 'List all broadcasts with their status, audience, and scheduling details.',
|
||||
inputSchema: jsonSchema<PaginationInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results to return (1-100, default 20).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: PaginationInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/broadcasts${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface SendBroadcastInput {
|
||||
broadcast_id: string;
|
||||
scheduled_at?: string;
|
||||
}
|
||||
|
||||
export const sendBroadcast = tool({
|
||||
description:
|
||||
'Send or schedule a previously created broadcast. Optionally provide a scheduled_at time.',
|
||||
inputSchema: jsonSchema<SendBroadcastInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
broadcast_id: { type: 'string', description: 'The broadcast ID to send.' },
|
||||
scheduled_at: {
|
||||
type: 'string',
|
||||
description: 'ISO 8601 datetime or natural language to schedule.',
|
||||
},
|
||||
},
|
||||
required: ['broadcast_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: SendBroadcastInput) {
|
||||
const { broadcast_id, ...body } = input;
|
||||
const hasBody = Object.keys(body).length > 0;
|
||||
return apiRequest<{ id: string }>(
|
||||
'POST',
|
||||
`/broadcasts/${broadcast_id}/send`,
|
||||
hasBody ? body : undefined
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface BroadcastIdInput {
|
||||
broadcast_id: string;
|
||||
}
|
||||
|
||||
export const deleteBroadcast = tool({
|
||||
description:
|
||||
'Delete a draft broadcast that has not been sent. Scheduled broadcasts are automatically cancelled.',
|
||||
inputSchema: jsonSchema<BroadcastIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
broadcast_id: { type: 'string', description: 'The broadcast ID to delete.' },
|
||||
},
|
||||
required: ['broadcast_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: BroadcastIdInput) {
|
||||
return apiRequest<{ object: string; id: string; deleted: boolean }>(
|
||||
'DELETE',
|
||||
`/broadcasts/${input.broadcast_id}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Audiences ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface NameInput {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const createAudience = tool({
|
||||
description: 'Create a new audience for organizing contacts into groups.',
|
||||
inputSchema: jsonSchema<NameInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name for the audience.' },
|
||||
},
|
||||
required: ['name'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: NameInput) {
|
||||
return apiRequest<{ object: string; id: string; name: string }>('POST', '/audiences', input);
|
||||
},
|
||||
});
|
||||
|
||||
export const listAudiences = tool({
|
||||
description: 'List all audiences in your Resend account with their names and creation dates.',
|
||||
inputSchema: jsonSchema<PaginationInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'number', description: 'Max results to return (1-100, default 20).' },
|
||||
after: { type: 'string', description: 'Cursor ID for forward pagination.' },
|
||||
before: { type: 'string', description: 'Cursor ID for backward pagination.' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: PaginationInput) {
|
||||
const qs = buildQueryString({ ...input });
|
||||
return apiRequest<{ object: string; has_more: boolean; data: unknown[] }>(
|
||||
'GET',
|
||||
`/audiences${qs}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export interface AudienceIdInput {
|
||||
audience_id: string;
|
||||
}
|
||||
|
||||
export const getAudience = tool({
|
||||
description: 'Retrieve details of a specific audience by its ID.',
|
||||
inputSchema: jsonSchema<AudienceIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
audience_id: { type: 'string', description: 'The audience ID to retrieve.' },
|
||||
},
|
||||
required: ['audience_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: AudienceIdInput) {
|
||||
return apiRequest<Record<string, unknown>>('GET', `/audiences/${input.audience_id}`);
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteAudience = tool({
|
||||
description: 'Delete an audience from your Resend account permanently.',
|
||||
inputSchema: jsonSchema<AudienceIdInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
audience_id: { type: 'string', description: 'The audience ID to delete.' },
|
||||
},
|
||||
required: ['audience_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: AudienceIdInput) {
|
||||
return apiRequest<{ object: string; id: string; deleted: boolean }>(
|
||||
'DELETE',
|
||||
`/audiences/${input.audience_id}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Default Export ─────────────────────────────────────────────────────────
|
||||
|
||||
export default {
|
||||
// Emails
|
||||
sendEmail,
|
||||
sendBatchEmails,
|
||||
getEmail,
|
||||
updateEmail,
|
||||
cancelEmail,
|
||||
listEmails,
|
||||
// Domains
|
||||
createDomain,
|
||||
getDomain,
|
||||
updateDomain,
|
||||
deleteDomain,
|
||||
listDomains,
|
||||
verifyDomain,
|
||||
// API Keys
|
||||
createApiKey,
|
||||
listApiKeys,
|
||||
deleteApiKey,
|
||||
// Contacts
|
||||
createContact,
|
||||
getContact,
|
||||
updateContact,
|
||||
deleteContact,
|
||||
listContacts,
|
||||
// Broadcasts
|
||||
createBroadcast,
|
||||
listBroadcasts,
|
||||
sendBroadcast,
|
||||
deleteBroadcast,
|
||||
// Audiences
|
||||
createAudience,
|
||||
listAudiences,
|
||||
getAudience,
|
||||
deleteAudience,
|
||||
};
|
||||
11
packages/tools/official/resend/tsconfig.json
Normal file
11
packages/tools/official/resend/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
10
packages/tools/official/resend/tsup.config.ts
Normal file
10
packages/tools/official/resend/tsup.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
});
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 3: Collections (0:24 - 0:32)
|
||||
|
|
@ -92,7 +86,7 @@ const CollectionCard = ({
|
|||
>
|
||||
{Array.from({ length: Math.min(toolCount, 5) }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
key={`dot-${String(i)}`}
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
|
|
@ -234,8 +228,7 @@ export const CollectionsFeatureScene = () => {
|
|||
>
|
||||
Curate tool sets
|
||||
<br />
|
||||
for specific{' '}
|
||||
<span style={{ color: colors.copper.default }}>use cases</span>
|
||||
for specific <span style={{ color: colors.copper.default }}>use cases</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 4: Custom Agents (0:32 - 0:40)
|
||||
|
|
@ -108,8 +102,7 @@ const AgentCard = ({
|
|||
<div
|
||||
style={{
|
||||
padding: '4px 10px',
|
||||
backgroundColor:
|
||||
status === 'public' ? colors.status.successMuted : colors.bg.surface2,
|
||||
backgroundColor: status === 'public' ? colors.status.successMuted : colors.bg.surface2,
|
||||
border: `1px solid ${status === 'public' ? colors.status.success : colors.border.default}`,
|
||||
fontSize: typography.fontSize.xs,
|
||||
color: status === 'public' ? colors.status.success : colors.text.muted,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 9: Developer SDK (1:13 - 1:22)
|
||||
|
|
@ -201,11 +195,7 @@ export const DeveloperSDKScene = () => {
|
|||
/>
|
||||
</div>
|
||||
{codeLines.map((line, i) => (
|
||||
<CodeLine
|
||||
key={i}
|
||||
{...line}
|
||||
delay={fps * 0.5 + i * 5}
|
||||
/>
|
||||
<CodeLine key={line.text} {...line} delay={fps * 0.5 + i * 5} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 8: Living Skills (1:05 - 1:13)
|
||||
|
|
@ -189,8 +183,7 @@ export const LivingSkillsScene = () => {
|
|||
opacity: headerProgress,
|
||||
}}
|
||||
>
|
||||
Documentation that{' '}
|
||||
<span style={{ color: colors.copper.default }}>evolves</span>
|
||||
Documentation that <span style={{ color: colors.copper.default }}>evolves</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 5: MCP Protocol (0:40 - 0:49)
|
||||
|
|
@ -253,11 +247,7 @@ export const MCPProtocolScene = () => {
|
|||
}}
|
||||
>
|
||||
{clients.map((client, i) => (
|
||||
<ClientLogo
|
||||
key={client.name}
|
||||
{...client}
|
||||
delay={fps * 1 + i * 8}
|
||||
/>
|
||||
<ClientLogo key={client.name} {...client} delay={fps * 1 + i * 8} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
|
@ -281,7 +271,7 @@ export const MCPProtocolScene = () => {
|
|||
|
||||
return (
|
||||
<path
|
||||
key={i}
|
||||
key={`path-${String(i)}`}
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke={colors.copper.default}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 2: Omega Agent (0:15 - 0:24)
|
||||
|
|
@ -13,11 +7,11 @@ import { colors, typography, springConfigs } from '../../design-tokens';
|
|||
*/
|
||||
|
||||
const Message = ({
|
||||
role,
|
||||
sender,
|
||||
content,
|
||||
delay,
|
||||
}: {
|
||||
role: 'user' | 'assistant';
|
||||
sender: 'user' | 'assistant';
|
||||
content: string;
|
||||
delay: number;
|
||||
}) => {
|
||||
|
|
@ -30,7 +24,7 @@ const Message = ({
|
|||
config: springConfigs.snappy,
|
||||
});
|
||||
|
||||
const isUser = role === 'user';
|
||||
const isUser = sender === 'user';
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -195,7 +189,7 @@ export const OmegaAgentScene = () => {
|
|||
}}
|
||||
>
|
||||
<Message
|
||||
role="user"
|
||||
sender="user"
|
||||
content="Scrape competitor pricing pages and create a comparison chart"
|
||||
delay={fps * 0.5}
|
||||
/>
|
||||
|
|
@ -230,7 +224,7 @@ export const OmegaAgentScene = () => {
|
|||
)}
|
||||
|
||||
<Message
|
||||
role="assistant"
|
||||
sender="assistant"
|
||||
content="I've scraped 5 competitor pages and generated a comparison chart. Found 23% average price difference..."
|
||||
delay={fps * 3.5}
|
||||
/>
|
||||
|
|
@ -256,8 +250,7 @@ export const OmegaAgentScene = () => {
|
|||
color: colors.text.secondary,
|
||||
}}
|
||||
>
|
||||
No configuration needed.{' '}
|
||||
<span style={{ color: colors.copper.default }}>Just ask.</span>
|
||||
No configuration needed. <span style={{ color: colors.copper.default }}>Just ask.</span>
|
||||
</span>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 6: Secure Execution (0:49 - 0:57)
|
||||
|
|
@ -222,12 +216,7 @@ export const SecureExecutionScene = () => {
|
|||
}}
|
||||
>
|
||||
{layers.map((layer, i) => (
|
||||
<SecurityLayer
|
||||
key={layer.label}
|
||||
{...layer}
|
||||
index={i}
|
||||
delay={fps * 1 + i * 10}
|
||||
/>
|
||||
<SecurityLayer key={layer.label} {...layer} index={i} delay={fps * 1 + i * 10} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 7: Test Scenarios (0:57 - 1:05)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
spring,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from 'remotion';
|
||||
import { colors, typography, springConfigs } from '../../design-tokens';
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
|
||||
import { colors, springConfigs, typography } from '../../design-tokens';
|
||||
|
||||
/**
|
||||
* Feature 1: Tool Registry (0:06 - 0:15)
|
||||
|
|
@ -250,11 +244,7 @@ export const ToolRegistryScene = () => {
|
|||
{/* Right - Tool list */}
|
||||
<div style={{ flex: 1, maxWidth: 500 }}>
|
||||
{tools.map((tool, i) => (
|
||||
<ToolCard
|
||||
key={tool.name}
|
||||
{...tool}
|
||||
delay={fps * 1 + i * 10}
|
||||
/>
|
||||
<ToolCard key={tool.name} {...tool} delay={fps * 1 + i * 10} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue