@extension.dev/mcp 6.1.0 → 6.3.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.
package/dist/module.js CHANGED
@@ -16,6 +16,394 @@ 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.3.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
+ userland: "https://extension.dev",
59
+ registry: "https://registry.extension.land",
60
+ media: "https://media.extension.land"
61
+ };
62
+ const DEV_LOCALHOST_ORIGINS = {
63
+ www: "http://localhost:3100",
64
+ console: "http://console.extension.localhost",
65
+ inspect: "http://inspect.extension.localhost",
66
+ preview: "http://preview.extension.localhost",
67
+ templates: "http://templates.extension.localhost",
68
+ intelligence: "http://intelligence.extension.localhost",
69
+ userland: "http://userland.extension.localhost",
70
+ registry: "https://registry.extension.land",
71
+ media: "https://media.extension.land"
72
+ };
73
+ function strip(value) {
74
+ return String(value ?? "").trim().replace(/\/+$/, "");
75
+ }
76
+ function isLocalOrigin(url) {
77
+ const raw = strip(url);
78
+ if (!raw) return false;
79
+ let host;
80
+ try {
81
+ host = new URL(raw).hostname;
82
+ } catch {
83
+ return false;
84
+ }
85
+ return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
86
+ }
87
+ function resolveOrigins(overrides = {}, opts = {}) {
88
+ const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
89
+ const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
90
+ const pick = (key)=>strip(overrides[key]) || base[key];
91
+ return {
92
+ www: pick("www"),
93
+ console: pick("console"),
94
+ inspect: pick("inspect"),
95
+ preview: pick("preview"),
96
+ templates: pick("templates"),
97
+ intelligence: pick("intelligence"),
98
+ userland: pick("userland"),
99
+ registry: pick("registry"),
100
+ media: pick("media")
101
+ };
102
+ }
103
+ exports.DEV_LOCALHOST_ORIGINS = __nested_rspack_exports__.DEV_LOCALHOST_ORIGINS;
104
+ exports.PROD_ORIGINS = __nested_rspack_exports__.PROD_ORIGINS;
105
+ exports.isLocalOrigin = __nested_rspack_exports__.isLocalOrigin;
106
+ exports.resolveOrigins = __nested_rspack_exports__.resolveOrigins;
107
+ for(var __webpack_i__ in __nested_rspack_exports__)if (-1 === [
108
+ "DEV_LOCALHOST_ORIGINS",
109
+ "PROD_ORIGINS",
110
+ "isLocalOrigin",
111
+ "resolveOrigins"
112
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __nested_rspack_exports__[__webpack_i__];
113
+ Object.defineProperty(exports, "__esModule", {
114
+ value: true
115
+ });
116
+ },
117
+ "./node_modules/.pnpm/@extension.dev+urls@0.3.0/node_modules/@extension.dev/urls/dist/paths.cjs" (__unused_rspack_module, exports) {
118
+ var __nested_rspack_require_18_37__ = {};
119
+ (()=>{
120
+ __nested_rspack_require_18_37__.d = (exports1, definition)=>{
121
+ 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, {
122
+ enumerable: true,
123
+ get: definition[key]
124
+ });
125
+ };
126
+ })();
127
+ (()=>{
128
+ __nested_rspack_require_18_37__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
129
+ })();
130
+ (()=>{
131
+ __nested_rspack_require_18_37__.r = (exports1)=>{
132
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
133
+ value: 'Module'
134
+ });
135
+ Object.defineProperty(exports1, '__esModule', {
136
+ value: true
137
+ });
138
+ };
139
+ })();
140
+ var __nested_rspack_exports__ = {};
141
+ __nested_rspack_require_18_37__.r(__nested_rspack_exports__);
142
+ __nested_rspack_require_18_37__.d(__nested_rspack_exports__, {
143
+ ConsoleProjectPage: ()=>ConsoleProjectPage,
144
+ consoleProjectPath: ()=>consoleProjectPath,
145
+ consoleWorkspacePath: ()=>consoleWorkspacePath,
146
+ inspectTabPath: ()=>inspectTabPath,
147
+ previewSharePath: ()=>previewSharePath,
148
+ templateTabPath: ()=>templateTabPath,
149
+ withQuery: ()=>withQuery,
150
+ wwwDevicePath: ()=>wwwDevicePath,
151
+ wwwImportPath: ()=>wwwImportPath,
152
+ wwwNewPath: ()=>wwwNewPath,
153
+ wwwTemplatesPath: ()=>wwwTemplatesPath
154
+ });
155
+ const seg = (value)=>encodeURIComponent(String(value));
156
+ function join(base, sub) {
157
+ if (!sub) return base;
158
+ return `${base}/${sub.replace(/^\/+/, "")}`;
159
+ }
160
+ function consoleWorkspacePath(workspace, page = "") {
161
+ return join(`/${seg(workspace)}`, page);
162
+ }
163
+ function consoleProjectPath(ref, page = "") {
164
+ return join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
165
+ }
166
+ const ConsoleProjectPage = {
167
+ overview: "",
168
+ onboard: "onboard",
169
+ activity: "activity",
170
+ builds: "builds",
171
+ build: (buildId, browser)=>browser ? `builds/${seg(buildId)}/${seg(browser)}` : `builds/${seg(buildId)}`,
172
+ releases: "releases",
173
+ releasesNew: "releases/new",
174
+ release: (releaseId)=>`releases/${seg(releaseId)}`,
175
+ stores: "stores",
176
+ storesNew: "stores/new",
177
+ store: (store)=>`stores/${seg(store)}`,
178
+ storeSubmissions: (store)=>`stores/${seg(store)}/submissions`,
179
+ storeSubmissionNew: (store)=>`stores/${seg(store)}/submissions/new`,
180
+ storeSubmission: (store, submissionId)=>`stores/${seg(store)}/submissions/${seg(submissionId)}`,
181
+ projectSettings: "project-settings",
182
+ projectSettingsSection: (section)=>`project-settings/${seg(section)}`,
183
+ accessTokens: "settings/access-tokens"
184
+ };
185
+ function withQuery(path, query) {
186
+ if (!query) return path;
187
+ const params = new URLSearchParams();
188
+ for (const [key, value] of Object.entries(query))if (null != value && "" !== value) params.set(key, String(value));
189
+ const qs = params.toString();
190
+ return qs ? `${path}?${qs}` : path;
191
+ }
192
+ function wwwNewPath(query) {
193
+ return withQuery("/new", query);
194
+ }
195
+ function wwwImportPath(query) {
196
+ return withQuery("/import", query);
197
+ }
198
+ function wwwDevicePath() {
199
+ return "/device";
200
+ }
201
+ function wwwTemplatesPath(slug) {
202
+ return slug ? `/templates/${seg(slug)}` : "/templates";
203
+ }
204
+ function templateTabPath(slug, tab = "preview") {
205
+ return "preview" === tab ? `/${seg(slug)}` : `/${seg(slug)}/${tab}`;
206
+ }
207
+ function inspectTabPath(tab = "preview") {
208
+ return "preview" === tab ? "/" : `/${tab}`;
209
+ }
210
+ function previewSharePath(previewId) {
211
+ return `/?preview=${seg(previewId)}`;
212
+ }
213
+ exports.ConsoleProjectPage = __nested_rspack_exports__.ConsoleProjectPage;
214
+ exports.consoleProjectPath = __nested_rspack_exports__.consoleProjectPath;
215
+ exports.consoleWorkspacePath = __nested_rspack_exports__.consoleWorkspacePath;
216
+ exports.inspectTabPath = __nested_rspack_exports__.inspectTabPath;
217
+ exports.previewSharePath = __nested_rspack_exports__.previewSharePath;
218
+ exports.templateTabPath = __nested_rspack_exports__.templateTabPath;
219
+ exports.withQuery = __nested_rspack_exports__.withQuery;
220
+ exports.wwwDevicePath = __nested_rspack_exports__.wwwDevicePath;
221
+ exports.wwwImportPath = __nested_rspack_exports__.wwwImportPath;
222
+ exports.wwwNewPath = __nested_rspack_exports__.wwwNewPath;
223
+ exports.wwwTemplatesPath = __nested_rspack_exports__.wwwTemplatesPath;
224
+ for(var __webpack_i__ in __nested_rspack_exports__)if (-1 === [
225
+ "ConsoleProjectPage",
226
+ "consoleProjectPath",
227
+ "consoleWorkspacePath",
228
+ "inspectTabPath",
229
+ "previewSharePath",
230
+ "templateTabPath",
231
+ "withQuery",
232
+ "wwwDevicePath",
233
+ "wwwImportPath",
234
+ "wwwNewPath",
235
+ "wwwTemplatesPath"
236
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __nested_rspack_exports__[__webpack_i__];
237
+ Object.defineProperty(exports, "__esModule", {
238
+ value: true
239
+ });
240
+ },
241
+ "./node_modules/.pnpm/@extension.dev+urls@0.3.0/node_modules/@extension.dev/urls/dist/userland.cjs" (__unused_rspack_module, exports) {
242
+ var __nested_rspack_require_18_37__ = {};
243
+ (()=>{
244
+ __nested_rspack_require_18_37__.d = (exports1, definition)=>{
245
+ 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, {
246
+ enumerable: true,
247
+ get: definition[key]
248
+ });
249
+ };
250
+ })();
251
+ (()=>{
252
+ __nested_rspack_require_18_37__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
253
+ })();
254
+ (()=>{
255
+ __nested_rspack_require_18_37__.r = (exports1)=>{
256
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
257
+ value: 'Module'
258
+ });
259
+ Object.defineProperty(exports1, '__esModule', {
260
+ value: true
261
+ });
262
+ };
263
+ })();
264
+ var __nested_rspack_exports__ = {};
265
+ __nested_rspack_require_18_37__.r(__nested_rspack_exports__);
266
+ __nested_rspack_require_18_37__.d(__nested_rspack_exports__, {
267
+ userlandBuildUrl: ()=>userlandBuildUrl,
268
+ USERLAND_BUILD_TABS: ()=>USERLAND_BUILD_TABS,
269
+ userlandUrl: ()=>userlandUrl,
270
+ UserlandProjectPage: ()=>UserlandProjectPage,
271
+ UserlandDialog: ()=>UserlandDialog,
272
+ userlandChannelUrl: ()=>userlandChannelUrl,
273
+ userlandProjectPath: ()=>userlandProjectPath,
274
+ isUserlandBuildTab: ()=>isUserlandBuildTab,
275
+ userlandDialogUrl: ()=>userlandDialogUrl,
276
+ userlandHostMode: ()=>userlandHostMode,
277
+ userlandOrigin: ()=>userlandOrigin
278
+ });
279
+ const PROD_ORIGINS = {
280
+ www: "https://www.extension.dev",
281
+ console: "https://console.extension.dev",
282
+ inspect: "https://inspect.extension.dev",
283
+ preview: "https://preview.extension.dev",
284
+ templates: "https://templates.extension.dev",
285
+ intelligence: "https://intelligence.extension.dev",
286
+ userland: "https://extension.dev",
287
+ registry: "https://registry.extension.land",
288
+ media: "https://media.extension.land"
289
+ };
290
+ function strip(value) {
291
+ return String(value ?? "").trim().replace(/\/+$/, "");
292
+ }
293
+ function isLocalOrigin(url) {
294
+ const raw = strip(url);
295
+ if (!raw) return false;
296
+ let host;
297
+ try {
298
+ host = new URL(raw).hostname;
299
+ } catch {
300
+ return false;
301
+ }
302
+ return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
303
+ }
304
+ function withQuery(path, query) {
305
+ if (!query) return path;
306
+ const params = new URLSearchParams();
307
+ for (const [key, value] of Object.entries(query))if (null != value && "" !== value) params.set(key, String(value));
308
+ const qs = params.toString();
309
+ return qs ? `${path}?${qs}` : path;
310
+ }
311
+ const userland_seg = (value)=>encodeURIComponent(String(value));
312
+ const USERLAND_BUILD_TABS = [
313
+ "preview",
314
+ "whats-new",
315
+ "usage"
316
+ ];
317
+ function isUserlandBuildTab(value) {
318
+ return USERLAND_BUILD_TABS.includes(value);
319
+ }
320
+ const UserlandDialog = {
321
+ run: "run",
322
+ integrity: "integrity"
323
+ };
324
+ const DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
325
+ function userland_strip(value) {
326
+ return String(value ?? "").trim().replace(/\/+$/, "");
327
+ }
328
+ function userlandHostMode(base) {
329
+ return isLocalOrigin(userland_strip(base) || PROD_ORIGINS.userland) ? "path" : "subdomain";
330
+ }
331
+ function userlandOrigin(workspace, options = {}) {
332
+ const base = userland_strip(options.base) || PROD_ORIGINS.userland;
333
+ if ("path" === userlandHostMode(base)) return base;
334
+ const slug = String(workspace ?? "").trim().toLowerCase();
335
+ if (!DNS_LABEL.test(slug)) throw new Error(`Workspace slug ${JSON.stringify(workspace)} cannot be a subdomain label; userland production hosts are <workspace>.extension.dev.`);
336
+ const url = new URL(base);
337
+ url.hostname = `${slug}.${url.hostname}`;
338
+ return userland_strip(url.toString());
339
+ }
340
+ const UserlandProjectPage = {
341
+ overview: "",
342
+ build: (buildId)=>`builds/${userland_seg(buildId)}`,
343
+ buildTab: (buildId, tab = "preview")=>"preview" === tab ? `builds/${userland_seg(buildId)}` : `builds/${userland_seg(buildId)}/${tab}`,
344
+ browserBuild: (buildId, browser)=>`builds/${userland_seg(buildId)}/${userland_seg(browser)}`,
345
+ channel: (channel)=>`channels/${userland_seg(channel)}`,
346
+ channelShortcut: (channel)=>userland_seg(channel),
347
+ channelBrowser: (browser, channel)=>`channels/${userland_seg(browser)}/${userland_seg(channel)}`,
348
+ version: (version)=>`versions/${userland_seg(version)}`,
349
+ versionBrowser: (browser, version)=>`versions/${userland_seg(browser)}/${userland_seg(version)}`
350
+ };
351
+ function userlandProjectPath(ref, page = "", options = {}) {
352
+ const head = "subdomain" === userlandHostMode(options.base) ? `/${userland_seg(ref.project)}` : `/${userland_seg(ref.workspace)}/${userland_seg(ref.project)}`;
353
+ const tail = String(page ?? "").replace(/^\/+/, "");
354
+ return tail ? `${head}/${tail}` : head;
355
+ }
356
+ function userlandUrl(ref, page = "", options = {}) {
357
+ const origin = userlandOrigin(ref.workspace, options);
358
+ const path = userlandProjectPath(ref, page, options);
359
+ return `${origin}${withQuery(path, options.query)}`;
360
+ }
361
+ function userlandBuildUrl(ref, buildId, options = {}) {
362
+ const page = options.browser ? UserlandProjectPage.browserBuild(buildId, options.browser) : UserlandProjectPage.build(buildId);
363
+ return userlandUrl(ref, page, options);
364
+ }
365
+ function userlandDialogUrl(ref, buildId, browser, dialog, options = {}) {
366
+ return userlandUrl(ref, UserlandProjectPage.browserBuild(buildId, browser), {
367
+ ...options,
368
+ query: {
369
+ ...options.query ?? {},
370
+ dialog
371
+ }
372
+ });
373
+ }
374
+ function userlandChannelUrl(ref, channel, options = {}) {
375
+ const page = options.browser ? UserlandProjectPage.channelBrowser(options.browser, channel) : options.shortcut ? UserlandProjectPage.channelShortcut(channel) : UserlandProjectPage.channel(channel);
376
+ return userlandUrl(ref, page, options);
377
+ }
378
+ exports.USERLAND_BUILD_TABS = __nested_rspack_exports__.USERLAND_BUILD_TABS;
379
+ exports.UserlandDialog = __nested_rspack_exports__.UserlandDialog;
380
+ exports.UserlandProjectPage = __nested_rspack_exports__.UserlandProjectPage;
381
+ exports.isUserlandBuildTab = __nested_rspack_exports__.isUserlandBuildTab;
382
+ exports.userlandBuildUrl = __nested_rspack_exports__.userlandBuildUrl;
383
+ exports.userlandChannelUrl = __nested_rspack_exports__.userlandChannelUrl;
384
+ exports.userlandDialogUrl = __nested_rspack_exports__.userlandDialogUrl;
385
+ exports.userlandHostMode = __nested_rspack_exports__.userlandHostMode;
386
+ exports.userlandOrigin = __nested_rspack_exports__.userlandOrigin;
387
+ exports.userlandProjectPath = __nested_rspack_exports__.userlandProjectPath;
388
+ exports.userlandUrl = __nested_rspack_exports__.userlandUrl;
389
+ for(var __webpack_i__ in __nested_rspack_exports__)if (-1 === [
390
+ "USERLAND_BUILD_TABS",
391
+ "UserlandDialog",
392
+ "UserlandProjectPage",
393
+ "isUserlandBuildTab",
394
+ "userlandBuildUrl",
395
+ "userlandChannelUrl",
396
+ "userlandDialogUrl",
397
+ "userlandHostMode",
398
+ "userlandOrigin",
399
+ "userlandProjectPath",
400
+ "userlandUrl"
401
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __nested_rspack_exports__[__webpack_i__];
402
+ Object.defineProperty(exports, "__esModule", {
403
+ value: true
404
+ });
405
+ }
406
+ });
19
407
  var add_feature_namespaceObject = {};
