@extension.dev/mcp 6.0.0 → 6.2.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 60+ 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": "6.0.0",
13
+ "version": "6.2.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 60+ 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": "6.0.0",
4
+ "version": "6.2.0",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.2.0
4
+
5
+ The URL layer stops being a copy. This server used to carry byte-identical
6
+ vendored mirrors of the fleet's origin resolver and path builders, kept honest
7
+ by a drift guard; it now depends on the published package instead.
8
+
9
+ ### Changed
10
+
11
+ - **Depends on `@extension.dev/urls` instead of vendoring it.** The mirrors at
12
+ `lib/urls-origins.ts` and `lib/urls-paths.ts` are gone, and `registry.ts`,
13
+ `login-flow.ts`, and `create.ts` import the package directly. It is bundled
14
+ into `dist`, so the install footprint is unchanged: no new runtime
15
+ dependency, same standalone server.
16
+ - **`preview.extension.dev` resolves like every other fleet origin.** `preview`
17
+ is now a first-class origin in the shared resolver, so `EXTENSION_DEV_PREVIEW_URL`
18
+ is honored and an unset preview host follows the same local-vs-prod signal as
19
+ console, inspect, and registry rather than defaulting to production.
20
+
21
+ ## 6.1.0
22
+
23
+ The create flow stops dead-ending at `run dev` and points you to the web to
24
+ host, template source resolves whichever path the catalog listed, and the
25
+ links the server hands back ride the exact corpus commit it built from.
26
+
27
+ ### Added
28
+
29
+ - **`extension_create` signposts the web deploy.** The result now carries a
30
+ `deployUrl` and a closing next step that says the scaffold runs locally and
31
+ where to open the template on the web to host it, so the local scaffold no
32
+ longer stops at `run dev` with nowhere to ship.
33
+ - **`extension_create` names the template it chose.** When no `template` is
34
+ passed the response now discloses the silent default instead of quietly
35
+ scaffolding TypeScript, and points at `extension_list_templates` to pick
36
+ another.
37
+
38
+ ### Fixed
39
+
40
+ - **`extension_get_template_source` resolves listed paths.** A file listed
41
+ with a leading `public/<slug>/` or `examples/<slug>/` prefix now strips to
42
+ the slug relative path both hosts actually serve, so passing back a listed
43
+ path no longer 404s.
44
+ - **`extension_list_templates` keeps the vanilla template filterable.** The
45
+ relabel no longer drops the framework key the filter reads.
46
+ - **`extension_add_feature` links ride the pinned corpus.** Feature links now
47
+ point at the pinned corpus commit instead of floating on `main`.
48
+
3
49
  ## 6.0.0
4
50
 
5
51
  The server moves to Apache-2.0, and the live-preview carrier stops living
package/dist/module.js CHANGED
@@ -16,6 +16,223 @@ import node_http from "node:http";
16
16
  import node_net from "node:net";
17
17
  import { extensionInstall, extensionUninstall, getManagedBrowsersCacheRoot } from "extension-install";
18
18
  import { promisify } from "node:util";
