You are browsing as a guest. Sign up (or log in) to start making projects!

VictoryLap

  • 6 Devlogs
  • 14 Total hours

victorylap is a cli tool that turns any git repo into a video. you run npx victorylap inside a project and it reads your readme, file tree and git history, an ai writes a short script about what you built, then hyperframes renders it into an actual mp4 on your own machine. no upload, no render server, no paid api. your computer does the work so the whole thing stays free forever. the idea came from the /brag skill for claude code, which i'm remaking from scratch as a real product instead of a skill. same engine (hyperframes), full credit to the original(https://github.com/latent-spaces/brag), but rebuilt better. my own repo analyzer, a free llm for the script, multiple video templates, and later auth plus a library so you can save and share your videos. basically every dev ships stuff that dies in a github graveyard nobody looks at. this turns the repo itself into something you can actually show people.

Open comments for this post

1h 34m 39s logged

victorylap Devlog #6 (now it has taste and a backup plan)

two things done:

paper theme

the first look was amber terminal, mono type, sharp slides. paper is the opposite: warm cream background, serif ink, a thin red line down the left margin, everything fading in soft. same slots, same wiring, completely different film. –theme paper gets you one, no flag gets you the other. thats the whole point of themes proving out.


model fallback.

the video script is written by a free model on OpenRouter, and free models get rate limited constantly. it kept returning nothing and the whole thing crashed.

so now it doesnt lean on one model. it runs down a list. if the first one returns no answer, it logs a line, saves the reason, and tries the next. first model that responds wins. it only gives up if every single one refuses, and then it prints the actual reason instead of a raw crash.

went from “run it again and hope” to the tool doing the hoping for me.

next up: making the text resize itself so long project names dont spill off the edge and a lot more :)

0
0
4
Open comments for this post

6h 22m 36s logged

victorylap Devlog #5 (one quote in the wrong place cost me a full render)

its been longer than i realized since my last devlog. a lot has happened.

TL;DR

  • tore the video out of one giant html string and split it into separate scene files
  • one misplaced quote in a script tag stopped gsap from loading. every animation silently died
  • built a loading bar so you dont watch hyperframes scroll a wall of text anymore
  • started a second theme, paper, so –theme actually changes the whole film

The Refactor

the whole video used to be one massive html string. every scene, every style, every timeline stuffed into one file. it worked but it was getting hard to read and harder to change.

so i split it. the host file holds the layout and the four slots. each scene is its own composition file: hook, name, stats, closer. hyperframes loads each one, seeks it on its own, and drops it into the slot at the right time.

adding a scene is now its own file instead of a needle in a haystack.


The Loading Bar

hyperframes prints a scrolling wall of render logs. it works but it looks like nothing you’d want a user to see.

so i wrapped the render in my own bar. a spinner, a set of rotating words, and a real percent bar that reads the actual progress out of the render output. all of hyperframes’ output gets buffered quietly in the background, and if the render fails i print the last chunk of it so i can actually see what broke. it’s clean on success, honest on failure.


Paper

i’m mid build on the second theme. the first look is amber terminal, mono, sharp slides. paper is the opposite: warm cream, serif ink, soft fades, a thin red margin rule down the side. same slots, same wiring, totally different film. thats the point of –theme. once this one renders clean i can add a third by copying the shape.


0
0
3
Open comments for this post

1h 39m logged


victorylap Devlog #4

victorylap Devlog #4 (it’s a real command now)

TL;DR

  • victorylap is npx-able. one command, any repo, out comes an mp4
  • added –out and –theme flags, friendly errors for bad input, and it runs the render itself now
  • you can paste a github url. it clones to a temp folder, makes the video, cleans up after itself
  • tested on three repos i did not write: slugify, requests, is-odd. all three rendered

What I Did

a bin field in package.json and a shebang line. that’s the whole trick behind every cli tool i’ve ever npx’d, and now mine is one of them.

the flags are not magic either. process.argv is just an array of what the user typed, and parsing –out is an if statement in a loop.

