@extension.dev/mcp 10.3.0 → 10.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/README.md +2 -2
- package/dist/module.js +74 -13
- package/dist/src/lib/artifacts-api.d.ts +1 -0
- package/dist/src/lib/profile-carryover.d.ts +4 -0
- package/dist/src/lib/types.d.ts +1 -0
- package/extensions/live-preview/chromium/action/index.html +0 -3
- package/extensions/live-preview/chromium/sandbox/page-0.html +0 -11
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -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.
|
|
13
|
+
"version": "10.3.1",
|
|
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.
|
|
4
|
+
"version": "10.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cezar Augusto",
|
|
7
7
|
"email": "hello@extension.dev",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 10.3.1
|
|
4
|
+
|
|
5
|
+
The 10.3.0 tarball shipped two comments a stranger could read: an HTML
|
|
6
|
+
comment in the popup markup naming a private package, and an eight line
|
|
7
|
+
internal roadmap comment plus two inline script comments in the live
|
|
8
|
+
preview sandbox page. Both were removed at source right after that
|
|
9
|
+
release. This release exists to take them off the registry, because a
|
|
10
|
+
published tarball cannot be edited in place.
|
|
11
|
+
|
|
12
|
+
- The popup markup no longer names any private package.
|
|
13
|
+
- `extensions/live-preview/chromium/sandbox/page-0.html` ships exactly
|
|
14
|
+
the markup it runs, with no comments at all.
|
|
15
|
+
|
|
3
16
|
## 10.3.0
|
|
4
17
|
|
|
5
18
|
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
|
|
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
|
|
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.
|
|
585
|
+
var package_namespaceObject = JSON.parse('{"rE":"10.3.1","El":{"OP":"4.0.20"}}');
|
|
586
586
|
const ENVELOPE_SCHEMA = 1;
|
|
587
587
|
const collectWarnings = (warnings)=>{
|
|
588
588
|
if (!warnings) return [];
|
|
@@ -3530,6 +3530,26 @@ async function pollBootVerdict(projectPath, browser, options) {
|
|
|
3530
3530
|
warnings: []
|
|
3531
3531
|
};
|
|
3532
3532
|
}
|
|
3533
|
+
const SYSTEM_PROFILE_ARG = "false";
|
|
3534
|
+
function holdsState(dir) {
|
|
3535
|
+
try {
|
|
3536
|
+
return node_fs.readdirSync(dir).length > 0;
|
|
3537
|
+
} catch {
|
|
3538
|
+
return false;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
function profileCarriesTabsOver(projectPath, browser, profileArg) {
|
|
3542
|
+
const raw = "string" == typeof profileArg ? profileArg.trim() : "";
|
|
3543
|
+
if (raw === SYSTEM_PROFILE_ARG) return true;
|
|
3544
|
+
if (raw) return holdsState(node_path.resolve(projectPath, raw));
|
|
3545
|
+
return holdsState(node_path.join(browserProfileRootDir(projectPath, browser), PERSISTED_PROFILE_DIR_NAME));
|
|
3546
|
+
}
|
|
3547
|
+
function sessionProfileReused(projectPath, browser) {
|
|
3548
|
+
return findSessionInfo(projectPath, browser)?.profileReused === true;
|
|
3549
|
+
}
|
|
3550
|
+
function restoredTabWarning(url) {
|
|
3551
|
+
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.`;
|
|
3552
|
+
}
|
|
3533
3553
|
function sweepCarriers(projectPaths) {
|
|
3534
3554
|
const out = [];
|
|
3535
3555
|
const seen = new Set();
|
|
@@ -3870,6 +3890,7 @@ function launchFlagArgs(args) {
|
|
|
3870
3890
|
if (args.extensions?.length) cli.push("--extensions", args.extensions.join(","));
|
|
3871
3891
|
return cli;
|
|
3872
3892
|
}
|
|
3893
|
+
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
3894
|
const dev_schema = {
|
|
3874
3895
|
name: "extension_dev",
|
|
3875
3896
|
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 +3975,7 @@ async function dev_handler(args) {
|
|
|
3954
3975
|
}
|
|
3955
3976
|
const carrier = args.carrier ? materializeCarrier(args.projectPath, browser) : null;
|
|
3956
3977
|
const allowControl = Boolean(args.allowControl || args.allowEval);
|
|
3978
|
+
const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
|
|
3957
3979
|
const cliArgs = [
|
|
3958
3980
|
"dev",
|
|
3959
3981
|
args.projectPath,
|
|
@@ -3979,7 +4001,8 @@ async function dev_handler(args) {
|
|
|
3979
4001
|
port: args.port,
|
|
3980
4002
|
projectPath: args.projectPath,
|
|
3981
4003
|
command: "dev",
|
|
3982
|
-
noBrowser: Boolean(args.noBrowser)
|
|
4004
|
+
noBrowser: Boolean(args.noBrowser),
|
|
4005
|
+
profileReused
|
|
3983
4006
|
});
|
|
3984
4007
|
child.on("exit", ()=>{
|
|
3985
4008
|
removeSession(args.projectPath, browser, pid);
|
|
@@ -4104,7 +4127,8 @@ async function dev_handler(args) {
|
|
|
4104
4127
|
port: boundPort,
|
|
4105
4128
|
projectPath: args.projectPath,
|
|
4106
4129
|
command: "dev",
|
|
4107
|
-
noBrowser: Boolean(args.noBrowser)
|
|
4130
|
+
noBrowser: Boolean(args.noBrowser),
|
|
4131
|
+
profileReused
|
|
4108
4132
|
});
|
|
4109
4133
|
const portReport = null !== boundPort ? {
|
|
4110
4134
|
port: boundPort,
|
|
@@ -4138,6 +4162,7 @@ async function dev_handler(args) {
|
|
|
4138
4162
|
},
|
|
4139
4163
|
warnings: [
|
|
4140
4164
|
portNote,
|
|
4165
|
+
profileReused && !args.noBrowser ? REUSED_PROFILE_NOTE : null,
|
|
4141
4166
|
...boot.warnings
|
|
4142
4167
|
],
|
|
4143
4168
|
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 +4217,7 @@ async function start_handler(args) {
|
|
|
4192
4217
|
if (args.noBrowser) cliArgs.push("--no-browser");
|
|
4193
4218
|
cliArgs.push(...launchFlagArgs(args));
|
|
4194
4219
|
const stale = removeCarrier(args.projectPath);
|
|
4220
|
+
const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
|
|
4195
4221
|
const spawnedAt = Date.now();
|
|
4196
4222
|
const spawned = spawnExtensionCli(cliArgs, {
|
|
4197
4223
|
projectDir: args.projectPath
|
|
@@ -4203,7 +4229,8 @@ async function start_handler(args) {
|
|
|
4203
4229
|
pid,
|
|
4204
4230
|
browser,
|
|
4205
4231
|
projectPath: args.projectPath,
|
|
4206
|
-
command
|
|
4232
|
+
command,
|
|
4233
|
+
profileReused
|
|
4207
4234
|
});
|
|
4208
4235
|
child.on("exit", ()=>{
|
|
4209
4236
|
removeSession(args.projectPath, browser, pid);
|
|
@@ -5742,6 +5769,7 @@ async function publish(options = {}) {
|
|
|
5742
5769
|
}
|
|
5743
5770
|
const ARTIFACT_ID = /^gen_(?:[0-9a-f]{32}|[0-9a-f]{64})$/;
|
|
5744
5771
|
const ARTIFACT_ID_CANDIDATE = /gen_[0-9a-f]+/;
|
|
5772
|
+
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
5773
|
function parseArtifactRef(input) {
|
|
5746
5774
|
const raw = String(input ?? "").trim();
|
|
5747
5775
|
if (!raw) return null;
|
|
@@ -6442,7 +6470,7 @@ async function buildShare(projectPath, distDir, manifest, browser, verifyInBrows
|
|
|
6442
6470
|
browserLoadable: null
|
|
6443
6471
|
},
|
|
6444
6472
|
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}` : "")
|
|
6473
|
+
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
6474
|
};
|
|
6447
6475
|
}
|
|
6448
6476
|
function detectSurfaces(manifest) {
|
|
@@ -7010,6 +7038,7 @@ async function listShares(args) {
|
|
|
7010
7038
|
} : {},
|
|
7011
7039
|
status: localOnlyStatus(entry, completeness, liveFiltered, now)
|
|
7012
7040
|
}));
|
|
7041
|
+
const handsOutAZip = shares.some((share)=>Boolean(share.zipUrl)) || localOnly.some((entry)=>Boolean(entry.zipUrl));
|
|
7013
7042
|
const liveCount = shares.filter((share)=>share.live).length;
|
|
7014
7043
|
const ownership = {
|
|
7015
7044
|
project: shares.filter((s)=>"project" === s.attribution.ownership).length,
|
|
@@ -7046,6 +7075,7 @@ async function listShares(args) {
|
|
|
7046
7075
|
warnings: [
|
|
7047
7076
|
"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
7077
|
"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.",
|
|
7078
|
+
handsOutAZip ? ZIP_URL_REDIRECT_NOTE : null,
|
|
7049
7079
|
truncatedNote
|
|
7050
7080
|
]
|
|
7051
7081
|
});
|
|
@@ -9252,6 +9282,10 @@ async function inspectViaBridge(args, browser, include, maxBytes) {
|
|
|
9252
9282
|
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
9283
|
}
|
|
9254
9284
|
const urlFilter = args.url ?? ("string" == typeof value.meta?.url ? value.meta.url : void 0);
|
|
9285
|
+
if (!args.url && sessionProfileReused(args.projectPath, browser)) {
|
|
9286
|
+
result.profileReused = true;
|
|
9287
|
+
notes.push(restoredTabWarning(urlFilter ?? "the tab this read landed on"));
|
|
9288
|
+
}
|
|
9255
9289
|
if (include.has("console")) await collectGeckoConsole(args, browser, urlFilter, result, notes);
|
|
9256
9290
|
if (args.deepDom) {
|
|
9257
9291
|
const cap = maxBytes > 0 ? maxBytes : 65536;
|
|
@@ -9351,9 +9385,13 @@ async function inspect_handler(args) {
|
|
|
9351
9385
|
}
|
|
9352
9386
|
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
9387
|
}
|
|
9388
|
+
const profileReused = !args.url && sessionProfileReused(args.projectPath, browser);
|
|
9354
9389
|
const result = {
|
|
9355
9390
|
cdpPort,
|
|
9356
9391
|
browser,
|
|
9392
|
+
...profileReused ? {
|
|
9393
|
+
profileReused: true
|
|
9394
|
+
} : {},
|
|
9357
9395
|
target: {
|
|
9358
9396
|
id: target.id,
|
|
9359
9397
|
url: target.url,
|
|
@@ -9415,7 +9453,8 @@ async function inspect_handler(args) {
|
|
|
9415
9453
|
value: result,
|
|
9416
9454
|
warnings: [
|
|
9417
9455
|
"string" == typeof probeWarning ? probeWarning : null,
|
|
9418
|
-
toolchainWarning
|
|
9456
|
+
toolchainWarning,
|
|
9457
|
+
profileReused ? restoredTabWarning(documentUrl || target.url) : null
|
|
9419
9458
|
]
|
|
9420
9459
|
});
|
|
9421
9460
|
} catch (err) {
|
|
@@ -11555,7 +11594,7 @@ function sightedContractBrowser(projectPath) {
|
|
|
11555
11594
|
}
|
|
11556
11595
|
const doctor_schema = {
|
|
11557
11596
|
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.",
|
|
11597
|
+
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
11598
|
inputSchema: {
|
|
11560
11599
|
type: "object",
|
|
11561
11600
|
properties: {
|
|
@@ -11643,7 +11682,14 @@ function projectEngineVersion(projectPath) {
|
|
|
11643
11682
|
}
|
|
11644
11683
|
}
|
|
11645
11684
|
function capabilityProbeChecks(parsed) {
|
|
11646
|
-
|
|
11685
|
+
if (!isEnvelope(parsed)) return parsed;
|
|
11686
|
+
const value = parsed.value;
|
|
11687
|
+
if (Array.isArray(value)) return value;
|
|
11688
|
+
return value?.checks;
|
|
11689
|
+
}
|
|
11690
|
+
const CONTROL_OFF_BY_CHOICE = /\bwas not started with --allow-control\b/i;
|
|
11691
|
+
function controlOffByChoiceLeg(checks) {
|
|
11692
|
+
return checks.find((leg)=>"control-channel" === leg.check && "fail" === leg.status && "string" == typeof leg.detail && CONTROL_OFF_BY_CHOICE.test(leg.detail)) ?? null;
|
|
11647
11693
|
}
|
|
11648
11694
|
async function doctor_handler(args) {
|
|
11649
11695
|
if (!args.projectPath) return environmentPreflight();
|
|
@@ -11674,8 +11720,10 @@ async function doctor_handler(args) {
|
|
|
11674
11720
|
const out = stdout.trim();
|
|
11675
11721
|
try {
|
|
11676
11722
|
const parsed = JSON.parse(out);
|
|
11677
|
-
const
|
|
11678
|
-
if (!Array.isArray(
|
|
11723
|
+
const probed = capabilityProbeChecks(parsed);
|
|
11724
|
+
if (!Array.isArray(probed)) throw new Error("not a check array");
|
|
11725
|
+
const checks = probed;
|
|
11726
|
+
const readOnlyLeg = controlOffByChoiceLeg(checks);
|
|
11679
11727
|
for (const check of checks){
|
|
11680
11728
|
if ("string" == typeof check.detail) check.detail = toMcpSpeak(check.detail);
|
|
11681
11729
|
if ("string" == typeof check.remediation) check.remediation = toMcpSpeak(check.remediation);
|
|
@@ -11717,17 +11765,30 @@ async function doctor_handler(args) {
|
|
|
11717
11765
|
} : {}
|
|
11718
11766
|
});
|
|
11719
11767
|
}
|
|
11768
|
+
const failures = checks.filter((leg)=>"fail" === leg.status);
|
|
11769
|
+
const readOnly = null !== readOnlyLeg && 1 === failures.length && failures[0] === readOnlyLeg;
|
|
11770
|
+
if (readOnly && readOnlyLeg) {
|
|
11771
|
+
readOnlyLeg.status = "warn";
|
|
11772
|
+
readOnlyLeg.detail = `read-only by choice: ${readOnlyLeg.detail}`;
|
|
11773
|
+
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.";
|
|
11774
|
+
}
|
|
11720
11775
|
return envelope_envelope({
|
|
11721
|
-
ok: healthy,
|
|
11776
|
+
ok: healthy || readOnly,
|
|
11722
11777
|
command: doctor_schema.name,
|
|
11723
|
-
status: healthy ? "healthy" : "unhealthy",
|
|
11778
|
+
status: healthy ? "healthy" : readOnly ? "read-only" : "unhealthy",
|
|
11724
11779
|
value: {
|
|
11725
11780
|
browser,
|
|
11726
11781
|
...engineVersion ? {
|
|
11727
11782
|
engineVersion
|
|
11728
11783
|
} : {},
|
|
11784
|
+
...readOnly ? {
|
|
11785
|
+
readOnly: true
|
|
11786
|
+
} : {},
|
|
11729
11787
|
checks
|
|
11730
|
-
}
|
|
11788
|
+
},
|
|
11789
|
+
...readOnly ? {
|
|
11790
|
+
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."
|
|
11791
|
+
} : {}
|
|
11731
11792
|
});
|
|
11732
11793
|
} catch {
|
|
11733
11794
|
const message = stderr.trim() || `extension exited with code ${code}`;
|
|
@@ -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;
|
package/dist/src/lib/types.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "10.3.1",
|
|
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.
|
|
10
|
+
"version": "10.3.1",
|
|
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.
|
|
16
|
+
"version": "10.3.1",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|