@rune-kit/rune 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -40
- package/compiler/__tests__/pack-split.test.js +145 -0
- package/compiler/bin/rune.js +26 -9
- package/compiler/doctor.js +42 -0
- package/compiler/emitter.js +27 -4
- package/compiler/parser.js +41 -3
- package/compiler/transformer.js +10 -6
- package/compiler/transforms/compliance.js +40 -0
- package/docs/ANTIGRAVITY-GAP-ANALYSIS.md +369 -0
- package/docs/ARCHITECTURE.md +332 -0
- package/docs/COMMUNITY-PACKS.md +109 -0
- package/docs/CONTRIBUTING-L4.md +215 -0
- package/docs/CROSS-IDE-ANALYSIS.md +164 -0
- package/docs/EXTENSION-TEMPLATE.md +108 -0
- package/docs/MESH-RULES.md +34 -0
- package/docs/MULTI-PLATFORM.md +804 -0
- package/docs/SKILL-DEPTH-AUDIT.md +191 -0
- package/docs/SKILL-TEMPLATE.md +72 -0
- package/docs/TRADE-MATRIX.md +327 -0
- package/docs/VERSIONING.md +91 -0
- package/docs/VISION.md +263 -0
- package/docs/assets/demo-subtitles.srt +215 -0
- package/docs/assets/end-card.html +276 -0
- package/docs/assets/mesh-diagram.html +654 -0
- package/docs/assets/thumbnail.html +295 -0
- package/docs/guides/cli.md +403 -0
- package/docs/guides/index.html +1346 -0
- package/docs/index.html +674 -0
- package/docs/references/claudekit-analysis.md +414 -0
- package/docs/references/voltagent-analysis.md +189 -0
- package/docs/script.js +277 -0
- package/docs/skills/index.html +832 -0
- package/docs/style.css +583 -0
- package/docs/video-demo-plan.md +172 -0
- package/extensions/ai-ml/PACK.md +38 -474
- package/extensions/ai-ml/skills/ai-agents.md +172 -0
- package/extensions/ai-ml/skills/code-sandbox.md +187 -0
- package/extensions/ai-ml/skills/deep-research.md +146 -0
- package/extensions/ai-ml/skills/embedding-search.md +66 -0
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -0
- package/extensions/ai-ml/skills/llm-architect.md +125 -0
- package/extensions/ai-ml/skills/llm-integration.md +64 -0
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -0
- package/extensions/ai-ml/skills/rag-patterns.md +66 -0
- package/extensions/ai-ml/skills/web-extraction.md +114 -0
- package/extensions/analytics/PACK.md +19 -484
- package/extensions/analytics/skills/ab-testing.md +72 -0
- package/extensions/analytics/skills/dashboard-patterns.md +83 -0
- package/extensions/analytics/skills/data-validation.md +68 -0
- package/extensions/analytics/skills/funnel-analysis.md +81 -0
- package/extensions/analytics/skills/sql-patterns.md +57 -0
- package/extensions/analytics/skills/statistical-analysis.md +79 -0
- package/extensions/analytics/skills/tracking-setup.md +71 -0
- package/extensions/backend/PACK.md +44 -618
- package/extensions/backend/skills/api-patterns.md +84 -0
- package/extensions/backend/skills/async-pipeline.md +193 -0
- package/extensions/backend/skills/auth-patterns.md +97 -0
- package/extensions/backend/skills/background-jobs.md +133 -0
- package/extensions/backend/skills/caching-patterns.md +108 -0
- package/extensions/backend/skills/cli-generation.md +133 -0
- package/extensions/backend/skills/database-patterns.md +87 -0
- package/extensions/backend/skills/middleware-patterns.md +104 -0
- package/extensions/chrome-ext/PACK.md +19 -921
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -0
- package/extensions/chrome-ext/skills/cws-publish.md +104 -0
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -0
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -0
- package/extensions/chrome-ext/skills/ext-storage.md +133 -0
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -0
- package/extensions/content/PACK.md +43 -335
- package/extensions/content/skills/blog-patterns.md +88 -0
- package/extensions/content/skills/cms-integration.md +131 -0
- package/extensions/content/skills/content-scoring.md +107 -0
- package/extensions/content/skills/i18n.md +83 -0
- package/extensions/content/skills/mdx-authoring.md +137 -0
- package/extensions/content/skills/reference.md +1014 -0
- package/extensions/content/skills/seo-patterns.md +67 -0
- package/extensions/content/skills/video-repurpose.md +153 -0
- package/extensions/devops/PACK.md +38 -457
- package/extensions/devops/skills/chaos-testing.md +67 -0
- package/extensions/devops/skills/ci-cd.md +75 -0
- package/extensions/devops/skills/docker.md +58 -0
- package/extensions/devops/skills/edge-serverless.md +163 -0
- package/extensions/devops/skills/infra-as-code.md +158 -0
- package/extensions/devops/skills/kubernetes.md +110 -0
- package/extensions/devops/skills/monitoring.md +57 -0
- package/extensions/devops/skills/server-setup.md +64 -0
- package/extensions/devops/skills/ssl-domain.md +42 -0
- package/extensions/ecommerce/PACK.md +62 -226
- package/extensions/ecommerce/skills/cart-system.md +79 -0
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -0
- package/extensions/ecommerce/skills/order-management.md +126 -0
- package/extensions/ecommerce/skills/payment-integration.md +472 -0
- package/extensions/ecommerce/skills/shopify-dev.md +69 -0
- package/extensions/ecommerce/skills/subscription-billing.md +93 -0
- package/extensions/ecommerce/skills/tax-compliance.md +117 -0
- package/extensions/gamedev/PACK.md +66 -317
- package/extensions/gamedev/skills/asset-pipeline.md +74 -0
- package/extensions/gamedev/skills/audio-system.md +129 -0
- package/extensions/gamedev/skills/camera-system.md +87 -0
- package/extensions/gamedev/skills/ecs.md +98 -0
- package/extensions/gamedev/skills/game-loops.md +72 -0
- package/extensions/gamedev/skills/input-system.md +199 -0
- package/extensions/gamedev/skills/multiplayer.md +180 -0
- package/extensions/gamedev/skills/particles.md +105 -0
- package/extensions/gamedev/skills/physics-engine.md +89 -0
- package/extensions/gamedev/skills/scene-management.md +146 -0
- package/extensions/gamedev/skills/threejs-patterns.md +90 -0
- package/extensions/gamedev/skills/webgl.md +71 -0
- package/extensions/mobile/PACK.md +56 -223
- package/extensions/mobile/skills/app-store-connect.md +152 -0
- package/extensions/mobile/skills/app-store-prep.md +66 -0
- package/extensions/mobile/skills/deep-linking.md +109 -0
- package/extensions/mobile/skills/flutter.md +60 -0
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -0
- package/extensions/mobile/skills/native-bridge.md +66 -0
- package/extensions/mobile/skills/ota-updates.md +97 -0
- package/extensions/mobile/skills/push-notifications.md +111 -0
- package/extensions/mobile/skills/react-native.md +82 -0
- package/extensions/saas/PACK.md +26 -720
- package/extensions/saas/skills/billing-integration.md +121 -0
- package/extensions/saas/skills/feature-flags.md +130 -0
- package/extensions/saas/skills/multi-tenant.md +103 -0
- package/extensions/saas/skills/onboarding-flow.md +139 -0
- package/extensions/saas/skills/subscription-flow.md +95 -0
- package/extensions/saas/skills/team-management.md +144 -0
- package/extensions/security/PACK.md +10 -448
- package/extensions/security/skills/api-security.md +140 -0
- package/extensions/security/skills/compliance.md +68 -0
- package/extensions/security/skills/owasp-audit.md +64 -0
- package/extensions/security/skills/pentest-patterns.md +77 -0
- package/extensions/security/skills/secret-mgmt.md +65 -0
- package/extensions/security/skills/supply-chain.md +65 -0
- package/extensions/trading/PACK.md +18 -535
- package/extensions/trading/skills/chart-components.md +55 -0
- package/extensions/trading/skills/experiment-loop.md +125 -0
- package/extensions/trading/skills/fintech-patterns.md +47 -0
- package/extensions/trading/skills/indicator-library.md +58 -0
- package/extensions/trading/skills/quant-analysis.md +111 -0
- package/extensions/trading/skills/realtime-data.md +58 -0
- package/extensions/trading/skills/trade-logic.md +104 -0
- package/extensions/ui/PACK.md +36 -853
- package/extensions/ui/skills/a11y-audit.md +91 -0
- package/extensions/ui/skills/animation-patterns.md +106 -0
- package/extensions/ui/skills/component-patterns.md +75 -0
- package/extensions/ui/skills/design-decision.md +108 -0
- package/extensions/ui/skills/design-system.md +68 -0
- package/extensions/ui/skills/landing-patterns.md +155 -0
- package/extensions/ui/skills/palette-picker.md +173 -0
- package/extensions/ui/skills/react-health.md +90 -0
- package/extensions/ui/skills/type-system.md +125 -0
- package/extensions/ui/skills/web-vitals.md +153 -0
- package/extensions/zalo/PACK.md +117 -0
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -0
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -0
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -0
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -0
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -0
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -0
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/auto-format/index.cjs +48 -0
- package/hooks/context-watch/index.cjs +68 -0
- package/hooks/hooks.json +99 -0
- package/hooks/metrics-collector/index.cjs +42 -0
- package/hooks/post-session-reflect/index.cjs +153 -0
- package/hooks/pre-compact/index.cjs +95 -0
- package/hooks/pre-tool-guard/index.cjs +68 -0
- package/hooks/run-hook +17 -0
- package/hooks/run-hook.cjs +16 -0
- package/hooks/run-hook.cmd +1 -0
- package/hooks/secrets-scan/index.cjs +100 -0
- package/hooks/session-start/index.cjs +65 -0
- package/hooks/typecheck/index.cjs +65 -0
- package/package.json +9 -4
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -0
- package/references/ui-pro-max-data/charts.csv +26 -0
- package/references/ui-pro-max-data/colors.csv +162 -0
- package/references/ui-pro-max-data/styles.csv +85 -0
- package/references/ui-pro-max-data/typography.csv +74 -0
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -0
- package/references/ui-pro-max-data/ux-guidelines.csv +100 -0
- package/skills/ba/SKILL.md +10 -0
- package/skills/brainstorm/SKILL.md +63 -1
- package/skills/completion-gate/SKILL.md +34 -1
- package/skills/context-engine/SKILL.md +13 -0
- package/skills/cook/SKILL.md +155 -5
- package/skills/debug/SKILL.md +56 -1
- package/skills/design/SKILL.md +11 -0
- package/skills/fix/SKILL.md +26 -1
- package/skills/mcp-builder/SKILL.md +48 -1
- package/skills/plan/SKILL.md +23 -6
- package/skills/review/SKILL.md +44 -5
- package/skills/review-intake/SKILL.md +17 -1
- package/skills/skill-forge/SKILL.md +38 -3
- package/skills/skill-router/SKILL.md +89 -7
- package/skills/team/SKILL.md +24 -1
- package/skills/test/SKILL.md +28 -1
- package/skills/verification/SKILL.md +98 -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
|
+
```
|