@jokerized/decksmith 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ storyboard rendered to a 9:16 short or an MP4 without replanning.
6
6
 
7
7
  Rendering is bought, not built. A deck is a
8
8
  [HyperFrames](https://github.com/heygen-com/hyperframes) composition (Apache-2.0, pinned
9
- at 0.8.35), so the animation runtime, the headless capture and the FFmpeg encode are
9
+ at 0.8.43), so the animation runtime, the headless capture and the FFmpeg encode are
10
10
  upstream's. What DeckSmith owns is the part nothing else does well: turning a document
11
11
  into a *good explanation*, and being able to check that the explanation is true to its
12
12
  source.
@@ -420,9 +420,12 @@ not a polish gap:
420
420
 
421
421
  - **No authentication and no TLS.** Anyone who can reach the port can spend your Codex
422
422
  quota. This is why the default bind is loopback.
423
- - **No CSRF defence.** `POST /api/jobs` is `multipart/form-data`, which is a CORS-*simple*
424
- request: any website a victim visits can make their browser submit a job. There is no
425
- token and no `Origin` check. Fix that before `DECKSMITH_HOST` is ever anything else.
423
+ - **DNS rebinding is refused only on a loopback bind.** A write another site's page makes
424
+ a browser send is refused on any bind: `Sec-Fetch-Site`, or `Origin` where that is
425
+ missing. But a rebinding page is same-origin to itself, and the only thing that gives it
426
+ away is a `Host` naming something other than the server. On `127.0.0.1` that check is
427
+ on. On any other `DECKSMITH_HOST` the server cannot know its own names, so the check is
428
+ off until it is given an allowlist.
426
429
  - **Codex spend is unmetered per upload.** The per-IP hourly limit is the only brake, and
427
430
  it is per-IP.
428
431
  - **Rate limiting is by `socket.remoteAddress` only.** `X-Forwarded-For` is deliberately
package/dist/cli.js CHANGED
@@ -1092,8 +1092,7 @@ var DESTINATIONS = {
1092
1092
  "video-16x9": [],
1093
1093
  "short-9x16": [
1094
1094
  { name: "YouTube Shorts", maxSeconds: 180 },
1095
- { name: "Instagram Reels", maxSeconds: 180 },
1096
- { name: "Facebook Reels", maxSeconds: 90 }
1095
+ { name: "Instagram Reels", maxSeconds: 180 }
1097
1096
  ],
1098
1097
  "post-1x1": [{ name: "X (standard account)", maxSeconds: 140 }]
1099
1098
  };
@@ -11547,13 +11546,13 @@ function readCanvas(html) {
11547
11546
  if (width === void 0 || height === void 0 || seconds === void 0) return void 0;
11548
11547
  return { width, height, seconds };
11549
11548
  }
