@editframe/create 0.44.0 → 0.45.1
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/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/skills/editframe-brand-video-generator/README.md +155 -0
- package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
- package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
- package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
- package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
- package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
- package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
- package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
- package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
- package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
- package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
- package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
- package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
- package/dist/skills/editframe-composition/SKILL.md +169 -0
- package/dist/skills/editframe-composition/references/audio.md +483 -0
- package/dist/skills/editframe-composition/references/captions.md +844 -0
- package/dist/skills/editframe-composition/references/composition-model.md +73 -0
- package/dist/skills/editframe-composition/references/configuration.md +403 -0
- package/dist/skills/editframe-composition/references/css-parts.md +105 -0
- package/dist/skills/editframe-composition/references/css-variables.md +640 -0
- package/dist/skills/editframe-composition/references/entry-points.md +810 -0
- package/dist/skills/editframe-composition/references/events.md +499 -0
- package/dist/skills/editframe-composition/references/getting-started.md +259 -0
- package/dist/skills/editframe-composition/references/hooks.md +234 -0
- package/dist/skills/editframe-composition/references/image.md +241 -0
- package/dist/skills/editframe-composition/references/r3f.md +580 -0
- package/dist/skills/editframe-composition/references/render-api.md +484 -0
- package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
- package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
- package/dist/skills/editframe-composition/references/scripting.md +606 -0
- package/dist/skills/editframe-composition/references/sequencing.md +116 -0
- package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
- package/dist/skills/editframe-composition/references/surface.md +329 -0
- package/dist/skills/editframe-composition/references/text.md +627 -0
- package/dist/skills/editframe-composition/references/time-model.md +99 -0
- package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
- package/dist/skills/editframe-composition/references/timegroup.md +457 -0
- package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
- package/dist/skills/editframe-composition/references/transcription.md +47 -0
- package/dist/skills/editframe-composition/references/transitions.md +608 -0
- package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
- package/dist/skills/editframe-composition/references/video.md +506 -0
- package/dist/skills/editframe-composition/references/waveform.md +327 -0
- package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
- package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
- package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
- package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
- package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
- package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
- package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
- package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
- package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
- package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
- package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
- package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
- package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
- package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
- package/dist/skills/editframe-editor-gui/references/play.md +370 -0
- package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
- package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
- package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
- package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
- package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
- package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
- package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
- package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
- package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
- package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
- package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
- package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
- package/dist/skills/editframe-motion-design/SKILL.md +101 -0
- package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
- package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
- package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
- package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
- package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
- package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
- package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
- package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
- package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
- package/dist/skills/editframe-webhooks/SKILL.md +126 -0
- package/dist/skills/editframe-webhooks/references/events.md +382 -0
- package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
- package/dist/skills/editframe-webhooks/references/security.md +418 -0
- package/dist/skills/editframe-webhooks/references/testing.md +409 -0
- package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
- package/dist/templates/html/AGENTS.md +13 -0
- package/dist/templates/react/AGENTS.md +13 -0
- package/dist/utils.js +15 -16
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- package/dist/detectAgent.js.map +0 -1
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Troubleshooting Webhooks
|
|
3
|
+
description: "Diagnose and fix common webhook issues: signature verification failures, delivery timeouts, retries, and missed events."
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Troubleshooting"
|
|
7
|
+
priority: 5
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Troubleshooting Webhooks
|
|
11
|
+
|
|
12
|
+
Debug common webhook issues and understand webhook delivery behavior.
|
|
13
|
+
|
|
14
|
+
## Signature Verification Failures
|
|
15
|
+
|
|
16
|
+
### Symptom
|
|
17
|
+
|
|
18
|
+
All webhooks are rejected with 401 Unauthorized or signature verification fails.
|
|
19
|
+
|
|
20
|
+
### Causes and Solutions
|
|
21
|
+
|
|
22
|
+
#### 1. Hashing Parsed JSON Instead of Raw Body
|
|
23
|
+
|
|
24
|
+
**Problem**: You're hashing `JSON.stringify(req.body)` instead of the raw request body.
|
|
25
|
+
|
|
26
|
+
**Why it fails**: JSON serialization order is not guaranteed. The signature was computed on the original body, which may have different key order.
|
|
27
|
+
|
|
28
|
+
**Solution**: Hash the raw body string as received:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// Wrong
|
|
32
|
+
const signature = crypto
|
|
33
|
+
.createHmac("sha256", secret)
|
|
34
|
+
.update(JSON.stringify(req.body))
|
|
35
|
+
.digest("hex");
|
|
36
|
+
|
|
37
|
+
// Right
|
|
38
|
+
const signature = crypto
|
|
39
|
+
.createHmac("sha256", secret)
|
|
40
|
+
.update(req.rawBody) // Raw body as string/buffer
|
|
41
|
+
.digest("hex");
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Express setup**:
|
|
45
|
+
```typescript
|
|
46
|
+
app.use(bodyParser.json({
|
|
47
|
+
verify: (req, res, buf) => {
|
|
48
|
+
(req as any).rawBody = buf.toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### 2. Wrong Webhook Secret
|
|
54
|
+
|
|
55
|
+
**Problem**: Using the API key instead of the webhook secret.
|
|
56
|
+
|
|
57
|
+
**Solution**: Use the webhook secret (shown once during API key creation):
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Wrong - this is your API key
|
|
61
|
+
const secret = "ef_live_abc123...";
|
|
62
|
+
|
|
63
|
+
// Right - this is your webhook secret
|
|
64
|
+
const secret = "whsec_abc123..." || process.env.EDITFRAME_WEBHOOK_SECRET;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If you lost your webhook secret, regenerate it in the dashboard.
|
|
68
|
+
|
|
69
|
+
#### 3. Secret Has Whitespace
|
|
70
|
+
|
|
71
|
+
**Problem**: Secret has extra spaces, tabs, or newlines.
|
|
72
|
+
|
|
73
|
+
**Solution**: Trim the secret:
|
|
74
|
+
```typescript
|
|
75
|
+
const secret = process.env.EDITFRAME_WEBHOOK_SECRET!.trim();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### 4. Using Non-Timing-Safe Comparison
|
|
79
|
+
|
|
80
|
+
**Problem**: String comparison is vulnerable to timing attacks and may behave unexpectedly.
|
|
81
|
+
|
|
82
|
+
**Solution**: Use timing-safe comparison:
|
|
83
|
+
```typescript
|
|
84
|
+
// Wrong
|
|
85
|
+
if (signature === expectedSignature) { }
|
|
86
|
+
|
|
87
|
+
// Right
|
|
88
|
+
import crypto from "node:crypto";
|
|
89
|
+
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Debug Steps
|
|
93
|
+
|
|
94
|
+
1. **Log both signatures**:
|
|
95
|
+
```typescript
|
|
96
|
+
console.log("Received:", signature);
|
|
97
|
+
console.log("Expected:", expectedSignature);
|
|
98
|
+
console.log("Match:", signature === expectedSignature);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
2. **Log the payload being hashed**:
|
|
102
|
+
```typescript
|
|
103
|
+
console.log("Raw body:", req.rawBody);
|
|
104
|
+
console.log("Body length:", req.rawBody.length);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
3. **Verify secret is correct**:
|
|
108
|
+
```typescript
|
|
109
|
+
console.log("Secret length:", secret.length);
|
|
110
|
+
console.log("Secret starts with:", secret.substring(0, 10));
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
4. **Test with known payload**:
|
|
114
|
+
```typescript
|
|
115
|
+
const testPayload = '{"topic":"webhook.test","data":{"id":"test"}}';
|
|
116
|
+
const testSignature = crypto
|
|
117
|
+
.createHmac("sha256", secret)
|
|
118
|
+
.update(testPayload)
|
|
119
|
+
.digest("hex");
|
|
120
|
+
console.log("Test signature:", testSignature);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Timeout Errors
|
|
124
|
+
|
|
125
|
+
### Symptom
|
|
126
|
+
|
|
127
|
+
Webhooks fail with timeout errors in delivery logs. Events show multiple retry attempts.
|
|
128
|
+
|
|
129
|
+
### Cause
|
|
130
|
+
|
|
131
|
+
Your endpoint takes longer than 30 seconds to respond.
|
|
132
|
+
|
|
133
|
+
### Solution
|
|
134
|
+
|
|
135
|
+
Respond with 200 OK immediately, then process the event asynchronously:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Wrong - synchronous processing
|
|
139
|
+
app.post("/webhooks/editframe", async (req, res) => {
|
|
140
|
+
await verifySignature(req);
|
|
141
|
+
await processEvent(req.body); // Takes 60 seconds
|
|
142
|
+
res.status(200).send("OK"); // Times out!
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Right - asynchronous processing
|
|
146
|
+
app.post("/webhooks/editframe", async (req, res) => {
|
|
147
|
+
await verifySignature(req);
|
|
148
|
+
|
|
149
|
+
// Respond immediately
|
|
150
|
+
res.status(200).send("OK");
|
|
151
|
+
|
|
152
|
+
// Process in background
|
|
153
|
+
processEvent(req.body).catch(console.error);
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Use a job queue for reliability:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Queue } from "bull";
|
|
161
|
+
|
|
162
|
+
const webhookQueue = new Queue("webhooks");
|
|
163
|
+
|
|
164
|
+
app.post("/webhooks/editframe", async (req, res) => {
|
|
165
|
+
await verifySignature(req);
|
|
166
|
+
|
|
167
|
+
// Add to queue
|
|
168
|
+
await webhookQueue.add({
|
|
169
|
+
topic: req.body.topic,
|
|
170
|
+
data: req.body.data,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
res.status(200).send("OK");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Process jobs in background
|
|
177
|
+
webhookQueue.process(async (job) => {
|
|
178
|
+
await processEvent(job.data);
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Missed Webhooks
|
|
183
|
+
|
|
184
|
+
### Symptom
|
|
185
|
+
|
|
186
|
+
Expected webhooks are not received.
|
|
187
|
+
|
|
188
|
+
### Causes and Solutions
|
|
189
|
+
|
|
190
|
+
#### 1. Events Not Subscribed
|
|
191
|
+
|
|
192
|
+
**Problem**: Webhook events not configured on API key.
|
|
193
|
+
|
|
194
|
+
**Solution**: Update API key's webhook events:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// Check current configuration
|
|
198
|
+
const apiKey = await db
|
|
199
|
+
.selectFrom("identity.api_keys")
|
|
200
|
+
.where("id", "=", apiKeyId)
|
|
201
|
+
.select(["webhook_events", "webhook_url"])
|
|
202
|
+
.executeTakeFirst();
|
|
203
|
+
|
|
204
|
+
console.log("Subscribed events:", apiKey.webhook_events);
|
|
205
|
+
console.log("Webhook URL:", apiKey.webhook_url);
|
|
206
|
+
|
|
207
|
+
// Update events
|
|
208
|
+
await db
|
|
209
|
+
.updateTable("identity.api_keys")
|
|
210
|
+
.where("id", "=", apiKeyId)
|
|
211
|
+
.set({
|
|
212
|
+
webhook_events: ["render.completed", "render.failed", "file.ready"]
|
|
213
|
+
})
|
|
214
|
+
.execute();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### 2. Webhook URL Not Set
|
|
218
|
+
|
|
219
|
+
**Problem**: API key doesn't have a webhook URL configured.
|
|
220
|
+
|
|
221
|
+
**Solution**: Set the webhook URL in the dashboard or via API.
|
|
222
|
+
|
|
223
|
+
#### 3. Endpoint Returns Error
|
|
224
|
+
|
|
225
|
+
**Problem**: Your endpoint returns 4xx or 5xx status, causing Editframe to mark delivery as failed.
|
|
226
|
+
|
|
227
|
+
**Solution**: Fix endpoint errors. Check logs for error details.
|
|
228
|
+
|
|
229
|
+
#### 4. Firewall Blocking Requests
|
|
230
|
+
|
|
231
|
+
**Problem**: Firewall or load balancer blocks webhook requests.
|
|
232
|
+
|
|
233
|
+
**Solution**:
|
|
234
|
+
- Whitelist Editframe's IP ranges (check documentation)
|
|
235
|
+
- Verify endpoint is publicly accessible
|
|
236
|
+
- Test with `curl` from external server
|
|
237
|
+
|
|
238
|
+
### Debug Steps
|
|
239
|
+
|
|
240
|
+
1. **Check delivery logs** in the Editframe dashboard:
|
|
241
|
+
- Go to API key detail page
|
|
242
|
+
- View "Webhook Deliveries"
|
|
243
|
+
- Check status codes and response bodies
|
|
244
|
+
|
|
245
|
+
2. **Verify webhook configuration**:
|
|
246
|
+
```typescript
|
|
247
|
+
// Test endpoint is reachable
|
|
248
|
+
fetch("https://your-app.com/webhooks/editframe", {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: { "Content-Type": "application/json" },
|
|
251
|
+
body: JSON.stringify({ test: true })
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
3. **Test with webhook.site**:
|
|
256
|
+
- Temporarily set webhook URL to webhook.site
|
|
257
|
+
- Trigger event
|
|
258
|
+
- Verify webhook is sent
|
|
259
|
+
|
|
260
|
+
## Duplicate Webhooks
|
|
261
|
+
|
|
262
|
+
### Symptom
|
|
263
|
+
|
|
264
|
+
Same event is processed multiple times.
|
|
265
|
+
|
|
266
|
+
### Cause
|
|
267
|
+
|
|
268
|
+
Webhooks are retried on failure or timeout. Your endpoint may process the same event multiple times.
|
|
269
|
+
|
|
270
|
+
### Solution
|
|
271
|
+
|
|
272
|
+
Implement idempotency:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const processedEvents = new Set<string>();
|
|
276
|
+
|
|
277
|
+
app.post("/webhooks/editframe", async (req, res) => {
|
|
278
|
+
await verifySignature(req);
|
|
279
|
+
|
|
280
|
+
const event = req.body;
|
|
281
|
+
const eventId = `${event.topic}:${event.data.id}`;
|
|
282
|
+
|
|
283
|
+
if (processedEvents.has(eventId)) {
|
|
284
|
+
console.log(`Duplicate webhook: ${eventId}`);
|
|
285
|
+
return res.status(200).send("OK"); // Still return 200
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
processedEvents.add(eventId);
|
|
289
|
+
res.status(200).send("OK");
|
|
290
|
+
|
|
291
|
+
await processEvent(event);
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
For production, use a database:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
async function isProcessed(eventId: string): Promise<boolean> {
|
|
299
|
+
const result = await db.query(
|
|
300
|
+
"SELECT 1 FROM processed_webhooks WHERE event_id = $1",
|
|
301
|
+
[eventId]
|
|
302
|
+
);
|
|
303
|
+
return result.rows.length > 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function markProcessed(eventId: string): Promise<void> {
|
|
307
|
+
await db.query(
|
|
308
|
+
"INSERT INTO processed_webhooks (event_id, processed_at) VALUES ($1, NOW()) ON CONFLICT DO NOTHING",
|
|
309
|
+
[eventId]
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Retry Behavior
|
|
315
|
+
|
|
316
|
+
### How Retries Work
|
|
317
|
+
|
|
318
|
+
When webhook delivery fails:
|
|
319
|
+
1. **First attempt**: Immediate delivery
|
|
320
|
+
2. **Second attempt**: 10 seconds later
|
|
321
|
+
3. **Third attempt**: 10 seconds later
|
|
322
|
+
4. **Max retries**: 3 attempts total
|
|
323
|
+
5. **Timeout**: 30 seconds per attempt
|
|
324
|
+
|
|
325
|
+
After 3 failed attempts, the event is marked as failed and retries stop.
|
|
326
|
+
|
|
327
|
+
### What Triggers Retries
|
|
328
|
+
|
|
329
|
+
Retries occur when:
|
|
330
|
+
- Endpoint returns 4xx or 5xx status code
|
|
331
|
+
- Request times out (>30 seconds)
|
|
332
|
+
- Network error (connection refused, DNS failure)
|
|
333
|
+
|
|
334
|
+
Retries **do not** occur when:
|
|
335
|
+
- Endpoint returns 200 OK (even if processing fails)
|
|
336
|
+
|
|
337
|
+
### Viewing Retry History
|
|
338
|
+
|
|
339
|
+
Check webhook delivery logs in the dashboard:
|
|
340
|
+
- Each attempt is logged with timestamp
|
|
341
|
+
- See status code and response for each attempt
|
|
342
|
+
- Failed events show number of retries
|
|
343
|
+
|
|
344
|
+
### Handling Retries in Your Endpoint
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
app.post("/webhooks/editframe", async (req, res) => {
|
|
348
|
+
try {
|
|
349
|
+
await verifySignature(req);
|
|
350
|
+
|
|
351
|
+
// Respond immediately
|
|
352
|
+
res.status(200).send("OK");
|
|
353
|
+
|
|
354
|
+
// Process asynchronously
|
|
355
|
+
await processEvent(req.body);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error("Webhook processing error:", error);
|
|
358
|
+
|
|
359
|
+
// Still return 200 to prevent retries
|
|
360
|
+
// Log error for manual investigation
|
|
361
|
+
res.status(200).send("OK");
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Important**: If you return an error status code, the webhook will be retried. Only return errors for transient failures that should be retried (e.g., database connection lost).
|
|
367
|
+
|
|
368
|
+
## Debugging Checklist
|
|
369
|
+
|
|
370
|
+
When webhooks aren't working:
|
|
371
|
+
|
|
372
|
+
1. **Verify configuration**:
|
|
373
|
+
- [ ] Webhook URL is correct
|
|
374
|
+
- [ ] Webhook URL uses HTTPS
|
|
375
|
+
- [ ] Webhook events are selected
|
|
376
|
+
- [ ] Endpoint is publicly accessible
|
|
377
|
+
|
|
378
|
+
2. **Test signature verification**:
|
|
379
|
+
- [ ] Using correct webhook secret (not API key)
|
|
380
|
+
- [ ] Hashing raw body (not parsed JSON)
|
|
381
|
+
- [ ] Using timing-safe comparison
|
|
382
|
+
- [ ] Secret has no extra whitespace
|
|
383
|
+
|
|
384
|
+
3. **Check endpoint behavior**:
|
|
385
|
+
- [ ] Returns 200 OK within 30 seconds
|
|
386
|
+
- [ ] Handles all subscribed event types
|
|
387
|
+
- [ ] Implements idempotency
|
|
388
|
+
- [ ] Logs errors for debugging
|
|
389
|
+
|
|
390
|
+
4. **Review delivery logs**:
|
|
391
|
+
- [ ] Check status codes
|
|
392
|
+
- [ ] Review response bodies
|
|
393
|
+
- [ ] Count retry attempts
|
|
394
|
+
- [ ] Look for patterns in failures
|
|
395
|
+
|
|
396
|
+
5. **Test locally**:
|
|
397
|
+
- [ ] Use ngrok to expose local server
|
|
398
|
+
- [ ] Send test webhook from dashboard
|
|
399
|
+
- [ ] Run integration tests
|
|
400
|
+
- [ ] Test with manual script
|
|
401
|
+
|
|
402
|
+
## Getting Help
|
|
403
|
+
|
|
404
|
+
If you're still experiencing issues:
|
|
405
|
+
|
|
406
|
+
1. **Check delivery logs** in the dashboard for detailed error messages
|
|
407
|
+
2. **Test with webhook.site** to isolate the issue
|
|
408
|
+
3. **Review webhook event payloads** in the [events.md](references/events.md) reference
|
|
409
|
+
4. **Contact support** with:
|
|
410
|
+
- API key ID
|
|
411
|
+
- Webhook event ID (from delivery logs)
|
|
412
|
+
- Error messages from your logs
|
|
413
|
+
- Steps to reproduce
|
|
414
|
+
|
|
415
|
+
## Common Error Messages
|
|
416
|
+
|
|
417
|
+
### "Invalid signature"
|
|
418
|
+
|
|
419
|
+
**Cause**: Signature verification failed
|
|
420
|
+
|
|
421
|
+
**Solution**: See [Signature Verification Failures](#signature-verification-failures)
|
|
422
|
+
|
|
423
|
+
### "Webhook URL is not set"
|
|
424
|
+
|
|
425
|
+
**Cause**: API key doesn't have webhook URL configured
|
|
426
|
+
|
|
427
|
+
**Solution**: Set webhook URL in API key configuration
|
|
428
|
+
|
|
429
|
+
### "Connection refused"
|
|
430
|
+
|
|
431
|
+
**Cause**: Endpoint is not reachable
|
|
432
|
+
|
|
433
|
+
**Solution**:
|
|
434
|
+
- Verify endpoint is running
|
|
435
|
+
- Check firewall rules
|
|
436
|
+
- Test with `curl` from external server
|
|
437
|
+
|
|
438
|
+
### "SSL certificate verify failed"
|
|
439
|
+
|
|
440
|
+
**Cause**: Endpoint uses invalid SSL certificate
|
|
441
|
+
|
|
442
|
+
**Solution**:
|
|
443
|
+
- Use a valid SSL certificate from a trusted CA
|
|
444
|
+
- For development, use ngrok which provides valid certificates
|
|
445
|
+
|
|
446
|
+
### "Timed out after 30000ms"
|
|
447
|
+
|
|
448
|
+
**Cause**: Endpoint took longer than 30 seconds to respond
|
|
449
|
+
|
|
450
|
+
**Solution**: See [Timeout Errors](#timeout-errors)
|
|
451
|
+
|
|
452
|
+
## Next Steps
|
|
453
|
+
|
|
454
|
+
- [security.md](references/security.md) — Review signature verification
|
|
455
|
+
- [testing.md](references/testing.md) — Test webhook endpoints
|
|
456
|
+
- [getting-started.md](references/getting-started.md) — Complete setup guide
|
|
457
|
+
- [events.md](references/events.md) — Webhook event reference
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Editframe Project
|
|
2
|
+
|
|
3
|
+
This is an Editframe video composition project. Use the skills in `.claude/skills/` or `.agents/skills/` for guidance.
|
|
4
|
+
|
|
5
|
+
## Agent Workflow
|
|
6
|
+
|
|
7
|
+
- To render video: `npx editframe render src/index.js` — do not run `npm start`
|
|
8
|
+
- Edit existing files in `src/` — do not scaffold a new project
|
|
9
|
+
- Video compositions are HTML files; use `<ef-*>` web components or React equivalents
|
|
10
|
+
|
|
11
|
+
## Docs
|
|
12
|
+
|
|
13
|
+
https://editframe.com/docs
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Editframe Project
|
|
2
|
+
|
|
3
|
+
This is an Editframe video composition project. Use the skills in `.claude/skills/` or `.agents/skills/` for guidance.
|
|
4
|
+
|
|
5
|
+
## Agent Workflow
|
|
6
|
+
|
|
7
|
+
- To render video: `npx editframe render src/index.js` — do not run `npm start`
|
|
8
|
+
- Edit existing files in `src/` — do not scaffold a new project
|
|
9
|
+
- Video compositions are HTML files; use `<ef-*>` web components or React equivalents
|
|
10
|
+
|
|
11
|
+
## Docs
|
|
12
|
+
|
|
13
|
+
https://editframe.com/docs
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { cp, mkdir, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
1
4
|
import chalk from "chalk";
|
|
2
5
|
import { execa } from "execa";
|
|
3
6
|
|
|
@@ -45,27 +48,23 @@ async function installDependencies(projectDir) {
|
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
48
|
-
* Install AI agent skills
|
|
51
|
+
* Install AI agent skills by copying bundled skill files into the project.
|
|
52
|
+
* Writes to both .claude/skills/ and .agents/skills/ for broad agent compatibility.
|
|
49
53
|
*/
|
|
50
|
-
async function installAgentSkills(projectDir
|
|
54
|
+
async function installAgentSkills(projectDir) {
|
|
51
55
|
try {
|
|
52
|
-
process.stderr.write(chalk.bold(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
stderr: "inherit"
|
|
62
|
-
});
|
|
63
|
-
process.stderr.write(chalk.green(`\n✓ Agent skills installed for ${agent}!\n`));
|
|
56
|
+
process.stderr.write(chalk.bold("\nInstalling AI agent skills...\n\n"));
|
|
57
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
const skillsSource = path.join(__dirname, "skills");
|
|
59
|
+
const skills = await readdir(skillsSource);
|
|
60
|
+
for (const destBase of [".claude/skills", ".agents/skills"]) {
|
|
61
|
+
await mkdir(path.join(projectDir, destBase), { recursive: true });
|
|
62
|
+
for (const skill of skills) await cp(path.join(skillsSource, skill), path.join(projectDir, destBase, skill), { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
process.stderr.write(chalk.green("\n✓ AI agent skills installed!\n"));
|
|
64
65
|
return true;
|
|
65
66
|
} catch (error) {
|
|
66
67
|
process.stderr.write(chalk.yellow("\n⚠ Failed to install agent skills\n"));
|
|
67
|
-
process.stderr.write(chalk.dim("You can install manually:\n"));
|
|
68
|
-
process.stderr.write(chalk.cyan(` npx ai-agent-skills install editframe/skills --agent ${agent}\n\n`));
|
|
69
68
|
return false;
|
|
70
69
|
}
|
|
71
70
|
}
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { execa } from \"execa\";\nimport chalk from \"chalk\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\n/**\n * Detect which package manager was used to invoke the create script.\n * Uses the npm_config_user_agent environment variable set by package managers.\n */\nexport function getUserPkgManager(): PackageManager {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"bun\")) return \"bun\";\n }\n\n return \"npm\"; // Default fallback\n}\n\n/**\n * Run the appropriate install command for the detected package manager.\n * Shows full output to the user so they can see progress.\n */\nasync function runInstallCommand(\n pkgManager: PackageManager,\n projectDir: string,\n): Promise<void> {\n // Show all output directly to user - no hiding behind spinners\n await execa(pkgManager, [\"install\"], {\n cwd: projectDir,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n}\n\n/**\n * Install dependencies in the project directory.\n */\nexport async function installDependencies(\n projectDir: string,\n): Promise<boolean> {\n const pkgManager = getUserPkgManager();\n\n try {\n process.stderr.write(\n chalk.bold(`\\nInstalling dependencies with ${pkgManager}...\\n\\n`),\n );\n\n await runInstallCommand(pkgManager, projectDir);\n\n process.stderr.write(\n chalk.green(\"\\n✓ Dependencies installed successfully!\\n\"),\n );\n\n return true;\n } catch (error) {\n process.stderr.write(chalk.yellow(\"\\n⚠ Dependency installation failed\\n\"));\n process.stderr.write(chalk.dim(\"You can install manually:\\n\"));\n process.stderr.write(chalk.cyan(` cd ${projectDir.split(\"/\").pop()}\\n`));\n process.stderr.write(chalk.cyan(` ${pkgManager} install\\n\\n`));\n return false;\n }\n}\n\n/**\n * Install AI agent skills
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { cp, mkdir, readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execa } from \"execa\";\nimport chalk from \"chalk\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\n/**\n * Detect which package manager was used to invoke the create script.\n * Uses the npm_config_user_agent environment variable set by package managers.\n */\nexport function getUserPkgManager(): PackageManager {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"bun\")) return \"bun\";\n }\n\n return \"npm\"; // Default fallback\n}\n\n/**\n * Run the appropriate install command for the detected package manager.\n * Shows full output to the user so they can see progress.\n */\nasync function runInstallCommand(\n pkgManager: PackageManager,\n projectDir: string,\n): Promise<void> {\n // Show all output directly to user - no hiding behind spinners\n await execa(pkgManager, [\"install\"], {\n cwd: projectDir,\n stdout: \"inherit\",\n stderr: \"inherit\",\n });\n}\n\n/**\n * Install dependencies in the project directory.\n */\nexport async function installDependencies(\n projectDir: string,\n): Promise<boolean> {\n const pkgManager = getUserPkgManager();\n\n try {\n process.stderr.write(\n chalk.bold(`\\nInstalling dependencies with ${pkgManager}...\\n\\n`),\n );\n\n await runInstallCommand(pkgManager, projectDir);\n\n process.stderr.write(\n chalk.green(\"\\n✓ Dependencies installed successfully!\\n\"),\n );\n\n return true;\n } catch (error) {\n process.stderr.write(chalk.yellow(\"\\n⚠ Dependency installation failed\\n\"));\n process.stderr.write(chalk.dim(\"You can install manually:\\n\"));\n process.stderr.write(chalk.cyan(` cd ${projectDir.split(\"/\").pop()}\\n`));\n process.stderr.write(chalk.cyan(` ${pkgManager} install\\n\\n`));\n return false;\n }\n}\n\n/**\n * Install AI agent skills by copying bundled skill files into the project.\n * Writes to both .claude/skills/ and .agents/skills/ for broad agent compatibility.\n */\nexport async function installAgentSkills(projectDir: string): Promise<boolean> {\n try {\n process.stderr.write(chalk.bold(\"\\nInstalling AI agent skills...\\n\\n\"));\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const skillsSource = path.join(__dirname, \"skills\");\n const skills = await readdir(skillsSource);\n\n for (const destBase of [\".claude/skills\", \".agents/skills\"]) {\n await mkdir(path.join(projectDir, destBase), { recursive: true });\n for (const skill of skills) {\n await cp(\n path.join(skillsSource, skill),\n path.join(projectDir, destBase, skill),\n { recursive: true },\n );\n }\n }\n\n process.stderr.write(chalk.green(\"\\n✓ AI agent skills installed!\\n\"));\n return true;\n } catch (error) {\n process.stderr.write(chalk.yellow(\"\\n⚠ Failed to install agent skills\\n\"));\n return false;\n }\n}\n\n/**\n * Get the appropriate start command for the package manager.\n */\nexport function getStartCommand(pkgManager: PackageManager): string {\n return pkgManager === \"npm\" ? \"npm start\" : `${pkgManager} start`;\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,oBAAoC;CAClD,MAAM,YAAY,QAAQ,IAAI;AAE9B,KAAI,WAAW;AACb,MAAI,UAAU,WAAW,OAAO,CAAE,QAAO;AACzC,MAAI,UAAU,WAAW,OAAO,CAAE,QAAO;AACzC,MAAI,UAAU,WAAW,MAAM,CAAE,QAAO;;AAG1C,QAAO;;;;;;AAOT,eAAe,kBACb,YACA,YACe;AAEf,OAAM,MAAM,YAAY,CAAC,UAAU,EAAE;EACnC,KAAK;EACL,QAAQ;EACR,QAAQ;EACT,CAAC;;;;;AAMJ,eAAsB,oBACpB,YACkB;CAClB,MAAM,aAAa,mBAAmB;AAEtC,KAAI;AACF,UAAQ,OAAO,MACb,MAAM,KAAK,kCAAkC,WAAW,SAAS,CAClE;AAED,QAAM,kBAAkB,YAAY,WAAW;AAE/C,UAAQ,OAAO,MACb,MAAM,MAAM,6CAA6C,CAC1D;AAED,SAAO;UACA,OAAO;AACd,UAAQ,OAAO,MAAM,MAAM,OAAO,uCAAuC,CAAC;AAC1E,UAAQ,OAAO,MAAM,MAAM,IAAI,8BAA8B,CAAC;AAC9D,UAAQ,OAAO,MAAM,MAAM,KAAK,QAAQ,WAAW,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;AACzE,UAAQ,OAAO,MAAM,MAAM,KAAK,KAAK,WAAW,cAAc,CAAC;AAC/D,SAAO;;;;;;;AAQX,eAAsB,mBAAmB,YAAsC;AAC7E,KAAI;AACF,UAAQ,OAAO,MAAM,MAAM,KAAK,sCAAsC,CAAC;EAEvE,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,SAAS;EACnD,MAAM,SAAS,MAAM,QAAQ,aAAa;AAE1C,OAAK,MAAM,YAAY,CAAC,kBAAkB,iBAAiB,EAAE;AAC3D,SAAM,MAAM,KAAK,KAAK,YAAY,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACjE,QAAK,MAAM,SAAS,OAClB,OAAM,GACJ,KAAK,KAAK,cAAc,MAAM,EAC9B,KAAK,KAAK,YAAY,UAAU,MAAM,EACtC,EAAE,WAAW,MAAM,CACpB;;AAIL,UAAQ,OAAO,MAAM,MAAM,MAAM,mCAAmC,CAAC;AACrE,SAAO;UACA,OAAO;AACd,UAAQ,OAAO,MAAM,MAAM,OAAO,uCAAuC,CAAC;AAC1E,SAAO;;;;;;AAOX,SAAgB,gBAAgB,YAAoC;AAClE,QAAO,eAAe,QAAQ,cAAc,GAAG,WAAW"}
|
package/package.json
CHANGED
package/tsdown.config.ts
CHANGED
package/dist/detectAgent.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
|
|
5
|
-
//#region src/detectAgent.ts
|
|
6
|
-
/**
|
|
7
|
-
* Detect which AI coding agents are installed on the system.
|
|
8
|
-
* Returns an array of detected agent names.
|
|
9
|
-
*/
|
|
10
|
-
async function detectInstalledAgents() {
|
|
11
|
-
const detected = [];
|
|
12
|
-
try {
|
|
13
|
-
await access(path.join(process.cwd(), ".cursor"));
|
|
14
|
-
detected.push("cursor");
|
|
15
|
-
} catch {}
|
|
16
|
-
try {
|
|
17
|
-
await access(path.join(process.cwd(), ".vscode"));
|
|
18
|
-
detected.push("vscode");
|
|
19
|
-
} catch {
|
|
20
|
-
try {
|
|
21
|
-
await access(path.join(process.cwd(), ".github/copilot"));
|
|
22
|
-
detected.push("vscode");
|
|
23
|
-
} catch {}
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
await access(path.join(os.homedir(), ".claude"));
|
|
27
|
-
detected.push("claude");
|
|
28
|
-
} catch {}
|
|
29
|
-
try {
|
|
30
|
-
await access(path.join(process.cwd(), ".opencode"));
|
|
31
|
-
detected.push("opencode");
|
|
32
|
-
} catch {
|
|
33
|
-
try {
|
|
34
|
-
await access(path.join(process.cwd(), "AGENTS.md"));
|
|
35
|
-
detected.push("opencode");
|
|
36
|
-
} catch {}
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
await access(path.join(os.homedir(), ".windsurf"));
|
|
40
|
-
detected.push("windsurf");
|
|
41
|
-
} catch {}
|
|
42
|
-
return detected;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Get agent choices sorted by detection (detected agents first, then others).
|
|
46
|
-
*/
|
|
47
|
-
async function getAgentChoices() {
|
|
48
|
-
const detected = await detectInstalledAgents();
|
|
49
|
-
return [
|
|
50
|
-
{
|
|
51
|
-
title: "Cursor",
|
|
52
|
-
value: "cursor"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
title: "VS Code Copilot",
|
|
56
|
-
value: "vscode"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
title: "Claude Code",
|
|
60
|
-
value: "claude"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
title: "OpenCode",
|
|
64
|
-
value: "opencode"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
title: "Windsurf",
|
|
68
|
-
value: "windsurf"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
title: "All agents",
|
|
72
|
-
value: "all"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
title: "Skip",
|
|
76
|
-
value: "skip"
|
|
77
|
-
}
|
|
78
|
-
].sort((a, b) => {
|
|
79
|
-
const aDetected = detected.includes(a.value);
|
|
80
|
-
const bDetected = detected.includes(b.value);
|
|
81
|
-
if (aDetected && !bDetected) return -1;
|
|
82
|
-
if (!aDetected && bDetected) return 1;
|
|
83
|
-
return 0;
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
//#endregion
|
|
88
|
-
export { getAgentChoices };
|
|
89
|
-
//# sourceMappingURL=detectAgent.js.map
|
package/dist/detectAgent.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"detectAgent.js","names":["detected: string[]"],"sources":["../src/detectAgent.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/**\n * Detect which AI coding agents are installed on the system.\n * Returns an array of detected agent names.\n */\nexport async function detectInstalledAgents(): Promise<string[]> {\n const detected: string[] = [];\n\n // Check for Cursor (project-level .cursor directory)\n try {\n await access(path.join(process.cwd(), \".cursor\"));\n detected.push(\"cursor\");\n } catch {\n // Not found\n }\n\n // Check for VS Code / Copilot (.vscode directory or .github/copilot)\n try {\n await access(path.join(process.cwd(), \".vscode\"));\n detected.push(\"vscode\");\n } catch {\n try {\n await access(path.join(process.cwd(), \".github/copilot\"));\n detected.push(\"vscode\");\n } catch {\n // Not found\n }\n }\n\n // Check for Claude Code (user-level ~/.claude directory)\n try {\n await access(path.join(os.homedir(), \".claude\"));\n detected.push(\"claude\");\n } catch {\n // Not found\n }\n\n // Check for OpenCode (project-level .opencode directory or AGENTS.md)\n try {\n await access(path.join(process.cwd(), \".opencode\"));\n detected.push(\"opencode\");\n } catch {\n try {\n await access(path.join(process.cwd(), \"AGENTS.md\"));\n detected.push(\"opencode\");\n } catch {\n // Not found\n }\n }\n\n // Check for Windsurf (user-level ~/.windsurf directory)\n try {\n await access(path.join(os.homedir(), \".windsurf\"));\n detected.push(\"windsurf\");\n } catch {\n // Not found\n }\n\n return detected;\n}\n\n/**\n * Get agent choices sorted by detection (detected agents first, then others).\n */\nexport async function getAgentChoices() {\n const detected = await detectInstalledAgents();\n\n // Define all available agents\n const allAgents = [\n { title: \"Cursor\", value: \"cursor\" },\n { title: \"VS Code Copilot\", value: \"vscode\" },\n { title: \"Claude Code\", value: \"claude\" },\n { title: \"OpenCode\", value: \"opencode\" },\n { title: \"Windsurf\", value: \"windsurf\" },\n { title: \"All agents\", value: \"all\" },\n { title: \"Skip\", value: \"skip\" },\n ];\n\n // Sort: detected agents first, then others\n const sorted = allAgents.sort((a, b) => {\n const aDetected = detected.includes(a.value);\n const bDetected = detected.includes(b.value);\n\n if (aDetected && !bDetected) return -1;\n if (!aDetected && bDetected) return 1;\n return 0;\n });\n\n return sorted;\n}\n"],"mappings":";;;;;;;;;AAQA,eAAsB,wBAA2C;CAC/D,MAAMA,WAAqB,EAAE;AAG7B,KAAI;AACF,QAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,CAAC;AACjD,WAAS,KAAK,SAAS;SACjB;AAKR,KAAI;AACF,QAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,EAAE,UAAU,CAAC;AACjD,WAAS,KAAK,SAAS;SACjB;AACN,MAAI;AACF,SAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,EAAE,kBAAkB,CAAC;AACzD,YAAS,KAAK,SAAS;UACjB;;AAMV,KAAI;AACF,QAAM,OAAO,KAAK,KAAK,GAAG,SAAS,EAAE,UAAU,CAAC;AAChD,WAAS,KAAK,SAAS;SACjB;AAKR,KAAI;AACF,QAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,EAAE,YAAY,CAAC;AACnD,WAAS,KAAK,WAAW;SACnB;AACN,MAAI;AACF,SAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,EAAE,YAAY,CAAC;AACnD,YAAS,KAAK,WAAW;UACnB;;AAMV,KAAI;AACF,QAAM,OAAO,KAAK,KAAK,GAAG,SAAS,EAAE,YAAY,CAAC;AAClD,WAAS,KAAK,WAAW;SACnB;AAIR,QAAO;;;;;AAMT,eAAsB,kBAAkB;CACtC,MAAM,WAAW,MAAM,uBAAuB;AAuB9C,QApBkB;EAChB;GAAE,OAAO;GAAU,OAAO;GAAU;EACpC;GAAE,OAAO;GAAmB,OAAO;GAAU;EAC7C;GAAE,OAAO;GAAe,OAAO;GAAU;EACzC;GAAE,OAAO;GAAY,OAAO;GAAY;EACxC;GAAE,OAAO;GAAY,OAAO;GAAY;EACxC;GAAE,OAAO;GAAc,OAAO;GAAO;EACrC;GAAE,OAAO;GAAQ,OAAO;GAAQ;EACjC,CAGwB,MAAM,GAAG,MAAM;EACtC,MAAM,YAAY,SAAS,SAAS,EAAE,MAAM;EAC5C,MAAM,YAAY,SAAS,SAAS,EAAE,MAAM;AAE5C,MAAI,aAAa,CAAC,UAAW,QAAO;AACpC,MAAI,CAAC,aAAa,UAAW,QAAO;AACpC,SAAO;GACP"}
|