19
+ __webpack_require__.add({
20
+ "./node_modules/.pnpm/@extension.dev+urls@0.2.0/node_modules/@extension.dev/urls/dist/origins.cjs" (__unused_rspack_module, exports) {
21
+ var __nested_rspack_require_18_37__ = {};
22
+ (()=>{
23
+ __nested_rspack_require_18_37__.d = (exports1, definition)=>{
24
+ for(var key in definition)if (__nested_rspack_require_18_37__.o(definition, key) && !__nested_rspack_require_18_37__.o(exports1, key)) Object.defineProperty(exports1, key, {
25
+ enumerable: true,
26
+ get: definition[key]
27
+ });
28
+ };
29
+ })();
30
+ (()=>{
31
+ __nested_rspack_require_18_37__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
32
+ })();
33
+ (()=>{
34
+ __nested_rspack_require_18_37__.r = (exports1)=>{
35
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
36
+ value: 'Module'
37
+ });
38
+ Object.defineProperty(exports1, '__esModule', {
39
+ value: true
40
+ });
41
+ };
42
+ })();
43
+ var __nested_rspack_exports__ = {};
44
+ __nested_rspack_require_18_37__.r(__nested_rspack_exports__);
45
+ __nested_rspack_require_18_37__.d(__nested_rspack_exports__, {
46
+ DEV_LOCALHOST_ORIGINS: ()=>DEV_LOCALHOST_ORIGINS,
47
+ PROD_ORIGINS: ()=>PROD_ORIGINS,
48
+ isLocalOrigin: ()=>isLocalOrigin,
49
+ resolveOrigins: ()=>resolveOrigins
50
+ });
51
+ const PROD_ORIGINS = {
52
+ www: "https://www.extension.dev",
53
+ console: "https://console.extension.dev",
54
+ inspect: "https://inspect.extension.dev",
55
+ preview: "https://preview.extension.dev",
56
+ templates: "https://templates.extension.dev",
57
+ intelligence: "https://intelligence.extension.dev",
58
+ registry: "https://registry.extension.land",
59
+ media: "https://media.extension.land"
60
+ };
61
+ const DEV_LOCALHOST_ORIGINS = {
62
+ www: "http://localhost:3100",
63
+ console: "http://console.extension.localhost",
64
+ inspect: "http://inspect.extension.localhost",
65
+ preview: "http://preview.extension.localhost",
66
+ templates: "http://templates.extension.localhost",
67
+ intelligence: "http://intelligence.extension.localhost",
68
+ registry: "https://registry.extension.land",
69
+ media: "https://media.extension.land"
70
+ };
71
+ function strip(value) {
72
+ return String(value ?? "").trim().replace(/\/+$/, "");
73
+ }
74
+ function isLocalOrigin(url) {
75
+ const raw = strip(url);
76
+ if (!raw) return false;
77
+ let host;
78
+ try {
79
+ host = new URL(raw).hostname;
80
+ } catch {
81
+ return false;
82
+ }
83
+ return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
84
+ }
85
+ function resolveOrigins(overrides = {}, opts = {}) {
86
+ const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
87
+ const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
88
+ const pick = (key)=>strip(overrides[key]) || base[key];
89
+ return {
90
+ www: pick("www"),
91
+ console: pick("console"),
92
+ inspect: pick("inspect"),
93
+ preview: pick("preview"),
94
+ templates: pick("templates"),
95
+ intelligence: pick("intelligence"),
96
+ registry: pick("registry"),
97
+ media: pick("media")
98
+ };
99
+ }
100
+ exports.DEV_LOCALHOST_ORIGINS = __nested_rspack_exports__.DEV_LOCALHOST_ORIGINS;
101
+ exports.PROD_ORIGINS = __nested_rspack_exports__.PROD_ORIGINS;
102
+ exports.isLocalOrigin = __nested_rspack_exports__.isLocalOrigin;
103
+ exports.resolveOrigins = __nested_rspack_exports__.resolveOrigins;
104
+ for(var __webpack_i__ in __nested_rspack_exports__)if (-1 === [
105
+ "DEV_LOCALHOST_ORIGINS",
106
+ "PROD_ORIGINS",
107
+ "isLocalOrigin",
108
+ "resolveOrigins"
109
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __nested_rspack_exports__[__webpack_i__];
110
+ Object.defineProperty(exports, "__esModule", {
111
+ value: true
112
+ });
113
+ },
114
+ "./node_modules/.pnpm/@extension.dev+urls@0.2.0/node_modules/@extension.dev/urls/dist/paths.cjs" (__unused_rspack_module, exports) {
115
+ var __nested_rspack_require_18_37__ = {};
116
+ (()=>{
117
+ __nested_rspack_require_18_37__.d = (exports1, definition)=>{
118
+ for(var key in definition)if (__nested_rspack_require_18_37__.o(definition, key) && !__nested_rspack_require_18_37__.o(exports1, key)) Object.defineProperty(exports1, key, {
119
+ enumerable: true,
120
+ get: definition[key]
121
+ });
122
+ };
123
+ })();
124
+ (()=>{
125
+ __nested_rspack_require_18_37__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
126
+ })();
127
+ (()=>{
128
+ __nested_rspack_require_18_37__.r = (exports1)=>{
129
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
130
+ value: 'Module'
131
+ });
132
+ Object.defineProperty(exports1, '__esModule', {
133
+ value: true
134
+ });
135
+ };
136
+ })();
137
+ var __nested_rspack_exports__ = {};
138
+ __nested_rspack_require_18_37__.r(__nested_rspack_exports__);
139
+ __nested_rspack_require_18_37__.d(__nested_rspack_exports__, {
140
+ ConsoleProjectPage: ()=>ConsoleProjectPage,
141
+ consoleProjectPath: ()=>consoleProjectPath,
142
+ consoleWorkspacePath: ()=>consoleWorkspacePath,
143
+ inspectTabPath: ()=>inspectTabPath,
144
+ previewSharePath: ()=>previewSharePath,
145
+ templateTabPath: ()=>templateTabPath,
146
+ wwwDevicePath: ()=>wwwDevicePath,
147
+ wwwImportPath: ()=>wwwImportPath,
148
+ wwwNewPath: ()=>wwwNewPath,
149
+ wwwTemplatesPath: ()=>wwwTemplatesPath
150
+ });
151
+ const seg = (value)=>encodeURIComponent(String(value));
152
+ function join(base, sub) {
153
+ if (!sub) return base;
154
+ return `${base}/${sub.replace(/^\/+/, "")}`;
155
+ }
156
+ function consoleWorkspacePath(workspace, page = "") {
157
+ return join(`/${seg(workspace)}`, page);
158
+ }
159
+ function consoleProjectPath(ref, page = "") {
160
+ return join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
161
+ }
162
+ const ConsoleProjectPage = {
163
+ overview: "",
164
+ onboard: "onboard",
165
+ activity: "activity",
166
+ builds: "builds",
167
+ build: (buildId, browser)=>browser ? `builds/${seg(buildId)}/${seg(browser)}` : `builds/${seg(buildId)}`,
168
+ releases: "releases",
169
+ releasesNew: "releases/new",
170
+ release: (releaseId)=>`releases/${seg(releaseId)}`,
171
+ stores: "stores",
172
+ storesNew: "stores/new",
173
+ store: (store)=>`stores/${seg(store)}`,
174
+ storeSubmissions: (store)=>`stores/${seg(store)}/submissions`,
175
+ storeSubmissionNew: (store)=>`stores/${seg(store)}/submissions/new`,
176
+ storeSubmission: (store, submissionId)=>`stores/${seg(store)}/submissions/${seg(submissionId)}`,
177
+ projectSettings: "project-settings",
178
+ projectSettingsSection: (section)=>`project-settings/${seg(section)}`,
179
+ accessTokens: "settings/access-tokens"
180
+ };
181
+ function withQuery(path, query) {
182
+ if (!query) return path;
183
+ const params = new URLSearchParams();
184
+ for (const [key, value] of Object.entries(query))if (null != value && "" !== value) params.set(key, String(value));
185
+ const qs = params.toString();
186
+ return qs ? `${path}?${qs}` : path;
187
+ }
188
+ function wwwNewPath(query) {
189
+ return withQuery("/new", query);
190
+ }
191
+ function wwwImportPath(query) {
192
+ return withQuery("/import", query);
193
+ }
194
+ function wwwDevicePath() {
195
+ return "/device";
196
+ }
197
+ function wwwTemplatesPath(slug) {
198
+ return slug ? `/templates/${seg(slug)}` : "/templates";
199
+ }
200
+ function templateTabPath(slug, tab = "preview") {
201
+ return "preview" === tab ? `/${seg(slug)}` : `/${seg(slug)}/${tab}`;
202
+ }
203
+ function inspectTabPath(tab = "preview") {
204
+ return "preview" === tab ? "/" : `/${tab}`;
205
+ }
206
+ function previewSharePath(previewId) {
207
+ return `/?preview=${seg(previewId)}`;
208
+ }
209
+ exports.ConsoleProjectPage = __nested_rspack_exports__.ConsoleProjectPage;
210
+ exports.consoleProjectPath = __nested_rspack_exports__.consoleProjectPath;
211
+ exports.consoleWorkspacePath = __nested_rspack_exports__.consoleWorkspacePath;
212
+ exports.inspectTabPath = __nested_rspack_exports__.inspectTabPath;
213
+ exports.previewSharePath = __nested_rspack_exports__.previewSharePath;
214
+ exports.templateTabPath = __nested_rspack_exports__.templateTabPath;
215
+ exports.wwwDevicePath = __nested_rspack_exports__.wwwDevicePath;
216
+ exports.wwwImportPath = __nested_rspack_exports__.wwwImportPath;
217
+ exports.wwwNewPath = __nested_rspack_exports__.wwwNewPath;
218
+ exports.wwwTemplatesPath = __nested_rspack_exports__.wwwTemplatesPath;
219
+ for(var __webpack_i__ in __nested_rspack_exports__)if (-1 === [
220
+ "ConsoleProjectPage",
221
+ "consoleProjectPath",
222
+ "consoleWorkspacePath",
223
+ "inspectTabPath",
224
+ "previewSharePath",
225
+ "templateTabPath",
226
+ "wwwDevicePath",
227
+ "wwwImportPath",
228
+ "wwwNewPath",
229
+ "wwwTemplatesPath"
230
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __nested_rspack_exports__[__webpack_i__];
231
+ Object.defineProperty(exports, "__esModule", {
232
+ value: true
233
+ });
234
+ }
235
+ });
19
236
  var add_feature_namespaceObject = {};
