@jokerized/decksmith 0.9.0 → 0.9.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 (35) hide show
  1. package/README.md +213 -0
  2. package/dist/cli.js +20037 -14179
  3. package/dist/deck-player-element.js +1 -1
  4. package/dist/deck-player.js +1 -1
  5. package/dist/index.js +10605 -4957
  6. package/dist/mcp.js +1558 -319
  7. package/dist/types/bespoke/art.d.ts +173 -0
  8. package/dist/types/bespoke/cache.d.ts +69 -0
  9. package/dist/types/bespoke/callouts.d.ts +76 -0
  10. package/dist/types/bespoke/contract.d.ts +57 -0
  11. package/dist/types/bespoke/inspect.d.ts +69 -0
  12. package/dist/types/bespoke/pipeline.d.ts +207 -0
  13. package/dist/types/bespoke/probe.d.ts +34 -0
  14. package/dist/types/bespoke/prompt.d.ts +182 -0
  15. package/dist/types/bespoke/references.d.ts +73 -0
  16. package/dist/types/bespoke/repair.d.ts +121 -0
  17. package/dist/types/bespoke/scene.d.ts +97 -0
  18. package/dist/types/bespoke/select.d.ts +52 -0
  19. package/dist/types/bespoke/sheet.d.ts +26 -0
  20. package/dist/types/bespoke/shots.d.ts +128 -0
  21. package/dist/types/bespoke/untangle.d.ts +5 -0
  22. package/dist/types/emit/archetypes/index.d.ts +0 -7
  23. package/dist/types/emit/composition.d.ts +37 -2
  24. package/dist/types/emit/kit.d.ts +17 -0
  25. package/dist/types/emit/themes/pick.d.ts +6 -3
  26. package/dist/types/index.d.ts +17 -0
  27. package/dist/types/plan/codex.d.ts +36 -0
  28. package/dist/types/prefs.d.ts +10 -1
  29. package/dist/types/render/capture.d.ts +6 -0
  30. package/dist/types/render/timing.d.ts +3 -0
  31. package/dist/types/types.d.ts +71 -0
  32. package/dist/types/verify/check.d.ts +1 -1
  33. package/dist/types/verify/index.d.ts +22 -0
  34. package/dist/types/verify/scenes.d.ts +325 -0
  35. package/package.json +2 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The camera of an illustrated scene, owned by the shell: STAGED SHOTS, not
