Skip to content

Add x-posting-license community skill - #5

Merged
jmoran-heygen merged 1 commit into
heygen-com:masterfrom
jmoran-heygen:add-x-posting-license
Aug 27, 2026
Merged

jmoran-heygen merged 1 commit into
heygen-com:masterfrom
jmoran-heygen:add-x-posting-license

Conversation

@jmoran-heygen

Copy link
Copy Markdown
Collaborator

Use case

Turns any X profile into a 10.3s animated "POSTING LICENSE" ID-card video — a parody driver's license in X's visual language. The composition is a finished, motion-locked template (overlapped keyframes end to end, verified zero dead frames), so the agent's job is minimal: gather five profile fields + the avatar, run scripts/build.mjs, render.

What the agent runs

  • scripts/build.mjs — token-fills template/index.template.html into a render-ready HyperFrames project (plain Node, no dependencies)
  • npx hyperframes render — standard HF rendering

Network destinations

  • x.com/<handle> — the profile page the agent reads (name, handle, joined date, follower/following counts, bio)
  • pbs.twimg.com — the profile avatar image
  • api.fxtwitter.com/<handle> — post count (not exposed on logged-out profile pages); free, no credentials

No credentials, no paid operations, no telemetry. Documented in references/profile-data.md.

Testing

Built and rendered six licenses end to end (five HeyGen team profiles + one 0-post/no-bio edge case) — counters, diacritics in names (Miguel Ángel), thousands separators (12,486), PNG avatars, and missing-bio fallback all exercised. Repo validator passes (Validated 3 skill(s).).

Residual risk

The card reproduces X's logo and brand look as parody/fan content; contributors using the skill publish at their own judgment. Profile data used is public.

🤖 Generated with Claude Code

@jmoran-heygen
jmoran-heygen requested review from a team and jrusso1020 as code owners August 27, 2026 18:24
@jmoran-heygen

Copy link
Copy Markdown
Collaborator Author

Addressed the review findings (new head commit):

  1. Injection — all profile strings are now control-char-stripped, length-capped, and HTML-escaped before substitution; counters (which land in script context) accept only plain non-negative integers. Re-tested with hostile names/bios: zero raw tags in output, entities verified.
  2. Symlink escapebuild.mjs refuses to write through symlinks (out dir, subdirs, or existing target files) and verifies every write path resolves inside the real --out. The reviewer's sentinel scenario now exits 1 without touching the target.
  3. Installed invocation — docs now use node <SKILL_DIR>/scripts/build.mjs with an explanation; the script already resolved its template relative to itself.
  4. Trust boundary — SKILL.md now lists every network destination including cdn.jsdelivr.net (GSAP 3.14.2, pinned) and registry.npmjs.org, drops the "fully local" claim, and notes @latest mutability with a pinning suggestion.

Also: invalid counts (NaN, negatives, fractions) are rejected with clear errors, and a Provenance section documents the Originkit Dot Matrix lineage (deterministic GLSL rewrite, no Originkit source redistributed; Ashima simplex noise, MIT) and the parody use of X branding.

🤖 Generated with Claude Code

Hardened per review: untrusted profile data escaped and validated, symlink-safe writes, installed invocation path, accurate network/dependency boundary, Originkit provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jmoran-heygen
jmoran-heygen force-pushed the add-x-posting-license branch from 87725b6 to 49a0aa0 Compare August 27, 2026 19:19
@jmoran-heygen
jmoran-heygen merged commit 0e20fd3 into heygen-com:master Aug 27, 2026
2 checks passed

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post-merge advisory review (this PR is already merged into master). Findings flagged for follow-up, not gating.

What improved: build.mjs:46-101 now escapes untrusted profile strings and rejects NaN, negative, and fractional counters; hostile-HTML probes no longer emit executable profile markup. SKILL.md:46-62 also works from an isolated npx skills@1.5.23 install.

