@bitmagic/cli 0.1.74 → 0.1.75

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 (46) hide show
  1. package/README.md +26 -0
  2. package/assets/skills/building-ads/SKILL.md +98 -0
  3. package/dist/ads/check.d.ts +31 -0
  4. package/dist/ads/check.js +142 -0
  5. package/dist/ads/check.js.map +1 -0
  6. package/dist/ads/client.d.ts +78 -0
  7. package/dist/ads/client.js +146 -0
  8. package/dist/ads/client.js.map +1 -0
  9. package/dist/ads/paths.d.ts +11 -0
  10. package/dist/ads/paths.js +36 -0
  11. package/dist/ads/paths.js.map +1 -0
  12. package/dist/ads/probe.d.ts +12 -0
  13. package/dist/ads/probe.js +44 -0
  14. package/dist/ads/probe.js.map +1 -0
  15. package/dist/ads/publish.d.ts +31 -0
  16. package/dist/ads/publish.js +108 -0
  17. package/dist/ads/publish.js.map +1 -0
  18. package/dist/ads/record.d.ts +19 -0
  19. package/dist/ads/record.js +53 -0
  20. package/dist/ads/record.js.map +1 -0
  21. package/dist/ads/render-plan.d.ts +57 -0
  22. package/dist/ads/render-plan.js +127 -0
  23. package/dist/ads/render-plan.js.map +1 -0
  24. package/dist/ads/render.d.ts +51 -0
  25. package/dist/ads/render.js +212 -0
  26. package/dist/ads/render.js.map +1 -0
  27. package/dist/cli.d.ts +1 -0
  28. package/dist/cli.js +2 -0
  29. package/dist/cli.js.map +1 -1
  30. package/dist/commands/ads.d.ts +5 -0
  31. package/dist/commands/ads.js +240 -0
  32. package/dist/commands/ads.js.map +1 -0
  33. package/dist/event/launch.d.ts +2 -2
  34. package/dist/event/launch.js +2 -2
  35. package/dist/event/launch.js.map +1 -1
  36. package/dist/event/preflight.js +2 -2
  37. package/dist/event/preflight.js.map +1 -1
  38. package/dist/scaffold/ads-skill-files.d.ts +2 -0
  39. package/dist/scaffold/ads-skill-files.js +7 -0
  40. package/dist/scaffold/ads-skill-files.js.map +1 -0
  41. package/dist/scaffold/project-files.js +1 -1
  42. package/dist/scaffold/project.js +2 -0
  43. package/dist/scaffold/project.js.map +1 -1
  44. package/dist/scaffold/skill-files.js +2 -1
  45. package/dist/scaffold/skill-files.js.map +1 -1
  46. package/package.json +4 -4
package/README.md CHANGED
@@ -215,6 +215,11 @@ browser reloads itself on every change and the assets panel shows each generatio
215
215
  | `bitmagic trailer make [--music-prompt "..." \| --music <file> \| --no-music] [--title <t>] [--seconds 30] [--ui] [--ui-keyframe-rate 30] [--publish] [--reset-shots]` | The one-shot: analyze the newest recording, lay a music bed (generated from a prompt — sparks — or your file), write a first cut to `trailer/shots.json` or cut the one you edited, render `<game>-trailer.mp4` (new `-rN` each run), and with `--publish` put it on the CDN and the game page. |
216
216
  | `bitmagic trailer analyze \| beats \| cut \| render \| merge` | The steps of `make` as separate commands: score a recording into highlight windows with contact sheets; detect a track's beat grid; turn `trailer/shots.json` into a frame-exact `edl.json`; render an EDL (`--ui` redraws the HUD, `--ui-keyframe-rate` caps how often it may change, `--logo`, `--music …`); join recordings from separate sittings. |
217
217
  | `bitmagic trailer publish <file.mp4> [--no-attach]` | Upload a trailer (`.mp4`/`.webm`, ≤ 200 MiB, header-checked so a renamed image is refused before upload) to the CDN under `game-media/<gameId>/` and put it at the front of the game page gallery. Re-running replaces the previous trailer (remembered in `.bitmagic/trailer/published.json`); the page is capped at 12 media items. Works before the first publish — the page goes live on the first `bitmagic publish --visibility public`. `--no-attach` uploads only. |