most of the session was making it fail nicely. bad folder, fake theme, empty repo, missing api key, broken llm reply. each one now gets one plain sentence instead of a stack trace.

my own typos fought back the whole way. i typed the new code and kept the old line twice, wrote studio instead of stdio, THEMS instead of THEMES. when a snippet replaces a line, the delete is half the edit.


What I Learned

the github url feature had a security lesson in it. my first instinct was to glue the url into a shell command string. bad idea. If someone passes a “url” with && and a delete command in it, the shell runs it. that’s command injection. the fix is execFileSync, which hands git the arguments as an array so no shell ever reads them. user input never gets glued into a command string.

three repos i have never read, three videos.

next up: multiple scenes, so the video stops being one card.

0
0
3
Open comments for this post

1h 6m 23s logged

victorylap Devlog #3 (the ai wrote a video about a repo it has never seen)

TL;DR

  • week 3 done, two weeks early. analyzer json goes to a free llm, the llm writes the script, the script renders to mp4
  • the hardcoded demo frame is gone. video/index.html is generated fresh for every repo
  • tested on site-slap, a repo victorylap had never seen. the video came out correct without me typing a word of it

The Brain

script.js sends the analyzer’s json to a free model on openrouter with three things: the facts, the exact json shape i want back, and rules. the rules do the heavy lifting. only use facts from the json. never invent numbers. no generic startup language.

for site-slap it wrote “Paste URL. Get roasted by AI.” as the hook and picked 15 commits, 11 ts files and 7 days built as the stats.

the 7 days one surprised me. i never gave it a duration. it read the first and last commit dates from the git data and did the math itself. that’s the whole idea of this project working on the first real run: give the model true facts and strict rules, and it stays honest.


The Template Became a Function

the frame i built by hand in week 1 was a dead end on purpose. now the whole page lives inside one javascript function as a template string, with slots where the hardcoded text used to be. the llm’s script fills the slots and the file gets written fresh each run.

the stats section is three lines of .map(). the ai sends an array, the template turns each entrinto a div, join glues them together. if it ever sends four stats instead of three, the template does not care.
.


What I Learned

there is a closing script tag inside my template string, because the template contains a full html page. anything parsing that file as html would see the tag and end the script early, right in the middle of the string. the fix is writing it as </script>. Inside a javascript string a backslash slash is just a slash, so the file that gets written out is normal html, but nothing scanning the template can mistake it for a real tag anymore. two characters of insurance against a genuinely confusing class of bug.

—.
next up: week 4. make it npx-able, so anyone can run this without cloning anything.

see y’all soon


0
0
1
Open comments for this post

1h 31m 12s logged

victorylap Devlog #2 (my analyzer thought the repo was 90% virtual environment)

TL;DR

  • wrote analyze.js, the first real victorylap code. point it at any repo, get structured json back
  • it reads package.json, pulls the first real sentence out of the readme, detects languages by walking the file tree, and reads git
    history
  • zero dependencies. just node builtins: fs, path, child_process
  • tested on two repos, one TypeScript and one Python. both came back clean

The Analyzer

this is the machine that replaces “ai looks at your repo and vibes.” victorylap needs facts about a project before it can make a video about it, so analyze.js collects them into one json report: name, description, first readme sentence, language breakdown, commit count, project dates, last 10 commit messages.

every source is its own function that returns null if the file isn’t there. repos are hostile territory. some have no package.json, some have no readme, some aren’t even git repos. the analyzer assumes nothing exists and survives all of it.

the readme part was more parsing than reading. readmes open with badges, logos and headings before any actual sentence, so the code skips every line that starts with #, ![ or < until it hits real prose. on site-slap that landed on “paste a url. get a score out of 100. get roasted.” which is exactly the tagline a video wants.


The venv Bug

first run on free-claude-code reported 1045 python files.

the repo has about 125.

the language detector walks the whole file tree and counts extensions, with a skip list for folders that aren’t your code: node_modules, .git, dist. my list had “.venv” in it. the folder in the repo was named “venv”. no dot, different string, the walker went right in and counted 928 files of installed packages as project code.