Follow-up findings: Inline comments cover the remaining output-safety, dependency, telemetry/network, runtime, input-validation, shell-path, and third-party-license gaps. The highest-risk items are silent/partial overwrites, the renders/ symlink escape, the mutable canonical renderer, and the false No telemetry claim.

Validation: exact merged head 49a0aa06afcaeb90570ce944259401678ad35d4d; green repository checks; validator and Node syntax check; isolated installer/build; hyperframes check; hostile profile, malformed-count, regular-target overwrite, target-symlink, nested-directory-symlink, and whitespace-path probes; independent adversarial pass.

Verdict: COMMENT (post-merge: With fixes)
Reasoning: The normal path works and the earlier injection/install issues are fixed, but a follow-up PR is needed before users can rely on the documented safety and side-effect boundaries.

— Codex

const outArg = resolve(args.out);
refuseSymlink(outArg);
mkdirSync(join(outArg, "assets"), { recursive: true });
mkdirSync(join(outArg, "renders"), { recursive: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: mkdirSync(..., {recursive:true}) accepts an existing renders symlink, and that directory is never checked through safePath. A probe with out/renders -> external-dir completed successfully; the documented render path then resolves outside --out. Reject symlinked output subdirectories and verify their real paths before advertising or using the render destination. The repository validator only rejects symlinks committed to this repository, so it cannot catch this generated-project case.

Comment on lines +138 to +146
writeFileSync(safePath("index.html"), html);
copyFileSync(args.avatar, safePath("assets", "avatar.jpg"));
writeFileSync(safePath("hyperframes.json"), JSON.stringify({
$schema: "https://hyperframes.heygen.com/schema/hyperframes.json",
paths: { blocks: "compositions", components: "compositions/components", assets: "assets" },
}, null, 2));
writeFileSync(safePath("meta.json"), JSON.stringify({
id: "x-posting-license", name: "x-posting-license-" + handle.toLowerCase(),
}, null, 2));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: These writes overwrite existing regular files without confirmation, and validation happens one target at a time. An existing project was silently replaced in testing; if a later target (for example meta.json) is a symlink, earlier files have already been overwritten before the script exits. Preflight every destination before modifying anything and require an explicit --force for replacement, ideally using atomic temp-file-and-rename writes.

3. **Render** (ask the user before rendering if your harness gates renders):

```bash
npx hyperframes@latest render ./license-jake -q high -o ./license-jake/renders/license.mp4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: The canonical workflow still executes mutable hyperframes@latest, even though CONTRIBUTING.md:69 rejects behavior-changing unpinned dependencies. An optional pinning suggestion does not make the default path deterministic. Pin the exact tested CLI version consistently in render, check, script comments, and the generated render hint.

Comment on lines +30 to +42
## Network and side effects (complete list)

- `x.com/<handle>` — profile page the agent reads (data gathering).
- `pbs.twimg.com` — the avatar image download (data gathering).
- `api.fxtwitter.com/<handle>` — post count; free, no credentials (data gathering).
- `registry.npmjs.org` — the HyperFrames CLI itself, via `npx`.
- `cdn.jsdelivr.net` — the composition loads GSAP (pinned `3.14.2`) from
jsDelivr at preview/render time. Rendering is NOT fully offline.

No credentials, no paid operations, no telemetry. `build.mjs` writes only
inside the `--out` directory: it validates counters as plain non-negative
integers, strips control characters, caps lengths, HTML-escapes every profile
string before substitution, and refuses to write through symlinks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: No telemetry and complete list do not match the documented workflow. HyperFrames 0.8.16 enables anonymous PostHog telemetry by default (us.i.posthog.com), while the verified check also fetched from Google Fonts and populated ~/.cache/hyperframes/fonts. Run the documented CLI commands with HYPERFRAMES_NO_TELEMETRY=1, and disclose fonts.googleapis.com, fonts.gstatic.com, and the external cache write.

var fs = "#version 300 es\n" +
"precision highp float; uniform float uTime; out vec4 fragColor;\n" +
"const vec2 uRes = vec2(1920.0, 1080.0);\n" +
"vec3 mod289(vec3 x){ return x - floor(x * (1.0/289.0)) * 289.0; }\n" +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: This embeds a substantial portion of the acknowledged Ashima/Stefan Gustavson simplex-noise implementation, but its copyright and MIT permission notice are absent. The upstream license requires that notice in copies or substantial portions: https://github.com/ashima/webgl-noise/blob/master/LICENSE. Include the required notice in a form that accompanies both the skill and generated copies.


## Requirements

- Node 18+ and the HyperFrames CLI via `npx` (fetched from npm; `@latest` is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: The CLI invoked here currently resolves to HyperFrames 0.8.16, whose published package metadata requires Node >=22. A Node 18 user satisfies this stated requirement but runs an unsupported configuration; raise the requirement to Node 22+ when pinning the CLI.

Comment on lines +55 to +58
function cleanCount(v, label) {
const s = v.trim().replace(/,/g, "");
if (!/^\d{1,10}$/.test(s)) die("--" + label + ' must be a non-negative integer (got "' + v + '")');
return String(Number(s));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: Removing every comma before validation silently changes malformed values: 1,,2 becomes 12, and 12,34 becomes 1234. Validate either plain digits or correctly grouped thousands before stripping separators so scraped/entered profile counts cannot be corrupted.

Comment on lines +150 to +151
console.log("render: npx hyperframes@latest render " + outReal +
" -q high -o " + join(outReal, "renders", "license.mp4"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

important follow-up: The copyable render command concatenates paths without shell quoting. Spaces make it unusable, while shell metacharacters can execute unintended commands if an agent copies the hint. Emit safely shell-escaped arguments or avoid presenting a directly executable command string.

@miga-heygen miga-heygen mentioned this pull request Sep 10, 2026
9 tasks
miguel-heygen pushed a commit that referenced this pull request Sep 10, 2026
## What

Adds `skills/duo/`, a community skill authored by @jmoran-heygen and
submitted here on his behalf. It composites any two HTML screens into a
locked 1448×1086 @ 30fps photo plate of hands holding an open foldable
phone, for side-by-side comparison and meme videos (TikTok vs Reels, two
chats answering the same prompt, before/after, two apps racing). The
plate, measured screen geometry, hinge divider and single camera push
are fixed; the agent only authors what plays inside the two `.content`
slots (left 495×849, right 498×849) and adds tweens to the one shared
GSAP timeline.

Contents:

- `SKILL.md` — when/when-not to use, requirements, complete
network/side-effect list, flow, verification, rules, provenance.
- `scripts/build.mjs` — dependency-free Node 18+ scaffolder (writes only
inside `--out`, refuses to write through symlinks, validates
`--duration` and `--icons`).
- `template/index.template.html` — the locked rig with two empty slots
and the timeline hook.
- `assets/plate.png` (398 KB), `assets/plate-preview.jpg`,
`assets/screen-spec.json`, `assets/icons-tiktok.svg`,
`assets/icons-instagram.svg` (plain-text `<symbol>` sheets).
- `references/screen-geometry.md`, `references/feed-recipes.md`,
`references/sourcing-real-ui.md`.
- README "Available skills" row (alphabetical, first).

Files excluded from the author's zip and why:

- `__MACOSX/`, `._*`, `.DS_Store` — macOS archive metadata, not skill
content.
- `TEST-scaffold-snapshot.png` — a generated test artifact; CONTRIBUTING
forbids generated output.

Edits to the author's content: none. The `>` folded-block `description`
in the frontmatter is the same form used by `x-posting-license` (#5) and
passes the validator as-is.

## Why it belongs here

It is a single fixed-plate template for one specific meme format. Same
shape as `x-posting-license` (#5): a locked composition where the agent
fills content and renders. Too narrow for the curated set, useful to
share.

## Trust boundary

- Commands: `node scripts/build.mjs --out <dir> [--id] [--duration]
[--icons tiktok,instagram]`, then `npx hyperframes@latest
check|snapshot|render` on the output dir.
- File access: `build.mjs` reads only the skill's own `template/` and
`assets/`, writes only inside `--out` (creates `assets/` and `renders/`
subdirs), refuses symlinked targets.
- Network: `registry.npmjs.org` (HyperFrames CLI via `npx`);
`cdn.jsdelivr.net` (GSAP pinned `3.14.2` loaded by the template at
preview/render time). `build.mjs` itself makes no network calls.
`references/sourcing-real-ui.md` documents how an agent may pull real
app UI/posts from TikTok/Instagram if the user's request calls for real
content, with a rights caveat; that is user-directed, not something the
scaffolder does.
- Credentials: None. Paid operations: None. Telemetry: None.
- Externally visible side effects: None (renders are local; publishing
is up to the user).
- Dependency pinning: GSAP is pinned. `npx hyperframes@latest` is
mutable; the SKILL.md advises pinning a version for byte-identical
re-renders. Reviewers may prefer a pinned example in the docs.

## Verification

- `bash .github/scripts/validate-skills.sh` → `Validated 4 skill(s).`
(exit 0).
- Scaffolder normal path: `node skills/duo/scripts/build.mjs --out
/tmp/duo-test --duration 10 --icons tiktok,instagram` → exit 0; produced
`index.html`, `hyperframes.json` (`1448x1086 @ 30fps`),
`assets/plate.png`, `assets/screen-spec.json`,
`assets/icons-tiktok.svg`, `assets/icons-instagram.svg`.
- Scaffolder failure paths: `--icons snapchat` → `unknown icon sheet
"snapchat"` exit 1, nothing written; no `--out` → usage, exit 1.
- `npx hyperframes@latest check /tmp/duo-test` (Node 22, Linux, software
GPU) → Lint 0/0, Runtime 0/0, Layout 0 issues across 9 samples, Motion
0/0, "Check passed". No render performed.
- Scanned the skill for symlinks (none), secrets/tokens/keys (none), and
URLs: only `w3.org` SVG namespace, the jsDelivr GSAP URL, the X status /
`pbs.twimg.com` provenance links in `screen-spec.json`, and the
`tiktok.com` example in the sourcing reference.
- TruffleHog runs in CI; not available locally.

## Licensing note for reviewers

Two items need code-owner judgment under CONTRIBUTING's "right to
redistribute every dependency and asset" requirement:

1. `assets/plate.png` is derived from a photo posted publicly on X by
@A_Kapustin (status `2097769962936868872`), with both phone screens
blanked to neutral. The SKILL.md and `screen-spec.json` document this
provenance and tell users to credit the source where appropriate. No
explicit license from the photographer is on file.
2. `assets/icons-tiktok.svg` and `assets/icons-instagram.svg` reproduce
TikTok's and Instagram's own UI glyphs, extracted from their public
mobile web DOM, so mock screens look like the real apps. They are
trademarks of their owners and are included for parody/commentary use
only, as the SKILL.md states.

If either is not acceptable for redistribution in this repo, the skill
can be adapted (for example, ship without the icon sheets, or require
the user to supply their own plate).

## Checklist

- [x] The skill is self-contained and its directory name matches
`SKILL.md`.
- [x] New or renamed skills are linked in the README's **Available
skills** table.
- [x] I read every instruction and script in the submitted skill.
- [x] I disclosed all dependencies, network access, credentials, costs,
and side effects.
- [x] No secrets, private data, private endpoints, generated output, or
opaque executables are included.
- [x] Destructive, paid, or externally visible actions require explicit
user confirmation (render is user-triggered; no destructive or paid
actions).
- [x] Dependencies are minimal, pinned where practical, and
license-compatible (GSAP pinned; `hyperframes@latest` mutable, pinning
advised; asset licensing flagged above).
- [x] I tested the normal path and a failure or cancellation path in a
fresh agent session (scaffolder normal + two failure paths, plus
`hyperframes check`; no end-to-end render).
- [x] `bash .github/scripts/validate-skills.sh` passes locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants