用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/jdeweedata/circletel --skill inngest-function-template命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Generate and edit images using OpenAI's gpt-image models (gpt-image-1 / gpt-image-2). Use as an alternative to the Gemini (Nano Banana) image path when the user asks for OpenAI image generation, or when comparing providers for a hero/product/social asset. Covers text-to-image, image editing, transparent backgrounds, and CircleTel brand context.
Generate images cost-effectively via OpenRouter (Seedream 4.5, FLUX.2, Recraft V4, etc.) as a cheaper alternative to OpenAI gpt-image. Use when the user wants low-cost image generation, a specific OpenRouter image model, or quality comparable to gpt-image-2 at a fraction of the price. Requires OPENROUTER_API_KEY in .env.local.
Filter and browse supplier catalogues (Scoop, Miro, Nology) to identify product candidates for CircleTel portfolio
基于 SOC 职业分类
正在显示 SKILL.md
| name | inngest-function-template |
| description | Create reliable Inngest functions with dual triggers, step checkpoints, and failure handling |
| allowed-tools | ["Read","Write","Edit","Glob","Grep"] |
Create production-ready Inngest functions following CircleTel patterns.
Add to lib/inngest/client.ts:
// Event types for [Feature]
'[feature]/[action].requested': {
data: {
triggered_by: 'cron' | 'manual';
log_id?: string;
options?: { dryRun?: boolean };
};
};
'[feature]/[action].completed': {
data: {
log_id: string;
stats: { processed: number; errors: number };
};
};
'[feature]/[action].failed': {
data: {
log_id: string;
error: string;
attempt: number;
};
};
// lib/inngest/functions/[feature]-[action].ts
import { inngest } from '../client';
import { createClient } from '@/lib/supabase/server';
export const [feature][Action]Function = inngest.createFunction(
{
id: '[feature]-[action]',
name: '[Feature] [Action]',
retries: 3,
cancelOn: [{ event: '[feature]/[action].cancelled', match: 'data.log_id' }],
},
[
{ cron: '0 22 * * *' }, // Midnight SAST
{ event: '[feature]/[action].requested' }, // Manual trigger
],
async ({ event, step }) => {
const supabase = await createClient();
const isCronTrigger = !event?.data?.log_id;
const logId = event?.data?.log_id;
try {
// Step 1: Initialize
await step.run('initialize', async () => {
if (logId) {
await supabase
.from()
.({ : , : ().() })
.(, logId);
}
});
data = step.(, () => {
fetchedData;
});
batches = (data, );
( i = ; i < batches.; i++) {
step.(, () => {
});
}
step.(, () => {
inngest.({
: ,
: { : logId, : { : data., : } },
});
});
{ : , : data. };
} (error) {
step.(, () => {
inngest.({
: ,
: {
: logId,
: error ? error. : ,
: ,
},
});
});
error;
}
}
);
Add to lib/inngest/index.ts:
import { [feature][Action]Function } from './functions/[feature]-[action]';
export const functions = [
// existing functions...
[feature][Action]Function,
];
// app/api/admin/[feature]/[action]/route.ts
export async function POST(request: NextRequest) {
const supabase = await createClient();
// Check for already running
const { data: running } = await supabase
.from('[feature]_logs')
.select('id')
.in('status', ['pending', 'running'])
.limit(1)
.single();
if (running) {
return NextResponse.json(
{ success: false, error: 'Already in progress', log_id: running.id },
{ status: 409 }
);
}
// Create log, then send event
const { data: log } = await supabase
.from('[feature]_logs')
.insert({ status: 'pending', trigger_type: 'manual', triggered_by: user.id })
.()
.();
inngest.({
: ,
: { : , : log. },
});
.({ : , : log. });
}
// app/api/admin/[feature]/[action]/status/route.ts
export async function GET() {
const supabase = await createClient();
const { data } = await supabase
.from('[feature]_logs')
.select('*')
.order('created_at', { ascending: false })
.limit(1)
.single();
return NextResponse.json(data);
}
// In your admin page component
const triggerAction = async () => {
setStatus('pending');
const response = await fetch('/api/admin/[feature]/[action]', { method: 'POST' });
const data = await response.json();
if (data.success) {
const pollInterval = setInterval(async () => {
const statusRes = await fetch('/api/admin/[feature]/[action]/status');
const status = await statusRes.json();
if (status.status === 'completed' || status.status === 'failed') {
clearInterval(pollInterval);
if (status.status === 'completed') refreshData();
}
}, 2000);
}
};
lib/inngest/client.tslib/inngest/index.tsFor functions that check multiple external APIs:
// CRITICAL: Order matters - index positions must match destructuring
const [result1, result2, result3] = await Promise.allSettled([
checkProvider1(coords), // Index 0
checkProvider2(coords), // Index 1
checkProvider3(coords), // Index 2
]);
// Process with type guards
const services = Array.isArray(provider?.services) ? provider.services : [];
const isAvailable = services.some(
(s: { type?: string }) => s?.type === 'target'
);
lib/inngest/client.ts - Event typeslib/inngest/functions/ - Function implementationslib/inngest/index.ts - Function registrysupabase/migrations/ - Logs tables