visual-prompt-kit 0.1.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 (35) hide show
  1. package/README.md +254 -0
  2. package/bin/visual-prompt.js +5 -0
  3. package/docs/keyword-authoring.md +207 -0
  4. package/package.json +43 -0
  5. package/skills/image-prompt-composer/SKILL.md +40 -0
  6. package/skills/image-prompt-composer/agents/openai.yaml +4 -0
  7. package/src/cli.js +261 -0
  8. package/src/define.js +78 -0
  9. package/src/generator/export.js +20 -0
  10. package/src/generator/generateTags.js +64 -0
  11. package/src/generator/index.js +8 -0
  12. package/src/generator/locks.js +40 -0
  13. package/src/generator/pick.js +27 -0
  14. package/src/generator/seed.js +28 -0
  15. package/src/index.js +23 -0
  16. package/src/prompt/buildPromptRequest.js +63 -0
  17. package/src/prompt/index.js +7 -0
  18. package/src/prompt/serializePromptRequest.js +21 -0
  19. package/src/themes/index.js +43 -0
  20. package/src/themes/sweet-girl/dimensions/camera.js +42 -0
  21. package/src/themes/sweet-girl/dimensions/expression.js +42 -0
  22. package/src/themes/sweet-girl/dimensions/facial.js +34 -0
  23. package/src/themes/sweet-girl/dimensions/lighting.js +34 -0
  24. package/src/themes/sweet-girl/dimensions/outfit.js +44 -0
  25. package/src/themes/sweet-girl/dimensions/pose.js +66 -0
  26. package/src/themes/sweet-girl/dimensions/scene.js +102 -0
  27. package/src/themes/sweet-girl/dimensions/style.js +32 -0
  28. package/src/themes/sweet-girl/dimensions/subject.js +38 -0
  29. package/src/themes/sweet-girl/dimensions/vibe.js +34 -0
  30. package/src/themes/sweet-girl/index.js +1 -0
  31. package/src/themes/sweet-girl/manifest.js +50 -0
  32. package/src/themes/sweet-girl/presets/index.js +26 -0
  33. package/src/validate/index.js +5 -0
  34. package/src/validate/rules.js +5 -0
  35. package/src/validate/theme.js +79 -0
