@bitmagic/cli 0.1.83-dev.0 → 0.1.83-dev.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.
Files changed (199) hide show
  1. package/README.md +70 -1
  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/import.js +2 -4
  14. package/dist/commands/import.js.map +1 -1
  15. package/dist/commands/init.js +0 -3
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/ios.js +88 -136
  18. package/dist/commands/ios.js.map +1 -1
  19. package/dist/commands/promote.js +2 -4
  20. package/dist/commands/promote.js.map +1 -1
  21. package/dist/commands/publish.js +0 -1
  22. package/dist/commands/publish.js.map +1 -1
  23. package/dist/commands/steam-build.d.ts +67 -0
  24. package/dist/commands/steam-build.js +286 -0
  25. package/dist/commands/steam-build.js.map +1 -0
  26. package/dist/commands/steam-portal.d.ts +1 -0
  27. package/dist/commands/steam-portal.js +319 -0
  28. package/dist/commands/steam-portal.js.map +1 -0
  29. package/dist/commands/steam-upload.d.ts +37 -0
  30. package/dist/commands/steam-upload.js +172 -0
  31. package/dist/commands/steam-upload.js.map +1 -0
  32. package/dist/commands/steam.d.ts +1 -0
  33. package/dist/commands/steam.js +502 -0
  34. package/dist/commands/steam.js.map +1 -0
  35. package/dist/commands/verify.js +6 -0
  36. package/dist/commands/verify.js.map +1 -1
  37. package/dist/editor/asset-viewer.js +5 -4
  38. package/dist/editor/asset-viewer.js.map +1 -1
  39. package/dist/editor/game-info-panel.d.ts +2 -2
  40. package/dist/editor/game-info-panel.js +1 -2
  41. package/dist/editor/game-info-panel.js.map +1 -1
  42. package/dist/editor/game-info.d.ts +1 -3
  43. package/dist/editor/game-info.js +1 -2
  44. package/dist/editor/game-info.js.map +1 -1
  45. package/dist/editor/server.js +2 -3
  46. package/dist/editor/server.js.map +1 -1
  47. package/dist/editor/shell-page.js +9 -7
  48. package/dist/editor/shell-page.js.map +1 -1
  49. package/dist/forge/browser-host.d.ts +6 -12
  50. package/dist/forge/browser-host.js +5 -11
  51. package/dist/forge/browser-host.js.map +1 -1
  52. package/dist/ios/native-overrides.d.ts +27 -0
  53. package/dist/ios/native-overrides.js +87 -0
  54. package/dist/ios/native-overrides.js.map +1 -0
  55. package/dist/ios/record-validate.d.ts +1 -33
  56. package/dist/ios/record-validate.js +2 -100
  57. package/dist/ios/record-validate.js.map +1 -1
  58. package/dist/ios/store-bridge.d.ts +26 -0
  59. package/dist/ios/store-bridge.js +224 -0
  60. package/dist/ios/store-bridge.js.map +1 -0
  61. package/dist/ios/storekit-config.d.ts +15 -0
  62. package/dist/ios/storekit-config.js +106 -0
  63. package/dist/ios/storekit-config.js.map +1 -0
  64. package/dist/ios/xcode-template.d.ts +12 -3
  65. package/dist/ios/xcode-template.js +158 -27
  66. package/dist/ios/xcode-template.js.map +1 -1
  67. package/dist/package/collect-urls.d.ts +1 -1
  68. package/dist/package/collect-urls.js +10 -2
  69. package/dist/package/collect-urls.js.map +1 -1
  70. package/dist/package/offline-payload.d.ts +35 -0
  71. package/dist/package/offline-payload.js +142 -0
  72. package/dist/package/offline-payload.js.map +1 -0
  73. package/dist/package/packaged-check.d.ts +39 -0
  74. package/dist/package/packaged-check.js +104 -0
  75. package/dist/package/packaged-check.js.map +1 -0
  76. package/dist/package/transform.d.ts +4 -4
  77. package/dist/package/transform.js +3 -3
  78. package/dist/project/context.d.ts +7 -2
  79. package/dist/project/context.js +1 -2
  80. package/dist/project/context.js.map +1 -1
  81. package/dist/project/published-three.d.ts +14 -0
  82. package/dist/project/published-three.js +93 -0
  83. package/dist/project/published-three.js.map +1 -0
  84. package/dist/publish/bundle.d.ts +1 -1
  85. package/dist/publish/bundle.js +8 -3
  86. package/dist/publish/bundle.js.map +1 -1
  87. package/dist/publish/platform.d.ts +1 -1
  88. package/dist/publish/platform.js +1 -1
  89. package/dist/publish/source-zip.d.ts +0 -1
  90. package/dist/publish/source-zip.js +1 -2
  91. package/dist/publish/source-zip.js.map +1 -1
  92. package/dist/scaffold/aliases.js +8 -5
  93. package/dist/scaffold/aliases.js.map +1 -1
  94. package/dist/scaffold/engine-download.d.ts +1 -1
  95. package/dist/scaffold/engine-download.js +1 -1
  96. package/dist/scaffold/export-zip.d.ts +0 -1
  97. package/dist/scaffold/export-zip.js +3 -6
  98. package/dist/scaffold/export-zip.js.map +1 -1
  99. package/dist/scaffold/import-project.js +1 -1
  100. package/dist/scaffold/project-export.js +0 -1
  101. package/dist/scaffold/project-export.js.map +1 -1
  102. package/dist/scaffold/project-files.d.ts +0 -1
  103. package/dist/scaffold/project-files.js +8 -5
  104. package/dist/scaffold/project-files.js.map +1 -1
  105. package/dist/scaffold/project.d.ts +15 -4
  106. package/dist/scaffold/project.js +30 -13
  107. package/dist/scaffold/project.js.map +1 -1
  108. package/dist/scaffold/skill-files.js +3 -1
  109. package/dist/scaffold/skill-files.js.map +1 -1
  110. package/dist/scaffold/steam-skill-files.d.ts +2 -0
  111. package/dist/scaffold/steam-skill-files.js +7 -0
  112. package/dist/scaffold/steam-skill-files.js.map +1 -0
  113. package/dist/scaffold/suggest-template.js +0 -1
  114. package/dist/scaffold/suggest-template.js.map +1 -1
  115. package/dist/scaffold/unity-skill-files.js +1 -1
  116. package/dist/steam/art-generate.d.ts +55 -0
  117. package/dist/steam/art-generate.js +173 -0
  118. package/dist/steam/art-generate.js.map +1 -0
  119. package/dist/steam/art-render.d.ts +25 -0
  120. package/dist/steam/art-render.js +157 -0
  121. package/dist/steam/art-render.js.map +1 -0
  122. package/dist/steam/art-use.d.ts +20 -0
  123. package/dist/steam/art-use.js +84 -0
  124. package/dist/steam/art-use.js.map +1 -0
  125. package/dist/steam/art.d.ts +98 -0
  126. package/dist/steam/art.js +243 -0
  127. package/dist/steam/art.js.map +1 -0
  128. package/dist/steam/check.d.ts +21 -0
  129. package/dist/steam/check.js +113 -0
  130. package/dist/steam/check.js.map +1 -0
  131. package/dist/steam/config.d.ts +57 -0
  132. package/dist/steam/config.js +145 -0
  133. package/dist/steam/config.js.map +1 -0
  134. package/dist/steam/electron-cache.d.ts +11 -0
  135. package/dist/steam/electron-cache.js +105 -0
  136. package/dist/steam/electron-cache.js.map +1 -0
  137. package/dist/steam/fuses.d.ts +16 -0
  138. package/dist/steam/fuses.js +75 -0
  139. package/dist/steam/fuses.js.map +1 -0
  140. package/dist/steam/icon-files.d.ts +16 -0
  141. package/dist/steam/icon-files.js +70 -0
  142. package/dist/steam/icon-files.js.map +1 -0
  143. package/dist/steam/image-info.d.ts +16 -0
  144. package/dist/steam/image-info.js +89 -0
  145. package/dist/steam/image-info.js.map +1 -0
  146. package/dist/steam/launch-check.d.ts +12 -0
  147. package/dist/steam/launch-check.js +101 -0
  148. package/dist/steam/launch-check.js.map +1 -0
  149. package/dist/steam/media.d.ts +48 -0
  150. package/dist/steam/media.js +151 -0
  151. package/dist/steam/media.js.map +1 -0
  152. package/dist/steam/package-macos.d.ts +52 -0
  153. package/dist/steam/package-macos.js +268 -0
  154. package/dist/steam/package-macos.js.map +1 -0
  155. package/dist/steam/package-windows.d.ts +24 -0
  156. package/dist/steam/package-windows.js +84 -0
  157. package/dist/steam/package-windows.js.map +1 -0
  158. package/dist/steam/paths.d.ts +37 -0
  159. package/dist/steam/paths.js +45 -0
  160. package/dist/steam/paths.js.map +1 -0
  161. package/dist/steam/playtest-shots.d.ts +18 -0
  162. package/dist/steam/playtest-shots.js +83 -0
  163. package/dist/steam/playtest-shots.js.map +1 -0
  164. package/dist/steam/portal-recipes.d.ts +25 -0
  165. package/dist/steam/portal-recipes.js +61 -0
  166. package/dist/steam/portal-recipes.js.map +1 -0
  167. package/dist/steam/portal.d.ts +58 -0
  168. package/dist/steam/portal.js +340 -0
  169. package/dist/steam/portal.js.map +1 -0
  170. package/dist/steam/raster.d.ts +59 -0
  171. package/dist/steam/raster.js +285 -0
  172. package/dist/steam/raster.js.map +1 -0
  173. package/dist/steam/shell-files.d.ts +17 -0
  174. package/dist/steam/shell-files.js +268 -0
  175. package/dist/steam/shell-files.js.map +1 -0
  176. package/dist/steam/specs.d.ts +61 -0
  177. package/dist/steam/specs.js +103 -0
  178. package/dist/steam/specs.js.map +1 -0
  179. package/dist/steam/steampipe.d.ts +46 -0
  180. package/dist/steam/steampipe.js +170 -0
  181. package/dist/steam/steampipe.js.map +1 -0
  182. package/dist/steam/store-copy.d.ts +89 -0
  183. package/dist/steam/store-copy.js +221 -0
  184. package/dist/steam/store-copy.js.map +1 -0
  185. package/dist/steam/tags.d.ts +9 -0
  186. package/dist/steam/tags.js +49 -0
  187. package/dist/steam/tags.js.map +1 -0
  188. package/dist/steam/trailer.d.ts +34 -0
  189. package/dist/steam/trailer.js +141 -0
  190. package/dist/steam/trailer.js.map +1 -0
  191. package/dist/verify/browser.d.ts +11 -0
  192. package/dist/verify/browser.js +195 -152
  193. package/dist/verify/browser.js.map +1 -1
  194. package/dist/verify/classify.d.ts +11 -2
  195. package/dist/verify/classify.js +16 -4
  196. package/dist/verify/classify.js.map +1 -1
  197. package/dist/verify/mobile-parity.d.ts +1 -1
  198. package/dist/verify/mobile-parity.js +1 -1
  199. package/package.json +5 -4
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Every image Steamworks asks for, at the size it asks for it, with the rule Valve reviews it
3
+ * against. One table, read by the renderer (what to draw), the check (what must exist) and the
4
+ * skill (what goes where) — a size that lived in three places would drift in one of them.
5
+ *
6
+ * Sizes and rules from partner.steamgames.com/doc/store/assets/standard and
7
+ * /doc/store/assets/libraryassets, as of 2026-10.
8
+ */
9
+ export const STORE_IMAGE_SPECS = [
10
+ {
11
+ id: 'header_capsule', file: 'store/header_capsule_920x430.png', width: 920, height: 430, format: 'png',
12
+ art: 'wide', logo: 'required', required: true,
13
+ where: 'Store Admin → Graphical Assets → Header Capsule',
14
+ rule: 'Top of the store page and most lists. Key art and a legible logo; no text but the title.',
15
+ },
16
+ {
17
+ id: 'small_capsule', file: 'store/small_capsule_462x174.png', width: 462, height: 174, format: 'png',
18
+ art: 'wide', logo: 'required', required: true,
19
+ where: 'Store Admin → Graphical Assets → Small Capsule',
20
+ rule: 'Search results and lists, shown as small as 120x45: the logo should nearly fill it.',
21
+ },
22
+ {
23
+ id: 'main_capsule', file: 'store/main_capsule_1232x706.png', width: 1232, height: 706, format: 'png',
24
+ art: 'wide', logo: 'required', required: true,
25
+ where: 'Store Admin → Graphical Assets → Main Capsule',
26
+ rule: 'The front-page carousel. Key art first, logo legible; no text but the title.',
27
+ },
28
+ {
29
+ id: 'vertical_capsule', file: 'store/vertical_capsule_748x896.png', width: 748, height: 896, format: 'png',
30
+ art: 'tall', logo: 'required', required: true,
31
+ where: 'Store Admin → Graphical Assets → Vertical Capsule',
32
+ rule: 'Seasonal sale pages. Key art first, logo legible; no text but the title.',
33
+ },
34
+ {
35
+ id: 'page_background', file: 'store/page_background_1438x810.png', width: 1438, height: 810, format: 'png',
36
+ art: 'wide', logo: 'forbidden', required: false,
37
+ where: 'Store Admin → Graphical Assets → Page Background',
38
+ rule: 'Ambient, low-contrast art behind the store page; no logo, no text.',
39
+ },
40
+ {
41
+ id: 'library_capsule', file: 'library/library_capsule_600x900.png', width: 600, height: 900, format: 'png',
42
+ art: 'tall', logo: 'required', required: true,
43
+ where: 'Store Admin → Graphical Assets → Library Capsule',
44
+ rule: 'The library grid. Key art and a legible logo; no text but the title.',
45
+ },
46
+ {
47
+ id: 'library_header', file: 'library/library_header_920x430.png', width: 920, height: 430, format: 'png',
48
+ art: 'wide', logo: 'required', required: true,
49
+ where: 'Store Admin → Graphical Assets → Library Header',
50
+ rule: 'Library lists and recent games. Branding first; no text but the title.',
51
+ },
52
+ {
53
+ id: 'library_hero', file: 'library/library_hero_3840x1240.png', width: 3840, height: 1240, format: 'png',
54
+ art: 'hero', logo: 'forbidden', required: true,
55
+ where: 'Store Admin → Graphical Assets → Library Hero',
56
+ rule: 'Top of the game\'s library page. NO text and no logo — Steam draws the library logo over it. '
57
+ + 'Keep what matters inside the central 860x380 safe area.',
58
+ },
59
+ {
60
+ id: 'library_logo', file: 'library/library_logo.png', width: 1280, height: 720, format: 'png',
61
+ art: null, logo: 'only', required: true,
62
+ where: 'Store Admin → Graphical Assets → Library Logo',
63
+ rule: 'The logo alone on a transparent background, 1280 px wide and/or 720 px tall. No other words.',
64
+ },
65
+ {
66
+ id: 'app_icon', file: 'icons/app_icon_184x184.jpg', width: 184, height: 184, format: 'jpg',
67
+ art: 'icon', logo: 'forbidden', required: true,
68
+ where: 'Graphical Assets → App Icon',
69
+ rule: 'The square icon in the library list, chat, notifications and the Deck. Readable at 32 px.',
70
+ },
71
+ {
72
+ id: 'shortcut_icon', file: 'icons/shortcut_icon_256x256.png', width: 256, height: 256, format: 'png',
73
+ art: 'icon', logo: 'forbidden', required: true,
74
+ where: 'Graphical Assets → Shortcut Icon (the .ico beside it carries the same picture at every size)',
75
+ rule: 'The desktop shortcut Steam creates. 256x256 or 512x512, ICO or PNG.',
76
+ },
77
+ ];
78
+ /** The multi-size Windows icon and the macOS icon, written next to the shortcut icon. */
79
+ export const SHORTCUT_ICO_FILE = 'icons/shortcut_icon.ico';
80
+ export const MAC_ICNS_FILE = 'icons/mac_icon.icns';
81
+ /** The library logo is "1280 wide and/or 720 tall": it keeps its own aspect inside that box. */
82
+ export const LIBRARY_LOGO_BOX = { width: 1280, height: 720 };
83
+ export const SCREENSHOT_WIDTH = 1920;
84
+ export const SCREENSHOT_HEIGHT = 1080;
85
+ /** Fewer than five and the store page cannot be submitted. */
86
+ export const MIN_SCREENSHOTS = 5;
87
+ /** Enough for a varied gallery without padding it with near-duplicates. */
88
+ export const RECOMMENDED_SCREENSHOTS = 8;
89
+ /** The icon sizes the Windows .ico and the macOS .icns carry. */
90
+ export const ICO_SIZES = [16, 24, 32, 48, 64, 128, 256];
91
+ export const ICNS_SIZES = [16, 32, 64, 128, 256, 512, 1024];
92
+ /** Steam's trailer rules (partner.steamgames.com/doc/store/trailer). */
93
+ export const TRAILER_RULES = {
94
+ maxWidth: 1920,
95
+ maxHeight: 1080,
96
+ frameRates: [30, 29.97, 60, 59.94],
97
+ minBitrateKbps: 5000,
98
+ containers: ['mp4', 'mov', 'wmv'],
99
+ };
100
+ export function specById(id) {
101
+ return STORE_IMAGE_SPECS.find((spec) => spec.id === id);
102
+ }
103
+ //# sourceMappingURL=specs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs.js","sourceRoot":"","sources":["../../src/steam/specs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export declare function steamcmdDir(baseDir?: string): string;
2
+ export declare function steamcmdExecutable(dir?: string): string;
3
+ /** steamcmd on this machine, downloaded from Valve on first use. */
4
+ export declare function ensureSteamcmd(log: (message: string) => void): Promise<string>;
5
+ /**
6
+ * steamcmd for macOS is an Intel binary. On Apple Silicon it needs Rosetta 2, which the creator
7
+ * installs once (it asks for their agreement to Apple's licence).
8
+ */
9
+ export declare function rosettaMissing(): boolean;
10
+ export interface DepotContent {
11
+ depotId: number;
12
+ /** Absolute content root: the folder whose contents become the depot. */
13
+ contentRoot: string;
14
+ }
15
+ /** One depot's build script. Pure. */
16
+ export declare function depotBuildVdf(depot: DepotContent): string;
17
+ /**
18
+ * The app build script. `setLive` only for a beta branch: Valve does not let a build script set
19
+ * the default branch live — that is a click in Steamworks, and the creator's to approve. Pure.
20
+ */
21
+ export declare function appBuildVdf(options: {
22
+ appId: number;
23
+ description: string;
24
+ buildOutput: string;
25
+ depotScripts: {
26
+ depotId: number;
27
+ file: string;
28
+ }[];
29
+ setLive?: string;
30
+ preview?: boolean;
31
+ }): string;
32
+ export interface SteamcmdRun {
33
+ ok: boolean;
34
+ output: string;
35
+ needsLogin: boolean;
36
+ buildId?: string;
37
+ }
38
+ /** Pull the BuildID out of steamcmd's report. Pure. */
39
+ export declare function parseBuildId(output: string): string | undefined;
40
+ /**
41
+ * Run steamcmd with no terminal attached. The moment it asks for a password or a Steam Guard
42
+ * code, it is stopped: that means the cached login is gone, and only the creator can renew it.
43
+ */
44
+ export declare function runSteamcmd(executable: string, args: string[], log: (line: string) => void, timeoutMs?: number): Promise<SteamcmdRun>;
45
+ /** The interactive login: steamcmd owns the terminal so the creator types into it directly. */
46
+ export declare function runSteamcmdInteractive(executable: string, args: string[]): number;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * SteamPipe: Valve's command-line uploader (steamcmd) and the build scripts it runs.
3
+ *
4
+ * steamcmd is downloaded from Valve's CDN into `~/.bitmagic/tools/steamcmd/` the first time it is
5
+ * needed. Logging it in is the CREATOR's step, once per machine, in their own terminal
6
+ * (`bitmagic steam login`): it asks for the Steam password and a Steam Guard code, and an agent
7
+ * must never be the one typing those. After that, steamcmd keeps its own cached login, and
8
+ * `bitmagic steam upload` runs it non-interactively — refusing, with that remedy, the moment it
9
+ * asks for a password again.
10
+ *
11
+ * The depots are uploaded straight from `.bitmagic/steam/build/` — never through a zip — so the
12
+ * macOS app's symlinks and executable bits reach the depot exactly as signed.
13
+ */
14
+ import { spawn, spawnSync } from 'child_process';
15
+ import * as fs from 'fs';
16
+ import * as os from 'os';
17
+ import * as path from 'path';
18
+ import { unzipSync } from 'fflate';
19
+ import * as tar from 'tar';
20
+ import { CliError } from '../errors.js';
21
+ const STEAMCMD_DOWNLOADS = {
22
+ darwin: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd_osx.tar.gz',
23
+ linux: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz',
24
+ win32: 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip',
25
+ };
26
+ export function steamcmdDir(baseDir = path.join(os.homedir(), '.bitmagic')) {
27
+ return path.join(baseDir, 'tools', 'steamcmd');
28
+ }
29
+ export function steamcmdExecutable(dir = steamcmdDir()) {
30
+ return process.platform === 'win32' ? path.join(dir, 'steamcmd.exe') : path.join(dir, 'steamcmd.sh');
31
+ }
32
+ /** steamcmd on this machine, downloaded from Valve on first use. */
33
+ export async function ensureSteamcmd(log) {
34
+ const dir = steamcmdDir();
35
+ const executable = steamcmdExecutable(dir);
36
+ if (fs.existsSync(executable))
37
+ return executable;
38
+ const url = STEAMCMD_DOWNLOADS[process.platform];
39
+ if (url === undefined)
40
+ throw new CliError(`steamcmd has no build for ${process.platform}.`, 3);
41
+ log(`Downloading steamcmd from Valve (${url})…`);
42
+ const response = await fetch(url, { signal: AbortSignal.timeout(5 * 60_000) });
43
+ if (!response.ok)
44
+ throw new CliError(`Could not download steamcmd (HTTP ${response.status}).`);
45
+ const bytes = Buffer.from(await response.arrayBuffer());
46
+ fs.mkdirSync(dir, { recursive: true });
47
+ if (url.endsWith('.zip')) {
48
+ for (const [name, data] of Object.entries(unzipSync(new Uint8Array(bytes)))) {
49
+ if (name.endsWith('/'))
50
+ continue;
51
+ const target = path.join(dir, ...name.split('/'));
52
+ fs.mkdirSync(path.dirname(target), { recursive: true });
53
+ fs.writeFileSync(target, data);
54
+ }
55
+ }
56
+ else {
57
+ const archive = path.join(dir, 'steamcmd.tar.gz');
58
+ fs.writeFileSync(archive, bytes);
59
+ await tar.x({ file: archive, cwd: dir });
60
+ fs.rmSync(archive, { force: true });
61
+ }
62
+ if (!fs.existsSync(executable))
63
+ throw new CliError(`The steamcmd download did not contain ${path.basename(executable)}.`);
64
+ if (process.platform !== 'win32')
65
+ fs.chmodSync(executable, 0o755);
66
+ return executable;
67
+ }
68
+ /**
69
+ * steamcmd for macOS is an Intel binary. On Apple Silicon it needs Rosetta 2, which the creator
70
+ * installs once (it asks for their agreement to Apple's licence).
71
+ */
72
+ export function rosettaMissing() {
73
+ if (process.platform !== 'darwin' || os.arch() !== 'arm64')
74
+ return false;
75
+ return spawnSync('/usr/bin/arch', ['-x86_64', '/usr/bin/true']).status !== 0;
76
+ }
77
+ function vdfString(value) {
78
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
79
+ }
80
+ /** One depot's build script. Pure. */
81
+ export function depotBuildVdf(depot) {
82
+ return `"DepotBuild"
83
+ {
84
+ \t"DepotID" ${vdfString(String(depot.depotId))}
85
+ \t"ContentRoot" ${vdfString(depot.contentRoot)}
86
+ \t"FileMapping"
87
+ \t{
88
+ \t\t"LocalPath" "*"
89
+ \t\t"DepotPath" "."
90
+ \t\t"recursive" "1"
91
+ \t}
92
+ \t// Finder and Explorer litter; never part of a game.
93
+ \t"FileExclusion" ".DS_Store"
94
+ \t"FileExclusion" "Thumbs.db"
95
+ }
96
+ `;
97
+ }
98
+ /**
99
+ * The app build script. `setLive` only for a beta branch: Valve does not let a build script set
100
+ * the default branch live — that is a click in Steamworks, and the creator's to approve. Pure.
101
+ */
102
+ export function appBuildVdf(options) {
103
+ if (options.setLive !== undefined && (options.setLive === '' || options.setLive === 'default' || options.setLive === 'public')) {
104
+ throw new CliError('Steam does not allow a build script to set the default branch live; do that in Steamworks (SteamPipe → Builds).');
105
+ }
106
+ const depots = options.depotScripts.map((depot) => `\t\t${vdfString(String(depot.depotId))} ${vdfString(depot.file)}`).join('\n');
107
+ return `"AppBuild"
108
+ {
109
+ \t"AppID" ${vdfString(String(options.appId))}
110
+ \t"Desc" ${vdfString(options.description)}
111
+ \t"BuildOutput" ${vdfString(options.buildOutput)}
112
+ ${options.setLive !== undefined ? `\t"SetLive" ${vdfString(options.setLive)}\n` : ''}${options.preview === true ? '\t"Preview" "1"\n' : ''}\t"Depots"
113
+ \t{
114
+ ${depots}
115
+ \t}
116
+ }
117
+ `;
118
+ }
119
+ // ============================================================================
120
+ // Running steamcmd
121
+ // ============================================================================
122
+ /** Lines that mean steamcmd wants a password or a code — the creator's to give, not ours. */
123
+ const NEEDS_LOGIN = /password:|steam guard|two-factor|twofactor|cached credentials not found|invalid password|login failure|account logon denied|ENTER.*CODE/i;
124
+ /** Pull the BuildID out of steamcmd's report. Pure. */
125
+ export function parseBuildId(output) {
126
+ return /BuildID\s+(\d+)/i.exec(output)?.[1] ?? /build\s+id[^0-9]*(\d+)/i.exec(output)?.[1];
127
+ }
128
+ /**
129
+ * Run steamcmd with no terminal attached. The moment it asks for a password or a Steam Guard
130
+ * code, it is stopped: that means the cached login is gone, and only the creator can renew it.
131
+ */
132
+ export function runSteamcmd(executable, args, log, timeoutMs = 60 * 60_000) {
133
+ return new Promise((resolve) => {
134
+ const child = spawn(executable, args, { stdio: ['ignore', 'pipe', 'pipe'], cwd: path.dirname(executable) });
135
+ let output = '';
136
+ let needsLogin = false;
137
+ const onData = (chunk) => {
138
+ const text = chunk.toString('utf-8');
139
+ output = (output + text).slice(-200_000);
140
+ for (const line of text.split(/\r?\n/)) {
141
+ const trimmed = line.trim();
142
+ if (trimmed !== '')
143
+ log(` steamcmd: ${trimmed}`);
144
+ }
145
+ if (NEEDS_LOGIN.test(text)) {
146
+ needsLogin = true;
147
+ child.kill('SIGTERM');
148
+ }
149
+ };
150
+ child.stdout.on('data', onData);
151
+ child.stderr.on('data', onData);
152
+ const timer = setTimeout(() => child.kill('SIGTERM'), timeoutMs);
153
+ child.on('close', (code) => {
154
+ clearTimeout(timer);
155
+ const buildId = parseBuildId(output);
156
+ resolve({
157
+ ok: code === 0 && !needsLogin,
158
+ output,
159
+ needsLogin,
160
+ ...(buildId !== undefined ? { buildId } : {}),
161
+ });
162
+ });
163
+ });
164
+ }
165
+ /** The interactive login: steamcmd owns the terminal so the creator types into it directly. */
166
+ export function runSteamcmdInteractive(executable, args) {
167
+ const result = spawnSync(executable, args, { stdio: 'inherit', cwd: path.dirname(executable) });
168
+ return result.status ?? 1;
169
+ }
170
+ //# sourceMappingURL=steampipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steampipe.js","sourceRoot":"","sources":["../../src/steam/steampipe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ export declare const SHORT_DESCRIPTION_MAX = 300;
2
+ export declare const MAX_TAGS = 20;
3
+ /** Below this the "About" reads as a stub to a reviewer. Valve asks for "detailed and coherent". */
4
+ export declare const ABOUT_MIN_CHARS = 400;
5
+ export interface Requirements {
6
+ os: string;
7
+ processor: string;
8
+ memory: string;
9
+ graphics: string;
10
+ storage: string;
11
+ additional?: string;
12
+ }
13
+ export interface LanguageSupport {
14
+ name: string;
15
+ interface: boolean;
16
+ fullAudio: boolean;
17
+ subtitles: boolean;
18
+ }
19
+ export interface StoreCopy {
20
+ name: string;
21
+ shortDescription: string;
22
+ /** "About This Game", in Steam's BBCode. */
23
+ about: string;
24
+ /** Steam user tags, most important first. */
25
+ tags: string[];
26
+ /** Steamworks' genre checkboxes (Action, Casual, Indie, ...). */
27
+ genres: string[];
28
+ developer: string;
29
+ publisher: string;
30
+ /** Public player-support destination, entered separately from the store description. */
31
+ supportUrl?: string;
32
+ supportEmail?: string;
33
+ languages: LanguageSupport[];
34
+ controllerSupport: 'full' | 'partial' | 'none';
35
+ /** USD; 0 is free. The creator's decision — the agent only records it. */
36
+ priceUsd?: number;
37
+ /** `YYYY-MM-DD`, or empty while the date is "Coming soon". */
38
+ releaseDate?: string;
39
+ /** Steam's AI Generated Content Disclosure, in the developer's words. */
40
+ ai: {
41
+ /** Content made with AI tools during development: art, code, sound, text. */
42
+ preGenerated: string;
43
+ /** Content AI generates while the game runs, and the guardrails on it; empty when none. */
44
+ liveGenerated: string;
45
+ };
46
+ /** Draft answers to the mature-content survey, for the creator to confirm. */
47
+ contentNotes: string;
48
+ systemRequirements: {
49
+ windows: {
50
+ minimum: Requirements;
51
+ recommended?: Requirements;
52
+ };
53
+ macos: {
54
+ minimum: Requirements;
55
+ recommended?: Requirements;
56
+ };
57
+ };
58
+ /** The copyright line under the store page, e.g. "© 2026 Studio Name". */
59
+ legalLine?: string;
60
+ }
61
+ export interface CopyFinding {
62
+ level: 'error' | 'warning';
63
+ field: string;
64
+ message: string;
65
+ }
66
+ /** Unknown or unbalanced BBCode, as findings. Pure. */
67
+ export declare function checkBbcode(text: string): string[];
68
+ /** Links a reviewer will reject: anything off Steam. Pure. */
69
+ export declare function externalLinks(text: string): string[];
70
+ /** Words that sell the game somewhere else, or price it, inside Steam copy. Pure. */
71
+ export declare function offStoreMentions(text: string): string[];
72
+ /**
73
+ * Every rule `steam check` holds the copy to. `knownTags` is Steam's current tag list, or null
74
+ * when it could not be fetched — then tag names go unchecked rather than failing offline.
75
+ */
76
+ export declare function checkStoreCopy(copy: StoreCopy, knownTags: ReadonlySet<string> | null): CopyFinding[];
77
+ /**
78
+ * The defaults the scaffold writes: the game's own name and description, desktop-shell system
79
+ * requirements, English, and empty fields where only the agent and creator can know the answer.
80
+ */
81
+ export declare function scaffoldStoreCopy(options: {
82
+ name: string;
83
+ description: string;
84
+ installMb: number;
85
+ webgpu: boolean;
86
+ }): StoreCopy;
87
+ export declare function readStoreCopy(file: string): StoreCopy | null;
88
+ export declare function writeStoreCopy(file: string, copy: StoreCopy): void;
89
+ export declare function requireStoreCopy(file: string): StoreCopy;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * `.bitmagic/steam/store.json` — the store page's words: the name, the short description, the
3
+ * "About This Game" text, tags, languages, the AI-content disclosure, system requirements.
4
+ *
5
+ * The agent writes this file (it is the one who knows the game), the creator approves it, and
6
+ * `bitmagic steam check` holds it to Valve's rules before anyone pastes it into Steamworks. The
7
+ * same split the ads lane uses for ads.json: the CLI scaffolds and validates, it does not write
8
+ * marketing copy.
9
+ */
10
+ import { readJsonFile, writeJsonAtomic } from '../atomic-json.js';
11
+ import { CliError } from '../errors.js';
12
+ export const SHORT_DESCRIPTION_MAX = 300;
13
+ export const MAX_TAGS = 20;
14
+ /** Below this the "About" reads as a stub to a reviewer. Valve asks for "detailed and coherent". */
15
+ export const ABOUT_MIN_CHARS = 400;
16
+ /**
17
+ * Steam's BBCode for store descriptions. `*` and `hr` stand alone; everything else must close.
18
+ */
19
+ const PAIRED_TAGS = new Set([
20
+ 'h1', 'h2', 'h3', 'b', 'u', 'i', 'strike', 'spoiler', 'noparse', 'url', 'list', 'olist',
21
+ 'img', 'table', 'tr', 'th', 'td', 'quote', 'code', 'p',
22
+ ]);
23
+ const SINGLE_TAGS = new Set(['*', 'hr']);
24
+ /** Unknown or unbalanced BBCode, as findings. Pure. */
25
+ export function checkBbcode(text) {
26
+ const problems = [];
27
+ const stack = [];
28
+ for (const match of text.matchAll(/\[(\/?)([a-z0-9*]+)(?:=[^\]]*)?\]/gi)) {
29
+ const closing = match[1] === '/';
30
+ const tag = match[2].toLowerCase();
31
+ if (SINGLE_TAGS.has(tag)) {
32
+ if (closing && tag !== '*')
33
+ problems.push(`[/${tag}] has no opening form.`);
34
+ continue;
35
+ }
36
+ if (!PAIRED_TAGS.has(tag)) {
37
+ problems.push(`[${tag}] is not Steam BBCode.`);
38
+ continue;
39
+ }
40
+ if (!closing) {
41
+ stack.push(tag);
42
+ }
43
+ else if (stack[stack.length - 1] === tag) {
44
+ stack.pop();
45
+ }
46
+ else {
47
+ problems.push(`[/${tag}] closes ${stack.length > 0 ? `[${stack[stack.length - 1]}]` : 'nothing'}.`);
48
+ }
49
+ }
50
+ for (const tag of stack)
51
+ problems.push(`[${tag}] is never closed.`);
52
+ return problems;
53
+ }
54
+ /** Links a reviewer will reject: anything off Steam. Pure. */
55
+ export function externalLinks(text) {
56
+ const links = [...text.matchAll(/https?:\/\/[^\s\]"'<>]+/gi)].map((match) => match[0]);
57
+ return links.filter((link) => !/^https?:\/\/([a-z0-9-]+\.)?(steampowered\.com|steamcommunity\.com)\//i.test(link));
58
+ }
59
+ /** Words that sell the game somewhere else, or price it, inside Steam copy. Pure. */
60
+ export function offStoreMentions(text) {
61
+ const hits = [];
62
+ for (const pattern of [/bitmagic\.ai/i, /play (it )?(for )?free (at|on)/i, /\bpoki\b/i, /app store/i, /google play/i, /itch\.io/i]) {
63
+ const match = pattern.exec(text);
64
+ if (match)
65
+ hits.push(match[0]);
66
+ }
67
+ return hits;
68
+ }
69
+ const REQUIREMENT_FIELDS = ['os', 'processor', 'memory', 'graphics', 'storage'];
70
+ function str(value) {
71
+ return typeof value === 'string' ? value.trim() : '';
72
+ }
73
+ /**
74
+ * Every rule `steam check` holds the copy to. `knownTags` is Steam's current tag list, or null
75
+ * when it could not be fetched — then tag names go unchecked rather than failing offline.
76
+ */
77
+ export function checkStoreCopy(copy, knownTags) {
78
+ const findings = [];
79
+ const error = (field, message) => { findings.push({ level: 'error', field, message }); };
80
+ const warn = (field, message) => { findings.push({ level: 'warning', field, message }); };
81
+ if (str(copy.name) === '')
82
+ error('name', 'The game needs a name.');
83
+ const short = str(copy.shortDescription);
84
+ if (short === '')
85
+ error('shortDescription', 'Empty. One or two sentences on what the player does and why it is fun.');
86
+ else if ([...short].length > SHORT_DESCRIPTION_MAX) {
87
+ error('shortDescription', `${[...short].length} characters; Steam allows ${SHORT_DESCRIPTION_MAX}.`);
88
+ }
89
+ if (/\[[a-z/*]/i.test(short))
90
+ error('shortDescription', 'BBCode is not rendered in the short description — plain text only.');
91
+ const about = str(copy.about);
92
+ if (about === '')
93
+ error('about', 'Empty. "About This Game" is the body of the store page.');
94
+ else if (about.length < ABOUT_MIN_CHARS) {
95
+ warn('about', `${about.length} characters reads as a stub; describe the modes, the content and what makes it different.`);
96
+ }
97
+ for (const problem of checkBbcode(about))
98
+ error('about', problem);
99
+ for (const [field, text] of [['shortDescription', short], ['about', about]]) {
100
+ for (const link of externalLinks(text))
101
+ error(field, `Links off Steam are rejected in review: ${link}`);
102
+ for (const hit of offStoreMentions(text))
103
+ warn(field, `"${hit}" points players away from Steam; reviewers flag that.`);
104
+ }
105
+ if ((copy.priceUsd ?? 0) > 0 && /\bfree\b/i.test(`${short} ${about}`)) {
106
+ warn('about', 'The copy says "free" but the game has a price.');
107
+ }
108
+ const tags = Array.isArray(copy.tags) ? copy.tags.map(str).filter((tag) => tag !== '') : [];
109
+ if (tags.length === 0)
110
+ warn('tags', 'No tags. Steamworks asks for up to 20; the first few decide where the game is shown.');
111
+ if (tags.length > MAX_TAGS)
112
+ error('tags', `${tags.length} tags; Steam allows ${MAX_TAGS}.`);
113
+ if (knownTags !== null) {
114
+ for (const tag of tags) {
115
+ if (!knownTags.has(tag.toLowerCase()))
116
+ error('tags', `"${tag}" is not one of Steam's tags.`);
117
+ }
118
+ }
119
+ if (new Set(tags.map((t) => t.toLowerCase())).size !== tags.length)
120
+ error('tags', 'A tag is listed twice.');
121
+ if (str(copy.developer) === '')
122
+ error('developer', 'Name the developer as it should appear on the store page.');
123
+ if (str(copy.publisher) === '')
124
+ error('publisher', 'Name the publisher (often the developer again).');
125
+ const languages = Array.isArray(copy.languages) ? copy.languages : [];
126
+ if (languages.length === 0)
127
+ error('languages', 'List at least one language with interface support.');
128
+ for (const language of languages) {
129
+ if (str(language?.name) === '')
130
+ error('languages', 'A language entry has no name.');
131
+ }
132
+ if (str(copy.ai?.preGenerated) === '') {
133
+ error('ai.preGenerated', 'Steam requires disclosing content made with AI tools. A Bitmagic game always has some — the '
134
+ + 'engine generates code, models, textures and sound — so say what, in the creator\'s words.');
135
+ }
136
+ for (const platform of ['windows', 'macos']) {
137
+ const minimum = copy.systemRequirements?.[platform]?.minimum;
138
+ for (const field of REQUIREMENT_FIELDS) {
139
+ if (str(minimum?.[field]) === '')
140
+ error(`systemRequirements.${platform}.minimum.${field}`, 'Missing.');
141
+ }
142
+ }
143
+ if (copy.releaseDate !== undefined && copy.releaseDate !== '' && !/^\d{4}-\d{2}-\d{2}$/.test(copy.releaseDate)) {
144
+ error('releaseDate', 'Use YYYY-MM-DD, or leave it empty while the date is "Coming soon".');
145
+ }
146
+ if (copy.priceUsd !== undefined && (typeof copy.priceUsd !== 'number' || copy.priceUsd < 0)) {
147
+ error('priceUsd', 'A price in US dollars, 0 for free.');
148
+ }
149
+ return findings;
150
+ }
151
+ /**
152
+ * The defaults the scaffold writes: the game's own name and description, desktop-shell system
153
+ * requirements, English, and empty fields where only the agent and creator can know the answer.
154
+ */
155
+ export function scaffoldStoreCopy(options) {
156
+ const storage = `${Math.max(500, Math.ceil(options.installMb / 100) * 100)} MB available space`;
157
+ return {
158
+ name: options.name,
159
+ shortDescription: options.description.slice(0, SHORT_DESCRIPTION_MAX),
160
+ about: '',
161
+ tags: [],
162
+ genres: [],
163
+ developer: '',
164
+ publisher: '',
165
+ languages: [{ name: 'English', interface: true, fullAudio: false, subtitles: false }],
166
+ controllerSupport: 'none',
167
+ ai: { preGenerated: '', liveGenerated: '' },
168
+ contentNotes: '',
169
+ systemRequirements: {
170
+ windows: {
171
+ minimum: {
172
+ os: 'Windows 10 64-bit',
173
+ processor: 'Dual-core 2.0 GHz',
174
+ memory: '4 GB RAM',
175
+ graphics: options.webgpu ? 'DirectX 12 capable GPU' : 'DirectX 11 capable GPU',
176
+ storage,
177
+ },
178
+ recommended: {
179
+ os: 'Windows 11 64-bit',
180
+ processor: 'Quad-core 3.0 GHz',
181
+ memory: '8 GB RAM',
182
+ graphics: 'Dedicated GPU with DirectX 12 support',
183
+ storage,
184
+ },
185
+ },
186
+ macos: {
187
+ minimum: {
188
+ os: 'macOS 13 Ventura',
189
+ processor: 'Apple Silicon or Intel Core i5',
190
+ memory: '8 GB RAM',
191
+ graphics: 'Metal capable GPU',
192
+ storage,
193
+ },
194
+ recommended: {
195
+ os: 'macOS 14 Sonoma',
196
+ processor: 'Apple M1 or newer',
197
+ memory: '16 GB RAM',
198
+ graphics: 'Apple Silicon GPU',
199
+ storage,
200
+ },
201
+ },
202
+ },
203
+ };
204
+ }
205
+ export function readStoreCopy(file) {
206
+ const parsed = readJsonFile(file);
207
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
208
+ return null;
209
+ return parsed;
210
+ }
211
+ export function writeStoreCopy(file, copy) {
212
+ writeJsonAtomic(file, copy);
213
+ }
214
+ export function requireStoreCopy(file) {
215
+ const copy = readStoreCopy(file);
216
+ if (copy === null) {
217
+ throw new CliError(`No store copy at ${file}. Run \`bitmagic steam init\` to scaffold it, then write the copy.`, 3);
218
+ }
219
+ return copy;
220
+ }
221
+ //# sourceMappingURL=store-copy.js.map