needware-cli 1.5.21 → 1.5.23
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/commands/system-prompt.d.ts.map +1 -1
- package/dist/commands/system-prompt.js +0 -158
- package/dist/commands/system-prompt.js.map +1 -1
- package/dist/tools/add-secret-tool.d.ts +15 -0
- package/dist/tools/add-secret-tool.d.ts.map +1 -0
- package/dist/tools/add-secret-tool.js +53 -0
- package/dist/tools/add-secret-tool.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +2 -0
- package/dist/tools/tool-registry.js.map +1 -1
- package/package.json +1 -1
- package/skills/resend-integration/SKILL.md +657 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/commands/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,UAAU,cAAc;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/commands/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,UAAU,cAAc;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,cAAc,GACxB,MAAM,CA8YR"}
|
|
@@ -1,163 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* System prompt for Agent command
|
|
3
3
|
*/
|
|
4
|
-
function getDatabaseInstructions(supabase) {
|
|
5
|
-
const hasSupabaseProject = supabase?.hasSelectedProject && supabase?.projectId && supabase?.publishableKey;
|
|
6
|
-
if (!hasSupabaseProject && !supabase) {
|
|
7
|
-
return `
|
|
8
|
-
## Database Instructions
|
|
9
|
-
|
|
10
|
-
CRITICAL: Use Supabase for databases by default, unless specified otherwise. Supabase can provide login, register, database query, authorization, storage, Edge Functions etc.
|
|
11
|
-
|
|
12
|
-
Supabase project setup handled separately by user!
|
|
13
|
-
`;
|
|
14
|
-
}
|
|
15
|
-
if (supabase && !supabase.hasSelectedProject) {
|
|
16
|
-
return `
|
|
17
|
-
## Database Instructions
|
|
18
|
-
|
|
19
|
-
CRITICAL: Use Supabase for databases by default, unless specified otherwise. Supabase can provide login, register, database query, authorization, storage, Edge Functions etc.
|
|
20
|
-
|
|
21
|
-
Supabase project setup handled separately by user! No project selected. Remind user to select project in chat box.
|
|
22
|
-
`;
|
|
23
|
-
}
|
|
24
|
-
const envConfig = hasSupabaseProject && supabase.url
|
|
25
|
-
? ` with:
|
|
26
|
-
VITE_SUPABASE_PROJECT_ID="${supabase.projectId}"
|
|
27
|
-
VITE_SUPABASE_PUBLISHABLE_KEY="${supabase.publishableKey}"
|
|
28
|
-
VITE_SUPABASE_URL="${supabase.url}"`
|
|
29
|
-
: '.';
|
|
30
|
-
return `
|
|
31
|
-
## Database Instructions
|
|
32
|
-
|
|
33
|
-
CRITICAL: Use Supabase for databases by default, unless specified otherwise. Supabase can provide login, register, database query, authorization, storage, Edge Functions etc.
|
|
34
|
-
|
|
35
|
-
Create .env file if it doesn't exist${envConfig}
|
|
36
|
-
|
|
37
|
-
### IMPORTANT: If user says "Enable Cloud" or asks to initialize Supabase:
|
|
38
|
-
|
|
39
|
-
You MUST immediately perform these steps WITHOUT asking for clarification:
|
|
40
|
-
|
|
41
|
-
1. **Install Supabase client** (if not already installed):
|
|
42
|
-
Run: \`pnpm add @supabase/supabase-js\`
|
|
43
|
-
|
|
44
|
-
2. **Create/Update .env file** with project credentials:
|
|
45
|
-
\`\`\`
|
|
46
|
-
VITE_SUPABASE_PROJECT_ID=${supabase.projectId}
|
|
47
|
-
VITE_SUPABASE_PUBLISHABLE_KEY=${supabase.publishableKey}
|
|
48
|
-
VITE_SUPABASE_URL=${supabase.url}
|
|
49
|
-
\`\`\`
|
|
50
|
-
|
|
51
|
-
3. **Create Supabase client file** at \`src/lib/supabase.ts\`:
|
|
52
|
-
\`\`\`typescript
|
|
53
|
-
import { createClient } from '@supabase/supabase-js';
|
|
54
|
-
|
|
55
|
-
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
56
|
-
const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY;
|
|
57
|
-
|
|
58
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
59
|
-
throw new Error('Missing Supabase environment variables');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const supabase = createClient(supabaseUrl, supabaseKey);
|
|
63
|
-
\`\`\`
|
|
64
|
-
|
|
65
|
-
4. **Create migrations directory**: \`supabase/migrations/\` (if not exists)
|
|
66
|
-
|
|
67
|
-
5. **Inform user** that Supabase is now initialized and ready to use
|
|
68
|
-
|
|
69
|
-
### DATA PRESERVATION REQUIREMENTS:
|
|
70
|
-
- DATA INTEGRITY IS HIGHEST PRIORITY - users must NEVER lose data
|
|
71
|
-
- FORBIDDEN: Destructive operations (DROP, DELETE) that could cause data loss
|
|
72
|
-
- FORBIDDEN: Transaction control (BEGIN, COMMIT, ROLLBACK, END)
|
|
73
|
-
Note: DO $$ BEGIN ... END $$ blocks (PL/pgSQL) are allowed
|
|
74
|
-
|
|
75
|
-
### SQL Migrations - CRITICAL:
|
|
76
|
-
For EVERY database change, create a migration file in /supabase/migrations/ directory:
|
|
77
|
-
|
|
78
|
-
1. Create a new .sql file with descriptive name (e.g., create_users_table.sql)
|
|
79
|
-
2. Write complete SQL migration content with comments
|
|
80
|
-
3. Include safety checks (IF EXISTS/IF NOT EXISTS)
|
|
81
|
-
4. Always enable RLS and add appropriate policies
|
|
82
|
-
|
|
83
|
-
### Migration Rules:
|
|
84
|
-
- NEVER use diffs, ALWAYS provide COMPLETE file content
|
|
85
|
-
- Create new migration file for each change in /home/project/supabase/migrations
|
|
86
|
-
- NEVER update existing migration files
|
|
87
|
-
- Descriptive names without number prefix (e.g., create_users.sql)
|
|
88
|
-
- ALWAYS enable RLS: \`alter table users enable row level security;\`
|
|
89
|
-
- Add appropriate RLS policies for CRUD operations
|
|
90
|
-
- Use default values: DEFAULT false/true, DEFAULT 0, DEFAULT '', DEFAULT now()
|
|
91
|
-
- Start with markdown summary in multi-line comment explaining changes
|
|
92
|
-
- Use IF EXISTS/IF NOT EXISTS for safe operations
|
|
93
|
-
|
|
94
|
-
### Migration Example:
|
|
95
|
-
\`\`\`sql
|
|
96
|
-
/*
|
|
97
|
-
# Create users table
|
|
98
|
-
1. New Tables: users (id uuid, email text, created_at timestamp)
|
|
99
|
-
2. Security: Enable RLS, add read policy for authenticated users
|
|
100
|
-
*/
|
|
101
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
102
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
103
|
-
email text UNIQUE NOT NULL,
|
|
104
|
-
created_at timestamptz DEFAULT now()
|
|
105
|
-
);
|
|
106
|
-
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
107
|
-
CREATE POLICY "Users read own data" ON users FOR SELECT TO authenticated USING (auth.uid() = id);
|
|
108
|
-
\`\`\`
|
|
109
|
-
|
|
110
|
-
### Client Setup:
|
|
111
|
-
- Use @supabase/supabase-js
|
|
112
|
-
- Create singleton client instance in \`src/lib/supabase.ts\`
|
|
113
|
-
- Use environment variables: VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY
|
|
114
|
-
- Example client setup:
|
|
115
|
-
\`\`\`typescript
|
|
116
|
-
import { createClient } from '@supabase/supabase-js';
|
|
117
|
-
|
|
118
|
-
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
119
|
-
const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY;
|
|
120
|
-
|
|
121
|
-
export const supabase = createClient(supabaseUrl, supabaseKey);
|
|
122
|
-
\`\`\`
|
|
123
|
-
|
|
124
|
-
### Authentication:
|
|
125
|
-
- ALWAYS use email/password signup
|
|
126
|
-
- FORBIDDEN: magic links, social providers, SSO (unless explicitly stated)
|
|
127
|
-
- FORBIDDEN: custom auth systems, ALWAYS use Supabase's built-in auth
|
|
128
|
-
- Email confirmation ALWAYS disabled unless stated
|
|
129
|
-
|
|
130
|
-
### User Profiles:
|
|
131
|
-
- ALWAYS create public.profiles table linked to auth.users
|
|
132
|
-
- Use: \`user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE UNIQUE NOT NULL\`
|
|
133
|
-
- Include common fields: username, avatar_url, created_at, updated_at
|
|
134
|
-
- Create trigger to auto-insert profile on user signup (INSERT INTO profiles on auth.users INSERT)
|
|
135
|
-
|
|
136
|
-
### Storage (If the user needs):
|
|
137
|
-
- Use Supabase Storage for file uploads (images, documents, etc.)
|
|
138
|
-
- Create buckets in migrations: \`INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true)\`
|
|
139
|
-
- ALWAYS add RLS policies for storage.objects table with bucket_id filter
|
|
140
|
-
- Common operations: .upload(), .download(), .getPublicUrl(), .remove(), .list()
|
|
141
|
-
- Organize files with user ID prefix: '{userId}/filename' for private files
|
|
142
|
-
|
|
143
|
-
### Edge Functions (If the user needs):
|
|
144
|
-
- Use Supabase Edge Functions for server-side logic, background tasks, webhooks, and API integrations
|
|
145
|
-
- Create functions in /supabase/functions/<function-name>/index.ts
|
|
146
|
-
- Use Deno runtime with TypeScript support
|
|
147
|
-
- Import from: 'https://esm.sh/@supabase/supabase-js@2' for Supabase client
|
|
148
|
-
- Access environment variables via Deno.env.get('VARIABLE_NAME')
|
|
149
|
-
- Invoke from client: supabase.functions.invoke('function-name', { body: { ... } })
|
|
150
|
-
- Common use cases: webhooks, scheduled jobs, third-party API calls, complex business logic
|
|
151
|
-
- Return Response object: new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
|
|
152
|
-
|
|
153
|
-
### Security:
|
|
154
|
-
- ALWAYS enable RLS for every new table
|
|
155
|
-
- Create policies based on user authentication
|
|
156
|
-
- One migration per logical change
|
|
157
|
-
- Use descriptive policy names
|
|
158
|
-
- Add indexes for frequently queried columns
|
|
159
|
-
`;
|
|
160
|
-
}
|
|
161
4
|
export function getSystemPrompt(workingDir, supabase) {
|
|
162
5
|
return `
|
|
163
6
|
## Working Directory Context
|
|
@@ -194,7 +37,6 @@ CRITICAL SECURITY RULES:
|
|
|
194
37
|
- FORBIDDEN: Spawning daemon processes or background jobs
|
|
195
38
|
- You may run standard development commands (pnpm, node, etc.) but NEVER manage system processes
|
|
196
39
|
|
|
197
|
-
${getDatabaseInstructions(supabase)}
|
|
198
40
|
|
|
199
41
|
## Project Structure
|
|
200
42
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/commands/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/commands/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,QAAyB;IAEzB,OAAO;;;+CAGsC,UAAU;;wEAEe,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAuYvE,CAAC,IAAI,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Secret Tool
|
|
3
|
+
* Tool for adding secrets to Supabase project
|
|
4
|
+
*/
|
|
5
|
+
import { BaseTool } from './base-tool.js';
|
|
6
|
+
import { ToolDefinition, ToolResult } from '../types/tool.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
export declare class AddSecretTool extends BaseTool {
|
|
9
|
+
definition: ToolDefinition;
|
|
10
|
+
getZodSchema(): {
|
|
11
|
+
placeholder: z.ZodString;
|
|
12
|
+
};
|
|
13
|
+
execute(input: Record<string, any>): Promise<ToolResult>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=add-secret-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret-tool.d.ts","sourceRoot":"","sources":["../../src/tools/add-secret-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,qBAAa,aAAc,SAAQ,QAAQ;IACzC,UAAU,EAAE,cAAc,CAaxB;IAEF,YAAY;;;IAMN,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;CA4B/D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Secret Tool
|
|
3
|
+
* Tool for adding secrets to Supabase project
|
|
4
|
+
*/
|
|
5
|
+
import { BaseTool } from './base-tool.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
export class AddSecretTool extends BaseTool {
|
|
9
|
+
definition = {
|
|
10
|
+
name: 'add-secret',
|
|
11
|
+
description: 'Add a secret (environment variable) to the Supabase project for use in Edge Functions',
|
|
12
|
+
input_schema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
placeholder: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'The placeholder of the secret (environment variable), e.g., RESEND_API_KEY',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ['placeholder'],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
getZodSchema() {
|
|
24
|
+
return {
|
|
25
|
+
placeholder: z.string().min(1).describe('The placeholder of the secret (environment variable)'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async execute(input) {
|
|
29
|
+
try {
|
|
30
|
+
// 验证输入参数
|
|
31
|
+
this.validateInput(input, ['placeholder']);
|
|
32
|
+
const { placeholder } = input;
|
|
33
|
+
console.log(chalk.blue(`\n🔐 准备添加密钥: ${placeholder}...\n`));
|
|
34
|
+
console.log(chalk.green(`\n✓ 密钥 "${placeholder}" 准备就绪\n`));
|
|
35
|
+
// 构建返回的 JSON 数据
|
|
36
|
+
const resultData = {
|
|
37
|
+
placeholder: placeholder,
|
|
38
|
+
secret_added: false,
|
|
39
|
+
secret_names: [placeholder],
|
|
40
|
+
requires_approval: true,
|
|
41
|
+
message: '密钥等待用户提供值',
|
|
42
|
+
instructions: `请在 Supabase 项目设置中添加此密钥,或使用 Supabase CLI: supabase secrets set ${placeholder}=your_value_here`
|
|
43
|
+
};
|
|
44
|
+
return this.successWithContent(JSON.stringify(resultData, null, 2));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : '生成密钥请求失败';
|
|
48
|
+
console.log(chalk.red(`\n✖ ${message}\n`));
|
|
49
|
+
return this.errorWithContent(message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=add-secret-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-secret-tool.js","sourceRoot":"","sources":["../../src/tools/add-secret-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,OAAO,aAAc,SAAQ,QAAQ;IACzC,UAAU,GAAmB;QAC3B,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,uFAAuF;QACpG,YAAY,EAAE;YACZ,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,WAAW,EAAE;oBACX,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,4EAA4E;iBAC1F;aACF;YACD,QAAQ,EAAE,CAAC,aAAa,CAAC;SAC1B;KACF,CAAC;IAEF,YAAY;QACV,OAAO;YACL,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,sDAAsD,CAAC;SAChG,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAA0B;QACtC,IAAI,CAAC;YACH,SAAS;YACT,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;YAE3C,MAAM,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;YAE9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,WAAW,OAAO,CAAC,CAAC,CAAC;YAE5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,WAAW,UAAU,CAAC,CAAC,CAAC;YAE3D,gBAAgB;YAChB,MAAM,UAAU,GAAG;gBACjB,WAAW,EAAE,WAAW;gBACxB,YAAY,EAAE,KAAK;gBACnB,YAAY,EAAE,CAAC,WAAW,CAAC;gBAC3B,iBAAiB,EAAE,IAAI;gBACvB,OAAO,EAAE,WAAW;gBACpB,YAAY,EAAE,iEAAiE,WAAW,kBAAkB;aAC7G,CAAC;YAEF,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;YACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,OAAO,IAAI,CAAC,CAAC,CAAC;YAC3C,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;CACF"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export { SupabaseEnableTool } from './supabase-enable-tool.js';
|
|
|
6
6
|
export { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
|
|
7
7
|
export { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
|
|
8
8
|
export { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
|
|
9
|
+
export { AddSecretTool } from './add-secret-tool.js';
|
|
9
10
|
export { ToolRegistry, ToolConfig } from './tool-registry.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -6,5 +6,6 @@ export { SupabaseEnableTool } from './supabase-enable-tool.js';
|
|
|
6
6
|
export { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
|
|
7
7
|
export { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
|
|
8
8
|
export { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
|
|
9
|
+
export { AddSecretTool } from './add-secret-tool.js';
|
|
9
10
|
export { ToolRegistry } from './tool-registry.js';
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAc,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAc,MAAM,oBAAoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOxD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAgC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAa5C;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIjC;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAI1C;;OAEG;IACH,MAAM,CAAC,iBAAiB,IAAI,cAAc,EAAE;IAI5C;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE;IAIvB;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC;;;;OAIG;WACU,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAQ5E;;OAEG;IACH,MAAM,CAAC,gBAAgB,IAAI,GAAG,EAAE;CAOjC"}
|
|
@@ -6,6 +6,7 @@ import { SupabaseEnableTool } from './supabase-enable-tool.js';
|
|
|
6
6
|
import { SupabaseDeployFunctionsTool } from './supabase-deploy-functions-tool.js';
|
|
7
7
|
import { SupabaseExecuteSqlTool } from './supabase-execute-sql-tool.js';
|
|
8
8
|
import { AiGatewayEnableTool } from './ai-gateway-enable-tool.js';
|
|
9
|
+
import { AddSecretTool } from './add-secret-tool.js';
|
|
9
10
|
export class ToolRegistry {
|
|
10
11
|
static tools = new Map();
|
|
11
12
|
/**
|
|
@@ -21,6 +22,7 @@ export class ToolRegistry {
|
|
|
21
22
|
this.register(new SupabaseDeployFunctionsTool(config?.gateway));
|
|
22
23
|
this.register(new SupabaseExecuteSqlTool(config?.gateway));
|
|
23
24
|
this.register(new AiGatewayEnableTool(config?.gateway));
|
|
25
|
+
this.register(new AddSecretTool(config?.gateway));
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* 注册工具
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAQrD,MAAM,OAAO,YAAY;IACf,MAAM,CAAC,KAAK,GAAsB,IAAI,GAAG,EAAE,CAAC;IAEpD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,MAAmB;QACnC,WAAW;QACX,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAEnB,cAAc;QACd,eAAe;QACf,IAAI,CAAC,QAAQ,CAAC,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,IAAI,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,IAAI,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAU;QACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB;QACtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxE,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,MAAM;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,KAA0B;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB;QACrB,OAAO,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC,CAAC,CAAC;IACN,CAAC;;AAGH,qCAAqC;AACrC,YAAY,CAAC,UAAU,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: resend-integration
|
|
3
|
+
description: Complete Resend email integration for sending transactional emails, notifications, and alerts. REQUIRES Supabase integration as prerequisite (Resend functions run on Supabase Edge Functions). Use this skill when users mention sending emails, email notifications, transactional emails, or email alerts. 使用此技能处理邮件发送、邮件通知、事务性邮件等功能。必须先集成 Supabase 才能使用此技能。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Resend Integration
|
|
7
|
+
|
|
8
|
+
## Before Starting - Integration Check
|
|
9
|
+
|
|
10
|
+
**CRITICAL**: Resend integration requires Supabase Edge Functions. Before proceeding, always check Supabase integration status:
|
|
11
|
+
|
|
12
|
+
**Step 1: Check Supabase Integration**
|
|
13
|
+
|
|
14
|
+
Check if Supabase is already integrated:
|
|
15
|
+
- Look for `src/lib/supabase.ts` file
|
|
16
|
+
- Check `.env` file for Supabase environment variables:
|
|
17
|
+
- `VITE_SUPABASE_PROJECT_ID`
|
|
18
|
+
- `VITE_SUPABASE_PUBLISHABLE_KEY`
|
|
19
|
+
- `VITE_SUPABASE_URL`
|
|
20
|
+
- Check for `supabase/functions/` directory
|
|
21
|
+
|
|
22
|
+
**Step 2: Handle Based on Status**
|
|
23
|
+
|
|
24
|
+
**If Supabase is already integrated** (supabase.ts exists with valid configuration):
|
|
25
|
+
- ✓ Proceed with Resend integration
|
|
26
|
+
- Inform user: "✓ Supabase is integrated. Proceeding with Resend setup."
|
|
27
|
+
|
|
28
|
+
**If Supabase is NOT integrated** (no supabase.ts or missing environment variables):
|
|
29
|
+
- ❌ Stop immediately
|
|
30
|
+
- Inform user: "⚠️ Supabase integration is required before setting up Resend. Resend email functions run on Supabase Edge Functions."
|
|
31
|
+
- Suggest: "Please enable Supabase first by saying 'Enable Cloud' or use the supabase-integration skill."
|
|
32
|
+
- Do NOT proceed with Resend setup until Supabase is properly configured
|
|
33
|
+
|
|
34
|
+
**Step 3: Verify Edge Functions Support**
|
|
35
|
+
|
|
36
|
+
Once Supabase is confirmed:
|
|
37
|
+
- Check if `supabase/functions/` directory exists
|
|
38
|
+
- If not, create the directory structure
|
|
39
|
+
- Proceed with Resend Edge Function creation
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### When to Use This Skill
|
|
46
|
+
|
|
47
|
+
Use this skill when working with:
|
|
48
|
+
- Transactional emails (welcome emails, password resets)
|
|
49
|
+
- Email notifications (alerts, reminders)
|
|
50
|
+
- Batch email sending
|
|
51
|
+
- HTML email templates
|
|
52
|
+
- Email verification and confirmation
|
|
53
|
+
- User communication
|
|
54
|
+
|
|
55
|
+
**关键原则**:
|
|
56
|
+
- ✅ 使用 Resend 作为邮件发送服务
|
|
57
|
+
- ✅ 所有邮件功能通过 Supabase Edge Functions 实现
|
|
58
|
+
- ⚠️ **必须先集成 Supabase** - Resend 依赖 Supabase Edge Functions 运行
|
|
59
|
+
|
|
60
|
+
## Initial Setup
|
|
61
|
+
|
|
62
|
+
### Prerequisites
|
|
63
|
+
|
|
64
|
+
**Required**:
|
|
65
|
+
1. ✅ **Supabase must be integrated** - Resend functions run on Supabase Edge Functions
|
|
66
|
+
- Verify `src/lib/supabase.ts` exists
|
|
67
|
+
- Verify Supabase environment variables are configured
|
|
68
|
+
- Verify `supabase/functions/` directory exists
|
|
69
|
+
|
|
70
|
+
2. 🔑 **Resend API key** - If no API key is available, remind user to:
|
|
71
|
+
- Visit [Resend Dashboard](https://resend.com/api-keys)
|
|
72
|
+
- Create a new API key
|
|
73
|
+
- Add key to Supabase Edge Functions environment variables (`supabase/.env.local`)
|
|
74
|
+
|
|
75
|
+
### Installation in Edge Functions
|
|
76
|
+
|
|
77
|
+
**Step 1: Import Resend in Edge Function**
|
|
78
|
+
|
|
79
|
+
Create a new Edge Function file in `/supabase/functions/<function-name>/index.ts`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
|
83
|
+
import { Resend } from "https://esm.sh/resend@2.0.0";
|
|
84
|
+
|
|
85
|
+
const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Step 2: Configure Environment Variables**
|
|
89
|
+
|
|
90
|
+
Add to `/supabase/.env.local`:
|
|
91
|
+
|
|
92
|
+
```env
|
|
93
|
+
RESEND_API_KEY=re_your_api_key_here
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Step 3: CORS Headers Setup**
|
|
97
|
+
|
|
98
|
+
Always include CORS headers for client-side invocation:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const corsHeaders = {
|
|
102
|
+
"Access-Control-Allow-Origin": "*",
|
|
103
|
+
"Access-Control-Allow-Headers":
|
|
104
|
+
"authorization, x-client-info, apikey, content-type",
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Email Sending Patterns
|
|
109
|
+
|
|
110
|
+
### Basic Email Template
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
|
114
|
+
import { Resend } from "https://esm.sh/resend@2.0.0";
|
|
115
|
+
|
|
116
|
+
const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
|
|
117
|
+
|
|
118
|
+
const corsHeaders = {
|
|
119
|
+
"Access-Control-Allow-Origin": "*",
|
|
120
|
+
"Access-Control-Allow-Headers":
|
|
121
|
+
"authorization, x-client-info, apikey, content-type",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
interface EmailRequest {
|
|
125
|
+
to: string;
|
|
126
|
+
subject: string;
|
|
127
|
+
message: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const handler = async (req: Request): Promise<Response> => {
|
|
131
|
+
// Handle CORS preflight requests
|
|
132
|
+
if (req.method === "OPTIONS") {
|
|
133
|
+
return new Response(null, { headers: corsHeaders });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const { to, subject, message }: EmailRequest = await req.json();
|
|
138
|
+
|
|
139
|
+
const { data, error } = await resend.emails.send({
|
|
140
|
+
from: "Your App <onboarding@resend.dev>",
|
|
141
|
+
to: [to],
|
|
142
|
+
subject: subject,
|
|
143
|
+
html: `<p>${message}</p>`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (error) {
|
|
147
|
+
throw new Error(error.message);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return new Response(
|
|
151
|
+
JSON.stringify({ success: true, data }),
|
|
152
|
+
{
|
|
153
|
+
status: 200,
|
|
154
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
} catch (error: any) {
|
|
158
|
+
console.error("Error sending email:", error);
|
|
159
|
+
return new Response(
|
|
160
|
+
JSON.stringify({ error: error.message }),
|
|
161
|
+
{
|
|
162
|
+
status: 500,
|
|
163
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
serve(handler);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Batch Email Sending
|
|
173
|
+
|
|
174
|
+
Send emails to multiple recipients:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
interface BatchEmailRequest {
|
|
178
|
+
recipients: {
|
|
179
|
+
name: string;
|
|
180
|
+
email: string;
|
|
181
|
+
}[];
|
|
182
|
+
subject: string;
|
|
183
|
+
message: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const handler = async (req: Request): Promise<Response> => {
|
|
187
|
+
if (req.method === "OPTIONS") {
|
|
188
|
+
return new Response(null, { headers: corsHeaders });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const { recipients, subject, message }: BatchEmailRequest = await req.json();
|
|
193
|
+
|
|
194
|
+
if (!recipients || recipients.length === 0) {
|
|
195
|
+
return new Response(
|
|
196
|
+
JSON.stringify({ error: "No recipients provided" }),
|
|
197
|
+
{
|
|
198
|
+
status: 400,
|
|
199
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Send emails in parallel
|
|
205
|
+
const results = await Promise.all(
|
|
206
|
+
recipients.map(async (recipient) => {
|
|
207
|
+
const emailResponse = await resend.emails.send({
|
|
208
|
+
from: "Your App <onboarding@resend.dev>",
|
|
209
|
+
to: [recipient.email],
|
|
210
|
+
subject: subject,
|
|
211
|
+
html: `<p>Hi ${recipient.name},</p><p>${message}</p>`,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
recipient: recipient.email,
|
|
215
|
+
response: emailResponse
|
|
216
|
+
};
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return new Response(
|
|
221
|
+
JSON.stringify({ success: true, results }),
|
|
222
|
+
{
|
|
223
|
+
status: 200,
|
|
224
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
} catch (error: any) {
|
|
228
|
+
return new Response(
|
|
229
|
+
JSON.stringify({ error: error.message }),
|
|
230
|
+
{
|
|
231
|
+
status: 500,
|
|
232
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Alert Email Example
|
|
240
|
+
|
|
241
|
+
Complete example for sending alert emails:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
|
245
|
+
import { Resend } from "https://esm.sh/resend@2.0.0";
|
|
246
|
+
|
|
247
|
+
const resend = new Resend(Deno.env.get("RESEND_API_KEY"));
|
|
248
|
+
|
|
249
|
+
const corsHeaders = {
|
|
250
|
+
"Access-Control-Allow-Origin": "*",
|
|
251
|
+
"Access-Control-Allow-Headers":
|
|
252
|
+
"authorization, x-client-info, apikey, content-type",
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
interface AlertEmailRequest {
|
|
256
|
+
contacts: {
|
|
257
|
+
name: string;
|
|
258
|
+
email: string;
|
|
259
|
+
}[];
|
|
260
|
+
userName?: string;
|
|
261
|
+
message?: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const handler = async (req: Request): Promise<Response> => {
|
|
265
|
+
if (req.method === "OPTIONS") {
|
|
266
|
+
return new Response(null, { headers: corsHeaders });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const { contacts, userName = "用户", message }: AlertEmailRequest = await req.json();
|
|
271
|
+
|
|
272
|
+
if (!contacts || contacts.length === 0) {
|
|
273
|
+
return new Response(
|
|
274
|
+
JSON.stringify({ error: "No contacts provided" }),
|
|
275
|
+
{
|
|
276
|
+
status: 400,
|
|
277
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const results = await Promise.all(
|
|
283
|
+
contacts.map(async (contact) => {
|
|
284
|
+
const emailResponse = await resend.emails.send({
|
|
285
|
+
from: "Your App <onboarding@resend.dev>",
|
|
286
|
+
to: [contact.email],
|
|
287
|
+
subject: `⚠️ Alert: ${userName} triggered notification`,
|
|
288
|
+
html: `
|
|
289
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
|
290
|
+
<h1 style="color: #f59e0b; border-bottom: 2px solid #f59e0b; padding-bottom: 10px;">
|
|
291
|
+
⚠️ Alert Notification
|
|
292
|
+
</h1>
|
|
293
|
+
<p>Hi ${contact.name},</p>
|
|
294
|
+
<p>This is an alert notification from Your App.</p>
|
|
295
|
+
<p><strong>${userName}</strong> ${message || "has triggered an alert notification."}</p>
|
|
296
|
+
<div style="background: #fef3c7; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
297
|
+
<p style="margin: 0; color: #92400e;">
|
|
298
|
+
Please take appropriate action.
|
|
299
|
+
</p>
|
|
300
|
+
</div>
|
|
301
|
+
<p style="color: #666; font-size: 14px;">
|
|
302
|
+
This is an automated email from Your App.
|
|
303
|
+
</p>
|
|
304
|
+
</div>
|
|
305
|
+
`,
|
|
306
|
+
});
|
|
307
|
+
return { contact: contact.email, response: emailResponse };
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
console.log("Emails sent successfully:", results);
|
|
312
|
+
|
|
313
|
+
return new Response(
|
|
314
|
+
JSON.stringify({ success: true, results }),
|
|
315
|
+
{
|
|
316
|
+
status: 200,
|
|
317
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
} catch (error: any) {
|
|
321
|
+
console.error("Error in send-alert-email function:", error);
|
|
322
|
+
return new Response(
|
|
323
|
+
JSON.stringify({ error: error.message }),
|
|
324
|
+
{
|
|
325
|
+
status: 500,
|
|
326
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
serve(handler);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Email Templates
|
|
336
|
+
|
|
337
|
+
### Welcome Email Template
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const welcomeEmailHTML = (userName: string) => `
|
|
341
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
|
|
342
|
+
<h1>Welcome to Our App!</h1>
|
|
343
|
+
<p>Hi ${userName},</p>
|
|
344
|
+
<p>Thank you for signing up. We're excited to have you on board!</p>
|
|
345
|
+
<a href="https://yourapp.com/get-started"
|
|
346
|
+
style="background: #3b82f6; color: white; padding: 12px 24px;
|
|
347
|
+
text-decoration: none; border-radius: 6px; display: inline-block;">
|
|
348
|
+
Get Started
|
|
349
|
+
</a>
|
|
350
|
+
</div>
|
|
351
|
+
`;
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Password Reset Template
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const resetPasswordHTML = (resetLink: string) => `
|
|
358
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
|
|
359
|
+
<h1>Reset Your Password</h1>
|
|
360
|
+
<p>Click the button below to reset your password:</p>
|
|
361
|
+
<a href="${resetLink}"
|
|
362
|
+
style="background: #ef4444; color: white; padding: 12px 24px;
|
|
363
|
+
text-decoration: none; border-radius: 6px; display: inline-block;">
|
|
364
|
+
Reset Password
|
|
365
|
+
</a>
|
|
366
|
+
<p style="color: #666; font-size: 14px; margin-top: 20px;">
|
|
367
|
+
If you didn't request this, please ignore this email.
|
|
368
|
+
</p>
|
|
369
|
+
</div>
|
|
370
|
+
`;
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Notification Email Template
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
const notificationEmailHTML = (title: string, content: string) => `
|
|
377
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
|
378
|
+
<h2 style="color: #1f2937; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;">
|
|
379
|
+
${title}
|
|
380
|
+
</h2>
|
|
381
|
+
<div style="background: #f3f4f6; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
|
382
|
+
<p style="margin: 0; color: #374151;">${content}</p>
|
|
383
|
+
</div>
|
|
384
|
+
<p style="color: #6b7280; font-size: 14px;">
|
|
385
|
+
This is an automated notification from Your App.
|
|
386
|
+
</p>
|
|
387
|
+
</div>
|
|
388
|
+
`;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Invoking from Client
|
|
392
|
+
|
|
393
|
+
### Using Supabase Functions
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
// In your React/Vue/etc. component
|
|
397
|
+
import { supabase } from '@/lib/supabase';
|
|
398
|
+
|
|
399
|
+
// Send single email
|
|
400
|
+
const sendEmail = async () => {
|
|
401
|
+
const { data, error } = await supabase.functions.invoke('send-email', {
|
|
402
|
+
body: {
|
|
403
|
+
to: 'user@example.com',
|
|
404
|
+
subject: 'Hello from Your App',
|
|
405
|
+
message: 'This is a test email'
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (error) {
|
|
410
|
+
console.error('Error sending email:', error);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.log('Email sent:', data);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// Send alert emails
|
|
418
|
+
const sendAlertEmails = async () => {
|
|
419
|
+
const { data, error } = await supabase.functions.invoke('send-alert-email', {
|
|
420
|
+
body: {
|
|
421
|
+
contacts: [
|
|
422
|
+
{ name: 'John Doe', email: 'john@example.com' },
|
|
423
|
+
{ name: 'Jane Smith', email: 'jane@example.com' }
|
|
424
|
+
],
|
|
425
|
+
userName: '张三',
|
|
426
|
+
message: '触发了紧急通知'
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (error) {
|
|
431
|
+
console.error('Error sending alerts:', error);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log('Alerts sent:', data);
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Common Use Cases
|
|
440
|
+
|
|
441
|
+
### 1. Welcome Emails
|
|
442
|
+
Send when user signs up:
|
|
443
|
+
```typescript
|
|
444
|
+
const { data, error } = await supabase.functions.invoke('send-welcome-email', {
|
|
445
|
+
body: { email: user.email, name: user.name }
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### 2. Password Reset
|
|
450
|
+
Send password reset link:
|
|
451
|
+
```typescript
|
|
452
|
+
const { data, error } = await supabase.functions.invoke('send-password-reset', {
|
|
453
|
+
body: { email: user.email, resetToken: token }
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 3. Email Verification
|
|
458
|
+
Send verification code:
|
|
459
|
+
```typescript
|
|
460
|
+
const { data, error } = await supabase.functions.invoke('send-verification', {
|
|
461
|
+
body: { email: user.email, code: verificationCode }
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 4. Transaction Confirmations
|
|
466
|
+
Send order/payment confirmations:
|
|
467
|
+
```typescript
|
|
468
|
+
const { data, error } = await supabase.functions.invoke('send-confirmation', {
|
|
469
|
+
body: { email: user.email, orderDetails: order }
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 5. Scheduled Reminders
|
|
474
|
+
Send reminder emails:
|
|
475
|
+
```typescript
|
|
476
|
+
const { data, error } = await supabase.functions.invoke('send-reminder', {
|
|
477
|
+
body: { email: user.email, reminderText: reminder }
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Best Practices
|
|
482
|
+
|
|
483
|
+
### 1. Email Sender Configuration
|
|
484
|
+
|
|
485
|
+
**✅ DO**:
|
|
486
|
+
- Use custom domain for production: `Your App <noreply@yourdomain.com>`
|
|
487
|
+
- Use descriptive sender names
|
|
488
|
+
- Keep sender email consistent
|
|
489
|
+
|
|
490
|
+
**❌ DON'T**:
|
|
491
|
+
- Use `onboarding@resend.dev` in production (for testing only)
|
|
492
|
+
- Change sender email frequently
|
|
493
|
+
- Use confusing sender names
|
|
494
|
+
|
|
495
|
+
### 2. Error Handling
|
|
496
|
+
|
|
497
|
+
Always handle errors gracefully:
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
try {
|
|
501
|
+
const { data, error } = await resend.emails.send({...});
|
|
502
|
+
|
|
503
|
+
if (error) {
|
|
504
|
+
console.error("Resend API error:", error);
|
|
505
|
+
throw new Error(error.message);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return new Response(JSON.stringify({ success: true, data }), {...});
|
|
509
|
+
} catch (error: any) {
|
|
510
|
+
console.error("Unexpected error:", error);
|
|
511
|
+
return new Response(
|
|
512
|
+
JSON.stringify({ error: error.message }),
|
|
513
|
+
{ status: 500, headers: {...} }
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 3. Rate Limiting
|
|
519
|
+
|
|
520
|
+
Implement rate limiting for batch sends:
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
// Send emails in batches of 10
|
|
524
|
+
const batchSize = 10;
|
|
525
|
+
for (let i = 0; i < recipients.length; i += batchSize) {
|
|
526
|
+
const batch = recipients.slice(i, i + batchSize);
|
|
527
|
+
await Promise.all(batch.map(recipient => sendEmail(recipient)));
|
|
528
|
+
// Add delay between batches if needed
|
|
529
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### 4. HTML Email Best Practices
|
|
534
|
+
|
|
535
|
+
**✅ DO**:
|
|
536
|
+
- Use inline CSS styles
|
|
537
|
+
- Keep width under 600px
|
|
538
|
+
- Test on multiple email clients
|
|
539
|
+
- Include plain text fallback
|
|
540
|
+
- Use responsive design
|
|
541
|
+
- Add unsubscribe links (if applicable)
|
|
542
|
+
|
|
543
|
+
**❌ DON'T**:
|
|
544
|
+
- Use external CSS files
|
|
545
|
+
- Use JavaScript
|
|
546
|
+
- Use complex layouts
|
|
547
|
+
- Forget alt text for images
|
|
548
|
+
|
|
549
|
+
### 5. Security
|
|
550
|
+
|
|
551
|
+
**✅ DO**:
|
|
552
|
+
- Store API key in environment variables
|
|
553
|
+
- Validate input data
|
|
554
|
+
- Sanitize user-generated content
|
|
555
|
+
- Use HTTPS for all links
|
|
556
|
+
- Implement rate limiting
|
|
557
|
+
|
|
558
|
+
**❌ DON'T**:
|
|
559
|
+
- Hardcode API keys
|
|
560
|
+
- Trust user input without validation
|
|
561
|
+
- Include sensitive data in emails
|
|
562
|
+
- Allow arbitrary email sending
|
|
563
|
+
|
|
564
|
+
## Debugging
|
|
565
|
+
|
|
566
|
+
### Common Issues
|
|
567
|
+
|
|
568
|
+
**1. Email not received**:
|
|
569
|
+
- Check spam/junk folder
|
|
570
|
+
- Verify recipient email address
|
|
571
|
+
- Check Resend Dashboard for delivery status
|
|
572
|
+
- Verify sender domain configuration
|
|
573
|
+
|
|
574
|
+
**2. API Key errors**:
|
|
575
|
+
- Verify `RESEND_API_KEY` in environment variables
|
|
576
|
+
- Check API key is active in Resend Dashboard
|
|
577
|
+
- Ensure proper Deno.env.get() usage
|
|
578
|
+
|
|
579
|
+
**3. CORS errors**:
|
|
580
|
+
- Verify CORS headers are included
|
|
581
|
+
- Handle OPTIONS requests
|
|
582
|
+
- Check browser console for specific errors
|
|
583
|
+
|
|
584
|
+
**4. Function timeout**:
|
|
585
|
+
- Reduce batch size
|
|
586
|
+
- Optimize email sending logic
|
|
587
|
+
- Check for blocking operations
|
|
588
|
+
|
|
589
|
+
### Logging
|
|
590
|
+
|
|
591
|
+
Add comprehensive logging:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
console.log("Sending email to:", recipient.email);
|
|
595
|
+
console.log("Email response:", emailResponse);
|
|
596
|
+
console.error("Error sending email:", error);
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Testing
|
|
600
|
+
|
|
601
|
+
Test Edge Function locally:
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
supabase functions serve send-email --env-file supabase/.env.local
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Test with curl:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
curl -i --location --request POST 'http://localhost:54321/functions/v1/send-email' \
|
|
611
|
+
--header 'Authorization: Bearer YOUR_ANON_KEY' \
|
|
612
|
+
--header 'Content-Type: application/json' \
|
|
613
|
+
--data '{"to":"test@example.com","subject":"Test","message":"Hello"}'
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Project Structure
|
|
617
|
+
|
|
618
|
+
```
|
|
619
|
+
project-root/
|
|
620
|
+
├── supabase/
|
|
621
|
+
│ ├── functions/
|
|
622
|
+
│ │ ├── send-email/
|
|
623
|
+
│ │ │ └── index.ts # Basic email sending
|
|
624
|
+
│ │ ├── send-alert-email/
|
|
625
|
+
│ │ │ └── index.ts # Alert emails
|
|
626
|
+
│ │ ├── send-welcome-email/
|
|
627
|
+
│ │ │ └── index.ts # Welcome emails
|
|
628
|
+
│ │ └── send-notification/
|
|
629
|
+
│ │ └── index.ts # Notification emails
|
|
630
|
+
│ └── .env.local # RESEND_API_KEY
|
|
631
|
+
├── src/
|
|
632
|
+
│ ├── lib/
|
|
633
|
+
│ │ └── supabase.ts # Supabase Client
|
|
634
|
+
│ └── components/
|
|
635
|
+
│ └── EmailForm.tsx # Email sending UI
|
|
636
|
+
└── .env # Frontend env vars
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
## Additional Resources
|
|
640
|
+
|
|
641
|
+
- [Resend Documentation](https://resend.com/docs)
|
|
642
|
+
- [Resend API Reference](https://resend.com/docs/api-reference)
|
|
643
|
+
- [Email Templates Best Practices](https://resend.com/docs/send-with-react)
|
|
644
|
+
- [Supabase Edge Functions Guide](https://supabase.com/docs/guides/functions)
|
|
645
|
+
|
|
646
|
+
## Migration Checklist
|
|
647
|
+
|
|
648
|
+
When moving to production:
|
|
649
|
+
|
|
650
|
+
- [ ] Register custom domain in Resend Dashboard
|
|
651
|
+
- [ ] Update sender email from `onboarding@resend.dev` to custom domain
|
|
652
|
+
- [ ] Configure SPF, DKIM, and DMARC records
|
|
653
|
+
- [ ] Update `RESEND_API_KEY` with production key
|
|
654
|
+
- [ ] Test all email flows
|
|
655
|
+
- [ ] Set up email delivery monitoring
|
|
656
|
+
- [ ] Implement unsubscribe functionality (if needed)
|
|
657
|
+
- [ ] Add email preference management
|