pulse-protocol 0.9.3

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.
@@ -0,0 +1,94 @@
1
+ import { D as DetectorResult, S as ScanInput } from '../types-BpZyHhFT.cjs';
2
+
3
+ interface XPost {
4
+ id: string;
5
+ text: string;
6
+ created_at: number;
7
+ reply_count: number;
8
+ like_count: number;
9
+ retweet_count: number;
10
+ in_reply_to_id?: string;
11
+ reply_delta_ms?: number;
12
+ }
13
+
14
+ /**
15
+ * Cadence detector.
16
+ *
17
+ * Autonomous agents post at regular intervals, at all hours of the day, and
18
+ * show no business-hours bias. Humans cluster posts in their waking hours
19
+ * and go quiet during sleep.
20
+ *
21
+ * Signals analyzed:
22
+ * - Hour-of-day distribution entropy (high entropy = agent-like)
23
+ * - Business-hours ratio (0.4-0.8 is typical for humans, <0.3 for agents)
24
+ * - Interval-between-posts standard deviation (agents = low variance)
25
+ * - Sleep-window detection (5am-9am local dead zone = human)
26
+ */
27
+ declare function cadenceDetector(posts: XPost[]): DetectorResult;
28
+
29
+ interface HeliusTransaction {
30
+ signature: string;
31
+ timestamp: number;
32
+ fee: number;
33
+ programs: string[];
34
+ type: string;
35
+ }
36
+
37
+ /**
38
+ * On-chain cadence detector.
39
+ *
40
+ * Autonomous agents transact in bursts when triggered by external events,
41
+ * and show either perfectly regular cron-like activity or reactive bursts.
42
+ * Humans transact sporadically with long gaps and clustered around waking
43
+ * hours.
44
+ *
45
+ * Signals analyzed:
46
+ * - Transaction time-of-day distribution
47
+ * - Burst detection (many txs within seconds = bot)
48
+ * - Program diversity (agents hit few programs, humans hit many)
49
+ * - Fee behavior (agents pay consistent priority fees)
50
+ */
51
+ declare function onchainDetector(txs: HeliusTransaction[]): DetectorResult;
52
+
53
+ /**
54
+ * Voice consistency detector.
55
+ *
56
+ * Humans make typos that evolve over time, reuse unique phrases, drift in
57
+ * tone, and use emoji inconsistently. Autonomous agents have frozen voice:
58
+ * stable vocabulary, no typos, consistent phrase templates, predictable
59
+ * emoji usage.
60
+ *
61
+ * Returns a HIGHER score for more consistent (agent-like) voice.
62
+ */
63
+ declare function voiceDetector(posts: XPost[]): DetectorResult;
64
+
65
+ /**
66
+ * Timing anomaly detector.
67
+ *
68
+ * Returns a count of anomalies rather than a 0-100 score. The aggregator
69
+ * normalizes the count to a score (0 anomalies = 100, 10+ = 0).
70
+ *
71
+ * Anomalies flagged:
72
+ * - Sub-second response times (bot-like)
73
+ * - Perfect regularity in posting intervals (cron job signature)
74
+ * - Simultaneous X and on-chain activity within 1 second of each other
75
+ * - Posts during 4am-7am UTC with high frequency
76
+ * - Absolute silence gaps > 48h followed by burst activity
77
+ */
78
+ declare function timingDetector(posts: XPost[], txs: HeliusTransaction[]): DetectorResult;
79
+
80
+ /**
81
+ * Cross-source correlation detector.
82
+ *
83
+ * The hardest signal to fake: do the X posting timeline and the on-chain
84
+ * activity timeline correlate? An autonomous agent that is actually running
85
+ * the claimed wallet will show synchronized bursts. A human operator
86
+ * manually posting will show uncorrelated timelines.
87
+ *
88
+ * Score is 0-100 where higher = stronger correlation = more autonomous.
89
+ * Uncorrelated timelines (two independent actors) score near 0, which
90
+ * usually flips the verdict to HYBRID.
91
+ */
92
+ declare function correlationDetector(posts: XPost[], txs: HeliusTransaction[], target: ScanInput): DetectorResult;
93
+
94
+ export { cadenceDetector, correlationDetector, onchainDetector, timingDetector, voiceDetector };
@@ -0,0 +1,94 @@
1
+ import { D as DetectorResult, S as ScanInput } from '../types-BpZyHhFT.js';
2
+
3
+ interface XPost {
4
+ id: string;
5
+ text: string;
6
+ created_at: number;
7
+ reply_count: number;
8
+ like_count: number;
9
+ retweet_count: number;
10
+ in_reply_to_id?: string;
11
+ reply_delta_ms?: number;
12
+ }
13
+
14
+ /**
15
+ * Cadence detector.
16
+ *
17
+ * Autonomous agents post at regular intervals, at all hours of the day, and
18
+ * show no business-hours bias. Humans cluster posts in their waking hours
19
+ * and go quiet during sleep.
20
+ *
21
+ * Signals analyzed:
22
+ * - Hour-of-day distribution entropy (high entropy = agent-like)
23
+ * - Business-hours ratio (0.4-0.8 is typical for humans, <0.3 for agents)
24
+ * - Interval-between-posts standard deviation (agents = low variance)
25
+ * - Sleep-window detection (5am-9am local dead zone = human)
26
+ */
27
+ declare function cadenceDetector(posts: XPost[]): DetectorResult;
28
+
29
+ interface HeliusTransaction {
30
+ signature: string;
31
+ timestamp: number;
32
+ fee: number;
33
+ programs: string[];
34
+ type: string;
35
+ }
36
+
37
+ /**
38
+ * On-chain cadence detector.
39
+ *
40
+ * Autonomous agents transact in bursts when triggered by external events,
41
+ * and show either perfectly regular cron-like activity or reactive bursts.
42
+ * Humans transact sporadically with long gaps and clustered around waking
43
+ * hours.
44
+ *
45
+ * Signals analyzed:
46
+ * - Transaction time-of-day distribution
47
+ * - Burst detection (many txs within seconds = bot)
48
+ * - Program diversity (agents hit few programs, humans hit many)
49
+ * - Fee behavior (agents pay consistent priority fees)
50
+ */
51
+ declare function onchainDetector(txs: HeliusTransaction[]): DetectorResult;
52
+
53
+ /**
54
+ * Voice consistency detector.
55
+ *
56
+ * Humans make typos that evolve over time, reuse unique phrases, drift in
57
+ * tone, and use emoji inconsistently. Autonomous agents have frozen voice:
58
+ * stable vocabulary, no typos, consistent phrase templates, predictable
59
+ * emoji usage.
60
+ *
61
+ * Returns a HIGHER score for more consistent (agent-like) voice.
62
+ */
63
+ declare function voiceDetector(posts: XPost[]): DetectorResult;
64
+
65
+ /**
66
+ * Timing anomaly detector.
67
+ *
68
+ * Returns a count of anomalies rather than a 0-100 score. The aggregator
69
+ * normalizes the count to a score (0 anomalies = 100, 10+ = 0).
70
+ *
71
+ * Anomalies flagged:
72
+ * - Sub-second response times (bot-like)
73
+ * - Perfect regularity in posting intervals (cron job signature)
74
+ * - Simultaneous X and on-chain activity within 1 second of each other
75
+ * - Posts during 4am-7am UTC with high frequency
76
+ * - Absolute silence gaps > 48h followed by burst activity
77
+ */
78
+ declare function timingDetector(posts: XPost[], txs: HeliusTransaction[]): DetectorResult;
79
+
80
+ /**
81
+ * Cross-source correlation detector.
82
+ *
83
+ * The hardest signal to fake: do the X posting timeline and the on-chain
84
+ * activity timeline correlate? An autonomous agent that is actually running
85
+ * the claimed wallet will show synchronized bursts. A human operator
86
+ * manually posting will show uncorrelated timelines.
87
+ *
88
+ * Score is 0-100 where higher = stronger correlation = more autonomous.
89
+ * Uncorrelated timelines (two independent actors) score near 0, which
90
+ * usually flips the verdict to HYBRID.
91
+ */
92
+ declare function correlationDetector(posts: XPost[], txs: HeliusTransaction[], target: ScanInput): DetectorResult;
93
+
94
+ export { cadenceDetector, correlationDetector, onchainDetector, timingDetector, voiceDetector };
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/detectors/cadence.ts
4
+ function cadenceDetector(posts) {
5
+ const notes = [];
6
+ if (posts.length < 20) {
7
+ notes.push(`only ${posts.length} posts available, low confidence`);
8
+ return {
9
+ detector: "cadence",
10
+ score: 50,
11
+ samples: posts.length,
12
+ notes,
13
+ runtime_ms: 0
14
+ };
15
+ }
16
+ const hourBuckets = new Array(24).fill(0);
17
+ for (const p of posts) {
18
+ const hour = new Date(p.created_at * 1e3).getUTCHours();
19
+ hourBuckets[hour]++;
20
+ }
21
+ const total = posts.length;
22
+ let entropy = 0;
23
+ for (const count of hourBuckets) {
24
+ if (count > 0) {
25
+ const p = count / total;
26
+ entropy -= p * Math.log2(p);
27
+ }
28
+ }
29
+ const entropyScore = entropy / Math.log2(24) * 100;
30
+ notes.push(`hour entropy: ${entropy.toFixed(2)} bits (${entropyScore.toFixed(0)}/100)`);
31
+ const businessHours = hourBuckets.slice(9, 18).reduce((a, b) => a + b, 0);
32
+ const businessRatio = businessHours / total;
33
+ notes.push(`business-hours ratio: ${(businessRatio * 100).toFixed(1)}%`);
34
+ const sleepWindow = hourBuckets.slice(4, 8).reduce((a, b) => a + b, 0);
35
+ const sleepRatio = sleepWindow / total;
36
+ const hasDeadZone = sleepRatio < 0.05;
37
+ notes.push(
38
+ hasDeadZone ? `sleep window detected (${(sleepRatio * 100).toFixed(1)}% posts in 4-8am)` : `no sleep window (${(sleepRatio * 100).toFixed(1)}% posts in 4-8am)`
39
+ );
40
+ const sorted = [...posts].sort((a, b) => a.created_at - b.created_at);
41
+ const intervals = [];
42
+ for (let i = 1; i < sorted.length; i++) {
43
+ intervals.push(sorted[i].created_at - sorted[i - 1].created_at);
44
+ }
45
+ const meanInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
46
+ const variance = intervals.reduce((a, b) => a + (b - meanInterval) ** 2, 0) / intervals.length;
47
+ const cv = Math.sqrt(variance) / meanInterval;
48
+ notes.push(`interval CV: ${cv.toFixed(2)} (lower = more regular = agent-like)`);
49
+ let score = 0;
50
+ score += entropyScore * 0.4;
51
+ score += (1 - Math.abs(businessRatio - 0.15)) * 100 * 0.25;
52
+ score += (hasDeadZone ? 0 : 100) * 0.2;
53
+ score += Math.max(0, 100 - cv * 30) * 0.15;
54
+ return {
55
+ detector: "cadence",
56
+ score: Math.round(Math.max(0, Math.min(100, score))),
57
+ samples: posts.length,
58
+ notes,
59
+ runtime_ms: 0
60
+ };
61
+ }
62
+
63
+ // src/detectors/onchain.ts
64
+ function onchainDetector(txs) {
65
+ const notes = [];
66
+ if (txs.length < 10) {
67
+ notes.push(`only ${txs.length} transactions, low confidence`);
68
+ return {
69
+ detector: "onchain",
70
+ score: 50,
71
+ samples: txs.length,
72
+ notes,
73
+ runtime_ms: 0
74
+ };
75
+ }
76
+ const hourBuckets = new Array(24).fill(0);
77
+ for (const t of txs) {
78
+ const hour = new Date(t.timestamp * 1e3).getUTCHours();
79
+ hourBuckets[hour]++;
80
+ }
81
+ let entropy = 0;
82
+ for (const count of hourBuckets) {
83
+ if (count > 0) {
84
+ const p = count / txs.length;
85
+ entropy -= p * Math.log2(p);
86
+ }
87
+ }
88
+ const entropyScore = entropy / Math.log2(24) * 100;
89
+ notes.push(`onchain hour entropy: ${entropy.toFixed(2)} bits`);
90
+ const sorted = [...txs].sort((a, b) => a.timestamp - b.timestamp);
91
+ let bursts = 0;
92
+ for (let i = 0; i < sorted.length - 3; i++) {
93
+ const window = sorted[i + 3].timestamp - sorted[i].timestamp;
94
+ if (window <= 10) bursts++;
95
+ }
96
+ const burstRatio = bursts / Math.max(1, sorted.length - 3);
97
+ notes.push(`burst clusters: ${bursts} (${(burstRatio * 100).toFixed(1)}% of windows)`);
98
+ const programs = /* @__PURE__ */ new Set();
99
+ for (const t of txs) {
100
+ for (const p of t.programs ?? []) {
101
+ programs.add(p);
102
+ }
103
+ }
104
+ const diversityScore = Math.min(100, programs.size * 10);
105
+ notes.push(`${programs.size} unique programs touched`);
106
+ const fees = txs.map((t) => t.fee ?? 5e3).filter((f) => f > 0);
107
+ const meanFee = fees.reduce((a, b) => a + b, 0) / Math.max(1, fees.length);
108
+ const feeStd = Math.sqrt(
109
+ fees.reduce((a, b) => a + (b - meanFee) ** 2, 0) / Math.max(1, fees.length)
110
+ );
111
+ const feeCV = meanFee > 0 ? feeStd / meanFee : 0;
112
+ notes.push(`fee CV: ${feeCV.toFixed(2)}`);
113
+ let score = 0;
114
+ score += entropyScore * 0.35;
115
+ score += Math.min(100, burstRatio * 300) * 0.3;
116
+ score += (100 - diversityScore) * 0.2;
117
+ score += Math.max(0, 100 - feeCV * 50) * 0.15;
118
+ return {
119
+ detector: "onchain",
120
+ score: Math.round(Math.max(0, Math.min(100, score))),
121
+ samples: txs.length,
122
+ notes,
123
+ runtime_ms: 0
124
+ };
125
+ }
126
+
127
+ // src/detectors/voice.ts
128
+ function voiceDetector(posts) {
129
+ const notes = [];
130
+ if (posts.length < 15) {
131
+ notes.push(`only ${posts.length} posts, low confidence`);
132
+ return {
133
+ detector: "voice",
134
+ score: 50,
135
+ samples: posts.length,
136
+ notes,
137
+ runtime_ms: 0
138
+ };
139
+ }
140
+ const allTokens = [];
141
+ for (const p of posts) {
142
+ const tokens = tokenize(p.text);
143
+ allTokens.push(...tokens);
144
+ }
145
+ const uniqueRatio = new Set(allTokens).size / allTokens.length;
146
+ notes.push(`lexical diversity: ${uniqueRatio.toFixed(3)}`);
147
+ let typoCount = 0;
148
+ for (const p of posts) {
149
+ if (/([a-z])\1{2,}/i.test(p.text)) typoCount++;
150
+ if (/\b[bcdfghjklmnpqrstvwxyz]{4,}\b/i.test(p.text)) typoCount++;
151
+ }
152
+ const typoRate = typoCount / posts.length;
153
+ notes.push(`typo rate: ${(typoRate * 100).toFixed(1)}%`);
154
+ const trigrams = /* @__PURE__ */ new Map();
155
+ for (const p of posts) {
156
+ const tokens = tokenize(p.text);
157
+ for (let i = 0; i < tokens.length - 2; i++) {
158
+ const key = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`;
159
+ trigrams.set(key, (trigrams.get(key) ?? 0) + 1);
160
+ }
161
+ }
162
+ const repeated = Array.from(trigrams.values()).filter((v) => v > 1).length;
163
+ const repetitionRate = repeated / Math.max(1, trigrams.size);
164
+ notes.push(`phrase repetition: ${(repetitionRate * 100).toFixed(1)}%`);
165
+ const emojiRegex = /[\p{Emoji_Presentation}]/u;
166
+ const withEmoji = posts.filter((p) => emojiRegex.test(p.text)).length;
167
+ const emojiRatio = withEmoji / posts.length;
168
+ const emojiConsistency = Math.abs(emojiRatio - 0.5) * 2;
169
+ notes.push(`emoji ratio: ${(emojiRatio * 100).toFixed(0)}%`);
170
+ let score = 0;
171
+ score += (1 - uniqueRatio) * 100 * 0.35;
172
+ score += (1 - typoRate) * 100 * 0.25;
173
+ score += repetitionRate * 100 * 0.2;
174
+ score += emojiConsistency * 100 * 0.2;
175
+ return {
176
+ detector: "voice",
177
+ score: Math.round(Math.max(0, Math.min(100, score))),
178
+ samples: posts.length,
179
+ notes,
180
+ runtime_ms: 0
181
+ };
182
+ }
183
+ function tokenize(text) {
184
+ return text.toLowerCase().replace(/https?:\/\/\S+/g, "").replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 1);
185
+ }
186
+
187
+ // src/detectors/timing.ts
188
+ function timingDetector(posts, txs) {
189
+ const notes = [];
190
+ let anomalies = 0;
191
+ const quickReplies = posts.filter(
192
+ (p) => p.in_reply_to_id && p.reply_delta_ms !== void 0 && p.reply_delta_ms < 1e3
193
+ ).length;
194
+ if (quickReplies > 0) {
195
+ anomalies += Math.min(3, quickReplies);
196
+ notes.push(`${quickReplies} sub-second reply posts`);
197
+ }
198
+ const sortedPosts = [...posts].sort((a, b) => a.created_at - b.created_at);
199
+ let regular = 0;
200
+ for (let i = 2; i < sortedPosts.length; i++) {
201
+ const d1 = sortedPosts[i].created_at - sortedPosts[i - 1].created_at;
202
+ const d2 = sortedPosts[i - 1].created_at - sortedPosts[i - 2].created_at;
203
+ if (Math.abs(d1 - d2) < 5 && d1 > 0) regular++;
204
+ }
205
+ if (regular >= 5) {
206
+ anomalies++;
207
+ notes.push(`${regular} posts with cron-like regularity`);
208
+ }
209
+ let simultaneous = 0;
210
+ const txTimes = new Set(txs.map((t) => t.timestamp));
211
+ for (const p of posts) {
212
+ for (let offset = -1; offset <= 1; offset++) {
213
+ if (txTimes.has(p.created_at + offset)) {
214
+ simultaneous++;
215
+ break;
216
+ }
217
+ }
218
+ }
219
+ if (simultaneous >= 3) {
220
+ anomalies++;
221
+ notes.push(`${simultaneous} simultaneous X+onchain events (within 1s)`);
222
+ }
223
+ const deadHour = posts.filter((p) => {
224
+ const h = new Date(p.created_at * 1e3).getUTCHours();
225
+ return h >= 4 && h < 7;
226
+ }).length;
227
+ if (deadHour / posts.length > 0.1) {
228
+ anomalies++;
229
+ notes.push(`${deadHour} posts during 4-7am UTC dead zone`);
230
+ }
231
+ let burstAfterSilence = 0;
232
+ for (let i = 1; i < sortedPosts.length; i++) {
233
+ const gap = sortedPosts[i].created_at - sortedPosts[i - 1].created_at;
234
+ if (gap > 48 * 3600) {
235
+ const nextFive = sortedPosts.slice(i, i + 5);
236
+ if (nextFive.length === 5) {
237
+ const span = nextFive[4].created_at - nextFive[0].created_at;
238
+ if (span < 600) burstAfterSilence++;
239
+ }
240
+ }
241
+ }
242
+ if (burstAfterSilence > 0) {
243
+ anomalies += burstAfterSilence;
244
+ notes.push(`${burstAfterSilence} silence-then-burst patterns`);
245
+ }
246
+ if (anomalies === 0) {
247
+ notes.push("no timing anomalies detected");
248
+ }
249
+ return {
250
+ detector: "timing",
251
+ score: anomalies,
252
+ // raw anomaly count, aggregator normalizes
253
+ samples: posts.length + txs.length,
254
+ notes,
255
+ runtime_ms: 0
256
+ };
257
+ }
258
+
259
+ // src/detectors/correlation.ts
260
+ function correlationDetector(posts, txs, target) {
261
+ const notes = [];
262
+ if (posts.length < 10 || txs.length < 10) {
263
+ notes.push(`insufficient data (posts=${posts.length}, txs=${txs.length})`);
264
+ return {
265
+ detector: "correlation",
266
+ score: 50,
267
+ samples: posts.length + txs.length,
268
+ notes,
269
+ runtime_ms: 0
270
+ };
271
+ }
272
+ const postsByHour = /* @__PURE__ */ new Map();
273
+ const txsByHour = /* @__PURE__ */ new Map();
274
+ const bucket = (t) => Math.floor(t / 3600);
275
+ for (const p of posts) {
276
+ const b = bucket(p.created_at);
277
+ postsByHour.set(b, (postsByHour.get(b) ?? 0) + 1);
278
+ }
279
+ for (const t of txs) {
280
+ const b = bucket(t.timestamp);
281
+ txsByHour.set(b, (txsByHour.get(b) ?? 0) + 1);
282
+ }
283
+ const allHours = /* @__PURE__ */ new Set([...postsByHour.keys(), ...txsByHour.keys()]);
284
+ const postVec = [];
285
+ const txVec = [];
286
+ for (const h of allHours) {
287
+ postVec.push(postsByHour.get(h) ?? 0);
288
+ txVec.push(txsByHour.get(h) ?? 0);
289
+ }
290
+ const pearson = pearsonCorrelation(postVec, txVec);
291
+ notes.push(`pearson r = ${pearson.toFixed(3)}`);
292
+ const postSpan = maxOf(posts.map((p) => p.created_at)) - minOf(posts.map((p) => p.created_at));
293
+ const txSpan = maxOf(txs.map((t) => t.timestamp)) - minOf(txs.map((t) => t.timestamp));
294
+ const postStart = minOf(posts.map((p) => p.created_at));
295
+ const txStart = minOf(txs.map((t) => t.timestamp));
296
+ const overlapStart = Math.max(postStart, txStart);
297
+ const overlapEnd = Math.min(postStart + postSpan, txStart + txSpan);
298
+ const overlap = Math.max(0, overlapEnd - overlapStart);
299
+ const overlapRatio = overlap / Math.max(postSpan, txSpan, 1);
300
+ notes.push(`timeline overlap: ${(overlapRatio * 100).toFixed(1)}%`);
301
+ const corrScore = Math.max(0, pearson) * 100;
302
+ const overlapScore = overlapRatio * 100;
303
+ const score = corrScore * 0.7 + overlapScore * 0.3;
304
+ notes.push(`target: @${target.handle}, ${target.wallet.slice(0, 8)}...`);
305
+ return {
306
+ detector: "correlation",
307
+ score: Math.round(Math.max(0, Math.min(100, score))),
308
+ samples: posts.length + txs.length,
309
+ notes,
310
+ runtime_ms: 0
311
+ };
312
+ }
313
+ function pearsonCorrelation(x, y) {
314
+ if (x.length !== y.length || x.length === 0) return 0;
315
+ const n = x.length;
316
+ const meanX = x.reduce((a, b) => a + b, 0) / n;
317
+ const meanY = y.reduce((a, b) => a + b, 0) / n;
318
+ let num = 0;
319
+ let denX = 0;
320
+ let denY = 0;
321
+ for (let i = 0; i < n; i++) {
322
+ const dx = x[i] - meanX;
323
+ const dy = y[i] - meanY;
324
+ num += dx * dy;
325
+ denX += dx * dx;
326
+ denY += dy * dy;
327
+ }
328
+ const denom = Math.sqrt(denX * denY);
329
+ return denom === 0 ? 0 : num / denom;
330
+ }
331
+ function minOf(arr) {
332
+ return arr.reduce((a, b) => a < b ? a : b, arr[0] ?? 0);
333
+ }
334
+ function maxOf(arr) {
335
+ return arr.reduce((a, b) => a > b ? a : b, arr[0] ?? 0);
336
+ }
337
+ export {
338
+ cadenceDetector,
339
+ correlationDetector,
340
+ onchainDetector,
341
+ timingDetector,
342
+ voiceDetector
343
+ };
344
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/detectors/cadence.ts","../../src/detectors/onchain.ts","../../src/detectors/voice.ts","../../src/detectors/timing.ts","../../src/detectors/correlation.ts"],"sourcesContent":["import type { DetectorResult } from \"../core/types.js\";\nimport type { XPost } from \"../sources/x.js\";\n\n/**\n * Cadence detector.\n *\n * Autonomous agents post at regular intervals, at all hours of the day, and\n * show no business-hours bias. Humans cluster posts in their waking hours\n * and go quiet during sleep.\n *\n * Signals analyzed:\n * - Hour-of-day distribution entropy (high entropy = agent-like)\n * - Business-hours ratio (0.4-0.8 is typical for humans, <0.3 for agents)\n * - Interval-between-posts standard deviation (agents = low variance)\n * - Sleep-window detection (5am-9am local dead zone = human)\n */\nexport function cadenceDetector(posts: XPost[]): DetectorResult {\n const notes: string[] = [];\n\n if (posts.length < 20) {\n notes.push(`only ${posts.length} posts available, low confidence`);\n return {\n detector: \"cadence\",\n score: 50,\n samples: posts.length,\n notes,\n runtime_ms: 0,\n };\n }\n\n // Bucket by hour of day (UTC).\n const hourBuckets = new Array(24).fill(0);\n for (const p of posts) {\n const hour = new Date(p.created_at * 1000).getUTCHours();\n hourBuckets[hour]++;\n }\n\n // Shannon entropy of the distribution (0 = all same hour, log2(24) = uniform).\n const total = posts.length;\n let entropy = 0;\n for (const count of hourBuckets) {\n if (count > 0) {\n const p = count / total;\n entropy -= p * Math.log2(p);\n }\n }\n const entropyScore = (entropy / Math.log2(24)) * 100;\n notes.push(`hour entropy: ${entropy.toFixed(2)} bits (${entropyScore.toFixed(0)}/100)`);\n\n // Business hours bias (9:00 - 18:00 UTC, adjust if you have timezone).\n const businessHours = hourBuckets.slice(9, 18).reduce((a, b) => a + b, 0);\n const businessRatio = businessHours / total;\n notes.push(`business-hours ratio: ${(businessRatio * 100).toFixed(1)}%`);\n\n // Sleep window detection (4am - 8am UTC by default).\n const sleepWindow = hourBuckets.slice(4, 8).reduce((a, b) => a + b, 0);\n const sleepRatio = sleepWindow / total;\n const hasDeadZone = sleepRatio < 0.05;\n notes.push(\n hasDeadZone\n ? `sleep window detected (${(sleepRatio * 100).toFixed(1)}% posts in 4-8am)`\n : `no sleep window (${(sleepRatio * 100).toFixed(1)}% posts in 4-8am)`\n );\n\n // Interval variance.\n const sorted = [...posts].sort((a, b) => a.created_at - b.created_at);\n const intervals: number[] = [];\n for (let i = 1; i < sorted.length; i++) {\n intervals.push(sorted[i]!.created_at - sorted[i - 1]!.created_at);\n }\n const meanInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const variance =\n intervals.reduce((a, b) => a + (b - meanInterval) ** 2, 0) /\n intervals.length;\n const cv = Math.sqrt(variance) / meanInterval; // coefficient of variation\n notes.push(`interval CV: ${cv.toFixed(2)} (lower = more regular = agent-like)`);\n\n // Aggregate the four sub-signals into a 0-100 score.\n let score = 0;\n score += entropyScore * 0.4;\n score += (1 - Math.abs(businessRatio - 0.15)) * 100 * 0.25; // 15% business hours is agent-like baseline\n score += (hasDeadZone ? 0 : 100) * 0.2;\n score += Math.max(0, 100 - cv * 30) * 0.15;\n\n return {\n detector: \"cadence\",\n score: Math.round(Math.max(0, Math.min(100, score))),\n samples: posts.length,\n notes,\n runtime_ms: 0,\n };\n}\n","import type { DetectorResult } from \"../core/types.js\";\nimport type { HeliusTransaction } from \"../sources/helius.js\";\n\n/**\n * On-chain cadence detector.\n *\n * Autonomous agents transact in bursts when triggered by external events,\n * and show either perfectly regular cron-like activity or reactive bursts.\n * Humans transact sporadically with long gaps and clustered around waking\n * hours.\n *\n * Signals analyzed:\n * - Transaction time-of-day distribution\n * - Burst detection (many txs within seconds = bot)\n * - Program diversity (agents hit few programs, humans hit many)\n * - Fee behavior (agents pay consistent priority fees)\n */\nexport function onchainDetector(txs: HeliusTransaction[]): DetectorResult {\n const notes: string[] = [];\n\n if (txs.length < 10) {\n notes.push(`only ${txs.length} transactions, low confidence`);\n return {\n detector: \"onchain\",\n score: 50,\n samples: txs.length,\n notes,\n runtime_ms: 0,\n };\n }\n\n // Time-of-day distribution.\n const hourBuckets = new Array(24).fill(0);\n for (const t of txs) {\n const hour = new Date(t.timestamp * 1000).getUTCHours();\n hourBuckets[hour]++;\n }\n let entropy = 0;\n for (const count of hourBuckets) {\n if (count > 0) {\n const p = count / txs.length;\n entropy -= p * Math.log2(p);\n }\n }\n const entropyScore = (entropy / Math.log2(24)) * 100;\n notes.push(`onchain hour entropy: ${entropy.toFixed(2)} bits`);\n\n // Burst detection: how many tx clusters have >3 txs within 10 seconds?\n const sorted = [...txs].sort((a, b) => a.timestamp - b.timestamp);\n let bursts = 0;\n for (let i = 0; i < sorted.length - 3; i++) {\n const window = sorted[i + 3]!.timestamp - sorted[i]!.timestamp;\n if (window <= 10) bursts++;\n }\n const burstRatio = bursts / Math.max(1, sorted.length - 3);\n notes.push(`burst clusters: ${bursts} (${(burstRatio * 100).toFixed(1)}% of windows)`);\n\n // Program diversity: unique program IDs touched.\n const programs = new Set<string>();\n for (const t of txs) {\n for (const p of t.programs ?? []) {\n programs.add(p);\n }\n }\n const diversityScore = Math.min(100, programs.size * 10);\n notes.push(`${programs.size} unique programs touched`);\n\n // Fee consistency: std dev of priority fees (lower = bot-like).\n const fees = txs.map((t) => t.fee ?? 5000).filter((f) => f > 0);\n const meanFee = fees.reduce((a, b) => a + b, 0) / Math.max(1, fees.length);\n const feeStd = Math.sqrt(\n fees.reduce((a, b) => a + (b - meanFee) ** 2, 0) / Math.max(1, fees.length)\n );\n const feeCV = meanFee > 0 ? feeStd / meanFee : 0;\n notes.push(`fee CV: ${feeCV.toFixed(2)}`);\n\n // Aggregate.\n let score = 0;\n score += entropyScore * 0.35;\n score += Math.min(100, burstRatio * 300) * 0.3;\n score += (100 - diversityScore) * 0.2; // low diversity = more agent-like\n score += Math.max(0, 100 - feeCV * 50) * 0.15;\n\n return {\n detector: \"onchain\",\n score: Math.round(Math.max(0, Math.min(100, score))),\n samples: txs.length,\n notes,\n runtime_ms: 0,\n };\n}\n","import type { DetectorResult } from \"../core/types.js\";\nimport type { XPost } from \"../sources/x.js\";\n\n/**\n * Voice consistency detector.\n *\n * Humans make typos that evolve over time, reuse unique phrases, drift in\n * tone, and use emoji inconsistently. Autonomous agents have frozen voice:\n * stable vocabulary, no typos, consistent phrase templates, predictable\n * emoji usage.\n *\n * Returns a HIGHER score for more consistent (agent-like) voice.\n */\nexport function voiceDetector(posts: XPost[]): DetectorResult {\n const notes: string[] = [];\n\n if (posts.length < 15) {\n notes.push(`only ${posts.length} posts, low confidence`);\n return {\n detector: \"voice\",\n score: 50,\n samples: posts.length,\n notes,\n runtime_ms: 0,\n };\n }\n\n // Lexical diversity: unique tokens / total tokens.\n const allTokens: string[] = [];\n for (const p of posts) {\n const tokens = tokenize(p.text);\n allTokens.push(...tokens);\n }\n const uniqueRatio = new Set(allTokens).size / allTokens.length;\n notes.push(`lexical diversity: ${uniqueRatio.toFixed(3)}`);\n\n // Typo detection: very rough heuristic (repeated chars, no-vowel words).\n let typoCount = 0;\n for (const p of posts) {\n if (/([a-z])\\1{2,}/i.test(p.text)) typoCount++;\n if (/\\b[bcdfghjklmnpqrstvwxyz]{4,}\\b/i.test(p.text)) typoCount++;\n }\n const typoRate = typoCount / posts.length;\n notes.push(`typo rate: ${(typoRate * 100).toFixed(1)}%`);\n\n // Phrase repetition: how many 3-word sequences appear more than once?\n const trigrams = new Map<string, number>();\n for (const p of posts) {\n const tokens = tokenize(p.text);\n for (let i = 0; i < tokens.length - 2; i++) {\n const key = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`;\n trigrams.set(key, (trigrams.get(key) ?? 0) + 1);\n }\n }\n const repeated = Array.from(trigrams.values()).filter((v) => v > 1).length;\n const repetitionRate = repeated / Math.max(1, trigrams.size);\n notes.push(`phrase repetition: ${(repetitionRate * 100).toFixed(1)}%`);\n\n // Emoji consistency: ratio of posts containing any emoji.\n const emojiRegex = /[\\p{Emoji_Presentation}]/u;\n const withEmoji = posts.filter((p) => emojiRegex.test(p.text)).length;\n const emojiRatio = withEmoji / posts.length;\n const emojiConsistency = Math.abs(emojiRatio - 0.5) * 2; // 0 or 1 both = consistent\n notes.push(`emoji ratio: ${(emojiRatio * 100).toFixed(0)}%`);\n\n // Aggregate. Higher score = more agent-like (more consistent).\n let score = 0;\n score += (1 - uniqueRatio) * 100 * 0.35; // lower diversity = more agent\n score += (1 - typoRate) * 100 * 0.25; // fewer typos = more agent\n score += repetitionRate * 100 * 0.2;\n score += emojiConsistency * 100 * 0.2;\n\n return {\n detector: \"voice\",\n score: Math.round(Math.max(0, Math.min(100, score))),\n samples: posts.length,\n notes,\n runtime_ms: 0,\n };\n}\n\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/https?:\\/\\/\\S+/g, \"\")\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n}\n","import type { DetectorResult } from \"../core/types.js\";\nimport type { XPost } from \"../sources/x.js\";\nimport type { HeliusTransaction } from \"../sources/helius.js\";\n\n/**\n * Timing anomaly detector.\n *\n * Returns a count of anomalies rather than a 0-100 score. The aggregator\n * normalizes the count to a score (0 anomalies = 100, 10+ = 0).\n *\n * Anomalies flagged:\n * - Sub-second response times (bot-like)\n * - Perfect regularity in posting intervals (cron job signature)\n * - Simultaneous X and on-chain activity within 1 second of each other\n * - Posts during 4am-7am UTC with high frequency\n * - Absolute silence gaps > 48h followed by burst activity\n */\nexport function timingDetector(\n posts: XPost[],\n txs: HeliusTransaction[]\n): DetectorResult {\n const notes: string[] = [];\n let anomalies = 0;\n\n // 1. Sub-second responses (reply posted within 1s of parent).\n const quickReplies = posts.filter(\n (p) => p.in_reply_to_id && p.reply_delta_ms !== undefined && p.reply_delta_ms < 1000\n ).length;\n if (quickReplies > 0) {\n anomalies += Math.min(3, quickReplies);\n notes.push(`${quickReplies} sub-second reply posts`);\n }\n\n // 2. Perfect regularity: check if 5+ consecutive intervals have delta < 5s.\n const sortedPosts = [...posts].sort((a, b) => a.created_at - b.created_at);\n let regular = 0;\n for (let i = 2; i < sortedPosts.length; i++) {\n const d1 = sortedPosts[i]!.created_at - sortedPosts[i - 1]!.created_at;\n const d2 = sortedPosts[i - 1]!.created_at - sortedPosts[i - 2]!.created_at;\n if (Math.abs(d1 - d2) < 5 && d1 > 0) regular++;\n }\n if (regular >= 5) {\n anomalies++;\n notes.push(`${regular} posts with cron-like regularity`);\n }\n\n // 3. Simultaneous X + on-chain activity.\n let simultaneous = 0;\n const txTimes = new Set(txs.map((t) => t.timestamp));\n for (const p of posts) {\n for (let offset = -1; offset <= 1; offset++) {\n if (txTimes.has(p.created_at + offset)) {\n simultaneous++;\n break;\n }\n }\n }\n if (simultaneous >= 3) {\n anomalies++;\n notes.push(`${simultaneous} simultaneous X+onchain events (within 1s)`);\n }\n\n // 4. High frequency in 4am-7am UTC window.\n const deadHour = posts.filter((p) => {\n const h = new Date(p.created_at * 1000).getUTCHours();\n return h >= 4 && h < 7;\n }).length;\n if (deadHour / posts.length > 0.1) {\n anomalies++;\n notes.push(`${deadHour} posts during 4-7am UTC dead zone`);\n }\n\n // 5. Silence-then-burst patterns.\n let burstAfterSilence = 0;\n for (let i = 1; i < sortedPosts.length; i++) {\n const gap = sortedPosts[i]!.created_at - sortedPosts[i - 1]!.created_at;\n if (gap > 48 * 3600) {\n // Check if next 5 posts come within 10 minutes.\n const nextFive = sortedPosts.slice(i, i + 5);\n if (nextFive.length === 5) {\n const span = nextFive[4]!.created_at - nextFive[0]!.created_at;\n if (span < 600) burstAfterSilence++;\n }\n }\n }\n if (burstAfterSilence > 0) {\n anomalies += burstAfterSilence;\n notes.push(`${burstAfterSilence} silence-then-burst patterns`);\n }\n\n if (anomalies === 0) {\n notes.push(\"no timing anomalies detected\");\n }\n\n return {\n detector: \"timing\",\n score: anomalies, // raw anomaly count, aggregator normalizes\n samples: posts.length + txs.length,\n notes,\n runtime_ms: 0,\n };\n}\n","import type { DetectorResult, ScanInput } from \"../core/types.js\";\nimport type { XPost } from \"../sources/x.js\";\nimport type { HeliusTransaction } from \"../sources/helius.js\";\n\n/**\n * Cross-source correlation detector.\n *\n * The hardest signal to fake: do the X posting timeline and the on-chain\n * activity timeline correlate? An autonomous agent that is actually running\n * the claimed wallet will show synchronized bursts. A human operator\n * manually posting will show uncorrelated timelines.\n *\n * Score is 0-100 where higher = stronger correlation = more autonomous.\n * Uncorrelated timelines (two independent actors) score near 0, which\n * usually flips the verdict to HYBRID.\n */\nexport function correlationDetector(\n posts: XPost[],\n txs: HeliusTransaction[],\n target: ScanInput\n): DetectorResult {\n const notes: string[] = [];\n\n if (posts.length < 10 || txs.length < 10) {\n notes.push(`insufficient data (posts=${posts.length}, txs=${txs.length})`);\n return {\n detector: \"correlation\",\n score: 50,\n samples: posts.length + txs.length,\n notes,\n runtime_ms: 0,\n };\n }\n\n // Bin by hour, count events per bin.\n const postsByHour = new Map<number, number>();\n const txsByHour = new Map<number, number>();\n\n const bucket = (t: number) => Math.floor(t / 3600);\n\n for (const p of posts) {\n const b = bucket(p.created_at);\n postsByHour.set(b, (postsByHour.get(b) ?? 0) + 1);\n }\n for (const t of txs) {\n const b = bucket(t.timestamp);\n txsByHour.set(b, (txsByHour.get(b) ?? 0) + 1);\n }\n\n // Build aligned vectors over the union of hours.\n const allHours = new Set([...postsByHour.keys(), ...txsByHour.keys()]);\n const postVec: number[] = [];\n const txVec: number[] = [];\n for (const h of allHours) {\n postVec.push(postsByHour.get(h) ?? 0);\n txVec.push(txsByHour.get(h) ?? 0);\n }\n\n // Pearson correlation coefficient.\n const pearson = pearsonCorrelation(postVec, txVec);\n notes.push(`pearson r = ${pearson.toFixed(3)}`);\n\n // Activity window overlap: how much of the X activity window overlaps\n // with the on-chain window?\n const postSpan = maxOf(posts.map((p) => p.created_at)) - minOf(posts.map((p) => p.created_at));\n const txSpan = maxOf(txs.map((t) => t.timestamp)) - minOf(txs.map((t) => t.timestamp));\n const postStart = minOf(posts.map((p) => p.created_at));\n const txStart = minOf(txs.map((t) => t.timestamp));\n const overlapStart = Math.max(postStart, txStart);\n const overlapEnd = Math.min(postStart + postSpan, txStart + txSpan);\n const overlap = Math.max(0, overlapEnd - overlapStart);\n const overlapRatio = overlap / Math.max(postSpan, txSpan, 1);\n notes.push(`timeline overlap: ${(overlapRatio * 100).toFixed(1)}%`);\n\n // Transform pearson [-1, 1] -> [0, 100] with emphasis on positive correlation.\n const corrScore = Math.max(0, pearson) * 100;\n const overlapScore = overlapRatio * 100;\n\n const score = corrScore * 0.7 + overlapScore * 0.3;\n\n // Tag the target for debugging in CLI output.\n notes.push(`target: @${target.handle}, ${target.wallet.slice(0, 8)}...`);\n\n return {\n detector: \"correlation\",\n score: Math.round(Math.max(0, Math.min(100, score))),\n samples: posts.length + txs.length,\n notes,\n runtime_ms: 0,\n };\n}\n\nfunction pearsonCorrelation(x: number[], y: number[]): number {\n if (x.length !== y.length || x.length === 0) return 0;\n const n = x.length;\n const meanX = x.reduce((a, b) => a + b, 0) / n;\n const meanY = y.reduce((a, b) => a + b, 0) / n;\n let num = 0;\n let denX = 0;\n let denY = 0;\n for (let i = 0; i < n; i++) {\n const dx = x[i]! - meanX;\n const dy = y[i]! - meanY;\n num += dx * dy;\n denX += dx * dx;\n denY += dy * dy;\n }\n const denom = Math.sqrt(denX * denY);\n return denom === 0 ? 0 : num / denom;\n}\n\nfunction minOf(arr: number[]): number {\n return arr.reduce((a, b) => (a < b ? a : b), arr[0] ?? 0);\n}\nfunction maxOf(arr: number[]): number {\n return arr.reduce((a, b) => (a > b ? a : b), arr[0] ?? 0);\n}\n"],"mappings":";;;AAgBO,SAAS,gBAAgB,OAAgC;AAC9D,QAAM,QAAkB,CAAC;AAEzB,MAAI,MAAM,SAAS,IAAI;AACrB,UAAM,KAAK,QAAQ,MAAM,MAAM,kCAAkC;AACjE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,SAAS,MAAM;AAAA,MACf;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,cAAc,IAAI,MAAM,EAAE,EAAE,KAAK,CAAC;AACxC,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,IAAI,KAAK,EAAE,aAAa,GAAI,EAAE,YAAY;AACvD,gBAAY,IAAI;AAAA,EAClB;AAGA,QAAM,QAAQ,MAAM;AACpB,MAAI,UAAU;AACd,aAAW,SAAS,aAAa;AAC/B,QAAI,QAAQ,GAAG;AACb,YAAM,IAAI,QAAQ;AAClB,iBAAW,IAAI,KAAK,KAAK,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,QAAM,eAAgB,UAAU,KAAK,KAAK,EAAE,IAAK;AACjD,QAAM,KAAK,iBAAiB,QAAQ,QAAQ,CAAC,CAAC,UAAU,aAAa,QAAQ,CAAC,CAAC,OAAO;AAGtF,QAAM,gBAAgB,YAAY,MAAM,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACxE,QAAM,gBAAgB,gBAAgB;AACtC,QAAM,KAAK,0BAA0B,gBAAgB,KAAK,QAAQ,CAAC,CAAC,GAAG;AAGvE,QAAM,cAAc,YAAY,MAAM,GAAG,CAAC,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACrE,QAAM,aAAa,cAAc;AACjC,QAAM,cAAc,aAAa;AACjC,QAAM;AAAA,IACJ,cACI,2BAA2B,aAAa,KAAK,QAAQ,CAAC,CAAC,sBACvD,qBAAqB,aAAa,KAAK,QAAQ,CAAC,CAAC;AAAA,EACvD;AAGA,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;AACpE,QAAM,YAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,cAAU,KAAK,OAAO,CAAC,EAAG,aAAa,OAAO,IAAI,CAAC,EAAG,UAAU;AAAA,EAClE;AACA,QAAM,eAAe,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,UAAU;AACtE,QAAM,WACJ,UAAU,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,iBAAiB,GAAG,CAAC,IACzD,UAAU;AACZ,QAAM,KAAK,KAAK,KAAK,QAAQ,IAAI;AACjC,QAAM,KAAK,gBAAgB,GAAG,QAAQ,CAAC,CAAC,sCAAsC;AAG9E,MAAI,QAAQ;AACZ,WAAS,eAAe;AACxB,YAAU,IAAI,KAAK,IAAI,gBAAgB,IAAI,KAAK,MAAM;AACtD,YAAU,cAAc,IAAI,OAAO;AACnC,WAAS,KAAK,IAAI,GAAG,MAAM,KAAK,EAAE,IAAI;AAEtC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAAA,IACnD,SAAS,MAAM;AAAA,IACf;AAAA,IACA,YAAY;AAAA,EACd;AACF;;;AC1EO,SAAS,gBAAgB,KAA0C;AACxE,QAAM,QAAkB,CAAC;AAEzB,MAAI,IAAI,SAAS,IAAI;AACnB,UAAM,KAAK,QAAQ,IAAI,MAAM,+BAA+B;AAC5D,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,SAAS,IAAI;AAAA,MACb;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,cAAc,IAAI,MAAM,EAAE,EAAE,KAAK,CAAC;AACxC,aAAW,KAAK,KAAK;AACnB,UAAM,OAAO,IAAI,KAAK,EAAE,YAAY,GAAI,EAAE,YAAY;AACtD,gBAAY,IAAI;AAAA,EAClB;AACA,MAAI,UAAU;AACd,aAAW,SAAS,aAAa;AAC/B,QAAI,QAAQ,GAAG;AACb,YAAM,IAAI,QAAQ,IAAI;AACtB,iBAAW,IAAI,KAAK,KAAK,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,QAAM,eAAgB,UAAU,KAAK,KAAK,EAAE,IAAK;AACjD,QAAM,KAAK,yBAAyB,QAAQ,QAAQ,CAAC,CAAC,OAAO;AAG7D,QAAM,SAAS,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAChE,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,UAAM,SAAS,OAAO,IAAI,CAAC,EAAG,YAAY,OAAO,CAAC,EAAG;AACrD,QAAI,UAAU,GAAI;AAAA,EACpB;AACA,QAAM,aAAa,SAAS,KAAK,IAAI,GAAG,OAAO,SAAS,CAAC;AACzD,QAAM,KAAK,mBAAmB,MAAM,MAAM,aAAa,KAAK,QAAQ,CAAC,CAAC,eAAe;AAGrF,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,KAAK,KAAK;AACnB,eAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,eAAS,IAAI,CAAC;AAAA,IAChB;AAAA,EACF;AACA,QAAM,iBAAiB,KAAK,IAAI,KAAK,SAAS,OAAO,EAAE;AACvD,QAAM,KAAK,GAAG,SAAS,IAAI,0BAA0B;AAGrD,QAAM,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO,GAAI,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC;AAC9D,QAAM,UAAU,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM;AACzE,QAAM,SAAS,KAAK;AAAA,IAClB,KAAK,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,YAAY,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM;AAAA,EAC5E;AACA,QAAM,QAAQ,UAAU,IAAI,SAAS,UAAU;AAC/C,QAAM,KAAK,WAAW,MAAM,QAAQ,CAAC,CAAC,EAAE;AAGxC,MAAI,QAAQ;AACZ,WAAS,eAAe;AACxB,WAAS,KAAK,IAAI,KAAK,aAAa,GAAG,IAAI;AAC3C,YAAU,MAAM,kBAAkB;AAClC,WAAS,KAAK,IAAI,GAAG,MAAM,QAAQ,EAAE,IAAI;AAEzC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAAA,IACnD,SAAS,IAAI;AAAA,IACb;AAAA,IACA,YAAY;AAAA,EACd;AACF;;;AC7EO,SAAS,cAAc,OAAgC;AAC5D,QAAM,QAAkB,CAAC;AAEzB,MAAI,MAAM,SAAS,IAAI;AACrB,UAAM,KAAK,QAAQ,MAAM,MAAM,wBAAwB;AACvD,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,SAAS,MAAM;AAAA,MACf;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,YAAsB,CAAC;AAC7B,aAAW,KAAK,OAAO;AACrB,UAAM,SAAS,SAAS,EAAE,IAAI;AAC9B,cAAU,KAAK,GAAG,MAAM;AAAA,EAC1B;AACA,QAAM,cAAc,IAAI,IAAI,SAAS,EAAE,OAAO,UAAU;AACxD,QAAM,KAAK,sBAAsB,YAAY,QAAQ,CAAC,CAAC,EAAE;AAGzD,MAAI,YAAY;AAChB,aAAW,KAAK,OAAO;AACrB,QAAI,iBAAiB,KAAK,EAAE,IAAI,EAAG;AACnC,QAAI,mCAAmC,KAAK,EAAE,IAAI,EAAG;AAAA,EACvD;AACA,QAAM,WAAW,YAAY,MAAM;AACnC,QAAM,KAAK,eAAe,WAAW,KAAK,QAAQ,CAAC,CAAC,GAAG;AAGvD,QAAM,WAAW,oBAAI,IAAoB;AACzC,aAAW,KAAK,OAAO;AACrB,UAAM,SAAS,SAAS,EAAE,IAAI;AAC9B,aAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,YAAM,MAAM,GAAG,OAAO,CAAC,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;AAC1D,eAAS,IAAI,MAAM,SAAS,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,IAChD;AAAA,EACF;AACA,QAAM,WAAW,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE;AACpE,QAAM,iBAAiB,WAAW,KAAK,IAAI,GAAG,SAAS,IAAI;AAC3D,QAAM,KAAK,uBAAuB,iBAAiB,KAAK,QAAQ,CAAC,CAAC,GAAG;AAGrE,QAAM,aAAa;AACnB,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,WAAW,KAAK,EAAE,IAAI,CAAC,EAAE;AAC/D,QAAM,aAAa,YAAY,MAAM;AACrC,QAAM,mBAAmB,KAAK,IAAI,aAAa,GAAG,IAAI;AACtD,QAAM,KAAK,iBAAiB,aAAa,KAAK,QAAQ,CAAC,CAAC,GAAG;AAG3D,MAAI,QAAQ;AACZ,YAAU,IAAI,eAAe,MAAM;AACnC,YAAU,IAAI,YAAY,MAAM;AAChC,WAAS,iBAAiB,MAAM;AAChC,WAAS,mBAAmB,MAAM;AAElC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAAA,IACnD,SAAS,MAAM;AAAA,IACf;AAAA,IACA,YAAY;AAAA,EACd;AACF;AAEA,SAAS,SAAS,MAAwB;AACxC,SAAO,KACJ,YAAY,EACZ,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,gBAAgB,GAAG,EAC3B,MAAM,KAAK,EACX,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/B;;;ACvEO,SAAS,eACd,OACA,KACgB;AAChB,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY;AAGhB,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,MAAM,EAAE,kBAAkB,EAAE,mBAAmB,UAAa,EAAE,iBAAiB;AAAA,EAClF,EAAE;AACF,MAAI,eAAe,GAAG;AACpB,iBAAa,KAAK,IAAI,GAAG,YAAY;AACrC,UAAM,KAAK,GAAG,YAAY,yBAAyB;AAAA,EACrD;AAGA,QAAM,cAAc,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;AACzE,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAM,KAAK,YAAY,CAAC,EAAG,aAAa,YAAY,IAAI,CAAC,EAAG;AAC5D,UAAM,KAAK,YAAY,IAAI,CAAC,EAAG,aAAa,YAAY,IAAI,CAAC,EAAG;AAChE,QAAI,KAAK,IAAI,KAAK,EAAE,IAAI,KAAK,KAAK,EAAG;AAAA,EACvC;AACA,MAAI,WAAW,GAAG;AAChB;AACA,UAAM,KAAK,GAAG,OAAO,kCAAkC;AAAA,EACzD;AAGA,MAAI,eAAe;AACnB,QAAM,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AACnD,aAAW,KAAK,OAAO;AACrB,aAAS,SAAS,IAAI,UAAU,GAAG,UAAU;AAC3C,UAAI,QAAQ,IAAI,EAAE,aAAa,MAAM,GAAG;AACtC;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,MAAI,gBAAgB,GAAG;AACrB;AACA,UAAM,KAAK,GAAG,YAAY,4CAA4C;AAAA,EACxE;AAGA,QAAM,WAAW,MAAM,OAAO,CAAC,MAAM;AACnC,UAAM,IAAI,IAAI,KAAK,EAAE,aAAa,GAAI,EAAE,YAAY;AACpD,WAAO,KAAK,KAAK,IAAI;AAAA,EACvB,CAAC,EAAE;AACH,MAAI,WAAW,MAAM,SAAS,KAAK;AACjC;AACA,UAAM,KAAK,GAAG,QAAQ,mCAAmC;AAAA,EAC3D;AAGA,MAAI,oBAAoB;AACxB,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAM,MAAM,YAAY,CAAC,EAAG,aAAa,YAAY,IAAI,CAAC,EAAG;AAC7D,QAAI,MAAM,KAAK,MAAM;AAEnB,YAAM,WAAW,YAAY,MAAM,GAAG,IAAI,CAAC;AAC3C,UAAI,SAAS,WAAW,GAAG;AACzB,cAAM,OAAO,SAAS,CAAC,EAAG,aAAa,SAAS,CAAC,EAAG;AACpD,YAAI,OAAO,IAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACA,MAAI,oBAAoB,GAAG;AACzB,iBAAa;AACb,UAAM,KAAK,GAAG,iBAAiB,8BAA8B;AAAA,EAC/D;AAEA,MAAI,cAAc,GAAG;AACnB,UAAM,KAAK,8BAA8B;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO;AAAA;AAAA,IACP,SAAS,MAAM,SAAS,IAAI;AAAA,IAC5B;AAAA,IACA,YAAY;AAAA,EACd;AACF;;;ACrFO,SAAS,oBACd,OACA,KACA,QACgB;AAChB,QAAM,QAAkB,CAAC;AAEzB,MAAI,MAAM,SAAS,MAAM,IAAI,SAAS,IAAI;AACxC,UAAM,KAAK,4BAA4B,MAAM,MAAM,SAAS,IAAI,MAAM,GAAG;AACzE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,SAAS,MAAM,SAAS,IAAI;AAAA,MAC5B;AAAA,MACA,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,cAAc,oBAAI,IAAoB;AAC5C,QAAM,YAAY,oBAAI,IAAoB;AAE1C,QAAM,SAAS,CAAC,MAAc,KAAK,MAAM,IAAI,IAAI;AAEjD,aAAW,KAAK,OAAO;AACrB,UAAM,IAAI,OAAO,EAAE,UAAU;AAC7B,gBAAY,IAAI,IAAI,YAAY,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,EAClD;AACA,aAAW,KAAK,KAAK;AACnB,UAAM,IAAI,OAAO,EAAE,SAAS;AAC5B,cAAU,IAAI,IAAI,UAAU,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,EAC9C;AAGA,QAAM,WAAW,oBAAI,IAAI,CAAC,GAAG,YAAY,KAAK,GAAG,GAAG,UAAU,KAAK,CAAC,CAAC;AACrE,QAAM,UAAoB,CAAC;AAC3B,QAAM,QAAkB,CAAC;AACzB,aAAW,KAAK,UAAU;AACxB,YAAQ,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC;AACpC,UAAM,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC;AAAA,EAClC;AAGA,QAAM,UAAU,mBAAmB,SAAS,KAAK;AACjD,QAAM,KAAK,eAAe,QAAQ,QAAQ,CAAC,CAAC,EAAE;AAI9C,QAAM,WAAW,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,IAAI,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AAC7F,QAAM,SAAS,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AACrF,QAAM,YAAY,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AACtD,QAAM,UAAU,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AACjD,QAAM,eAAe,KAAK,IAAI,WAAW,OAAO;AAChD,QAAM,aAAa,KAAK,IAAI,YAAY,UAAU,UAAU,MAAM;AAClE,QAAM,UAAU,KAAK,IAAI,GAAG,aAAa,YAAY;AACrD,QAAM,eAAe,UAAU,KAAK,IAAI,UAAU,QAAQ,CAAC;AAC3D,QAAM,KAAK,sBAAsB,eAAe,KAAK,QAAQ,CAAC,CAAC,GAAG;AAGlE,QAAM,YAAY,KAAK,IAAI,GAAG,OAAO,IAAI;AACzC,QAAM,eAAe,eAAe;AAEpC,QAAM,QAAQ,YAAY,MAAM,eAAe;AAG/C,QAAM,KAAK,YAAY,OAAO,MAAM,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK;AAEvE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAAA,IACnD,SAAS,MAAM,SAAS,IAAI;AAAA,IAC5B;AAAA,IACA,YAAY;AAAA,EACd;AACF;AAEA,SAAS,mBAAmB,GAAa,GAAqB;AAC5D,MAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAG,QAAO;AACpD,QAAM,IAAI,EAAE;AACZ,QAAM,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AAC7C,QAAM,QAAQ,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI;AAC7C,MAAI,MAAM;AACV,MAAI,OAAO;AACX,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,KAAK,EAAE,CAAC,IAAK;AACnB,UAAM,KAAK,EAAE,CAAC,IAAK;AACnB,WAAO,KAAK;AACZ,YAAQ,KAAK;AACb,YAAQ,KAAK;AAAA,EACf;AACA,QAAM,QAAQ,KAAK,KAAK,OAAO,IAAI;AACnC,SAAO,UAAU,IAAI,IAAI,MAAM;AACjC;AAEA,SAAS,MAAM,KAAuB;AACpC,SAAO,IAAI,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,GAAI,IAAI,CAAC,KAAK,CAAC;AAC1D;AACA,SAAS,MAAM,KAAuB;AACpC,SAAO,IAAI,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,GAAI,IAAI,CAAC,KAAK,CAAC;AAC1D;","names":[]}