one word added to the skip list and python dropped to 125. the numbers always look right until you check them against reality.

git history had its own surprise. my hardcoded demo frame has said 107 COMMITS since day 1, a number i eyeballed. git rev-list says 494. the analyzer’s first real act was fact-checking my own video.


What I Learned

the file tree walk was my first recursive function, a function that calls itself.

walk() reads one folder. every file gets its extension counted. every subfolder triggers walk() on that subfolder, and here’s the part that made it click: every call receives the same counts object, passed down as an argument. there aren’t 50 tallies getting merged at the end, there is one shared tally that every level of the tree writes into. when the outermost call finishes, the whole repo is already counted.

recursion sounds like a trick until you need to handle “a folder can contain folders which contain folders.” then it’s just the honest shape of the problem.


Tip of the Day

try/catch will eat your typos, not just your expected errors. i wrapped the git code in try/catch so non-git folders return null
instead of crashing. then i typed “utf8 “ with a trailing space as the encoding. node rejected it, the catch swallowed it, and git
came back null with zero explanation. if a section of your output is mysteriously null, the first suspect is a typo hiding inside
your own safety net.

next up: feed this json to a free llm and get a video script back.


0
0
1
Open comments for this post

1h 44m 38s logged

🏁 victorylap Devlog #1 (two days in and i already made like 200 mistakes)

TL;DR

  • started victorylap, my main summer project. one command, npx victorylap, turns any git repo into a brag video
  • built the first video frame by hand in html, 1920x1080, amber on black
  • promoted it to a real Hyperframes composition and rendered my first mp4. the pipeline works
  • test data for the demo frame comes from the free-claude-code repo

What This Actually Is

every dev has repos that die quietly on github. you build something real, push it, and nobody ever sees it. victorylap turns the
repo itself into a short video you can post. it reads your readme, file tree and git history, an ai writes a script about what you
built, and the whole thing renders to mp4 on your own machine.

that last part is the point. no render server, no paid api, no upload. your computer does the work so it stays free forever.

credit where it’s due: the idea comes from the /brag skill for claude code, and the render engine is Hyperframes. i’m not porting
brag, i’m remaking it as a standalone product. same engine, my own analyzer, my own templates, free llm for the script. the
original creators get named in every version of this.


Day 1, the Frame

wrote one hardcoded html file. project name huge, a tagline, three stats. the data is real, it’s from free-claude-code, which i’m
using as the test repo all summer.

it broke immediately. the name rendered tiny and almost invisible. three separate causes: i never typed the .project-name css
block, forgot color on body so the text defaulted to black, and typed .tagLine with a capital L so the selector never matched. two
different bugs producing the same symptom taught me more about css inheritance than any tutorial has.


Day 2, the Render

Hyperframes doesn’t screenshot a plain page. it needs a composition, which is html with a contract: a root div with data-duration,
clips with data-start and data-track-index, and one paused gsap timeline registered on window.__timelines.

lint caught a missing data-start on the root. validate ran it in headless chrome, clean. render gave me an mp4.

then it gave me a black one.

turns out i had typed all my fixes into template.html, the day 1 file, while the renderer reads video/index.html. did that twice.
closed the old tab in vscode and the problem stopped existing. also found height: 180px where 1080px should have been, which would
have rendered the whole video as a squished strip.

second render worked. five seconds, text staggering in, my first programmatically rendered video.


What I Learned

the paused timeline is the core trick of the whole engine. a normal browser animation plays on the browser’s clock, so recording it
means racing it in real time and hoping no frame drops. Hyperframes refuses to race. the timeline is created paused, and the
renderer takes ownership of time itself. it seeks to 0.000s, screenshots, seeks to 0.033s, screenshots, three hundred times for a
ten second video. then ffmpeg stitches the stills into video. every frame is exact because nothing was ever actually moving.

deterministic time is why the same file renders identically on any machine.


0
0
1

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…