11550
- function profilesFor(width, height) {
11551
- return Object.values(FORMATS).filter((f) => f.width === width && f.height === height);
11549
+ function profilesFor(width, height, formats = FORMATS) {
11550
+ return Object.values(formats).filter((f) => f.width === width && f.height === height);
11552
11551
  }
11553
- function scanBudget(html, storyboard, kept) {
11552
+ function scanBudget(html, storyboard, kept, formats = FORMATS) {
11554
11553
  const canvas = readCanvas(html);
11555
11554
  if (!canvas) return [];
11556
- const profiles = profilesFor(canvas.width, canvas.height);
11555
+ const profiles = profilesFor(canvas.width, canvas.height, formats);
11557
11556
  if (profiles.length === 0) {
11558
11557
  return [
11559
11558
  {
package/dist/index.js CHANGED
@@ -1087,8 +1087,7 @@ var DESTINATIONS = {
1087
1087
  "video-16x9": [],
1088
1088
  "short-9x16": [
1089
1089
  { name: "YouTube Shorts", maxSeconds: 180 },
1090
- { name: "Instagram Reels", maxSeconds: 180 },
1091
- { name: "Facebook Reels", maxSeconds: 90 }
1090
+ { name: "Instagram Reels", maxSeconds: 180 }
1092
1091
  ],
1093
1092
  "post-1x1": [{ name: "X (standard account)", maxSeconds: 140 }]
1094
1093
  };
@@ -10934,13 +10933,13 @@ function readCanvas(html) {
10934
10933
  if (width === void 0 || height === void 0 || seconds === void 0) return void 0;
10935
10934
  return { width, height, seconds };
10936
10935
  }
10937
- function profilesFor(width, height) {
10938
- return Object.values(FORMATS).filter((f) => f.width === width && f.height === height);
10936
+ function profilesFor(width, height, formats = FORMATS) {
10937
+ return Object.values(formats).filter((f) => f.width === width && f.height === height);
10939
10938
  }
10940
- function scanBudget(html, storyboard, kept) {
10939
+ function scanBudget(html, storyboard, kept, formats = FORMATS) {
10941
10940
  const canvas = readCanvas(html);
10942
10941
  if (!canvas) return [];
10943
- const profiles = profilesFor(canvas.width, canvas.height);
10942
+ const profiles = profilesFor(canvas.width, canvas.height, formats);
10944
10943
  if (profiles.length === 0) {
10945
10944
  return [
10946
10945
  {
package/dist/mcp.js CHANGED
@@ -1118,8 +1118,7 @@ var DESTINATIONS = {
1118
1118
  "video-16x9": [],
1119
1119
  "short-9x16": [
1120
1120
  { name: "YouTube Shorts", maxSeconds: 180 },
1121
- { name: "Instagram Reels", maxSeconds: 180 },
1122
- { name: "Facebook Reels", maxSeconds: 90 }
1121
+ { name: "Instagram Reels", maxSeconds: 180 }
1123
1122
  ],
1124
1123
  "post-1x1": [{ name: "X (standard account)", maxSeconds: 140 }]
1125
1124
  };
@@ -2531,6 +2531,29 @@ export interface Destination {
2531
2531
  * Verified by hand, not by network (nothing here may reach one). These move;
2532
2532
  * when one does, the finding it produces names it, so the stale number is
2533
2533
  * findable from the message rather than only from this file.
2534
+ *
2535
+ * LAST CHECKED 2026-09-17, against each platform's own pages where they would
2536
+ * load. And it happened again: FACEBOOK REELS IS GONE, because its 90 was stale
2537
+ * the same way Instagram's once was. Meta's newsroom, 2025-06-17: "Reels on
2538
+ * Facebook will also not have any length or format restrictions." By the rule
2539
+ * above a platform that takes anything adds nothing but slack — and the 90 was
2540
+ * not harmless slack, it was the tightest entry, so it made `near_budget` tell
2541
+ * every short between 1m30s and 3m00s that it was "not postable everywhere",
2542
+ * naming a destination that would have taken it. The 90 had been flagged in
2543
+ * EXPERIMENT-010 as "from memory, not the network" for months before anyone
2544
+ * looked.
2545
+ *
2546
+ * Note what each remaining number MEANS, because they are not the same thing:
2547
+ *
2548
+ * - YouTube Shorts, 180 — a CLASSIFICATION limit. A vertical or square upload of
2549
+ * three minutes or less is a Short; longer is long-form, off the Shorts feed.
2550
+ * YouTube Help, "Understand three-minute YouTube Shorts".
2551
+ * - Instagram Reels, 180 — a DISTRIBUTION limit, not an upload cap. Uploads run
2552
+ * longer, but "we recommend videos to unconnected audiences that are 3 minutes
2553
+ * or less" (Instagram for Creators, FAQ). For a short, reaching people who do
2554
+ * not follow you is the point, so this is the limit that constrains it.
2555
+ * - X, standard account, 140 — NOT verified at source: X's help centre refused
2556
+ * the fetch. Every secondary source agrees and it has not moved in years.
2534
2557
  */
2535
2558
  export declare const DESTINATIONS: Readonly<Record<PresetId, readonly Destination[]>>;
2536
2559
  /** The loosest destination: what the format may produce at all. */
@@ -26,7 +26,7 @@ export declare function readCanvas(html: string): Canvas | undefined;
26
26
  * be its own is a gate people learn to ignore. Today the two 16:9 profiles have
27
27
  * the same (absent) budget, so the reading is exact as well as safe.
28
28
  */
29
- export declare function profilesFor(width: number, height: number): Format[];
29
+ export declare function profilesFor(width: number, height: number, formats?: Readonly<Record<string, Format>>): Format[];
30
30
  /**
31
31
  * The budget finding for one built composition, if there is one.
32
32
  *
@@ -37,5 +37,13 @@ export declare function profilesFor(width: number, height: number): Format[];
37
37
  * length of the beat at the same index, and getting that pairing wrong would
38
38
  * attribute one beat's seconds to another. Absent, it is re-derived with the
39
39
  * flat threshold, which is the same list only while the budget cut nothing.
40
+ *
41
+ * `formats` is a seam for the test and nothing else, and it exists because of
42
+ * what the shipped table stopped containing. `near_budget` fires only when a
43
+ * format's destinations DISAGREE, and since Facebook Reels left `DESTINATIONS`
44
+ * on 2026-09-17 none of them do. A branch no shipped format can reach is a gate
45
+ * that cannot fail — the thing this project keeps finding — so the test hands in
46
+ * a table that disagrees, rather than the rule being deleted and silently absent
47
+ * the next time a real destination is tighter than the rest.
40
48
  */
41
- export declare function scanBudget(html: string, storyboard?: Storyboard, kept?: readonly Beat[]): Finding[];
49
+ export declare function scanBudget(html: string, storyboard?: Storyboard, kept?: readonly Beat[], formats?: Readonly<Record<string, Format>>): Finding[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokerized/decksmith",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Turn a source document into an animated explanation deck.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -63,7 +63,7 @@
63
63
  "commander": "^14.0.1",
64
64
  "fflate": "^0.8.3",
65
65
  "gsap": "^3.14.2",
66
- "hyperframes": "0.8.35",
66
+ "hyperframes": "0.8.43",
67
67
  "katex": "^0.16.11",
68
68
  "puppeteer-core": "^25.3.0",
69
69
  "remark-gfm": "^4.0.1",