@rune-kit/rune 2.10.0 → 2.12.0

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.
Files changed (240) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +65 -6
  3. package/commands/rune.md +168 -168
  4. package/compiler/__tests__/detect-invariants.test.js +136 -0
  5. package/compiler/__tests__/doctor-mesh.test.js +229 -0
  6. package/compiler/__tests__/hook-dispatch.test.js +91 -0
  7. package/compiler/__tests__/hooks-antigravity.test.js +118 -0
  8. package/compiler/__tests__/hooks-cursor.test.js +139 -0
  9. package/compiler/__tests__/hooks-install.test.js +305 -0
  10. package/compiler/__tests__/hooks-merge.test.js +204 -0
  11. package/compiler/__tests__/hooks-tiers.test.js +519 -0
  12. package/compiler/__tests__/hooks-windsurf.test.js +115 -0
  13. package/compiler/__tests__/inject-claude-md.test.js +152 -0
  14. package/compiler/__tests__/load-invariants.test.js +408 -0
  15. package/compiler/__tests__/onboard-invariants.test.js +240 -0
  16. package/compiler/adapters/hooks/antigravity.js +140 -0
  17. package/compiler/adapters/hooks/claude.js +166 -0
  18. package/compiler/adapters/hooks/cursor.js +191 -0
  19. package/compiler/adapters/hooks/index.js +82 -0
  20. package/compiler/adapters/hooks/tier-emitter.js +182 -0
  21. package/compiler/adapters/hooks/windsurf.js +202 -0
  22. package/compiler/bin/rune.js +196 -6
  23. package/compiler/commands/hook-dispatch.js +87 -0
  24. package/compiler/commands/hooks/install.js +120 -0
  25. package/compiler/commands/hooks/merge.js +211 -0
  26. package/compiler/commands/hooks/presets.js +116 -0
  27. package/compiler/commands/hooks/status.js +112 -0
  28. package/compiler/commands/hooks/tiers.js +221 -0
  29. package/compiler/commands/hooks/uninstall.js +94 -0
  30. package/compiler/doctor.js +236 -0
  31. package/contexts/dev.md +34 -34
  32. package/contexts/research.md +43 -43
  33. package/contexts/review.md +55 -55
  34. package/extensions/ai-ml/PACK.md +88 -88
  35. package/extensions/ai-ml/skills/ai-agents.md +172 -172
  36. package/extensions/ai-ml/skills/code-sandbox.md +187 -187
  37. package/extensions/ai-ml/skills/deep-research.md +146 -146
  38. package/extensions/ai-ml/skills/embedding-search.md +66 -66
  39. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
  40. package/extensions/ai-ml/skills/llm-architect.md +125 -125
  41. package/extensions/ai-ml/skills/llm-integration.md +64 -64
  42. package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
  43. package/extensions/ai-ml/skills/rag-patterns.md +66 -66
  44. package/extensions/ai-ml/skills/web-extraction.md +114 -114
  45. package/extensions/analytics/PACK.md +92 -92
  46. package/extensions/analytics/skills/ab-testing.md +72 -72
  47. package/extensions/analytics/skills/dashboard-patterns.md +83 -83
  48. package/extensions/analytics/skills/data-validation.md +68 -68
  49. package/extensions/analytics/skills/funnel-analysis.md +81 -81
  50. package/extensions/analytics/skills/sql-patterns.md +57 -57
  51. package/extensions/analytics/skills/statistical-analysis.md +79 -79
  52. package/extensions/analytics/skills/tracking-setup.md +71 -71
  53. package/extensions/backend/PACK.md +104 -104
  54. package/extensions/backend/skills/api-patterns.md +84 -84
  55. package/extensions/backend/skills/async-pipeline.md +193 -193
  56. package/extensions/backend/skills/auth-patterns.md +97 -97
  57. package/extensions/backend/skills/background-jobs.md +133 -133
  58. package/extensions/backend/skills/caching-patterns.md +108 -108
  59. package/extensions/backend/skills/cli-generation.md +133 -133
  60. package/extensions/backend/skills/database-patterns.md +87 -87
  61. package/extensions/backend/skills/middleware-patterns.md +104 -104
  62. package/extensions/chrome-ext/PACK.md +93 -93
  63. package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
  64. package/extensions/chrome-ext/skills/cws-publish.md +104 -104
  65. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
  66. package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
  67. package/extensions/chrome-ext/skills/ext-storage.md +133 -133
  68. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
  69. package/extensions/content/PACK.md +96 -96
  70. package/extensions/content/skills/blog-patterns.md +88 -88
  71. package/extensions/content/skills/cms-integration.md +131 -131
  72. package/extensions/content/skills/content-scoring.md +107 -107
  73. package/extensions/content/skills/i18n.md +83 -83
  74. package/extensions/content/skills/mdx-authoring.md +137 -137
  75. package/extensions/content/skills/reference.md +1014 -1014
  76. package/extensions/content/skills/seo-patterns.md +67 -67
  77. package/extensions/content/skills/video-repurpose.md +153 -153
  78. package/extensions/devops/PACK.md +101 -101
  79. package/extensions/devops/skills/chaos-testing.md +67 -67
  80. package/extensions/devops/skills/ci-cd.md +75 -75
  81. package/extensions/devops/skills/docker.md +58 -58
  82. package/extensions/devops/skills/edge-serverless.md +163 -163
  83. package/extensions/devops/skills/infra-as-code.md +158 -158
  84. package/extensions/devops/skills/kubernetes.md +110 -110
  85. package/extensions/devops/skills/monitoring.md +57 -57
  86. package/extensions/devops/skills/server-setup.md +64 -64
  87. package/extensions/devops/skills/ssl-domain.md +42 -42
  88. package/extensions/ecommerce/PACK.md +116 -116
  89. package/extensions/ecommerce/skills/cart-system.md +79 -79
  90. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
  91. package/extensions/ecommerce/skills/order-management.md +126 -126
  92. package/extensions/ecommerce/skills/payment-integration.md +472 -472
  93. package/extensions/ecommerce/skills/shopify-dev.md +69 -69
  94. package/extensions/ecommerce/skills/subscription-billing.md +93 -93
  95. package/extensions/ecommerce/skills/tax-compliance.md +117 -117
  96. package/extensions/gamedev/PACK.md +142 -142
  97. package/extensions/gamedev/skills/asset-pipeline.md +74 -74
  98. package/extensions/gamedev/skills/audio-system.md +129 -129
  99. package/extensions/gamedev/skills/camera-system.md +87 -87
  100. package/extensions/gamedev/skills/ecs.md +98 -98
  101. package/extensions/gamedev/skills/game-loops.md +72 -72
  102. package/extensions/gamedev/skills/input-system.md +199 -199
  103. package/extensions/gamedev/skills/multiplayer.md +180 -180
  104. package/extensions/gamedev/skills/particles.md +105 -105
  105. package/extensions/gamedev/skills/physics-engine.md +89 -89
  106. package/extensions/gamedev/skills/scene-management.md +146 -146
  107. package/extensions/gamedev/skills/threejs-patterns.md +90 -90
  108. package/extensions/gamedev/skills/webgl.md +71 -71
  109. package/extensions/mobile/PACK.md +106 -106
  110. package/extensions/mobile/skills/app-store-connect.md +152 -152
  111. package/extensions/mobile/skills/app-store-prep.md +66 -66
  112. package/extensions/mobile/skills/deep-linking.md +109 -109
  113. package/extensions/mobile/skills/flutter.md +60 -60
  114. package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
  115. package/extensions/mobile/skills/native-bridge.md +66 -66
  116. package/extensions/mobile/skills/ota-updates.md +97 -97
  117. package/extensions/mobile/skills/push-notifications.md +111 -111
  118. package/extensions/mobile/skills/react-native.md +82 -82
  119. package/extensions/saas/PACK.md +116 -116
  120. package/extensions/saas/skills/billing-integration.md +200 -200
  121. package/extensions/saas/skills/feature-flags.md +130 -130
  122. package/extensions/saas/skills/multi-tenant.md +103 -103
  123. package/extensions/saas/skills/onboarding-flow.md +139 -139
  124. package/extensions/saas/skills/subscription-flow.md +95 -95
  125. package/extensions/saas/skills/team-management.md +144 -144
  126. package/extensions/security/PACK.md +99 -99
  127. package/extensions/security/skills/api-security.md +140 -140
  128. package/extensions/security/skills/compliance.md +68 -68
  129. package/extensions/security/skills/owasp-audit.md +64 -64
  130. package/extensions/security/skills/pentest-patterns.md +77 -77
  131. package/extensions/security/skills/secret-mgmt.md +65 -65
  132. package/extensions/security/skills/supply-chain.md +65 -65
  133. package/extensions/trading/PACK.md +80 -80
  134. package/extensions/trading/skills/chart-components.md +55 -55
  135. package/extensions/trading/skills/experiment-loop.md +125 -125
  136. package/extensions/trading/skills/fintech-patterns.md +47 -47
  137. package/extensions/trading/skills/indicator-library.md +58 -58
  138. package/extensions/trading/skills/quant-analysis.md +111 -111
  139. package/extensions/trading/skills/realtime-data.md +58 -58
  140. package/extensions/trading/skills/trade-logic.md +104 -104
  141. package/extensions/ui/PACK.md +130 -130
  142. package/extensions/ui/skills/a11y-audit.md +91 -91
  143. package/extensions/ui/skills/animation-patterns.md +127 -127
  144. package/extensions/ui/skills/component-patterns.md +100 -100
  145. package/extensions/ui/skills/design-decision.md +108 -108
  146. package/extensions/ui/skills/design-system.md +68 -68
  147. package/extensions/ui/skills/landing-patterns.md +155 -155
  148. package/extensions/ui/skills/palette-picker.md +173 -173
  149. package/extensions/ui/skills/react-health.md +90 -90
  150. package/extensions/ui/skills/type-system.md +125 -125
  151. package/extensions/ui/skills/web-vitals.md +153 -153
  152. package/extensions/zalo/PACK.md +145 -145
  153. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
  154. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
  155. package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
  156. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
  157. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
  158. package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
  159. package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
  160. package/hooks/auto-format/index.cjs +48 -48
  161. package/hooks/hooks.json +111 -111
  162. package/hooks/post-session-reflect/index.cjs +189 -189
  163. package/hooks/pre-compact/index.cjs +95 -95
  164. package/hooks/run-hook.cmd +1 -1
  165. package/hooks/secrets-scan/index.cjs +100 -100
  166. package/hooks/session-start/index.cjs +71 -71
  167. package/hooks/typecheck/index.cjs +65 -65
  168. package/package.json +63 -63
  169. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
  170. package/references/ui-pro-max-data/charts.csv +26 -26
  171. package/references/ui-pro-max-data/colors.csv +161 -161
  172. package/references/ui-pro-max-data/styles.csv +68 -68
  173. package/references/ui-pro-max-data/typography.csv +74 -74
  174. package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
  175. package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
  176. package/skills/adversary/SKILL.md +283 -283
  177. package/skills/asset-creator/SKILL.md +157 -157
  178. package/skills/audit/SKILL.md +147 -2
  179. package/skills/autopsy/SKILL.md +335 -335
  180. package/skills/ba/SKILL.md +85 -1
  181. package/skills/brainstorm/SKILL.md +380 -342
  182. package/skills/browser-pilot/SKILL.md +169 -168
  183. package/skills/constraint-check/SKILL.md +165 -165
  184. package/skills/context-engine/SKILL.md +408 -404
  185. package/skills/cook/SKILL.md +917 -863
  186. package/skills/db/SKILL.md +273 -273
  187. package/skills/debug/SKILL.md +465 -465
  188. package/skills/dependency-doctor/SKILL.md +265 -235
  189. package/skills/deploy/SKILL.md +274 -231
  190. package/skills/design/DESIGN-REFERENCE.md +365 -365
  191. package/skills/design/SKILL.md +590 -589
  192. package/skills/doc-processor/SKILL.md +254 -254
  193. package/skills/docs/SKILL.md +374 -374
  194. package/skills/docs-seeker/SKILL.md +178 -177
  195. package/skills/fix/SKILL.md +332 -330
  196. package/skills/git/SKILL.md +339 -339
  197. package/skills/hallucination-guard/SKILL.md +220 -219
  198. package/skills/incident/SKILL.md +254 -253
  199. package/skills/integrity-check/SKILL.md +169 -169
  200. package/skills/journal/SKILL.md +241 -240
  201. package/skills/launch/SKILL.md +344 -344
  202. package/skills/logic-guardian/SKILL.md +269 -251
  203. package/skills/marketing/SKILL.md +351 -289
  204. package/skills/mcp-builder/SKILL.md +425 -425
  205. package/skills/neural-memory/SKILL.md +359 -362
  206. package/skills/onboard/SKILL.md +432 -403
  207. package/skills/onboard/references/invariants-template.md +76 -0
  208. package/skills/onboard/scripts/detect-invariants.js +439 -0
  209. package/skills/onboard/scripts/inject-claude-md.js +150 -0
  210. package/skills/onboard/scripts/onboard-invariants.js +194 -0
  211. package/skills/perf/SKILL.md +347 -346
  212. package/skills/plan/SKILL.md +435 -428
  213. package/skills/preflight/SKILL.md +415 -415
  214. package/skills/problem-solver/SKILL.md +380 -284
  215. package/skills/rescue/SKILL.md +474 -474
  216. package/skills/research/SKILL.md +4 -0
  217. package/skills/retro/SKILL.md +3 -1
  218. package/skills/review/SKILL.md +614 -588
  219. package/skills/review-intake/SKILL.md +249 -249
  220. package/skills/safeguard/SKILL.md +200 -200
  221. package/skills/sast/SKILL.md +190 -190
  222. package/skills/scaffold/SKILL.md +328 -287
  223. package/skills/scope-guard/SKILL.md +183 -180
  224. package/skills/scout/SKILL.md +269 -263
  225. package/skills/sentinel/SKILL.md +384 -381
  226. package/skills/sentinel-env/SKILL.md +254 -254
  227. package/skills/sequential-thinking/SKILL.md +234 -234
  228. package/skills/session-bridge/SKILL.md +595 -543
  229. package/skills/session-bridge/scripts/load-invariants.js +397 -0
  230. package/skills/skill-forge/SKILL.md +581 -581
  231. package/skills/skill-router/SKILL.md +3 -0
  232. package/skills/slides/SKILL.md +19 -0
  233. package/skills/surgeon/SKILL.md +215 -215
  234. package/skills/team/SKILL.md +557 -537
  235. package/skills/test/SKILL.md +620 -614
  236. package/skills/trend-scout/SKILL.md +145 -145
  237. package/skills/verification/SKILL.md +334 -326
  238. package/skills/video-creator/SKILL.md +201 -201
  239. package/skills/watchdog/SKILL.md +168 -168
  240. package/skills/worktree/SKILL.md +140 -140