20
408
  __webpack_require__.r(add_feature_namespaceObject);
21
409
  __webpack_require__.d(add_feature_namespaceObject, {
@@ -222,7 +610,7 @@ __webpack_require__.d(whoami_namespaceObject, {
222
610
  handler: ()=>whoami_handler,
223
611
  schema: ()=>whoami_schema
224
612
  });
225
- var package_namespaceObject = JSON.parse('{"rE":"6.0.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
613
+ var package_namespaceObject = JSON.parse('{"rE":"6.3.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
226
614
  function credentialsPath() {
227
615
  if ("win32" === process.platform) {
228
616
  const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
@@ -295,77 +683,249 @@ function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
295
683
  if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
296
684
  return creds;
297
685
  }
298
- const PROD_ORIGINS = {
299
- www: "https://www.extension.dev",
300
- console: "https://console.extension.dev",
301
- inspect: "https://inspect.extension.dev",
302
- templates: "https://templates.extension.dev",
303
- intelligence: "https://intelligence.extension.dev",
304
- registry: "https://registry.extension.land",
305
- media: "https://media.extension.land"
306
- };
307
- const DEV_LOCALHOST_ORIGINS = {
308
- www: "http://localhost:3100",
309
- console: "http://console.extension.localhost",
310
- inspect: "http://inspect.extension.localhost",
311
- templates: "http://templates.extension.localhost",
312
- intelligence: "http://intelligence.extension.localhost",
313
- registry: "https://registry.extension.land",
314
- media: "https://media.extension.land"
315
- };
316
- function strip(value) {
317
- return String(value ?? "").trim().replace(/\/+$/, "");
686
+ const origins = __webpack_require__("./node_modules/.pnpm/@extension.dev+urls@0.3.0/node_modules/@extension.dev/urls/dist/origins.cjs");
687
+ const DEFAULT_API = origins.PROD_ORIGINS.www;
688
+ function tokenTtlNote(workspaceSlug, projectSlug) {
689
+ const tokensUrl = workspaceSlug && projectSlug ? consoleProjectUrl({
690
+ workspace: workspaceSlug,
691
+ project: projectSlug
692
+ }, "settings/access-tokens") : consoleBase();
693
+ return `extension.dev CLI tokens live at most 7 days (server-enforced). CI pipelines must re-mint before expiry on the console's Access tokens page: ${tokensUrl}`;
694
+ }
695
+ function resolveApiBase(api) {
696
+ return String(api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API).replace(/\/+$/, "");
318
697
  }
319
- function isLocalOrigin(url) {
320
- const raw = strip(url);
321
- if (!raw) return false;
322
- let host;
698
+ function safeApiBase(raw) {
699
+ let parsed;
323
700
  try {
324
- host = new URL(raw).hostname;
701
+ parsed = new URL(raw);
325
702
  } catch {
326
- return false;
703
+ return {
704
+ ok: false,
705
+ message: `Invalid platform URL: ${raw}`
706
+ };
327
707
  }
328
- return "localhost" === host || "127.0.0.1" === host || "::1" === host || "[::1]" === host || "extension.localhost" === host || host.endsWith(".extension.localhost");
708
+ const isLocalhost = "localhost" === parsed.hostname || "127.0.0.1" === parsed.hostname || "[::1]" === parsed.hostname || "::1" === parsed.hostname;
709
+ if ("https:" !== parsed.protocol && !("http:" === parsed.protocol && isLocalhost)) return {
710
+ ok: false,
711
+ message: `Refusing to send the access token to ${raw}: use https (http is allowed only for localhost).`
712
+ };
713
+ return {
714
+ ok: true,
715
+ base: `${parsed.origin}${parsed.pathname}`.replace(/\/+$/, "")
716
+ };
329
717
  }
330
- function resolveOrigins(overrides = {}, opts = {}) {
331
- const devLike = isLocalOrigin(overrides.www) || isLocalOrigin(overrides.console) || isLocalOrigin(opts.hint);
332
- const base = devLike ? DEV_LOCALHOST_ORIGINS : PROD_ORIGINS;
333
- const pick = (key)=>strip(overrides[key]) || base[key];
718
+ async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
719
+ const override = String(process.env.EXTENSION_DEV_GITHUB_CLIENT_ID || "").trim();
720
+ if (override) return {
721
+ provider: "github",
722
+ clientId: override,
723
+ scope: "read:user",
724
+ deviceCodeUrl: "/api/cli/device/code",
725
+ deviceTokenUrl: "/api/cli/device/token",
726
+ verificationUri: "https://github.com/login/device"
727
+ };
728
+ const res = await fetchImpl(`${apiBase}/api/cli/login/config`, {
729
+ headers: {
730
+ accept: "application/json"
731
+ }
732
+ });
733
+ if (!res.ok) throw new Error(`Could not fetch login config from ${apiBase} (${res.status}).`);
734
+ const data = await res.json().catch(()=>({}));
735
+ const provider = "extensiondev" === data.provider ? "extensiondev" : "github";
736
+ const clientId = String(data.githubClientId || "").trim();
737
+ if ("github" === provider && !clientId) throw new Error("Login is not configured on the server (no GitHub client id). Set EXTENSION_DEV_GITHUB_CLIENT_ID to override.");
334
738
  return {
335
- www: pick("www"),
336
- console: pick("console"),
337
- inspect: pick("inspect"),
338
- templates: pick("templates"),
339
- intelligence: pick("intelligence"),
340
- registry: pick("registry"),
341
- media: pick("media")
739
+ provider,
740
+ clientId,
741
+ scope: String(data.scope || "read:user"),
742
+ deviceCodeUrl: String(data.deviceCodeUrl || "/api/cli/device/code"),
743
+ deviceTokenUrl: String(data.deviceTokenUrl || "/api/cli/device/token"),
744
+ verificationUri: String(data.verificationUri || "https://github.com/login/device")
342
745
  };
343
746
  }
344
- const seg = (value)=>encodeURIComponent(String(value));
345
- function urls_paths_join(base, sub) {
346
- if (!sub) return base;
347
- return `${base}/${sub.replace(/^\/+/, "")}`;
747
+ function persistTokenResponse(args) {
748
+ const token = String(args.data.token || "").trim();
749
+ if (!token) throw new Error("Login returned no token.");
750
+ const creds = {
751
+ version: 1,
752
+ token,
753
+ workspaceSlug: String(args.data.workspaceSlug || ""),
754
+ projectSlug: String(args.data.projectSlug || ""),
755
+ expiresAt: Number(args.data.expiresAt || 0),
756
+ api: args.apiBase,
757
+ provider: args.provider
758
+ };
759
+ writeCredentials(creds);
760
+ return creds;
761
+ }
762
+ async function exchangeAndPersist(args) {
763
+ const doFetch = args.fetchImpl ?? fetch;
764
+ const res = await doFetch(`${args.apiBase}/api/cli/login/exchange`, {
765
+ method: "POST",
766
+ headers: {
767
+ "content-type": "application/json",
768
+ accept: "application/json"
769
+ },
770
+ body: JSON.stringify({
771
+ githubToken: args.githubToken,
772
+ project: args.project
773
+ })
774
+ });
775
+ const text = await res.text();
776
+ let data;
777
+ try {
778
+ data = JSON.parse(text);
779
+ } catch {
780
+ data = {
781
+ message: text
782
+ };
783
+ }
784
+ if (!res.ok) throw new Error(`Login exchange failed (${res.status}): ${data.message || "unknown error"}`);
785
+ return persistTokenResponse({
786
+ apiBase: args.apiBase,
787
+ data,
788
+ provider: "github"
789
+ });
348
790
  }
349
- function consoleProjectPath(ref, page = "") {
350
- return urls_paths_join(`/${seg(ref.workspace)}/${seg(ref.project)}`, page);
791
+ function _define_property(obj, key, value) {
792
+ if (key in obj) Object.defineProperty(obj, key, {
793
+ value: value,
794
+ enumerable: true,
795
+ configurable: true,
796
+ writable: true
797
+ });
798
+ else obj[key] = value;
799
+ return obj;
351
800
  }
352
- function withQuery(path, query) {
353
- if (!query) return path;
354
- const params = new URLSearchParams();
355
- for (const [key, value] of Object.entries(query))if (null != value && "" !== value) params.set(key, String(value));
356
- const qs = params.toString();
357
- return qs ? `${path}?${qs}` : path;
801
+ const REFRESH_LEAD_SECONDS = 60;
802
+ function cacheKey(ref) {
803
+ return `${ref.workspace.toLowerCase()}/${ref.project.toLowerCase()}`;
804
+ }
805
+ class RegistryAccessTokens {
806
+ peek(ref) {
807
+ const entry = this.cache.get(cacheKey(ref));
808
+ if (!entry || "fresh" !== entry.kind) return "";
809
+ return entry.expiresAt - REFRESH_LEAD_SECONDS > this.nowSeconds() ? entry.token : "";
810
+ }
811
+ async get(ref, apiHint) {
812
+ const key = cacheKey(ref);
813
+ const entry = this.cache.get(key);
814
+ if (entry?.kind === "pending") return entry.promise;
815
+ const cached = this.peek(ref);
816
+ if (cached) return {
817
+ status: "ok",
818
+ token: cached,
819
+ expiresAt: this.cache.get(key).expiresAt
820
+ };
821
+ const promise = this.mint(ref, apiHint);
822
+ this.cache.set(key, {
823
+ kind: "pending",
824
+ promise
825
+ });
826
+ const result = await promise;
827
+ if ("ok" === result.status) this.cache.set(key, {
828
+ kind: "fresh",
829
+ token: result.token,
830
+ expiresAt: result.expiresAt
831
+ });
832
+ else this.cache.delete(key);
833
+ return result;
834
+ }
835
+ async mint(ref, apiHint) {
836
+ const creds = readCredentials();
837
+ const token = String(process.env.EXTENSION_DEV_TOKEN || creds?.token || "").trim();
838
+ if (!token) return {
839
+ status: "no-credential"
840
+ };
841
+ if (creds?.workspaceSlug && creds?.projectSlug && !process.env.EXTENSION_DEV_TOKEN) {
842
+ const same = creds.workspaceSlug.toLowerCase() === ref.workspace.toLowerCase() && creds.projectSlug.toLowerCase() === ref.project.toLowerCase();
843
+ if (!same) return {
844
+ status: "no-credential"
845
+ };
846
+ }
847
+ const check = safeApiBase(resolveApiBase(apiHint || creds?.api));
848
+ if (!check.ok) return {
849
+ status: "denied",
850
+ message: check.message
851
+ };
852
+ let res;
853
+ try {
854
+ res = await this.fetchImpl(`${check.base}/api/access-grant`, {
855
+ method: "POST",
856
+ headers: {
857
+ authorization: `Bearer ${token}`,
858
+ "content-type": "application/json"
859
+ },
860
+ body: JSON.stringify({
861
+ workspaceSlug: ref.workspace,
862
+ projectSlug: ref.project
863
+ })
864
+ });
865
+ } catch (err) {
866
+ return {
867
+ status: "denied",
868
+ message: `Could not reach the access-grant endpoint: ${err?.message || err}`
869
+ };
870
+ }
871
+ if (400 === res.status) return {
872
+ status: "public"
873
+ };
874
+ if (!res.ok) return {
875
+ status: "denied",
876
+ httpStatus: res.status,
877
+ message: `access-grant returned ${res.status}`
878
+ };
879
+ let data;
880
+ try {
881
+ data = JSON.parse(await res.text());
882
+ } catch {
883
+ return {
884
+ status: "denied",
885
+ message: "access-grant did not return JSON"
886
+ };
887
+ }
888
+ const minted = String(data?.token || "").trim();
889
+ if (!minted) return {
890
+ status: "denied",
891
+ message: "access-grant returned no token"
892
+ };
893
+ return {
894
+ status: "ok",
895
+ token: minted,
896
+ expiresAt: Number(data?.expiresAt || 0)
897
+ };
898
+ }
899
+ constructor(options){
900
+ _define_property(this, "cache", new Map());
901
+ _define_property(this, "fetchImpl", void 0);
902
+ _define_property(this, "nowSeconds", void 0);
903
+ this.fetchImpl = options?.fetchImpl ?? fetch;
904
+ this.nowSeconds = options?.nowSeconds ?? (()=>Math.floor(Date.now() / 1000));
905
+ }
358
906
  }
359
- function wwwNewPath(query) {
360
- return withQuery("/new", query);
907
+ const defaultRegistryAccessTokens = new RegistryAccessTokens();
908
+ function withAccessToken(url, token) {
909
+ if (!token) return url;
910
+ try {
911
+ const u = new URL(url);
912
+ u.searchParams.set("t", token);
913
+ return u.toString();
914
+ } catch {
915
+ return url;
916
+ }
361
917
  }
362
- PROD_ORIGINS.registry;
918
+ const paths_0 = __webpack_require__("./node_modules/.pnpm/@extension.dev+urls@0.3.0/node_modules/@extension.dev/urls/dist/paths.cjs");
919
+ const userland = __webpack_require__("./node_modules/.pnpm/@extension.dev+urls@0.3.0/node_modules/@extension.dev/urls/dist/userland.cjs");
920
+ origins.PROD_ORIGINS.registry;
363
921
  function mcpOrigins(apiHint) {
364
922
  const www = String(apiHint || process.env.EXTENSION_DEV_API_URL || "").trim() || void 0;
365
- return resolveOrigins({
923
+ return (0, origins.resolveOrigins)({
366
924
  www,
367
925
  console: process.env.EXTENSION_DEV_CONSOLE_URL,
368
926
  inspect: process.env.EXTENSION_DEV_INSPECT_URL,
927
+ preview: process.env.EXTENSION_DEV_PREVIEW_URL,
928
+ userland: process.env.EXTENSION_DEV_USERLAND_URL,
369
929
  registry: process.env.EXTENSION_DEV_REGISTRY_URL,
370
930
  media: process.env.EXTENSION_MEDIA_ORIGIN
371
931
  }, {
@@ -400,35 +960,77 @@ function registryFileUrl(ref, file) {
400
960
  function consoleProjectUrl(ref, page, apiHint) {
401
961
  const base = consoleBase(apiHint);
402
962
  if (!ref) return base;
403
- return `${base}${consoleProjectPath(ref, page)}`;
963
+ return `${base}${(0, paths_0.consoleProjectPath)(ref, page)}`;
964
+ }
965
+ function userlandProjectUrl(ref, page = "", apiHint) {
966
+ if (!ref) return "";
967
+ try {
968
+ return (0, userland.userlandUrl)(ref, page, {
969
+ base: mcpOrigins(apiHint).userland
970
+ });
971
+ } catch {
972
+ return "";
973
+ }
974
+ }
975
+ async function readJson(url, res) {
976
+ try {
977
+ const text = await res.text();
978
+ return {
979
+ ok: true,
980
+ json: JSON.parse(text)
981
+ };
982
+ } catch {
983
+ return {
984
+ ok: false,
985
+ message: `${url} did not return valid JSON`
986
+ };
987
+ }
404
988
  }
405
- async function fetchRegistryJson(url, fetchImpl = fetch) {
989
+ async function fetchRegistryJson(url, fetchImpl = fetch, options) {
990
+ const tokens = options?.tokens ?? defaultRegistryAccessTokens;
991
+ const ref = options?.ref ?? null;
992
+ const cached = ref ? tokens.peek(ref) : "";
993
+ const firstUrl = cached ? withAccessToken(url, cached) : url;
406
994
  let res;
407
995
  try {
408
- res = await fetchImpl(url);
996
+ res = await fetchImpl(firstUrl);
409
997
  } catch (err) {
410
998
  return {
411
999
  ok: false,
412
1000
  message: `Could not reach ${url}: ${err?.message || err}`
413
1001
  };
414
1002
  }
415
- if (!res.ok) return {
1003
+ if (res.ok) return readJson(url, res);
1004
+ const authFailed = 401 === res.status || 403 === res.status;
1005
+ if (!authFailed || !ref) return {
416
1006
  ok: false,
417
1007
  status: res.status,
418
1008
  message: `${url} returned ${res.status}`
419
1009
  };
420
- try {
421
- const text = await res.text();
1010
+ const grant = await tokens.get(ref, options?.api);
1011
+ if ("ok" !== grant.status) {
1012
+ const detail = "no-credential" === grant.status ? "This project is private. Run extension_login for it, or set EXTENSION_DEV_TOKEN." : "public" === grant.status ? "The platform reports this project is public, but the registry refused the read." : grant.message;
422
1013
  return {
423
- ok: true,
424
- json: JSON.parse(text)
1014
+ ok: false,
1015
+ status: res.status,
1016
+ message: `${url} returned ${res.status}. ${detail}`
425
1017
  };
426
- } catch {
1018
+ }
1019
+ let retried;
1020
+ try {
1021
+ retried = await fetchImpl(withAccessToken(url, grant.token));
1022
+ } catch (err) {
427
1023
  return {
428
1024
  ok: false,
429
- message: `${url} did not return valid JSON`
1025
+ message: `Could not reach ${url}: ${err?.message || err}`
430
1026
  };
431
1027
  }
1028
+ if (!retried.ok) return {
1029
+ ok: false,
1030
+ status: retried.status,
1031
+ message: `${url} returned ${retried.status} even with an access token`
1032
+ };
1033
+ return readJson(url, retried);
432
1034
  }
433
1035
  function parseChannels(json) {
434
1036
  if (!json || "object" != typeof json || Array.isArray(json)) return [];
@@ -613,7 +1215,7 @@ async function create_handler(args) {
613
1215
  const resolvedParent = args.parentDir ? node_path.resolve(args.parentDir) : process.cwd();
614
1216
  const gitInit = node_fs.existsSync(node_path.join(result.projectPath, ".git"));
615
1217
  const wwwOrigin = mcpOrigins().www;
616
- const deployUrl = `${wwwOrigin}${wwwNewPath({
1218
+ const deployUrl = `${wwwOrigin}${(0, paths_0.wwwNewPath)({
617
1219
  template: result.template
618
1220
  })}`;
619
1221
  return JSON.stringify({
@@ -3197,7 +3799,7 @@ function summarizeConsoleMessages(messages) {
3197
3799
  topMessages: topMessages.sort((a, b)=>b.count - a.count).slice(0, 10)
3198
3800
  };
3199
3801
  }
3200
- function _define_property(obj, key, value) {
3802
+ function cdp_connection_define_property(obj, key, value) {
3201
3803
  if (key in obj) Object.defineProperty(obj, key, {
3202
3804
  value: value,
3203
3805
  enumerable: true,
@@ -3315,11 +3917,11 @@ class CDPConnection {
3315
3917
  };
3316
3918
  }
3317
3919
  constructor(){
3318
- _define_property(this, "ws", null);
3319
- _define_property(this, "messageId", 0);
3320
- _define_property(this, "eventListeners", new Set());
3321
- _define_property(this, "pendingRequests", new Map());
3322
- _define_property(this, "consoleMessages", []);
3920
+ cdp_connection_define_property(this, "ws", null);
3921
+ cdp_connection_define_property(this, "messageId", 0);
3922
+ cdp_connection_define_property(this, "eventListeners", new Set());
3923
+ cdp_connection_define_property(this, "pendingRequests", new Map());
3924
+ cdp_connection_define_property(this, "consoleMessages", []);
3323
3925
  }
3324
3926
  }
3325
3927
  const PAGE_HTML_SCRIPT = `(() => {
@@ -6049,110 +6651,6 @@ async function dom_inspect_handler(args) {
6049
6651
  return raw;
6050
6652
  }
6051
6653
  }
6052
- const DEFAULT_API = PROD_ORIGINS.www;
6053
- function tokenTtlNote(workspaceSlug, projectSlug) {
6054
- const tokensUrl = workspaceSlug && projectSlug ? consoleProjectUrl({
6055
- workspace: workspaceSlug,
6056
- project: projectSlug
6057
- }, "settings/access-tokens") : consoleBase();
6058
- return `extension.dev CLI tokens live at most 7 days (server-enforced). CI pipelines must re-mint before expiry on the console's Access tokens page: ${tokensUrl}`;
6059
- }
6060
- function resolveApiBase(api) {
6061
- return String(api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API).replace(/\/+$/, "");
6062
- }
6063
- function safeApiBase(raw) {
6064
- let parsed;
6065
- try {
6066
- parsed = new URL(raw);
6067
- } catch {
6068
- return {
6069
- ok: false,
6070
- message: `Invalid platform URL: ${raw}`
6071
- };
6072
- }
6073
- const isLocalhost = "localhost" === parsed.hostname || "127.0.0.1" === parsed.hostname || "[::1]" === parsed.hostname || "::1" === parsed.hostname;
6074
- if ("https:" !== parsed.protocol && !("http:" === parsed.protocol && isLocalhost)) return {
6075
- ok: false,
6076
- message: `Refusing to send the access token to ${raw}: use https (http is allowed only for localhost).`
6077
- };
6078
- return {
6079
- ok: true,
6080
- base: `${parsed.origin}${parsed.pathname}`.replace(/\/+$/, "")
6081
- };
6082
- }
6083
- async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
6084
- const override = String(process.env.EXTENSION_DEV_GITHUB_CLIENT_ID || "").trim();
6085
- if (override) return {
6086
- provider: "github",
6087
- clientId: override,
6088
- scope: "read:user",
6089
- deviceCodeUrl: "/api/cli/device/code",
6090
- deviceTokenUrl: "/api/cli/device/token",
6091
- verificationUri: "https://github.com/login/device"
6092
- };
6093
- const res = await fetchImpl(`${apiBase}/api/cli/login/config`, {
6094
- headers: {
6095
- accept: "application/json"
6096
- }
6097
- });
6098
- if (!res.ok) throw new Error(`Could not fetch login config from ${apiBase} (${res.status}).`);
6099
- const data = await res.json().catch(()=>({}));
6100
- const provider = "extensiondev" === data.provider ? "extensiondev" : "github";
6101
- const clientId = String(data.githubClientId || "").trim();
6102
- if ("github" === provider && !clientId) throw new Error("Login is not configured on the server (no GitHub client id). Set EXTENSION_DEV_GITHUB_CLIENT_ID to override.");
6103
- return {
6104
- provider,
6105
- clientId,
6106
- scope: String(data.scope || "read:user"),
6107
- deviceCodeUrl: String(data.deviceCodeUrl || "/api/cli/device/code"),
6108
- deviceTokenUrl: String(data.deviceTokenUrl || "/api/cli/device/token"),
6109
- verificationUri: String(data.verificationUri || "https://github.com/login/device")
6110
- };
6111
- }
6112
- function persistTokenResponse(args) {
6113
- const token = String(args.data.token || "").trim();
6114
- if (!token) throw new Error("Login returned no token.");
6115
- const creds = {
6116
- version: 1,
6117
- token,
6118
- workspaceSlug: String(args.data.workspaceSlug || ""),
6119
- projectSlug: String(args.data.projectSlug || ""),
6120
- expiresAt: Number(args.data.expiresAt || 0),
6121
- api: args.apiBase,
6122
- provider: args.provider
6123
- };
6124
- writeCredentials(creds);
6125
- return creds;
6126
- }
6127
- async function exchangeAndPersist(args) {
6128
- const doFetch = args.fetchImpl ?? fetch;
6129
- const res = await doFetch(`${args.apiBase}/api/cli/login/exchange`, {
6130
- method: "POST",
6131
- headers: {
6132
- "content-type": "application/json",
6133
- accept: "application/json"
6134
- },
6135
- body: JSON.stringify({
6136
- githubToken: args.githubToken,
6137
- project: args.project
6138
- })
6139
- });
6140
- const text = await res.text();
6141
- let data;
6142
- try {
6143
- data = JSON.parse(text);
6144
- } catch {
6145
- data = {
6146
- message: text
6147
- };
6148
- }
6149
- if (!res.ok) throw new Error(`Login exchange failed (${res.status}): ${data.message || "unknown error"}`);
6150
- return persistTokenResponse({
6151
- apiBase: args.apiBase,
6152
- data,
6153
- provider: "github"
6154
- });
6155
- }
6156
6654
  function resolveToken() {
6157
6655
  const fromEnv = String(process.env.EXTENSION_DEV_TOKEN || "").trim();
6158
6656
  if (fromEnv) return fromEnv;
@@ -6274,7 +6772,10 @@ async function publish_handler(args) {
6274
6772
  const ref = resolveProjectRef();
6275
6773
  if (ref) {
6276
6774
  const buildsUrl = registryFileUrl(ref, "builds/index.json");
6277
- const buildsRes = await fetchRegistryJson(buildsUrl);
6775
+ const buildsRes = await fetchRegistryJson(buildsUrl, fetch, {
6776
+ ref,
6777
+ api: args.api
6778
+ });
6278
6779
  if (buildsRes.ok) {
6279
6780
  const items = parseBuildIndex(buildsRes.json);
6280
6781
  const pinned = args.buildSha ? items.find((item)=>{
@@ -6396,7 +6897,10 @@ async function release_promote_handler(args) {
6396
6897
  enrich.hint = "Run extension_release_list to see this project's channels, their promoted shas, and recent builds.";
6397
6898
  if (ref) {
6398
6899
  const channelsUrl = registryFileUrl(ref, "channels.json");
6399
- const channelsRes = await fetchRegistryJson(channelsUrl);
6900
+ const channelsRes = await fetchRegistryJson(channelsUrl, fetch, {
6901
+ ref,
6902
+ api: args.api
6903
+ });
6400
6904
  if (channelsRes.ok) {
6401
6905
  const rows = parseChannels(channelsRes.json).filter((c)=>c.sha);
6402
6906
  enrich.validChannelShas = Object.fromEntries(rows.map((c)=>[
@@ -6419,11 +6923,23 @@ async function release_promote_handler(args) {
6419
6923
  ...enrich
6420
6924
  });
6421
6925
  }
6422
- return JSON.stringify(data);
6926
+ const promotedRef = resolveProjectRef();
6927
+ const publicChannelUrl = userlandProjectUrl(promotedRef, userland.UserlandProjectPage.channel(channel), args.api);
6928
+ const publicBuildUrl = userlandProjectUrl(promotedRef, userland.UserlandProjectPage.build(buildId), args.api);
6929
+ const enriched = data && "object" == typeof data && !Array.isArray(data) ? {
6930
+ ...data,
6931
+ ...publicChannelUrl ? {
6932
+ publicChannelUrl
6933
+ } : {},
6934
+ ...publicBuildUrl ? {
6935
+ publicBuildUrl
6936
+ } : {}
6937
+ } : data;
6938
+ return JSON.stringify(enriched);
6423
6939
  }
6424
6940
  const release_list_schema = {
6425
6941
  name: "extension_release_list",
6426
- description: "List the project's release channels (channel -> promoted build sha) and recent builds from the public registry (registry.extension.land), so you can pick a valid buildSha for extension_release_promote, extension_deploy, or extension_publish. Read-only, no dispatch. Defaults to the logged-in project (extension_login); pass workspace + project to inspect another public project. Also returns the registry URLs it read and the console Builds page URL.",
6942
+ description: "List the project's release channels (channel -> promoted build sha) and recent builds from the registry (registry.extension.land), so you can pick a valid buildSha for extension_release_promote, extension_deploy, or extension_publish. Read-only, no dispatch. Defaults to the logged-in project (extension_login); pass workspace + project to inspect another project. Works for PRIVATE projects too when your stored login covers them. Returns the registry URLs it read, the console Builds page URL (needs a login), and a publicUrl per channel and build on the public viewer (no login needed for a public project).",
6427
6943
  inputSchema: {
6428
6944
  type: "object",
6429
6945
  properties: {
@@ -6434,6 +6950,10 @@ const release_list_schema = {
6434
6950
  project: {
6435
6951
  type: "string",
6436
6952
  description: "Project slug override (defaults to the stored login's project)."
6953
+ },
6954
+ api: {
6955
+ type: "string",
6956
+ description: "Platform base URL (defaults to https://www.extension.dev or EXTENSION_DEV_API_URL). Used to resolve link origins and, for a private project, to mint a short-lived read token."
6437
6957
  }
6438
6958
  },
6439
6959
  required: []
@@ -6456,12 +6976,21 @@ async function release_list_handler(args) {
6456
6976
  const metaUrl = registryFileUrl(ref, "meta.json");
6457
6977
  const buildsUrl = registryFileUrl(ref, "builds/index.json");
6458
6978
  const [channelsRes, metaRes, buildsRes] = await Promise.all([
6459
- fetchRegistryJson(channelsUrl),
6460
- fetchRegistryJson(metaUrl),
6461
- fetchRegistryJson(buildsUrl)
6979
+ fetchRegistryJson(channelsUrl, fetch, {
6980
+ ref,
6981
+ api: args.api
6982
+ }),
6983
+ fetchRegistryJson(metaUrl, fetch, {
6984
+ ref,
6985
+ api: args.api
6986
+ }),
6987
+ fetchRegistryJson(buildsUrl, fetch, {
6988
+ ref,
6989
+ api: args.api
6990
+ })
6462
6991
  ]);
6463
- const buildsPageUrl = consoleProjectUrl(ref, "builds");
6464
- if (!channelsRes.ok && !metaRes.ok && !buildsRes.ok) return release_list_fail("ReleaseListNotFound", `No registry data for ${ref.workspace}/${ref.project} (${channelsUrl} returned ${channelsRes.status ?? "no response"}). The project may have no builds yet, be private (private registry data needs a share token), or the workspace/project slugs may be wrong. The console Builds page is the authoritative view: ${buildsPageUrl}`, {
6992
+ const buildsPageUrl = consoleProjectUrl(ref, "builds", args.api);
6993
+ if (!channelsRes.ok && !metaRes.ok && !buildsRes.ok) return release_list_fail("ReleaseListNotFound", `No registry data for ${ref.workspace}/${ref.project} (${channelsUrl} returned ${channelsRes.status ?? "no response"}). The project may have no builds yet, or the workspace/project slugs may be wrong. If it is private, make sure extension_login covers this exact project (a token scoped elsewhere cannot read it). The console Builds page is the authoritative view: ${buildsPageUrl}`, {
6465
6994
  workspace: ref.workspace,
6466
6995
  project: ref.project,
6467
6996
  registryUrl: channelsUrl,
@@ -6472,6 +7001,16 @@ async function release_list_handler(args) {
6472
7001
  recentBuilds.sort((a, b)=>String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
6473
7002
  const meta = metaRes.ok ? metaRes.json : void 0;
6474
7003
  const promotable = Array.from(new Set(channels.map((c)=>c.sha).filter(Boolean)));
7004
+ const isPrivate = "private" === String(meta?.visibility || "").toLowerCase();
7005
+ const publicProjectUrl = userlandProjectUrl(ref, "", args.api);
7006
+ const channelsWithUrls = channels.map((c)=>({
7007
+ ...c,
7008
+ publicUrl: userlandProjectUrl(ref, userland.UserlandProjectPage.channel(c.channel), args.api)
7009
+ }));
7010
+ const buildsWithUrls = recentBuilds.map((b)=>({
7011
+ ...b,
7012
+ publicUrl: userlandProjectUrl(ref, userland.UserlandProjectPage.build(b.sha), args.api)
7013
+ }));
6475
7014
  const result = {
6476
7015
  ok: true,
6477
7016
  workspace: ref.workspace,
@@ -6482,10 +7021,16 @@ async function release_list_handler(args) {
6482
7021
  ...meta?.visibility ? {
6483
7022
  visibility: meta.visibility
6484
7023
  } : {},
6485
- channels,
6486
- recentBuilds,
7024
+ channels: channelsWithUrls,
7025
+ recentBuilds: buildsWithUrls,
6487
7026
  registryUrl: channelsUrl,
6488
7027
  buildsPageUrl,
7028
+ ...publicProjectUrl ? {
7029
+ publicProjectUrl
7030
+ } : {},
7031
+ ...publicProjectUrl ? {
7032
+ publicUrlNote: isPrivate ? "publicUrl links open only for workspace members. This project is private, so an outside recipient needs a share link from extension_publish." : "publicUrl links are the public build pages: no login needed, and they carry the per-browser downloads and the run locally instructions."
7033
+ } : {},
6489
7034
  message: promotable.length > 0 || recentBuilds.length > 0 ? `Promotable shas: channels currently pin ${promotable.length > 0 ? promotable.join(", ") : "none"}; recent builds add ${recentBuilds.filter((b)=>"success" === b.status).map((b)=>b.sha).join(", ") || "none"}. Use one of these as buildId/buildSha for promote/deploy/publish.` : `No channels or builds are recorded on the registry yet for ${ref.workspace}/${ref.project}. Push a commit to produce a build, then check ${buildsPageUrl}.`
6490
7035
  };
6491
7036
  if (!channelsRes.ok) result.channelsUnavailable = `channels.json unreadable: ${channelsRes.message}`;
@@ -6633,8 +7178,14 @@ async function deploy_handler(args) {
6633
7178
  let channelRows = null;
6634
7179
  if (ref) {
6635
7180
  const [healthRes, channelsRes] = await Promise.all([
6636
- fetchRegistryJson(registryFileUrl(ref, "stores/health.json")),
6637
- fetchRegistryJson(registryFileUrl(ref, "channels.json"))
7181
+ fetchRegistryJson(registryFileUrl(ref, "stores/health.json"), fetch, {
7182
+ ref,
7183
+ api: args.api
7184
+ }),
7185
+ fetchRegistryJson(registryFileUrl(ref, "channels.json"), fetch, {
7186
+ ref,
7187
+ api: args.api
7188
+ })
6638
7189
  ]);
6639
7190
  if (healthRes.ok) {
6640
7191
  const stores = healthRes.json?.stores;
@@ -6717,6 +7268,10 @@ const store_status_schema = {
6717
7268
  project: {
6718
7269
  type: "string",
6719
7270
  description: "Project slug override (defaults to the stored login's project)."
7271
+ },
7272
+ api: {
7273
+ type: "string",
7274
+ description: "Platform base URL (defaults to https://www.extension.dev or EXTENSION_DEV_API_URL). Used to resolve link origins and, for a private project, to mint a short-lived read token."
6720
7275
  }
6721
7276
  },
6722
7277
  required: []
@@ -6816,11 +7371,20 @@ async function store_status_handler(args) {
6816
7371
  const healthUrl = registryFileUrl(ref, "stores/health.json");
6817
7372
  const statusUrl = registryFileUrl(ref, "stores/status.json");
6818
7373
  const submissionsUrl = registryFileUrl(ref, "stores/submissions.json");
6819
- const consoleStoresUrl = consoleProjectUrl(ref, "stores");
7374
+ const consoleStoresUrl = consoleProjectUrl(ref, "stores", args.api);
6820
7375
  const [healthRes, statusRes, submissionsRes] = await Promise.all([
6821
- fetchRegistryJson(healthUrl),
6822
- fetchRegistryJson(statusUrl),
6823
- fetchRegistryJson(submissionsUrl)
7376
+ fetchRegistryJson(healthUrl, fetch, {
7377
+ ref,
7378
+ api: args.api
7379
+ }),
7380
+ fetchRegistryJson(statusUrl, fetch, {
7381
+ ref,
7382
+ api: args.api
7383
+ }),
7384
+ fetchRegistryJson(submissionsUrl, fetch, {
7385
+ ref,
7386
+ api: args.api
7387
+ })
6824
7388
  ]);
6825
7389
  if (!healthRes.ok && !statusRes.ok && !submissionsRes.ok) return store_status_fail("StoreStatusNotFound", `No store data on the registry for ${ref.workspace}/${ref.project} (${healthUrl} returned ${healthRes.status ?? "no response"}). The project may have no stores configured yet, be private (private registry data needs a share token), or the workspace/project slugs may be wrong. Configure stores at ${consoleStoresUrl}/new; the console Stores page is the authoritative view: ${consoleStoresUrl}`, {
6826
7390
  workspace: ref.workspace,