vibemovie 0.1.0
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/LICENSE +21 -0
- package/README.md +146 -0
- package/dist/chunk-HI6AAJID.js +6 -0
- package/dist/chunk-JTRTKTPK.js +729 -0
- package/dist/cli.d.ts +42 -0
- package/dist/cli.js +192 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +10 -0
- package/dist/mcp.d.ts +13 -0
- package/dist/mcp.js +80 -0
- package/dist/scenes-CKgKppfK.d.ts +118 -0
- package/package.json +64 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { R as Ratio, T as Template } from './scenes-CKgKppfK.js';
|
|
3
|
+
import '@pooriaarab/vibe-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Package version, kept in sync with package.json (dist ships without it).
|
|
7
|
+
* Lives in its own module so the CLI entry's is-main guard is never
|
|
8
|
+
* code-split into a shared chunk.
|
|
9
|
+
*/
|
|
10
|
+
declare const VERSION = "0.1.0";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* vibemovie CLI — render a session recap from JSON events, or serve MCP.
|
|
14
|
+
*
|
|
15
|
+
* vibemovie render [file.json] [--ratio 16:9|9:16|1:1] [--template documentary|speedrun|meme]
|
|
16
|
+
* [--out recap.html] [--title "my session"]
|
|
17
|
+
* vibemovie mcp start the MCP server on stdio
|
|
18
|
+
* vibemovie --version · vibemovie --help
|
|
19
|
+
*
|
|
20
|
+
* Events are read from the file argument, or stdin when piped. The recap is
|
|
21
|
+
* rendered by the local Hyperframes tier — offline, zero keys.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
declare class CliError extends Error {
|
|
25
|
+
}
|
|
26
|
+
interface CliArgs {
|
|
27
|
+
command: 'render' | 'mcp' | 'help' | 'version';
|
|
28
|
+
/** Positional JSON file for `render` (absent → read stdin). */
|
|
29
|
+
file?: string;
|
|
30
|
+
ratio: Ratio;
|
|
31
|
+
template: Template;
|
|
32
|
+
out: string;
|
|
33
|
+
title?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse argv (already sliced past node + script). Throws CliError on unknown
|
|
37
|
+
* flags, missing values, or invalid enum values.
|
|
38
|
+
*/
|
|
39
|
+
declare function parseArgs(argv: readonly string[]): CliArgs;
|
|
40
|
+
declare function main(argv: readonly string[]): Promise<void>;
|
|
41
|
+
|
|
42
|
+
export { type CliArgs, CliError, VERSION, main, parseArgs };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-HI6AAJID.js";
|
|
5
|
+
import {
|
|
6
|
+
RATIOS,
|
|
7
|
+
TEMPLATES,
|
|
8
|
+
renderMovie
|
|
9
|
+
} from "./chunk-JTRTKTPK.js";
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
import { pathToFileURL } from "url";
|
|
14
|
+
var CliError = class extends Error {
|
|
15
|
+
};
|
|
16
|
+
var HELP = `vibemovie \u2014 your agent coding session as a short recap video
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
vibemovie render [file.json] [options] render a recap (file or stdin)
|
|
20
|
+
vibemovie mcp start the MCP server (stdio)
|
|
21
|
+
vibemovie --version print version
|
|
22
|
+
vibemovie --help show this help
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--ratio 16:9|9:16|1:1 player aspect ratio (default 16:9)
|
|
26
|
+
--template documentary|speedrun|meme caption tone + pacing (default documentary)
|
|
27
|
+
--out <path> output HTML file (default ./vibe-recap.html)
|
|
28
|
+
--title <name> session name on the title card
|
|
29
|
+
|
|
30
|
+
Input: a JSON array of events (or { "events": [...] }). Each event:
|
|
31
|
+
{ "kind": "task-done", "ts": 1720000000000, "payload": { "label": "..." } }
|
|
32
|
+
|
|
33
|
+
The recap is rendered on-device by the Hyperframes tier: offline, zero keys.
|
|
34
|
+
`;
|
|
35
|
+
function takeValue(argv, i, flag, inline) {
|
|
36
|
+
if (inline !== void 0) {
|
|
37
|
+
if (inline.length === 0) throw new CliError(`${flag} needs a value`);
|
|
38
|
+
return { value: inline, next: i };
|
|
39
|
+
}
|
|
40
|
+
const v = argv[i + 1];
|
|
41
|
+
if (v === void 0 || v.startsWith("--")) throw new CliError(`${flag} needs a value`);
|
|
42
|
+
return { value: v, next: i + 1 };
|
|
43
|
+
}
|
|
44
|
+
function parseArgs(argv) {
|
|
45
|
+
if (argv.length === 0) {
|
|
46
|
+
return { command: "help", ratio: "16:9", template: "documentary", out: "./vibe-recap.html" };
|
|
47
|
+
}
|
|
48
|
+
const args = { command: "render", ratio: "16:9", template: "documentary", out: "./vibe-recap.html" };
|
|
49
|
+
let sawCommand = false;
|
|
50
|
+
let sawFile = false;
|
|
51
|
+
for (let i = 0; i < argv.length; i++) {
|
|
52
|
+
const tok = argv[i];
|
|
53
|
+
if (tok === "--help" || tok === "-h") {
|
|
54
|
+
args.command = "help";
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (tok === "--version" || tok === "-V" || tok === "-v") {
|
|
58
|
+
args.command = "version";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (!sawCommand && !tok.startsWith("-")) {
|
|
62
|
+
if (tok === "render" || tok === "mcp") {
|
|
63
|
+
args.command = tok;
|
|
64
|
+
sawCommand = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (tok === "help") {
|
|
68
|
+
args.command = "help";
|
|
69
|
+
sawCommand = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
throw new CliError(`unknown command: ${tok}`);
|
|
73
|
+
}
|
|
74
|
+
if (tok.startsWith("--")) {
|
|
75
|
+
const eq = tok.indexOf("=");
|
|
76
|
+
const flag = eq === -1 ? tok : tok.slice(0, eq);
|
|
77
|
+
if (flag !== "--ratio" && flag !== "--template" && flag !== "--out" && flag !== "--title") {
|
|
78
|
+
throw new CliError(`unknown flag: ${flag}`);
|
|
79
|
+
}
|
|
80
|
+
const inline = eq === -1 ? void 0 : tok.slice(eq + 1);
|
|
81
|
+
const { value, next } = takeValue(argv, i, flag, inline);
|
|
82
|
+
i = next;
|
|
83
|
+
if (flag === "--ratio") {
|
|
84
|
+
if (!RATIOS.includes(value)) {
|
|
85
|
+
throw new CliError(`invalid --ratio "${value}" (expected ${RATIOS.join("|")})`);
|
|
86
|
+
}
|
|
87
|
+
args.ratio = value;
|
|
88
|
+
} else if (flag === "--template") {
|
|
89
|
+
if (!TEMPLATES.includes(value)) {
|
|
90
|
+
throw new CliError(`invalid --template "${value}" (expected ${TEMPLATES.join("|")})`);
|
|
91
|
+
}
|
|
92
|
+
args.template = value;
|
|
93
|
+
} else if (flag === "--out") {
|
|
94
|
+
args.out = value;
|
|
95
|
+
} else if (flag === "--title") {
|
|
96
|
+
args.title = value;
|
|
97
|
+
} else {
|
|
98
|
+
throw new CliError(`unknown flag: ${flag}`);
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (tok.startsWith("-") && tok !== "-") {
|
|
103
|
+
throw new CliError(`unknown flag: ${tok}`);
|
|
104
|
+
}
|
|
105
|
+
if (!sawFile) {
|
|
106
|
+
args.file = tok;
|
|
107
|
+
sawFile = true;
|
|
108
|
+
} else {
|
|
109
|
+
throw new CliError(`unexpected argument: ${tok}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (args.command === "mcp" && (args.file !== void 0 || sawFile)) {
|
|
113
|
+
throw new CliError("mcp takes no input file");
|
|
114
|
+
}
|
|
115
|
+
return args;
|
|
116
|
+
}
|
|
117
|
+
async function readStdin() {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
for await (const chunk of process.stdin) {
|
|
120
|
+
chunks.push(chunk);
|
|
121
|
+
}
|
|
122
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
123
|
+
}
|
|
124
|
+
async function readInput(file) {
|
|
125
|
+
if (file !== void 0 && file !== "-") {
|
|
126
|
+
try {
|
|
127
|
+
return await readFile(file, "utf8");
|
|
128
|
+
} catch {
|
|
129
|
+
throw new CliError(`cannot read ${file}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (process.stdin.isTTY) {
|
|
133
|
+
throw new CliError("no input \u2014 pass a JSON file or pipe events on stdin (try --help)");
|
|
134
|
+
}
|
|
135
|
+
return readStdin();
|
|
136
|
+
}
|
|
137
|
+
function parseEvents(json) {
|
|
138
|
+
let data;
|
|
139
|
+
try {
|
|
140
|
+
data = JSON.parse(json);
|
|
141
|
+
} catch {
|
|
142
|
+
throw new CliError("input is not valid JSON");
|
|
143
|
+
}
|
|
144
|
+
const events = Array.isArray(data) ? data : typeof data === "object" && data !== null && Array.isArray(data.events) ? data.events : null;
|
|
145
|
+
if (events === null) {
|
|
146
|
+
throw new CliError('expected a JSON array of events (or { "events": [...] })');
|
|
147
|
+
}
|
|
148
|
+
return events;
|
|
149
|
+
}
|
|
150
|
+
async function main(argv) {
|
|
151
|
+
const args = parseArgs(argv);
|
|
152
|
+
if (args.command === "help") {
|
|
153
|
+
process.stdout.write(HELP);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (args.command === "version") {
|
|
157
|
+
process.stdout.write(`${VERSION}
|
|
158
|
+
`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (args.command === "mcp") {
|
|
162
|
+
const { startMcpServer } = await import("./mcp.js");
|
|
163
|
+
await startMcpServer();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const input = await readInput(args.file);
|
|
167
|
+
const events = parseEvents(input);
|
|
168
|
+
const result = await renderMovie(events, {
|
|
169
|
+
ratio: args.ratio,
|
|
170
|
+
template: args.template,
|
|
171
|
+
out: args.out,
|
|
172
|
+
...args.title !== void 0 ? { title: args.title } : {}
|
|
173
|
+
});
|
|
174
|
+
process.stdout.write(`\u2713 recap rendered \u2192 ${result.path}
|
|
175
|
+
`);
|
|
176
|
+
process.stdout.write(" hyperframes \xB7 offline \xB7 zero keys\n");
|
|
177
|
+
}
|
|
178
|
+
var invokedAsScript = typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
179
|
+
if (invokedAsScript) {
|
|
180
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
182
|
+
process.stderr.write(`vibemovie: ${msg}
|
|
183
|
+
`);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export {
|
|
188
|
+
CliError,
|
|
189
|
+
VERSION,
|
|
190
|
+
main,
|
|
191
|
+
parseArgs
|
|
192
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { R as Ratio, S as Scene, B as BuildOptions, a as RawEvent } from './scenes-CKgKppfK.js';
|
|
2
|
+
export { D as DiffData, E as EndData, M as MergeData, b as SceneKind, c as TaskRow, d as TasksData, T as Template, e as TermLine, f as TerminalData, g as TitleData, h as Transition, i as buildScenes } from './scenes-CKgKppfK.js';
|
|
3
|
+
import { VibeEvent } from '@pooriaarab/vibe-core';
|
|
4
|
+
export { VibeEvent } from '@pooriaarab/vibe-core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* hyperframes.ts — pure scene-list → self-contained animated HTML renderer.
|
|
8
|
+
*
|
|
9
|
+
* The output is a single .html file with all CSS + JS inline: no CDN, no fonts,
|
|
10
|
+
* no network calls, no external URLs of any kind. Playback is driven entirely by
|
|
11
|
+
* scene timing (every animation is a pure function of the local scene clock), so
|
|
12
|
+
* scrubbing to any timestamp lands on the exact in-between state — and rendering
|
|
13
|
+
* is deterministic: the same scenes always produce byte-identical HTML.
|
|
14
|
+
*
|
|
15
|
+
* The scene engine (timeline, transitions, scrubber, per-scene animators) is
|
|
16
|
+
* adapted from docs/prototype.html.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface RenderOptions {
|
|
20
|
+
/** Player aspect ratio. Default '16:9'. */
|
|
21
|
+
ratio?: Ratio;
|
|
22
|
+
/** Document <title>. Default: the session name from the title scene. */
|
|
23
|
+
title?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render a scene list into a self-contained animated HTML document.
|
|
27
|
+
* PURE and deterministic: identical input → identical output string.
|
|
28
|
+
*/
|
|
29
|
+
declare function renderHyperframes(scenes: readonly Scene[], opts?: RenderOptions): string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @pooriaarab/vibemovie — your agent coding session as a short recap video.
|
|
33
|
+
*
|
|
34
|
+
* v0 ships the Hyperframes tier of the vibe-core video cascade: a recap "movie"
|
|
35
|
+
* is a self-contained animated HTML page rendered 100% on-device — no gen-video
|
|
36
|
+
* model, no API key, no network. The cascade seam is real: gen-video providers
|
|
37
|
+
* (Sora, Wavespeed, …) can later be registered as tier-1/2 providers without
|
|
38
|
+
* touching this API; the consent ledger gates their egress.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { renderMovie } from '@pooriaarab/vibemovie';
|
|
42
|
+
* const { html, path } = await renderMovie(events, { ratio: '9:16', out: 'recap.html' });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
interface RenderMovieOptions extends BuildOptions, RenderOptions {
|
|
47
|
+
/** When set, the HTML is also written to this path. */
|
|
48
|
+
out?: string;
|
|
49
|
+
}
|
|
50
|
+
interface RenderMovieResult {
|
|
51
|
+
/** The self-contained recap HTML (always returned, whether or not `out` was set). */
|
|
52
|
+
html: string;
|
|
53
|
+
/** Absolute or relative path the HTML was written to — only when `opts.out` was set. */
|
|
54
|
+
path?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compile events → scenes → a self-contained animated HTML recap.
|
|
58
|
+
*
|
|
59
|
+
* Rendering goes through the vibe-core cascade with egress disabled and an
|
|
60
|
+
* in-memory consent ledger, so it always resolves to the local Hyperframes
|
|
61
|
+
* runner — this call works offline with zero keys. (The cascade is the seam
|
|
62
|
+
* for future gen-video tiers; they are not registered in v0.)
|
|
63
|
+
*
|
|
64
|
+
* When `opts.out` is set the HTML is also written to disk (the only IO here).
|
|
65
|
+
*/
|
|
66
|
+
declare function renderMovie(events: readonly (VibeEvent | RawEvent)[], opts?: RenderMovieOptions): Promise<RenderMovieResult>;
|
|
67
|
+
|
|
68
|
+
export { BuildOptions, Ratio, RawEvent, type RenderMovieOptions, type RenderMovieResult, type RenderOptions, Scene, renderHyperframes, renderMovie };
|
package/dist/index.js
ADDED
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibemovie MCP server (stdio) — lets an agent render a session recap.
|
|
3
|
+
*
|
|
4
|
+
* Exposes one tool, `render`: { events, ratio?, template?, out? } → the recap
|
|
5
|
+
* HTML (or the output path when `out` is given). Rendering is the local
|
|
6
|
+
* Hyperframes tier, so the tool works offline with zero keys.
|
|
7
|
+
*
|
|
8
|
+
* Uses the SDK's low-level Server with a plain JSON Schema for input — no
|
|
9
|
+
* schema-library dependency beyond the SDK itself.
|
|
10
|
+
*/
|
|
11
|
+
declare function startMcpServer(): Promise<void>;
|
|
12
|
+
|
|
13
|
+
export { startMcpServer };
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VERSION
|
|
3
|
+
} from "./chunk-HI6AAJID.js";
|
|
4
|
+
import {
|
|
5
|
+
RATIOS,
|
|
6
|
+
TEMPLATES,
|
|
7
|
+
renderMovie
|
|
8
|
+
} from "./chunk-JTRTKTPK.js";
|
|
9
|
+
|
|
10
|
+
// src/mcp.ts
|
|
11
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import { pathToFileURL } from "url";
|
|
15
|
+
var RENDER_TOOL = {
|
|
16
|
+
name: "render",
|
|
17
|
+
description: 'Render an agent coding session as a recap "movie" \u2014 a self-contained animated HTML page (Hyperframes tier: offline, zero keys, no data out). Returns the HTML, or the output path when `out` is set.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
events: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: { type: "object" },
|
|
24
|
+
description: 'Session events: [{ "kind": "task-done", "ts": 1720000000000, "payload": { "label": "..." } }, ...]. Known kinds: task-done, tests-pass, tests-fail, error, pr-opened, pr-merged, session-end. Payload fields used: label, durationMin, files, additions, deletions, filesChanged, pr, branch, reviewers, passed.'
|
|
25
|
+
},
|
|
26
|
+
ratio: { type: "string", enum: [...RATIOS], description: "Player aspect ratio (default 16:9)." },
|
|
27
|
+
template: { type: "string", enum: [...TEMPLATES], description: "Caption tone + pacing (default documentary)." },
|
|
28
|
+
title: { type: "string", description: "Session name on the title card." },
|
|
29
|
+
out: { type: "string", description: "Optional path to write the HTML to." }
|
|
30
|
+
},
|
|
31
|
+
required: ["events"]
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function enumArg(value, name, allowed) {
|
|
35
|
+
if (value === void 0) return void 0;
|
|
36
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
37
|
+
throw new Error(`render: "${name}" must be one of ${allowed.join(", ")}`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
async function startMcpServer() {
|
|
42
|
+
const server = new Server({ name: "vibemovie", version: VERSION }, { capabilities: { tools: {} } });
|
|
43
|
+
server.setRequestHandler(ListToolsRequestSchema, () => Promise.resolve({ tools: [RENDER_TOOL] }));
|
|
44
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
45
|
+
if (req.params.name !== "render") {
|
|
46
|
+
throw new Error(`unknown tool: ${req.params.name}`);
|
|
47
|
+
}
|
|
48
|
+
const args = req.params.arguments ?? {};
|
|
49
|
+
if (!Array.isArray(args["events"])) {
|
|
50
|
+
throw new Error('render: "events" must be an array of event objects');
|
|
51
|
+
}
|
|
52
|
+
const ratio = enumArg(args["ratio"], "ratio", RATIOS);
|
|
53
|
+
const template = enumArg(args["template"], "template", TEMPLATES);
|
|
54
|
+
const title = args["title"];
|
|
55
|
+
const out = args["out"];
|
|
56
|
+
if (title !== void 0 && typeof title !== "string") throw new Error('render: "title" must be a string');
|
|
57
|
+
if (out !== void 0 && typeof out !== "string") throw new Error('render: "out" must be a string');
|
|
58
|
+
const result = await renderMovie(args["events"], {
|
|
59
|
+
...ratio !== void 0 ? { ratio } : {},
|
|
60
|
+
...template !== void 0 ? { template } : {},
|
|
61
|
+
...title !== void 0 ? { title } : {},
|
|
62
|
+
...out !== void 0 ? { out } : {}
|
|
63
|
+
});
|
|
64
|
+
const text = result.path !== void 0 ? `vibemovie recap written to ${result.path} (hyperframes \xB7 offline \xB7 zero keys)` : result.html;
|
|
65
|
+
return { content: [{ type: "text", text }] };
|
|
66
|
+
});
|
|
67
|
+
await server.connect(new StdioServerTransport());
|
|
68
|
+
}
|
|
69
|
+
var invokedAsScript = typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
70
|
+
if (invokedAsScript) {
|
|
71
|
+
startMcpServer().catch((err) => {
|
|
72
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73
|
+
process.stderr.write(`vibemovie mcp: ${msg}
|
|
74
|
+
`);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
startMcpServer
|
|
80
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { VibeEvent } from '@pooriaarab/vibe-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scenes.ts — pure events → scene-list compiler.
|
|
5
|
+
*
|
|
6
|
+
* Takes normalized `VibeEvent`s (from vibe-core) or loose `RawEvent`s (anything a
|
|
7
|
+
* harness/logger might emit) and compiles them into an ordered, fully-described
|
|
8
|
+
* scene list for the Hyperframes renderer. No IO, no randomness, no clock — the
|
|
9
|
+
* same events always produce the same scenes, which makes this unit-testable and
|
|
10
|
+
* the rendered HTML deterministic.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type Ratio = '16:9' | '9:16' | '1:1';
|
|
14
|
+
type Template = 'documentary' | 'speedrun' | 'meme';
|
|
15
|
+
type Transition = 'fade' | 'slide' | 'scale' | 'rise';
|
|
16
|
+
type SceneKind = 'title' | 'tasks' | 'diff' | 'terminal' | 'merge' | 'end';
|
|
17
|
+
/**
|
|
18
|
+
* A loosely-shaped event from any source: a harness transcript export, a JSON
|
|
19
|
+
* log, a hand-written fixture. Any field may be missing; known aliases
|
|
20
|
+
* (`type`/`timestamp`) are normalized.
|
|
21
|
+
*/
|
|
22
|
+
interface RawEvent {
|
|
23
|
+
kind?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
ts?: number;
|
|
26
|
+
timestamp?: number | string;
|
|
27
|
+
agent?: string;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
payload?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
interface BuildOptions {
|
|
32
|
+
/** Accepted for API symmetry with the renderer; layout is a render-time concern. */
|
|
33
|
+
ratio?: Ratio;
|
|
34
|
+
/** Caption tone + pacing. Default 'documentary'. */
|
|
35
|
+
template?: Template;
|
|
36
|
+
/** Session name for the title card. Default: basename of the events' cwd. */
|
|
37
|
+
title?: string;
|
|
38
|
+
}
|
|
39
|
+
interface TitleData {
|
|
40
|
+
sessionName: string;
|
|
41
|
+
/** Total session length in whole minutes, counted up during the title scene. */
|
|
42
|
+
totalMinutes: number;
|
|
43
|
+
subtitle: string;
|
|
44
|
+
}
|
|
45
|
+
interface TaskRow {
|
|
46
|
+
label: string;
|
|
47
|
+
durationMin?: number;
|
|
48
|
+
}
|
|
49
|
+
interface TasksData {
|
|
50
|
+
/** Real number of completed tasks (may exceed `tasks.length`). */
|
|
51
|
+
total: number;
|
|
52
|
+
/** Rows actually shown (capped at MAX_TASK_ROWS). */
|
|
53
|
+
tasks: TaskRow[];
|
|
54
|
+
}
|
|
55
|
+
interface DiffData {
|
|
56
|
+
filesChanged: number;
|
|
57
|
+
additions: number;
|
|
58
|
+
deletions: number;
|
|
59
|
+
/** Up to 4 representative file paths. */
|
|
60
|
+
files: string[];
|
|
61
|
+
}
|
|
62
|
+
interface TermLine {
|
|
63
|
+
text: string;
|
|
64
|
+
cls: 'ok' | '';
|
|
65
|
+
}
|
|
66
|
+
interface TerminalData {
|
|
67
|
+
lines: TermLine[];
|
|
68
|
+
}
|
|
69
|
+
interface MergeData {
|
|
70
|
+
pr: number | null;
|
|
71
|
+
branch: string;
|
|
72
|
+
reviewers: number | null;
|
|
73
|
+
merged: boolean;
|
|
74
|
+
}
|
|
75
|
+
interface EndData {
|
|
76
|
+
tagline: string;
|
|
77
|
+
}
|
|
78
|
+
interface SceneBase {
|
|
79
|
+
/** DOM id in the rendered page, `scene-<kind>`. */
|
|
80
|
+
id: string;
|
|
81
|
+
transition: Transition;
|
|
82
|
+
/** Milliseconds on the timeline. */
|
|
83
|
+
duration: number;
|
|
84
|
+
/** Narrator caption shown while the scene is live. */
|
|
85
|
+
caption: string;
|
|
86
|
+
}
|
|
87
|
+
type Scene = (SceneBase & {
|
|
88
|
+
kind: 'title';
|
|
89
|
+
data: TitleData;
|
|
90
|
+
}) | (SceneBase & {
|
|
91
|
+
kind: 'tasks';
|
|
92
|
+
data: TasksData;
|
|
93
|
+
}) | (SceneBase & {
|
|
94
|
+
kind: 'diff';
|
|
95
|
+
data: DiffData;
|
|
96
|
+
}) | (SceneBase & {
|
|
97
|
+
kind: 'terminal';
|
|
98
|
+
data: TerminalData;
|
|
99
|
+
}) | (SceneBase & {
|
|
100
|
+
kind: 'merge';
|
|
101
|
+
data: MergeData;
|
|
102
|
+
}) | (SceneBase & {
|
|
103
|
+
kind: 'end';
|
|
104
|
+
data: EndData;
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Compile events into an ordered scene list.
|
|
108
|
+
*
|
|
109
|
+
* Always emits a title card and an end card; the middle scenes appear only when
|
|
110
|
+
* the events carry the data for them (tasks → task ticks, file/diff stats →
|
|
111
|
+
* animated diff, notable command events → terminal moment, PR event → merge
|
|
112
|
+
* celebration). Empty input yields `[title, end]`.
|
|
113
|
+
*
|
|
114
|
+
* PURE: no IO, no clock, no randomness.
|
|
115
|
+
*/
|
|
116
|
+
declare function buildScenes(events: readonly (VibeEvent | RawEvent)[], opts?: BuildOptions): Scene[];
|
|
117
|
+
|
|
118
|
+
export { type BuildOptions as B, type DiffData as D, type EndData as E, type MergeData as M, type Ratio as R, type Scene as S, type Template as T, type RawEvent as a, type SceneKind as b, type TaskRow as c, type TasksData as d, type TermLine as e, type TerminalData as f, type TitleData as g, type Transition as h, buildScenes as i };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibemovie",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Your agent coding session as a short recap video — Hyperframes (HTML/CSS/JS) with zero gen-video keys, or your own video model. For Claude Code, Codex, Gemini, and other agentic CLIs. Local-first.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Pooria Arab",
|
|
8
|
+
"bin": {
|
|
9
|
+
"vibemovie": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup src/index.ts src/cli.ts src/mcp.ts --format esm --dts --clean",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@pooriaarab/vibe-core": "^0.1.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.11.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.4.0",
|
|
36
|
+
"vitest": "^1.6.0"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/pooriaarab/vibemovie#readme",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/pooriaarab/vibemovie.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/pooriaarab/vibemovie/issues"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"vibe-suite",
|
|
48
|
+
"vibemovie",
|
|
49
|
+
"claude-code",
|
|
50
|
+
"codex",
|
|
51
|
+
"agentic",
|
|
52
|
+
"cli",
|
|
53
|
+
"mcp",
|
|
54
|
+
"video",
|
|
55
|
+
"hyperframes",
|
|
56
|
+
"local-first"
|
|
57
|
+
],
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18"
|
|
63
|
+
}
|
|
64
|
+
}
|