@@ -1,219 +1,219 @@
1
- ---
2
- name: zalo-rate-guard
3
- pack: "@rune/zalo"
4
- description: Rate limiting patterns for Zalo OA and personal APIs — token bucket per endpoint, exponential backoff, queue management, quota monitoring, anti-ban strategies.
5
- model: sonnet
6
- tools: "Read, Glob, Grep, Bash, Write, Edit"
7
- ---
8
-
9
- # zalo-rate-guard
10
-
11
- Shared rate limiting layer for both Track A (OA API) and Track B (Personal via zca-js).
12
- Zalo has **undocumented rate limits** — no official RPM/QPM numbers published.
13
- Exceeding limits: throttled (429) → warned → OA suspended / account banned.
14
- Neither `zalo-php-sdk`, `zalo-java-sdk`, nor `zca-js` implement any rate limiting.
15
- This skill fills that gap.
16
-
17
- ---
18
-
19
- ## Estimated Safe Limits
20
-
21
- **Track A — OA API:**
22
-
23
- | Endpoint | Safe RPM | Burst | Notes |
24
- |----------|----------|-------|-------|
25
- | Send CS message | 200 | 10 | Per OA, includes all message types |
26
- | Send broadcast | 50 | 5 | Monthly quota based on follower count |
27
- | Get user profile | 300 | 20 | Cacheable — use name cache |
28
- | Get follower list | 100 | 10 | Paginated, cache results |
29
- | Upload media | 60 | 5 | Large payloads, slower |
30
- | Global | 500 | 30 | Total across all endpoints |
31
-
32
- **Track B — Personal (zca-js):**
33
-
34
- | Action | Safe RPM | Burst | Notes |
35
- |--------|----------|-------|-------|
36
- | Send message (DM) | 30 | 5 | Much lower than OA — personal account |
37
- | Send message (group) | 20 | 3 | Groups are more scrutinized |
38
- | Friend operations | 10 | 2 | Add/remove friend is very sensitive |
39
- | Profile lookups | 60 | 10 | Less sensitive, still cache |
40
- | Global | 100 | 15 | Err on side of caution |
41
-
42
- ---
43
-
44
- ## Token Bucket Implementation
45
-
46
- ```typescript
47
- import PQueue from 'p-queue'
48
-
49
- interface RateLimitConfig {
50
- rpm: number // requests per minute
51
- burst: number // max concurrent
52
- retryAfter: number // ms to wait on 429
53
- }
54
-
55
- const LIMITS: Record<string, RateLimitConfig> = {
56
- 'oa:send_message': { rpm: 200, burst: 10, retryAfter: 5000 },
57
- 'oa:broadcast': { rpm: 50, burst: 5, retryAfter: 10000 },
58
- 'oa:get_profile': { rpm: 300, burst: 20, retryAfter: 3000 },
59
- 'oa:upload': { rpm: 60, burst: 5, retryAfter: 5000 },
60
- 'personal:send_dm': { rpm: 30, burst: 5, retryAfter: 10000 },
61
- 'personal:send_grp': { rpm: 20, burst: 3, retryAfter: 15000 },
62
- 'personal:friend': { rpm: 10, burst: 2, retryAfter: 30000 },
63
- }
64
-
65
- export class ZaloRateLimiter {
66
- private queues = new Map<string, PQueue>()
67
-
68
- constructor() {
69
- for (const [key, config] of Object.entries(LIMITS)) {
70
- this.queues.set(key, new PQueue({
71
- concurrency: config.burst,
72
- intervalCap: config.rpm,
73
- interval: 60_000, // per minute window
74
- }))
75
- }
76
- }
77
-
78
- async execute<T>(endpoint: string, fn: () => Promise<T>): Promise<T> {
79
- const queue = this.queues.get(endpoint)
80
- if (!queue) throw new Error(`Unknown endpoint: ${endpoint}`)
81
- return queue.add(fn) as Promise<T>
82
- }
83
-
84
- queueSize(endpoint: string): number {
85
- return this.queues.get(endpoint)?.size ?? 0
86
- }
87
-
88
- pending(endpoint: string): number {
89
- return this.queues.get(endpoint)?.pending ?? 0
90
- }
91
- }
92
- ```
93
-
94
- ---
95
-
96
- ## Exponential Backoff on 429
97
-
98
- ```typescript
99
- export async function withBackoff<T>(
100
- fn: () => Promise<T>,
101
- maxRetries = 3,
102
- baseDelay = 1000
103
- ): Promise<T> {
104
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
105
- try {
106
- return await fn()
107
- } catch (error: any) {
108
- const isRateLimit = error?.status === 429 || error?.error_code === 429
109
- if (!isRateLimit || attempt === maxRetries) throw error
110
- const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000
111
- console.warn(`[zalo-rate-guard] Rate limited. Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`)
112
- await new Promise(r => setTimeout(r, delay))
113
- }
114
- }
115
- throw new Error('Unreachable')
116
- }
117
- ```
118
-
119
- ---
120
-
121
- ## Quota Monitoring (OA Broadcast)
122
-
123
- Broadcast quota is a **hard monthly limit** — exceeding it silently drops messages, no error returned.
124
-
125
- ```typescript
126
- interface QuotaTracker {
127
- monthly_limit: number // based on follower count + OA level
128
- used: number
129
- resets_at: Date // 1st of each month
130
- }
131
-
132
- export function canBroadcast(tracker: QuotaTracker, recipientCount: number): boolean {
133
- const remaining = tracker.monthly_limit - tracker.used
134
- if (recipientCount > remaining) {
135
- console.error(
136
- `[zalo-rate-guard] Broadcast quota insufficient: need ${recipientCount}, have ${remaining}/${tracker.monthly_limit}`
137
- )
138
- return false
139
- }
140
- return true
141
- }
142
-
143
- export function trackBroadcastUsed(tracker: QuotaTracker, sent: number): QuotaTracker {
144
- return { ...tracker, used: tracker.used + sent }
145
- }
146
- ```
147
-
148
- ---
149
-
150
- ## Integration Pattern
151
-
152
- ```typescript
153
- // Singleton — shared across the app
154
- export const limiter = new ZaloRateLimiter()
155
-
156
- // Track A: OA message send with rate limiting
157
- export async function sendOaMessage(userId: string, text: string) {
158
- return limiter.execute('oa:send_message', () =>
159
- withBackoff(() =>
160
- oaApiCall('/message/cs', {
161
- recipient: { user_id: userId },
162
- message: { text },
163
- })
164
- )
165
- )
166
- }
167
-
168
- // Track B: Personal DM with rate limiting + human jitter
169
- export async function sendPersonalMessage(threadId: string, text: string) {
170
- const jitter = 500 + Math.random() * 1500 // 500–2000ms
171
- await new Promise(r => setTimeout(r, jitter))
172
- return limiter.execute('personal:send_dm', () =>
173
- withBackoff(() => api.sendMessage(text, threadId, 'User'))
174
- )
175
- }
176
-
177
- // Track B: Friend operation — highest-risk, extra jitter
178
- export async function addFriend(userId: string) {
179
- const jitter = 2000 + Math.random() * 3000 // 2–5s
180
- await new Promise(r => setTimeout(r, jitter))
181
- return limiter.execute('personal:friend', () =>
182
- withBackoff(() => api.sendFriendRequest(userId), 2, 5000)
183
- )
184
- }
185
- ```
186
-
187
- ---
188
-
189
- ## Anti-Ban Strategies
190
-
191
- **Track A (OA):**
192
- 1. Stay under safe RPM limits (table above)
193
- 2. Exponential backoff on ALL 429 responses — never retry immediately
194
- 3. Cache user profiles — avoid repeated lookups for the same user
195
- 4. Spread broadcasts over time — don't burst the entire follower list at once
196
- 5. Monitor quota before each broadcast batch — stop before hitting monthly limit
197
- 6. Use `appsecret_proof` on all requests — proves you're the legitimate app owner
198
-
199
- **Track B (Personal):**
200
- 1. Much lower limits than OA — personal accounts are watched more closely
201
- 2. Add human-like jitter: 500–2000ms random delay between messages (not optional)
202
- 3. Avoid 3–6 AM (VN timezone) — traffic at those hours flags automated activity
203
- 4. Never change profile info programmatically — triggers manual review
204
- 5. Friend operations are highest-risk — max 10 RPM, prefer lower in practice
205
- 6. Keep sessions long-lived — repeated login/logout is a strong ban signal
206
- 7. Use a consistent device fingerprint (`userAgent` + `IMEI`) per account
207
- 8. On `DuplicateConnection` (error 3000): wait 30s before reconnecting, never spam reconnects
208
-
209
- ---
210
-
211
- ## Sharp Edges
212
-
213
- - Rate limits are **estimated** — Zalo does not publish official numbers; treat all figures as conservative targets
214
- - `p-queue` `intervalCap` applies per window, not per request — test behavior under burst
215
- - 429 without backoff = accelerating toward ban, not slowing down
216
- - Broadcast quota overflow **silently drops messages** — no 429, no error, just lost sends
217
- - Stale cached profile data is acceptable; hitting rate limits for fresh data is not
218
- - Personal account friend operations are the single highest-risk action — handle with care
219
- - Human jitter for personal track is a survival strategy, not a nice-to-have
1
+ ---
2
+ name: zalo-rate-guard
3
+ pack: "@rune/zalo"
4
+ description: Rate limiting patterns for Zalo OA and personal APIs — token bucket per endpoint, exponential backoff, queue management, quota monitoring, anti-ban strategies.
5
+ model: sonnet
6
+ tools: "Read, Glob, Grep, Bash, Write, Edit"
7
+ ---
8
+
9
+ # zalo-rate-guard
10
+
11
+ Shared rate limiting layer for both Track A (OA API) and Track B (Personal via zca-js).
12
+ Zalo has **undocumented rate limits** — no official RPM/QPM numbers published.
13
+ Exceeding limits: throttled (429) → warned → OA suspended / account banned.
14
+ Neither `zalo-php-sdk`, `zalo-java-sdk`, nor `zca-js` implement any rate limiting.
15
+ This skill fills that gap.
16
+
17
+ ---
18
+
19
+ ## Estimated Safe Limits
20
+
21
+ **Track A — OA API:**
22
+
23
+ | Endpoint | Safe RPM | Burst | Notes |
24
+ |----------|----------|-------|-------|
25
+ | Send CS message | 200 | 10 | Per OA, includes all message types |
26
+ | Send broadcast | 50 | 5 | Monthly quota based on follower count |
27
+ | Get user profile | 300 | 20 | Cacheable — use name cache |
28
+ | Get follower list | 100 | 10 | Paginated, cache results |
29
+ | Upload media | 60 | 5 | Large payloads, slower |
30
+ | Global | 500 | 30 | Total across all endpoints |
31
+
32
+ **Track B — Personal (zca-js):**
33
+
34
+ | Action | Safe RPM | Burst | Notes |
35
+ |--------|----------|-------|-------|
36
+ | Send message (DM) | 30 | 5 | Much lower than OA — personal account |
37
+ | Send message (group) | 20 | 3 | Groups are more scrutinized |
38
+ | Friend operations | 10 | 2 | Add/remove friend is very sensitive |
39
+ | Profile lookups | 60 | 10 | Less sensitive, still cache |
40
+ | Global | 100 | 15 | Err on side of caution |
41
+
42
+ ---
43
+
44
+ ## Token Bucket Implementation
45
+
46
+ ```typescript
47
+ import PQueue from 'p-queue'
48
+
49
+ interface RateLimitConfig {
50
+ rpm: number // requests per minute
51
+ burst: number // max concurrent
52
+ retryAfter: number // ms to wait on 429
53
+ }
54
+
55
+ const LIMITS: Record<string, RateLimitConfig> = {
56
+ 'oa:send_message': { rpm: 200, burst: 10, retryAfter: 5000 },
57
+ 'oa:broadcast': { rpm: 50, burst: 5, retryAfter: 10000 },
58
+ 'oa:get_profile': { rpm: 300, burst: 20, retryAfter: 3000 },
59
+ 'oa:upload': { rpm: 60, burst: 5, retryAfter: 5000 },
60
+ 'personal:send_dm': { rpm: 30, burst: 5, retryAfter: 10000 },
61
+ 'personal:send_grp': { rpm: 20, burst: 3, retryAfter: 15000 },
62
+ 'personal:friend': { rpm: 10, burst: 2, retryAfter: 30000 },
63
+ }
64
+
65
+ export class ZaloRateLimiter {
66
+ private queues = new Map<string, PQueue>()
67
+
68
+ constructor() {
69
+ for (const [key, config] of Object.entries(LIMITS)) {
70
+ this.queues.set(key, new PQueue({
71
+ concurrency: config.burst,
72
+ intervalCap: config.rpm,
73
+ interval: 60_000, // per minute window
74
+ }))
75
+ }
76
+ }
77
+
78
+ async execute<T>(endpoint: string, fn: () => Promise<T>): Promise<T> {
79
+ const queue = this.queues.get(endpoint)
80
+ if (!queue) throw new Error(`Unknown endpoint: ${endpoint}`)
81
+ return queue.add(fn) as Promise<T>
82
+ }
83
+
84
+ queueSize(endpoint: string): number {
85
+ return this.queues.get(endpoint)?.size ?? 0
86
+ }
87
+
88
+ pending(endpoint: string): number {
89
+ return this.queues.get(endpoint)?.pending ?? 0
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Exponential Backoff on 429
97
+
98
+ ```typescript
99
+ export async function withBackoff<T>(
100
+ fn: () => Promise<T>,
101
+ maxRetries = 3,
102
+ baseDelay = 1000
103
+ ): Promise<T> {
104
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
105
+ try {
106
+ return await fn()
107
+ } catch (error: any) {
108
+ const isRateLimit = error?.status === 429 || error?.error_code === 429
109
+ if (!isRateLimit || attempt === maxRetries) throw error
110
+ const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000
111
+ console.warn(`[zalo-rate-guard] Rate limited. Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`)
112
+ await new Promise(r => setTimeout(r, delay))
113
+ }
114
+ }
115
+ throw new Error('Unreachable')
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Quota Monitoring (OA Broadcast)
122
+
123
+ Broadcast quota is a **hard monthly limit** — exceeding it silently drops messages, no error returned.
124
+
125
+ ```typescript
126
+ interface QuotaTracker {
127
+ monthly_limit: number // based on follower count + OA level
128
+ used: number
129
+ resets_at: Date // 1st of each month
130
+ }
131
+
132
+ export function canBroadcast(tracker: QuotaTracker, recipientCount: number): boolean {
133
+ const remaining = tracker.monthly_limit - tracker.used
134
+ if (recipientCount > remaining) {
135
+ console.error(
136
+ `[zalo-rate-guard] Broadcast quota insufficient: need ${recipientCount}, have ${remaining}/${tracker.monthly_limit}`
137
+ )
138
+ return false
139
+ }
140
+ return true
141
+ }
142
+
143
+ export function trackBroadcastUsed(tracker: QuotaTracker, sent: number): QuotaTracker {
144
+ return { ...tracker, used: tracker.used + sent }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Integration Pattern
151
+
152
+ ```typescript
153
+ // Singleton — shared across the app
154
+ export const limiter = new ZaloRateLimiter()
155
+
156
+ // Track A: OA message send with rate limiting
157
+ export async function sendOaMessage(userId: string, text: string) {
158
+ return limiter.execute('oa:send_message', () =>
159
+ withBackoff(() =>
160
+ oaApiCall('/message/cs', {
161
+ recipient: { user_id: userId },
162
+ message: { text },
163
+ })
164
+ )
165
+ )
166
+ }
167
+
168
+ // Track B: Personal DM with rate limiting + human jitter
169
+ export async function sendPersonalMessage(threadId: string, text: string) {
170
+ const jitter = 500 + Math.random() * 1500 // 500–2000ms
171
+ await new Promise(r => setTimeout(r, jitter))
172
+ return limiter.execute('personal:send_dm', () =>
173
+ withBackoff(() => api.sendMessage(text, threadId, 'User'))
174
+ )
175
+ }
176
+
177
+ // Track B: Friend operation — highest-risk, extra jitter
178
+ export async function addFriend(userId: string) {
179
+ const jitter = 2000 + Math.random() * 3000 // 2–5s
180
+ await new Promise(r => setTimeout(r, jitter))
181
+ return limiter.execute('personal:friend', () =>
182
+ withBackoff(() => api.sendFriendRequest(userId), 2, 5000)
183
+ )
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Anti-Ban Strategies
190
+
191
+ **Track A (OA):**
192
+ 1. Stay under safe RPM limits (table above)
193
+ 2. Exponential backoff on ALL 429 responses — never retry immediately
194
+ 3. Cache user profiles — avoid repeated lookups for the same user
195
+ 4. Spread broadcasts over time — don't burst the entire follower list at once
196
+ 5. Monitor quota before each broadcast batch — stop before hitting monthly limit
197
+ 6. Use `appsecret_proof` on all requests — proves you're the legitimate app owner
198
+
199
+ **Track B (Personal):**
200
+ 1. Much lower limits than OA — personal accounts are watched more closely
201
+ 2. Add human-like jitter: 500–2000ms random delay between messages (not optional)
202
+ 3. Avoid 3–6 AM (VN timezone) — traffic at those hours flags automated activity
203
+ 4. Never change profile info programmatically — triggers manual review
204
+ 5. Friend operations are highest-risk — max 10 RPM, prefer lower in practice
205
+ 6. Keep sessions long-lived — repeated login/logout is a strong ban signal
206
+ 7. Use a consistent device fingerprint (`userAgent` + `IMEI`) per account
207
+ 8. On `DuplicateConnection` (error 3000): wait 30s before reconnecting, never spam reconnects
208
+
209
+ ---
210
+
211
+ ## Sharp Edges
212
+
213
+ - Rate limits are **estimated** — Zalo does not publish official numbers; treat all figures as conservative targets
214
+ - `p-queue` `intervalCap` applies per window, not per request — test behavior under burst
215
+ - 429 without backoff = accelerating toward ban, not slowing down
216
+ - Broadcast quota overflow **silently drops messages** — no 429, no error, just lost sends
217
+ - Stale cached profile data is acceptable; hitting rate limits for fresh data is not
218
+ - Personal account friend operations are the single highest-risk action — handle with care
219
+ - Human jitter for personal track is a survival strategy, not a nice-to-have
@@ -1,48 +1,48 @@
1
- // Rune Auto-Format Hook
2
- // PostToolUse hook on Edit/Write — runs Prettier on JS/TS files after modification
3
- //
4
- // Only runs if Prettier is available in the project.
5
- // Silent pass-through if not applicable.
6
-
7
- const { execSync } = require('child_process');
8
- const path = require('path');
9
-
10
- const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
11
- const filePath = input.file_path || input.filePath || '';
12
-
13
- // Only format JS/TS/JSON/CSS files
14
- if (!/\.(js|jsx|ts|tsx|json|css|scss|md|html|yaml|yml)$/i.test(filePath)) {
15
- process.exit(0);
16
- }
17
-
18
- // Check if file exists and is within a project with Prettier
19
- const dir = path.dirname(filePath);
20
-
21
- // Try to find prettier in the project
22
- let hasPrettier = false;
23
- try {
24
- execSync('npx prettier --version', { cwd: dir, encoding: 'utf-8', stdio: 'pipe', timeout: 5000 });
25
- hasPrettier = true;
26
- } catch {
27
- // No Prettier available — skip silently
28
- }
29
-
30
- if (!hasPrettier) {
31
- process.exit(0);
32
- }
33
-
34
- // Run Prettier on the file
35
- try {
36
- execSync(`npx prettier --write "${filePath}"`, {
37
- cwd: dir,
38
- encoding: 'utf-8',
39
- stdio: 'pipe',
40
- timeout: 10000,
41
- });
42
- // Silent success — formatted files are seamless
43
- } catch (e) {
44
- // Prettier failed — non-critical, just warn
45
- console.log(`[Rune auto-format] Prettier failed on ${path.basename(filePath)}: ${e.message.split('\n')[0]}`);
46
- }
47
-
48
- process.exit(0);
1
+ // Rune Auto-Format Hook
2
+ // PostToolUse hook on Edit/Write — runs Prettier on JS/TS files after modification
3
+ //
4
+ // Only runs if Prettier is available in the project.
5
+ // Silent pass-through if not applicable.
6
+
7
+ const { execSync } = require('child_process');
8
+ const path = require('path');
9
+
10
+ const input = JSON.parse(process.env.CLAUDE_TOOL_INPUT || '{}');
11
+ const filePath = input.file_path || input.filePath || '';
12
+
13
+ // Only format JS/TS/JSON/CSS files
14
+ if (!/\.(js|jsx|ts|tsx|json|css|scss|md|html|yaml|yml)$/i.test(filePath)) {
15
+ process.exit(0);
16
+ }
17
+
18
+ // Check if file exists and is within a project with Prettier
19
+ const dir = path.dirname(filePath);
20
+
21
+ // Try to find prettier in the project
22
+ let hasPrettier = false;
23
+ try {
24
+ execSync('npx prettier --version', { cwd: dir, encoding: 'utf-8', stdio: 'pipe', timeout: 5000 });
25
+ hasPrettier = true;
26
+ } catch {
27
+ // No Prettier available — skip silently
28
+ }
29
+
30
+ if (!hasPrettier) {
31
+ process.exit(0);
32
+ }
33
+
34
+ // Run Prettier on the file
35
+ try {
36
+ execSync(`npx prettier --write "${filePath}"`, {
37
+ cwd: dir,
38
+ encoding: 'utf-8',
39
+ stdio: 'pipe',
40
+ timeout: 10000,
41
+ });
42
+ // Silent success — formatted files are seamless
43
+ } catch (e) {
44
+ // Prettier failed — non-critical, just warn
45
+ console.log(`[Rune auto-format] Prettier failed on ${path.basename(filePath)}: ${e.message.split('\n')[0]}`);
46
+ }
47
+
48
+ process.exit(0);