@extenshi/cli 0.20.0 → 0.22.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.
Files changed (122) hide show
  1. package/README.md +4 -0
  2. package/dist/amo-source-detect.d.ts +18 -0
  3. package/dist/amo-source-detect.d.ts.map +1 -0
  4. package/dist/amo-source-detect.js +41 -0
  5. package/dist/amo-source-detect.js.map +1 -0
  6. package/dist/amo-source.d.ts +41 -0
  7. package/dist/amo-source.d.ts.map +1 -0
  8. package/dist/amo-source.js +240 -0
  9. package/dist/amo-source.js.map +1 -0
  10. package/dist/amowebstore-md.d.ts +38 -0
  11. package/dist/amowebstore-md.d.ts.map +1 -0
  12. package/dist/amowebstore-md.js +118 -0
  13. package/dist/amowebstore-md.js.map +1 -0
  14. package/dist/auto-evidence.d.ts +100 -0
  15. package/dist/auto-evidence.d.ts.map +1 -0
  16. package/dist/auto-evidence.js +197 -0
  17. package/dist/auto-evidence.js.map +1 -0
  18. package/dist/cli.js +320 -8
  19. package/dist/cli.js.map +1 -1
  20. package/dist/edgewebstore-md.d.ts +26 -0
  21. package/dist/edgewebstore-md.d.ts.map +1 -0
  22. package/dist/edgewebstore-md.js +95 -0
  23. package/dist/edgewebstore-md.js.map +1 -0
  24. package/dist/evidence-ingest.d.ts +60 -0
  25. package/dist/evidence-ingest.d.ts.map +1 -0
  26. package/dist/evidence-ingest.js +141 -0
  27. package/dist/evidence-ingest.js.map +1 -0
  28. package/dist/expected-error.d.ts +9 -0
  29. package/dist/expected-error.d.ts.map +1 -1
  30. package/dist/expected-error.js +16 -1
  31. package/dist/expected-error.js.map +1 -1
  32. package/dist/firefox-rdp.d.ts +55 -0
  33. package/dist/firefox-rdp.d.ts.map +1 -0
  34. package/dist/firefox-rdp.js +203 -0
  35. package/dist/firefox-rdp.js.map +1 -0
  36. package/dist/gecko-id.d.ts +55 -0
  37. package/dist/gecko-id.d.ts.map +1 -0
  38. package/dist/gecko-id.js +132 -0
  39. package/dist/gecko-id.js.map +1 -0
  40. package/dist/generate-listing.d.ts +19 -8
  41. package/dist/generate-listing.d.ts.map +1 -1
  42. package/dist/generate-listing.js +66 -18
  43. package/dist/generate-listing.js.map +1 -1
  44. package/dist/icon.d.ts +18 -0
  45. package/dist/icon.d.ts.map +1 -1
  46. package/dist/icon.js +29 -20
  47. package/dist/icon.js.map +1 -1
  48. package/dist/json-ld.d.ts +89 -0
  49. package/dist/json-ld.d.ts.map +1 -0
  50. package/dist/json-ld.js +118 -0
  51. package/dist/json-ld.js.map +1 -0
  52. package/dist/landing-page.d.ts +123 -0
  53. package/dist/landing-page.d.ts.map +1 -0
  54. package/dist/landing-page.js +405 -0
  55. package/dist/landing-page.js.map +1 -0
  56. package/dist/media.d.ts +42 -0
  57. package/dist/media.d.ts.map +1 -0
  58. package/dist/media.js +96 -0
  59. package/dist/media.js.map +1 -0
  60. package/dist/package.d.ts +163 -0
  61. package/dist/package.d.ts.map +1 -0
  62. package/dist/package.js +500 -0
  63. package/dist/package.js.map +1 -0
  64. package/dist/page.d.ts +141 -0
  65. package/dist/page.d.ts.map +1 -0
  66. package/dist/page.js +227 -0
  67. package/dist/page.js.map +1 -0
  68. package/dist/pay-contract.d.ts +20 -13
  69. package/dist/pay-contract.d.ts.map +1 -1
  70. package/dist/pay-contract.js +20 -11
  71. package/dist/pay-contract.js.map +1 -1
  72. package/dist/pay-test-cycle.d.ts +40 -0
  73. package/dist/pay-test-cycle.d.ts.map +1 -0
  74. package/dist/pay-test-cycle.js +207 -0
  75. package/dist/pay-test-cycle.js.map +1 -0
  76. package/dist/pay.d.ts +1 -1
  77. package/dist/pay.d.ts.map +1 -1
  78. package/dist/pay.js +1 -0
  79. package/dist/pay.js.map +1 -1
  80. package/dist/project.d.ts +39 -1
  81. package/dist/project.d.ts.map +1 -1
  82. package/dist/project.js +231 -20
  83. package/dist/project.js.map +1 -1
  84. package/dist/publish.d.ts +33 -0
  85. package/dist/publish.d.ts.map +1 -1
  86. package/dist/publish.js +113 -2
  87. package/dist/publish.js.map +1 -1
  88. package/dist/review-checks.d.ts +13 -0
  89. package/dist/review-checks.d.ts.map +1 -1
  90. package/dist/review-checks.js.map +1 -1
  91. package/dist/review-risk.d.ts +10 -1
  92. package/dist/review-risk.d.ts.map +1 -1
  93. package/dist/review-risk.js +1 -0
  94. package/dist/review-risk.js.map +1 -1
  95. package/dist/scan.d.ts +3 -2
  96. package/dist/scan.d.ts.map +1 -1
  97. package/dist/scan.js +4 -2
  98. package/dist/scan.js.map +1 -1
  99. package/dist/screenshots-scenes.d.ts +1383 -0
  100. package/dist/screenshots-scenes.d.ts.map +1 -0
  101. package/dist/screenshots-scenes.js +276 -0
  102. package/dist/screenshots-scenes.js.map +1 -0
  103. package/dist/screenshots.d.ts +145 -0
  104. package/dist/screenshots.d.ts.map +1 -0
  105. package/dist/screenshots.js +557 -0
  106. package/dist/screenshots.js.map +1 -0
  107. package/dist/smoke.d.ts +81 -0
  108. package/dist/smoke.d.ts.map +1 -0
  109. package/dist/smoke.js +491 -0
  110. package/dist/smoke.js.map +1 -0
  111. package/dist/store-assets.d.ts +139 -0
  112. package/dist/store-assets.d.ts.map +1 -0
  113. package/dist/store-assets.js +582 -0
  114. package/dist/store-assets.js.map +1 -0
  115. package/dist/telemetry.d.ts.map +1 -1
  116. package/dist/telemetry.js +3 -0
  117. package/dist/telemetry.js.map +1 -1
  118. package/dist/zip.d.ts +57 -0
  119. package/dist/zip.d.ts.map +1 -0
  120. package/dist/zip.js +134 -0
  121. package/dist/zip.js.map +1 -0
  122. package/package.json +4 -3
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Page Generator: an extension's landing / homepage as ONE static HTML file.
3
+ *
4
+ * No JavaScript in the output — pure HTML/CSS, so it deploys anywhere
5
+ * (GitHub Pages, Netlify, S3) and trivially passes CSP. Store CTAs render
6
+ * only for the stores the developer actually filled in.
7
+ *
8
+ * Every user-supplied string is HTML-escaped, every link is scheme-clamped
9
+ * (safeHref: http(s) only; the support link may also be a single mailto:
10
+ * address) and every image source is clamped to https, a
11
+ * relative path or a raster `data:image/…;base64` URL (safeImageSrc). An
12
+ * inline SVG logo is validated (landingLogoSvgProblem) and then embedded
13
+ * through `<img src="data:image/svg+xml,…">`, where browsers never run
14
+ * scripts or fetch external resources — so even a validator miss cannot
15
+ * script the page.
16
+ *
17
+ * SINGLE SOURCE, THREE COPIES:
18
+ * shared-types/landing-page.ts (canonical — edit here; dojo re-exports it)
19
+ * tools/extenshi-mcp/src/landing-page.ts (copy for the published @extenshi/mcp)
20
+ * tools/extenshi-cli/src/landing-page.ts (copy for the published @extenshi/cli)
21
+ * The published packages cannot import workspace shared-types at runtime.
22
+ * The copies differ from this file ONLY in the json-ld import specifier
23
+ * (`./json-ld.js` there, the package specifier here — see
24
+ * module-specifiers.test.ts for why shared-types may not use relative ones).
25
+ * `landing-page-sync.test.ts` in both packages fails when a copy drifts.
26
+ * After editing, run:
27
+ * sed '/^import/s#shared-types/#./#' shared-types/landing-page.ts > tools/extenshi-mcp/src/landing-page.ts
28
+ * sed '/^import/s#shared-types/#./#' shared-types/landing-page.ts > tools/extenshi-cli/src/landing-page.ts
29
+ */
30
+ import { jsonLdFromLanding, jsonLdScriptTag } from './json-ld.js';
31
+ /** Input limits shared by the MCP tool and CLI schemas. */
32
+ export const LANDING_LIMITS = {
33
+ extensionName: 120,
34
+ tagline: 200,
35
+ description: 2000,
36
+ url: 2000,
37
+ features: 12,
38
+ featureTitle: 120,
39
+ featureDescription: 500,
40
+ screenshots: 8,
41
+ screenshotAlt: 200,
42
+ logoSvgBytes: 100_000,
43
+ logoUrl: 400_000,
44
+ };
45
+ export const DEFAULT_LANDING_FORM = {
46
+ extensionName: '',
47
+ tagline: '',
48
+ description: '',
49
+ accentColor: '#5e5ce6',
50
+ theme: 'dark',
51
+ chromeUrl: '',
52
+ firefoxUrl: '',
53
+ edgeUrl: '',
54
+ privacyPolicyUrl: '',
55
+ supportUrl: '',
56
+ features: [
57
+ { title: '', description: '' },
58
+ { title: '', description: '' },
59
+ { title: '', description: '' },
60
+ ],
61
+ };
62
+ /**
63
+ * Rebuild the editor form from a saved `page-generator` row (or the project
64
+ * name when nothing is saved). Called during render — not in an effect —
65
+ * so the live preview iframe mounts with this payload, not the empty default.
66
+ * An older row that predates `features` (or the optional fields) still yields
67
+ * a full form.
68
+ */
69
+ export function hydrateLandingForm(saved, projectName) {
70
+ if (saved?.form) {
71
+ const features = Array.isArray(saved.form.features) ? saved.form.features : DEFAULT_LANDING_FORM.features;
72
+ const { screenshots, ...rest } = saved.form;
73
+ const form = { ...DEFAULT_LANDING_FORM, ...rest, features };
74
+ if (Array.isArray(screenshots))
75
+ form.screenshots = screenshots;
76
+ return form;
77
+ }
78
+ const name = projectName?.trim();
79
+ if (name)
80
+ return { ...DEFAULT_LANDING_FORM, extensionName: name };
81
+ return { ...DEFAULT_LANDING_FORM, features: [...DEFAULT_LANDING_FORM.features] };
82
+ }
83
+ export function landingFormToInput(form, logoSvg) {
84
+ return {
85
+ extensionName: form.extensionName,
86
+ tagline: form.tagline,
87
+ description: form.description,
88
+ accentColor: form.accentColor,
89
+ theme: form.theme,
90
+ storeUrls: { chrome: form.chromeUrl, firefox: form.firefoxUrl, edge: form.edgeUrl },
91
+ features: form.features,
92
+ privacyPolicyUrl: form.privacyPolicyUrl,
93
+ supportUrl: form.supportUrl,
94
+ logoSvg: logoSvg ?? form.logoSvg ?? '',
95
+ logoUrl: form.logoUrl ?? '',
96
+ homepageUrl: form.homepageUrl ?? '',
97
+ screenshots: form.screenshots ?? [],
98
+ };
99
+ }
100
+ /** Tolerates non-strings from hand-edited JSON or an old saved row. */
101
+ function str(v) {
102
+ return typeof v === 'string' ? v.trim() : '';
103
+ }
104
+ function esc(s) {
105
+ return s
106
+ .replaceAll('&', '&amp;')
107
+ .replaceAll('<', '&lt;')
108
+ .replaceAll('>', '&gt;')
109
+ .replaceAll('"', '&quot;')
110
+ .replaceAll("'", '&#39;');
111
+ }
112
+ // HTML-escape AND scheme-clamp a user-supplied URL before it lands in an
113
+ // href="". esc() alone stops attribute-breakout but not a `javascript:`/`data:`
114
+ // scheme, so a value like `javascript:alert(1)` would survive into the
115
+ // downloaded page the developer publishes for their own users. Only http(s)
116
+ // URLs are emitted verbatim; anything else collapses to "#".
117
+ function safeHref(raw) {
118
+ const u = raw.trim();
119
+ return /^https?:\/\//i.test(u) ? esc(u) : '#';
120
+ }
121
+ // One plain address, optionally `?subject=` with a percent-encoded value.
122
+ // No `&` (a second header such as bcc/body), no raw or encoded CR/LF, no
123
+ // whitespace, and the address itself admits no `%` — so
124
+ // `mailto:a@b.c%0d%0aBcc:x` and `mailto:javascript:` are both refused.
125
+ const MAILTO = /^mailto:[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+(?:\?subject=(?:[A-Za-z0-9._~-]|%(?![01][0-9a-f]|7f)[0-9a-f]{2})+)?$/i;
126
+ /** safeHref() for the support link only: http(s), or a validated mailto: address. */
127
+ function safeSupportHref(raw) {
128
+ const u = raw.trim();
129
+ return MAILTO.test(u) ? esc(u) : safeHref(u);
130
+ }
131
+ const HTTPS_URL = /^https:\/\/[^\s"'<>\\]+$/i;
132
+ const DATA_IMAGE_URL = /^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
133
+ // A path relative to the page: no scheme (`javascript:`, `data:`, `http:`), not
134
+ // protocol-relative (`//host`, `\\host` — browsers treat `\` as `/`), and no
135
+ // whitespace or quotes that could smuggle a second attribute.
136
+ const RELATIVE_PATH = /^(?![a-z][a-z0-9+.-]*:)(?![/\\]{2})[^\s"'<>\\]+$/i;
137
+ /** Image src clamp: https, relative path, or raster data URI; '' when unsafe. */
138
+ function safeImageSrc(raw) {
139
+ const u = str(raw);
140
+ if (!u)
141
+ return '';
142
+ if (HTTPS_URL.test(u) || RELATIVE_PATH.test(u))
143
+ return esc(u);
144
+ if (DATA_IMAGE_URL.test(u))
145
+ return u;
146
+ return '';
147
+ }
148
+ function canonicalUrl(raw) {
149
+ const u = str(raw);
150
+ return HTTPS_URL.test(u) ? u : '';
151
+ }
152
+ /** Absolute https form of an image URL (for og:image / JSON-LD), or ''. */
153
+ function absoluteImageUrl(raw, base) {
154
+ const u = str(raw);
155
+ if (HTTPS_URL.test(u))
156
+ return u;
157
+ if (!base || !RELATIVE_PATH.test(u))
158
+ return '';
159
+ try {
160
+ const resolved = new URL(u, base).href;
161
+ return HTTPS_URL.test(resolved) ? resolved : '';
162
+ }
163
+ catch {
164
+ return '';
165
+ }
166
+ }
167
+ const SVG_FORBIDDEN = [
168
+ [/<!DOCTYPE|<!ENTITY/i, 'DOCTYPE/ENTITY declarations are not allowed'],
169
+ [
170
+ /<\s*(script|foreignObject|iframe|object|embed|applet|form|input|textarea|select|button|animate|animateTransform|animateMotion|set|handler|listener)\b/i,
171
+ 'script, foreignObject, embedded documents, form controls and animation elements are not allowed',
172
+ ],
173
+ [/\son[a-z]+\s*=/i, 'event-handler attributes (on*) are not allowed'],
174
+ [/(javascript|vbscript)\s*:/i, 'script URLs are not allowed'],
175
+ [/&#/, 'numeric character references are not allowed'],
176
+ [/@import|expression\s*\(/i, 'CSS @import / expression() is not allowed'],
177
+ [
178
+ // Quote handled by alternation, not `["']?`, so backtracking cannot skip
179
+ // the quote and let `(?!#)` look at it instead of the value.
180
+ /\s(?:xlink:)?href\s*=\s*(?:"\s*|'\s*|)(?!["'#\s]|data:image\/(?:png|jpeg|gif|webp);)/i,
181
+ 'href values other than #fragments or raster data: images are not allowed',
182
+ ],
183
+ ];
184
+ /**
185
+ * Why an inline SVG logo is refused, or null when it is acceptable. A refusal
186
+ * never throws: buildLandingPage falls back to logoUrl / the monogram, and the
187
+ * MCP tool and CLI surface the reason instead of silently dropping the logo.
188
+ */
189
+ export function landingLogoSvgProblem(svg) {
190
+ const s = str(svg);
191
+ if (!s)
192
+ return 'the SVG is empty';
193
+ if (new TextEncoder().encode(s).length > LANDING_LIMITS.logoSvgBytes) {
194
+ return `the SVG exceeds ${LANDING_LIMITS.logoSvgBytes} bytes`;
195
+ }
196
+ if (!/^(?:<\?xml[^>]*\?>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg[\s>]/i.test(s) || !/<\/svg>$/i.test(s)) {
197
+ return 'the logo is not a single <svg>…</svg> document';
198
+ }
199
+ for (const [pattern, reason] of SVG_FORBIDDEN)
200
+ if (pattern.test(s))
201
+ return reason;
202
+ return null;
203
+ }
204
+ const BROWSER_LABELS = {
205
+ chrome: 'Google Chrome',
206
+ firefox: 'Mozilla Firefox',
207
+ edge: 'Microsoft Edge',
208
+ };
209
+ const STORE_CTA_LABELS = {
210
+ chrome: 'Add to Chrome',
211
+ firefox: 'Get for Firefox',
212
+ edge: 'Get for Edge',
213
+ };
214
+ const DEFAULT_ACCENT = '#5e5ce6';
215
+ const HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
216
+ /**
217
+ * Human-readable notes about input the page silently corrected (a link clamped
218
+ * to "#", an unsafe image dropped, a refused logo). Same rules as the builder,
219
+ * so agents and the CLI can report exactly what did not make it into the page.
220
+ */
221
+ export function landingPageWarnings(input) {
222
+ const out = [];
223
+ const links = [
224
+ ['chromeUrl', input.storeUrls.chrome],
225
+ ['firefoxUrl', input.storeUrls.firefox],
226
+ ['edgeUrl', input.storeUrls.edge],
227
+ ['privacyPolicyUrl', input.privacyPolicyUrl],
228
+ ];
229
+ for (const [field, value] of links) {
230
+ const v = str(value);
231
+ if (v && safeHref(v) === '#')
232
+ out.push(`${field} is not an http(s) URL; its link points to "#".`);
233
+ }
234
+ const support = str(input.supportUrl);
235
+ if (support && safeSupportHref(support) === '#') {
236
+ out.push('supportUrl is not an http(s) URL or a single mailto: address; its link points to "#".');
237
+ }
238
+ if (!Object.keys(STORE_CTA_LABELS).some((b) => str(input.storeUrls[b]))) {
239
+ out.push('No store URL is set, so the page has no install button.');
240
+ }
241
+ if (str(input.accentColor) && !HEX_COLOR.test(str(input.accentColor))) {
242
+ out.push(`accentColor is not a hex colour; ${DEFAULT_ACCENT} is used.`);
243
+ }
244
+ const homepage = canonicalUrl(input.homepageUrl);
245
+ if (str(input.homepageUrl) && !homepage) {
246
+ out.push('homepageUrl is not an https URL; canonical, og:url and JSON-LD url are omitted.');
247
+ }
248
+ const svg = str(input.logoSvg);
249
+ const svgProblem = svg ? landingLogoSvgProblem(svg) : null;
250
+ if (svgProblem)
251
+ out.push(`logoSvg was refused (${svgProblem}); the fallback logo is shown.`);
252
+ if (str(input.logoUrl) && !safeImageSrc(input.logoUrl)) {
253
+ out.push('logoUrl is not an https URL, relative path or raster data: image; it is omitted.');
254
+ }
255
+ const shots = Array.isArray(input.screenshots) ? input.screenshots : [];
256
+ let relativeShots = 0;
257
+ for (const [i, shot] of shots.slice(0, LANDING_LIMITS.screenshots).entries()) {
258
+ const url = str(shot?.url);
259
+ if (!safeImageSrc(url))
260
+ out.push(`screenshots[${i}].url is not an https URL or relative path; it is omitted.`);
261
+ else if (!HTTPS_URL.test(url) && !DATA_IMAGE_URL.test(url))
262
+ relativeShots++;
263
+ }
264
+ if (shots.length > LANDING_LIMITS.screenshots) {
265
+ out.push(`Only the first ${LANDING_LIMITS.screenshots} screenshots are used.`);
266
+ }
267
+ if (relativeShots > 0 && !homepage) {
268
+ out.push('Relative screenshot paths need an https homepageUrl to appear in og:image and JSON-LD.');
269
+ }
270
+ return out;
271
+ }
272
+ export function buildLandingPage(input) {
273
+ const rawName = str(input.extensionName) || 'My Extension';
274
+ const name = esc(rawName);
275
+ const tagline = esc(str(input.tagline));
276
+ const description = str(input.description);
277
+ const accent = HEX_COLOR.test(str(input.accentColor)) ? str(input.accentColor) : DEFAULT_ACCENT;
278
+ const dark = input.theme === 'dark';
279
+ const bg = dark ? '#0f1115' : '#ffffff';
280
+ const fg = dark ? '#e8eaf0' : '#1f2430';
281
+ const muted = dark ? '#9aa1b2' : '#5b6372';
282
+ const cardBg = dark ? '#171a21' : '#f6f7fa';
283
+ const border = dark ? '#262b36' : '#e5e8ef';
284
+ const homepage = canonicalUrl(input.homepageUrl);
285
+ const monogram = rawName.charAt(0).toUpperCase() || 'E';
286
+ const svg = str(input.logoSvg);
287
+ const logoSrc = svg && !landingLogoSvgProblem(svg)
288
+ ? `data:image/svg+xml,${encodeURIComponent(svg)}`
289
+ : safeImageSrc(input.logoUrl);
290
+ const logo = logoSrc
291
+ ? `<div class="logo"><img src="${logoSrc}" alt="${name} logo" width="64" height="64" /></div>`
292
+ : `<div class="logo monogram" aria-hidden="true">${esc(monogram)}</div>`;
293
+ const ctas = Object.keys(STORE_CTA_LABELS)
294
+ .filter((b) => str(input.storeUrls[b]))
295
+ .map((b) => `<a class="cta${b === 'chrome' ? ' primary' : ''}" href="${safeHref(str(input.storeUrls[b]))}" rel="noopener">${STORE_CTA_LABELS[b]}<span class="cta-sub">${esc(BROWSER_LABELS[b])}</span></a>`)
296
+ .join('\n\t\t\t');
297
+ const featureList = Array.isArray(input.features) ? input.features : [];
298
+ const features = featureList
299
+ .filter((f) => str(f?.title))
300
+ .map((f) => `<div class="feature">
301
+ <h3>${esc(str(f.title))}</h3>
302
+ <p>${esc(str(f.description))}</p>
303
+ </div>`)
304
+ .join('\n\t\t\t');
305
+ const shots = (Array.isArray(input.screenshots) ? input.screenshots : [])
306
+ .slice(0, LANDING_LIMITS.screenshots)
307
+ .map((shot, i) => ({
308
+ url: str(shot?.url),
309
+ src: safeImageSrc(shot?.url),
310
+ alt: str(shot?.alt).slice(0, LANDING_LIMITS.screenshotAlt) || `${rawName} screenshot ${i + 1}`,
311
+ }))
312
+ .filter((shot) => shot.src);
313
+ const screenshots = shots
314
+ .map((shot) => `<figure class="shot"><img src="${shot.src}" alt="${esc(shot.alt)}" loading="lazy" /></figure>`)
315
+ .join('\n\t\t\t');
316
+ const absoluteShots = shots.map((shot) => absoluteImageUrl(shot.url, homepage)).filter(Boolean);
317
+ const footerLinks = [];
318
+ if (str(input.privacyPolicyUrl)) {
319
+ footerLinks.push(`<a href="${safeHref(str(input.privacyPolicyUrl))}">Privacy policy</a>`);
320
+ }
321
+ if (str(input.supportUrl)) {
322
+ footerLinks.push(`<a href="${safeSupportHref(str(input.supportUrl))}">Support</a>`);
323
+ }
324
+ const jsonLd = jsonLdScriptTag(jsonLdFromLanding({
325
+ extensionName: rawName,
326
+ tagline: str(input.tagline),
327
+ description,
328
+ storeUrls: input.storeUrls,
329
+ features: featureList
330
+ .filter((f) => str(f?.title))
331
+ .map((f) => ({ title: str(f.title), description: str(f.description) })),
332
+ privacyPolicyUrl: str(input.privacyPolicyUrl),
333
+ homepageUrl: homepage || undefined,
334
+ screenshots: absoluteShots,
335
+ }));
336
+ const metaDescription = esc(description || str(input.tagline));
337
+ const ogImage = absoluteShots[0] ?? '';
338
+ const head = [
339
+ `<title>${name}${tagline ? ` — ${tagline}` : ''}</title>`,
340
+ `<meta name="description" content="${metaDescription}" />`,
341
+ homepage ? `<link rel="canonical" href="${esc(homepage)}" />` : '',
342
+ '<meta property="og:type" content="website" />',
343
+ `<meta property="og:title" content="${name}" />`,
344
+ metaDescription ? `<meta property="og:description" content="${metaDescription}" />` : '',
345
+ homepage ? `<meta property="og:url" content="${esc(homepage)}" />` : '',
346
+ ogImage ? `<meta property="og:image" content="${esc(ogImage)}" />` : '',
347
+ `<meta name="twitter:card" content="${ogImage ? 'summary_large_image' : 'summary'}" />`,
348
+ jsonLd,
349
+ ]
350
+ .filter(Boolean)
351
+ .join('\n');
352
+ return `<!doctype html>
353
+ <html lang="en">
354
+ <head>
355
+ <meta charset="utf-8" />
356
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
357
+ ${head}
358
+ <style>
359
+ * { box-sizing: border-box; margin: 0; padding: 0; }
360
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: ${bg}; color: ${fg}; line-height: 1.6; }
361
+ .wrap { max-width: 880px; margin: 0 auto; padding: 0 24px; }
362
+ header { padding: 88px 0 56px; text-align: center; }
363
+ .logo { width: 88px; height: 88px; margin: 0 auto 28px; border-radius: 22px; display: flex; align-items: center; justify-content: center; background: ${cardBg}; border: 1px solid ${border}; overflow: hidden; }
364
+ .logo img { width: 64px; height: 64px; object-fit: contain; }
365
+ .logo.monogram { font-size: 2.4rem; font-weight: 700; color: ${accent}; }
366
+ h1 { font-size: 2.6rem; letter-spacing: -0.02em; line-height: 1.15; }
367
+ .tagline { margin-top: 14px; font-size: 1.25rem; color: ${muted}; }
368
+ .ctas { margin-top: 36px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
369
+ .cta { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; padding: 12px 26px; border-radius: 12px; border: 1px solid ${border}; background: ${cardBg}; color: ${fg}; text-decoration: none; font-weight: 600; transition: transform 0.12s ease; }
370
+ .cta:hover { transform: translateY(-2px); }
371
+ .cta.primary { background: ${accent}; border-color: ${accent}; color: #fff; }
372
+ .cta-sub { font-size: 0.72rem; font-weight: 400; opacity: 0.75; }
373
+ .about { padding: 8px 0 24px; text-align: center; }
374
+ .about p { max-width: 640px; margin: 0 auto; color: ${muted}; }
375
+ .shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; padding: 24px 0 8px; }
376
+ .shot img { display: block; width: 100%; height: auto; border-radius: 12px; border: 1px solid ${border}; }
377
+ .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; padding: 40px 0 64px; }
378
+ .feature { background: ${cardBg}; border: 1px solid ${border}; border-radius: 14px; padding: 22px; }
379
+ .feature h3 { font-size: 1.02rem; margin-bottom: 8px; }
380
+ .feature p { font-size: 0.92rem; color: ${muted}; }
381
+ footer { border-top: 1px solid ${border}; padding: 28px 0 48px; text-align: center; font-size: 0.85rem; color: ${muted}; }
382
+ footer a { color: ${muted}; margin: 0 10px; }
383
+ </style>
384
+ </head>
385
+ <body>
386
+ <div class="wrap">
387
+ <header>
388
+ ${logo}
389
+ <h1>${name}</h1>
390
+ ${tagline ? `<p class="tagline">${tagline}</p>` : ''}
391
+ ${ctas ? `<div class="ctas">\n\t\t\t${ctas}\n\t\t\t</div>` : ''}
392
+ </header>
393
+ ${description ? `<section class="about"><p>${esc(description)}</p></section>` : ''}
394
+ ${screenshots ? `<section class="shots">\n\t\t\t${screenshots}\n\t\t</section>` : ''}
395
+ ${features ? `<section class="features">\n\t\t\t${features}\n\t\t</section>` : ''}
396
+ <footer>
397
+ ${footerLinks.join('\n\t\t\t')}
398
+ <div>© ${name}</div>
399
+ </footer>
400
+ </div>
401
+ </body>
402
+ </html>
403
+ `;
404
+ }
405
+ //# sourceMappingURL=landing-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing-page.js","sourceRoot":"","sources":["../src/landing-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * `extenshi media upload <file…> --project <id>` — put a logo or screenshot in
3
+ * the project's PUBLIC media store and print its stable URL.
4
+ *
5
+ * Same store and same BFF procedure (`agentUploadProjectMedia`, hosted.write)
6
+ * as the MCP `upload_project_media` tool; the URLs are what `page publish`
7
+ * (and MCP `publish_landing_page`) accept for hosted screenshots and logos —
8
+ * `page publish --upload-local` calls this for every local screenshot path.
9
+ *
10
+ * Client-side, before a byte leaves the machine:
11
+ * - secret-looking file names are refused (same list as evidence attachments);
12
+ * - the content is sniffed — PNG/JPEG/WebP go as-is, anything else is refused;
13
+ * - an SVG is rasterized locally to PNG with the store-asset renderer (the
14
+ * public store never serves SVG, so there is no SVG script surface at all).
15
+ * The BFF then re-checks everything, strips metadata and applies the quota.
16
+ */
17
+ export declare const MEDIA_MAX_BYTES: number;
18
+ export type MediaMime = 'image/png' | 'image/jpeg' | 'image/webp';
19
+ export declare function sniffMediaMime(bytes: Uint8Array): MediaMime | null;
20
+ export interface PreparedMedia {
21
+ file: string;
22
+ mime: MediaMime;
23
+ bytes: Buffer;
24
+ /** Set when an SVG was rasterized locally. */
25
+ rasterizedFrom?: 'svg';
26
+ }
27
+ /** Validate a local file and turn it into upload bytes. Exported for tests. */
28
+ export declare function prepareMediaFile(file: string, svgWidth?: number): Promise<PreparedMedia>;
29
+ export interface MediaUploadResult {
30
+ file: string;
31
+ url: string;
32
+ [key: string]: unknown;
33
+ }
34
+ /** Upload one prepared file. Shared by `media upload` and `page publish --upload-local`. */
35
+ export declare function uploadMediaFile(projectId: string, file: string, opts?: {
36
+ svgWidth?: number;
37
+ }): Promise<MediaUploadResult>;
38
+ export declare function runMediaUpload(files: string[], opts: {
39
+ project: string;
40
+ svgWidth?: string;
41
+ }): Promise<MediaUploadResult[]>;
42
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,eAAO,MAAM,eAAe,QAAkB,CAAA;AAE9C,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,CAAA;AAEjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAWlE;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,cAAc,CAAC,EAAE,KAAK,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA8B9F;AAID,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CACpC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAED,wBAAsB,cAAc,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAsB9B"}
package/dist/media.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `extenshi media upload <file…> --project <id>` — put a logo or screenshot in
3
+ * the project's PUBLIC media store and print its stable URL.
4
+ *
5
+ * Same store and same BFF procedure (`agentUploadProjectMedia`, hosted.write)
6
+ * as the MCP `upload_project_media` tool; the URLs are what `page publish`
7
+ * (and MCP `publish_landing_page`) accept for hosted screenshots and logos —
8
+ * `page publish --upload-local` calls this for every local screenshot path.
9
+ *
10
+ * Client-side, before a byte leaves the machine:
11
+ * - secret-looking file names are refused (same list as evidence attachments);
12
+ * - the content is sniffed — PNG/JPEG/WebP go as-is, anything else is refused;
13
+ * - an SVG is rasterized locally to PNG with the store-asset renderer (the
14
+ * public store never serves SVG, so there is no SVG script surface at all).
15
+ * The BFF then re-checks everything, strips metadata and applies the quota.
16
+ */
17
+ import { readFile, stat } from 'node:fs/promises';
18
+ import path from 'node:path';
19
+ import { z } from 'zod';
20
+ import { loadConfig } from './config.js';
21
+ import { assertNotSecretLike } from './evidence-attachments.js';
22
+ import { InvalidInputError } from './expected-error.js';
23
+ import { readIconSource } from './icon.js';
24
+ import { projectRequest } from './project.js';
25
+ import { renderSvg, sourceInfo } from './store-assets.js';
26
+ export const MEDIA_MAX_BYTES = 2 * 1024 * 1024;
27
+ export function sniffMediaMime(bytes) {
28
+ const b = bytes;
29
+ if (b.length >= 8 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47)
30
+ return 'image/png';
31
+ if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
32
+ return 'image/jpeg';
33
+ if (b.length >= 12 &&
34
+ String.fromCharCode(b[0], b[1], b[2], b[3]) === 'RIFF' &&
35
+ String.fromCharCode(b[8], b[9], b[10], b[11]) === 'WEBP')
36
+ return 'image/webp';
37
+ return null;
38
+ }
39
+ /** Validate a local file and turn it into upload bytes. Exported for tests. */
40
+ export async function prepareMediaFile(file, svgWidth) {
41
+ assertNotSecretLike(file);
42
+ const resolved = path.resolve(process.cwd(), file);
43
+ const info = await stat(resolved).catch(() => null);
44
+ if (!info?.isFile())
45
+ throw new InvalidInputError(`File not found: ${file}`);
46
+ if (info.size > MEDIA_MAX_BYTES && !/\.svg$/i.test(file))
47
+ throw new InvalidInputError(`"${file}" is ${(info.size / 1024 / 1024).toFixed(1)} MB; the limit is 2 MB per image. Re-export it smaller.`);
48
+ const bytes = await readFile(resolved);
49
+ const mime = sniffMediaMime(bytes);
50
+ if (mime)
51
+ return { file, mime, bytes };
52
+ if (/\.svg$/i.test(file)) {
53
+ // readIconSource scrubs scripts/handlers; the output is a plain PNG.
54
+ const source = readIconSource(resolved, { svgBytes: MEDIA_MAX_BYTES, pngBytes: MEDIA_MAX_BYTES });
55
+ const { width, height } = await sourceInfo(source);
56
+ if (!width || !height)
57
+ throw new InvalidInputError(`"${file}" has no size (set width/height or a viewBox)`);
58
+ const target = Math.min(4096, Math.max(16, Math.round(svgWidth || 1024)));
59
+ const raster = await renderSvg(source.svgSource, target);
60
+ const png = Buffer.from(raster.png);
61
+ if (png.length > MEDIA_MAX_BYTES)
62
+ throw new InvalidInputError(`"${file}" rasterized to ${target}px is over 2 MB — pass a smaller --svg-width`);
63
+ return { file, mime: 'image/png', bytes: png, rasterizedFrom: 'svg' };
64
+ }
65
+ throw new InvalidInputError(`"${file}" is not a PNG, JPEG or WebP image (checked by content, not by name); .svg files are rasterized to PNG.`);
66
+ }
67
+ const projectIdSchema = z.string().uuid({ message: '--project must be a project UUID' });
68
+ /** Upload one prepared file. Shared by `media upload` and `page publish --upload-local`. */
69
+ export async function uploadMediaFile(projectId, file, opts = {}) {
70
+ const prepared = await prepareMediaFile(file, opts.svgWidth);
71
+ const result = (await projectRequest(await loadConfig(), 'agentUploadProjectMedia', { projectId, mime: prepared.mime, dataBase64: prepared.bytes.toString('base64') }, true));
72
+ if (typeof result.url !== 'string' || !/^https:\/\//.test(result.url))
73
+ throw new Error('The server did not return an https URL for the upload');
74
+ return { file, ...result, url: result.url, ...(prepared.rasterizedFrom ? { rasterizedFrom: 'svg' } : {}) };
75
+ }
76
+ export async function runMediaUpload(files, opts) {
77
+ const projectId = projectIdSchema.safeParse(opts.project);
78
+ if (!projectId.success)
79
+ throw new InvalidInputError(projectId.error.issues[0]?.message ?? 'Invalid --project');
80
+ const svgWidth = opts.svgWidth ? Number(opts.svgWidth) : undefined;
81
+ if (svgWidth !== undefined && !(Number.isInteger(svgWidth) && svgWidth >= 16 && svgWidth <= 4096))
82
+ throw new InvalidInputError('--svg-width must be an integer between 16 and 4096');
83
+ // Validate every file before uploading any, so a typo does not leave half a batch public.
84
+ for (const file of files)
85
+ await prepareMediaFile(file, svgWidth);
86
+ const results = [];
87
+ for (const file of files) {
88
+ const result = await uploadMediaFile(projectId.data, file, { svgWidth });
89
+ results.push(result);
90
+ process.stderr.write(`${file} → ${result.url}${result.deduplicated ? ' (already stored)' : ''}${result.rasterizedFrom ? ' (SVG rasterized to PNG)' : ''}\n`);
91
+ }
92
+ console.log(JSON.stringify(results, null, 2));
93
+ process.stderr.write('These URLs are public. Use them as screenshots[].url or logoUrl in landing.json, then extenshi page publish.\n');
94
+ return results;
95
+ }
96
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}