@rune-kit/rune 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +40 -34
  2. package/compiler/__tests__/pack-split.test.js +145 -0
  3. package/compiler/bin/rune.js +0 -5
  4. package/compiler/doctor.js +42 -0
  5. package/compiler/emitter.js +27 -4
  6. package/compiler/parser.js +41 -3
  7. package/compiler/transformer.js +10 -6
  8. package/compiler/transforms/compliance.js +40 -0
  9. package/extensions/ai-ml/PACK.md +38 -474
  10. package/extensions/ai-ml/skills/ai-agents.md +172 -0
  11. package/extensions/ai-ml/skills/code-sandbox.md +187 -0
  12. package/extensions/ai-ml/skills/deep-research.md +146 -0
  13. package/extensions/ai-ml/skills/embedding-search.md +66 -0
  14. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -0
  15. package/extensions/ai-ml/skills/llm-architect.md +125 -0
  16. package/extensions/ai-ml/skills/llm-integration.md +64 -0
  17. package/extensions/ai-ml/skills/prompt-patterns.md +72 -0
  18. package/extensions/ai-ml/skills/rag-patterns.md +66 -0
  19. package/extensions/ai-ml/skills/web-extraction.md +114 -0
  20. package/extensions/analytics/PACK.md +19 -484
  21. package/extensions/analytics/skills/ab-testing.md +72 -0
  22. package/extensions/analytics/skills/dashboard-patterns.md +83 -0
  23. package/extensions/analytics/skills/data-validation.md +68 -0
  24. package/extensions/analytics/skills/funnel-analysis.md +81 -0
  25. package/extensions/analytics/skills/sql-patterns.md +57 -0
  26. package/extensions/analytics/skills/statistical-analysis.md +79 -0
  27. package/extensions/analytics/skills/tracking-setup.md +71 -0
  28. package/extensions/backend/PACK.md +44 -618
  29. package/extensions/backend/skills/api-patterns.md +84 -0
  30. package/extensions/backend/skills/async-pipeline.md +193 -0
  31. package/extensions/backend/skills/auth-patterns.md +97 -0
  32. package/extensions/backend/skills/background-jobs.md +133 -0
  33. package/extensions/backend/skills/caching-patterns.md +108 -0
  34. package/extensions/backend/skills/cli-generation.md +133 -0
  35. package/extensions/backend/skills/database-patterns.md +87 -0
  36. package/extensions/backend/skills/middleware-patterns.md +104 -0
  37. package/extensions/chrome-ext/PACK.md +19 -921
  38. package/extensions/chrome-ext/skills/cws-preflight.md +143 -0
  39. package/extensions/chrome-ext/skills/cws-publish.md +104 -0
  40. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -0
  41. package/extensions/chrome-ext/skills/ext-messaging.md +139 -0
  42. package/extensions/chrome-ext/skills/ext-storage.md +133 -0
  43. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -0
  44. package/extensions/content/PACK.md +43 -335
  45. package/extensions/content/skills/blog-patterns.md +88 -0
  46. package/extensions/content/skills/cms-integration.md +131 -0
  47. package/extensions/content/skills/content-scoring.md +107 -0
  48. package/extensions/content/skills/i18n.md +83 -0
  49. package/extensions/content/skills/mdx-authoring.md +137 -0
  50. package/extensions/content/skills/reference.md +1014 -0
  51. package/extensions/content/skills/seo-patterns.md +67 -0
  52. package/extensions/content/skills/video-repurpose.md +153 -0
  53. package/extensions/devops/PACK.md +38 -457
  54. package/extensions/devops/skills/chaos-testing.md +67 -0
  55. package/extensions/devops/skills/ci-cd.md +75 -0
  56. package/extensions/devops/skills/docker.md +58 -0
  57. package/extensions/devops/skills/edge-serverless.md +163 -0
  58. package/extensions/devops/skills/infra-as-code.md +158 -0
  59. package/extensions/devops/skills/kubernetes.md +110 -0
  60. package/extensions/devops/skills/monitoring.md +57 -0
  61. package/extensions/devops/skills/server-setup.md +64 -0
  62. package/extensions/devops/skills/ssl-domain.md +42 -0
  63. package/extensions/ecommerce/PACK.md +62 -226
  64. package/extensions/ecommerce/skills/cart-system.md +79 -0
  65. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -0
  66. package/extensions/ecommerce/skills/order-management.md +126 -0
  67. package/extensions/ecommerce/skills/payment-integration.md +472 -0
  68. package/extensions/ecommerce/skills/shopify-dev.md +69 -0
  69. package/extensions/ecommerce/skills/subscription-billing.md +93 -0
  70. package/extensions/ecommerce/skills/tax-compliance.md +117 -0
  71. package/extensions/gamedev/PACK.md +66 -317
  72. package/extensions/gamedev/skills/asset-pipeline.md +74 -0
  73. package/extensions/gamedev/skills/audio-system.md +129 -0
  74. package/extensions/gamedev/skills/camera-system.md +87 -0
  75. package/extensions/gamedev/skills/ecs.md +98 -0
  76. package/extensions/gamedev/skills/game-loops.md +72 -0
  77. package/extensions/gamedev/skills/input-system.md +199 -0
  78. package/extensions/gamedev/skills/multiplayer.md +180 -0
  79. package/extensions/gamedev/skills/particles.md +105 -0
  80. package/extensions/gamedev/skills/physics-engine.md +89 -0
  81. package/extensions/gamedev/skills/scene-management.md +146 -0
  82. package/extensions/gamedev/skills/threejs-patterns.md +90 -0
  83. package/extensions/gamedev/skills/webgl.md +71 -0
  84. package/extensions/mobile/PACK.md +56 -223
  85. package/extensions/mobile/skills/app-store-connect.md +152 -0
  86. package/extensions/mobile/skills/app-store-prep.md +66 -0
  87. package/extensions/mobile/skills/deep-linking.md +109 -0
  88. package/extensions/mobile/skills/flutter.md +60 -0
  89. package/extensions/mobile/skills/ios-build-pipeline.md +142 -0
  90. package/extensions/mobile/skills/native-bridge.md +66 -0
  91. package/extensions/mobile/skills/ota-updates.md +97 -0
  92. package/extensions/mobile/skills/push-notifications.md +111 -0
  93. package/extensions/mobile/skills/react-native.md +82 -0
  94. package/extensions/saas/PACK.md +26 -720
  95. package/extensions/saas/skills/billing-integration.md +121 -0
  96. package/extensions/saas/skills/feature-flags.md +130 -0
  97. package/extensions/saas/skills/multi-tenant.md +103 -0
  98. package/extensions/saas/skills/onboarding-flow.md +139 -0
  99. package/extensions/saas/skills/subscription-flow.md +95 -0
  100. package/extensions/saas/skills/team-management.md +144 -0
  101. package/extensions/security/PACK.md +10 -448
  102. package/extensions/security/skills/api-security.md +140 -0
  103. package/extensions/security/skills/compliance.md +68 -0
  104. package/extensions/security/skills/owasp-audit.md +64 -0
  105. package/extensions/security/skills/pentest-patterns.md +77 -0
  106. package/extensions/security/skills/secret-mgmt.md +65 -0
  107. package/extensions/security/skills/supply-chain.md +65 -0
  108. package/extensions/trading/PACK.md +18 -535
  109. package/extensions/trading/skills/chart-components.md +55 -0
  110. package/extensions/trading/skills/experiment-loop.md +125 -0
  111. package/extensions/trading/skills/fintech-patterns.md +47 -0
  112. package/extensions/trading/skills/indicator-library.md +58 -0
  113. package/extensions/trading/skills/quant-analysis.md +111 -0
  114. package/extensions/trading/skills/realtime-data.md +58 -0
  115. package/extensions/trading/skills/trade-logic.md +104 -0
  116. package/extensions/ui/PACK.md +34 -853
  117. package/extensions/ui/skills/a11y-audit.md +91 -0
  118. package/extensions/ui/skills/animation-patterns.md +106 -0
  119. package/extensions/ui/skills/component-patterns.md +75 -0
  120. package/extensions/ui/skills/design-decision.md +98 -0
  121. package/extensions/ui/skills/design-system.md +68 -0
  122. package/extensions/ui/skills/landing-patterns.md +155 -0
  123. package/extensions/ui/skills/palette-picker.md +162 -0
  124. package/extensions/ui/skills/react-health.md +90 -0
  125. package/extensions/ui/skills/type-system.md +125 -0
  126. package/extensions/ui/skills/web-vitals.md +153 -0
  127. package/extensions/zalo/PACK.md +117 -0
  128. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -0
  129. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -0
  130. package/extensions/zalo/skills/zalo-oa-setup.md +236 -0
  131. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -0
  132. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -0
  133. package/extensions/zalo/skills/zalo-personal-setup.md +153 -0
  134. package/extensions/zalo/skills/zalo-rate-guard.md +219 -0
  135. package/package.json +1 -1
  136. package/skills/brainstorm/SKILL.md +63 -1
  137. package/skills/cook/SKILL.md +84 -5
  138. package/skills/mcp-builder/SKILL.md +48 -1
  139. package/skills/review/SKILL.md +42 -5
  140. package/skills/review-intake/SKILL.md +17 -1
  141. package/skills/skill-router/SKILL.md +89 -7
  142. package/skills/team/SKILL.md +24 -1
  143. package/skills/test/SKILL.md +18 -0
  144. package/skills/verification/SKILL.md +40 -1
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: "cws-preflight"
3
+ pack: "@rune/chrome-ext"
4
+ description: "Chrome Web Store compliance audit — scan for over-permissioning, remote code execution, CSP violations, missing assets, and generate permission justification text."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # cws-preflight
10
+
11
+ Chrome Web Store compliance audit — scan for over-permissioning, remote code execution, CSP violations, missing assets, and generate permission justification text. The highest-value skill in this pack: 58% of CWS rejections are preventable compliance errors caught here before submission.
12
+
13
+ **Top 5 CWS rejection reasons (2024 data):**
14
+ 1. Over-permissioning — requesting permissions not demonstrably used in submitted code
15
+ 2. Remote code execution — `eval()`, `Function()` constructor, CDN `<script>` tags, `import()` from external URLs
16
+ 3. Misleading description — functionality not matching store listing claims
17
+ 4. Missing or inaccessible privacy policy — required for any extension that handles user data
18
+ 5. Branding violations — trademarked names (Google, Chrome, YouTube) in extension name or icon
19
+
20
+ **Triggers for manual review (3+ weeks instead of 24-72h):**
21
+ - Broad `host_permissions` with `<all_urls>` or `https://*/*`
22
+ - Sensitive permission combinations: `tabs` + `history` + `cookies`
23
+ - New developer account submitting extension with sensitive permissions
24
+ - Relaxed `content_security_policy` (`unsafe-eval`, `unsafe-inline`)
25
+ - First submission of a new extension (always manual)
26
+
27
+ #### Workflow
28
+
29
+ **Step 1 — Lint manifest for over-permissioning**
30
+ Use `Read` on `manifest.json`. For each declared permission, verify it is actually used in source code with `Grep`. Flag any permission declared but not found in `*.ts` / `*.js` source files. Severity: HIGH.
31
+
32
+ Common over-permissioning patterns to flag:
33
+ - `"tabs"` declared when only `activeTab` is needed (activeTab is granted on user click, requires no declaration)
34
+ - `"history"` declared without `chrome.history.*` usage
35
+ - `"bookmarks"` declared without `chrome.bookmarks.*` usage
36
+ - `"<all_urls>"` in `host_permissions` when specific domains suffice
37
+ - `"cookies"` declared without `chrome.cookies.*` usage
38
+
39
+ **Step 2 — Scan for remote code execution**
40
+ Use `Grep` to find patterns that trigger automatic CWS rejection:
41
+
42
+ ```
43
+ pattern: "eval\s*\(" → remote code execution
44
+ pattern: "new Function\s*\(" → remote code execution
45
+ pattern: "<script[^>]+src=['\"]https?://" → remote script loading in HTML files
46
+ pattern: "import\s*\(['\"]https?://" → dynamic import from external URL
47
+ ```
48
+
49
+ Flag each result as CRITICAL — these cause automatic rejection with no appeal path.
50
+
51
+ **Step 3 — Validate Content Security Policy**
52
+ Read the `content_security_policy.extension_pages` value from `manifest.json`. Flag any of:
53
+ - `'unsafe-eval'` in `script-src` — allows eval, triggers rejection
54
+ - `'unsafe-inline'` in `script-src` — allows inline scripts, triggers rejection
55
+ - External domains in `script-src` (anything not `'self'`) — remote code execution risk
56
+ - Missing CSP entirely — defaults to `script-src 'self'` which is fine, but document it
57
+
58
+ **Step 4 — Verify privacy policy**
59
+ Check if the extension collects user data (network requests to external servers, `chrome.storage` usage, content script reading page content). If yes:
60
+ - Privacy policy URL must be set in CWS Developer Dashboard
61
+ - Privacy policy must be publicly accessible (verify URL is live)
62
+ - Generate a minimal privacy policy template if none exists
63
+
64
+ **Step 5 — Check required assets**
65
+ Verify the following exist at declared paths in `manifest.json`:
66
+ - Icon at 128×128px (required for store listing)
67
+ - Screenshots: at least 1, dimensions 1280×800 or 640×400 (PNG or JPEG)
68
+ - Promotional tile: 440×280px (optional but strongly recommended)
69
+ - All declared icons (16, 32, 48, 128px) present at referenced paths
70
+
71
+ Use `Glob` to verify file existence. Use `Bash` to check image dimensions with `file` or `identify` if ImageMagick is available.
72
+
73
+ **Step 6 — Generate permission justification text**
74
+ For each declared permission, generate CWS-ready justification text. The CWS dashboard requires one justification per permission. Justifications must be specific — "We need this to work" is rejected.
75
+
76
+ **Step 7 — Produce preflight report**
77
+ Write `.rune/chrome-ext/preflight-report.md` with:
78
+ - PASS / WARN / FAIL per check
79
+ - Specific file + line for each issue
80
+ - Fix instructions
81
+ - Estimated review timeline (fast-track vs manual review triggers)
82
+ - Submission checklist
83
+
84
+ #### Example
85
+
86
+ ```markdown
87
+ <!-- .rune/chrome-ext/preflight-report.md (generated by cws-preflight) -->
88
+
89
+ # CWS Preflight Report — Page Summarizer v1.0.0
90
+ Generated: 2026-03-12
91
+
92
+ ## Summary
93
+ | Check | Status | Issues |
94
+ |-------|--------|--------|
95
+ | Permissions audit | ⚠️ WARN | 1 over-permission |
96
+ | Remote code execution | ✅ PASS | None found |
97
+ | Content Security Policy | ✅ PASS | Correct default |
98
+ | Privacy policy | ⚠️ WARN | URL not set in manifest |
99
+ | Required assets | ✅ PASS | All present |
100
+ | Permission justifications | ✅ READY | Generated below |
101
+
102
+ ## Issues
103
+
104
+ ### WARN: Over-permission — `"tabs"` not required
105
+ **File**: manifest.json line 7
106
+ **Detail**: `"tabs"` permission is declared but no `chrome.tabs.*` API calls found in source.
107
+ The extension uses `activeTab` (implicit on action click) — remove `"tabs"` from permissions array.
108
+ **Fix**: Remove `"tabs"` from `"permissions"` array.
109
+
110
+ ### WARN: Privacy policy URL missing
111
+ **Detail**: Extension reads page content via content script (content.ts:L12 — `document.body.innerText`).
112
+ This constitutes user data handling and requires a privacy policy URL in the CWS Developer Dashboard.
113
+ **Fix**: Add privacy policy URL at publish time. Template: `.rune/chrome-ext/privacy-policy-template.md`
114
+
115
+ ## Permission Justifications (paste into CWS dashboard)
116
+
117
+ ### activeTab
118
+ "The extension reads the content of the current active tab when the user clicks the toolbar button
119
+ to initiate a summarization. No data is collected without explicit user action."
120
+
121
+ ### storage
122
+ "The extension stores user settings (AI preference, API key, summary length) locally to persist
123
+ preferences between browser sessions. No data is synced externally."
124
+
125
+ ### sidePanel
126
+ "The extension uses the Side Panel API to display AI-generated summaries in a persistent panel
127
+ without obscuring the page content."
128
+
129
+ ## Estimated Review Timeline
130
+ - No sensitive permissions detected
131
+ - No broad host_permissions
132
+ - Timeline: **24–72 hours** (standard review)
133
+ - Recommendation: submit Tuesday–Thursday for fastest turnaround
134
+
135
+ ## Submission Checklist
136
+ - [ ] Remove `"tabs"` from permissions array
137
+ - [ ] Add privacy policy URL to CWS Developer Dashboard
138
+ - [ ] Upload 1280×800 screenshot showing extension in use
139
+ - [ ] Write store description (min 132 chars for detailed description)
140
+ - [ ] Set category: Productivity
141
+ - [ ] Set language: English
142
+ - [ ] $5 one-time developer registration fee paid
143
+ ```
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: "cws-publish"
3
+ pack: "@rune/chrome-ext"
4
+ description: "Chrome Web Store listing preparation and submission guide — store listing copy, screenshot descriptions, permission justifications, visibility settings, and timeline expectations."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # cws-publish
10
+
11
+ Chrome Web Store listing preparation and submission guide — store listing copy, screenshot descriptions, permission justifications, visibility settings, and timeline expectations. Produces a ready-to-paste store listing document.
12
+
13
+ #### Workflow
14
+
15
+ **Step 1 — Verify preflight passed**
16
+ Check for `.rune/chrome-ext/preflight-report.md`. If it does not exist or contains FAIL items, halt and direct user to run `cws-preflight` first. WARN items should be reviewed and resolved before submission.
17
+
18
+ **Step 2 — Prepare store listing copy**
19
+ Generate CWS listing text following Google's constraints:
20
+ - **Name**: max 45 characters. Must not include trademarked names (Google, Chrome, YouTube, Gmail). Cannot include "Extension" (Chrome adds it automatically).
21
+ - **Short description**: max 132 characters. First thing users see in search results — front-load the value proposition.
22
+ - **Detailed description**: no hard limit but 400–800 words is optimal. Structure: opening hook (1 sentence) → feature bullets (5-7) → how it works (2-3 sentences) → privacy statement (1-2 sentences).
23
+ - Avoid keyword stuffing — Google's policy considers it spam.
24
+
25
+ **Step 3 — Generate screenshot descriptions**
26
+ CWS screenshots need captions (optional but recommended). Generate 3-5 screenshot scenarios showing distinct use cases. Each screenshot should be 1280×800 or 640×400 pixels, PNG or JPEG, <2MB.
27
+
28
+ **Step 4 — Fill permission justifications**
29
+ Pull from `cws-preflight` output. Each permission needs a one-paragraph justification in plain English. Write from the user's perspective: "This permission allows the extension to..." not "We need this to...".
30
+
31
+ **Step 5 — Choose visibility and distribution**
32
+ | Visibility | Use Case |
33
+ |------------|----------|
34
+ | Public | Visible in CWS search — default for most extensions |
35
+ | Unlisted | Direct URL only — good for beta testing with known users |
36
+ | Private | Team-only — enterprise internal tools |
37
+
38
+ Select distribution regions (default: all). Consider unlisted for v1.0 while gathering initial feedback, then switch to public after first positive reviews.
39
+
40
+ **Step 6 — Generate submission guide with timeline**
41
+ Emit `.rune/chrome-ext/store-listing.md` with all copy ready to paste. Include submission steps and timeline expectations.
42
+
43
+ **Timeline expectations:**
44
+ - Simple extension, experienced developer account, no sensitive permissions: **24–72 hours**
45
+ - Sensitive permissions (`tabs`, `history`, `cookies`, `management`): **3–7 business days**
46
+ - Broad `host_permissions` or first submission: **up to 3 weeks** (manual review queue)
47
+ - Rejection: **10-day resubmission window** after fixing issues; same review time applies
48
+
49
+ **Submission tips:**
50
+ - Never submit on Friday — reviewers are less available Mon-Tue; submit Tue-Thu
51
+ - Use `optional_permissions` for non-critical features — reduces barrier to install and CWS scrutiny
52
+ - `optional_host_permissions` can be requested at runtime, reducing declared permissions
53
+ - Version bump required for each resubmission after rejection
54
+ - Include a test account in submission notes if extension requires authentication
55
+
56
+ #### Example
57
+
58
+ ```markdown
59
+ <!-- .rune/chrome-ext/store-listing.md (generated by cws-publish) -->
60
+
61
+ # CWS Store Listing — Page Summarizer
62
+
63
+ ## Name (max 45 chars)
64
+ Page Summarizer — AI-Powered Summaries
65
+ (38 chars ✅)
66
+
67
+ ## Short Description (max 132 chars)
68
+ Summarize any webpage instantly with built-in Chrome AI. One click, no account required, no data sent externally.
69
+ (113 chars ✅)
70
+
71
+ ## Detailed Description
72
+ Tired of spending 10 minutes reading a page to find out it wasn't worth your time?
73
+
74
+ **Page Summarizer** gives you the core ideas of any webpage in seconds — powered by Chrome's built-in Gemini Nano model, which runs entirely on your device.
75
+
76
+ **Features:**
77
+ - One-click summarization — click the toolbar button or select text to summarize a section
78
+ - Built-in AI — no API key required, no data leaves your device (requires Chrome 138+ with AI hardware support)
79
+ - External API fallback — configure your own OpenAI or Anthropic key for older hardware
80
+ - Summary length control — short (100 words), medium (300 words), or detailed (500 words)
81
+ - Side panel view — summaries appear in a non-intrusive panel alongside the page
82
+ - Dark mode support
83
+
84
+ **How it works:**
85
+ Click the toolbar button on any page. The extension reads the visible text and generates a summary using the on-device Gemini Nano model. If your hardware does not support built-in AI, the extension falls back to an external API of your choice (optional — extension still works without it in built-in AI mode).
86
+
87
+ **Privacy:**
88
+ No user data is collected, stored, or transmitted without your action. Summaries generated via the built-in AI model never leave your device. External API calls (if configured) are made directly to the API provider — not through any intermediary server.
89
+
90
+ ## Screenshots (1280x800px)
91
+
92
+ 1. **Main Use** — Extension sidebar showing a 3-paragraph summary of a news article beside the original page.
93
+ 2. **Settings** — Settings panel showing AI model selector, API key field, and length preference.
94
+ 3. **Text Selection** — Right-click context menu on selected text showing "Summarize selection" option.
95
+
96
+ ## Category
97
+ Productivity
98
+
99
+ ## Language
100
+ English
101
+
102
+ ## Submission Notes (visible to reviewers, not users)
103
+ Test the extension on https://en.wikipedia.org/wiki/Artificial_intelligence — click the toolbar button to summarize. The extension requires Chrome 138+ for built-in AI. On older Chrome versions, configure an external API key in Settings to test the fallback path.
104
+ ```
@@ -0,0 +1,251 @@
1
+ ---
2
+ name: "ext-ai-integration"
3
+ pack: "@rune/chrome-ext"
4
+ description: "Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # ext-ai-integration
10
+
11
+ Chrome built-in AI and external API integration — detect AI type, check hardware requirements, implement Gemini Nano with graceful fallback, wire streaming responses via ports, handle rate limits, and test offline behavior. The differentiating skill for next-generation extensions.
12
+
13
+ **Chrome AI APIs (Chrome 138+ stable):**
14
+ | API | Namespace | Purpose |
15
+ |-----|-----------|---------|
16
+ | Prompt API | `chrome.aiLanguageModel` | General text generation, Q&A, classification |
17
+ | Summarizer | `chrome.aiSummarizer` | Condense long text |
18
+ | Writer | `chrome.aiWriter` | Generate new content from prompts |
19
+ | Rewriter | `chrome.aiRewriter` | Transform existing text (tone, length, format) |
20
+ | Translator | `chrome.aiTranslator` | Language translation |
21
+ | Language Detector | `chrome.aiLanguageDetector` | Detect text language |
22
+
23
+ **Hardware requirements for Gemini Nano:**
24
+ - Storage: 22 GB free disk space (model download)
25
+ - RAM: 4 GB VRAM (dedicated GPU) OR 16 GB system RAM (CPU inference)
26
+ - OS: macOS 13+, Windows 10/11 64-bit, ChromeOS (no Linux support)
27
+ - Cannot be checked programmatically — use capability API and handle `NotSupportedError`
28
+
29
+ **Manifest permission:**
30
+ ```json
31
+ { "permissions": ["aiLanguageModelParams"] }
32
+ ```
33
+
34
+ #### Workflow
35
+
36
+ **Step 1 — Detect AI integration type**
37
+ Use `Read` on existing source and `manifest.json` to determine:
38
+ - Does `"aiLanguageModelParams"` appear in permissions? → Built-in Nano intended
39
+ - Does code reference `openai`, `anthropic`, `fetch` to an external AI endpoint? → External API
40
+ - Neither? → Need to design integration from scratch
41
+
42
+ Ask the user: "Do you want to use Chrome's built-in Gemini Nano (no API cost, runs on device, requires Chrome 138+ and compatible hardware), an external API (OpenAI/Anthropic, requires API key and network), or both with automatic fallback?"
43
+
44
+ **Step 2 — Check hardware capability for Nano**
45
+ `chrome.aiLanguageModel.capabilities()` returns `{ available: 'readily' | 'after-download' | 'no' }`. Map these:
46
+ - `'readily'` → model is downloaded, use immediately
47
+ - `'after-download'` → model needs download (~2GB), show progress UI and wait
48
+ - `'no'` → hardware not supported, fall through to fallback
49
+
50
+ This check MUST happen in the service worker (not content script — restricted APIs). Cache the result in `chrome.storage.session` to avoid repeated capability checks.
51
+
52
+ **Step 3 — Implement with graceful fallback chain**
53
+ Fallback chain: Gemini Nano → External API → Static response
54
+
55
+ Each tier is a distinct function with the same signature. The orchestrator tries each in order, catching `NotSupportedError`, network errors, and quota errors.
56
+
57
+ **Step 4 — Wire streaming responses via port messaging**
58
+ AI streaming MUST use ports — not `sendMessage`. `sendMessage` is one-shot: the response is sent once and the channel closes. Streaming requires a port to send multiple `CHUNK` messages followed by a `DONE` message.
59
+
60
+ See `ext-messaging` skill for port setup. Streaming pattern:
61
+ 1. Sidebar/popup opens a port named `'ai-stream'`
62
+ 2. Sends `{ text: inputText }` to start generation
63
+ 3. Service worker receives, calls `session.promptStreaming()`
64
+ 4. For each chunk in the async iterator, posts `{ type: 'CHUNK', content: chunk }` back on the port
65
+ 5. On completion, posts `{ type: 'DONE' }` and calls `session.destroy()`
66
+
67
+ **Step 5 — Handle rate limits and quota**
68
+ Chrome built-in AI has per-session token limits. External APIs have rate limits and cost.
69
+ - Per session: call `session.destroy()` after each summary to free context window
70
+ - External API: implement exponential backoff on 429 responses (1s, 2s, 4s, cap 30s)
71
+ - User-facing: show token usage in settings panel if using external API
72
+
73
+ **Step 6 — Test offline behavior**
74
+ Extensions may run without network. Test:
75
+ - Built-in Nano: works offline (on-device model)
76
+ - External API: fails offline — catch `TypeError: Failed to fetch` and show "No network connection" message
77
+ - Storage: `chrome.storage.local` works offline
78
+ - Service worker: registers and responds to messages offline
79
+
80
+ #### Example
81
+
82
+ ```typescript
83
+ // src/lib/ai.ts — AI integration with graceful fallback
84
+ import { storageGet } from './storage';
85
+
86
+ export interface AiSummaryResult {
87
+ summary: string;
88
+ source: 'builtin' | 'external' | 'error';
89
+ error?: string;
90
+ }
91
+
92
+ // Check and cache Nano capability
93
+ export async function getNanoCapability(): Promise<'readily' | 'after-download' | 'no'> {
94
+ // Check session cache first (avoid repeated API calls)
95
+ const cached = await chrome.storage.session.get('nanoCapability');
96
+ if (cached['nanoCapability']) return cached['nanoCapability'] as 'readily' | 'after-download' | 'no';
97
+
98
+ const caps = await chrome.aiLanguageModel.capabilities();
99
+ await chrome.storage.session.set({ nanoCapability: caps.available });
100
+ return caps.available;
101
+ }
102
+
103
+ // Tier 1: Gemini Nano (built-in, on-device)
104
+ async function summarizeWithNano(text: string): Promise<string> {
105
+ const capability = await getNanoCapability();
106
+
107
+ if (capability === 'no') {
108
+ throw new Error('NotSupportedError: Built-in AI not available on this device');
109
+ }
110
+
111
+ if (capability === 'after-download') {
112
+ // Notify UI that model is downloading — caller can show progress
113
+ // Download starts automatically when create() is called
114
+ chrome.runtime.sendMessage({ type: 'AI_DOWNLOADING' });
115
+ }
116
+
117
+ const session = await chrome.aiLanguageModel.create({
118
+ systemPrompt: 'You are a concise summarizer. Summarize the provided text in 3-5 sentences.',
119
+ });
120
+
121
+ try {
122
+ const summary = await session.prompt(
123
+ `Summarize this text:\n\n${text.slice(0, 4000)}` // context window limit
124
+ );
125
+ return summary;
126
+ } finally {
127
+ session.destroy(); // always destroy to free resources
128
+ }
129
+ }
130
+
131
+ // Tier 2: External API (OpenAI-compatible)
132
+ async function summarizeWithExternalApi(text: string): Promise<string> {
133
+ const settings = await storageGet('settings');
134
+ if (!settings.externalApiKey) {
135
+ throw new Error('No external API key configured');
136
+ }
137
+
138
+ const controller = new AbortController();
139
+ const timeoutId = setTimeout(() => controller.abort(), 30_000);
140
+
141
+ try {
142
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
143
+ method: 'POST',
144
+ headers: {
145
+ 'Content-Type': 'application/json',
146
+ Authorization: `Bearer ${settings.externalApiKey}`,
147
+ },
148
+ body: JSON.stringify({
149
+ model: 'gpt-4o-mini',
150
+ messages: [
151
+ { role: 'system', content: 'Summarize the provided text in 3-5 sentences.' },
152
+ { role: 'user', content: text.slice(0, 8000) },
153
+ ],
154
+ max_tokens: 300,
155
+ }),
156
+ signal: controller.signal,
157
+ });
158
+
159
+ if (!response.ok) {
160
+ if (response.status === 429) throw new Error('RateLimitError');
161
+ throw new Error(`API error: ${response.status}`);
162
+ }
163
+
164
+ const data = await response.json() as {
165
+ choices: Array<{ message: { content: string } }>;
166
+ };
167
+ return data.choices[0]?.message.content ?? '';
168
+ } finally {
169
+ clearTimeout(timeoutId);
170
+ }
171
+ }
172
+
173
+ // Orchestrator — tries each tier in order
174
+ export async function summarize(text: string): Promise<AiSummaryResult> {
175
+ const settings = await storageGet('settings');
176
+
177
+ if (settings.useBuiltinAI) {
178
+ try {
179
+ const summary = await summarizeWithNano(text);
180
+ return { summary, source: 'builtin' };
181
+ } catch (err) {
182
+ console.warn('[AI] Nano failed, falling back to external API:', err);
183
+ }
184
+ }
185
+
186
+ if (settings.externalApiKey) {
187
+ try {
188
+ const summary = await summarizeWithExternalApi(text);
189
+ return { summary, source: 'external' };
190
+ } catch (err) {
191
+ console.error('[AI] External API failed:', err);
192
+ return {
193
+ summary: '',
194
+ source: 'error',
195
+ error: err instanceof Error ? err.message : 'Unknown error',
196
+ };
197
+ }
198
+ }
199
+
200
+ return {
201
+ summary: '',
202
+ source: 'error',
203
+ error: 'No AI source available. Enable built-in AI or configure an external API key in Settings.',
204
+ };
205
+ }
206
+ ```
207
+
208
+ ```typescript
209
+ // Streaming with port (service worker side)
210
+ // background.ts
211
+ chrome.runtime.onConnect.addListener((port) => {
212
+ if (port.name !== 'ai-stream') return;
213
+
214
+ let session: chrome.aiLanguageModel.LanguageModel | null = null;
215
+
216
+ port.onMessage.addListener(async (message: { text: string }) => {
217
+ try {
218
+ const capability = await getNanoCapability();
219
+ if (capability === 'no') throw new Error('NotSupportedError');
220
+
221
+ session = await chrome.aiLanguageModel.create({
222
+ systemPrompt: 'Summarize concisely.',
223
+ });
224
+
225
+ const stream = session.promptStreaming(
226
+ `Summarize:\n\n${message.text.slice(0, 4000)}`
227
+ );
228
+
229
+ let previous = '';
230
+ for await (const chunk of stream) {
231
+ // Chrome's streaming returns cumulative text — extract the delta
232
+ const delta = chunk.slice(previous.length);
233
+ previous = chunk;
234
+ port.postMessage({ type: 'CHUNK', content: delta });
235
+ }
236
+
237
+ port.postMessage({ type: 'DONE' });
238
+ } catch (err) {
239
+ port.postMessage({ type: 'ERROR', error: String(err) });
240
+ } finally {
241
+ session?.destroy();
242
+ session = null;
243
+ }
244
+ });
245
+
246
+ port.onDisconnect.addListener(() => {
247
+ session?.destroy();
248
+ session = null;
249
+ });
250
+ });
251
+ ```
@@ -0,0 +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
+ ```