package/src/cli.js ADDED
@@ -0,0 +1,261 @@
1
+ const {
2
+ listThemes,
3
+ getTheme,
4
+ listDimensions,
5
+ validateTheme,
6
+ generateTags,
7
+ serializeSelection,
8
+ buildPromptRequest,
9
+ serializePromptRequest
10
+ } = require("./index");
11
+
12
+ function writeLine(stream, message = "") {
13
+ stream.write(`${message}\n`);
14
+ }
15
+
16
+ function parseKeyValue(raw, optionName) {
17
+ const separator = raw.indexOf("=");
18
+ if (separator <= 0 || separator === raw.length - 1) {
19
+ throw new Error(`Option ${optionName} expects KEY=VALUE, received "${raw}".`);
20
+ }
21
+
22
+ return {
23
+ key: raw.slice(0, separator),
24
+ value: raw.slice(separator + 1)
25
+ };
26
+ }
27
+
28
+ function ensureNextValue(argv, index, optionName) {
29
+ const value = argv[index + 1];
30
+ if (!value || value.startsWith("-")) {
31
+ throw new Error(`Option ${optionName} requires a value.`);
32
+ }
33
+ return value;
34
+ }
35
+
36
+ function parseArgv(argv) {
37
+ const args = [...argv];
38
+ const first = args[0];
39
+ const command = !first || first.startsWith("-") ? "help" : args.shift();
40
+ const options = {
41
+ theme: "sweet-girl",
42
+ format: undefined,
43
+ seed: undefined,
44
+ preset: undefined,
45
+ locks: {},
46
+ counts: {}
47
+ };
48
+
49
+ for (let index = 0; index < args.length; index += 1) {
50
+ const token = args[index];
51
+
52
+ switch (token) {
53
+ case "--theme":
54
+ case "-t": {
55
+ options.theme = ensureNextValue(args, index, token);
56
+ index += 1;
57
+ break;
58
+ }
59
+ case "--seed":
60
+ case "-s": {
61
+ options.seed = ensureNextValue(args, index, token);
62
+ index += 1;
63
+ break;
64
+ }
65
+ case "--preset":
66
+ case "-p": {
67
+ options.preset = ensureNextValue(args, index, token);
68
+ index += 1;
69
+ break;
70
+ }
71
+ case "--format":
72
+ case "-f": {
73
+ options.format = ensureNextValue(args, index, token);
74
+ index += 1;
75
+ break;
76
+ }
77
+ case "--lock":
78
+ case "-l": {
79
+ const parsed = parseKeyValue(ensureNextValue(args, index, token), token);
80
+ options.locks[parsed.key] ||= [];
81
+ options.locks[parsed.key].push(parsed.value);
82
+ index += 1;
83
+ break;
84
+ }
85
+ case "--count":
86
+ case "-c": {
87
+ const parsed = parseKeyValue(ensureNextValue(args, index, token), token);
88
+ const count = Number(parsed.value);
89
+ if (!Number.isInteger(count) || count < 1) {
90
+ throw new Error(`Option ${token} requires a positive integer count.`);
91
+ }
92
+ options.counts[parsed.key] = count;
93
+ index += 1;
94
+ break;
95
+ }
96
+ case "--help":
97
+ case "-h": {
98
+ options.help = true;
99
+ break;
100
+ }
101
+ default:
102
+ throw new Error(`Unknown option "${token}".`);
103
+ }
104
+ }
105
+
106
+ return {
107
+ command,
108
+ options
109
+ };
110
+ }
111
+
112
+ function formatList(items, format) {
113
+ if (format === "json") {
114
+ return JSON.stringify(items, null, 2);
115
+ }
116
+
117
+ if (format === "txt" || format === "text") {
118
+ return items.join("\n");
119
+ }
120
+
121
+ throw new Error(`Unsupported list format "${format}".`);
122
+ }
123
+
124
+ function printHelp(stdout) {
125
+ writeLine(stdout, "visual-prompt <command> [options]");
126
+ writeLine(stdout);
127
+ writeLine(stdout, "Commands:");
128
+ writeLine(stdout, " themes List theme ids");
129
+ writeLine(stdout, " dimensions [--theme <id>] List dimension keys for a theme");
130
+ writeLine(stdout, " presets [--theme <id>] List preset names for a theme");
131
+ writeLine(stdout, " validate [--theme <id>] Validate a theme");
132
+ writeLine(stdout, " generate [options] Generate prompt tags");
133
+ writeLine(stdout, " prompt [options] Build an LLM prompt from generated tags");
134
+ writeLine(stdout);
135
+ writeLine(stdout, "Shared options for generate / prompt:");
136
+ writeLine(stdout, " --theme, -t <id> Theme id, default sweet-girl");
137
+ writeLine(stdout, " --seed, -s <value> Stable random seed");
138
+ writeLine(stdout, " --preset, -p <name> Apply a preset");
139
+ writeLine(stdout, " --format, -f <json|txt> Output format");
140
+ writeLine(stdout, " --lock, -l <dimension=text> Repeatable lock option");
141
+ writeLine(stdout, " --count, -c <dimension=n> Override pick count");
142
+ }
143
+
144
+ function resolveFormat(command, format) {
145
+ if (format) {
146
+ return format;
147
+ }
148
+
149
+ if (command === "generate") {
150
+ return "json";
151
+ }
152
+
153
+ if (command === "prompt") {
154
+ return "txt";
155
+ }
156
+
157
+ return "txt";
158
+ }
159
+
160
+ function runCli(argv, io = {}) {
161
+ const stdout = io.stdout || process.stdout;
162
+ const stderr = io.stderr || process.stderr;
163
+
164
+ try {
165
+ const { command, options } = parseArgv(argv);
166
+ const format = resolveFormat(command, options.format);
167
+
168
+ if (options.help || command === "help") {
169
+ printHelp(stdout);
170
+ return 0;
171
+ }
172
+
173
+ if (command === "themes") {
174
+ writeLine(stdout, formatList(listThemes().map((theme) => theme.id), format));
175
+ return 0;
176
+ }
177
+
178
+ const theme = getTheme(options.theme);
179
+
180
+ if (command === "dimensions") {
181
+ writeLine(
182
+ stdout,
183
+ formatList(listDimensions(theme).map((dimension) => dimension.key), format)
184
+ );
185
+ return 0;
186
+ }
187
+
188
+ if (command === "presets") {
189
+ writeLine(stdout, formatList(Object.keys(theme.presets), format));
190
+ return 0;
191
+ }
192
+
193
+ if (command === "validate") {
194
+ const result = validateTheme(theme);
195
+ if (format === "json") {
196
+ writeLine(
197
+ stdout,
198
+ JSON.stringify(
199
+ {
200
+ theme: theme.id,
201
+ valid: result.valid,
202
+ warnings: result.warnings,
203
+ errors: result.errors
204
+ },
205
+ null,
206
+ 2
207
+ )
208
+ );
209
+ } else {
210
+ for (const warning of result.warnings) {
211
+ writeLine(stderr, `WARN: ${warning}`);
212
+ }
213
+
214
+ if (!result.valid) {
215
+ for (const error of result.errors) {
216
+ writeLine(stderr, `ERROR: ${error}`);
217
+ }
218
+ } else {
219
+ writeLine(stdout, `Theme "${theme.id}" passed validation with ${theme.dimensions.length} dimensions.`);
220
+ }
221
+ }
222
+
223
+ return result.valid ? 0 : 1;
224
+ }
225
+
226
+ if (command === "generate") {
227
+ const result = generateTags({
228
+ theme,
229
+ seed: options.seed,
230
+ preset: options.preset,
231
+ locks: options.locks,
232
+ counts: options.counts
233
+ });
234
+
235
+ writeLine(stdout, serializeSelection(result, format));
236
+ return 0;
237
+ }
238
+
239
+ if (command === "prompt") {
240
+ const request = buildPromptRequest({
241
+ theme,
242
+ seed: options.seed,
243
+ preset: options.preset,
244
+ locks: options.locks,
245
+ counts: options.counts
246
+ });
247
+
248
+ writeLine(stdout, serializePromptRequest(request, format));
249
+ return 0;
250
+ }
251
+
252
+ throw new Error(`Unknown command "${command}".`);
253
+ } catch (error) {
254
+ writeLine(stderr, error.message);
255
+ return 1;
256
+ }
257
+ }
258
+
259
+ module.exports = {
260
+ runCli
261
+ };
package/src/define.js ADDED
@@ -0,0 +1,78 @@
1
+ function normalizeItem(key, item, index) {
2
+ if (typeof item === "string") {
3
+ return {
4
+ id: `${key}-${String(index + 1).padStart(3, "0")}`,
5
+ text: item,
6
+ weight: 1
7
+ };
8
+ }
9
+
10
+ return {
11
+ id: item.id || `${key}-${String(index + 1).padStart(3, "0")}`,
12
+ text: item.text,
13
+ weight: item.weight || 1,
14
+ tags: item.tags || [],
15
+ bannedWith: item.bannedWith || []
16
+ };
17
+ }
18
+
19
+ function defineDimension(definition) {
20
+ if (!definition || !definition.key) {
21
+ throw new Error("Dimension definition requires a key.");
22
+ }
23
+
24
+ if (!Array.isArray(definition.items) || definition.items.length === 0) {
25
+ throw new Error(`Dimension "${definition.key}" must define a non-empty items array.`);
26
+ }
27
+
28
+ const items = definition.items.map((item, index) => normalizeItem(definition.key, item, index));
29
+
30
+ return Object.freeze({
31
+ key: definition.key,
32
+ zhName: definition.zhName,
33
+ description: definition.description || "",
34
+ pick: Object.freeze({
35
+ min: definition.pick?.min ?? 1,
36
+ max: definition.pick?.max ?? 1,
37
+ recommended: definition.pick?.recommended ?? definition.pick?.min ?? 1
38
+ }),
39
+ rules: Object.freeze([...(definition.rules || [])]),
40
+ minimumItems: definition.minimumItems ?? 20,
41
+ forbiddenTerms: Object.freeze([...(definition.forbiddenTerms || [])]),
42
+ items: Object.freeze(items)
43
+ });
44
+ }
45
+
46
+ function defineTheme(definition) {
47
+ if (!definition || !definition.id) {
48
+ throw new Error("Theme definition requires an id.");
49
+ }
50
+
51
+ if (!Array.isArray(definition.dimensions) || definition.dimensions.length === 0) {
52
+ throw new Error(`Theme "${definition.id}" must define dimensions.`);
53
+ }
54
+
55
+ const seen = new Set();
56
+ for (const dimension of definition.dimensions) {
57
+ if (seen.has(dimension.key)) {
58
+ throw new Error(`Duplicate dimension key "${dimension.key}" in theme "${definition.id}".`);
59
+ }
60
+ seen.add(dimension.key);
61
+ }
62
+
63
+ return Object.freeze({
64
+ id: definition.id,
65
+ version: definition.version || "0.1.0",
66
+ locale: definition.locale || "zh-CN",
67
+ target: definition.target,
68
+ description: definition.description || "",
69
+ bannedLexicon: Object.freeze([...(definition.bannedLexicon || [])]),
70
+ presets: Object.freeze({ ...(definition.presets || {}) }),
71
+ dimensions: Object.freeze([...definition.dimensions])
72
+ });
73
+ }
74
+
75
+ module.exports = {
76
+ defineDimension,
77
+ defineTheme
78
+ };
@@ -0,0 +1,20 @@
1
+ function flattenSelection(tags) {
2
+ return Object.values(tags).flat();
3
+ }
4
+
5
+ function serializeSelection(result, format = "json") {
6
+ if (format === "txt") {
7
+ return result.flat.join(",");
8
+ }
9
+
10
+ if (format === "json") {
11
+ return JSON.stringify(result, null, 2);
12
+ }
13
+
14
+ throw new Error(`Unsupported export format "${format}".`);
15
+ }
16
+
17
+ module.exports = {
18
+ flattenSelection,
19
+ serializeSelection
20
+ };
@@ -0,0 +1,64 @@
1
+ const { createSeededRandom } = require("./seed");
2
+ const { pickWeightedUnique } = require("./pick");
3
+ const { mergeLocks } = require("./locks");
4
+ const { flattenSelection } = require("./export");
5
+
6
+ function findItemsByText(dimension, texts) {
7
+ return texts.map((text) => {
8
+ const item = dimension.items.find((entry) => entry.text === text);
9
+ if (!item) {
10
+ throw new Error(`Unknown keyword "${text}" in dimension "${dimension.key}".`);
11
+ }
12
+ return item;
13
+ });
14
+ }
15
+
16
+ function generateTags(options) {
17
+ const theme = options.theme;
18
+ const seed = options.seed ?? `seed-${Date.now()}`;
19
+ const preset = options.preset ?? null;
20
+ const counts = options.counts || {};
21
+ const mergedLocks = mergeLocks(theme, preset, options.locks || {});
22
+ const rng = createSeededRandom(seed);
23
+ const tags = {};
24
+ const detailed = {};
25
+
26
+ for (const dimension of theme.dimensions) {
27
+ const lockedTexts = mergedLocks[dimension.key] || [];
28
+ const lockedItems = findItemsByText(dimension, lockedTexts);
29
+ if (lockedItems.length > dimension.pick.max) {
30
+ throw new Error(
31
+ `Locked keyword count for "${dimension.key}" exceeds max pick count ${dimension.pick.max}.`
32
+ );
33
+ }
34
+ const lockedIds = new Set(lockedItems.map((item) => item.id));
35
+ const requestedCount = counts[dimension.key] ?? dimension.pick.recommended;
36
+ const finalCount = Math.max(
37
+ dimension.pick.min,
38
+ Math.min(dimension.pick.max, Math.max(requestedCount, lockedItems.length))
39
+ );
40
+ const picks = pickWeightedUnique(
41
+ dimension.items,
42
+ finalCount - lockedItems.length,
43
+ rng,
44
+ lockedIds
45
+ );
46
+ const selected = [...lockedItems, ...picks];
47
+
48
+ tags[dimension.key] = selected.map((item) => item.text);
49
+ detailed[dimension.key] = selected;
50
+ }
51
+
52
+ return {
53
+ theme: theme.id,
54
+ seed,
55
+ preset,
56
+ tags,
57
+ detailed,
58
+ flat: flattenSelection(tags)
59
+ };
60
+ }
61
+
62
+ module.exports = {
63
+ generateTags
64
+ };
@@ -0,0 +1,8 @@
1
+ const { generateTags } = require("./generateTags");
2
+ const { flattenSelection, serializeSelection } = require("./export");
3
+
4
+ module.exports = {
5
+ generateTags,
6
+ flattenSelection,
7
+ serializeSelection
8
+ };
@@ -0,0 +1,40 @@
1
+ function dedupe(values) {
2
+ return [...new Set(values)];
3
+ }
4
+
5
+ function resolvePreset(theme, presetName) {
6
+ if (!presetName) {
7
+ return {};
8
+ }
9
+
10
+ const preset = theme.presets[presetName];
11
+ if (!preset) {
12
+ throw new Error(`Unknown preset "${presetName}" for theme "${theme.id}".`);
13
+ }
14
+
15
+ return preset;
16
+ }
17
+
18
+ function mergeLocks(theme, presetName, explicitLocks = {}) {
19
+ const presetLocks = resolvePreset(theme, presetName);
20
+ const keys = new Set([
21
+ ...Object.keys(presetLocks),
22
+ ...Object.keys(explicitLocks)
23
+ ]);
24
+
25
+ const merged = {};
26
+
27
+ for (const key of keys) {
28
+ merged[key] = dedupe([
29
+ ...(presetLocks[key] || []),
30
+ ...(explicitLocks[key] || [])
31
+ ]);
32
+ }
33
+
34
+ return merged;
35
+ }
36
+
37
+ module.exports = {
38
+ mergeLocks,
39
+ resolvePreset
40
+ };
@@ -0,0 +1,27 @@
1
+ function pickWeightedUnique(items, count, rng, excludedIds = new Set()) {
2
+ const pool = items.filter((item) => !excludedIds.has(item.id));
3
+ const selected = [];
4
+
5
+ while (selected.length < count && pool.length > 0) {
6
+ const totalWeight = pool.reduce((sum, item) => sum + (item.weight || 1), 0);
7
+ let threshold = rng() * totalWeight;
8
+ let pickedIndex = 0;
9
+
10
+ for (let index = 0; index < pool.length; index += 1) {
11
+ threshold -= pool[index].weight || 1;
12
+ if (threshold <= 0) {
13
+ pickedIndex = index;
14
+ break;
15
+ }
16
+ }
17
+
18
+ selected.push(pool[pickedIndex]);
19
+ pool.splice(pickedIndex, 1);
20
+ }
21
+
22
+ return selected;
23
+ }
24
+
25
+ module.exports = {
26
+ pickWeightedUnique
27
+ };
@@ -0,0 +1,28 @@
1
+ function hashSeed(input) {
2
+ const text = String(input);
3
+ let hash = 2166136261;
4
+
5
+ for (let index = 0; index < text.length; index += 1) {
6
+ hash ^= text.charCodeAt(index);
7
+ hash = Math.imul(hash, 16777619);
8
+ }
9
+
10
+ return hash >>> 0;
11
+ }
12
+
13
+ function createSeededRandom(seed) {
14
+ let state = hashSeed(seed);
15
+
16
+ return function next() {
17
+ state += 0x6D2B79F5;
18
+ let value = state;
19
+ value = Math.imul(value ^ (value >>> 15), value | 1);
20
+ value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
21
+ return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
22
+ };
23
+ }
24
+
25
+ module.exports = {
26
+ hashSeed,
27
+ createSeededRandom
28
+ };
package/src/index.js ADDED
@@ -0,0 +1,23 @@
1
+ const { defineDimension, defineTheme } = require("./define");
2
+ const { themes, listThemes, getTheme, listDimensions, getDimension } = require("./themes");
3
+ const sweetGirl = require("./themes/sweet-girl");
4
+ const { validateTheme } = require("./validate");
5
+ const { generateTags, flattenSelection, serializeSelection } = require("./generator");
6
+ const { buildPromptRequest, serializePromptRequest } = require("./prompt");
7
+
8
+ module.exports = {
9
+ defineDimension,
10
+ defineTheme,
11
+ themes,
12
+ sweetGirl,
13
+ listThemes,
14
+ getTheme,
15
+ listDimensions,
16
+ getDimension,
17
+ validateTheme,
18
+ generateTags,
19
+ flattenSelection,
20
+ serializeSelection,
21
+ buildPromptRequest,
22
+ serializePromptRequest
23
+ };
@@ -0,0 +1,63 @@
1
+ const { generateTags } = require("../generator");
2
+
3
+ const DEFAULT_SYSTEM_PROMPT = [
4
+ "你是通用生图模型的人像提示词编辑器。",
5
+ "把离散视觉标签重组为一段自然完整的中文生图提示词,适用于 Google、豆包等常见生图模型。",
6
+ "你必须围绕甜美少女写真,吸收全部标签并重组为具体、连贯、有画面感的描述。",
7
+ "禁止罗列标签,禁止按原顺序复述,禁止使用“采用、呈现、营造”等机械拼接句。"
8
+ ].join("");
9
+
10
+ function formatDimensionTags(tags) {
11
+ return Object.entries(tags)
12
+ .map(([key, values]) => `- ${key}: ${values.join(" / ")}`)
13
+ .join("\n");
14
+ }
15
+
16
+ function buildPromptRequest(options) {
17
+ const theme = options.theme;
18
+ const selection =
19
+ options.selection ||
20
+ generateTags({
21
+ theme,
22
+ seed: options.seed,
23
+ preset: options.preset,
24
+ locks: options.locks,
25
+ counts: options.counts
26
+ });
27
+
28
+ const system = options.systemPrompt || DEFAULT_SYSTEM_PROMPT;
29
+ const user = [
30
+ "请把下面这些视觉标签无损重组为最终生图提示词。",
31
+ "",
32
+ "要求:",
33
+ "1. 必须吸收全部标签,但允许等价改写,不要逐词照抄。",
34
+ "2. 先建立主体、场景与核心氛围,再自然融入姿态、神情、光线、镜头和质感,形成完整画面。",
35
+ "3. 不要按输入顺序机械串联,不要写成标签清单扩写;如果读起来像顺序拼接,视为失败。",
36
+ "4. 可以补足少量合理细节,如发丝、肤感、空气感、空间层次,但不得新增冲突设定。",
37
+ "5. 语言应简洁、具体、可视化,适合常见通用生图模型理解。",
38
+ "6. 只输出一段中文提示词,建议 80 到 140 字,不要解释,不要分点,不要标题。",
39
+ "",
40
+ "按维度整理的视觉标签:",
41
+ formatDimensionTags(selection.tags),
42
+ "",
43
+ "请直接输出最终提示词。"
44
+ ].join("\n");
45
+
46
+ return {
47
+ theme: theme.id,
48
+ seed: selection.seed,
49
+ preset: selection.preset,
50
+ system,
51
+ user,
52
+ tags: selection.tags,
53
+ flat: selection.flat,
54
+ messages: [
55
+ { role: "system", content: system },
56
+ { role: "user", content: user }
57
+ ]
58
+ };
59
+ }
60
+
61
+ module.exports = {
62
+ buildPromptRequest
63
+ };
@@ -0,0 +1,7 @@
1
+ const { buildPromptRequest } = require("./buildPromptRequest");
2
+ const { serializePromptRequest } = require("./serializePromptRequest");
3
+
4
+ module.exports = {
5
+ buildPromptRequest,
6
+ serializePromptRequest
7
+ };
@@ -0,0 +1,21 @@
1
+ function serializePromptRequest(request, format = "txt") {
2
+ if (format === "json") {
3
+ return JSON.stringify(request, null, 2);
4
+ }
5
+
6
+ if (format === "txt" || format === "text") {
7
+ return [
8
+ "[System Prompt]",
9
+ request.system,
10
+ "",
11
+ "[User Prompt]",
12
+ request.user
13
+ ].join("\n");
14
+ }
15
+
16
+ throw new Error(`Unsupported prompt format "${format}".`);
17
+ }
18
+
19
+ module.exports = {
20
+ serializePromptRequest
21
+ };
@@ -0,0 +1,43 @@
1
+ const sweetGirl = require("./sweet-girl");
2
+
3
+ const themes = {
4
+ [sweetGirl.id]: sweetGirl
5
+ };
6
+
7
+ function listThemes() {
8
+ return Object.values(themes);
9
+ }
10
+
11
+ function getTheme(themeId) {
12
+ const theme = themes[themeId];
13
+
14
+ if (!theme) {
15
+ throw new Error(`Unknown theme "${themeId}".`);
16
+ }
17
+
18
+ return theme;
19
+ }
20
+
21
+ function listDimensions(themeOrId) {
22
+ const theme = typeof themeOrId === "string" ? getTheme(themeOrId) : themeOrId;
23
+ return [...theme.dimensions];
24
+ }
25
+
26
+ function getDimension(themeOrId, dimensionKey) {
27
+ const theme = typeof themeOrId === "string" ? getTheme(themeOrId) : themeOrId;
28
+ const dimension = theme.dimensions.find((entry) => entry.key === dimensionKey);
29
+
30
+ if (!dimension) {
31
+ throw new Error(`Unknown dimension "${dimensionKey}" in theme "${theme.id}".`);
32
+ }
33
+
34
+ return dimension;
35
+ }
36
+
37
+ module.exports = {
38
+ themes,
39
+ listThemes,
40
+ getTheme,
41
+ listDimensions,
42
+ getDimension
43
+ };