@kernocal/viteshot 0.1.1 → 0.1.2

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/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { resolve } from "node:path";
2
3
  import { styleText } from "node:util";
3
4
 
4
5
  //#region src/cli.ts
@@ -11,7 +12,7 @@ function getFlag(name) {
11
12
  }
12
13
  switch (command) {
13
14
  case "dev": {
14
- const { DEV_HELP_MESSAGE } = await import("./help-messages-B_-FA6OE.mjs");
15
+ const { DEV_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
15
16
  if (showHelp) {
16
17
  console.log(DEV_HELP_MESSAGE);
17
18
  process.exit(0);
@@ -23,7 +24,7 @@ switch (command) {
23
24
  break;
24
25
  }
25
26
  case "export": {
26
- const { EXPORT_HELP_MESSAGE } = await import("./help-messages-B_-FA6OE.mjs");
27
+ const { EXPORT_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
27
28
  if (showHelp) {
28
29
  console.log(EXPORT_HELP_MESSAGE);
29
30
  process.exit(0);
@@ -37,7 +38,7 @@ switch (command) {
37
38
  process.exit(0);
38
39
  }
39
40
  case "popup": {
40
- const { POPUP_HELP_MESSAGE } = await import("./help-messages-B_-FA6OE.mjs");
41
+ const { POPUP_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
41
42
  if (showHelp) {
42
43
  console.log(POPUP_HELP_MESSAGE);
43
44
  process.exit(0);
@@ -52,18 +53,32 @@ switch (command) {
52
53
  });
53
54
  process.exit(0);
54
55
  }
56
+ case "suggest": {
57
+ const { SUGGEST_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
58
+ if (showHelp) {
59
+ console.log(SUGGEST_HELP_MESSAGE);
60
+ process.exit(0);
61
+ }
62
+ const dir = args[0] ?? DEFAULT_DIR;
63
+ const { suggest } = await import("./suggest-D4mx8DKj.mjs");
64
+ await suggest({
65
+ dataSrc: resolve(dir, "data"),
66
+ exportsDir: resolve(dir, "exports")
67
+ });
68
+ process.exit(0);
69
+ }
55
70
  case "init": {
56
- const { INIT_HELP_MESSAGE } = await import("./help-messages-B_-FA6OE.mjs");
71
+ const { INIT_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
57
72
  if (showHelp) {
58
73
  console.log(INIT_HELP_MESSAGE);
59
74
  process.exit(0);
60
75
  }
61
- const { init } = await import("./init-BUrgR61N.mjs");
76
+ const { init } = await import("./init-EKAK797h.mjs");
62
77
  await init(args[0] ?? DEFAULT_DIR);
63
78
  process.exit(0);
64
79
  }
65
80
  default: {
66
- const { GENERAL_HELP_MESSAGE } = await import("./help-messages-B_-FA6OE.mjs");
81
+ const { GENERAL_HELP_MESSAGE } = await import("./help-messages-D98KkJBb.mjs");
67
82
  if (showHelp) {
68
83
  console.log(GENERAL_HELP_MESSAGE);
69
84
  process.exit(0);
@@ -1,7 +1,7 @@
1
1
  import { styleText } from "node:util";
2
2
 
3
3
  //#region package.json
4
- var version = "0.1.1";
4
+ var version = "0.1.2";
5
5
 
6
6
  //#endregion
7
7
  //#region src/core/help-messages.ts
@@ -15,6 +15,7 @@ ${styleText("bold", "Commands")}
15
15
  ${styleText(["bold", "magenta"], "export")} ${styleText("dim", "store")} Export screenshots
16
16
  ${styleText(["bold", "magenta"], "init")} ${styleText("dim", "store")} Initialize viteshot in your project
17
17
  ${styleText(["bold", "magenta"], "popup")} ${styleText("dim", "--extension-path")} Screenshot a browser extension popup
18
+ ${styleText(["bold", "magenta"], "suggest")} ${styleText("dim", "store")} Generate store listing suggestions via AI
18
19
 
19
20
  ${styleText("dim", "<command>")} ${styleText(["bold", "blue"], "--help")} Print help text for a command
20
21
  `;
@@ -56,6 +57,21 @@ ${styleText("bold", "Examples:")}
56
57
  ${styleText(["bold", "green"], "viteshot popup")} --extension-path=.output/chrome-mv3
57
58
  ${styleText(["bold", "green"], "viteshot popup")} --extension-path=.output/chrome-mv3 --output=screenshots
58
59
  `;
60
+ const SUGGEST_HELP_MESSAGE = `${styleText("bold", "Usage:")} ${styleText(["bold", "green"], "viteshot suggest")} ${styleText("bold", "[<folder>]")}
61
+ Generate AI suggestions for store listing fields (permission justifications, single purpose, search terms).
62
+
63
+ ${styleText("bold", "Args:")}
64
+
65
+ ${FOLDER_ARG}
66
+
67
+ ${styleText("bold", "Environment:")}
68
+
69
+ ${styleText(["bold", "blue"], "GEMINI_API_KEY")} Google AI API key ${styleText("red", "(required)")}
70
+
71
+ ${styleText("bold", "Examples:")}
72
+ ${styleText(["bold", "green"], "viteshot suggest")}
73
+ ${styleText(["bold", "green"], "viteshot suggest")} store
74
+ `;
59
75
  const INIT_HELP_MESSAGE = `
60
76
  ${styleText("bold", "Usage:")} ${styleText(["bold", "green"], "viteshot init")} ${styleText("bold", "[<folder>]")}
61
77
  Initialize viteshot in your project.
@@ -70,4 +86,4 @@ ${styleText("bold", "Examples:")}
70
86
  `;
71
87
 
72
88
  //#endregion
73
- export { DEV_HELP_MESSAGE, EXPORT_HELP_MESSAGE, GENERAL_HELP_MESSAGE, INIT_HELP_MESSAGE, POPUP_HELP_MESSAGE };
89
+ export { DEV_HELP_MESSAGE, EXPORT_HELP_MESSAGE, GENERAL_HELP_MESSAGE, INIT_HELP_MESSAGE, POPUP_HELP_MESSAGE, SUGGEST_HELP_MESSAGE };
package/dist/index.d.mts CHANGED
@@ -109,4 +109,13 @@ type Screenshot = {
109
109
  declare function getScreenshots(designsDir: string): Promise<Screenshot[]>;
110
110
  declare function logInvalidDesignFiles(designsDir: string): Promise<void>;
111
111
  //#endregion
112
- export { CaptureOptions, CaptureResult, CapturedImages, InlineConfig, PopupScreenshotOptions, PuppeteerOptions, ResolvedConfig, Screenshot, UserConfig, captureExtensionScreenshots, captureLocale, createServer, defineConfig, exportPopupScreenshot, exportScreenshots, getScreenshots, logInvalidDesignFiles, resolveConfig };
112
+ //#region src/core/suggest.d.ts
113
+ interface SuggestOptions {
114
+ /** Path to the store data module (resolved by jiti). @default "store/data" */
115
+ dataSrc?: string;
116
+ /** Directory to write suggested-data.json. @default "store/exports" */
117
+ exportsDir?: string;
118
+ }
119
+ declare function suggest(options?: SuggestOptions): Promise<void>;
120
+ //#endregion
121
+ export { CaptureOptions, CaptureResult, CapturedImages, InlineConfig, PopupScreenshotOptions, PuppeteerOptions, ResolvedConfig, Screenshot, SuggestOptions, UserConfig, captureExtensionScreenshots, captureLocale, createServer, defineConfig, exportPopupScreenshot, exportScreenshots, getScreenshots, logInvalidDesignFiles, resolveConfig, suggest };
package/dist/index.mjs CHANGED
@@ -3,5 +3,6 @@ import { i as logInvalidDesignFiles, n as captureLocale, r as getScreenshots, t
3
3
  import { t as createServer } from "./create-server-CpPbF7ms.mjs";
4
4
  import { t as exportScreenshots } from "./export-screenshots-B6IGCV4J.mjs";
5
5
  import { t as exportPopupScreenshot } from "./export-popup-screenshot-UhxUtKCP.mjs";
6
+ import { t as suggest } from "./suggest-C2hZd_gx.mjs";
6
7
 
7
- export { captureExtensionScreenshots, captureLocale, createServer, defineConfig, exportPopupScreenshot, exportScreenshots, getScreenshots, logInvalidDesignFiles, resolveConfig };
8
+ export { captureExtensionScreenshots, captureLocale, createServer, defineConfig, exportPopupScreenshot, exportScreenshots, getScreenshots, logInvalidDesignFiles, resolveConfig, suggest };
@@ -56,6 +56,39 @@ body {
56
56
  <p>Screenshot design here</p>
57
57
  <p>{{translated}}</p>
58
58
  </div>
59
+ `,
60
+ "data.ts": `import type { StoreDetails } from '@kernocal/validate-store'
61
+
62
+ export const storeData = {
63
+ common: {
64
+ privacyPolicyUrl: '',
65
+ screenshots: [{ path: 'exports/screenshot-1.png', width: 1280, height: 800 }],
66
+ smallPromoTile: { path: 'exports/small-promo.png', width: 440, height: 280 },
67
+ largePromoTile: null,
68
+ promoVideoUrl: null,
69
+ websiteUrl: '',
70
+ matureContent: false,
71
+ locales: {
72
+ en: { description: '' },
73
+ },
74
+ },
75
+ chrome: {
76
+ category: 'Tools',
77
+ icon: { path: 'exports/icon-128.png', width: 128, height: 128 },
78
+ officialUrl: null,
79
+ supportUrl: '',
80
+ singlePurpose: '',
81
+ permissionJustifications: {},
82
+ remoteCode: false,
83
+ },
84
+ edge: {
85
+ category: 'Productivity',
86
+ icon: { path: 'exports/icon-300.png', width: 300, height: 300 },
87
+ searchTerms: [],
88
+ supportContact: '',
89
+ collectsPersonalData: false,
90
+ },
91
+ } satisfies StoreDetails
59
92
  `
60
93
  };
61
94
 
@@ -0,0 +1,105 @@
1
+ import { resolve } from "node:path";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { createJiti } from "jiti";
4
+
5
+ //#region src/core/suggest.ts
6
+ async function generatePermissionJustifications(ai, model, permissions, description) {
7
+ const response = await ai.models.generateContent({
8
+ model,
9
+ contents: `You are writing Chrome Web Store permission justifications for a browser extension.
10
+
11
+ Extension description: ${description}
12
+ Permissions requested: ${permissions.join(", ")}
13
+
14
+ For each permission, write a clear, concise justification (1 sentence) explaining
15
+ why the extension needs this permission. Chrome reviewers will read these.
16
+
17
+ Rules:
18
+ - Be specific about what the permission is used for
19
+ - Reference the extension's actual functionality
20
+ - Keep each justification under 200 characters
21
+ - Do not use marketing language
22
+ - Do not start the justification with "to ".
23
+ - Do not start with The x permission or the permission x.
24
+ - Do not end the justification with a period.
25
+ - Do not start the justification with captilisation, first word can be lowercase.
26
+
27
+ Respond as JSON: { "permissionName": "justification text" }`,
28
+ config: {
29
+ responseMimeType: "application/json",
30
+ responseSchema: {
31
+ type: "object",
32
+ properties: Object.fromEntries(permissions.map((p) => [p, { type: "string" }])),
33
+ required: permissions
34
+ }
35
+ }
36
+ });
37
+ return JSON.parse(response.text ?? "{}");
38
+ }
39
+ async function generateSinglePurpose(ai, model, description) {
40
+ return (await ai.models.generateContent({
41
+ model,
42
+ contents: `You are writing a Chrome Web Store "single purpose" description for a browser extension.
43
+
44
+ Extension description: ${description}
45
+
46
+ Write a concise single-purpose statement (1 sentence) that describes what this extension does.
47
+ Chrome requires this to be a clear, single purpose. Keep it under 132 characters.`
48
+ })).text ?? "";
49
+ }
50
+ async function generateSearchTerms(ai, model, description) {
51
+ const response = await ai.models.generateContent({
52
+ model,
53
+ contents: `Generate search terms for a Microsoft Edge Add-ons listing.
54
+
55
+ Extension description: ${description}
56
+
57
+ Rules:
58
+ - Maximum 7 terms
59
+ - Each term max 30 characters
60
+ - Total max 21 words across all terms
61
+ - Terms should be what users would search to find this extension
62
+ - Include both specific and broad terms
63
+
64
+ Respond as JSON array of strings.`,
65
+ config: {
66
+ responseMimeType: "application/json",
67
+ responseSchema: {
68
+ type: "array",
69
+ items: { type: "string" }
70
+ }
71
+ }
72
+ });
73
+ return JSON.parse(response.text ?? "[]");
74
+ }
75
+ async function suggest(options) {
76
+ const dataSrc = resolve(options?.dataSrc ?? "store/data");
77
+ const exportsDir = resolve(options?.exportsDir ?? "store/exports");
78
+ const { storeData } = await createJiti(import.meta.url).import(dataSrc);
79
+ const { GoogleGenAI } = await import("@google/genai");
80
+ const apiKey = process.env.GEMINI_API_KEY;
81
+ if (!apiKey) {
82
+ console.error("GEMINI_API_KEY env var is required");
83
+ process.exit(1);
84
+ }
85
+ const ai = new GoogleGenAI({ apiKey });
86
+ const model = "gemini-2.5-flash";
87
+ const permissions = Object.keys(storeData.chrome.permissionJustifications);
88
+ const { description } = storeData.common.locales.en;
89
+ const pause = () => new Promise((res) => setTimeout(res, 1e3));
90
+ const permissionJustifications = await generatePermissionJustifications(ai, model, permissions, description);
91
+ await pause();
92
+ const singlePurpose = await generateSinglePurpose(ai, model, description);
93
+ await pause();
94
+ const searchTerms = await generateSearchTerms(ai, model, description);
95
+ const resultJson = JSON.stringify({
96
+ permissionJustifications,
97
+ singlePurpose,
98
+ searchTerms
99
+ }, null, 4);
100
+ await mkdir(exportsDir, { recursive: true });
101
+ await writeFile(resolve(exportsDir, "suggested-data.json"), resultJson, "utf-8");
102
+ }
103
+
104
+ //#endregion
105
+ export { suggest as t };
@@ -0,0 +1,3 @@
1
+ import { t as suggest } from "./suggest-C2hZd_gx.mjs";
2
+
3
+ export { suggest };
package/package.json CHANGED
@@ -1,20 +1,29 @@
1
1
  {
2
2
  "name": "@kernocal/viteshot",
3
3
  "description": "fork adding weird popup sidebar options screenshots. original by @aklinker1",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "async-mutex": "^0.5.0",
8
+ "jiti": "^2.4.2",
8
9
  "natural-compare-lite": "^1.4.0",
9
10
  "p-map": "^7.0.4",
10
11
  "p-queue": "^9.1.0",
11
12
  "puppeteer-core": "^24.37.5"
12
13
  },
13
14
  "peerDependencies": {
15
+ "@google/genai": ">=1",
16
+ "@kernocal/validate-store": ">=0.1.8",
14
17
  "svelte": ">=5",
15
18
  "vite": ">=5"
16
19
  },
17
20
  "peerDependenciesMeta": {
21
+ "@google/genai": {
22
+ "optional": true
23
+ },
24
+ "@kernocal/validate-store": {
25
+ "optional": true
26
+ },
18
27
  "svelte": {
19
28
  "optional": true
20
29
  }