@bitmagic/cli 0.1.83-dev.1 → 0.1.83-dev.3

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 (206) hide show
  1. package/README.md +69 -0
  2. package/assets/skills/publishing-to-steam/SKILL.md +218 -0
  3. package/dist/assets/vxl-summary.d.ts +6 -20
  4. package/dist/assets/vxl-summary.js +9 -100
  5. package/dist/assets/vxl-summary.js.map +1 -1
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +2 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/check.js +5 -0
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/forge.js +1 -1
  12. package/dist/commands/forge.js.map +1 -1
  13. package/dist/commands/ios.js +88 -136
  14. package/dist/commands/ios.js.map +1 -1
  15. package/dist/commands/steam-build.d.ts +67 -0
  16. package/dist/commands/steam-build.js +286 -0
  17. package/dist/commands/steam-build.js.map +1 -0
  18. package/dist/commands/steam-portal.d.ts +1 -0
  19. package/dist/commands/steam-portal.js +319 -0
  20. package/dist/commands/steam-portal.js.map +1 -0
  21. package/dist/commands/steam-upload.d.ts +37 -0
  22. package/dist/commands/steam-upload.js +174 -0
  23. package/dist/commands/steam-upload.js.map +1 -0
  24. package/dist/commands/steam.d.ts +1 -0
  25. package/dist/commands/steam.js +504 -0
  26. package/dist/commands/steam.js.map +1 -0
  27. package/dist/commands/verify.js +6 -0
  28. package/dist/commands/verify.js.map +1 -1
  29. package/dist/editor/asset-viewer.js +5 -4
  30. package/dist/editor/asset-viewer.js.map +1 -1
  31. package/dist/editor/server.js +1 -1
  32. package/dist/editor/server.js.map +1 -1
  33. package/dist/editor/shell-page.js +3 -3
  34. package/dist/forge/browser-host.d.ts +1 -1
  35. package/dist/forge/browser-host.js +1 -1
  36. package/dist/forge/browser-host.js.map +1 -1
  37. package/dist/ios/native-overrides.d.ts +27 -0
  38. package/dist/ios/native-overrides.js +87 -0
  39. package/dist/ios/native-overrides.js.map +1 -0
  40. package/dist/ios/record-validate.d.ts +1 -33
  41. package/dist/ios/record-validate.js +2 -100
  42. package/dist/ios/record-validate.js.map +1 -1
  43. package/dist/ios/store-bridge.d.ts +26 -0
  44. package/dist/ios/store-bridge.js +224 -0
  45. package/dist/ios/store-bridge.js.map +1 -0
  46. package/dist/ios/storekit-config.d.ts +15 -0
  47. package/dist/ios/storekit-config.js +106 -0
  48. package/dist/ios/storekit-config.js.map +1 -0
  49. package/dist/ios/xcode-template.d.ts +12 -3
  50. package/dist/ios/xcode-template.js +158 -27
  51. package/dist/ios/xcode-template.js.map +1 -1
  52. package/dist/package/collect-urls.d.ts +1 -1
  53. package/dist/package/collect-urls.js +10 -2
  54. package/dist/package/collect-urls.js.map +1 -1
  55. package/dist/package/offline-payload.d.ts +35 -0
  56. package/dist/package/offline-payload.js +142 -0
  57. package/dist/package/offline-payload.js.map +1 -0
  58. package/dist/package/packaged-check.d.ts +39 -0
  59. package/dist/package/packaged-check.js +104 -0
  60. package/dist/package/packaged-check.js.map +1 -0
  61. package/dist/package/transform.d.ts +4 -4
  62. package/dist/package/transform.js +3 -3
  63. package/dist/project/published-three.d.ts +14 -0
  64. package/dist/project/published-three.js +91 -0
  65. package/dist/project/published-three.js.map +1 -0
  66. package/dist/publish/bundle.js +6 -0
  67. package/dist/publish/bundle.js.map +1 -1
  68. package/dist/scaffold/project-files.js +7 -4
  69. package/dist/scaffold/project-files.js.map +1 -1
  70. package/dist/scaffold/project.js +2 -0
  71. package/dist/scaffold/project.js.map +1 -1
  72. package/dist/scaffold/skill-files.js +3 -1
  73. package/dist/scaffold/skill-files.js.map +1 -1
  74. package/dist/scaffold/steam-skill-files.d.ts +2 -0
  75. package/dist/scaffold/steam-skill-files.js +7 -0
  76. package/dist/scaffold/steam-skill-files.js.map +1 -0
  77. package/dist/steam/art-generate.d.ts +55 -0
  78. package/dist/steam/art-generate.js +174 -0
  79. package/dist/steam/art-generate.js.map +1 -0
  80. package/dist/steam/art-render.d.ts +25 -0
  81. package/dist/steam/art-render.js +157 -0
  82. package/dist/steam/art-render.js.map +1 -0
  83. package/dist/steam/art-use.d.ts +20 -0
  84. package/dist/steam/art-use.js +84 -0
  85. package/dist/steam/art-use.js.map +1 -0
  86. package/dist/steam/art.d.ts +98 -0
  87. package/dist/steam/art.js +243 -0
  88. package/dist/steam/art.js.map +1 -0
  89. package/dist/steam/check.d.ts +21 -0
  90. package/dist/steam/check.js +113 -0
  91. package/dist/steam/check.js.map +1 -0
  92. package/dist/steam/config.d.ts +57 -0
  93. package/dist/steam/config.js +145 -0
  94. package/dist/steam/config.js.map +1 -0
  95. package/dist/steam/electron-cache.d.ts +11 -0
  96. package/dist/steam/electron-cache.js +105 -0
  97. package/dist/steam/electron-cache.js.map +1 -0
  98. package/dist/steam/fuses.d.ts +16 -0
  99. package/dist/steam/fuses.js +75 -0
  100. package/dist/steam/fuses.js.map +1 -0
  101. package/dist/steam/icon-files.d.ts +16 -0
  102. package/dist/steam/icon-files.js +70 -0
  103. package/dist/steam/icon-files.js.map +1 -0
  104. package/dist/steam/image-info.d.ts +16 -0
  105. package/dist/steam/image-info.js +89 -0
  106. package/dist/steam/image-info.js.map +1 -0
  107. package/dist/steam/launch-check.d.ts +12 -0
  108. package/dist/steam/launch-check.js +101 -0
  109. package/dist/steam/launch-check.js.map +1 -0
  110. package/dist/steam/media.d.ts +48 -0
  111. package/dist/steam/media.js +155 -0
  112. package/dist/steam/media.js.map +1 -0
  113. package/dist/steam/package-macos.d.ts +52 -0
  114. package/dist/steam/package-macos.js +268 -0
  115. package/dist/steam/package-macos.js.map +1 -0
  116. package/dist/steam/package-windows.d.ts +23 -0
  117. package/dist/steam/package-windows.js +83 -0
  118. package/dist/steam/package-windows.js.map +1 -0
  119. package/dist/steam/paths.d.ts +35 -0
  120. package/dist/steam/paths.js +44 -0
  121. package/dist/steam/paths.js.map +1 -0
  122. package/dist/steam/playtest-shots.d.ts +18 -0
  123. package/dist/steam/playtest-shots.js +83 -0
  124. package/dist/steam/playtest-shots.js.map +1 -0
  125. package/dist/steam/portal-recipes.d.ts +25 -0
  126. package/dist/steam/portal-recipes.js +61 -0
  127. package/dist/steam/portal-recipes.js.map +1 -0
  128. package/dist/steam/portal.d.ts +58 -0
  129. package/dist/steam/portal.js +340 -0
  130. package/dist/steam/portal.js.map +1 -0
  131. package/dist/steam/raster.d.ts +58 -0
  132. package/dist/steam/raster.js +285 -0
  133. package/dist/steam/raster.js.map +1 -0
  134. package/dist/steam/shell-files.d.ts +17 -0
  135. package/dist/steam/shell-files.js +268 -0
  136. package/dist/steam/shell-files.js.map +1 -0
  137. package/dist/steam/specs.d.ts +61 -0
  138. package/dist/steam/specs.js +103 -0
  139. package/dist/steam/specs.js.map +1 -0
  140. package/dist/steam/steampipe.d.ts +46 -0
  141. package/dist/steam/steampipe.js +170 -0
  142. package/dist/steam/steampipe.js.map +1 -0
  143. package/dist/steam/store-copy.d.ts +88 -0
  144. package/dist/steam/store-copy.js +214 -0
  145. package/dist/steam/store-copy.js.map +1 -0
  146. package/dist/steam/tags.d.ts +9 -0
  147. package/dist/steam/tags.js +52 -0
  148. package/dist/steam/tags.js.map +1 -0
  149. package/dist/steam/trailer.d.ts +34 -0
  150. package/dist/steam/trailer.js +141 -0
  151. package/dist/steam/trailer.js.map +1 -0
  152. package/dist/theme/kernel.d.ts +14 -0
  153. package/dist/theme/kernel.js +15 -0
  154. package/dist/theme/kernel.js.map +1 -0
  155. package/dist/theme/theme-io.js +11 -16
  156. package/dist/theme/theme-io.js.map +1 -1
  157. package/dist/vendor/hud-theme/catalog.js +71 -0
  158. package/dist/vendor/hud-theme/engine/hud/ThemeTokens.js +115 -0
  159. package/dist/{theme/theme-color-math.js → vendor/hud-theme/engine/hud/colorMath.js} +13 -14
  160. package/dist/vendor/hud-theme/engine/hud/decorations.js +179 -0
  161. package/dist/vendor/hud-theme/engine/hud/fonts.js +147 -0
  162. package/dist/vendor/hud-theme/engine/hud/presets.js +80 -0
  163. package/dist/vendor/hud-theme/engine/hud/resolveWorldTheme.js +66 -0
  164. package/dist/vendor/hud-theme/engine/hud/themes/bitmagic.js +29 -0
  165. package/dist/vendor/hud-theme/engine/hud/themes/nexus.js +69 -0
  166. package/dist/vendor/hud-theme/engine/hud/themes/racing.js +57 -0
  167. package/dist/vendor/hud-theme/engine/hud/themes/rift-raider.js +63 -0
  168. package/dist/vendor/hud-theme/engine/hud/themes/simulator.js +55 -0
  169. package/dist/vendor/hud-theme/engine/hud/themes/village-keep.js +108 -0
  170. package/dist/vendor/hud-theme/engine/hud/validateTheme.js +535 -0
  171. package/dist/vendor/hud-theme/index.js +26 -0
  172. package/dist/{theme/theme-merge.js → vendor/hud-theme/merge.js} +0 -1
  173. package/dist/{theme/theme-report.js → vendor/hud-theme/report.js} +8 -9
  174. package/dist/{theme/theme-stored.js → vendor/hud-theme/stored.js} +6 -9
  175. package/dist/{theme/theme-validate.js → vendor/hud-theme/validate-strict.js} +102 -164
  176. package/dist/verify/browser.d.ts +11 -0
  177. package/dist/verify/browser.js +222 -155
  178. package/dist/verify/browser.js.map +1 -1
  179. package/dist/verify/categories.d.ts +1 -1
  180. package/dist/verify/categories.js +1 -0
  181. package/dist/verify/categories.js.map +1 -1
  182. package/dist/verify/classify.d.ts +14 -2
  183. package/dist/verify/classify.js +23 -4
  184. package/dist/verify/classify.js.map +1 -1
  185. package/dist/verify/hud-theme.d.ts +19 -14
  186. package/dist/verify/hud-theme.js +23 -17
  187. package/dist/verify/hud-theme.js.map +1 -1
  188. package/dist/verify/mobile-parity.d.ts +1 -1
  189. package/dist/verify/mobile-parity.js +1 -1
  190. package/dist/verify/start.d.ts +3 -2
  191. package/dist/verify/start.js +1 -1
  192. package/dist/verify/start.js.map +1 -1
  193. package/package.json +8 -6
  194. package/dist/theme/hud-theme-catalog.d.ts +0 -235
  195. package/dist/theme/hud-theme-catalog.js +0 -389
  196. package/dist/theme/hud-theme-catalog.js.map +0 -1
  197. package/dist/theme/theme-color-math.d.ts +0 -21
  198. package/dist/theme/theme-color-math.js.map +0 -1
  199. package/dist/theme/theme-merge.d.ts +0 -36
  200. package/dist/theme/theme-merge.js.map +0 -1
  201. package/dist/theme/theme-report.d.ts +0 -28
  202. package/dist/theme/theme-report.js.map +0 -1
  203. package/dist/theme/theme-stored.d.ts +0 -21
  204. package/dist/theme/theme-stored.js.map +0 -1
  205. package/dist/theme/theme-validate.d.ts +0 -41
  206. package/dist/theme/theme-validate.js.map +0 -1