3
+ * pans. Round 3 let each scene move the camera itself and it mostly drifted at
4
+ * 1.1-1.4x — a different gentle pan in every scene, and the founder's bar is
5
+ * an explainer's camera: an establishing shot of the whole picture, a push in
6
+ * on the subject the voice is naming, a cut-like move to the next, and a pull
7
+ * back that reveals the whole again for the summary.
8
+ *
9
+ * So the scene names, per cue, WHICH subject the shot is on (its `shots`), and
10
+ * this file turns that into the camera — one fixed grammar, the same in every
11
+ * scene of every deck, which is also what makes two runs of a deck move alike:
12
+ *
13
+ * 1. ESTABLISHING: the whole picture at scale 1 from the start, for at least
14
+ * `ESTABLISH` seconds of the first cue (a slow 3% creep, so it is alive);
15
+ * 2. PUSH IN on the subject a shot names, starting at its time, `MOVE`s,
16
+ * power3.inOut, framed on the subject's box (padded) at `CLOSE_MIN` to
17
+ * `CAMERA_MAX_SCALE`; a held shot creeps 3% further so it never freezes;
18
+ * 3. a shot on another subject is a move straight there (no pull-out between);
19
+ * 4. REVEAL: back to the whole picture at the start of the last cue (or after
20
+ * the last push has been held `MIN_HOLD`), home before the end frame.
21
+ *
22
+ * Shots closer than `MIN_HOLD` to the previous one are dropped: a shot the
23
+ * audience cannot read is a shake.
24
+ */
25
+ import type { UnitBox } from "./inspect.js";
26
+ /** One entry of a scene's shot list: on cue `cue` (1-based), `at` of the way in, frame `subject` (0 = the whole picture). */
27
+ export interface Shot {
28
+ cue: number;
29
+ at: number;
30
+ subject: number;
31
+ }
32
+ /** A box in the body box's px. */
33
+ export interface Box {
34
+ x: number;
35
+ y: number;
36
+ w: number;
37
+ h: number;
38
+ }
39
+ /** One camera move: from the previous framing to this one, starting at `t`. */
40
+ export interface CamMove {
41
+ t: number;
42
+ dur: number;
43
+ s: number;
44
+ x: number;
45
+ y: number;
46
+ ease: string;
47
+ /** The subject framed (0 = the whole picture). */
48
+ subject: number;
49
+ }
50
+ /** The camera's largest push-in: past it a picture turns to mush. */
51
+ export declare const CAMERA_MAX_SCALE = 2.2;
52
+ /** The smallest scale that is a close shot: below it a push reads as a nudge (round 3's 1.1-1.4). */
53
+ export declare const CLOSE_MIN = 1.6;
54
+ /** Seconds a move takes. */
55
+ export declare const MOVE = 1.1;
56
+ /** The reveal is slower: the picture opens out. */
57
+ export declare const REVEAL = 1.3;
58
+ /** A shot is held at least this long before the next move. */
59
+ export declare const MIN_HOLD = 1.6;
60
+ /** The establishing shot holds at least this long into the first cue. */
61
+ export declare const ESTABLISH = 1.6;
62
+ /**
63
+ * The band at the top of an illustrated scene's box that the picture leaves
64
+ * clear: the subjects' labels go above them (src/bespoke/callouts.ts), and a
65
+ * picture that covered the whole box put its subjects' heads at its top edge,
66
+ * where the only room for a label was across the subject's face (round 4's
67
+ * first full run, en s2: "observations" over a robot's eyes).
68
+ */
69
+ export declare const ART_BAND = 120;
70
+ /** Where the picture sits in the body box: under the band, covering the rest. */
71
+ export declare function artPlacement(box: {
72
+ width: number;
73
+ height: number;
74
+ }): Box;
75
+ /**
76
+ * Where the picture's subjects sit in the body box, when the picture covers
77
+ * its placement (`artPlacement`) with `preserveAspectRatio="xMidYMid slice"`
78
+ * (the shell places it so): scaled to cover and centred, so a share of the
79
+ * picture maps through that scale and offset. A subject cut off by more than
80
+ * half is not pointed at; the rest are clipped to the placement.
81
+ */
82
+ export declare function subjectsInBox(units: readonly UnitBox[], art: {
83
+ width: number;
84
+ height: number;
85
+ }, box: {
86
+ width: number;
87
+ height: number;
88
+ }): Box[];
89
+ /** The inverse of `subjectsInBox` for boxes inside the placement: box px back to shares of the picture. */
90
+ export declare function unitsInPicture(boxes: readonly Box[], art: {
91
+ width: number;
92
+ height: number;
93
+ }, box: {
94
+ width: number;
95
+ height: number;
96
+ }): UnitBox[];
97
+ /** The camera's scale and offset that frame `b` (origin 0 0), held to the box's edges. */
98
+ export declare function frameOn(b: Box, W: number, H: number): {
99
+ s: number;
100
+ x: number;
101
+ y: number;
102
+ };
103
+ /**
104
+ * The default shot list, for a scene whose own is missing or unusable: the
105
+ * subjects in order, one per cue after the first (the first cue is the
106
+ * establishing shot), so every subject the picture has is pointed at once.
107
+ */
108
+ export declare function defaultShots(cues: number, subjects: number): Shot[];
109
+ /**
110
+ * The camera moves for one scene. `cues` on the scene's clock; `duration` the
111
+ * scene's length (the end frame is photographed at `duration - 0.5`).
112
+ */
113
+ export declare function compileShots(shots: readonly Shot[], cues: ReadonlyArray<{
114
+ t0: number;
115
+ t1: number;
116
+ }>, duration: number, subjects: readonly Box[], W: number, H: number): CamMove[];
117
+ /**
118
+ * The camera as GSAP source, for the shell to put before the scene's own
119
+ * script. Every tween is a `fromTo` with explicit from-values and
120
+ * `immediateRender:false`, so a frame never depends on seek history. Between
121
+ * two moves the held shot creeps `CREEP` further in about the point at the
122
+ * centre of the view (x' = W/2 - (W/2 - x)·k), so a hold is never a still
123
+ * frame; the next move starts from where the creep ended. Nothing moves after
124
+ * the reveal: the end frame is the whole picture, exactly home.
125
+ */
126
+ export declare function cameraScript(sid: string, moves: readonly CamMove[], W: number, H: number): string;
127
+ /** What a scene's camera does, for the report and the prompt: one word per move. */
128
+ export declare function shotSummary(moves: readonly CamMove[]): string;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The script with every overlap it can read removed, or undefined when it
3
+ * found none to remove. Bounded: at most 40 fixes.
4
+ */
5
+ export declare function untangle(script: string): string | undefined;
@@ -1,10 +1,3 @@
1
- /**
2
- * The explanatory vocabulary, indexed by archetype.
3
- *
4
- * The mapped type is the point: adding an archetype to `Beat` without an emitter
5
- * beside it fails to compile, which is the only guarantee that keeps the union in
6
- * `types.ts` honest.
7
- */
8
1
  import type { Archetype, Beat } from "../../types.js";