20
237
  __webpack_require__.r(add_feature_namespaceObject);
21
238
  __webpack_require__.d(add_feature_namespaceObject, {
@@ -222,7 +439,199 @@ __webpack_require__.d(whoami_namespaceObject, {
222
439
  handler: ()=>whoami_handler,
223
440
  schema: ()=>whoami_schema
224
441
  });
225
- var package_namespaceObject = JSON.parse('{"rE":"5.7.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
442
+ var package_namespaceObject = JSON.parse('{"rE":"6.1.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
443
+ function credentialsPath() {
444
+ if ("win32" === process.platform) {
445
+ const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
446
+ return node_path.join(base, "extension-dev", "auth.json");
447
+ }
448
+ const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
449
+ const base = xdg || node_path.join(node_os.homedir(), ".config");
450
+ return node_path.join(base, "extension-dev", "auth.json");
451
+ }
452
+ function readCredentials() {
453
+ try {
454
+ const raw = node_fs.readFileSync(credentialsPath(), "utf8");
455
+ const data = JSON.parse(raw);
456
+ if (!data || "object" != typeof data) return null;
457
+ if (1 !== data.version) return null;
458
+ const token = String(data.token || "").trim();
459
+ if (!token) return null;
460
+ const provider = "extensiondev" === data.provider || "github" === data.provider ? data.provider : void 0;
461
+ return {
462
+ version: 1,
463
+ token,
464
+ workspaceSlug: String(data.workspaceSlug || ""),
465
+ projectSlug: String(data.projectSlug || ""),
466
+ expiresAt: Number(data.expiresAt || 0),
467
+ api: String(data.api || ""),
468
+ ...provider ? {
469
+ provider
470
+ } : {}
471
+ };
472
+ } catch {
473
+ return null;
474
+ }
475
+ }
476
+ function writeCredentials(creds) {
477
+ const file = credentialsPath();
478
+ const dir = node_path.dirname(file);
479
+ node_fs.mkdirSync(dir, {
480
+ recursive: true,
481
+ mode: 448
482
+ });
483
+ try {
484
+ node_fs.chmodSync(dir, 448);
485
+ } catch {}
486
+ node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
487
+ mode: 384
488
+ });
489
+ try {
490
+ node_fs.chmodSync(file, 384);
491
+ } catch {}
492
+ return file;
493
+ }
494
+ function clearCredentials() {
495
+ const file = credentialsPath();
496
+ try {
497
+ node_fs.unlinkSync(file);
498
+ return {
499
+ cleared: true,
500
+ path: file
501
+ };
502
+ } catch {
503
+ return {
504
+ cleared: false,
505
+ path: file
506
+ };
507
+ }
508
+ }
509
+ function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
510
+ const creds = readCredentials();
511
+ if (!creds) return null;
512
+ if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
513
+ return creds;
514
+ }
515
+ const origins = __webpack_require__("./node_modules/.pnpm/@extension.dev+urls@0.2.0/node_modules/@extension.dev/urls/dist/origins.cjs");
516
+ const paths_0 = __webpack_require__("./node_modules/.pnpm/@extension.dev+urls@0.2.0/node_modules/@extension.dev/urls/dist/paths.cjs");
517
+ origins.PROD_ORIGINS.registry;
518
+ function mcpOrigins(apiHint) {
519
+ const www = String(apiHint || process.env.EXTENSION_DEV_API_URL || "").trim() || void 0;
520
+ return (0, origins.resolveOrigins)({
521
+ www,
522
+ console: process.env.EXTENSION_DEV_CONSOLE_URL,
523
+ inspect: process.env.EXTENSION_DEV_INSPECT_URL,
524
+ preview: process.env.EXTENSION_DEV_PREVIEW_URL,
525
+ registry: process.env.EXTENSION_DEV_REGISTRY_URL,
526
+ media: process.env.EXTENSION_MEDIA_ORIGIN
527
+ }, {
528
+ hint: www
529
+ });
530
+ }
531
+ function consoleBase(apiHint) {
532
+ return mcpOrigins(apiHint).console;
533
+ }
534
+ function registryBase() {
535
+ return mcpOrigins().registry;
536
+ }
537
+ function resolveProjectRef(overrides) {
538
+ const workspace = String(overrides?.workspace || "").trim();
539
+ const project = String(overrides?.project || "").trim();
540
+ if (workspace && project) return {
541
+ workspace,
542
+ project
543
+ };
544
+ const creds = readCredentials();
545
+ const ws = workspace || String(creds?.workspaceSlug || "").trim();
546
+ const proj = project || String(creds?.projectSlug || "").trim();
547
+ if (!ws || !proj) return null;
548
+ return {
549
+ workspace: ws,
550
+ project: proj
551
+ };
552
+ }
553
+ function registryFileUrl(ref, file) {
554
+ return `${registryBase()}/${encodeURIComponent(ref.workspace)}/${encodeURIComponent(ref.project)}/_extension-dev/${file}`;
555
+ }
556
+ function consoleProjectUrl(ref, page, apiHint) {
557
+ const base = consoleBase(apiHint);
558
+ if (!ref) return base;
559
+ return `${base}${(0, paths_0.consoleProjectPath)(ref, page)}`;
560
+ }
561
+ async function fetchRegistryJson(url, fetchImpl = fetch) {
562
+ let res;
563
+ try {
564
+ res = await fetchImpl(url);
565
+ } catch (err) {
566
+ return {
567
+ ok: false,
568
+ message: `Could not reach ${url}: ${err?.message || err}`
569
+ };
570
+ }
571
+ if (!res.ok) return {
572
+ ok: false,
573
+ status: res.status,
574
+ message: `${url} returned ${res.status}`
575
+ };
576
+ try {
577
+ const text = await res.text();
578
+ return {
579
+ ok: true,
580
+ json: JSON.parse(text)
581
+ };
582
+ } catch {
583
+ return {
584
+ ok: false,
585
+ message: `${url} did not return valid JSON`
586
+ };
587
+ }
588
+ }
589
+ function parseChannels(json) {
590
+ if (!json || "object" != typeof json || Array.isArray(json)) return [];
591
+ const out = [];
592
+ for (const [channel, raw] of Object.entries(json)){
593
+ if (!raw || "object" != typeof raw) continue;
594
+ const row = raw;
595
+ const description = "string" == typeof row.description ? row.description : void 0;
596
+ const promotedAtField = "string" == typeof row.promotedAt && row.promotedAt ? row.promotedAt : void 0;
597
+ const fromDescription = description?.match(/\bon (\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)/)?.[1];
598
+ const entry = {
599
+ channel,
600
+ sha: String(row.sha ?? "")
601
+ };
602
+ if (row.buildId) entry.buildId = String(row.buildId);
603
+ if (row.version) entry.version = String(row.version);
604
+ const promotedAt = promotedAtField || fromDescription;
605
+ if (promotedAt) entry.promotedAt = promotedAt;
606
+ if (description) entry.description = description;
607
+ out.push(entry);
608
+ }
609
+ return out;
610
+ }
611
+ function parseBuildIndex(json) {
612
+ const items = json?.items;
613
+ if (!Array.isArray(items)) return [];
614
+ const out = [];
615
+ for (const raw of items){
616
+ if (!raw || "object" != typeof raw) continue;
617
+ const row = raw;
618
+ const sha = String(row.shortSha ?? row.sha ?? row.id ?? row.buildId ?? "").trim();
619
+ if (!sha) continue;
620
+ const entry = {
621
+ sha
622
+ };
623
+ if (row.commit) entry.commit = String(row.commit);
624
+ if (row.channel) entry.channel = String(row.channel);
625
+ if (row.buildEnv) entry.buildEnv = String(row.buildEnv);
626
+ if (row.status) entry.status = String(row.status);
627
+ if (row.version) entry.version = String(row.version);
628
+ if ("string" == typeof row.message) entry.message = row.message.split("\n", 1)[0];
629
+ if (row.timestamp) entry.timestamp = String(row.timestamp);
630
+ if (Array.isArray(row.browsers)) entry.browsers = row.browsers.map((b)=>String(b)).filter(Boolean);
631
+ out.push(entry);
632
+ }
633
+ return out;
634
+ }
226
635
  function scaffoldEnginePin(projectPath) {
227
636
  try {
228
637
  const pkg = JSON.parse(node_fs.readFileSync(node_path.join(projectPath, "package.json"), "utf8"));
@@ -359,6 +768,10 @@ async function create_handler(args) {
359
768
  const engineWarning = pin && "latest" !== pin && !pinMatches ? `The scaffold pins "extension": "${scaffoldPin ?? "unknown"}"; the project-local engine wins over EXTENSION_MCP_CLI_VERSION=${pin}. Run \`(cd ${result.projectPath} && ${addDev(`extension@${pin}`)})\` to match the pinned engine.` : void 0;
360
769
  const resolvedParent = args.parentDir ? node_path.resolve(args.parentDir) : process.cwd();
361
770
  const gitInit = node_fs.existsSync(node_path.join(result.projectPath, ".git"));
771
+ const wwwOrigin = mcpOrigins().www;
772
+ const deployUrl = `${wwwOrigin}${(0, paths_0.wwwNewPath)({
773
+ template: result.template
774
+ })}`;
362
775
  return JSON.stringify({
363
776
  resolvedPath: result.projectPath,
364
777
  projectPath: result.projectPath,
@@ -366,20 +779,27 @@ async function create_handler(args) {
366
779
  template: result.template,
367
780
  depsInstalled: result.depsInstalled,
368
781
  packageManager: result.depsInstalled ? packageManager : null,
782
+ deployUrl,
369
783
  defaultsApplied: {
370
784
  parentDir: args.parentDir ? `${resolvedParent} (explicit)` : `${resolvedParent} (default: the MCP server process cwd, not yours; pass parentDir to choose)`,
785
+ ...void 0 === args.template ? {
786
+ template: "typescript (default; run extension_list_templates to pick another, e.g. javascript for plain JS)"
787
+ } : {},
371
788
  packageManager: `${packageManager} (auto-detected by the scaffolder, not asked)`,
372
789
  browser: "chrome (default: extension_dev and extension_build target chrome unless you pass browser)",
373
790
  gitInit
374
791
  },
375
792
  duration: Date.now() - start,
376
- nextSteps: result.depsInstalled ? [
377
- `cd ${result.projectPath}`,
378
- runDev
379
- ] : [
380
- `cd ${result.projectPath}`,
381
- `${packageManager} install`,
382
- runDev
793
+ nextSteps: [
794
+ ...result.depsInstalled ? [
795
+ `cd ${result.projectPath}`,
796
+ runDev
797
+ ] : [
798
+ `cd ${result.projectPath}`,
799
+ `${packageManager} install`,
800
+ runDev
801
+ ],
802
+ `To ship: extension_create scaffolds and runs locally, it does not host. Open ${deployUrl} to deploy this template to the web.`
383
803
  ],
384
804
  ...engineWarning ? {
385
805
  engineWarning
@@ -458,11 +878,22 @@ async function templateMetaUrls() {
458
878
  urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/templates-meta.json`);
459
879
  return urls;
460
880
  }
881
+ function stripTemplatePathPrefix(slug, relativePath) {
882
+ for (const dir of [
883
+ "public",
884
+ "examples"
885
+ ]){
886
+ const prefix = `${dir}/${slug}/`;
887
+ if (relativePath.startsWith(prefix)) return relativePath.slice(prefix.length);
888
+ }
889
+ return relativePath;
890
+ }
461
891
  async function templateFileUrls(slug, relativePath) {
892
+ const relative = stripTemplatePathPrefix(slug, relativePath);
462
893
  const urls = [];
463
894
  const release = await resolveRelease();
464
- if (release) urls.push(`${release.filesBaseUrl}/${slug}/${relativePath}`);
465
- urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${relativePath}`);
895
+ if (release) urls.push(`${release.filesBaseUrl}/${slug}/${relative}`);
896
+ urls.push(`${rawBaseForCommit(PINNED_COMMIT)}/examples/${slug}/${relative}`);
466
897
  return urls;
467
898
  }
468
899
  const CACHE_DIR = node_path.join(node_os.homedir(), ".cache", "extension-js");
@@ -606,7 +1037,8 @@ async function list_templates_handler(args) {
606
1037
  const results = templates.map((t)=>({
607
1038
  slug: t.slug,
608
1039
  description: t.description,
609
- uiFramework: t.uiFramework || "vanilla",
1040
+ uiFramework: t.uiFramework,
1041
+ frameworkLabel: t.uiFramework || "vanilla",
610
1042
  surfaces: t.surfaces,
611
1043
  tags: t.tags,
612
1044
  difficulty: t.difficulty,
@@ -2219,7 +2651,7 @@ async function get_template_source_handler(args) {
2219
2651
  };
2220
2652
  if (!args.files?.length) return JSON.stringify({
2221
2653
  ...meta,
2222
- files: template.files,
2654
+ files: template.files.map((f)=>stripTemplatePathPrefix(template.slug, f)),
2223
2655
  hint: "Pass specific file paths in the files parameter to read their contents."
2224
2656
  });
2225
2657
  const fileContents = {};
@@ -5773,250 +6205,7 @@ async function dom_inspect_handler(args) {
5773
6205
  return raw;
5774
6206
  }
5775
6207
  }
5776
- function credentialsPath() {
5777
- if ("win32" === process.platform) {
5778
- const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
5779
- return node_path.join(base, "extension-dev", "auth.json");
5780
- }
5781
- const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
5782
- const base = xdg || node_path.join(node_os.homedir(), ".config");
5783
- return node_path.join(base, "extension-dev", "auth.json");
5784
- }
5785
- function readCredentials() {
5786
- try {
5787
- const raw = node_fs.readFileSync(credentialsPath(), "utf8");
5788
- const data = JSON.parse(raw);
5789
- if (!data || "object" != typeof data) return null;
5790
- if (1 !== data.version) return null;
5791
- const token = String(data.token || "").trim();
5792
- if (!token) return null;
5793
- const provider = "extensiondev" === data.provider || "github" === data.provider ? data.provider : void 0;
5794
- return {
5795
- version: 1,
5796
- token,
5797
- workspaceSlug: String(data.workspaceSlug || ""),
5798
- projectSlug: String(data.projectSlug || ""),
5799
- expiresAt: Number(data.expiresAt || 0),
5800
- api: String(data.api || ""),
5801
- ...provider ? {
5802
- provider
5803
- } : {}
5804
- };
5805
- } catch {
5806
- return null;
5807
- }
5808
- }
5809
- function writeCredentials(creds) {
5810
- const file = credentialsPath();
5811
- const dir = node_path.dirname(file);
5812
- node_fs.mkdirSync(dir, {
5813
- recursive: true,
5814
- mode: 448
5815
- });
5816
- try {
5817
- node_fs.chmodSync(dir, 448);
5818
- } catch {}
5819
- node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
5820
- mode: 384
5821
- });
5822
- try {
5823
- node_fs.chmodSync(file, 384);
5824
- } catch {}
5825
- return file;
5826
- }
5827
- function clearCredentials() {
5828
- const file = credentialsPath();
5829
- try {
5830
- node_fs.unlinkSync(file);
5831
- return {
5832
- cleared: true,
5833
- path: file
5834
- };
5835
- } catch {
5836
- return {
5837
- cleared: false,
5838
- path: file
5839
- };
5840
- }
5841
- }
5842
- function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
5843
- const creds = readCredentials();
5844
- if (!creds) return null;
5845
- if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
5846
- return creds;
5847
- }
5848
- const PROD_ORIGINS = {
5849
- www: "https://www.extension.dev",
5850
- console: "https://console.extension.dev",
5851
- inspect: "https://inspect.extension.dev",
5852
- templates: "https://templates.extension.dev",
5853
- intelligence: "https://intelligence.extension.dev",
5854
- registry: "https://registry.extension.land",
5855
- media: "https://media.extension.land"
5856
- };
5857
- const DEV_LOCALHOST_ORIGINS = {
5858
- www: "http://localhost:3100",
5859
- console: "http://console.extension.localhost",
5860
- inspect: "http://inspect.extension.localhost",
5861
- templates: "http://templates.extension.localhost",
5862
- intelligence: "http://intelligence.extension.localhost",
5863
- registry: "https://registry.extension.land",
5864
- media: "https://media.extension.land"
5865
- };
5866
- function strip(value) {
5867
- return String(value ?? "").trim().replace(/\/+$/, "");
5868
- }
5869
- function isLocalOrigin(url) {
5870
- const raw = strip(url);
5871
- if (!raw) return false;
5872
- let host;
5873
- try {
5874
- host = new URL(raw).hostname;
5875
- } catch {
5876
- return false;
5877
- }
5878
- return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
5879
- }
5880
- function resolveOrigins(overrides = {}, opts = {}) {
5881
- const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
5882
- const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
5883
- const pick = (key)=>strip(overrides[key]) || base[key];
5884
- return {
5885
- www: pick("www"),
5886
- console: pick("console"),
5887
- inspect: pick("inspect"),
5888
- templates: pick("templates"),
5889
- intelligence: pick("intelligence"),
5890
- registry: pick("registry"),
5891
- media: pick("media")
5892
- };
5893
- }
5894
- const seg = (value)=>encodeURIComponent(String(value));
5895
- function urls_paths_join(base, sub) {
5896
- if (!sub) return base;
5897
- return `${base}/${sub.replace(/^\/+/, "")}`;
5898
- }
5899
- function consoleProjectPath(ref, page = "") {
5900
- return urls_paths_join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
5901
- }
5902
- PROD_ORIGINS.registry;
5903
- function mcpOrigins(apiHint) {
5904
- const www = String(apiHint || process.env.EXTENSION_DEV_API_URL || "").trim() || void 0;
5905
- return resolveOrigins({
5906
- www,
5907
- console: process.env.EXTENSION_DEV_CONSOLE_URL,
5908
- inspect: process.env.EXTENSION_DEV_INSPECT_URL,
5909
- registry: process.env.EXTENSION_DEV_REGISTRY_URL,
5910
- media: process.env.EXTENSION_MEDIA_ORIGIN
5911
- }, {
5912
- hint: www
5913
- });
5914
- }
5915
- function consoleBase(apiHint) {
5916
- return mcpOrigins(apiHint).console;
5917
- }
5918
- function registryBase() {
5919
- return mcpOrigins().registry;
5920
- }
5921
- function resolveProjectRef(overrides) {
5922
- const workspace = String(overrides?.workspace || "").trim();
5923
- const project = String(overrides?.project || "").trim();
5924
- if (workspace && project) return {
5925
- workspace,
5926
- project
5927
- };
5928
- const creds = readCredentials();
5929
- const ws = workspace || String(creds?.workspaceSlug || "").trim();
5930
- const proj = project || String(creds?.projectSlug || "").trim();
5931
- if (!ws || !proj) return null;
5932
- return {
5933
- workspace: ws,
5934
- project: proj
5935
- };
5936
- }
5937
- function registryFileUrl(ref, file) {
5938
- return `${registryBase()}/${encodeURIComponent(ref.workspace)}/${encodeURIComponent(ref.project)}/_extension-dev/${file}`;
5939
- }
5940
- function consoleProjectUrl(ref, page, apiHint) {
5941
- const base = consoleBase(apiHint);
5942
- if (!ref) return base;
5943
- return `${base}${consoleProjectPath(ref, page)}`;
5944
- }
5945
- async function fetchRegistryJson(url, fetchImpl = fetch) {
5946
- let res;
5947
- try {
5948
- res = await fetchImpl(url);
5949
- } catch (err) {
5950
- return {
5951
- ok: false,
5952
- message: `Could not reach ${url}: ${err?.message || err}`
5953
- };
5954
- }
5955
- if (!res.ok) return {
5956
- ok: false,
5957
- status: res.status,
5958
- message: `${url} returned ${res.status}`
5959
- };
5960
- try {
5961
- const text = await res.text();
5962
- return {
5963
- ok: true,
5964
- json: JSON.parse(text)
5965
- };
5966
- } catch {
5967
- return {
5968
- ok: false,
5969
- message: `${url} did not return valid JSON`
5970
- };
5971
- }
5972
- }
5973
- function parseChannels(json) {
5974
- if (!json || "object" != typeof json || Array.isArray(json)) return [];
5975
- const out = [];
5976
- for (const [channel, raw] of Object.entries(json)){
5977
- if (!raw || "object" != typeof raw) continue;
5978
- const row = raw;
5979
- const description = "string" == typeof row.description ? row.description : void 0;
5980
- const promotedAtField = "string" == typeof row.promotedAt && row.promotedAt ? row.promotedAt : void 0;
5981
- const fromDescription = description?.match(/\bon (\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)/)?.[1];
5982
- const entry = {
5983
- channel,
5984
- sha: String(row.sha ?? "")
5985
- };
5986
- if (row.buildId) entry.buildId = String(row.buildId);
5987
- if (row.version) entry.version = String(row.version);
5988
- const promotedAt = promotedAtField || fromDescription;
5989
- if (promotedAt) entry.promotedAt = promotedAt;
5990
- if (description) entry.description = description;
5991
- out.push(entry);
5992
- }
5993
- return out;
5994
- }
5995
- function parseBuildIndex(json) {
5996
- const items = json?.items;
5997
- if (!Array.isArray(items)) return [];
5998
- const out = [];
5999
- for (const raw of items){
6000
- if (!raw || "object" != typeof raw) continue;
6001
- const row = raw;
6002
- const sha = String(row.shortSha ?? row.sha ?? row.id ?? row.buildId ?? "").trim();
6003
- if (!sha) continue;
6004
- const entry = {
6005
- sha
6006
- };
6007
- if (row.commit) entry.commit = String(row.commit);
6008
- if (row.channel) entry.channel = String(row.channel);
6009
- if (row.buildEnv) entry.buildEnv = String(row.buildEnv);
6010
- if (row.status) entry.status = String(row.status);
6011
- if (row.version) entry.version = String(row.version);
6012
- if ("string" == typeof row.message) entry.message = row.message.split("\n", 1)[0];
6013
- if (row.timestamp) entry.timestamp = String(row.timestamp);
6014
- if (Array.isArray(row.browsers)) entry.browsers = row.browsers.map((b)=>String(b)).filter(Boolean);
6015
- out.push(entry);
6016
- }
6017
- return out;
6018
- }
6019
- const DEFAULT_API = PROD_ORIGINS.www;
6208
+ const DEFAULT_API = origins.PROD_ORIGINS.www;
6020
6209
  function tokenTtlNote(workspaceSlug, projectSlug) {
6021
6210
  const tokensUrl = workspaceSlug && projectSlug ? consoleProjectUrl({
6022
6211
  workspace: workspaceSlug,
@@ -7263,6 +7452,7 @@ async function wait_handler(args) {
7263
7452
  hint: "Still building, call extension_wait again to keep waiting (it resumes polling the same contract). If it never readies, check the dev process with extension_doctor."
7264
7453
  });
7265
7454
  }
7455
+ const EXAMPLES_TREE_BASE = `https://github.com/extension-js/examples/tree/${PINNED_COMMIT}/examples`;
7266
7456
  const add_feature_schema = {
7267
7457
  name: "extension_add_feature",
7268
7458
  description: "Plan a new feature surface for an existing extension. Returns step-by-step instructions, the manifest additions to make, and reference templates from the extension.dev catalog. Does not modify files; apply the returned plan yourself.",
@@ -7506,7 +7696,7 @@ async function add_feature_handler(args) {
7506
7696
  framework,
7507
7697
  referenceTemplate: {
7508
7698
  slug: templateSlug,
7509
- repositoryUrl: `https://github.com/extension-js/examples/tree/main/examples/${templateSlug}`,
7699
+ repositoryUrl: `${EXAMPLES_TREE_BASE}/${templateSlug}`,
7510
7700
  referenceFiles: referenceFiles.filter((f)=>f.includes(featureDir) || f.includes("manifest"))
7511
7701
  },
7512
7702
  manifestUpdates,
@@ -7520,7 +7710,7 @@ async function add_feature_handler(args) {
7520
7710
  "2. Create the following files in your project:",
7521
7711
  ...filesToCreate.map((f)=>` - ${f.path} (${f.hint})`),
7522
7712
  "sidebar" === args.feature ? "3. Add background.ts to handle sidebar open: chromium uses chrome.sidePanel.setPanelBehavior, firefox uses browser.sidebarAction.open()" : "",
7523
- `4. Reference template source: https://github.com/extension-js/examples/tree/main/examples/${templateSlug}/src`,
7713
+ `4. Reference template source: ${EXAMPLES_TREE_BASE}/${templateSlug}/src`,
7524
7714
  "5. Run npm run dev to test"
7525
7715
  ].filter(Boolean),
7526
7716
  hint: conflicts.length ? `Warning: ${conflicts.length} file(s) already exist and would be overwritten.` : "No conflicts detected. Safe to create all files."
@@ -1,3 +1,4 @@
1
+ var __webpack_modules__ = {};
1
2
  var __webpack_module_cache__ = {};
2
3
  function __webpack_require__(moduleId) {
3
4
  var cachedModule = __webpack_module_cache__[moduleId];
@@ -8,6 +9,7 @@ function __webpack_require__(moduleId) {
8
9
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
9
10
  return module.exports;
10
11
  }
12
+ __webpack_require__.m = __webpack_modules__;
11
13
  (()=>{
12
14
  __webpack_require__.add = function(modules) {
13
15
  Object.assign(__webpack_require__.m, modules);
@@ -1,4 +1,4 @@
1
- import { type Origins } from "./urls-origins";
1
+ import { type Origins } from "@extension.dev/urls/origins";
2
2
  export declare const REGISTRY_BASE_DEFAULT: string;
3
3
  /**
4
4
  * Resolve the fleet origins from the MCP's env vars, once per call. Console,
@@ -7,7 +7,7 @@ export declare const REGISTRY_BASE_DEFAULT: string;
7
7
  * MCP hands back follows to the dev host (`console.extension.localhost`) instead
8
8
  * of silently pointing at prod. Explicit per-host env vars still win. Falls back
9
9
  * to production when nothing is set. The dev-derivation lives in
10
- * `@extensiondev/urls` so it stays identical to what the apps resolve.
10
+ * `@extension.dev/urls` so it stays identical to what the apps resolve.
11
11
  */
12
12
  export declare function mcpOrigins(apiHint?: string): Origins;
13
13
  /** Base origin of the console dashboard, dev-aware (no trailing slash). */
@@ -1,2 +1,4 @@
1
+ export declare const PINNED_COMMIT = "2d2ed9668cca002148d9eecd953a08b54d0bad9d";
1
2
  export declare function templateMetaUrls(): Promise<string[]>;
3
+ export declare function stripTemplatePathPrefix(slug: string, relativePath: string): string;
2
4
  export declare function templateFileUrls(slug: string, relativePath: string): Promise<string[]>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "6.0.0",
4
+ "version": "6.2.0",
5
5
  "description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 33 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",
@@ -97,6 +97,7 @@
97
97
  "ws": "^8.20.0"
98
98
  },
99
99
  "devDependencies": {
100
+ "@extension.dev/urls": "^0.2.0",
100
101
  "@rslib/core": "^0.19.4",
101
102
  "@types/cross-spawn": "^6.0.6",
102
103
  "@types/node": "^25.2.0",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://extension.dev",
10
- "version": "6.0.0",
10
+ "version": "6.2.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": "6.0.0",
16
+ "version": "6.2.0",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"
@@ -1,34 +0,0 @@
1
- /** The set of app/service origins the fleet links between. */
2
- export interface Origins {
3
- www: string;
4
- console: string;
5
- inspect: string;
6
- templates: string;
7
- intelligence: string;
8
- /** Public release-state JSON host (registry.extension.land). */
9
- registry: string;
10
- /** Content-addressed template corpus host (media.extension.land). */
11
- media: string;
12
- }
13
- /** Production origins. The prod fallback for every resolver in the fleet. */
14
- export declare const PROD_ORIGINS: Origins;
15
- export declare const DEV_LOCALHOST_ORIGINS: Origins;
16
- /**
17
- * True for `localhost`, `127.0.0.1`, `[::1]`, or any `*.extension.localhost`
18
- * host -- the shapes the dev proxy serves. Anything unparseable is treated as
19
- * not-local so an odd value falls back to prod rather than leaking a dev host.
20
- */
21
- export declare function isLocalOrigin(url: string | undefined | null): boolean;
22
- /**
23
- * Resolve the full origin set from a caller's overrides.
24
- *
25
- * Precedence per origin: explicit override -> derived base (dev vs prod) ->
26
- * prod. The base is chosen by whether the environment looks local: if any of
27
- * `www`/`console`/`hint` points at a local host, unset origins derive from the
28
- * Caddy dev map instead of prod. This is what lets an operator set only
29
- * `EXTENSION_DEV_API_URL=http://localhost:3100` and still get a console link at
30
- * `console.extension.localhost` rather than one that silently points at prod.
31
- */
32
- export declare function resolveOrigins(overrides?: Partial<Origins>, opts?: {
33
- hint?: string;
34
- }): Origins;
@@ -1,56 +0,0 @@
1
- /** A project is addressed by its workspace slug + project slug across the fleet. */
2
- export interface ProjectRef {
3
- workspace: string;
4
- project: string;
5
- }
6
- /** `/:workspace` (or a sub-page like `settings` / `settings/:section`). */
7
- export declare function consoleWorkspacePath(workspace: string, page?: string): string;
8
- /**
9
- * `/:workspace/:project/<page>`. Pass a page tail from `ConsoleProjectPage`
10
- * (e.g. `ConsoleProjectPage.builds`) rather than hand-typing it, so a route
11
- * rename is a one-line change here instead of a scattered string edit.
12
- */
13
- export declare function consoleProjectPath(ref: ProjectRef, page?: string): string;
14
- /**
15
- * Named project-page tails. Values are the exact segments under
16
- * `/:username/:reponame/` in console's route table; deep pages take ids.
17
- */
18
- export declare const ConsoleProjectPage: {
19
- readonly overview: "";
20
- readonly onboard: "onboard";
21
- readonly activity: "activity";
22
- readonly builds: "builds";
23
- readonly build: (buildId: string, browser?: string) => string;
24
- readonly releases: "releases";
25
- readonly releasesNew: "releases/new";
26
- readonly release: (releaseId: string) => string;
27
- readonly stores: "stores";
28
- readonly storesNew: "stores/new";
29
- readonly store: (store: string) => string;
30
- readonly storeSubmissions: (store: string) => string;
31
- readonly storeSubmissionNew: (store: string) => string;
32
- readonly storeSubmission: (store: string, submissionId: string) => string;
33
- readonly projectSettings: "project-settings";
34
- readonly projectSettingsSection: (section: string) => string;
35
- /** Where CLI/MCP tokens are minted and revoked. */
36
- readonly accessTokens: "settings/access-tokens";
37
- };
38
- export type QueryValue = string | number | boolean | null | undefined;
39
- /**
40
- * `/new?...` -- project creation. The public deep-link contract: external
41
- * READMEs use `extension.dev/new?template=<slug>`, which www normalizes to
42
- * `/import` preserving every param. Do not change the `template` key.
43
- */
44
- export declare function wwwNewPath(query?: Record<string, QueryValue>): string;
45
- /** `/import?...` -- the ported import/deploy flow templates' Deploy buttons target. */
46
- export declare function wwwImportPath(query?: Record<string, QueryValue>): string;
47
- /** `/device` -- CLI/MCP device-code consent. Lives on www, never console. */
48
- export declare function wwwDevicePath(): string;
49
- /** `/templates` and `/templates/:slug` -- externally linkable gallery entry on www. */
50
- export declare function wwwTemplatesPath(slug?: string): string;
51
- export type TemplateTab = "preview" | "instructions" | "source";
52
- /** `/:slug` for the default `preview` tab, else `/:slug/<tab>`. */
53
- export declare function templateTabPath(slug: string, tab?: TemplateTab): string;
54
- export type InspectTab = "preview" | "details" | "source" | "trace";
55
- /** `/` for the default `preview` tab, else `/details` / `/source` / `/trace`. */
56
- export declare function inspectTabPath(tab?: InspectTab): string;