@@ -0,0 +1,55 @@
1
+ import { styleLead } from '@bitmagic/asset-core';
2
+ import { type GenerateRunDeps } from '../commands/generate.js';
3
+ import type { SteamPaths } from './paths.js';
4
+ export type ArtKind = 'wide' | 'tall' | 'hero' | 'logo' | 'icon';
5
+ export declare const ART_KINDS: readonly ArtKind[];
6
+ /**
7
+ * Generation sizes: the Asset Forger takes at most 2048 px on either side, in multiples of 16.
8
+ * Every Steam asset but one fits inside that at 1:1 or better; the library hero (3840x1240) is
9
+ * drawn from a 2048-wide 3:1 picture, which Steam's own half-size copy (1920x620, the one most
10
+ * screens show) uses at native resolution.
11
+ */
12
+ export declare const KIND_SIZES: Record<ArtKind, {
13
+ width: number;
14
+ height: number;
15
+ }>;
16
+ /** The flat colour the logo is drawn on, so `steam art use --as logo` can key it out. */
17
+ export declare const LOGO_KEY_COLORS: Record<string, string>;
18
+ export declare function buildArtPrompt(options: {
19
+ kind: ArtKind;
20
+ designText: string;
21
+ artStyle: Parameters<typeof styleLead>[0];
22
+ gameName: string;
23
+ /** The agent's own description — replaces the design excerpt, never the framing rules. */
24
+ prompt?: string;
25
+ keyColorName?: string;
26
+ }): string;
27
+ export interface CandidateRecord {
28
+ id: string;
29
+ kind: ArtKind;
30
+ file: string;
31
+ url: string;
32
+ prompt: string;
33
+ width: number;
34
+ height: number;
35
+ reference?: string;
36
+ createdAt: string;
37
+ }
38
+ export declare function readCandidateRecord(paths: SteamPaths, id: string): CandidateRecord | null;
39
+ export declare function listCandidateRecords(paths: SteamPaths): CandidateRecord[];
40
+ /**
41
+ * The reference this kind is drawn against, when the caller named none: the accepted project
42
+ * reference if there is one, else the cover — the picture that already defines the game's look.
43
+ * The logo gets no default reference: a scene would only pull scenery into it.
44
+ */
45
+ export declare function defaultArtReference(root: string, kind: ArtKind, projectReference: string | undefined): string | undefined;
46
+ export declare function generateArtCandidate(options: {
47
+ root: string;
48
+ paths: SteamPaths;
49
+ kind: ArtKind;
50
+ gameName: string;
51
+ prompt?: string;
52
+ referenceUrl?: string;
53
+ keyColorName?: string;
54
+ deps: GenerateRunDeps;
55
+ }): Promise<CandidateRecord>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * `bitmagic steam art generate <kind>` — one AI-generated candidate for the store kit's key art,
3
+ * logo or icon, saved under `.bitmagic/steam/art/candidates/` for the agent to look at.
4
+ *
5
+ * Built on the `generate image` lane exactly as `bitmagic cover` is (ownership, spark billing,
6
+ * the server-side deadline), but through `requestAssetWithJobRecord` so the patch the image
7
+ * generator returns is READ for its URL and never applied: store art is not a game asset, and
8
+ * writing it into world.json would invalidate a passing verify for nothing.
9
+ *
10
+ * The images are drafts by construction — the agent looks at each one, iterates with `--prompt`
11
+ * or a reference, and only `steam art use` makes one part of the kit.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { VOXEL_ART_IMAGE_MODEL, styleLead, styleTail } from '@bitmagic/asset-core';
16
+ import { CliError } from '../errors.js';
17
+ import { readJsonFile, writeJsonAtomic } from '../atomic-json.js';
18
+ import { resolveArtStyle } from '../project/art-style.js';
19
+ import { readCoverRecord, resolveDesignText, coverUrlFromPatches } from '../commands/cover.js';
20
+ import { requestAssetWithJobRecord } from '../commands/generate.js';
21
+ import { creatorPromptField } from '../telemetry/creator-prompt.js';
22
+ import { readImageInfo } from './image-info.js';
23
+ export const ART_KINDS = ['wide', 'tall', 'hero', 'logo', 'icon'];
24
+ /**
25
+ * Generation sizes: the Asset Forger takes at most 2048 px on either side, in multiples of 16.
26
+ * Every Steam asset but one fits inside that at 1:1 or better; the library hero (3840x1240) is
27
+ * drawn from a 2048-wide 3:1 picture, which Steam's own half-size copy (1920x620, the one most
28
+ * screens show) uses at native resolution.
29
+ */
30
+ export const KIND_SIZES = {
31
+ wide: { width: 2048, height: 1152 },
32
+ tall: { width: 1344, height: 2016 },
33
+ hero: { width: 2048, height: 688 },
34
+ logo: { width: 2048, height: 1024 },
35
+ icon: { width: 1024, height: 1024 },
36
+ };
37
+ const FRAMING = {
38
+ wide: 'Steam store key art in a wide 16:9 cinematic composition: the hero and the world\'s most '
39
+ + 'recognisable landmark, bold readable shapes, a strong silhouette, depth and atmosphere. Leave '
40
+ + 'the centre of the frame calm enough for a title logo to sit over it later.',
41
+ tall: 'Steam store key art in a tall 2:3 portrait composition: the hero large in the middle third, '
42
+ + 'open sky or calm space in the top quarter and quiet ground in the bottom fifth, where a title '
43
+ + 'logo will sit later.',
44
+ hero: 'An ultra-wide 3:1 panoramic banner of the game\'s world: the hero and the main landmarks inside '
45
+ + 'the central third, the scene continuing naturally to both edges, nothing important near the '
46
+ + 'top and bottom edges.',
47
+ icon: 'A square app icon: the hero alone, large and centred, one bold silhouette on a simple '
48
+ + 'contrasting background, readable even at 32 pixels.',
49
+ };
50
+ /** The flat colour the logo is drawn on, so `steam art use --as logo` can key it out. */
51
+ export const LOGO_KEY_COLORS = {
52
+ green: '#00FF00',
53
+ magenta: '#FF00FF',
54
+ blue: '#0000FF',
55
+ };
56
+ export function buildArtPrompt(options) {
57
+ const scene = (options.prompt ?? options.designText).trim().slice(0, 1500);
58
+ if (options.kind === 'logo') {
59
+ const keyName = options.keyColorName ?? 'green';
60
+ const keyHex = LOGO_KEY_COLORS[keyName] ?? LOGO_KEY_COLORS.green;
61
+ return [
62
+ `A video game title logo that reads exactly "${options.gameName}" — every letter spelled exactly like that, nothing added.`,
63
+ 'Chunky, highly legible display lettering with a thick dark outline around the whole logo and a slight 3D bevel, '
64
+ + 'designed to read at small sizes on a store page.',
65
+ scene ? `The game: ${scene}` : '',
66
+ `The logo is centred with generous empty margins on every side and sits on a perfectly flat, uniform pure ${keyName} `
67
+ + `(${keyHex}) background: no gradient, no shadow cast on the background, no scenery, no frame, no other text. `
68
+ + `Do not use ${keyName} anywhere in the logo itself.`,
69
+ ].filter((line) => line !== '').join('\n');
70
+ }
71
+ return [
72
+ styleLead(options.artStyle),
73
+ FRAMING[options.kind],
74
+ `The game: ${scene}`,
75
+ styleTail(options.artStyle),
76
+ ].join('\n');
77
+ }
78
+ function nextCandidateId(dir, kind) {
79
+ let max = 0;
80
+ if (fs.existsSync(dir)) {
81
+ const pattern = new RegExp(`^${kind}-(\\d+)\\.json$`);
82
+ for (const name of fs.readdirSync(dir)) {
83
+ const match = pattern.exec(name);
84
+ if (match)
85
+ max = Math.max(max, Number(match[1]));
86
+ }
87
+ }
88
+ return `${kind}-${String(max + 1).padStart(2, '0')}`;
89
+ }
90
+ export function readCandidateRecord(paths, id) {
91
+ if (!/^[a-z]+-\d+$/.test(id))
92
+ return null;
93
+ const parsed = readJsonFile(path.join(paths.candidatesDir, `${id}.json`));
94
+ if (typeof parsed !== 'object' || parsed === null)
95
+ return null;
96
+ const record = parsed;
97
+ return typeof record.file === 'string' ? record : null;
98
+ }
99
+ export function listCandidateRecords(paths) {
100
+ if (!fs.existsSync(paths.candidatesDir))
101
+ return [];
102
+ return fs.readdirSync(paths.candidatesDir)
103
+ .filter((name) => name.endsWith('.json'))
104
+ .map((name) => readCandidateRecord(paths, name.slice(0, -5)))
105
+ .filter((record) => record !== null)
106
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt));
107
+ }
108
+ /**
109
+ * The reference this kind is drawn against, when the caller named none: the accepted project
110
+ * reference if there is one, else the cover — the picture that already defines the game's look.
111
+ * The logo gets no default reference: a scene would only pull scenery into it.
112
+ */
113
+ export function defaultArtReference(root, kind, projectReference) {
114
+ if (kind === 'logo')
115
+ return undefined;
116
+ if (projectReference !== undefined)
117
+ return projectReference;
118
+ return readCoverRecord(root)?.url;
119
+ }
120
+ export async function generateArtCandidate(options) {
121
+ const { root, paths, kind, deps } = options;
122
+ if (options.keyColorName !== undefined && LOGO_KEY_COLORS[options.keyColorName] === undefined) {
123
+ throw new CliError(`--key-color must be one of ${Object.keys(LOGO_KEY_COLORS).join(', ')}.`);
124
+ }
125
+ const designText = options.prompt !== undefined ? '' : resolveDesignText({ root });
126
+ const prompt = buildArtPrompt({
127
+ kind,
128
+ designText,
129
+ artStyle: resolveArtStyle(root),
130
+ gameName: options.gameName,
131
+ ...(options.prompt !== undefined ? { prompt: options.prompt } : {}),
132
+ ...(options.keyColorName !== undefined ? { keyColorName: options.keyColorName } : {}),
133
+ });
134
+ const size = KIND_SIZES[kind];
135
+ const body = {
136
+ name: `steam_${kind}`,
137
+ prompt,
138
+ width: size.width,
139
+ height: size.height,
140
+ model: VOXEL_ART_IMAGE_MODEL,
141
+ ...(options.referenceUrl !== undefined ? { referenceImageUrl: options.referenceUrl } : {}),
142
+ ...creatorPromptField(),
143
+ };
144
+ const outcome = await requestAssetWithJobRecord('image', `Steam ${kind} art`, body, deps, (result) => result);
145
+ const url = coverUrlFromPatches(outcome.patches);
146
+ if (url === null) {
147
+ throw new CliError('The image was generated but the response carried no URL. Run the command again; nothing was saved.');
148
+ }
149
+ const response = await deps.fetch(url);
150
+ if (!response.ok)
151
+ throw new CliError(`Could not download the generated image (HTTP ${response.status}): ${url}`);
152
+ const bytes = Buffer.from(await response.arrayBuffer());
153
+ const info = readImageInfo(bytes);
154
+ if (info === null)
155
+ throw new CliError(`The generated file is not an image: ${url}`);
156
+ fs.mkdirSync(paths.candidatesDir, { recursive: true });
157
+ const id = nextCandidateId(paths.candidatesDir, kind);
158
+ const file = path.join(paths.candidatesDir, `${id}.${info.format}`);
159
+ fs.writeFileSync(file, bytes);
160
+ const record = {
161
+ id,
162
+ kind,
163
+ file,
164
+ url,
165
+ prompt,
166
+ width: info.width,
167
+ height: info.height,
168
+ ...(options.referenceUrl !== undefined ? { reference: options.referenceUrl } : {}),
169
+ createdAt: new Date().toISOString(),
170
+ };
171
+ writeJsonAtomic(path.join(paths.candidatesDir, `${id}.json`), record);
172
+ return record;
173
+ }
174
+ //# sourceMappingURL=art-generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"art-generate.js","sourceRoot":"","sources":["../../src/steam/art-generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { SteamPaths } from './paths.js';
2
+ export interface RenderedAsset {
3
+ id: string;
4
+ file: string;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface ArtRenderResult {
9
+ rendered: RenderedAsset[];
10
+ /** One labelled sheet of every rendered asset, for looking at the whole kit at once. */
11
+ overview: string | null;
12
+ /** Assets that could not be drawn, and why. */
13
+ skipped: {
14
+ id: string;
15
+ reason: string;
16
+ }[];
17
+ /** Fallbacks taken — a tall capsule cut from the wide art, and so on. */
18
+ notes: string[];
19
+ }
20
+ export declare function renderArt(options: {
21
+ paths: SteamPaths;
22
+ /** Only these asset ids; every asset when absent. */
23
+ only?: readonly string[];
24
+ log: (message: string) => void;
25
+ }): Promise<ArtRenderResult>;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * `bitmagic steam art render`: draw every image in the store kit from art.json, at Steamworks'
3
+ * exact sizes, plus the executable icons the builds embed.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { CliError } from '../errors.js';
8
+ import { assetHtml, libraryLogoSize, logoOnlyHtml, readArtConfig, resolveSource, } from './art.js';
9
+ import { buildIcns, buildIco } from './icon-files.js';
10
+ import { RasterSession, dataUrlFor } from './raster.js';
11
+ import { ICNS_SIZES, ICO_SIZES, LIBRARY_LOGO_BOX, MAC_ICNS_FILE, SHORTCUT_ICO_FILE, STORE_IMAGE_SPECS, } from './specs.js';
12
+ /** Which source each art kind falls back to when art.json names none. */
13
+ const FALLBACKS = {
14
+ wide: [],
15
+ tall: ['wide'],
16
+ hero: ['wide'],
17
+ icon: ['tall', 'wide'],
18
+ };
19
+ export async function renderArt(options) {
20
+ const { paths } = options;
21
+ const config = readArtConfig(paths.artConfigPath);
22
+ const result = { rendered: [], overview: null, skipped: [], notes: [] };
23
+ const wanted = STORE_IMAGE_SPECS.filter((spec) => options.only === undefined || options.only.includes(spec.id));
24
+ for (const id of options.only ?? []) {
25
+ if (!STORE_IMAGE_SPECS.some((spec) => spec.id === id) && id !== 'icons') {
26
+ throw new CliError(`--only names "${id}", which is not a Steam asset. Known: ${STORE_IMAGE_SPECS.map((s) => s.id).join(', ')}, icons.`);
27
+ }
28
+ }
29
+ if (resolveSource(paths.steamDir, config, 'wide') === null) {
30
+ throw new CliError('No wide key art yet. Generate one with `bitmagic steam art generate wide` and choose it with '
31
+ + '`bitmagic steam art use <candidate> --as wide` (or point art.json\'s sources.wide at your own image).', 3);
32
+ }
33
+ const session = await RasterSession.open('bitmagic steam art render');
34
+ try {
35
+ const cache = new Map();
36
+ const load = async (name) => {
37
+ const file = resolveSource(paths.steamDir, config, name);
38
+ if (file === null)
39
+ return null;
40
+ const cached = cache.get(file);
41
+ if (cached)
42
+ return cached;
43
+ const converted = await session.toPng(fs.readFileSync(file));
44
+ const loaded = { path: file, dataUrl: dataUrlFor(converted.png), width: converted.width, height: converted.height };
45
+ cache.set(file, loaded);
46
+ return loaded;
47
+ };
48
+ const artFor = async (kind) => {
49
+ const own = await load(kind);
50
+ if (own !== null)
51
+ return own;
52
+ for (const fallback of FALLBACKS[kind]) {
53
+ const used = await load(fallback);
54
+ if (used !== null) {
55
+ const note = `No ${kind} art — ${kind} assets are cut from the ${fallback} art.`;
56
+ if (!result.notes.includes(note))
57
+ result.notes.push(note);
58
+ return used;
59
+ }
60
+ }
61
+ return null;
62
+ };
63
+ const logo = await load('logo');
64
+ const write = (spec, bytes, width, height) => {
65
+ const target = path.join(paths.kitDir, spec.file);
66
+ fs.mkdirSync(path.dirname(target), { recursive: true });
67
+ fs.writeFileSync(target, bytes);
68
+ result.rendered.push({ id: spec.id, file: target, width, height });
69
+ options.log(` ${spec.file} (${width}x${height})`);
70
+ };
71
+ for (const spec of wanted) {
72
+ if (spec.logo === 'only') {
73
+ if (logo === null) {
74
+ result.skipped.push({ id: spec.id, reason: 'no logo — `bitmagic steam art use <file> --as logo`' });
75
+ continue;
76
+ }
77
+ const size = libraryLogoSize(logo, LIBRARY_LOGO_BOX);
78
+ const bytes = await session.render(logoOnlyHtml(logo.dataUrl, size), size.width, size.height, { transparent: true });
79
+ write(spec, bytes, size.width, size.height);
80
+ continue;
81
+ }
82
+ const layout = config.layouts[spec.id];
83
+ const kind = layout.source ?? spec.art;
84
+ if (kind === null)
85
+ continue;
86
+ const art = await artFor(kind);
87
+ if (art === null) {
88
+ result.skipped.push({ id: spec.id, reason: `no ${kind} art` });
89
+ continue;
90
+ }
91
+ if (spec.logo === 'required' && layout.logo !== null && logo === null) {
92
+ result.skipped.push({ id: spec.id, reason: 'needs the logo — `bitmagic steam art use <file> --as logo`' });
93
+ continue;
94
+ }
95
+ const html = assetHtml({
96
+ spec,
97
+ layout,
98
+ artUrl: art.dataUrl,
99
+ logo: spec.logo === 'forbidden' || logo === null ? null : { url: logo.dataUrl, width: logo.width, height: logo.height },
100
+ });
101
+ const bytes = await session.render(html, spec.width, spec.height, { format: spec.format === 'jpg' ? 'jpeg' : 'png' });
102
+ write(spec, bytes, spec.width, spec.height);
103
+ }
104
+ if (options.only === undefined || options.only.includes('icons') || options.only.includes('shortcut_icon')) {
105
+ const icon = await artFor('icon');
106
+ if (icon !== null) {
107
+ const layout = config.layouts.shortcut_icon;
108
+ const pngs = await session.squareIcons(fs.readFileSync(icon.path), [...new Set([...ICO_SIZES, ...ICNS_SIZES])], layout.focus);
109
+ const ico = buildIco(pngs.filter((p) => ICO_SIZES.includes(p.size)));
110
+ const icns = buildIcns(pngs.filter((p) => ICNS_SIZES.includes(p.size)));
111
+ for (const [rel, bytes] of [[SHORTCUT_ICO_FILE, ico], [MAC_ICNS_FILE, icns]]) {
112
+ const target = path.join(paths.kitDir, rel);
113
+ fs.mkdirSync(path.dirname(target), { recursive: true });
114
+ fs.writeFileSync(target, bytes);
115
+ options.log(` ${rel}`);
116
+ }
117
+ }
118
+ }
119
+ if (result.rendered.length > 0) {
120
+ const overview = path.join(paths.checkDir, 'kit-overview.png');
121
+ fs.mkdirSync(paths.checkDir, { recursive: true });
122
+ const html = overviewHtml(result.rendered.map((asset) => ({
123
+ label: `${asset.id} — ${asset.width}x${asset.height}`,
124
+ url: dataUrlFor(fs.readFileSync(asset.file)),
125
+ width: asset.width,
126
+ height: asset.height,
127
+ })));
128
+ fs.writeFileSync(overview, await session.render(html, OVERVIEW_WIDTH, overviewHeight(result.rendered)));
129
+ result.overview = overview;
130
+ options.log(` overview: ${overview}`);
131
+ }
132
+ }
133
+ finally {
134
+ await session.close();
135
+ }
136
+ return result;
137
+ }
138
+ const OVERVIEW_WIDTH = 1600;
139
+ const OVERVIEW_TILE = { width: 365, height: 230 };
140
+ function overviewHeight(assets) {
141
+ const columns = Math.floor((OVERVIEW_WIDTH - 20) / (OVERVIEW_TILE.width + 20));
142
+ return 20 + Math.ceil(assets.length / columns) * (OVERVIEW_TILE.height + 50);
143
+ }
144
+ /** Every asset scaled into a tile on a checkerboard (so a transparent logo reads as transparent). */
145
+ function overviewHtml(tiles) {
146
+ const cells = tiles.map((tile) => `<figure><div class="box"><img src="${tile.url}"></div><figcaption>${tile.label}</figcaption></figure>`).join('');
147
+ return `<!doctype html><html><head><meta charset="utf-8"><style>
148
+ html,body{margin:0;background:#16141c;font:600 14px system-ui,sans-serif;color:#e8e4f0}
149
+ main{display:flex;flex-wrap:wrap;gap:20px;padding:20px;width:${OVERVIEW_WIDTH - 40}px}
150
+ figure{margin:0;width:${OVERVIEW_TILE.width}px}
151
+ .box{width:${OVERVIEW_TILE.width}px;height:${OVERVIEW_TILE.height}px;display:flex;align-items:center;justify-content:center;
152
+ background:repeating-conic-gradient(#2a2733 0 25%,#221f2a 0 50%) 0 0/16px 16px;border-radius:6px;overflow:hidden}
153
+ img{max-width:100%;max-height:100%}
154
+ figcaption{margin-top:6px;opacity:.85}
155
+ </style></head><body><main>${cells}</main></body></html>`;
156
+ }
157
+ //# sourceMappingURL=art-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"art-render.js","sourceRoot":"","sources":["../../src/steam/art-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type SourceName } from './art.js';
2
+ import type { SteamPaths } from './paths.js';
3
+ export interface UseArtResult {
4
+ source: SourceName;
5
+ file: string;
6
+ width: number;
7
+ height: number;
8
+ /** Set when a flat background was keyed out of a logo. */
9
+ keyColor?: string;
10
+ }
11
+ export declare function resolveArtInput(paths: SteamPaths, input: string, cwd: string): string;
12
+ export declare function useArtSource(options: {
13
+ paths: SteamPaths;
14
+ file: string;
15
+ source: SourceName;
16
+ /** `auto`, `#rrggbb`, or `none` to keep the background. Logos only. */
17
+ keyColor?: string;
18
+ tolerance?: number;
19
+ softness?: number;
20
+ }): Promise<UseArtResult>;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `bitmagic steam art use <candidate|file> --as <source>` — make one picture part of the store kit.
3
+ *
4
+ * Every source is stored as a PNG under `.bitmagic/steam/art/` and recorded in art.json, so the
5
+ * kit never depends on a generator URL staying alive. The logo is the one source that changes on
6
+ * the way in: a generated logo arrives on a flat colour and is keyed to transparency, an
7
+ * agent-authored `.html`/`.svg` logo is rendered on a transparent page, and both are trimmed to
8
+ * their own bounds so every layout can size them by their real aspect.
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { CliError } from '../errors.js';
13
+ import { DEFAULT_SOURCE_FILES, setArtSource } from './art.js';
14
+ import { readCandidateRecord } from './art-generate.js';
15
+ import { readImageInfo } from './image-info.js';
16
+ import { RasterSession } from './raster.js';
17
+ /** The canvas an HTML or SVG logo is laid out on before trimming. */
18
+ const LOGO_PAGE = { width: 2400, height: 1200 };
19
+ export function resolveArtInput(paths, input, cwd) {
20
+ const candidate = readCandidateRecord(paths, input);
21
+ if (candidate !== null) {
22
+ if (!fs.existsSync(candidate.file))
23
+ throw new CliError(`Candidate ${input} is recorded but its file is gone: ${candidate.file}`);
24
+ return candidate.file;
25
+ }
26
+ const file = path.resolve(cwd, input);
27
+ if (!fs.existsSync(file)) {
28
+ throw new CliError(`No candidate called ${input} and no file at ${file}. \`bitmagic steam art list\` shows the candidates.`);
29
+ }
30
+ return file;
31
+ }
32
+ export async function useArtSource(options) {
33
+ const { paths, file, source } = options;
34
+ const ext = path.extname(file).toLowerCase();
35
+ const session = await RasterSession.open('bitmagic steam art use');
36
+ try {
37
+ let png;
38
+ let width;
39
+ let height;
40
+ let keyColor;
41
+ if (ext === '.html' || ext === '.htm' || ext === '.svg') {
42
+ if (source !== 'logo')
43
+ throw new CliError('Only the logo can come from an .html or .svg file.');
44
+ const markup = fs.readFileSync(file, 'utf-8');
45
+ const html = ext === '.svg'
46
+ ? `<!doctype html><html><body style="margin:0;background:transparent;display:flex;align-items:center;justify-content:center;width:${LOGO_PAGE.width}px;height:${LOGO_PAGE.height}px">`
47
+ + `<img style="max-width:94%;max-height:94%" src="data:image/svg+xml;base64,${Buffer.from(markup).toString('base64')}"></body></html>`
48
+ : markup;
49
+ const rendered = await session.render(html, LOGO_PAGE.width, LOGO_PAGE.height, { transparent: true });
50
+ ({ png, width, height } = await session.trimTransparent(rendered));
51
+ }
52
+ else {
53
+ const bytes = fs.readFileSync(file);
54
+ const info = readImageInfo(bytes);
55
+ if (info === null)
56
+ throw new CliError(`${file} is not a PNG, JPEG or WebP image.`);
57
+ if (source === 'logo' && options.keyColor !== 'none' && !(info.alpha && options.keyColor === undefined)) {
58
+ const keyed = await session.keyAndTrim(bytes, {
59
+ keyColor: options.keyColor ?? 'auto',
60
+ tolerance: options.tolerance ?? 70,
61
+ softness: options.softness ?? 60,
62
+ });
63
+ ({ png, width, height } = keyed);
64
+ keyColor = keyed.keyColor;
65
+ }
66
+ else if (source === 'logo') {
67
+ ({ png, width, height } = await session.trimTransparent((await session.toPng(bytes)).png));
68
+ }
69
+ else {
70
+ ({ png, width, height } = await session.toPng(bytes));
71
+ }
72
+ }
73
+ const rel = DEFAULT_SOURCE_FILES[source];
74
+ const target = path.join(paths.steamDir, rel);
75
+ fs.mkdirSync(path.dirname(target), { recursive: true });
76
+ fs.writeFileSync(target, png);
77
+ setArtSource(paths.artConfigPath, source, rel);
78
+ return { source, file: target, width, height, ...(keyColor !== undefined ? { keyColor } : {}) };
79
+ }
80
+ finally {
81
+ await session.close();
82
+ }
83
+ }
84
+ //# sourceMappingURL=art-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"art-use.js","sourceRoot":"","sources":["../../src/steam/art-use.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { type ArtSource, type SteamImageSpec } from './specs.js';
2
+ export type SourceName = ArtSource | 'logo';
3
+ export declare const SOURCE_NAMES: readonly SourceName[];
4
+ /** Where each source lands when a candidate is chosen, relative to `.bitmagic/steam/`. */
5
+ export declare const DEFAULT_SOURCE_FILES: Record<SourceName, string>;
6
+ export type Scrim = 'none' | 'bottom' | 'top' | 'left' | 'right' | 'radial';
7
+ export interface LogoPlacement {
8
+ /** Centre of the logo, as fractions of the canvas. */
9
+ x: number;
10
+ y: number;
11
+ /** Largest width, as a fraction of the canvas width. */
12
+ width: number;
13
+ /** Largest height, as a fraction of the canvas height — whichever limit binds first wins. */
14
+ maxHeight: number;
15
+ }
16
+ export interface AssetLayout {
17
+ /** Which source to cut from; the spec's own when absent. */
18
+ source?: ArtSource;
19
+ /** The point of the art kept in view, as fractions (CSS background-position). */
20
+ focus: [number, number];
21
+ /** Scale beyond cover-fit, about `focus`. 1 = just covers the canvas. */
22
+ zoom: number;
23
+ /** null: no logo on this asset. */
24
+ logo: LogoPlacement | null;
25
+ scrim: Scrim;
26
+ /** 0..1 — how dark the scrim gets at its darkest. */
27
+ scrimStrength: number;
28
+ /** 0..1 — a flat darkening over the whole art. */
29
+ dim: number;
30
+ /** Gaussian blur of the art, in pixels. */
31
+ blur: number;
32
+ }
33
+ export interface ArtConfig {
34
+ /** Paths relative to `.bitmagic/steam/`. A missing source is reported, not guessed. */
35
+ sources: Partial<Record<SourceName, string>>;
36
+ layouts: Record<string, AssetLayout>;
37
+ }
38
+ /**
39
+ * Defaults per asset, tuned for a key art with its subject near the centre. The small capsule's
40
+ * logo nearly fills it because Valve asks for exactly that (it is shown as small as 120x45); the
41
+ * tall capsules put the logo where the art is usually emptiest — sky above, ground below.
42
+ */
43
+ export declare const DEFAULT_LAYOUTS: Record<string, AssetLayout>;
44
+ /** One asset's layout: the file's fields over the default, each clamped to a sane range. */
45
+ export declare function readLayout(id: string, value: unknown): AssetLayout;
46
+ export declare function readArtConfig(file: string): ArtConfig;
47
+ /** Write the file with every layout spelled out, so the agent sees every knob it can turn. */
48
+ export declare function writeArtConfig(file: string, config: ArtConfig): void;
49
+ /** Record `relPath` as the `name` source, keeping everything else in the file. */
50
+ export declare function setArtSource(file: string, name: SourceName, relPath: string): ArtConfig;
51
+ /** The absolute path of a source, or null when art.json names none or the file is gone. */
52
+ export declare function resolveSource(steamDir: string, config: ArtConfig, name: SourceName): string | null;
53
+ export interface PlacedLogo {
54
+ left: number;
55
+ top: number;
56
+ width: number;
57
+ height: number;
58
+ }
59
+ /** Where the logo lands on a `width`x`height` canvas, keeping its aspect inside both limits. */
60
+ export declare function placeLogo(placement: LogoPlacement, canvas: {
61
+ width: number;
62
+ height: number;
63
+ }, logo: {
64
+ width: number;
65
+ height: number;
66
+ }): PlacedLogo;
67
+ /**
68
+ * The page that draws one asset. Images arrive as data URLs; `logo` is null for assets that must
69
+ * carry none (the hero, the background, the icons).
70
+ */
71
+ export declare function assetHtml(options: {
72
+ spec: SteamImageSpec;
73
+ layout: AssetLayout;
74
+ artUrl: string;
75
+ logo: {
76
+ url: string;
77
+ width: number;
78
+ height: number;
79
+ } | null;
80
+ }): string;
81
+ /**
82
+ * The library logo: the logo alone, transparent, scaled to touch the 1280x720 box on one side —
83
+ * "1280 px wide and/or 720 px tall", in Valve's words — with its own aspect kept.
84
+ */
85
+ export declare function libraryLogoSize(logo: {
86
+ width: number;
87
+ height: number;
88
+ }, box: {
89
+ width: number;
90
+ height: number;
91
+ }): {
92
+ width: number;
93
+ height: number;
94
+ };
95
+ export declare function logoOnlyHtml(logoUrl: string, size: {
96
+ width: number;
97
+ height: number;
98
+ }): string;