9
2
  import type { EmitContext, Emitter, Scene } from "../kit.js";
10
3
  export declare const emitters: {
@@ -11,11 +11,12 @@
11
11
  * timeline, wraps whatever the emitter returns, and closes the document.
12
12
  */
13
13
  import type { z } from "zod";
14
+ import { type BespokeMap } from "../bespoke/scene.js";
14
15
  import { type Direction } from "../plan/direct.js";
15
16
  import { type Cut } from "../plan/select.js";
16
- import type { Design, Format, NarrationCanvas, Source, Storyboard, segmentSchema } from "../types.js";
17
+ import type { Beat, Design, Format, NarrationCanvas, Source, Storyboard, segmentSchema } from "../types.js";
17
18
  import type { FitManifest } from "./fit.js";
18
- import { type Scene } from "./kit.js";
19
+ import { type EmitContext, type Scene } from "./kit.js";
19
20
  /** A built deck. `page` is present only for navigable formats. */
20
21
  export interface Deck {
21
22
  composition: string;
@@ -129,6 +130,12 @@ export interface DeckOptions {
129
130
  * (`vendorInter` in src/build/files.ts), for the same reason.
130
131
  */
131
132
  fontCss?: string;
133
+ /**
134
+ * `--design v2 --bespoke`: beats drawn by a generated scene, keyed by beat id
135
+ * (src/bespoke/). Absent on every other build, which is what keeps them
136
+ * byte-identical. A deck that carries one also carries `BESPOKE_CSP`.
137
+ */
138
+ bespoke?: BespokeMap;
132
139
  }
133
140
  /**
134
141
  * Both artifacts from one layout pass.
@@ -262,7 +269,35 @@ export declare function speechPlan(open: number, holds: readonly number[], segme
262
269
  starts: number[];
263
270
  end: number;
264
271
  };
272
+ /**
273
+ * Where a bespoke scene's stops go and when its sentences are spoken, worked out
274
+ * with the same functions `layout` stages with — so the cue times the model is
275
+ * given are the times the deck will have. See `bespokeHolds` for why the voice
276
+ * does not move.
277
+ *
278
+ * `ctx` must not carry the bespoke map: this stages the ARCHETYPE first.
279
+ */
280
+ export declare function bespokeStaging(beat: Beat, ctx: EmitContext, segments: readonly Segment[], speed: number): {
281
+ holds: number[];
282
+ open: number;
283
+ };
265
284
  export declare function emitComposition(storyboard: Storyboard, source: Source, format: Format, opts?: DeckOptions): string;
285
+ /**
286
+ * The policy a composition carrying a generated scene runs under.
287
+ *
288
+ * The static walk in src/bespoke/contract.ts refuses every NAME that reaches the
289
+ * network, eval, storage or navigation; this refuses the BEHAVIOUR, so a scene
290
+ * that spelled its way past the walk still gets nothing. No `connect-src`, no
291
+ * frames, no plugins, and no `unsafe-eval` — `Function("…")` assembled from
292
+ * fragments throws here. `'unsafe-inline'` stays because every scene's timeline
293
+ * is an inline script, as it is in every deck; what it buys an attacker is
294
+ * nothing a scene could not already write. Local files only (`'self'`), which is
295
+ * every vendored script, stylesheet, font and figure a deck ships.
296
+ *
297
+ * Only on a deck with a bespoke scene: a `<meta>` in every head would move the
298
+ * bytes of every deck ever built.
299
+ */
300
+ export declare const BESPOKE_CSP: string;
266
301
  /** Shipped beside the deck so a built artifact needs no network to navigate. */
267
302
  export declare const PLAYER_FILE = "hyperframes-player.global.js";
268
303
  /**
@@ -6,6 +6,7 @@
6
6
  * and nothing else. Adding a domain means adding an emitter — the shell never
7
7
  * learns what a camera frustum or an orderbook is.
8
8
  */
9
+ import type { BespokeMap } from "../bespoke/scene.js";
9
10
  import type { Archetype, BeatOf, Design, Format, Source } from "../types.js";
10
11
  import type { Fit } from "./fit.js";
11
12
  import type { Look } from "./look.js";
@@ -251,6 +252,14 @@ export interface EmitContext {
251
252
  * the Director rejects any look whose holds or chrome landing differ.
252
253
  */
253
254
  look?: Look;
255
+ /**
256
+ * `--design v2 --bespoke` only: the beats drawn by a generated scene instead
257
+ * of their archetype, keyed by beat id (src/bespoke/scene.ts). `emitScene`
258
+ * reads it, so every caller that stages a beat — `planCut`, `layout`, the
259
+ * timing manifest — sees the same scene. ABSENT ON EVERY OTHER BUILD, and
260
+ * absent means the archetype, byte for byte.
261
+ */
262
+ bespoke?: BespokeMap;
254
263
  }
255
264
  /**
256
265
  * A value in a GSAP vars payload that is JavaScript rather than data.
@@ -366,6 +375,14 @@ export interface Scene {
366
375
  * seeking that deck navigation performs.
367
376
  */
368
377
  tl: Tween[];
378
+ /**
379
+ * A bespoke scene's generated script, and nothing else's. Run inside this
380
+ * scene's timeline closure as `function (tl, root) { "use strict"; … }`, after
381
+ * `tl` exists and before the shell's own tweens are appended to it. Checked
382
+ * by src/bespoke/contract.ts before it can get here; an archetype never sets
383
+ * it, so every archetype scene is the bytes it was.
384
+ */
385
+ script?: string;
369
386
  /** Statements run as the document parses, before anything else, e.g.
370
387
  * `katex.render(...)`. Anything that CHANGES layout belongs here, so that a
371
388
  * `measure` below reads the finished document. */
@@ -9,11 +9,14 @@
9
9
  *
10
10
  * WEIGHTED BY CONTENT, so the hash is not the only voice. Each pack carries an
11
11
  * affinity for the four archetype families (`ARCHETYPE_FAMILY`): a deck that is
12
- * mostly equations leans to the serif packs, one that is mostly bars and tables
13
- * to the grotesk and drafting ones. The lean is mild — every pack keeps a floor
12
+ * mostly structure leans to the drafting pack, one that is mostly bars and
13
+ * tables to the grotesk one. The lean is mild — every pack keeps a floor
14
14
  * weight — because the founder's complaint is sameness across decks, and a
15
15
  * strong content rule would put every paper of one field on one pack.
16
16
  *
17
+ * DARK ONLY. The pick ranks the dark packs (`PICKED`); a light one is worn only
18
+ * when named. The founder, 2026-10-11: "I prefer dark theme".
19
+ *
17
20
  * Rendezvous hashing: each pack draws `-ln(u) / weight` from its own hash of
18
21
  * (seed, pack) and the smallest draw wins. That is a weighted choice whose
19
22
  * probabilities are exactly the normalised weights, and the full ordering is a
@@ -27,7 +30,7 @@ import { type DeckNarration } from "../composition.js";
27
30
  export declare function fnv1a(text: string): number;
28
31
  /** A pack's weight for this deck: 1, plus its affinity times the family mix. */
29
32
  export declare function packWeights(beats: Storyboard["beats"]): Record<string, number>;
30
- /** Every pack, most-preferred first, for this deck and seed. */
33
+ /** Every pickable pack, most-preferred first, for this deck and seed. */
31
34
  export declare function rankPacks(beats: Storyboard["beats"], seed: string): string[];
32
35
  /** The design generation a build emits. `classic` is v0.8.0, byte for byte. */
33
36
  export type Design = "classic" | "v2";
@@ -1,3 +1,4 @@
1
+ import type { BespokeMap } from "./bespoke/scene.js";
1
2
  import { type DeckNarration } from "./emit/composition.js";
2
3
  import type { Cut } from "./plan/select.js";
3
4
  import { type Design, type Format, type Source, type Storyboard } from "./types.js";
@@ -220,6 +221,17 @@ export { guardTmpdir } from "./tmpdir.js";
220
221
  * consumers restate the halves they need.
221
222
  */
222
223
  export * from "./types.js";
224
+ /**
225
+ * `--design v2 --bespoke`: generated scenes for a few mechanism beats per deck.
226
+ * The pass, its contract checker, its selection rule and its cache are all
227
+ * exported so a host can run them on its own schedule.
228
+ */
229
+ export { BESPOKE_FILE, type BespokeEntry, type BespokeMap, bespokeScene } from "./bespoke/scene.js";
230
+ export { checkFragment, type Fragment, instantiate } from "./bespoke/contract.js";
231
+ export { selectBespoke } from "./bespoke/select.js";
232
+ export { cacheKey, SceneCache } from "./bespoke/cache.js";
233
+ export { type BespokeReport, type BespokeResult, bespokePass, type GateFn, } from "./bespoke/pipeline.js";
234
+ export { browserGate } from "./bespoke/probe.js";
223
235
  /** Where the emitted deck's parts came from and where they went. */
224
236
  export interface BuildResult {
225
237
  /** Absolute path of the directory written. */
@@ -257,6 +269,11 @@ export interface BuildDeckOptions {
257
269
  packSeed?: string;
258
270
  /** Multiplies every duration and hold. 1 leaves the bytes untouched. */
259
271
  speed?: number;
272
+ /**
273
+ * `--design v2` only: beats drawn by a generated scene (src/bespoke/), from
274
+ * `bespokePass`. Absent on every other build, which keeps their bytes.
275
+ */
276
+ bespoke?: BespokeMap;
260
277
  /** Default `FORMATS["deck-16x9"]`. */
261
278
  format?: Format;
262
279
  /** From `narrate`. `dir` is where the mp3s sit relative to `deck.html`. */
@@ -33,6 +33,14 @@ export interface RunnerArgs {
33
33
  * `cwd` to fence the writes to.
34
34
  */
35
35
  sandbox?: "read-only" | "workspace-write";
36
+ /** Images attached to the prompt (`codex exec -i`). The bespoke critique round sends its frames. */
37
+ images?: string[];
38
+ /** The CLI to run. Absent: `codex` on PATH. */
39
+ bin?: string;
40
+ /** Told the run's token count when the CLI reports one. */
41
+ onUsage?: (tokens: number) => void;
42
+ /** Extra `-c key=value` overrides, in order (see `leanCodexConfig`). */
43
+ config?: readonly string[];
36
44
  }
37
45
  /** What the planner needs from the outside world: a prompt in, a final message out. */
38
46
  export type Runner = (args: RunnerArgs) => Promise<void>;
@@ -48,5 +56,33 @@ export declare function codexCommand(args: RunnerArgs): {
48
56
  argv: string[];
49
57
  env?: NodeJS.ProcessEnv;
50
58
  };
59
+ /**
60
+ * Features that give a `codex exec` run TOOLS. A call that is a pure text
61
+ * transform — a prompt in, a schema-held JSON reply out — needs none of them,
62
+ * and with them the agent spends turns on the account's own setup instead:
63
+ * MEASURED 2026-10-08 on one bespoke draft prompt, the default config ran
64
+ * `cat` on the user's global AGENTS.md and an animation skill before
65
+ * answering — 56,679 input tokens over several turns and 135-488s — where the
66
+ * same prompt with these off was one turn of 18,247 input tokens.
67
+ */
68
+ export declare const TOOL_FEATURES: readonly ["shell_tool", "unified_exec", "apps", "plugins", "multi_agent", "browser_use", "computer_use", "image_generation", "skill_search", "tool_suggest"];
69
+ /**
70
+ * `-c` overrides for a tool-less, single-turn `codex exec`: every tool feature
71
+ * off, web search off, and every MCP server the account configured disabled by
72
+ * name (the config's own names, read once per process with `codex mcp list`;
73
+ * a name the config does not define cannot be disabled, only invented). If the
74
+ * list cannot be read, the features alone still remove the shell.
75
+ */
76
+ export declare function leanCodexConfig(bin?: string, list?: (bin: string) => Promise<string>): Promise<string[]>;
77
+ /**
78
+ * `leanCodexConfig` with ONE tool back: the image tool, for the bespoke pass's
79
+ * illustration call (src/bespoke/art.ts). The shell stays off — the agent
80
+ * reports where the tool saved the picture and the caller reads it from there —
81
+ * and so does `view_image`, which would spend the call's tokens looking at a
82
+ * picture the scene call is shown anyway. MEASURED 2026-10-09: 42s and 19k
83
+ * tokens for one picture this way, against 49s and 45k with the agent asked to
84
+ * inspect it.
85
+ */
86
+ export declare function artCodexConfig(bin?: string, list?: (bin: string) => Promise<string>): Promise<string[]>;
51
87
  /** The production `Runner`: `codex exec` on PATH, stdin in, a file out. */
52
88
  export declare function runCodex(args: RunnerArgs): Promise<void>;
@@ -7,9 +7,10 @@ export type Prefs = z.infer<typeof prefsSchema>;
7
7
  * wipe a `rate` the config file set, and `--image-style` must not wipe its
8
8
  * `provider`.
9
9
  */
10
- export type PrefsPatch = Partial<Omit<Prefs, "narration" | "images">> & {
10
+ export type PrefsPatch = Partial<Omit<Prefs, "narration" | "images" | "bespoke">> & {
11
11
  narration?: Partial<Prefs["narration"]>;
12
12
  images?: Partial<Prefs["images"]>;
13
+ bespoke?: Partial<NonNullable<Prefs["bespoke"]>>;
13
14
  };
14
15
  export declare const CONFIG_FILE = "decksmith.config.json";
15
16
  /**
@@ -70,4 +71,12 @@ export interface PrefFlags {
70
71
  imageModel?: string;
71
72
  imageStyle?: string;
72
73
  imageMax?: string | number;
74
+ /** `--bespoke`: generated scenes for a few mechanism beats (v2 only). */
75
+ bespoke?: boolean;
76
+ bespokeCalls?: string | number;
77
+ bespokeSeconds?: string | number;
78
+ bespokeModel?: string;
79
+ bespokeCli?: string;
80
+ bespokeCache?: string;
81
+ bespokeArt?: string | number;
73
82
  }
@@ -107,6 +107,12 @@ export interface DeckPage {
107
107
  }
108
108
  export interface OpenOptions {
109
109
  timeoutMs?: number;
110
+ /**
111
+ * Collect what the page did that a deck must not: script errors, console
112
+ * errors (a CSP refusal is one), and any request or navigation that is not
113
+ * a local file. Registered before the page loads, so load-time ones count.
114
+ */
115
+ watch?: string[];
110
116
  }
111
117
  /**
112
118
  * Open `dir`'s deck in the renderer's own browser, ready to seek.
@@ -48,6 +48,7 @@
48
48
  * rendering a deck whose audio is aimed at the wrong frames.
49
49
  */
50
50
  import type { z } from "zod";
51
+ import type { BespokeMap } from "../bespoke/scene.js";
51
52
  import { type Cue } from "../deck/subtitles.js";
52
53
  import { type DeckNarration } from "../emit/composition.js";
53
54
  import type { Beat, Format, Source, Storyboard, segmentSchema } from "../types.js";
@@ -218,6 +219,8 @@ export interface TimingInput {
218
219
  beats?: readonly Beat[];
219
220
  narration?: DeckNarration;
220
221
  theme?: string;
222
+ /** `DeckOptions.bespoke`, exactly as `emitDeck` was given it. */
223
+ bespoke?: BespokeMap;
221
224
  }
222
225
  /**
223
226
  * One pass over the built deck, producing the manifest `render` consumes.