@jokerized/decksmith 0.3.0 → 0.3.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 +112 -1
- package/dist/cli.js +3032 -746
- package/dist/deck-player-element.js +1 -0
- package/dist/deck-player.js +1 -0
- package/dist/deck-runtime.js +27 -2
- package/dist/embed.html +243 -0
- package/dist/index.js +3117 -894
- package/dist/mcp.js +2969 -817
- package/dist/types/deck/player-element.d.ts +1 -0
- package/dist/types/deck/player.d.ts +37 -0
- package/dist/types/deck/protocol.d.ts +80 -0
- package/dist/types/deck/runtime.d.ts +36 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +0 -7
- package/dist/types/emit/archetypes/stack.d.ts +2 -0
- package/dist/types/emit/kit.d.ts +33 -0
- package/dist/types/images/providers.d.ts +14 -5
- package/dist/types/index.d.ts +14 -1
- package/dist/types/mcp/tools.d.ts +32 -0
- package/dist/types/net/fetch.d.ts +81 -0
- package/dist/types/pack/media.d.ts +12 -0
- package/dist/types/plan/arc.d.ts +82 -0
- package/dist/types/plan/codex.d.ts +6 -0
- package/dist/types/plan/prompt.d.ts +2 -1
- package/dist/types/prefs.d.ts +1 -0
- package/dist/types/render/ffmpeg.d.ts +0 -14
- package/dist/types/server/pipeline.d.ts +51 -8
- package/dist/types/server/upload.d.ts +29 -5
- package/dist/types/source/assets.d.ts +77 -4
- package/dist/types/source/harvest.d.ts +250 -0
- package/dist/types/source/readability.d.ts +120 -0
- package/dist/types/source/transcode.d.ts +83 -0
- package/dist/types/types.d.ts +291 -0
- package/dist/types/verify/index.d.ts +64 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** One stop, as a host sees it. */
|
|
2
|
+
export interface PlayerStop {
|
|
3
|
+
i: number;
|
|
4
|
+
label: string;
|
|
5
|
+
notes?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class DecksmithPlayer extends HTMLElement {
|
|
8
|
+
#private;
|
|
9
|
+
static observedAttributes: string[];
|
|
10
|
+
/** Every stop the deck reported, empty until `ds-ready`. */
|
|
11
|
+
get stops(): PlayerStop[];
|
|
12
|
+
/** Where the deck is now. */
|
|
13
|
+
get at(): number;
|
|
14
|
+
/** Whether the deck answered the handshake. */
|
|
15
|
+
get connected(): boolean;
|
|
16
|
+
connectedCallback(): void;
|
|
17
|
+
disconnectedCallback(): void;
|
|
18
|
+
attributeChangedCallback(name: string, was: string | null, now: string | null): void;
|
|
19
|
+
next(): void;
|
|
20
|
+
prev(): void;
|
|
21
|
+
go(at: number): void;
|
|
22
|
+
play(on?: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Register the element, once.
|
|
26
|
+
*
|
|
27
|
+
* GUARDED AND PARAMETERISED because `customElements.define` throws on a
|
|
28
|
+
* duplicate name, and a host page that imports this twice — two bundlers, or a
|
|
29
|
+
* hot reload — should get a no-op rather than an exception that takes the page
|
|
30
|
+
* down. The tag is an argument so a consumer whose page already owns
|
|
31
|
+
* `decksmith-player` can mount it under another name.
|
|
32
|
+
*
|
|
33
|
+
* NOT called at import time. A module with a side effect cannot be imported for
|
|
34
|
+
* its types, and `player-element.ts` exists precisely so the side-effecting
|
|
35
|
+
* entry point is a separate file a consumer opts into.
|
|
36
|
+
*/
|
|
37
|
+
export declare function define(tag?: string): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a host page and a deck say to each other.
|
|
3
|
+
*
|
|
4
|
+
* ONE FILE, IMPORTED BY BOTH ENDS, so `tsc` checks the sender against the
|
|
5
|
+
* receiver. The alternative — a string literal in the runtime and a matching one
|
|
6
|
+
* in the element — is two copies of a contract that drift the first time a field
|
|
7
|
+
* is added, and the drift is invisible because `postMessage` takes `any` and a
|
|
8
|
+
* message nobody understands is simply ignored.
|
|
9
|
+
*
|
|
10
|
+
* WHY A BRIDGE AT ALL, rather than hosting the deck in the page. A deck is a
|
|
11
|
+
* whole document with its own fonts, GSAP, KaTeX, a vendored player and a ready
|
|
12
|
+
* gate, and `frameOf` in runtime.ts can only reach a SAME-ORIGIN frame — it
|
|
13
|
+
* returns null otherwise and the runtime merely warns, so a deck served from
|
|
14
|
+
* another origin would navigate perfectly and paint nothing, silently. Keeping
|
|
15
|
+
* the frame keeps that same-origin pair inside one directory, where it is always
|
|
16
|
+
* true, and makes the host link `postMessage`, which does not care about origin.
|
|
17
|
+
*
|
|
18
|
+
* VERSION COUPLING IS THE HAZARD. Every deck published before this existed has
|
|
19
|
+
* no listener, and a deck is a static artifact that outlives the tool that made
|
|
20
|
+
* it. So silence is a SUPPORTED state, not an error: the element times out, says
|
|
21
|
+
* so with a distinct code, and leaves the deck usable — it is still a deck in a
|
|
22
|
+
* frame, and its own keyboard still works.
|
|
23
|
+
*/
|
|
24
|
+
/** The channel name, on every message in both directions. */
|
|
25
|
+
export declare const CHANNEL = "decksmith-deck";
|
|
26
|
+
/** Sent by the host once the frame has loaded, to open the conversation. */
|
|
27
|
+
export interface Hello {
|
|
28
|
+
channel: typeof CHANNEL;
|
|
29
|
+
type: "hello";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The deck's answer, and the only message that establishes the host's origin.
|
|
33
|
+
*
|
|
34
|
+
* NOTHING IS POSTED TO "*" AFTER THIS. A stop carries the slide's speaker
|
|
35
|
+
* notes, and any page on the internet can put a deck in a frame — posting
|
|
36
|
+
* wildcard would hand an arbitrary framer the presenter's notes. The origin is
|
|
37
|
+
* taken from the `hello` event and every later message is addressed to it.
|
|
38
|
+
*/
|
|
39
|
+
export interface Ready {
|
|
40
|
+
channel: typeof CHANNEL;
|
|
41
|
+
type: "ready";
|
|
42
|
+
/** Every stop the deck can land on, in order. */
|
|
43
|
+
stops: {
|
|
44
|
+
i: number;
|
|
45
|
+
label: string;
|
|
46
|
+
notes?: string;
|
|
47
|
+
}[];
|
|
48
|
+
/** Where it is now. */
|
|
49
|
+
at: number;
|
|
50
|
+
}
|
|
51
|
+
/** Where the deck landed, after any move. */
|
|
52
|
+
export interface Stopped {
|
|
53
|
+
channel: typeof CHANNEL;
|
|
54
|
+
type: "stop";
|
|
55
|
+
at: number;
|
|
56
|
+
total: number;
|
|
57
|
+
label: string;
|
|
58
|
+
notes?: string;
|
|
59
|
+
playing: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** What a host may ask for. Deliberately small: everything else is a deck concern. */
|
|
62
|
+
export type Command = {
|
|
63
|
+
channel: typeof CHANNEL;
|
|
64
|
+
type: "next";
|
|
65
|
+
} | {
|
|
66
|
+
channel: typeof CHANNEL;
|
|
67
|
+
type: "prev";
|
|
68
|
+
} | {
|
|
69
|
+
channel: typeof CHANNEL;
|
|
70
|
+
type: "go";
|
|
71
|
+
at: number;
|
|
72
|
+
} | {
|
|
73
|
+
channel: typeof CHANNEL;
|
|
74
|
+
type: "play";
|
|
75
|
+
on: boolean;
|
|
76
|
+
};
|
|
77
|
+
export type FromDeck = Ready | Stopped;
|
|
78
|
+
export type ToDeck = Hello | Command;
|
|
79
|
+
/** A message from us, rather than from anything else sharing the window. */
|
|
80
|
+
export declare function isOurs(data: unknown): data is FromDeck | ToDeck;
|
|
@@ -57,3 +57,39 @@ export declare function parseHash(hash: string): Pos | null;
|
|
|
57
57
|
* longer emit falls back to its slide rather than to nothing.
|
|
58
58
|
*/
|
|
59
59
|
export declare function findStop(stops: readonly Stop[], pos: Pos): number;
|
|
60
|
+
interface Player extends HTMLElement {
|
|
61
|
+
ready?: boolean;
|
|
62
|
+
seek: (t: number) => void;
|
|
63
|
+
pause?: () => void;
|
|
64
|
+
}
|
|
65
|
+
/** Only what we call on a GSAP timeline. */
|
|
66
|
+
interface Seekable {
|
|
67
|
+
seek: (t: number) => void;
|
|
68
|
+
}
|
|
69
|
+
interface Frame {
|
|
70
|
+
doc: Document;
|
|
71
|
+
timelines: Record<string, Seekable>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The composition, reached through the player's iframe.
|
|
75
|
+
*
|
|
76
|
+
* Same-origin only, which means a deck must be served over http — opening
|
|
77
|
+
* `deck.html` from the filesystem gives the iframe an opaque origin and we
|
|
78
|
+
* cannot drive it. `present()` says so out loud rather than rendering blank.
|
|
79
|
+
*/
|
|
80
|
+
export declare function frameOf(player: Player): Frame | null;
|
|
81
|
+
/** One player-page video, keyed in the island by the scene that draws its still. */
|
|
82
|
+
export interface ClipSpec {
|
|
83
|
+
/** Already in embeddable form — `embedUrl` in src/pack/media.ts did that at build time. */
|
|
84
|
+
url: string;
|
|
85
|
+
/** The figure's caption. It titles the frame, which is all a screen reader gets. */
|
|
86
|
+
title: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read the island, defensively, for the same reason `parseNarration` is
|
|
90
|
+
* defensive: a deck built before this existed has no island, and one built by a
|
|
91
|
+
* newer emitter may carry fields this reader has never heard of. Neither may do
|
|
92
|
+
* anything worse than leave the poster alone.
|
|
93
|
+
*/
|
|
94
|
+
export declare function parseClips(json: string | null | undefined): Record<string, ClipSpec>;
|
|
95
|
+
export {};
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A claim from the source, shown next to the figure that backs it.
|
|
3
|
-
*
|
|
4
|
-
* The layout is chosen from the figure's own aspect ratio rather than being a
|
|
5
|
-
* parameter: EXPERIMENT-002 full-bled a 1.98-aspect figure and pushed its
|
|
6
|
-
* caption 200px off-canvas. Only a genuine strip earns the full width.
|
|
7
|
-
*/
|
|
8
1
|
import type { Emitter } from "../kit.js";
|
|
9
2
|
export declare const claimFigure: Emitter<"claim-figure">;
|
|
@@ -25,6 +25,8 @@ import type { BeatOf, Format } from "../../types.js";
|
|
|
25
25
|
import type { Emitter } from "../kit.js";
|
|
26
26
|
import { type Face } from "../svg.js";
|
|
27
27
|
type Params = BeatOf<"stack">["params"];
|
|
28
|
+
/** Where the numerals' right edge lands once the type floor has moved. */
|
|
29
|
+
export declare function numSpine(floor: number): number;
|
|
28
30
|
export interface StackLayout {
|
|
29
31
|
/**
|
|
30
32
|
* The type floor this layout solved against, which is `MIN_FONT` for a flat
|
package/dist/types/emit/kit.d.ts
CHANGED
|
@@ -137,6 +137,39 @@ export interface EmitContext {
|
|
|
137
137
|
* `hyperframes lint` rejects it (`unscoped_gsap_selector`).
|
|
138
138
|
*/
|
|
139
139
|
sid: string;
|
|
140
|
+
/**
|
|
141
|
+
* WHERE THIS SCENE BEGINS ON THE DECK'S OWN CLOCK, in seconds, ALREADY ROUNDED
|
|
142
|
+
* to invariant 10's three places — the identical number the scene wrapper
|
|
143
|
+
* publishes as `data-start`. Write it; do not round it again, because two
|
|
144
|
+
* roundings of one number is how a byte moves.
|
|
145
|
+
*
|
|
146
|
+
* Everything else an archetype emits is scene-relative, and that is the right
|
|
147
|
+
* default: an emitter that knew where it sat in the deck would be an emitter a
|
|
148
|
+
* cut could invalidate. ONE thing is not expressible that way. A `<video>` is
|
|
149
|
+
* timed by the RUNTIME, not by this scene's timeline, and the runtime reads
|
|
150
|
+
* `data-start` as an ABSOLUTE composition second — so a clip that declares a
|
|
151
|
+
* scene-relative start is seeked into a window that has already passed.
|
|
152
|
+
*
|
|
153
|
+
* MEASURED, because the alternative looked right on paper. hyperframes'
|
|
154
|
+
* compiler injects `data-start="0" data-hf-auto-start=""` into a media tag
|
|
155
|
+
* that declares no timing, and its runtime resolves that marker against the
|
|
156
|
+
* enclosing `[data-composition-id]`, which is exactly this scene — so the
|
|
157
|
+
* marker ought to have been enough. Rendered at 0.8.27, on a two-beat deck
|
|
158
|
+
* whose clip is red for 2s, green for 2s then blue for 2s and whose second
|
|
159
|
+
* scene starts at 7s: with the marker the plate is BLUE at composition 9.5s,
|
|
160
|
+
* 10.5s and 12.5s — the clip's last frame, frozen, because the render placed
|
|
161
|
+
* it at second 0 and it had ended before the scene began. With `data-start="7"`
|
|
162
|
+
* the same frames are green, green, blue: clip seconds 2.5, 3.5 and 5.5. Every
|
|
163
|
+
* gate is green over both.
|
|
164
|
+
*
|
|
165
|
+
* OPTIONAL, because the shell is not the only caller: an archetype test calls
|
|
166
|
+
* `emitScene` with a context it builds by hand, and twelve of the thirteen
|
|
167
|
+
* emitters have no use for this. The one that does refuses by name when it is
|
|
168
|
+
* missing rather than inventing a second, i.e. wrong, clock. `planCut` passes
|
|
169
|
+
* a provisional 0 for the same reason it passes provisional scene ids — its
|
|
170
|
+
* pass exists to measure holds, and every scene it emits is thrown away.
|
|
171
|
+
*/
|
|
172
|
+
start: number;
|
|
140
173
|
}
|
|
141
174
|
/**
|
|
142
175
|
* A value in a GSAP vars payload that is JavaScript rather than data.
|
|
@@ -82,11 +82,20 @@ export declare function codexImages(opts?: CodexImagesOptions): ImageProvider;
|
|
|
82
82
|
* something better draws it. Same request, same bytes.
|
|
83
83
|
*/
|
|
84
84
|
export declare function drawSvg(req: ImageRequest): string;
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
/**
|
|
86
|
+
* The size an SVG declares, for `sizeOf` in ./illustrate.ts and for anything
|
|
87
|
+
* else that already imports this module.
|
|
88
|
+
*
|
|
89
|
+
* It USED to live here, as a regex for `viewBox="0 0 %d %d"` over the first 200
|
|
90
|
+
* bytes — four integers, no units, no preamble — which is exactly the SVG
|
|
91
|
+
* `drawSvg` above writes and nothing else. Figure ingest needs to measure an SVG
|
|
92
|
+
* a stranger wrote, so the reader was generalised and moved to src/source/assets.ts
|
|
93
|
+
* beside the other formats. It moved THERE rather than staying here because
|
|
94
|
+
* that module imports nothing but node and the schemas: the reverse would put
|
|
95
|
+
* the Codex runner and this file's OpenAI adapter on the import path of every
|
|
96
|
+
* source parse, for two integers.
|
|
97
|
+
*/
|
|
98
|
+
export { svgSize } from "../source/assets.js";
|
|
90
99
|
/** The last rung. Pure, so it cannot fail, so `illustrate` always finishes. */
|
|
91
100
|
export declare function toolSvg(): ImageProvider;
|
|
92
101
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,12 +8,25 @@ import { type Format, type Source, type Storyboard } from "./types.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export { parseMarkdown } from "./source/markdown.js";
|
|
10
10
|
export type { ParseOptions } from "./source/markdown.js";
|
|
11
|
+
/**
|
|
12
|
+
* A web page in, a markdown document and a directory of files out — the step
|
|
13
|
+
* BEFORE `parseMarkdown`, not a replacement for it. Separate because it needs a
|
|
14
|
+
* browser and the network, which `parseMarkdown` deliberately does not.
|
|
15
|
+
*
|
|
16
|
+
* `attachClips` is exported with it because a harvest is not finished without
|
|
17
|
+
* it: the markdown dialect has no way to say `kind: "clip"`, so a page's videos
|
|
18
|
+
* come back beside the document and this is what puts them into the source. It
|
|
19
|
+
* runs BEFORE `fetchFigures`, which passes a clip through untouched.
|
|
20
|
+
*/
|
|
21
|
+
export { attachClips, harvest, toMarkdown } from "./source/harvest.js";
|
|
22
|
+
export type { Block, Harvested, HarvestedClip, HarvestOptions } from "./source/harvest.js";
|
|
11
23
|
/**
|
|
12
24
|
* Figures referenced by URL, downloaded beside the source. Separate from
|
|
13
25
|
* `parseMarkdown` because it touches the network and a server may want to fetch
|
|
14
26
|
* through its own client instead.
|
|
15
27
|
*/
|
|
16
28
|
export { fetchFigures } from "./source/assets.js";
|
|
29
|
+
export { fetchGuarded, isBlockedAddress } from "./net/fetch.js";
|
|
17
30
|
/**
|
|
18
31
|
* Subset a CJK webfont over the glyphs a deck actually renders. Exported
|
|
19
32
|
* because invariant 9 — a font stack naming a family the bundle does not
|
|
@@ -111,7 +124,7 @@ export { verify } from "./verify/index.js";
|
|
|
111
124
|
* deck was asked for, which a built directory does not carry — and
|
|
112
125
|
* `scanUnusedFigures` folds in only where the source was passed alongside.
|
|
113
126
|
*/
|
|
114
|
-
export { scanBeatCount, scanHeadlines, scanNarrationLead, scanRepeatedObject, scanUnusedFigures, } from "./verify/index.js";
|
|
127
|
+
export { scanBeatCount, scanNarrationDrift, scanPaperArc, scanHeadlines, scanNarrationLead, scanRepeatedObject, scanUnusedFigures, } from "./verify/index.js";
|
|
115
128
|
export { check, parseCheckReport, sampleTimes } from "./verify/check.js";
|
|
116
129
|
export type { CheckOptions } from "./verify/check.js";
|
|
117
130
|
/**
|
|
@@ -26,6 +26,10 @@ export declare const settingsSchema: z.ZodObject<{
|
|
|
26
26
|
normal: "normal";
|
|
27
27
|
dense: "dense";
|
|
28
28
|
}>>;
|
|
29
|
+
genre: z.ZodOptional<z.ZodEnum<{
|
|
30
|
+
general: "general";
|
|
31
|
+
paper: "paper";
|
|
32
|
+
}>>;
|
|
29
33
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
30
34
|
slides: z.ZodOptional<z.ZodInt>;
|
|
31
35
|
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -63,6 +67,10 @@ export declare const estimateSchema: z.ZodObject<{
|
|
|
63
67
|
normal: "normal";
|
|
64
68
|
dense: "dense";
|
|
65
69
|
}>>;
|
|
70
|
+
genre: z.ZodOptional<z.ZodEnum<{
|
|
71
|
+
general: "general";
|
|
72
|
+
paper: "paper";
|
|
73
|
+
}>>;
|
|
66
74
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
67
75
|
slides: z.ZodOptional<z.ZodInt>;
|
|
68
76
|
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -82,6 +90,7 @@ export declare const estimateSchema: z.ZodObject<{
|
|
|
82
90
|
export declare const createSchema: z.ZodObject<{
|
|
83
91
|
document_path: z.ZodOptional<z.ZodString>;
|
|
84
92
|
document_text: z.ZodOptional<z.ZodString>;
|
|
93
|
+
document_url: z.ZodOptional<z.ZodString>;
|
|
85
94
|
settings: z.ZodOptional<z.ZodObject<{
|
|
86
95
|
format: z.ZodOptional<z.ZodEnum<{
|
|
87
96
|
[x: string]: string;
|
|
@@ -101,6 +110,10 @@ export declare const createSchema: z.ZodObject<{
|
|
|
101
110
|
normal: "normal";
|
|
102
111
|
dense: "dense";
|
|
103
112
|
}>>;
|
|
113
|
+
genre: z.ZodOptional<z.ZodEnum<{
|
|
114
|
+
general: "general";
|
|
115
|
+
paper: "paper";
|
|
116
|
+
}>>;
|
|
104
117
|
duration: z.ZodOptional<z.ZodNumber>;
|
|
105
118
|
slides: z.ZodOptional<z.ZodInt>;
|
|
106
119
|
animation_speed: z.ZodOptional<z.ZodNumber>;
|
|
@@ -209,6 +222,25 @@ export declare function deckTools(opts: McpOptions): {
|
|
|
209
222
|
state: import("../server/queue.js").StepState;
|
|
210
223
|
}[];
|
|
211
224
|
log: string[];
|
|
225
|
+
} | {
|
|
226
|
+
harvest_warnings: string[];
|
|
227
|
+
next: string;
|
|
228
|
+
deck_path?: string | undefined;
|
|
229
|
+
slides?: number | undefined;
|
|
230
|
+
duration_seconds?: number | undefined;
|
|
231
|
+
warnings?: string[] | undefined;
|
|
232
|
+
storyboard_path: string;
|
|
233
|
+
error?: import("../server/errors.js").JobError | undefined;
|
|
234
|
+
job_id: string;
|
|
235
|
+
state: import("../server/queue.js").JobState;
|
|
236
|
+
stage: import("../server/queue.js").Stage | undefined;
|
|
237
|
+
queue_position: number | undefined;
|
|
238
|
+
elapsed_seconds: number;
|
|
239
|
+
steps: {
|
|
240
|
+
name: import("../server/queue.js").Stage;
|
|
241
|
+
state: import("../server/queue.js").StepState;
|
|
242
|
+
}[];
|
|
243
|
+
log: string[];
|
|
212
244
|
}>;
|
|
213
245
|
/** Poll, blocking up to `wait_seconds` for the job to move on. */
|
|
214
246
|
status(input: z.infer<typeof statusSchema>): Promise<{
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one guarded door out of this process for a URL somebody else chose.
|
|
3
|
+
*
|
|
4
|
+
* Two holes made this file. `src/source/assets.ts` fetched a figure with a bare
|
|
5
|
+
* `fetch(src)` — no timeout, no size cap, no idea what came back. And
|
|
6
|
+
* `src/server/pipeline.ts` grew an SSRF pre-check, `reachable()`, which resolves
|
|
7
|
+
* the hostname, refuses the private ranges, and then hands the URL to `fetch`,
|
|
8
|
+
* which resolves it again. A pre-check the socket never honours stops the naive
|
|
9
|
+
* class and nothing else: a public host that 302s to 169.254.169.254 walks
|
|
10
|
+
* straight past it, because the address that was checked is not the address that
|
|
11
|
+
* got connected to.
|
|
12
|
+
*
|
|
13
|
+
* WHY THIS IS node:http/node:https AND NOT `fetch`. Exactly one reason, and it
|
|
14
|
+
* is the reason the file exists: `fetch` gives no way to say "connect to THIS
|
|
15
|
+
* address". `http.request` takes a `lookup`, so the address this module
|
|
16
|
+
* validated is the address the socket opens, and the second DNS answer that
|
|
17
|
+
* would have been a rebinding attack is never asked for. Everything else here —
|
|
18
|
+
* the manual redirect walk, the streaming cap, the timeout, the content-type
|
|
19
|
+
* check — could have been written over `fetch`. The pin could not, and
|
|
20
|
+
* pipeline.ts's own comment named that gap and called it unclosable. It was
|
|
21
|
+
* closable; it just needed a different client.
|
|
22
|
+
*
|
|
23
|
+
* WHAT IS STILL OPEN, because a guard that oversells itself is worse than none:
|
|
24
|
+
* - Resolution is `dns.lookup`, the OS resolver, so `/etc/hosts` and any local
|
|
25
|
+
* override are honoured. That is deliberate — it is how this machine resolves
|
|
26
|
+
* every other name — but it does mean a poisoned hosts file is trusted.
|
|
27
|
+
* - A hostname with several A records is refused if ANY of them is blocked, and
|
|
28
|
+
* the socket is pinned to the first. That is stricter than a browser and is
|
|
29
|
+
* the intended trade: a round-robin where one member is internal is not a
|
|
30
|
+
* host this process wants to talk to at all.
|
|
31
|
+
* - Nothing here bounds how many of these run at once. The caller owns that.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isBlockedAddress(ip: string): string | null;
|
|
34
|
+
export interface GuardOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Cap on the body, applied to DECODED bytes — see `readCapped` and `decode`
|
|
37
|
+
* for the two places it is enforced and why it takes both.
|
|
38
|
+
*/
|
|
39
|
+
maxBytes: number;
|
|
40
|
+
/** Whole-call budget: DNS, connect, every hop, and reading the body. */
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
/**
|
|
43
|
+
* Refuse unless the response's bare media type matches. Matched against the
|
|
44
|
+
* type WITHOUT its parameters, so `/^image\//` and `/^text\/html$/` both do
|
|
45
|
+
* what they look like against `text/html; charset=utf-8`.
|
|
46
|
+
*/
|
|
47
|
+
accept?: RegExp;
|
|
48
|
+
/**
|
|
49
|
+
* Extra request headers, lowercased on the way in. `authorization` and
|
|
50
|
+
* `cookie` are dropped the moment a redirect changes origin.
|
|
51
|
+
*/
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* TEST SEAM, and the only way to point this at a server on this machine.
|
|
55
|
+
*
|
|
56
|
+
* It exempts loopback from the address block and lifts the 80/443 rule, which
|
|
57
|
+
* together is exactly what it takes to drive a `node:http` server on an
|
|
58
|
+
* ephemeral port through this module's real code path — see test/net.test.ts,
|
|
59
|
+
* which is the only caller. It relaxes NOTHING else: link-local, the private
|
|
60
|
+
* ranges, the non-http schemes, the hop cap, the size cap, the timeout and the
|
|
61
|
+
* content-type check all still apply, which is what makes those tests evidence
|
|
62
|
+
* about the guard rather than about a bypass of it. Nothing in src passes it.
|
|
63
|
+
*/
|
|
64
|
+
allowLoopback?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface Fetched {
|
|
67
|
+
bytes: Buffer;
|
|
68
|
+
/** The `Content-Type` header as sent, parameters and all — a caller decoding
|
|
69
|
+
* text needs the charset, and dropping it here would lose it for good. */
|
|
70
|
+
contentType: string;
|
|
71
|
+
/** The URL the bytes actually came from, after redirects. What a caller must
|
|
72
|
+
* resolve relative links against, and what it should cache under. */
|
|
73
|
+
url: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fetch `url`, refusing loudly at the first thing that looks wrong.
|
|
77
|
+
*
|
|
78
|
+
* Every refusal names the URL, what was wrong with it, and what to do; a caller
|
|
79
|
+
* surfacing `err.message` to a user is giving them something actionable.
|
|
80
|
+
*/
|
|
81
|
+
export declare function fetchGuarded(url: string, opts: GuardOptions): Promise<Fetched>;
|
|
@@ -37,6 +37,18 @@ export interface MediaPlan {
|
|
|
37
37
|
}
|
|
38
38
|
/** A player URL is one whose host is in the list, or a subdomain of one. */
|
|
39
39
|
export declare function isEmbed(url: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Every origin an embed can come from, deduped.
|
|
42
|
+
*
|
|
43
|
+
* DERIVED FROM THE RULES ABOVE RATHER THAN RESTATED BESIDE THEM, because the
|
|
44
|
+
* served deck's `frame-src` names exactly this list (src/server/http.ts): a host
|
|
45
|
+
* added here and forgotten there is a frame the browser refuses, and CSP refuses
|
|
46
|
+
* it the way CSP refuses everything — an empty rectangle and a line in a console
|
|
47
|
+
* nobody has open.
|
|
48
|
+
*/
|
|
49
|
+
export declare const EMBED_ORIGINS: readonly string[];
|
|
50
|
+
/** The embeddable form of a player page, or undefined when we cannot say. */
|
|
51
|
+
export declare function embedUrl(url: string): string | undefined;
|
|
40
52
|
/**
|
|
41
53
|
* The final policy for one asset.
|
|
42
54
|
*
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The paper arc: what a research-talk deck must open and close with.
|
|
3
|
+
*
|
|
4
|
+
* A research talk has a shape a general explainer does not. It opens on the
|
|
5
|
+
* problem and on the ground the work stands on, and it closes on what the work
|
|
6
|
+
* does NOT do and then on what to take away. DeckSmith's planner writes the
|
|
7
|
+
* opening well already — 13 of 15 committed full-deck runs open with a `title` —
|
|
8
|
+
* and the ending badly: 10 of 15 close with a `callout`, but ZERO of 15 carry
|
|
9
|
+
* two, because the limitation arrives as a subordinate clause inside the
|
|
10
|
+
* conclusion ("The idea works, but the strongest lightweight models remain
|
|
11
|
+
* ahead") rather than as its own slide. Splitting that clause into a slide is
|
|
12
|
+
* the actual behaviour change this module asks for.
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS FILE EXISTS AT ALL, rather than the rule living where it is enforced:
|
|
15
|
+
* three directories read this definition and none of them can own it — the
|
|
16
|
+
* prompt (src/plan/prompt.ts) asks the model for the arc, the scan
|
|
17
|
+
* (src/verify/index.ts) reports where a plan missed it, and the cut
|
|
18
|
+
* (src/plan/select.ts) refuses to delete a beat that carries a role. Restating
|
|
19
|
+
* the rule three times is how the three fall out of step; deriving it from one
|
|
20
|
+
* table is how they cannot.
|
|
21
|
+
*
|
|
22
|
+
* WHAT THIS MODULE DELIBERATELY DOES NOT DO. It never decides that a document is
|
|
23
|
+
* a research paper. `prefs.genre` is declared by the author and nothing here
|
|
24
|
+
* sniffs it — see the field's own note in src/types.ts for the measurement that
|
|
25
|
+
* settled that. And it never requires a beat the source cannot support: RULE 3
|
|
26
|
+
* forbids inventing a baseline or a result, so a source that says nothing about
|
|
27
|
+
* prior work honestly yields no `background` beat, and this file's job is then
|
|
28
|
+
* to report the gap rather than to have manufactured one.
|
|
29
|
+
*/
|
|
30
|
+
import type { Prefs } from "../prefs.js";
|
|
31
|
+
import type { Beat, BeatRole, Storyboard } from "../types.js";
|
|
32
|
+
/** Every structural job, in the order a deck performs them. */
|
|
33
|
+
export declare const ARC_ROLES: readonly BeatRole[];
|
|
34
|
+
/**
|
|
35
|
+
* Roles required at a given deck length, and why the list shortens.
|
|
36
|
+
*
|
|
37
|
+
* Four reserved slides out of five is not a deck, it is a table of contents. At
|
|
38
|
+
* `--duration 60` a deck is about five beats, so asking for the full arc there
|
|
39
|
+
* is asking for something nobody could deliver, and a gate that fires on the
|
|
40
|
+
* impossible is one people learn to ignore. The thresholds:
|
|
41
|
+
*
|
|
42
|
+
* n >= 8 the full arc. 8 is this project's own definition of a full-deck
|
|
43
|
+
* run — the corpus count of "15 committed full-deck plans" is beats
|
|
44
|
+
* >= 8 — not a number chosen here.
|
|
45
|
+
* 5..7 the ending only. An `intro` is what the planner already writes
|
|
46
|
+
* unprompted 13 times in 15, and `background` is the beat most likely
|
|
47
|
+
* to have no source material behind it, so those two are the ones to
|
|
48
|
+
* give up first when the budget is short.
|
|
49
|
+
* n < 5 nothing. There is no room for a shape.
|
|
50
|
+
*
|
|
51
|
+
* Read off the beat count the plan actually came back with, not off
|
|
52
|
+
* `prefs.slides`: the floor the author asked for and the deck the planner
|
|
53
|
+
* returned are different numbers, and `scanBeatCount` already owns the gap
|
|
54
|
+
* between them.
|
|
55
|
+
*/
|
|
56
|
+
export declare function requiredRoles(beatCount: number): readonly BeatRole[];
|
|
57
|
+
/** Whether the paper arc was asked for at all. Declared, never sniffed. */
|
|
58
|
+
export declare function paperArcRequested(prefs: Pick<Prefs, "genre">): boolean;
|
|
59
|
+
/** The beats carrying a structural role, by role. Later duplicates are reported, not kept. */
|
|
60
|
+
export declare function arcBeats(storyboard: Storyboard): Map<BeatRole, Beat[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Ids of every beat carrying a role — what the cut refuses to release.
|
|
63
|
+
*
|
|
64
|
+
* Takes a beat list rather than a Storyboard because the cut works over the
|
|
65
|
+
* surviving beats, not over the plan.
|
|
66
|
+
*/
|
|
67
|
+
export declare function arcIds(beats: readonly Beat[]): Set<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Where a plan departs from the arc it was asked for, as sentences.
|
|
70
|
+
*
|
|
71
|
+
* ORDER IS CHECKED, NOT JUST PRESENCE, and only where order is the point. The
|
|
72
|
+
* user's requirement is specifically that the deck END on the conclusion with
|
|
73
|
+
* limitations immediately before it — an ending is a position, not a topic — so
|
|
74
|
+
* those two are checked against the last two slots. The opening pair is checked
|
|
75
|
+
* for presence and for being early, because "the first couple of slides" is a
|
|
76
|
+
* region rather than an index, and a deck that opens title, problem, background
|
|
77
|
+
* is not wrong.
|
|
78
|
+
*
|
|
79
|
+
* Returns an empty array when the arc was not requested, when the deck is too
|
|
80
|
+
* short to carry it, or when the plan satisfied it.
|
|
81
|
+
*/
|
|
82
|
+
export declare function arcProblems(storyboard: Storyboard, prefs: Pick<Prefs, "genre" | "slides">): string[];
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Prefs } from "../prefs.js";
|
|
2
2
|
import { type Source, type Storyboard } from "../types.js";
|
|
3
|
+
/** The schema for one run. `role` is present only when the paper arc was asked for. */
|
|
4
|
+
export declare function schemaFor(prefs: Pick<Prefs, "genre">): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* The default-preferences schema, unchanged and still exported: `general` hides
|
|
7
|
+
* `role`, so these bytes are what they have always been.
|
|
8
|
+
*/
|
|
3
9
|
export declare const SCHEMA: unknown;
|
|
4
10
|
export interface CodexOptions {
|
|
5
11
|
/** Left unset by default: use whatever model the user's Codex is configured for. */
|
|
@@ -48,6 +48,7 @@ export declare const SYSTEM: string;
|
|
|
48
48
|
*
|
|
49
49
|
* The figure block carries two facts beyond the caption — the section the image
|
|
50
50
|
* sat under and the prose that refers to it — because a planner that cannot see
|
|
51
|
-
* the picture has nothing else to decide what the picture is FOR.
|
|
51
|
+
* the picture has nothing else to decide what the picture is FOR. A clip carries
|
|
52
|
+
* one more: what the deck can actually show of it, which is a still.
|
|
52
53
|
*/
|
|
53
54
|
export declare function renderSource(source: Source): string;
|
package/dist/types/prefs.d.ts
CHANGED
|
@@ -60,20 +60,6 @@ export interface AudioInput {
|
|
|
60
60
|
file: string;
|
|
61
61
|
delayMs: number;
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Delay every segment onto one track and sum them.
|
|
65
|
-
*
|
|
66
|
-
* `amix` with `normalize=0` sums rather than averaging: the default divides by
|
|
67
|
-
* the input count, which on a 37-segment deck would render the narration 31 dB
|
|
68
|
-
* down and sound exactly like a bug in the TTS. The segments never overlap — the
|
|
69
|
-
* timing model gives each one the video's undivided attention — so summing is
|
|
70
|
-
* safe. `dropout_transition=0` stops amix ramping the gain as inputs end.
|
|
71
|
-
*
|
|
72
|
-
* Every input is resampled and laid out identically first, because amix refuses
|
|
73
|
-
* a mismatch and edge-tts emits 24 kHz mono while the video wants 48 kHz.
|
|
74
|
-
* `apad` runs the track out to the video's length so the mux does not have to
|
|
75
|
-
* choose between a short audio stream and `-shortest` truncating the picture.
|
|
76
|
-
*/
|
|
77
63
|
export declare function audioGraph(inputs: readonly AudioInput[], seconds: number,
|
|
78
64
|
/**
|
|
79
65
|
* ffmpeg input index of the first mp3. 1 when the video is the only other
|