@extension.dev/mcp 10.3.0 → 10.4.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.
@@ -10,7 +10,7 @@
10
10
  "name": "extension-mcp",
11
11
  "source": "./",
12
12
  "description": "MCP tools for browser extension development: scaffold from 50+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox.",
13
- "version": "10.3.0",
13
+ "version": "10.4.0",
14
14
  "category": "development",
15
15
  "author": {
16
16
  "name": "Cezar Augusto"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "extension-mcp",
3
3
  "description": "MCP tools for browser extension development: scaffold from 50+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox. Ships /extension, /extension-add, /extension-debug, and /extension-publish commands.",
4
- "version": "10.3.0",
4
+ "version": "10.4.0",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 10.4.0
4
+
5
+ Every place a tool named a template, it pointed the caller at GitHub and
6
+ never at the catalog that serves the same template with screenshots,
7
+ metadata and a deploy button. The MCP was routing around its own front
8
+ door. Template emissions now carry the catalog detail URL alongside the
9
+ existing fields, and the offline fallback corpus moved to the commit the
10
+ catalog actually serves.
11
+
12
+ - `extension_templates` list results gain `catalogUrl`,
13
+ `https://templates.extension.dev/<slug>?utm_source=mcp&utm_medium=tool`,
14
+ beside `repositoryUrl` and `downloads`, which are unchanged. The source
15
+ view and `extension_add_feature`'s reference template carry the same
16
+ field.
17
+ - `extension_create` results gain `templateCatalogUrl` for the template
18
+ the project was scaffolded from, with `utm_source=mcp-create` so
19
+ create-path visits stay distinguishable from browse-path visits.
20
+ - The pinned fallback template corpus moved to `52c0d871c433`, the commit
21
+ the live `latest` channel pointer resolves to, so the offline corpus and
22
+ the catalog agree.
23
+
24
+ ## 10.3.1
25
+
26
+ The 10.3.0 tarball shipped two comments a stranger could read: an HTML
27
+ comment in the popup markup naming a private package, and an eight line
28
+ internal roadmap comment plus two inline script comments in the live
29
+ preview sandbox page. Both were removed at source right after that
30
+ release. This release exists to take them off the registry, because a
31
+ published tarball cannot be edited in place.
32
+
33
+ - The popup markup no longer names any private package.
34
+ - `extensions/live-preview/chromium/sandbox/page-0.html` ships exactly
35
+ the markup it runs, with no comments at all.
36
+
3
37
  ## 10.3.0
4
38
 
5
39
  Four fixes for places where a tool answered confidently about something it
package/README.md CHANGED
@@ -127,7 +127,7 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
127
127
  | platform | `extension_shares` | List every link you have shared, and revoke one permanently |
128
128
  | platform | `extension_publish` | Publish a shareable preview to extension.dev |
129
129
  | platform | `extension_release_promote` | Promote a build to a release channel, headless |
130
- | platform | `extension_submit` | Submit for store review: Chrome, Firefox, Edge, Safari, through extension.dev |
130
+ | platform | `extension_submit` | Submit for store review: Chrome, Firefox and Edge, through extension.dev |
131
131
  | platform | `extension_release_status` | Read release channels, recent builds, and store submission and review state |
132
132
 
133
133
  Browser-launching tools (`dev`, `start`) shell out to the `extension` CLI, the project's own `node_modules/.bin/extension` when present, otherwise `npx extension@<pinned>` at the version this package is verified against; everything else runs in-process.
@@ -142,7 +142,7 @@ That is a different job from shipping. Use `share` for the build you are holding
142
142
 
143
143
  ## From preview to store
144
144
 
145
- The platform tools connect agents to [extension.dev](https://extension.dev): `extension_auth` runs extension.dev's own device flow (you approve the code at [extension.dev/device](https://extension.dev/device), and GitHub is federated server-side, so no GitHub token ever reaches your machine) and stores a project-scoped token locally (never returned to the agent), `extension_publish` turns a build your project has already published into a shareable URL, and `extension_release_promote` promotes a tested build to a release channel from CI or an agent session, no browser required. `extension_submit` submits a built extension to the Chrome Web Store, Edge Add-ons, Firefox AMO, and the App Store for Safari through extension.dev, which holds your store credentials and dispatches the release from your project's mirror CI, it defaults to a dry run and store credentials are never tool arguments. The two verbs are not interchangeable: `extension_publish` pushes to the extension.dev platform, `extension_submit` sends the build into a store's review queue, which is irreversible. After a real submission, `extension_release_status` reads the recorded outcome, per-store credential health, and review state from the project's public registry, so agents and CI can answer "was it approved?" without a console visit. Access tokens live at most 7 days; CI pipelines re-mint them from the console's Access tokens page.
145
+ The platform tools connect agents to [extension.dev](https://extension.dev): `extension_auth` runs extension.dev's own device flow (you approve the code at [extension.dev/device](https://extension.dev/device), and GitHub is federated server-side, so no GitHub token ever reaches your machine) and stores a project-scoped token locally (never returned to the agent), `extension_publish` turns a build your project has already published into a shareable URL, and `extension_release_promote` promotes a tested build to a release channel from CI or an agent session, no browser required. `extension_submit` submits a built extension to the Chrome Web Store, Edge Add-ons, and Firefox AMO through extension.dev, which holds your store credentials and dispatches the release from your project's mirror CI, it defaults to a dry run and store credentials are never tool arguments. Safari and the App Store are one paid lane on the platform, so a free workspace is refused there and the other three stores are unaffected. The two verbs are not interchangeable: `extension_publish` pushes to the extension.dev platform, `extension_submit` sends the build into a store's review queue, which is irreversible. After a real submission, `extension_release_status` reads the recorded outcome, per-store credential health, and review state from the project's public registry, so agents and CI can answer "was it approved?" without a console visit. Access tokens live at most 7 days; CI pipelines re-mint them from the console's Access tokens page.
146
146
 
147
147
  ## The extension.dev stack
148
148
 
package/dist/module.js CHANGED
@@ -582,7 +582,7 @@ __webpack_require__.d(wait_namespaceObject, {
582
582
  handler: ()=>wait_handler,
583
583
  schema: ()=>wait_schema
584
584
  });
585
- var package_namespaceObject = JSON.parse('{"rE":"10.3.0","El":{"OP":"4.0.20"}}');
585
+ var package_namespaceObject = JSON.parse('{"rE":"10.4.0","El":{"OP":"4.0.20"}}');
586
586
  const ENVELOPE_SCHEMA = 1;
587
587
  const collectWarnings = (warnings)=>{
588
588
  if (!warnings) return [];
@@ -1328,6 +1328,97 @@ function parseBuildIndex(json) {
1328
1328
  }
1329
1329
  return out;
1330
1330
  }
1331
+ const DEFAULT_MEDIA_ORIGIN = "https://media.extension.land";
1332
+ const DEFAULT_CHANNEL = "latest";
1333
+ const PINNED_COMMIT = "52c0d871c433d9c54878767175ce8ffc9a951756";
1334
+ const CHANNEL_CACHE_TTL_MS = 300000;
1335
+ function mediaOrigin() {
1336
+ return (process.env.EXTENSION_MEDIA_ORIGIN || "").trim() || DEFAULT_MEDIA_ORIGIN;
1337
+ }
1338
+ function channelName() {
1339
+ return (process.env.EXTENSION_TEMPLATES_CHANNEL || "").trim() || DEFAULT_CHANNEL;
1340
+ }
1341
+ function pinnedCommitOverride() {
1342
+ return (process.env.EXTENSION_TEMPLATES_COMMIT || "").trim();
1343
+ }
1344
+ function rawBaseForCommit(commit) {
1345
+ return `https://raw.githubusercontent.com/extension-js/examples/${commit}`;
1346
+ }
1347
+ let releaseCache = null;
1348
+ let releaseCacheExpiresAt = 0;
1349
+ let releaseRequest = null;
1350
+ function releaseForCommit(origin, commit) {
1351
+ return {
1352
+ commit,
1353
+ metaUrl: `${origin}/templates/${commit}/templates-meta.json`,
1354
+ filesBaseUrl: `${origin}/templates/${commit}/files`
1355
+ };
1356
+ }
1357
+ async function resolveRelease() {
1358
+ const now = Date.now();
1359
+ if (releaseCache && now < releaseCacheExpiresAt) return releaseCache;
1360
+ if (releaseRequest) return releaseRequest;
1361
+ const origin = mediaOrigin();
1362
+ const override = pinnedCommitOverride();
1363
+ if (override) {
1364
+ releaseCache = releaseForCommit(origin, override);
1365
+ releaseCacheExpiresAt = now + CHANNEL_CACHE_TTL_MS;
1366
+ return releaseCache;
1367
+ }
1368
+ releaseRequest = (async ()=>{
1369
+ try {
1370
+ const pointerUrl = `${origin}/templates/${channelName()}.json`;
1371
+ const response = await fetch(pointerUrl, {
1372
+ headers: {
1373
+ Accept: "application/json"
1374
+ }
1375
+ });
1376
+ if (!response.ok) return null;
1377
+ const pointer = await response.json();
1378
+ const commit = String(pointer?.commit || "").trim();
1379
+ if (!commit) return null;
1380
+ releaseCache = releaseForCommit(origin, commit);
1381
+ releaseCacheExpiresAt = Date.now() + CHANNEL_CACHE_TTL_MS;
1382
+ return releaseCache;
1383
+ } catch {
1384
+ return null;
1385
+ }
1386
+ })();
1387
+ try {
1388
+ return await releaseRequest;
1389
+ } finally{
1390
+ releaseRequest = null;
1391
+ }
1392
+ }
1393
+ function templateCatalogUrl(slug, utmSource = "mcp") {
1394
+ const origin = mcpOrigins().templates.replace(/\/+$/, "");
1395
+ return `${origin}/${encodeURIComponent(slug)}?utm_source=${utmSource}&utm_medium=tool`;
1396
+ }
1397
+ async function templateMetaUrls() {
1398
+ const urls = [];
1399
+ const release = await resolveRelease();
1400
+ if (release) urls.push(release.metaUrl);
1401
+ urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/templates-meta.json`);
1402
+ return urls;
1403
+ }
1404
+ function stripTemplatePathPrefix(slug, relativePath) {
1405
+ for (const dir of [
1406
+ "public",
1407
+ "examples"
1408
+ ]){
1409
+ const prefix = `${dir}/${slug}/`;
1410
+ if (relativePath.startsWith(prefix)) return relativePath.slice(prefix.length);
1411
+ }
1412
+ return relativePath;
1413
+ }
1414
+ async function templateFileUrls(slug, relativePath) {
1415
+ const relative = stripTemplatePathPrefix(slug, relativePath);
1416
+ const urls = [];
1417
+ const release = await resolveRelease();
1418
+ if (release) urls.push(`${release.filesBaseUrl}/${slug}/${relative}`);
1419
+ urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${relative}`);
1420
+ return urls;
1421
+ }
1331
1422
  const COMMAND = "extension_create";
1332
1423
  function scaffoldEnginePin(projectPath) {
1333
1424
  try {
@@ -1495,6 +1586,7 @@ async function create_handler(args) {
1495
1586
  const deployUrl = `${wwwOrigin}${(0, paths_0.wwwNewPath)({
1496
1587
  template: result.template
1497
1588
  })}`;
1589
+ const catalogUrl = templateCatalogUrl(result.template, "mcp-create");
1498
1590
  return envelope_envelope({
1499
1591
  ok: true,
1500
1592
  command: COMMAND,
@@ -1504,6 +1596,7 @@ async function create_handler(args) {
1504
1596
  projectPath: result.projectPath,
1505
1597
  projectName: result.projectName,
1506
1598
  template: result.template,
1599
+ templateCatalogUrl: catalogUrl,
1507
1600
  depsInstalled: result.depsInstalled,
1508
1601
  packageManager: result.depsInstalled ? packageManager : null,
1509
1602
  deployUrl,
@@ -1535,93 +1628,6 @@ async function create_handler(args) {
1535
1628
  ]
1536
1629
  });
1537
1630
  }
1538
- const DEFAULT_MEDIA_ORIGIN = "https://media.extension.land";
1539
- const DEFAULT_CHANNEL = "latest";
1540
- const PINNED_COMMIT = "520a8ede767fe1937c7184f4857408caf9c5934f";
1541
- const CHANNEL_CACHE_TTL_MS = 300000;
1542
- function mediaOrigin() {
1543
- return (process.env.EXTENSION_MEDIA_ORIGIN || "").trim() || DEFAULT_MEDIA_ORIGIN;
1544
- }
1545
- function channelName() {
1546
- return (process.env.EXTENSION_TEMPLATES_CHANNEL || "").trim() || DEFAULT_CHANNEL;
1547
- }
1548
- function pinnedCommitOverride() {
1549
- return (process.env.EXTENSION_TEMPLATES_COMMIT || "").trim();
1550
- }
1551
- function rawBaseForCommit(commit) {
1552
- return `https://raw.githubusercontent.com/extension-js/examples/${commit}`;
1553
- }
1554
- let releaseCache = null;
1555
- let releaseCacheExpiresAt = 0;
1556
- let releaseRequest = null;
1557
- function releaseForCommit(origin, commit) {
1558
- return {
1559
- commit,
1560
- metaUrl: `${origin}/templates/${commit}/templates-meta.json`,
1561
- filesBaseUrl: `${origin}/templates/${commit}/files`
1562
- };
1563
- }
1564
- async function resolveRelease() {
1565
- const now = Date.now();
1566
- if (releaseCache && now < releaseCacheExpiresAt) return releaseCache;
1567
- if (releaseRequest) return releaseRequest;
1568
- const origin = mediaOrigin();
1569
- const override = pinnedCommitOverride();
1570
- if (override) {
1571
- releaseCache = releaseForCommit(origin, override);
1572
- releaseCacheExpiresAt = now + CHANNEL_CACHE_TTL_MS;
1573
- return releaseCache;
1574
- }
1575
- releaseRequest = (async ()=>{
1576
- try {
1577
- const pointerUrl = `${origin}/templates/${channelName()}.json`;
1578
- const response = await fetch(pointerUrl, {
1579
- headers: {
1580
- Accept: "application/json"
1581
- }
1582
- });
1583
- if (!response.ok) return null;
1584
- const pointer = await response.json();
1585
- const commit = String(pointer?.commit || "").trim();
1586
- if (!commit) return null;
1587
- releaseCache = releaseForCommit(origin, commit);
1588
- releaseCacheExpiresAt = Date.now() + CHANNEL_CACHE_TTL_MS;
1589
- return releaseCache;
1590
- } catch {
1591
- return null;
1592
- }
1593
- })();
1594
- try {
1595
- return await releaseRequest;
1596
- } finally{
1597
- releaseRequest = null;
1598
- }
1599
- }
1600
- async function templateMetaUrls() {
1601
- const urls = [];
1602
- const release = await resolveRelease();
1603
- if (release) urls.push(release.metaUrl);
1604
- urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/templates-meta.json`);
1605
- return urls;
1606
- }
1607
- function stripTemplatePathPrefix(slug, relativePath) {
1608
- for (const dir of [
1609
- "public",
1610
- "examples"
1611
- ]){
1612
- const prefix = `${dir}/${slug}/`;
1613
- if (relativePath.startsWith(prefix)) return relativePath.slice(prefix.length);
1614
- }
1615
- return relativePath;
1616
- }
1617
- async function templateFileUrls(slug, relativePath) {
1618
- const relative = stripTemplatePathPrefix(slug, relativePath);
1619
- const urls = [];
1620
- const release = await resolveRelease();
1621
- if (release) urls.push(`${release.filesBaseUrl}/${slug}/${relative}`);
1622
- urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${relative}`);
1623
- return urls;
1624
- }
1625
1631
  var templates_meta_snapshot_namespaceObject = JSON.parse('{"version":"2","sourceRepo":"extension-js/examples","generatorVersion":"2","commit":"336cfc03c39f9fa52f4ecffd351313241b5ca0a4","generatedAt":"2026-07-16T13:47:35.883Z","templates":[{"slug":"action","name":"action","title":"Action (Popup)","version":"1.0.0","manifest_version":3,"description":"Action popup example. Opens a toolbar popup with a simple page.","uiContext":["background"],"surfaces":["background"],"entrypoints":[],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"tags":["action","popup","javascript"],"difficulty":"beginner","timeToFirstSuccessMinutes":2,"firstSteps":["Run npm run dev to launch the extension","Click the toolbar icon to open the action popup","Edit src/action/index.html and watch HMR update the popup"],"useCases":["Toolbar popup as the primary UI surface","Quick actions that don\'t need a full sidebar or page","Starting point for popup-based browser extensions"],"docsUrl":"https://extension.js.org/docs/implementation-guide/manifest-json","files":["extension.config.js","public/screenshot.png","src/action/index.html","src/action/scripts.js","src/action/styles.css","src/background.js","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/action/public/screenshot.png","icon":"examples/action/src/images/icon.png"},{"slug":"action-locales","name":"action-locales","title":"Action Locales","version":"1.0.0","manifest_version":3,"description":"Action popup example demonstrating i18n with _locales and message placeholders.","uiContext":["background"],"surfaces":["background"],"entrypoints":[],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/action/index.html","src/action/scripts.js","src/action/styles.css","src/background.js","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/action-locales/public/screenshot.png","icon":"examples/action-locales/src/images/icon.png"},{"slug":"ai-chatgpt","name":"ai-chatgpt","title":"ChatGPT AI Sidebar","version":"1.0.0","manifest_version":3,"description":"React sidebar with ChatGPT chat. Adds a side panel with a conversational interface powered by the OpenAI SDK.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["ai","chatgpt","openai","gpt","chat","sidebar","content-scripts","react","shadcn"],"difficulty":"beginner","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any web page and click the extension icon to open the sidebar","Enter your ChatGPT API key (from platform.openai.com)","Ask ChatGPT about the active tab — the content script feeds the page text into the conversation"],"useCases":["Chat with the active tab\'s content using ChatGPT","Summarize or question articles, docs, and dashboards in place","Starting point for page-aware ChatGPT extensions"],"docsUrl":"https://platform.openai.com/docs/api-reference/chat","files":["extension.config.js","public/screenshot.png","src/background.ts","src/components/ApiKeyForm.tsx","src/components/ChatInput.tsx","src/components/ChatMessage.tsx","src/components/ui/button.tsx","src/components/ui/card.tsx","src/components/ui/scroll-area.tsx","src/content/ContentApp.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/lib/client.ts","src/lib/page-context.ts","src/lib/utils.ts","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/ai-chatgpt/public/screenshot.png","icon":"examples/ai-chatgpt/src/images/icon.png"},{"slug":"ai-claude","name":"ai-claude","title":"Claude AI Sidebar","version":"1.0.0","manifest_version":3,"description":"React sidebar with Claude AI chat. Adds a side panel with a conversational interface powered by the Anthropic SDK.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["ai","claude","anthropic","chat","sidebar","content-scripts","react","shadcn"],"difficulty":"beginner","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any web page and click the extension icon to open the sidebar","Enter your Claude API key (from console.anthropic.com)","Ask Claude about the active tab — the content script feeds the page text into the conversation"],"useCases":["Chat with the active tab\'s content using Claude","Summarize or question articles, docs, and dashboards in place","Starting point for page-aware Claude extensions"],"docsUrl":"https://docs.anthropic.com/en/api/getting-started","files":["extension.config.js","public/screenshot.png","src/background.ts","src/components/ApiKeyForm.tsx","src/components/ChatInput.tsx","src/components/ChatMessage.tsx","src/components/ui/button.tsx","src/components/ui/card.tsx","src/components/ui/scroll-area.tsx","src/content/ContentApp.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/lib/client.ts","src/lib/page-context.ts","src/lib/utils.ts","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/ai-claude/public/screenshot.png","icon":"examples/ai-claude/src/images/icon.png"},{"slug":"ai-gemini","name":"ai-gemini","title":"Gemini AI Sidebar","version":"1.0.0","manifest_version":3,"description":"React sidebar with Gemini chat. Adds a side panel with a conversational interface powered by the Google Generative AI SDK.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["ai","gemini","google","chat","sidebar","content-scripts","react","shadcn"],"difficulty":"beginner","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any web page and click the extension icon to open the sidebar","Enter your Gemini API key (from aistudio.google.com)","Ask Gemini about the active tab — the content script feeds the page text into the conversation"],"useCases":["Chat with the active tab\'s content using Gemini","Summarize or question articles, docs, and dashboards in place","Starting point for page-aware Gemini extensions"],"docsUrl":"https://ai.google.dev/gemini-api/docs","files":["extension.config.js","public/screenshot.png","src/background.ts","src/components/ApiKeyForm.tsx","src/components/ChatInput.tsx","src/components/ChatMessage.tsx","src/components/ui/button.tsx","src/components/ui/card.tsx","src/components/ui/scroll-area.tsx","src/content/ContentApp.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/lib/client.ts","src/lib/page-context.ts","src/lib/utils.ts","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/ai-gemini/public/screenshot.png","icon":"examples/ai-gemini/src/images/icon.png"},{"slug":"ai-perplexity","name":"ai-perplexity","title":"Perplexity AI Sidebar","version":"1.0.0","manifest_version":3,"description":"React sidebar with Perplexity chat. Adds a side panel with a conversational interface powered by the Perplexity API (OpenAI-compatible).","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["ai","perplexity","search","chat","sidebar","content-scripts","react","shadcn"],"difficulty":"beginner","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any web page and click the extension icon to open the sidebar","Enter your Perplexity API key (from perplexity.ai/settings/api)","Ask Perplexity about the active tab — the content script feeds the page text into the conversation"],"useCases":["Chat with the active tab\'s content using Perplexity","Summarize or question articles, docs, and dashboards in place","Starting point for page-aware Perplexity extensions"],"docsUrl":"https://docs.perplexity.ai/","files":["extension.config.js","public/screenshot.png","src/background.ts","src/components/ApiKeyForm.tsx","src/components/ChatInput.tsx","src/components/ChatMessage.tsx","src/components/ui/button.tsx","src/components/ui/card.tsx","src/components/ui/scroll-area.tsx","src/content/ContentApp.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/lib/client.ts","src/lib/page-context.ts","src/lib/utils.ts","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/ai-perplexity/public/screenshot.png","icon":"examples/ai-perplexity/src/images/icon.png"},{"slug":"content","name":"@extension-js/content","title":"Content scripts","version":"1.0.0","manifest_version":3,"description":"Content script example that injects a small badge into web pages to demonstrate DOM injection and styling in an extension.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.css","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content/public/screenshot.png","icon":"examples/content/src/images/icon.png"},{"slug":"content-css-modules","name":"@extension-js/content-css-modules","title":"Content CSS Modules","version":"1.0.0","manifest_version":3,"description":"Content script example styled with CSS Modules. Injects a small badge and shows how to scope styles in content scripts.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.module.css","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-css-modules/public/screenshot.png","icon":"examples/content-css-modules/src/images/icon.png"},{"slug":"content-custom-font","name":"content-custom-font","title":"Content Custom Font","version":"1.0.0","manifest_version":3,"description":"Content script example that loads custom web fonts. Demonstrates font files and web_accessible_resources setup.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"css","configFiles":["postcss.config.js","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.css","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-custom-font/public/screenshot.png","icon":"examples/content-custom-font/src/images/icon.png"},{"slug":"content-env","name":"content-env","title":"Content Env","version":"1.0.0","manifest_version":3,"description":"Content script example that reads environment variables (.env) and renders a small UI.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.ts"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-env/public/screenshot.png","icon":"examples/content-env/src/images/icon.png"},{"slug":"content-less","name":"@extension-js/content-less","title":"Content Less","version":"1.0.0","manifest_version":3,"description":"Content script example styled with Less. Injects a small badge and shows how to organize styles with Less in content scripts.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"less","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.less","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-less/public/screenshot.png","icon":"examples/content-less/src/images/icon.png"},{"slug":"content-less-modules","name":"@extension-js/content-less-modules","title":"Content Less Modules","version":"1.0.0","manifest_version":3,"description":"Content script example using Less with CSS Modules. Injects a small badge and shows how to scope Less styles in content scripts.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"less","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.module.less","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-less-modules/public/screenshot.png","icon":"examples/content-less-modules/src/images/icon.png"},{"slug":"content-main-world","name":"content-main-world","title":"Main-world Content Script","version":"1.0.0","manifest_version":3,"description":"Main world content script with multiple groups. ISOLATED entries precede the MAIN world entry so bridge injection exercises canonical-index vs array-position resolution.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/isolated-one.js","content/isolated-two.js","content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["content-scripts","main-world","isolated-world","javascript"],"difficulty":"intermediate","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any page and watch the main-world script run alongside isolated entries","Inspect window.postMessage bridges between isolated and main worlds"],"useCases":["Hook into page-level globals that isolated content scripts cannot reach","Bridge isolated and main-world execution contexts cleanly","Reference for multi-group content_scripts in MV3"],"docsUrl":"https://extension.js.org/docs/implementation-guide/content-scripts","files":["extension.config.js","public/screenshot.png","src/background.js","src/content/isolated-one.js","src/content/isolated-two.js","src/content/scripts.js","src/content/styles.css","src/content/utils/constants.js","src/content/utils/create-badge.js","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-main-world/public/screenshot.png","icon":"examples/content-main-world/src/images/icon.png"},{"slug":"content-multi-one-entry","name":"content-multi-one-entry","title":"Content Multi One Entry","version":"1.0.0","manifest_version":3,"description":"Content script example with four scripts listed in one entry. Each script injects a small element.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/script-top-left.js","content/script-top-right.js","content/script-bottom-left.js","content/script-bottom-right.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/script-bottom-left.js","src/content/script-bottom-right.js","src/content/script-top-left.js","src/content/script-top-right.js","src/content/styles.css","src/content/utils/constants.js","src/content/utils/create-badge.js","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-multi-one-entry/public/screenshot.png","icon":"examples/content-multi-one-entry/src/images/icon.png"},{"slug":"content-multi-three-entries","name":"content-multi-three-entries","title":"Multi-entry Content Scripts","version":"1.0.0","manifest_version":3,"description":"Content script example split across three entries (2 + 1 + 1). Each script injects a small element.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/script-top-left.js","content/script-top-right.js","content/script-bottom-left.js","content/script-bottom-right.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"tags":["content-scripts","multi-entry","javascript"],"difficulty":"intermediate","timeToFirstSuccessMinutes":3,"firstSteps":["Run npm run dev to launch the extension","Open any page — three separate content scripts inject small elements","Edit any of the entries in src/content/ to see HMR update independently"],"useCases":["Split content-script logic across multiple entries with different match rules","Target different page contexts with focused, independent bundles","Reference for the multi-entry content_scripts pattern in MV3"],"docsUrl":"https://extension.js.org/docs/implementation-guide/content-scripts","files":["extension.config.js","public/screenshot.png","src/background.js","src/content/script-bottom-left.js","src/content/script-bottom-right.js","src/content/script-top-left.js","src/content/script-top-right.js","src/content/styles.css","src/content/utils/constants.js","src/content/utils/create-badge.js","src/images/icon.png","src/manifest.json","src/screenshot.png"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-multi-three-entries/public/screenshot.png","icon":"examples/content-multi-three-entries/src/images/icon.png"},{"slug":"content-preact","name":"content-preact","title":"Content Preact","version":"1.0.0","manifest_version":3,"description":"Content script example rendered with Preact. Mounts a small UI inside a Shadow DOM on any page.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["./content/scripts.tsx"],"uiFramework":"preact","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/background.ts","src/content/ContentApp.tsx","src/content/scripts.tsx","src/content/styles.css","src/images/chromeWindow.png","src/images/icon.png","src/images/preact.png","src/images/tailwind.png","src/images/tailwind_bg.png","src/images/typescript.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-preact/screenshot.png","icon":"examples/content-preact/src/images/icon.png"},{"slug":"content-react","name":"content-react","title":"Content React","version":"1.0.0","manifest_version":3,"description":"Content script example rendered with React. Mounts a small UI inside a Shadow DOM on any page.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["./content/scripts.tsx"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/background.ts","src/content/ContentApp.tsx","src/content/scripts.tsx","src/content/styles.css","src/images/chromeWindow.png","src/images/icon.png","src/images/react.png","src/images/tailwind.png","src/images/tailwind_bg.png","src/images/typescript.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-react/screenshot.png","icon":"examples/content-react/src/images/icon.png"},{"slug":"content-sass","name":"@extension-js/content-sass","title":"Content Sass","version":"1.0.0","manifest_version":3,"description":"Content script example styled with Sass. Injects a small badge and shows how to organize styles with Sass in content scripts.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"sass","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.scss","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-sass/public/screenshot.png","icon":"examples/content-sass/src/images/icon.png"},{"slug":"content-sass-modules","name":"@extension-js/content-sass-modules","title":"Content Sass Modules","version":"1.0.0","manifest_version":3,"description":"A browser extension content script example built with Extension.js using Sass CSS Modules. Demonstrates scoped styles for content scripts with Sass preprocessing.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"sass","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{"build":"extension build"},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.module.scss","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-sass-modules/public/screenshot.png","icon":"examples/content-sass-modules/src/images/icon.png"},{"slug":"content-svelte","name":"content-svelte","title":"Content Svelte","version":"1.0.0","manifest_version":3,"description":"Content script example rendered with Svelte. Mounts a small UI inside a Shadow DOM on any page.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["./content/scripts.ts"],"uiFramework":"svelte","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/background.ts","src/content/ContentApp.svelte","src/content/scripts.ts","src/content/styles.css","src/content/svelte.d.ts","src/images/chromeWindow.png","src/images/icon.png","src/images/svelte.png","src/images/tailwind.png","src/images/tailwind_bg.png","src/images/typescript.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-svelte/screenshot.png","icon":"examples/content-svelte/src/images/icon.png"},{"slug":"content-typescript","name":"content-typescript","title":"Content TypeScript","version":"1.0.0","manifest_version":3,"description":"Content script example built with TypeScript. Mounts a small UI and uses typed modules.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.ts"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-typescript/public/screenshot.png","icon":"examples/content-typescript/src/images/icon.png"},{"slug":"content-vue","name":"content-vue","title":"Content Vue","version":"1.0.0","manifest_version":3,"description":"Content script example rendered with Vue. Mounts a small UI inside a Shadow DOM on any page.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["./content/scripts.ts"],"uiFramework":"vue","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/background.ts","src/content/ContentApp.vue","src/content/scripts.ts","src/content/shims-vue.d.ts","src/content/styles.css","src/images/chromeWindow.png","src/images/icon.png","src/images/logo.svg","src/images/tailwind.png","src/images/tailwind_bg.png","src/images/typescript.png","src/images/vue.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/content-vue/screenshot.png","icon":"examples/content-vue/src/images/logo.svg"},{"slug":"init","name":"init","title":"Initial","version":"1.0.0","manifest_version":3,"description":"A basic browser extension example built with Extension.js. A starting point for developers learning current browser extension tooling.","uiContext":[],"surfaces":[],"entrypoints":[],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":false,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/images/icon.png","src/images/javascript.png","src/index.js","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/init/screenshot.png","icon":"examples/init/src/images/icon.png"},{"slug":"javascript","name":"javascript","title":"JavaScript","version":"1.0.0","manifest_version":3,"description":"JavaScript-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/ContentApp.js","src/content/scripts.js","src/content/styles.css","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.js","src/sidebar/index.html","src/sidebar/scripts.js","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/javascript/screenshot.png","icon":"examples/javascript/src/images/icon.png"},{"slug":"new","name":"new","title":"Newtab","version":"1.0.0","manifest_version":3,"description":"New tab page example using plain JavaScript. Renders a simple page you can customize.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.js","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new/public/screenshot.png","icon":"examples/new/src/images/icon.png"},{"slug":"new-browser-flags","name":"new-browser-flags","title":"Newtab Browser Flags","version":"1.0.0","manifest_version":3,"description":"A browser extension example built with Extension.js demonstrating browser-specific features and flags. Shows how to handle browser differences across targets.","uiContext":["content","newtab","sidebar","background"],"surfaces":["content","newtab","sidebar","background"],"entrypoints":["newtab/index.html","sidebar/index.html","content/content.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{"build":"extension build"},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting","notifications"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","src/background.js","src/content/content.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/newtab.css","src/newtab/newtab.js","src/sidebar/index.html","src/sidebar/sidebar.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-browser-flags/screenshot.png","icon":"examples/new-browser-flags/src/images/icon.png"},{"slug":"new-config-eslint","name":"new-config-eslint","title":"Newtab Config ESLint","version":"1.0.0","manifest_version":3,"description":"New tab page example with ESLint configured. Shows how linting is set up in an extension project.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js","eslint.config.mjs"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-config-eslint/public/screenshot.png","icon":"examples/new-config-eslint/src/images/icon.png"},{"slug":"new-config-prettier","name":"new-config-prettier","title":"Newtab Config Prettier","version":"1.0.0","manifest_version":3,"description":"New tab page example with Prettier configured. Shows how formatting is set up in an extension project.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js",".prettierrc"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-config-prettier/public/screenshot.png","icon":"examples/new-config-prettier/src/images/icon.png"},{"slug":"new-config-stylelint","name":"new-config-stylelint","title":"Newtab Config Stylelint","version":"1.0.0","manifest_version":3,"description":"New tab page example with Stylelint configured. Shows how CSS linting is set up in an extension project.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"sass","configFiles":[".stylelintrc.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.js","src/newtab/styles.scss"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-config-stylelint/public/screenshot.png","icon":"examples/new-config-stylelint/src/images/icon.png"},{"slug":"new-crypto","name":"new-crypto","title":"Newtab Crypto","version":"1.0.0","manifest_version":3,"description":"New tab page example demonstrating basic crypto operations inside an extension.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-crypto/public/screenshot.png","icon":"examples/new-crypto/src/images/icon.png"},{"slug":"new-env","name":"new-env","title":"Environment Variables","version":"1.0.0","manifest_version":3,"description":"New tab page example demonstrating environment variables (.env) in an extension project.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"tags":["env","configuration","new-tab","javascript"],"difficulty":"beginner","timeToFirstSuccessMinutes":2,"firstSteps":["Copy .env.example to .env and edit the values","Run npm run dev to launch the extension","Open a new tab — the page reads injected EXTENSION_PUBLIC_* values"],"useCases":["Inject build-time configuration without leaking secrets","Switch behavior per browser, mode, or deployment target","Reference for safe env-var usage across JS, JSON, and HTML"],"docsUrl":"https://extension.js.org/docs/features/environment-variables","files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-env/public/screenshot.png","icon":"examples/new-env/src/images/icon.png"},{"slug":"new-less","name":"new-less","title":"Newtab Less","version":"1.0.0","manifest_version":3,"description":"New tab page example styled with Less. Renders a simple page and organizes styles with Less.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"less","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.js","src/newtab/styles.less"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-less/public/screenshot.png","icon":"examples/new-less/src/images/icon.png"},{"slug":"new-preact","name":"new-preact","title":"Newtab Preact","version":"1.0.0","manifest_version":3,"description":"New tab page example rendered with Preact. Loads a small Preact app you can extend.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"preact","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/NewTabApp.tsx","src/newtab/index.html","src/newtab/scripts.tsx","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-preact/public/screenshot.png","icon":"examples/new-preact/src/images/icon.png"},{"slug":"new-react","name":"new-react","title":"Newtab React","version":"1.0.0","manifest_version":3,"description":"A browser extension new tab page example built with Extension.js and React. Demonstrates a React-based new tab page with a working build and preview flow.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"react","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/NewTabApp.tsx","src/newtab/index.html","src/newtab/scripts.tsx","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-react/public/screenshot.png","icon":"examples/new-react/src/images/icon.png"},{"slug":"new-react-router","name":"new-react-router","title":"Newtab React Router","version":"1.0.0","manifest_version":3,"description":"New tab page example using React Router. Shows multiple routes inside the new tab app.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"react","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/NewTabApp.tsx","src/newtab/index.html","src/newtab/scripts.tsx","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-react-router/public/screenshot.png","icon":"examples/new-react-router/src/images/icon.png"},{"slug":"new-sass","name":"new-sass","title":"Newtab Sass","version":"1.0.0","manifest_version":3,"description":"New tab page example styled with Sass. Renders a simple page and organizes styles with Sass.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"sass","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.js","src/newtab/styles.scss"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-sass/public/screenshot.png","icon":"examples/new-sass/src/images/icon.png"},{"slug":"new-svelte","name":"new-svelte","title":"Newtab Svelte","version":"1.0.0","manifest_version":3,"description":"New tab page example rendered with Svelte. Loads a small Svelte app you can extend.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"svelte","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/NewTabApp.svelte","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-svelte/public/screenshot.png","icon":"examples/new-svelte/src/images/icon.png"},{"slug":"new-typescript","name":"new-typescript","title":"Newtab TypeScript","version":"1.0.0","manifest_version":3,"description":"A browser extension new tab page example built with Extension.js and TypeScript. Demonstrates a type-safe new tab page with a working build and preview flow.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-typescript/public/screenshot.png","icon":"examples/new-typescript/src/images/icon.png"},{"slug":"new-vue","name":"new-vue","title":"Newtab Vue","version":"1.0.0","manifest_version":3,"description":"New tab page example rendered with Vue. Loads a small Vue app you can extend.","uiContext":["newtab","background"],"surfaces":["newtab","background"],"entrypoints":["newtab/index.html"],"uiFramework":"vue","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/newtab/NewTabApp.vue","src/newtab/index.html","src/newtab/scripts.ts","src/newtab/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/new-vue/public/screenshot.png","icon":"examples/new-vue/src/images/icon.png"},{"slug":"playwright","name":"playwright","title":"Playwright E2E Sidebar","version":"1.0.0","manifest_version":3,"description":"Sidebar extension example demonstrating Playwright-driven E2E automation.","uiContext":["sidebar","background"],"surfaces":["sidebar","background"],"entrypoints":["sidebar/index.html"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"tags":["testing","playwright","e2e","sidebar","typescript"],"difficulty":"intermediate","timeToFirstSuccessMinutes":4,"firstSteps":["Run npm install to set up Playwright deps","Run npm run dev to launch the extension","Run npm test to execute the Playwright spec against the loaded extension"],"useCases":["End-to-end testing for browser-extension surfaces (popup, sidebar, content)","CI-ready Playwright setup wired to Extension.js builds","Reference for automating extension flows in headed and headless modes"],"docsUrl":"https://extension.js.org/docs/workflows/playwright-e2e","files":["extension.config.js","public/screenshot.png","src/background.ts","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.ts","src/sidebar/index.html","src/sidebar/scripts.ts","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/playwright/public/screenshot.png","icon":"examples/playwright/src/images/icon.png"},{"slug":"preact","name":"preact","title":"Preact","version":"1.0.0","manifest_version":3,"description":"Preact-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.tsx"],"uiFramework":"preact","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/ContentApp.tsx","src/content/scripts.tsx","src/content/styles.css","src/images/icon.png","src/manifest.json","src/public/extension.svg","src/public/logo.svg","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/preact/public/screenshot.png","icon":"examples/preact/src/images/icon.png"},{"slug":"react","name":"react","title":"React","version":"1.0.0","manifest_version":3,"description":"React-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.tsx"],"uiFramework":"react","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/ContentApp.tsx","src/content/scripts.tsx","src/content/styles.css","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/react/public/screenshot.png","icon":"examples/react/src/images/icon.png"},{"slug":"sidebar","name":"sidebar","title":"Sidebar page","version":"1.0.0","manifest_version":3,"description":"Sidebar panel example. Adds a sidebar to the browser with a simple page.","uiContext":["sidebar","background"],"surfaces":["sidebar","background"],"entrypoints":["sidebar/index.html"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/sidebar/index.html","src/sidebar/scripts.js","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/sidebar/public/screenshot.png","icon":"examples/sidebar/src/images/icon.png"},{"slug":"sidebar-antd","name":"sidebar-antd","title":"Sidebar Ant Design","version":"1.0.0","manifest_version":3,"description":"React sidebar using antd and @ant-design/x. Regression coverage for issue #445.","uiContext":["sidebar","background"],"surfaces":["sidebar","background"],"entrypoints":["sidebar/index.html"],"uiFramework":"react","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.jsx","src/sidebar/index.html","src/sidebar/scripts.jsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/sidebar-antd/public/screenshot.png","icon":"examples/sidebar-antd/src/images/icon.png"},{"slug":"sidebar-monorepo-turbopack","name":"sidebar-monorepo-turbopack","title":"Sidebar Turbopack","version":"1.0.0","manifest_version":3,"description":"Monorepo example with content script and sidebar.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{"dev":"extension dev packages/extension","build":"extension build packages/extension"},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","packages/analytics/src/index.js","packages/extension/src/background.js","packages/extension/src/content/scripts.js","packages/extension/src/content/styles.css","packages/extension/src/images/icon.png","packages/extension/src/manifest.json","packages/extension/src/sidebar/SidebarApp.js","packages/extension/src/sidebar/index.html","packages/extension/src/sidebar/scripts.js","packages/extension/src/sidebar/styles.css","public/screenshot.png"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/sidebar-monorepo-turbopack/public/screenshot.png","icon":null},{"slug":"sidebar-shadcn","name":"sidebar-shadcn","title":"Sidebar shadcn","version":"1.0.0","manifest_version":3,"description":"React sidebar example using shadcn/ui components. Adds a sidebar panel with a simple React page.","uiContext":["sidebar","background"],"surfaces":["sidebar","background"],"entrypoints":["sidebar/index.html"],"uiFramework":"react","css":"css","configFiles":["postcss.config.js","tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/components/ui/button.tsx","src/components/ui/card.tsx","src/components/ui/label.tsx","src/components/ui/switch.tsx","src/images/icon.png","src/lib/utils.ts","src/manifest.json","src/sidebar/SidebarApp.tsx","src/sidebar/index.html","src/sidebar/scripts.tsx","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/sidebar-shadcn/public/screenshot.png","icon":"examples/sidebar-shadcn/src/images/icon.png"},{"slug":"special-folders-pages","name":"special-folders-pages","title":"Special Folders: pages/","version":"1.0.0","manifest_version":3,"description":"Opens a welcome page on extension load, showcasing the pages/ folder.","uiContext":["background"],"surfaces":["background"],"entrypoints":[],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"tags":["special-folders","pages","welcome-page","javascript"],"difficulty":"beginner","timeToFirstSuccessMinutes":2,"firstSteps":["Run npm run dev to launch the extension","A welcome page from src/pages/ opens automatically on install","Add another HTML file under src/pages/ — it ships as a separate page"],"useCases":["Onboarding, help, and welcome pages that ship with the extension","Standalone HTML routes without manifest plumbing","Reference for Extension.js\'s pages/ special folder convention"],"docsUrl":"https://extension.js.org/docs/features/special-folders","files":["extension.config.js","public/css/file.css","public/html/file.html","public/js/file.js","public/screenshot.png","src/background.js","src/images/icon.png","src/images/javascript.png","src/manifest.json","src/sandbox/index.html","src/sandbox/scripts.js","src/sandbox/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/special-folders-pages/public/screenshot.png","icon":"examples/special-folders-pages/src/images/icon.png"},{"slug":"special-folders-scripts","name":"special-folders-scripts","title":"Special Folders Scripts","version":"1.0.0","manifest_version":3,"description":"Demonstrates scripts/ folder organization and how to run standalone scripts via the extension.","uiContext":["content","background"],"surfaces":["content","background"],"entrypoints":["content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":["activeTab","scripting"],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":false,"files":["extension.config.js","public/screenshot.png","src/background.js","src/content/scripts.js","src/content/styles.css","src/images/icon.png","src/images/javascript.png","src/manifest.json"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/special-folders-scripts/public/screenshot.png","icon":"examples/special-folders-scripts/src/images/icon.png"},{"slug":"svelte","name":"svelte","title":"Svelte","version":"1.0.0","manifest_version":3,"description":"Svelte-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"svelte","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/ContentApp.svelte","src/content/scripts.ts","src/content/styles.css","src/images/icon.png","src/manifest.json","src/public/extension.svg","src/sidebar/SidebarApp.svelte","src/sidebar/index.html","src/sidebar/scripts.ts","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/svelte/public/screenshot.png","icon":"examples/svelte/src/images/icon.png"},{"slug":"transformers-js","name":"transformers-js","title":"Transformers.js","version":"1.0.0","manifest_version":3,"description":"Transformers.js demo with a sidebar and a content script: classify the active page or your selection on-device via WebGPU/WASM.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.js"],"uiFramework":"","css":"css","configFiles":["extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":["<all_urls>"],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.js","src/constants.js","src/content/scripts.js","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.js","src/sidebar/index.html","src/sidebar/sakura.css","src/sidebar/scripts.js","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/transformers-js/public/screenshot.png","icon":"examples/transformers-js/src/images/icon.png"},{"slug":"typescript","name":"typescript","title":"TypeScript","version":"1.0.0","manifest_version":3,"description":"TypeScript-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/ContentApp.ts","src/content/scripts.ts","src/content/styles.css","src/content/types.d.ts","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.ts","src/sidebar/index.html","src/sidebar/scripts.ts","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/typescript/screenshot.png","icon":"examples/typescript/src/images/icon.png"},{"slug":"vue","name":"vue","title":"Vue","version":"1.0.0","manifest_version":3,"description":"Vue.js-based extension with a sidebar panel. Adds a sidebar with a simple page.","uiContext":["content","sidebar","background"],"surfaces":["content","sidebar","background"],"entrypoints":["sidebar/index.html","content/scripts.ts"],"uiFramework":"vue","css":"css","configFiles":["tsconfig.json","extension.config.js"],"hasBackground":true,"hasEnv":false,"setup":{"packageManager":"pnpm","scripts":{},"env":{"hasEnvExample":false,"hasEnv":false}},"permissions":[],"host_permissions":[],"optional_permissions":[],"featured":true,"files":["extension.config.js","public/screenshot.png","src/background.ts","src/content/ContentApp.vue","src/content/scripts.ts","src/content/styles.css","src/content/types.d.ts","src/images/icon.png","src/manifest.json","src/sidebar/SidebarApp.vue","src/sidebar/index.html","src/sidebar/scripts.ts","src/sidebar/styles.css"],"browsers":["chrome","edge","firefox"],"screenshot":"examples/vue/public/screenshot.png","icon":"examples/vue/src/images/icon.png"}]}');
1626
1632
  const CACHE_DIR = node_path.join(node_os.homedir(), ".cache", "extension-js");
1627
1633
  const CACHE_FILE = node_path.join(CACHE_DIR, "templates-meta.json");
@@ -1743,6 +1749,7 @@ async function searchTemplates(args) {
1743
1749
  featured: t.featured,
1744
1750
  useCases: t.useCases,
1745
1751
  repositoryUrl: t.repositoryUrl,
1752
+ catalogUrl: templateCatalogUrl(t.slug),
1746
1753
  downloads: t.downloads
1747
1754
  }));
1748
1755
  return envelope_envelope({
@@ -1776,7 +1783,8 @@ async function readTemplateSource(args) {
1776
1783
  permissions: template.permissions,
1777
1784
  patternExplanation: template.patternExplanation,
1778
1785
  keyFiles: template.keyFiles,
1779
- repositoryUrl: template.repositoryUrl
1786
+ repositoryUrl: template.repositoryUrl,
1787
+ catalogUrl: templateCatalogUrl(template.slug)
1780
1788
  };
1781
1789
  if (!args.files?.length) return envelope_envelope({
1782
1790
  ok: true,
@@ -3530,6 +3538,26 @@ async function pollBootVerdict(projectPath, browser, options) {
3530
3538
  warnings: []
3531
3539
  };
3532
3540
  }
3541
+ const SYSTEM_PROFILE_ARG = "false";
3542
+ function holdsState(dir) {
3543
+ try {
3544
+ return node_fs.readdirSync(dir).length > 0;
3545
+ } catch {
3546
+ return false;
3547
+ }
3548
+ }
3549
+ function profileCarriesTabsOver(projectPath, browser, profileArg) {
3550
+ const raw = "string" == typeof profileArg ? profileArg.trim() : "";
3551
+ if (raw === SYSTEM_PROFILE_ARG) return true;
3552
+ if (raw) return holdsState(node_path.resolve(projectPath, raw));
3553
+ return holdsState(node_path.join(browserProfileRootDir(projectPath, browser), PERSISTED_PROFILE_DIR_NAME));
3554
+ }
3555
+ function sessionProfileReused(projectPath, browser) {
3556
+ return findSessionInfo(projectPath, browser)?.profileReused === true;
3557
+ }
3558
+ function restoredTabWarning(url) {
3559
+ return `No target was given and this session runs on a browser profile that already held a previous session's state, so ${url} may be a tab restored from that previous session rather than anything this run opened. Nothing here proves the page belongs to the extension you just started: pass url, or open a surface with extension_open, and inspect again.`;
3560
+ }
3533
3561
  function sweepCarriers(projectPaths) {
3534
3562
  const out = [];
3535
3563
  const seen = new Set();
@@ -3870,6 +3898,7 @@ function launchFlagArgs(args) {
3870
3898
  if (args.extensions?.length) cli.push("--extensions", args.extensions.join(","));
3871
3899
  return cli;
3872
3900
  }
3901
+ const REUSED_PROFILE_NOTE = "This session reuses a browser profile that already held a previous session's state, so the browser may restore that session's tabs alongside the extension. extension_inspect with no target ranks whatever is open and can land on one of them; it flags this session as profileReused for that reason. Pass url, or open the surface you mean with extension_open, before reading anything.";
3873
3902
  const dev_schema = {
3874
3903
  name: "extension_dev",
3875
3904
  description: "Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. Reach for this first when the ask is \"run my extension\". ONLY this tool unlocks the control channel that extension_storage, extension_reload, extension_open and extension_dom_snapshot need (allowControl:true) and the eval channel that extension_eval needs (allowEval:true, which implies allowControl, so you never need to pass both). Use extension_start instead to run the production build in a browser. The result carries the process info that extension_wait and extension_inspect need.",
@@ -3954,6 +3983,7 @@ async function dev_handler(args) {
3954
3983
  }
3955
3984
  const carrier = args.carrier ? materializeCarrier(args.projectPath, browser) : null;
3956
3985
  const allowControl = Boolean(args.allowControl || args.allowEval);
3986
+ const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
3957
3987
  const cliArgs = [
3958
3988
  "dev",
3959
3989
  args.projectPath,
@@ -3979,7 +4009,8 @@ async function dev_handler(args) {
3979
4009
  port: args.port,
3980
4010
  projectPath: args.projectPath,
3981
4011
  command: "dev",
3982
- noBrowser: Boolean(args.noBrowser)
4012
+ noBrowser: Boolean(args.noBrowser),
4013
+ profileReused
3983
4014
  });
3984
4015
  child.on("exit", ()=>{
3985
4016
  removeSession(args.projectPath, browser, pid);
@@ -4104,7 +4135,8 @@ async function dev_handler(args) {
4104
4135
  port: boundPort,
4105
4136
  projectPath: args.projectPath,
4106
4137
  command: "dev",
4107
- noBrowser: Boolean(args.noBrowser)
4138
+ noBrowser: Boolean(args.noBrowser),
4139
+ profileReused
4108
4140
  });
4109
4141
  const portReport = null !== boundPort ? {
4110
4142
  port: boundPort,
@@ -4138,6 +4170,7 @@ async function dev_handler(args) {
4138
4170
  },
4139
4171
  warnings: [
4140
4172
  portNote,
4173
+ profileReused && !args.noBrowser ? REUSED_PROFILE_NOTE : null,
4141
4174
  ...boot.warnings
4142
4175
  ],
4143
4176
  hint: args.noBrowser ? "Build-only session (noBrowser: true): no browser will launch, so no runtime will ever attach. extension_wait returns as soon as the first compile lands (compiled: true, browserAttached: false) instead of waiting out its budget; do not wait for a browser. The control verbs (storage/reload/open/dom_snapshot/eval) need a live browser and will not work against this session. When you are done, call extension_stop to shut down the dev server." : "Use extension_wait to check when the extension is fully loaded, then extension_inspect to inspect the live state. " + (allowControl ? `Control channel is ON: extension_${controlVerbs.split(", ").join("/extension_")}${args.allowEval ? "/extension_eval" : ""} will work against this session.` : "Control channel is OFF: extension_storage/reload/open/dom_snapshot need allowControl: true, and extension_eval needs allowEval: true (which also implies allowControl). To unlock them, call extension_dev again with the flag you need plus replace: true (it stops this session first); a plain second call is refused so the session does not fork.") + " When you are done, call extension_stop to shut down the dev server and browser."
@@ -4192,6 +4225,7 @@ async function start_handler(args) {
4192
4225
  if (args.noBrowser) cliArgs.push("--no-browser");
4193
4226
  cliArgs.push(...launchFlagArgs(args));
4194
4227
  const stale = removeCarrier(args.projectPath);
4228
+ const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
4195
4229
  const spawnedAt = Date.now();
4196
4230
  const spawned = spawnExtensionCli(cliArgs, {
4197
4231
  projectDir: args.projectPath
@@ -4203,7 +4237,8 @@ async function start_handler(args) {
4203
4237
  pid,
4204
4238
  browser,
4205
4239
  projectPath: args.projectPath,
4206
- command
4240
+ command,
4241
+ profileReused
4207
4242
  });
4208
4243
  child.on("exit", ()=>{
4209
4244
  removeSession(args.projectPath, browser, pid);
@@ -5742,6 +5777,7 @@ async function publish(options = {}) {
5742
5777
  }
5743
5778
  const ARTIFACT_ID = /^gen_(?:[0-9a-f]{32}|[0-9a-f]{64})$/;
5744
5779
  const ARTIFACT_ID_CANDIDATE = /gen_[0-9a-f]+/;
5780
+ const ZIP_URL_REDIRECT_NOTE = "zipUrl does not serve the archive itself: it answers 302 with a short-lived presigned storage URL in Location. Follow redirects when you fetch it (curl -L; fetch and most HTTP clients already do), because a client that does not follow them reads 0 bytes and reports the share as empty when it is not.";
5745
5781
  function parseArtifactRef(input) {
5746
5782
  const raw = String(input ?? "").trim();
5747
5783
  if (!raw) return null;
@@ -6442,7 +6478,7 @@ async function buildShare(projectPath, distDir, manifest, browser, verifyInBrows
6442
6478
  browserLoadable: null
6443
6479
  },
6444
6480
  record,
6445
- note: "Anyone with this link can open the build you just made, running in the emulator. No install, no sign-in, no dev server. They can also download the whole build as a zip from zipUrl, so the link hands over the built code. It stays live until expiresAt; DELETE revokeUrl with the same token to kill it sooner, and a revoked link stays dead. revokeUrl is the handle that pulls this link early. Re-sharing an unchanged build returns this same link rather than a second one, and only a revoked link is replaced by a different one, so " + (record.recorded ? `it was also written to ${record.path} (record.path), which lists every share from this project.` : `keep it: ${record.note}`) + " To find this link again later, or to pull it back once it has left this conversation, run extension_shares: it lists every link this token has shared with its live or dead state, and revokes one by artifactId or by pasting any of its URLs." + (record.warning ? ` ${record.warning}` : "")
6481
+ note: "Anyone with this link can open the build you just made, running in the emulator. No install, no sign-in, no dev server. They can also download the whole build as a zip from zipUrl, so the link hands over the built code. It stays live until expiresAt; DELETE revokeUrl with the same token to kill it sooner, and a revoked link stays dead. revokeUrl is the handle that pulls this link early. Re-sharing an unchanged build returns this same link rather than a second one, and only a revoked link is replaced by a different one, so " + (record.recorded ? `it was also written to ${record.path} (record.path), which lists every share from this project.` : `keep it: ${record.note}`) + " To find this link again later, or to pull it back once it has left this conversation, run extension_shares: it lists every link this token has shared with its live or dead state, and revokes one by artifactId or by pasting any of its URLs." + (result.data.zipUrl ? ` ${ZIP_URL_REDIRECT_NOTE}` : "") + (record.warning ? ` ${record.warning}` : "")
6446
6482
  };
6447
6483
  }
6448
6484
  function detectSurfaces(manifest) {
@@ -7010,6 +7046,7 @@ async function listShares(args) {
7010
7046
  } : {},
7011
7047
  status: localOnlyStatus(entry, completeness, liveFiltered, now)
7012
7048
  }));
7049
+ const handsOutAZip = shares.some((share)=>Boolean(share.zipUrl)) || localOnly.some((entry)=>Boolean(entry.zipUrl));
7013
7050
  const liveCount = shares.filter((share)=>share.live).length;
7014
7051
  const ownership = {
7015
7052
  project: shares.filter((s)=>"project" === s.attribution.ownership).length,
@@ -7046,6 +7083,7 @@ async function listShares(args) {
7046
7083
  warnings: [
7047
7084
  "previewUrl and zipUrl are null for a share that is no longer live, because a revoked or expired link cannot resolve for anyone. revokeUrl stays on every row. Revocation is permanent: a revoked id is burned, and re-sharing that build mints a different link. Re-sharing a build that was NOT revoked returns its existing link instead.",
7048
7085
  "attribution.ownership says who the share belongs to and therefore who may revoke it: project means the owning workspace holds it and any member can pull it back, personal means one person holds it alone. attribution.credit names the publisher and is attribution only, granting and restricting nothing. A credit of \"CLI token ...\" means the platform could not resolve which human minted that token, and a credit of \"not recorded\" means it never knew; neither is a name, and neither should be reported as one.",
7086
+ handsOutAZip ? ZIP_URL_REDIRECT_NOTE : null,
7049
7087
  truncatedNote
7050
7088
  ]
7051
7089
  });
@@ -9252,6 +9290,10 @@ async function inspectViaBridge(args, browser, include, maxBytes) {
9252
9290
  if (jsLooking.length) probeWarning = `Probes are CSS selectors run through querySelectorAll against the live page, NOT JavaScript expressions. ${jsLooking.map((s)=>`"${s}"`).join(", ")} parsed as selectors and will match nothing. To evaluate JS, use extension_eval.`;
9253
9291
  }
9254
9292
  const urlFilter = args.url ?? ("string" == typeof value.meta?.url ? value.meta.url : void 0);
9293
+ if (!args.url && sessionProfileReused(args.projectPath, browser)) {
9294
+ result.profileReused = true;
9295
+ notes.push(restoredTabWarning(urlFilter ?? "the tab this read landed on"));
9296
+ }
9255
9297
  if (include.has("console")) await collectGeckoConsole(args, browser, urlFilter, result, notes);
9256
9298
  if (args.deepDom) {
9257
9299
  const cap = maxBytes > 0 ? maxBytes : 65536;
@@ -9351,9 +9393,13 @@ async function inspect_handler(args) {
9351
9393
  }
9352
9394
  toolchainWarning = isEngineCompanionUrl(documentUrl) || isEngineCompanionUrl(target.url) ? `Inspected ${target.url}, which is rendered by the Extension.js toolchain's own companion extension, not by this project. Pass url, or open one of the extension's surfaces first (extension_open), then inspect again.` : `No url was given and only override pages were open, so this inspected ${target.url}. Unless this extension provides that override itself, this is the toolchain's welcome surface, not your extension: pass url or open a surface with extension_open.`;
9353
9395
  }
9396
+ const profileReused = !args.url && sessionProfileReused(args.projectPath, browser);
9354
9397
  const result = {
9355
9398
  cdpPort,
9356
9399
  browser,
9400
+ ...profileReused ? {
9401
+ profileReused: true
9402
+ } : {},
9357
9403
  target: {
9358
9404
  id: target.id,
9359
9405
  url: target.url,
@@ -9415,7 +9461,8 @@ async function inspect_handler(args) {
9415
9461
  value: result,
9416
9462
  warnings: [
9417
9463
  "string" == typeof probeWarning ? probeWarning : null,
9418
- toolchainWarning
9464
+ toolchainWarning,
9465
+ profileReused ? restoredTabWarning(documentUrl || target.url) : null
9419
9466
  ]
9420
9467
  });
9421
9468
  } catch (err) {
@@ -11555,7 +11602,7 @@ function sightedContractBrowser(projectPath) {
11555
11602
  }
11556
11603
  const doctor_schema = {
11557
11604
  name: "extension_doctor",
11558
- description: "Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. Run this first when any act tool (storage, reload, eval, open) errors unexpectedly. Call it with no projectPath for a pre-flight environment check (node, the Extension.js CLI, the template cache) before any project exists.",
11605
+ description: "Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. A session started without allowControl comes back ok:true with status 'read-only', not as an error: its control channel is off by choice. Run this first when any act tool (storage, reload, eval, open) errors unexpectedly. Call it with no projectPath for a pre-flight environment check (node, the Extension.js CLI, the template cache) before any project exists.",
11559
11606
  inputSchema: {
11560
11607
  type: "object",
11561
11608
  properties: {
@@ -11643,7 +11690,14 @@ function projectEngineVersion(projectPath) {
11643
11690
  }
11644
11691
  }
11645
11692
  function capabilityProbeChecks(parsed) {
11646
- return isEnvelope(parsed) ? parsed.value?.checks : parsed;
11693
+ if (!isEnvelope(parsed)) return parsed;
11694
+ const value = parsed.value;
11695
+ if (Array.isArray(value)) return value;
11696
+ return value?.checks;
11697
+ }
11698
+ const CONTROL_OFF_BY_CHOICE = /\bwas not started with --allow-control\b/i;
11699
+ function controlOffByChoiceLeg(checks) {
11700
+ return checks.find((leg)=>"control-channel" === leg.check && "fail" === leg.status && "string" == typeof leg.detail && CONTROL_OFF_BY_CHOICE.test(leg.detail)) ?? null;
11647
11701
  }
11648
11702
  async function doctor_handler(args) {
11649
11703
  if (!args.projectPath) return environmentPreflight();
@@ -11674,8 +11728,10 @@ async function doctor_handler(args) {
11674
11728
  const out = stdout.trim();
11675
11729
  try {
11676
11730
  const parsed = JSON.parse(out);
11677
- const checks = capabilityProbeChecks(parsed);
11678
- if (!Array.isArray(checks)) throw new Error("not a check array");
11731
+ const probed = capabilityProbeChecks(parsed);
11732
+ if (!Array.isArray(probed)) throw new Error("not a check array");
11733
+ const checks = probed;
11734
+ const readOnlyLeg = controlOffByChoiceLeg(checks);
11679
11735
  for (const check of checks){
11680
11736
  if ("string" == typeof check.detail) check.detail = toMcpSpeak(check.detail);
11681
11737
  if ("string" == typeof check.remediation) check.remediation = toMcpSpeak(check.remediation);
@@ -11717,17 +11773,30 @@ async function doctor_handler(args) {
11717
11773
  } : {}
11718
11774
  });
11719
11775
  }
11776
+ const failures = checks.filter((leg)=>"fail" === leg.status);
11777
+ const readOnly = null !== readOnlyLeg && 1 === failures.length && failures[0] === readOnlyLeg;
11778
+ if (readOnly && readOnlyLeg) {
11779
+ readOnlyLeg.status = "warn";
11780
+ readOnlyLeg.detail = `read-only by choice: ${readOnlyLeg.detail}`;
11781
+ readOnlyLeg.remediation = "Nothing failed. To unlock the control verbs, call extension_dev again with allowControl: true (or allowEval: true) plus replace: true, which stops this session first; a plain second call is refused so the session does not fork.";
11782
+ }
11720
11783
  return envelope_envelope({
11721
- ok: healthy,
11784
+ ok: healthy || readOnly,
11722
11785
  command: doctor_schema.name,
11723
- status: healthy ? "healthy" : "unhealthy",
11786
+ status: healthy ? "healthy" : readOnly ? "read-only" : "unhealthy",
11724
11787
  value: {
11725
11788
  browser,
11726
11789
  ...engineVersion ? {
11727
11790
  engineVersion
11728
11791
  } : {},
11792
+ ...readOnly ? {
11793
+ readOnly: true
11794
+ } : {},
11729
11795
  checks
11730
- }
11796
+ },
11797
+ ...readOnly ? {
11798
+ hint: "This session is read-only because it was started without allowControl, not because anything is wrong: logs, inspect, wait and doctor all work, while storage, reload, open, dom_snapshot and eval stay locked."
11799
+ } : {}
11731
11800
  });
11732
11801
  } catch {
11733
11802
  const message = stderr.trim() || `extension exited with code ${code}`;
@@ -12208,6 +12277,7 @@ async function add_feature_handler(args) {
12208
12277
  referenceTemplate: {
12209
12278
  slug: templateSlug,
12210
12279
  repositoryUrl: `${EXAMPLES_TREE_BASE}/${templateSlug}`,
12280
+ catalogUrl: templateCatalogUrl(templateSlug),
12211
12281
  referenceFiles: referenceFiles.filter((f)=>f.includes(featureDir) || f.includes("manifest"))
12212
12282
  },
12213
12283
  manifestUpdates,
@@ -13,6 +13,7 @@ export interface ArtifactPublisher {
13
13
  project: string | null;
14
14
  tokenId: string | null;
15
15
  }
16
+ export declare const ZIP_URL_REDIRECT_NOTE = "zipUrl does not serve the archive itself: it answers 302 with a short-lived presigned storage URL in Location. Follow redirects when you fetch it (curl -L; fetch and most HTTP clients already do), because a client that does not follow them reads 0 bytes and reports the share as empty when it is not.";
16
17
  export interface ListedArtifact {
17
18
  artifactId: string;
18
19
  kind?: string;
@@ -0,0 +1,4 @@
1
+ export declare const SYSTEM_PROFILE_ARG = "false";
2
+ export declare function profileCarriesTabsOver(projectPath: string, browser: string, profileArg?: string): boolean;
3
+ export declare function sessionProfileReused(projectPath: string, browser: string): boolean;
4
+ export declare function restoredTabWarning(url: string): string;
@@ -1,4 +1,6 @@
1
- export declare const PINNED_COMMIT = "520a8ede767fe1937c7184f4857408caf9c5934f";
1
+ export declare const PINNED_COMMIT = "52c0d871c433d9c54878767175ce8ffc9a951756";
2
+ export type TemplateCatalogUtmSource = "mcp" | "mcp-create";
3
+ export declare function templateCatalogUrl(slug: string, utmSource?: TemplateCatalogUtmSource): string;
2
4
  export declare function templateMetaUrls(): Promise<string[]>;
3
5
  export declare function stripTemplatePathPrefix(slug: string, relativePath: string): string;
4
6
  export declare function templateFileUrls(slug: string, relativePath: string): Promise<string[]>;
@@ -71,5 +71,6 @@ export interface ProcessInfo {
71
71
  projectPath: string;
72
72
  command: "dev" | "start" | "preview";
73
73
  noBrowser?: boolean;
74
+ profileReused?: boolean;
74
75
  }
75
76
  export type BrowserType = "chrome" | "edge" | "firefox" | "chromium-based" | "gecko-based";
@@ -1,9 +1,6 @@
1
1
  <!DOCTYPE html><html lang="en"><head>
2
2
  <meta charset="utf-8">
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1">
4
- <!-- Dark-only surface: signals the popup chrome up front so there is no
5
- white flash before styles.css loads. The brand tokens live in
6
- ./styles.css (synced with @extensiondev/ui), not inline here. -->
7
4
  <meta name="color-scheme" content="dark">
8
5
  <title>Extension.dev Live Preview</title>
9
6
  <link rel="stylesheet" href="/action/index.css"></head>
@@ -3,19 +3,8 @@
3
3
  <title>Extension preview host</title>
4
4
  </head>
5
5
  <body>
6
- <!--
7
- Sandbox execution surface for guest extension code.
8
- Declared in manifest.json under chromium:sandbox.pages with a CSP
9
- that allows unsafe-eval / blob: so guest code can run for real inside this
10
- installed extension. This file exists so the sandbox reference resolves and
11
- the build has a host page to mount; the full executor wiring (receiving a
12
- guest PreviewSession and running its surfaces here) is the next increment.
13
- Until then, guest surfaces still render through the creator-preview iframes.
14
- -->
15
6
  <div id="preview-host-root"></div>
16
7
  <script>
17
- // Signal the parent (the newtab runtime) that the sandbox host is mounted
18
- // and ready to receive a guest preview to execute.
19
8
  if (window.parent) {
20
9
  window.parent.postMessage(
21
10
  { type: "EXTENSION_PREVIEW_HOST_READY" },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "10.3.0",
4
+ "version": "10.4.0",
5
5
  "description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 28 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, Firefox, Safari, and every Chromium- or Gecko-based browser (Brave, Opera, Vivaldi, Yandex, Waterfox, LibreWolf). Powered by extension.dev and Extension.js.",
6
6
  "mcpName": "io.github.extensiondev/mcp",
7
7
  "license": "Apache-2.0",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://extension.dev",
10
- "version": "10.3.0",
10
+ "version": "10.4.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@extension.dev/mcp",
16
- "version": "10.3.0",
16
+ "version": "10.4.0",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"