@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,139 +1,139 @@
1
- ---
2
- name: "ext-messaging"
3
- pack: "@rune/chrome-ext"
4
- description: "Typed message passing between popup, service worker, and content script — discriminated union message types, one-shot sendMessage, long-lived port connections for streaming, and Chrome 146+ error handling."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # ext-messaging
10
-
11
- Typed message passing between popup, service worker, and content script — discriminated union message types, one-shot `sendMessage`, long-lived port connections for streaming, and Chrome 146+ error handling. Prevents the #2 MV3 failure: untyped `any` messages, missing `return true` for async handlers, and ports used for single messages.
12
-
13
- #### Workflow
14
-
15
- **Step 1 — Identify message flows**
16
- Use `Grep` to find existing `chrome.runtime.sendMessage`, `chrome.tabs.sendMessage`, and `chrome.runtime.connect` calls. Map the full message topology:
17
- - popup → service worker (sendMessage — one-shot)
18
- - service worker → content script (chrome.tabs.sendMessage — requires tab ID)
19
- - content script → service worker (sendMessage — one-shot)
20
- - service worker → popup (port — only if popup is open)
21
- - streaming AI responses → use Port (not sendMessage — ports survive multiple sends)
22
-
23
- **Step 2 — Define TypeScript message types**
24
- Create `src/types/messages.ts` with a discriminated union covering all message directions. Each message type has a `type` literal and a strongly-typed `payload`. Response types are paired per message type.
25
-
26
- **Step 3 — Implement chrome.runtime.sendMessage patterns**
27
- For one-shot request/response between extension contexts. Key rules:
28
- - Listener must `return true` if the response is sent asynchronously (inside a Promise or async function)
29
- - `chrome.runtime.lastError` MUST be checked in the callback — unhandled errors throw in MV3
30
- - Content scripts cannot receive messages via `chrome.runtime.sendMessage` — use `chrome.tabs.sendMessage` from the service worker with the target tab's ID
31
-
32
- **Step 4 — Implement chrome.tabs.sendMessage (service worker → content)**
33
- Service worker must resolve the target tab ID before sending. Use `chrome.tabs.query({ active: true, currentWindow: true })` or receive the tab ID from the content script's original message (sender.tab.id).
34
-
35
- **Step 5 — Implement port-based long-lived connections**
36
- Use `chrome.runtime.connect` for streaming scenarios (AI token streaming, progress updates, live data feeds). Ports stay open until explicitly disconnected. Each side must handle `port.onDisconnect` to clean up.
37
-
38
- **Step 6 — Add Chrome 146+ error handling**
39
- Chrome 146 changed message listener error behavior: uncaught errors in listeners now reject the Promise returned by `sendMessage` on the sender side. Wrap all listener handlers in try/catch and send structured error responses.
40
-
41
- #### Example
42
-
43
- ```typescript
44
- // src/types/messages.ts — discriminated union message types
45
- export type ExtensionMessage =
46
- | { type: 'SUMMARIZE_PAGE'; payload: { text: string; tabId: number } }
47
- | { type: 'GET_SETTINGS'; payload: Record<string, never> }
48
- | { type: 'UPDATE_SETTINGS'; payload: Partial<Settings> }
49
- | { type: 'OPEN_SIDEBAR'; payload: { tabId: number } };
50
-
51
- export type ExtensionResponse<T extends ExtensionMessage> =
52
- T extends { type: 'SUMMARIZE_PAGE' } ? { summary: string; error?: string } :
53
- T extends { type: 'GET_SETTINGS' } ? { settings: Settings } :
54
- T extends { type: 'UPDATE_SETTINGS' } ? { ok: boolean } :
55
- T extends { type: 'OPEN_SIDEBAR' } ? { ok: boolean } :
56
- never;
57
-
58
- export interface Settings {
59
- useBuiltinAI: boolean;
60
- externalApiKey: string;
61
- maxLength: number;
62
- }
63
- ```
64
-
65
- ```typescript
66
- // background.ts — typed message handler
67
- import type { ExtensionMessage } from './types/messages';
68
-
69
- chrome.runtime.onMessage.addListener(
70
- (message: ExtensionMessage, sender, sendResponse) => {
71
- // CRITICAL: return true to keep channel open for async response
72
- (async () => {
73
- try {
74
- switch (message.type) {
75
- case 'SUMMARIZE_PAGE': {
76
- const summary = await summarize(message.payload.text);
77
- sendResponse({ summary });
78
- break;
79
- }
80
- case 'GET_SETTINGS': {
81
- const result = await chrome.storage.sync.get('settings');
82
- sendResponse({ settings: result['settings'] as Settings });
83
- break;
84
- }
85
- default:
86
- sendResponse({ error: 'Unknown message type' });
87
- }
88
- } catch (err) {
89
- // Chrome 146+: send error response instead of letting it throw
90
- sendResponse({ error: String(err) });
91
- }
92
- })();
93
- return true; // MUST return true — async response
94
- }
95
- );
96
- ```
97
-
98
- ```typescript
99
- // Port-based streaming (service worker → sidebar/popup)
100
- // background.ts
101
- chrome.runtime.onConnect.addListener((port) => {
102
- if (port.name !== 'ai-stream') return;
103
-
104
- port.onMessage.addListener(async (message: { text: string }) => {
105
- try {
106
- const session = await chrome.aiLanguageModel.create();
107
- const stream = session.promptStreaming(message.text);
108
-
109
- for await (const chunk of stream) {
110
- port.postMessage({ type: 'CHUNK', content: chunk });
111
- }
112
- port.postMessage({ type: 'DONE' });
113
- session.destroy();
114
- } catch (err) {
115
- port.postMessage({ type: 'ERROR', error: String(err) });
116
- }
117
- });
118
-
119
- port.onDisconnect.addListener(() => {
120
- // cleanup — sidebar/popup was closed
121
- });
122
- });
123
-
124
- // sidebar.ts — connect and stream
125
- const port = chrome.runtime.connect({ name: 'ai-stream' });
126
- port.postMessage({ text: selectedText });
127
-
128
- port.onMessage.addListener((msg: { type: string; content?: string; error?: string }) => {
129
- if (msg.type === 'CHUNK') appendToOutput(msg.content ?? '');
130
- if (msg.type === 'DONE') finalizeOutput();
131
- if (msg.type === 'ERROR') showError(msg.error ?? 'Unknown error');
132
- });
133
-
134
- port.onDisconnect.addListener(() => {
135
- if (chrome.runtime.lastError) {
136
- console.error('[Sidebar] Port disconnected with error:', chrome.runtime.lastError.message);
137
- }
138
- });
139
- ```
1
+ ---
2
+ name: "ext-messaging"
3
+ pack: "@rune/chrome-ext"
4
+ description: "Typed message passing between popup, service worker, and content script — discriminated union message types, one-shot sendMessage, long-lived port connections for streaming, and Chrome 146+ error handling."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # ext-messaging
10
+
11
+ Typed message passing between popup, service worker, and content script — discriminated union message types, one-shot `sendMessage`, long-lived port connections for streaming, and Chrome 146+ error handling. Prevents the #2 MV3 failure: untyped `any` messages, missing `return true` for async handlers, and ports used for single messages.
12
+
13
+ #### Workflow
14
+
15
+ **Step 1 — Identify message flows**
16
+ Use `Grep` to find existing `chrome.runtime.sendMessage`, `chrome.tabs.sendMessage`, and `chrome.runtime.connect` calls. Map the full message topology:
17
+ - popup → service worker (sendMessage — one-shot)
18
+ - service worker → content script (chrome.tabs.sendMessage — requires tab ID)
19
+ - content script → service worker (sendMessage — one-shot)
20
+ - service worker → popup (port — only if popup is open)
21
+ - streaming AI responses → use Port (not sendMessage — ports survive multiple sends)
22
+
23
+ **Step 2 — Define TypeScript message types**
24
+ Create `src/types/messages.ts` with a discriminated union covering all message directions. Each message type has a `type` literal and a strongly-typed `payload`. Response types are paired per message type.
25
+
26
+ **Step 3 — Implement chrome.runtime.sendMessage patterns**
27
+ For one-shot request/response between extension contexts. Key rules:
28
+ - Listener must `return true` if the response is sent asynchronously (inside a Promise or async function)
29
+ - `chrome.runtime.lastError` MUST be checked in the callback — unhandled errors throw in MV3
30
+ - Content scripts cannot receive messages via `chrome.runtime.sendMessage` — use `chrome.tabs.sendMessage` from the service worker with the target tab's ID
31
+
32
+ **Step 4 — Implement chrome.tabs.sendMessage (service worker → content)**
33
+ Service worker must resolve the target tab ID before sending. Use `chrome.tabs.query({ active: true, currentWindow: true })` or receive the tab ID from the content script's original message (sender.tab.id).
34
+
35
+ **Step 5 — Implement port-based long-lived connections**
36
+ Use `chrome.runtime.connect` for streaming scenarios (AI token streaming, progress updates, live data feeds). Ports stay open until explicitly disconnected. Each side must handle `port.onDisconnect` to clean up.
37
+
38
+ **Step 6 — Add Chrome 146+ error handling**
39
+ Chrome 146 changed message listener error behavior: uncaught errors in listeners now reject the Promise returned by `sendMessage` on the sender side. Wrap all listener handlers in try/catch and send structured error responses.
40
+
41
+ #### Example
42
+
43
+ ```typescript
44
+ // src/types/messages.ts — discriminated union message types
45
+ export type ExtensionMessage =
46
+ | { type: 'SUMMARIZE_PAGE'; payload: { text: string; tabId: number } }
47
+ | { type: 'GET_SETTINGS'; payload: Record<string, never> }
48
+ | { type: 'UPDATE_SETTINGS'; payload: Partial<Settings> }
49
+ | { type: 'OPEN_SIDEBAR'; payload: { tabId: number } };
50
+
51
+ export type ExtensionResponse<T extends ExtensionMessage> =
52
+ T extends { type: 'SUMMARIZE_PAGE' } ? { summary: string; error?: string } :
53
+ T extends { type: 'GET_SETTINGS' } ? { settings: Settings } :
54
+ T extends { type: 'UPDATE_SETTINGS' } ? { ok: boolean } :
55
+ T extends { type: 'OPEN_SIDEBAR' } ? { ok: boolean } :
56
+ never;
57
+
58
+ export interface Settings {
59
+ useBuiltinAI: boolean;
60
+ externalApiKey: string;
61
+ maxLength: number;
62
+ }
63
+ ```
64
+
65
+ ```typescript
66
+ // background.ts — typed message handler
67
+ import type { ExtensionMessage } from './types/messages';
68
+
69
+ chrome.runtime.onMessage.addListener(
70
+ (message: ExtensionMessage, sender, sendResponse) => {
71
+ // CRITICAL: return true to keep channel open for async response
72
+ (async () => {
73
+ try {
74
+ switch (message.type) {
75
+ case 'SUMMARIZE_PAGE': {
76
+ const summary = await summarize(message.payload.text);
77
+ sendResponse({ summary });
78
+ break;
79
+ }
80
+ case 'GET_SETTINGS': {
81
+ const result = await chrome.storage.sync.get('settings');
82
+ sendResponse({ settings: result['settings'] as Settings });
83
+ break;
84
+ }
85
+ default:
86
+ sendResponse({ error: 'Unknown message type' });
87
+ }
88
+ } catch (err) {
89
+ // Chrome 146+: send error response instead of letting it throw
90
+ sendResponse({ error: String(err) });
91
+ }
92
+ })();
93
+ return true; // MUST return true — async response
94
+ }
95
+ );
96
+ ```
97
+
98
+ ```typescript
99
+ // Port-based streaming (service worker → sidebar/popup)
100
+ // background.ts
101
+ chrome.runtime.onConnect.addListener((port) => {
102
+ if (port.name !== 'ai-stream') return;
103
+
104
+ port.onMessage.addListener(async (message: { text: string }) => {
105
+ try {
106
+ const session = await chrome.aiLanguageModel.create();
107
+ const stream = session.promptStreaming(message.text);
108
+
109
+ for await (const chunk of stream) {
110
+ port.postMessage({ type: 'CHUNK', content: chunk });
111
+ }
112
+ port.postMessage({ type: 'DONE' });
113
+ session.destroy();
114
+ } catch (err) {
115
+ port.postMessage({ type: 'ERROR', error: String(err) });
116
+ }
117
+ });
118
+
119
+ port.onDisconnect.addListener(() => {
120
+ // cleanup — sidebar/popup was closed
121
+ });
122
+ });
123
+
124
+ // sidebar.ts — connect and stream
125
+ const port = chrome.runtime.connect({ name: 'ai-stream' });
126
+ port.postMessage({ text: selectedText });
127
+
128
+ port.onMessage.addListener((msg: { type: string; content?: string; error?: string }) => {
129
+ if (msg.type === 'CHUNK') appendToOutput(msg.content ?? '');
130
+ if (msg.type === 'DONE') finalizeOutput();
131
+ if (msg.type === 'ERROR') showError(msg.error ?? 'Unknown error');
132
+ });
133
+
134
+ port.onDisconnect.addListener(() => {
135
+ if (chrome.runtime.lastError) {
136
+ console.error('[Sidebar] Port disconnected with error:', chrome.runtime.lastError.message);
137
+ }
138
+ });
139
+ ```
@@ -1,133 +1,133 @@
1
- ---
2
- name: "ext-storage"
3
- pack: "@rune/chrome-ext"
4
- description: "Typed Chrome storage patterns — choose the right storage tier, define schema, implement typed helpers, handle schema migrations, and monitor quota."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # ext-storage
10
-
11
- Typed Chrome storage patterns — choose the right storage tier, define schema, implement typed helpers, handle schema migrations, and monitor quota. Prevents the #3 MV3 failure: storing state in service worker JS variables that reset on termination.
12
-
13
- #### Workflow
14
-
15
- **Step 1 — Choose storage type**
16
- | Type | Capacity | Persistence | Sync | Use For |
17
- |------|----------|-------------|------|---------|
18
- | `chrome.storage.local` | 10 MB | Until uninstall | No | User data, large payloads, cached content |
19
- | `chrome.storage.sync` | 100 KB / 8 KB per item | Cross-device | Yes | Settings, small preferences |
20
- | `chrome.storage.session` | 10 MB | Until browser closes | No | Ephemeral state that service worker needs across terminations |
21
- | `chrome.storage.managed` | Read-only | Admin-controlled | No | Enterprise policy |
22
-
23
- CRITICAL: `chrome.storage.session` is the correct replacement for service worker JS variables. If you need state to survive a 30-second termination but clear on browser close, use session storage.
24
-
25
- **Step 2 — Define TypeScript storage schema**
26
- Create `src/types/storage.ts` with versioned schema interface. Include a `version` field for migration tracking.
27
-
28
- **Step 3 — Implement typed get/set helpers**
29
- Create `src/lib/storage.ts` with typed wrappers that preserve the schema type. Avoid `chrome.storage.*.get(null)` which returns `any` — always specify keys.
30
-
31
- **Step 4 — Add migration logic**
32
- On `chrome.runtime.onInstalled` with `reason === 'update'`, check stored schema version and run incremental migrations. Each migration transforms data from version N to N+1.
33
-
34
- **Step 5 — Implement quota monitoring**
35
- Chrome storage has hard limits that throw `QUOTA_BYTES_PER_ITEM` and `QUOTA_BYTES` errors on write. Wrap all writes with error handling and warn the user or prune old data when approaching 80% capacity.
36
-
37
- #### Example
38
-
39
- ```typescript
40
- // src/types/storage.ts — versioned storage schema
41
- export const STORAGE_VERSION = 2;
42
-
43
- export interface StorageSchema {
44
- version: number;
45
- settings: {
46
- useBuiltinAI: boolean;
47
- externalApiKey: string;
48
- maxLength: number;
49
- theme: 'light' | 'dark' | 'system';
50
- };
51
- cache: {
52
- lastSummary: string;
53
- lastUrl: string;
54
- timestamp: number;
55
- } | null;
56
- }
57
-
58
- export const STORAGE_DEFAULTS: StorageSchema = {
59
- version: STORAGE_VERSION,
60
- settings: {
61
- useBuiltinAI: true,
62
- externalApiKey: '',
63
- maxLength: 500,
64
- theme: 'system',
65
- },
66
- cache: null,
67
- };
68
- ```
69
-
70
- ```typescript
71
- // src/lib/storage.ts — typed get/set helpers with quota monitoring
72
-
73
- import type { StorageSchema } from '../types/storage';
74
- import { STORAGE_DEFAULTS, STORAGE_VERSION } from '../types/storage';
75
-
76
- type StorageKey = keyof StorageSchema;
77
-
78
- export async function storageGet<K extends StorageKey>(
79
- key: K
80
- ): Promise<StorageSchema[K]> {
81
- const result = await chrome.storage.local.get(key);
82
- return (result[key] as StorageSchema[K]) ?? STORAGE_DEFAULTS[key];
83
- }
84
-
85
- export async function storageSet<K extends StorageKey>(
86
- key: K,
87
- value: StorageSchema[K]
88
- ): Promise<void> {
89
- try {
90
- await chrome.storage.local.set({ [key]: value });
91
- } catch (err) {
92
- const error = err as Error;
93
- if (error.message.includes('QUOTA_BYTES')) {
94
- console.warn('[Storage] Quota exceeded — clearing cache');
95
- await chrome.storage.local.remove('cache');
96
- // retry once after clearing cache
97
- await chrome.storage.local.set({ [key]: value });
98
- } else {
99
- throw err;
100
- }
101
- }
102
- }
103
-
104
- // Quota monitoring — warn at 80% capacity
105
- export async function checkStorageQuota(): Promise<void> {
106
- const bytesUsed = await chrome.storage.local.getBytesInUse(null);
107
- const quota = chrome.storage.local.QUOTA_BYTES; // 10 MB = 10,485,760 bytes
108
- const pct = (bytesUsed / quota) * 100;
109
- if (pct > 80) {
110
- console.warn(`[Storage] ${pct.toFixed(1)}% of local storage used (${bytesUsed} / ${quota} bytes)`);
111
- }
112
- }
113
-
114
- // Migration runner — call on onInstalled with reason='update'
115
- export async function runMigrations(): Promise<void> {
116
- const stored = await chrome.storage.local.get('version');
117
- const currentVersion = (stored['version'] as number | undefined) ?? 1;
118
-
119
- if (currentVersion < 2) {
120
- // v1 → v2: renamed 'apiKey' to 'externalApiKey'
121
- const legacy = await chrome.storage.local.get('settings');
122
- const legacySettings = legacy['settings'] as Record<string, unknown> | undefined;
123
- if (legacySettings?.['apiKey']) {
124
- await chrome.storage.local.set({
125
- settings: { ...legacySettings, externalApiKey: legacySettings['apiKey'], apiKey: undefined },
126
- version: 2,
127
- });
128
- }
129
- }
130
-
131
- await chrome.storage.local.set({ version: STORAGE_VERSION });
132
- }
133
- ```
1
+ ---
2
+ name: "ext-storage"
3
+ pack: "@rune/chrome-ext"
4
+ description: "Typed Chrome storage patterns — choose the right storage tier, define schema, implement typed helpers, handle schema migrations, and monitor quota."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # ext-storage
10
+
11
+ Typed Chrome storage patterns — choose the right storage tier, define schema, implement typed helpers, handle schema migrations, and monitor quota. Prevents the #3 MV3 failure: storing state in service worker JS variables that reset on termination.
12
+
13
+ #### Workflow
14
+
15
+ **Step 1 — Choose storage type**
16
+ | Type | Capacity | Persistence | Sync | Use For |
17
+ |------|----------|-------------|------|---------|
18
+ | `chrome.storage.local` | 10 MB | Until uninstall | No | User data, large payloads, cached content |
19
+ | `chrome.storage.sync` | 100 KB / 8 KB per item | Cross-device | Yes | Settings, small preferences |
20
+ | `chrome.storage.session` | 10 MB | Until browser closes | No | Ephemeral state that service worker needs across terminations |
21
+ | `chrome.storage.managed` | Read-only | Admin-controlled | No | Enterprise policy |
22
+
23
+ CRITICAL: `chrome.storage.session` is the correct replacement for service worker JS variables. If you need state to survive a 30-second termination but clear on browser close, use session storage.
24
+
25
+ **Step 2 — Define TypeScript storage schema**
26
+ Create `src/types/storage.ts` with versioned schema interface. Include a `version` field for migration tracking.
27
+
28
+ **Step 3 — Implement typed get/set helpers**
29
+ Create `src/lib/storage.ts` with typed wrappers that preserve the schema type. Avoid `chrome.storage.*.get(null)` which returns `any` — always specify keys.
30
+
31
+ **Step 4 — Add migration logic**
32
+ On `chrome.runtime.onInstalled` with `reason === 'update'`, check stored schema version and run incremental migrations. Each migration transforms data from version N to N+1.
33
+
34
+ **Step 5 — Implement quota monitoring**
35
+ Chrome storage has hard limits that throw `QUOTA_BYTES_PER_ITEM` and `QUOTA_BYTES` errors on write. Wrap all writes with error handling and warn the user or prune old data when approaching 80% capacity.
36
+
37
+ #### Example
38
+
39
+ ```typescript
40
+ // src/types/storage.ts — versioned storage schema
41
+ export const STORAGE_VERSION = 2;
42
+
43
+ export interface StorageSchema {
44
+ version: number;
45
+ settings: {
46
+ useBuiltinAI: boolean;
47
+ externalApiKey: string;
48
+ maxLength: number;
49
+ theme: 'light' | 'dark' | 'system';
50
+ };
51
+ cache: {
52
+ lastSummary: string;
53
+ lastUrl: string;
54
+ timestamp: number;
55
+ } | null;
56
+ }
57
+
58
+ export const STORAGE_DEFAULTS: StorageSchema = {
59
+ version: STORAGE_VERSION,
60
+ settings: {
61
+ useBuiltinAI: true,
62
+ externalApiKey: '',
63
+ maxLength: 500,
64
+ theme: 'system',
65
+ },
66
+ cache: null,
67
+ };
68
+ ```
69
+
70
+ ```typescript
71
+ // src/lib/storage.ts — typed get/set helpers with quota monitoring
72
+
73
+ import type { StorageSchema } from '../types/storage';
74
+ import { STORAGE_DEFAULTS, STORAGE_VERSION } from '../types/storage';
75
+
76
+ type StorageKey = keyof StorageSchema;
77
+
78
+ export async function storageGet<K extends StorageKey>(
79
+ key: K
80
+ ): Promise<StorageSchema[K]> {
81
+ const result = await chrome.storage.local.get(key);
82
+ return (result[key] as StorageSchema[K]) ?? STORAGE_DEFAULTS[key];
83
+ }
84
+
85
+ export async function storageSet<K extends StorageKey>(
86
+ key: K,
87
+ value: StorageSchema[K]
88
+ ): Promise<void> {
89
+ try {
90
+ await chrome.storage.local.set({ [key]: value });
91
+ } catch (err) {
92
+ const error = err as Error;
93
+ if (error.message.includes('QUOTA_BYTES')) {
94
+ console.warn('[Storage] Quota exceeded — clearing cache');
95
+ await chrome.storage.local.remove('cache');
96
+ // retry once after clearing cache
97
+ await chrome.storage.local.set({ [key]: value });
98
+ } else {
99
+ throw err;
100
+ }
101
+ }
102
+ }
103
+
104
+ // Quota monitoring — warn at 80% capacity
105
+ export async function checkStorageQuota(): Promise<void> {
106
+ const bytesUsed = await chrome.storage.local.getBytesInUse(null);
107
+ const quota = chrome.storage.local.QUOTA_BYTES; // 10 MB = 10,485,760 bytes
108
+ const pct = (bytesUsed / quota) * 100;
109
+ if (pct > 80) {
110
+ console.warn(`[Storage] ${pct.toFixed(1)}% of local storage used (${bytesUsed} / ${quota} bytes)`);
111
+ }
112
+ }
113
+
114
+ // Migration runner — call on onInstalled with reason='update'
115
+ export async function runMigrations(): Promise<void> {
116
+ const stored = await chrome.storage.local.get('version');
117
+ const currentVersion = (stored['version'] as number | undefined) ?? 1;
118
+
119
+ if (currentVersion < 2) {
120
+ // v1 → v2: renamed 'apiKey' to 'externalApiKey'
121
+ const legacy = await chrome.storage.local.get('settings');
122
+ const legacySettings = legacy['settings'] as Record<string, unknown> | undefined;
123
+ if (legacySettings?.['apiKey']) {
124
+ await chrome.storage.local.set({
125
+ settings: { ...legacySettings, externalApiKey: legacySettings['apiKey'], apiKey: undefined },
126
+ version: 2,
127
+ });
128
+ }
129
+ }
130
+
131
+ await chrome.storage.local.set({ version: STORAGE_VERSION });
132
+ }
133
+ ```