218
+ | `bitmagic ads render [--video <file>] [--portrait <file>] [--image <file>] [--seconds 15] [--start <s>] [--cta "..."] [--no-end-card]` | Cut the media of an ad package from the newest `bitmagic trailer` render (its own end card trimmed off) into `.bitmagic/ads/`: videos at 16:9, 9:16 and 1:1 with an end card naming the game, and stills at 1.91:1, 1:1 and 4:5 from the cover. A source of another shape is blur-filled, never cropped into the HUD; `--portrait` takes a 9:16 trailer for the 9:16 ad. Starts an empty `ads.json` for the copy. Free; needs ffmpeg and Chrome. See **Building ads** below. |
219
+ | `bitmagic ads check` | Check the package in `.bitmagic/ads/` — copy, sizes, lengths, file types — against the rules api-server uploads it under (Google Ads' limits plus its style rules). Exits 1 naming every problem. |
220
+ | `bitmagic ads publish` | Upload the package to Bitmagic under `game-media/<gameId>/` and register it. **Nothing is advertised.** Files unchanged since the last publish are not uploaded again (`.bitmagic/ads/published.json`). Prints the page where a promotion can be bought. |
221
+ | `bitmagic ads buy [--no-open] [--wait]` | Open that page, where the creator chooses a budget, a run length and an audience, sees an estimate and pays with Stripe; the Bitmagic team then runs the campaign, usually within a day. Refuses when there is no uploaded package or the game is not public. `--wait` waits for the payment. |
222
+ | `bitmagic ads status` | The uploaded package and every promotion bought for this game: its state, and how many people the ads brought to the game's page and how many started playing. |
218
223
  | `bitmagic forge --prompt "..." [--city\|--dungeon\|--platformer\|--freeform] [--name <name>] [--resume <jobId>]` | Design and bake a whole playable level into `world.json`. Costs sparks, and takes many minutes — the bake alone can take twenty. `--resume <jobId>` picks a failed run back up from the last step that finished, rather than redoing the whole thing. |
219
224
  | `bitmagic forge --edit "..." [--level <id or name>]` | Change a detail of a level this project forged — a door style, the light fixtures, a prop — without re-forging it. The stored design is patched and only what the change touches is re-made; the level is replaced in place. See [Editing a forged level](#editing-a-forged-level). |
220
225
  | `bitmagic tools <list\|path\|install> [bundle]` | The script bundles the CLI ships for skills that drive Blender, Python or Node (`blender-level`, `unity-import`, `metahuman`): what they are, where this install keeps them, and `install` to copy one into the project's `tools/`. Local, no network, no sparks. |
@@ -656,6 +661,27 @@ records a fly-through with nobody at the keyboard.
656
661
 
657
662
  Needs `ffmpeg` and Google Chrome on this machine; each command says so if one is missing.
658
663
 
664
+ ## Building ads
665
+
666
+ `bitmagic ads` builds everything an ad campaign for the game needs, uploads it, and — only when the
667
+ creator wants to pay for one — opens the page where they buy a promotion that the Bitmagic team runs
668
+ on Google Ads, sending players to the game's page. Ask your agent to "build ads"; the shipped
669
+ `building-ads` skill walks it through:
670
+
671
+ ```bash
672
+ bitmagic ads render # videos and stills from the newest trailer, into .bitmagic/ads/
673
+ # write the headlines and descriptions into .bitmagic/ads/ads.json
674
+ bitmagic ads check # the rules the upload is checked against
675
+ bitmagic ads publish # upload; nothing is advertised
676
+ bitmagic ads buy # opens the page to choose a budget and pay
677
+ bitmagic ads status # visitors and plays per promotion
678
+ ```
679
+
680
+ Everything lives in `.bitmagic/ads/`, outside the publish fingerprint, so building ads never
681
+ invalidates a passing verify. The game must be public before a promotion can be bought, since an ad
682
+ links to its page. Budgets, run lengths, audiences and the estimates are shown on the page, and
683
+ the visitor estimate is an estimate, not a guarantee.
684
+
659
685
  ## The dev view
660
686
 
661
687
  `bitmagic dev` is meant to be started once and left running, beside your agent:
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: building-ads
3
+ description: Build an ad package for this game with bitmagic ads — videos in 16:9, 9:16 and 1:1, three stills, and the headlines and descriptions — upload it to Bitmagic, and, only if the creator wants to pay, open the page where they buy a promotion that the Bitmagic team runs on Google Ads. Use when asked to build ads, make an ad, advertise or promote the game, run a campaign, or get players.
4
+ ---
5
+
6
+ # Building ads
7
+
8
+ `bitmagic ads` turns the game's trailer into everything an ad campaign needs, and uploads it to
9
+ Bitmagic. Building and uploading costs nothing and advertises nothing. Running the ads is a
10
+ separate choice the creator makes and pays for on a web page; the Bitmagic team then runs the
11
+ campaign on Google Ads and sends the players to the game's page.
12
+
13
+ ## The flow
14
+
15
+ 0. **Check that promotion is open:** `bitmagic ads status`. If it answers that game promotion is
16
+ not available yet, stop and tell the creator so; there is nothing to build for yet.
17
+ 1. **The game has to be public before anyone can pay to advertise it**: an ad sends people to its
18
+ page. You can build and upload the package first, but say so if `bitmagic publish
19
+ --visibility public` has not happened yet. Never change the game's visibility without the
20
+ creator's yes.
21
+ 2. **You need a trailer.** `bitmagic ads render` cuts the ads from the newest `bitmagic trailer`
22
+ render. With no trailer, make one first: the `making-a-trailer/` skill beside this one. A
23
+ trailer recorded in portrait (`bitmagic trailer record --resolution 1080x1920`, then
24
+ `bitmagic trailer make`) makes the best 9:16 ad, which is what phone feeds show. Pass it as
25
+ `--portrait <file>`.
26
+ 3. **Render the media:**
27
+
28
+ ```
29
+ bitmagic ads render # 15 s from the start of the newest trailer
30
+ bitmagic ads render --start 8 --seconds 20
31
+ bitmagic ads render --portrait .bitmagic/trailer/<dir>/<portrait-trailer>.mp4
32
+ ```
33
+
34
+ This writes `.bitmagic/ads/`: `video-16x9.mp4`, `video-9x16.mp4`, `video-1x1.mp4` (6 to 60 s, an
35
+ end card with the game's name and "Play now in your browser" fades in at the end), and
36
+ `image-1.91x1.jpg`, `image-1x1.jpg`, `image-4x5.jpg` (from the cover, `.bitmagic/cover.webp`,
37
+ when there is one). A source of another shape is fitted by blurring a copy of it behind it, so
38
+ nothing is cut off. The first ad seconds are the ones that matter: if the trailer opens slowly,
39
+ use `--start` to begin at the action.
40
+ 4. **Look at what you made.** Read the stills. For the videos, take a few frames and look at them
41
+ too, the end card included:
42
+
43
+ ```
44
+ ffmpeg -v error -ss 3 -i .bitmagic/ads/video-9x16.mp4 -frames:v 1 -vf scale=360:-2 /tmp/ad-frame.png
45
+ ```
46
+
47
+ Re-render rather than ship an ad that opens on a menu, a loading screen or a black frame.
48
+ 5. **Write the copy** in `.bitmagic/ads/ads.json` (`render` starts an empty one):
49
+
50
+ ```json
51
+ {
52
+ "headlines": ["Ride the storm swell", "Jet-ski racing on big waves"],
53
+ "longHeadlines": ["Launch off storm waves and race rivals to the lighthouse"],
54
+ "descriptions": ["Carve down each swell for speed, then launch off the crest."]
55
+ }
56
+ ```
57
+
58
+ Write it from `GAME-DESIGN.md` and from what the game really does. The rules are below.
59
+ 6. **Check it:** `bitmagic ads check` names every problem the server would refuse. Fix them and run
60
+ it again until it passes.
61
+ 7. **Upload it:** `bitmagic ads publish`. Nothing is advertised. It prints the page where a
62
+ promotion can be bought. Re-running it after a change uploads only what changed.
63
+ 8. **Ask the creator** whether they want to pay to advertise the game. Tell them what that means:
64
+ they choose a budget (from $10), how many days it runs and who sees it; the page shows an
65
+ estimate of how many people it may bring, which is an estimate, not a promise; the Bitmagic team
66
+ starts the campaign as soon as possible, usually within a day. Show them the stills first.
67
+ 9. **Only if they say yes:** `bitmagic ads buy`. It opens that page in their browser, where they
68
+ choose and pay. You never choose a budget for them, and you never pay. `--wait` waits for the
69
+ payment and reports it.
70
+ 10. **Afterwards:** `bitmagic ads status` shows every promotion, its state, and how many people
71
+ the ads brought to the game's page and how many of them started playing.
72
+
73
+ ## The copy rules
74
+
75
+ `bitmagic ads check` enforces the hard limits. Google refuses ads for the rest, so follow them too.
76
+
77
+ | Field | How many | Longest |
78
+ |---|---|---|
79
+ | `headlines` | 1 to 5 (write at least 3) | 40 characters |
80
+ | `longHeadlines` | 1 to 5 | 90 characters |
81
+ | `descriptions` | 1 to 5 (write at least 2) | 90 characters |
82
+
83
+ - **Every line must be true of this game.** Name what the player does, not what you wish it did. No
84
+ "best", "#1" or "most popular" claims, which Google requires proof of.
85
+ - **No other game or brand names.** Not "like Fortnite", not "Minecraft-style".
86
+ - **No emoji, no "!!", no exclamation mark in a headline, no words in capitals** (RPG and 3D are
87
+ fine; GREAT is not). No URLs or prices: Google shows the link itself.
88
+ - **Each line must stand on its own.** Google mixes headlines and descriptions in any order, so
89
+ none of them can lean on another.
90
+ - Say "free" only when anyone can play the game without paying, which is true of a public
91
+ bitmagic.ai game unless the creator gated it.
92
+
93
+ ## What not to do
94
+
95
+ - Do not run `bitmagic ads buy` because the package is ready. It is the creator's money: ask, and
96
+ run it only on a clear yes.
97
+ - Do not promise a number of players. Point to the estimate on the page and say it is an estimate.
98
+ - Do not edit `.bitmagic/ads/published.json`; it is how `publish` knows what is already uploaded.
@@ -0,0 +1,31 @@
1
+ import { type AdCopy, type AdImageSlot, type AdPackageManifest, type AdVideoSlot } from '@bitmagic/asset-core/ad-package';
2
+ import { type MediaProbe } from './probe.js';
3
+ export type AdFileKind = 'mp4' | 'jpeg' | 'png';
4
+ /** What a file's first bytes say it is, or null. */
5
+ export declare function sniffAdFile(bytes: Uint8Array): AdFileKind | null;
6
+ export declare const CONTENT_TYPES: Record<AdFileKind, string>;
7
+ export interface CheckedFile {
8
+ /** The name inside `.bitmagic/ads/`, e.g. `video-9x16.mp4`. */
9
+ name: string;
10
+ path: string;
11
+ kind: AdFileKind;
12
+ width: number;
13
+ height: number;
14
+ durationSec: number;
15
+ bytes: number;
16
+ }
17
+ export interface AdPackageCheck {
18
+ ok: boolean;
19
+ problems: string[];
20
+ gameName: string;
21
+ copy: AdCopy | null;
22
+ videos: Partial<Record<AdVideoSlot, CheckedFile>>;
23
+ images: Partial<Record<AdImageSlot, CheckedFile>>;
24
+ }
25
+ export interface CheckDeps {
26
+ probe: (file: string) => Promise<MediaProbe>;
27
+ }
28
+ /** The manifest api-server would receive, with `urlFor` deciding each asset's URL. */
29
+ export declare function manifestFrom(gameName: string, copy: AdCopy, videos: Record<AdVideoSlot, CheckedFile>, images: Record<AdImageSlot, CheckedFile>, urlFor: (file: CheckedFile) => string): AdPackageManifest;
30
+ export declare function isComplete<K extends string>(slots: readonly K[], files: Partial<Record<K, CheckedFile>>): files is Record<K, CheckedFile>;
31
+ export declare function checkAdPackage(root: string, deps?: CheckDeps): Promise<AdPackageCheck>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `bitmagic ads check` — is the package in `.bitmagic/ads/` one api-server will accept?
3
+ *
4
+ * The answer comes from the same function api-server runs on an upload (`adManifestProblems` in
5
+ * `@bitmagic/asset-core/ad-package`), fed a manifest built from what is actually on disk: the copy
6
+ * as written, and every file's size and length as ffprobe reads them. So a package that passes here
7
+ * is refused upstream only for something that changed on the way, and the words of any refusal are
8
+ * the same in both places. Files are also sniffed by their first bytes, so a renamed PNG never
9
+ * reaches the bucket as a JPEG.
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import { AD_IMAGE_SLOT_NAMES, AD_PACKAGE_VERSION, AD_VIDEO_SLOT_NAMES, adCopyProblems, adManifestProblems, normalizeAdCopy, } from '@bitmagic/asset-core/ad-package';
14
+ import { gameNameFor } from '../trailer/game-name.js';
15
+ import { adCopyPath, adImagePath, adVideoPath } from './paths.js';
16
+ import { probeMedia } from './probe.js';
17
+ /** Stands in for the CDN while checking locally: every asset "lives" under it. */
18
+ const LOCAL_PREFIX = 'local:';
19
+ /** What a file's first bytes say it is, or null. */
20
+ export function sniffAdFile(bytes) {
21
+ if (bytes.length >= 8 && bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70)
22
+ return 'mp4';
23
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
24
+ return 'jpeg';
25
+ if (bytes.length >= 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
26
+ return 'png';
27
+ return null;
28
+ }
29
+ export const CONTENT_TYPES = {
30
+ mp4: 'video/mp4',
31
+ jpeg: 'image/jpeg',
32
+ png: 'image/png',
33
+ };
34
+ const defaultDeps = { probe: probeMedia };
35
+ /** The parsed copy file, as written (checked later), or null when there is nothing to check. */
36
+ function readCopy(root, problems) {
37
+ const file = adCopyPath(root);
38
+ let raw;
39
+ try {
40
+ raw = fs.readFileSync(file, 'utf-8');
41
+ }
42
+ catch {
43
+ problems.push(`There is no copy yet: write ${path.relative(root, file)} (\`bitmagic ads render\` starts an empty one).`);
44
+ return null;
45
+ }
46
+ try {
47
+ return { parsed: JSON.parse(raw) };
48
+ }
49
+ catch (error) {
50
+ problems.push(`${path.relative(root, file)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
51
+ return null;
52
+ }
53
+ }
54
+ async function checkFile(file, expected, problems, deps) {
55
+ const name = path.basename(file);
56
+ if (!fs.existsSync(file)) {
57
+ problems.push(`${name} is missing — run \`bitmagic ads render\`.`);
58
+ return null;
59
+ }
60
+ const bytes = fs.statSync(file).size;
61
+ const head = new Uint8Array(16);
62
+ const fd = fs.openSync(file, 'r');
63
+ try {
64
+ fs.readSync(fd, head, 0, head.length, 0);
65
+ }
66
+ finally {
67
+ fs.closeSync(fd);
68
+ }
69
+ const kind = sniffAdFile(head);
70
+ if (kind === null || !expected.includes(kind)) {
71
+ problems.push(`${name} is not ${expected.length === 1 ? `an ${expected[0].toUpperCase()}` : 'a JPEG or a PNG'} file.`);
72
+ return null;
73
+ }
74
+ let probe;
75
+ try {
76
+ probe = await deps.probe(file);
77
+ }
78
+ catch (error) {
79
+ problems.push(`${name} cannot be read: ${error instanceof Error ? error.message : String(error)}`);
80
+ return null;
81
+ }
82
+ return { name, path: file, kind, width: probe.width, height: probe.height, durationSec: probe.durationSec, bytes };
83
+ }
84
+ /** The manifest api-server would receive, with `urlFor` deciding each asset's URL. */
85
+ export function manifestFrom(gameName, copy, videos, images, urlFor) {
86
+ const video = (file) => ({
87
+ url: urlFor(file),
88
+ width: file.width,
89
+ height: file.height,
90
+ // ffprobe reports container length to the microsecond; the spec only needs tenths.
91
+ durationSec: Math.round(file.durationSec * 10) / 10,
92
+ bytes: file.bytes,
93
+ });
94
+ const image = (file) => ({ url: urlFor(file), width: file.width, height: file.height, bytes: file.bytes });
95
+ return {
96
+ version: AD_PACKAGE_VERSION,
97
+ gameName,
98
+ copy,
99
+ videos: { landscape: video(videos.landscape), portrait: video(videos.portrait), square: video(videos.square) },
100
+ images: { landscape: image(images.landscape), square: image(images.square), portrait: image(images.portrait) },
101
+ };
102
+ }
103
+ /** A placeholder the checking manifest's copy is swapped out of. */
104
+ const NO_COPY = { headlines: [], longHeadlines: [], descriptions: [] };
105
+ export function isComplete(slots, files) {
106
+ return slots.every((slot) => files[slot] !== undefined);
107
+ }
108
+ export async function checkAdPackage(root, deps = defaultDeps) {
109
+ const problems = [];
110
+ const gameName = gameNameFor(root).slice(0, 80);
111
+ const written = readCopy(root, problems);
112
+ const videos = {};
113
+ for (const slot of AD_VIDEO_SLOT_NAMES) {
114
+ const checked = await checkFile(adVideoPath(root, slot), ['mp4'], problems, deps);
115
+ if (checked)
116
+ videos[slot] = checked;
117
+ }
118
+ const images = {};
119
+ for (const slot of AD_IMAGE_SLOT_NAMES) {
120
+ const checked = await checkFile(adImagePath(root, slot), ['jpeg', 'png'], problems, deps);
121
+ if (checked)
122
+ images[slot] = checked;
123
+ }
124
+ if (written !== null) {
125
+ if (isComplete(AD_VIDEO_SLOT_NAMES, videos) && isComplete(AD_IMAGE_SLOT_NAMES, images)) {
126
+ // The whole manifest api-server would get — with the copy exactly as written, so a malformed
127
+ // file is described in the server's words rather than this one's.
128
+ const manifest = {
129
+ ...manifestFrom(gameName, NO_COPY, videos, images, (file) => `${LOCAL_PREFIX}${file.name}`),
130
+ copy: written.parsed,
131
+ };
132
+ problems.push(...adManifestProblems(manifest, LOCAL_PREFIX));
133
+ }
134
+ else {
135
+ // The missing files are reported already; the copy can still be checked on its own.
136
+ problems.push(...adCopyProblems(written.parsed));
137
+ }
138
+ }
139
+ const copy = written !== null ? normalizeAdCopy(written.parsed) : null;
140
+ return { ok: problems.length === 0, problems, gameName, copy, videos, images };
141
+ }
142
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/ads/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The three api-server calls behind `bitmagic ads` (api-server cli/cli-ads.ts): a signed PUT for
3
+ * one package file, registering the uploaded package, and the game's package and orders.
4
+ *
5
+ * Status mapping is this module's own, as in trailer/client.ts: on these routes a 403 is usually
6
+ * "not your game", not a Bitmagic Pro refusal, and a 503 is usually the feature being switched off
7
+ * (`ads_unavailable`), which deserves its own sentence.
8
+ */
9
+ import type { AdPackageManifest } from '@bitmagic/asset-core/ad-package';
10
+ import type { Environment } from '../config/environments.js';
11
+ import { type ApiDeps } from '../http/client.js';
12
+ export declare const ADS_UPLOAD_URL_PATH = "/api/cli/v1/ads/upload-url";
13
+ export declare const ADS_PACKAGE_PATH = "/api/cli/v1/ads/package";
14
+ export declare const ADS_STATUS_PATH = "/api/cli/v1/ads/status";
15
+ export interface AdUploadTicket {
16
+ uploadUrl: string;
17
+ publicUrl: string;
18
+ /** The exact Content-Type the PUT must carry (the signature binds it). */
19
+ contentType: string;
20
+ }
21
+ export interface RegisteredAdPackage {
22
+ packageId: string;
23
+ /** The page where the creator can choose to pay for ads. */
24
+ promoteUrl: string;
25
+ /** False until the game is published publicly: an ad sends people to its page. */
26
+ promotable: boolean;
27
+ }
28
+ export interface AdOrderMetrics {
29
+ visitors: number;
30
+ plays: number;
31
+ daily: Array<{
32
+ day: string;
33
+ visitors: number;
34
+ plays: number;
35
+ }>;
36
+ }
37
+ export interface AdOrderSummary {
38
+ id: string;
39
+ status: string;
40
+ tier: string;
41
+ days: number;
42
+ audience: string;
43
+ audienceName: string;
44
+ priceCents: number;
45
+ currency: string;
46
+ mediaBudgetCents: number;
47
+ spendCents: number | null;
48
+ createdAt: number;
49
+ paidAt: number | null;
50
+ startedAt: number | null;
51
+ endedAt: number | null;
52
+ metrics: AdOrderMetrics;
53
+ }
54
+ export interface AdStatus {
55
+ package: {
56
+ id: string;
57
+ createdAt: number;
58
+ gameName: string;
59
+ } | null;
60
+ orders: AdOrderSummary[];
61
+ promoteUrl: string;
62
+ promotable: boolean;
63
+ }
64
+ /** One order off the wire, every field checked: an older or newer api-server never crashes `status`. */
65
+ export declare function parseAdOrder(raw: unknown): AdOrderSummary | null;
66
+ /** A signed PUT under the game's `game-media/<gameId>/`, for an MP4, a JPEG or a PNG. */
67
+ export declare function mintAdUploadUrl(environment: Environment, accessToken: string, body: {
68
+ gameId: string;
69
+ contentType: string;
70
+ bytes: number;
71
+ }, deps: ApiDeps): Promise<AdUploadTicket>;
72
+ /** Register the uploaded package. Nothing is advertised by this. */
73
+ export declare function registerAdPackage(environment: Environment, accessToken: string, body: {
74
+ gameId: string;
75
+ manifest: AdPackageManifest;
76
+ }, deps: ApiDeps): Promise<RegisteredAdPackage>;
77
+ /** The game's newest package and every order past checkout, with visitors and plays. */
78
+ export declare function fetchAdStatus(environment: Environment, accessToken: string, gameId: string, deps: ApiDeps): Promise<AdStatus>;
@@ -0,0 +1,146 @@
1
+ import { CliError } from '../errors.js';
2
+ import { proRefusal } from '../http/client.js';
3
+ import { isJsonObject } from '../project/world-json.js';
4
+ import { apiServerHeaders } from '../telemetry/request-headers.js';
5
+ export const ADS_UPLOAD_URL_PATH = '/api/cli/v1/ads/upload-url';
6
+ export const ADS_PACKAGE_PATH = '/api/cli/v1/ads/package';
7
+ export const ADS_STATUS_PATH = '/api/cli/v1/ads/status';
8
+ const str = (value, fallback = '') => (typeof value === 'string' ? value : fallback);
9
+ const num = (value, fallback = 0) => (typeof value === 'number' && Number.isFinite(value) ? value : fallback);
10
+ const numOrNull = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
11
+ function parseMetrics(raw) {
12
+ if (!isJsonObject(raw))
13
+ return { visitors: 0, plays: 0, daily: [] };
14
+ const daily = Array.isArray(raw.daily)
15
+ ? raw.daily.filter(isJsonObject).map((d) => ({ day: str(d.day), visitors: num(d.visitors), plays: num(d.plays) }))
16
+ : [];
17
+ return { visitors: num(raw.visitors), plays: num(raw.plays), daily };
18
+ }
19
+ /** One order off the wire, every field checked: an older or newer api-server never crashes `status`. */
20
+ export function parseAdOrder(raw) {
21
+ if (!isJsonObject(raw) || typeof raw.id !== 'string')
22
+ return null;
23
+ return {
24
+ id: raw.id,
25
+ status: str(raw.status, 'unknown'),
26
+ tier: str(raw.tier),
27
+ days: num(raw.days),
28
+ audience: str(raw.audience),
29
+ audienceName: str(raw.audienceName, str(raw.audience)),
30
+ priceCents: num(raw.priceCents),
31
+ currency: str(raw.currency, 'usd'),
32
+ mediaBudgetCents: num(raw.mediaBudgetCents),
33
+ spendCents: numOrNull(raw.spendCents),
34
+ createdAt: num(raw.createdAt),
35
+ paidAt: numOrNull(raw.paidAt),
36
+ startedAt: numOrNull(raw.startedAt),
37
+ endedAt: numOrNull(raw.endedAt),
38
+ metrics: parseMetrics(raw.metrics),
39
+ };
40
+ }
41
+ async function readJsonBody(response) {
42
+ try {
43
+ const parsed = await response.json();
44
+ return isJsonObject(parsed) ? parsed : {};
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ }
50
+ async function callApi(environment, path, method, accessToken, body, deps) {
51
+ try {
52
+ return await deps.fetch(`${environment.apiUrl}${path}`, {
53
+ method,
54
+ headers: {
55
+ ...apiServerHeaders(),
56
+ Authorization: `Bearer ${accessToken}`,
57
+ Accept: 'application/json',
58
+ ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
59
+ },
60
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
61
+ });
62
+ }
63
+ catch {
64
+ throw new CliError(`Cannot reach api-server at ${environment.apiUrl}.`, 1, 'unreachable');
65
+ }
66
+ }
67
+ async function refusal(response, environment, path, gameId, what) {
68
+ if (response.status === 401) {
69
+ return new CliError('Your session has expired. Run `bitmagic login`.', 1, 'session_expired');
70
+ }
71
+ const clone = response.clone();
72
+ const parsed = await readJsonBody(response);
73
+ if (response.status === 403) {
74
+ if (parsed.code === 'pro_expired' || parsed.code === 'pro_required') {
75
+ const pro = await proRefusal(clone);
76
+ return new CliError(pro.message, 1, pro.code);
77
+ }
78
+ if (parsed.error === 'Game is banned') {
79
+ return new CliError(`Game "${gameId}" is banned, so it cannot be advertised.`, 1, 'banned');
80
+ }
81
+ return new CliError(`You do not own game "${gameId}", so ${what} is not allowed. Check the gameId in bitmagic.json.`, 1, 'not_owner');
82
+ }
83
+ if (response.status === 404) {
84
+ return new CliError(`Game "${gameId}" does not exist in the "${environment.name}" environment. Check the gameId in bitmagic.json.`, 1, 'not_found');
85
+ }
86
+ if (response.status === 400) {
87
+ const problems = Array.isArray(parsed.problems) ? parsed.problems.filter((p) => typeof p === 'string') : [];
88
+ const message = typeof parsed.error === 'string' ? parsed.error : `api-server rejected ${what}.`;
89
+ return new CliError(problems.length > 0 ? `api-server refused ${what}:\n${problems.map((p) => ` - ${p}`).join('\n')}` : `api-server refused ${what}: ${message}`, 1, 'ads_invalid');
90
+ }
91
+ if (response.status === 503) {
92
+ if (parsed.code === 'ads_unavailable') {
93
+ return new CliError(`Game promotion is not available in the "${environment.name}" environment yet.`, 1, 'ads_unavailable');
94
+ }
95
+ return new CliError(`api-server reported uploads are unavailable in the "${environment.name}" environment.`, 1, 'unavailable');
96
+ }
97
+ return new CliError(`api-server returned HTTP ${response.status} for ${path}.`, 1, 'http_error');
98
+ }
99
+ function newerApi() {
100
+ return new CliError('api-server answered in a shape this CLI cannot read — run `bitmagic self-update`.');
101
+ }
102
+ /** A signed PUT under the game's `game-media/<gameId>/`, for an MP4, a JPEG or a PNG. */
103
+ export async function mintAdUploadUrl(environment, accessToken, body, deps) {
104
+ const response = await callApi(environment, ADS_UPLOAD_URL_PATH, 'POST', accessToken, body, deps);
105
+ if (!response.ok)
106
+ throw await refusal(response, environment, ADS_UPLOAD_URL_PATH, body.gameId, 'the ad upload');
107
+ const parsed = await readJsonBody(response);
108
+ if (typeof parsed.uploadUrl !== 'string' || typeof parsed.publicUrl !== 'string')
109
+ throw newerApi();
110
+ return {
111
+ uploadUrl: parsed.uploadUrl,
112
+ publicUrl: parsed.publicUrl,
113
+ contentType: typeof parsed.contentType === 'string' ? parsed.contentType : body.contentType,
114
+ };
115
+ }
116
+ /** Register the uploaded package. Nothing is advertised by this. */
117
+ export async function registerAdPackage(environment, accessToken, body, deps) {
118
+ const response = await callApi(environment, ADS_PACKAGE_PATH, 'POST', accessToken, body, deps);
119
+ if (!response.ok)
120
+ throw await refusal(response, environment, ADS_PACKAGE_PATH, body.gameId, 'the ad package');
121
+ const parsed = await readJsonBody(response);
122
+ if (typeof parsed.packageId !== 'string' || typeof parsed.promoteUrl !== 'string')
123
+ throw newerApi();
124
+ return { packageId: parsed.packageId, promoteUrl: parsed.promoteUrl, promotable: parsed.promotable === true };
125
+ }
126
+ /** The game's newest package and every order past checkout, with visitors and plays. */
127
+ export async function fetchAdStatus(environment, accessToken, gameId, deps) {
128
+ const path = `${ADS_STATUS_PATH}?gameId=${encodeURIComponent(gameId)}`;
129
+ const response = await callApi(environment, path, 'GET', accessToken, undefined, deps);
130
+ if (!response.ok)
131
+ throw await refusal(response, environment, ADS_STATUS_PATH, gameId, 'reading the ads');
132
+ const parsed = await readJsonBody(response);
133
+ if (typeof parsed.promoteUrl !== 'string' || !Array.isArray(parsed.orders))
134
+ throw newerApi();
135
+ const raw = parsed.package;
136
+ const pkg = isJsonObject(raw) && typeof raw.id === 'string'
137
+ ? { id: raw.id, createdAt: num(raw.createdAt), gameName: isJsonObject(raw.manifest) ? str(raw.manifest.gameName) : '' }
138
+ : null;
139
+ return {
140
+ package: pkg,
141
+ orders: parsed.orders.map(parseAdOrder).filter((order) => order !== null),
142
+ promoteUrl: parsed.promoteUrl,
143
+ promotable: parsed.promotable === true,
144
+ };
145
+ }
146
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/ads/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type AdImageSlot, type AdVideoSlot } from '@bitmagic/asset-core/ad-package';
2
+ export declare const ADS_DIR: string;
3
+ export declare function adsDir(root: string): string;
4
+ /** `.bitmagic/ads/ads.json` — the headlines and descriptions, written by the agent. */
5
+ export declare function adCopyPath(root: string): string;
6
+ export declare function adVideoPath(root: string, slot: AdVideoSlot): string;
7
+ export declare function adImagePath(root: string, slot: AdImageSlot): string;
8
+ /** `.bitmagic/ads/published.json` — what the last `bitmagic ads publish` uploaded. */
9
+ export declare function adRecordPath(root: string): string;
10
+ /** Scratch space for one render (end-card PNGs, a grabbed frame); removed when the render ends. */
11
+ export declare function adWorkDir(root: string): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Where `bitmagic ads` keeps an ad package inside a project (docs/creator-ads.md).
3
+ *
4
+ * Everything lives in `<root>/.bitmagic/ads/`: the copy (`ads.json`), the three videos and three
5
+ * stills `bitmagic ads render` writes, and the record of the last upload. Inside `.bitmagic/` means
6
+ * gitignored AND outside the publish fingerprint, so building ads never invalidates a passing
7
+ * `bitmagic verify` — the copy is a marketing decision, not part of the game.
8
+ *
9
+ * File names come from the shared spec (`@bitmagic/asset-core/ad-package`), the same one api-server
10
+ * checks an uploaded package against.
11
+ */
12
+ import * as path from 'path';
13
+ import { AD_COPY_FILE, AD_IMAGE_SLOTS, AD_VIDEO_SLOTS, } from '@bitmagic/asset-core/ad-package';
14
+ export const ADS_DIR = path.join('.bitmagic', 'ads');
15
+ export function adsDir(root) {
16
+ return path.join(root, ADS_DIR);
17
+ }
18
+ /** `.bitmagic/ads/ads.json` — the headlines and descriptions, written by the agent. */
19
+ export function adCopyPath(root) {
20
+ return path.join(adsDir(root), AD_COPY_FILE);
21
+ }
22
+ export function adVideoPath(root, slot) {
23
+ return path.join(adsDir(root), AD_VIDEO_SLOTS[slot].file);
24
+ }
25
+ export function adImagePath(root, slot) {
26
+ return path.join(adsDir(root), AD_IMAGE_SLOTS[slot].file);
27
+ }
28
+ /** `.bitmagic/ads/published.json` — what the last `bitmagic ads publish` uploaded. */
29
+ export function adRecordPath(root) {
30
+ return path.join(adsDir(root), 'published.json');
31
+ }
32
+ /** Scratch space for one render (end-card PNGs, a grabbed frame); removed when the render ends. */
33
+ export function adWorkDir(root) {
34
+ return path.join(adsDir(root), '.work');
35
+ }
36
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/ads/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,cAAc,GAGf,MAAM,iCAAiC,CAAC;AAEzC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AAErD,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAiB;IACzD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAiB;IACzD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,CAAC;AACnD,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface MediaProbe {
2
+ width: number;
3
+ height: number;
4
+ /** 0 for a still image. */
5
+ durationSec: number;
6
+ hasAudio: boolean;
7
+ /** The first video stream's codec, e.g. `h264`, `mjpeg`, `png`, `webp`. */
8
+ codec: string;
9
+ }
10
+ /** ffprobe's JSON for one file, parsed. Pure, so the parsing is testable without ffprobe. */
11
+ export declare function parseProbe(json: string, file: string): MediaProbe;
12
+ export declare function probeMedia(file: string): Promise<MediaProbe>;