mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 12:17:03 +00:00
feat(cli): reverse start stops a Free account when the server blocks it (#7017)
When /v1/hyperframes/reverse/start answers gate "block" for plan "free", `reverse start` saves the session, then exits non-zero. The error and its `fix` carry the upgrade link that credits the purchase to this run: the server's `upgrade_url`, else the plans page with `feature=reverse&rs=<session>`. Pro, unknown plans, `record`, and every error still go on. The reverse-any-video skill tells the agent to pass that exact link on and stop. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e3a7cdd2b8
commit
7da583ec69
@@ -1518,9 +1518,9 @@ run started and saves the session HeyGen returns in the project's
|
||||
with the render. It then prints the video's [`measure probe`](#measure) JSON,
|
||||
with a `reverse` field: whether you are signed in, the plan (`free`, `pro`, or
|
||||
`null` when unknown), the session, and on Free the `upgrade_url` that credits a
|
||||
purchase to this run. It never stops the run: signed out,
|
||||
offline, or on a server without the route, it skips the session and still
|
||||
probes.
|
||||
purchase to this run. It stops the run only when HeyGen blocks it for a Free
|
||||
account: it then exits non-zero with that upgrade link. Signed out, offline, or
|
||||
on a server without the route, it skips the session and still probes.
|
||||
|
||||
```bash
|
||||
npx hyperframes reverse start ref.mp4 --dir my-rebuild
|
||||
|
||||
@@ -31,9 +31,13 @@ vi.mock("../measure/probe.js", () => ({
|
||||
const { default: start } = await import("./start.js");
|
||||
|
||||
const SESSION = "rvs_0f8fad5b-d9cb-469f-a165-70867728950e";
|
||||
const recorded = (plan: "free" | "pro" | "unknown" = "pro", upgradeUrl?: string): ReverseStart => ({
|
||||
const recorded = (
|
||||
plan: "free" | "pro" | "unknown" = "pro",
|
||||
upgradeUrl?: string,
|
||||
gate: "record" | "block" = "record",
|
||||
): ReverseStart => ({
|
||||
recorded: true,
|
||||
session: { sessionId: SESSION, plan, gate: "record", ...(upgradeUrl ? { upgradeUrl } : {}) },
|
||||
session: { sessionId: SESSION, plan, gate, ...(upgradeUrl ? { upgradeUrl } : {}) },
|
||||
});
|
||||
|
||||
let root: string;
|
||||
@@ -169,6 +173,32 @@ describe("reverse start", () => {
|
||||
expect(() => readFileSync(join(dir, "hyperframes.json"))).toThrow();
|
||||
});
|
||||
|
||||
it("stops a Free account the server blocks, with its upgrade link as the fix, after saving the session", async () => {
|
||||
const link = "https://hyperframes.studio/pricing?feature=reverse&rs=1";
|
||||
server.answer = recorded("free", link, "block");
|
||||
const dir = join(root, "rebuild");
|
||||
await expect(run("--dir", dir)).rejects.toThrow(CliRuntimeError);
|
||||
expect(output()).toMatchObject({ ok: false, error: expect.stringContaining(link), fix: link });
|
||||
expect(config(dir)).toMatchObject({ reverseSessionId: SESSION });
|
||||
});
|
||||
|
||||
it("builds the attributed plans link when a block names none", async () => {
|
||||
server.answer = recorded("free", undefined, "block");
|
||||
await expect(run()).rejects.toThrow(CliRuntimeError);
|
||||
const link = `https://plans.test/?feature=reverse&rs=${SESSION}`;
|
||||
expect(output()).toMatchObject({ ok: false, error: expect.stringContaining(link), fix: link });
|
||||
});
|
||||
|
||||
it.each<[string, ReverseStart]>([
|
||||
["a Pro account under block", recorded("pro", undefined, "block")],
|
||||
["an unknown plan under block", recorded("unknown", undefined, "block")],
|
||||
["a Free account under record", recorded("free")],
|
||||
])("goes on for %s", async (_label, answer) => {
|
||||
server.answer = answer;
|
||||
await run();
|
||||
expect(output()).toMatchObject({ ok: true, width: 1080, reverse: { recorded: true } });
|
||||
});
|
||||
|
||||
it("fails like measure probe when the video cannot be probed, before asking the server", async () => {
|
||||
probe.fail = true;
|
||||
await expect(run()).rejects.toThrow(CliRuntimeError);
|
||||
|
||||
@@ -2,7 +2,13 @@ import { existsSync } from "node:fs";
|
||||
import { defineCommand } from "../../utils/defineCommand.js";
|
||||
import type { Example } from "../_examples.js";
|
||||
import { jsonArg, runMeasure, videoArg } from "../../measure/command.js";
|
||||
import { reverseUpgradeUrl, startReverseSession, type ReverseStart } from "../../heygen/reverse.js";
|
||||
import { Fix } from "../../measure/media.js";
|
||||
import {
|
||||
reverseUpgradeUrl,
|
||||
startReverseSession,
|
||||
type ReverseSession,
|
||||
type ReverseStart,
|
||||
} from "../../heygen/reverse.js";
|
||||
import {
|
||||
projectConfigPath,
|
||||
readProjectConfig,
|
||||
@@ -41,11 +47,20 @@ function summary(started: ReverseStart, savedTo: string | undefined) {
|
||||
};
|
||||
}
|
||||
|
||||
function refusalWhenServerBlocksFree(session: ReverseSession): Fix | undefined {
|
||||
if (session.gate !== "block" || session.plan !== "free") return undefined;
|
||||
const link = reverseUpgradeUrl(session);
|
||||
return new Fix(
|
||||
`Reverse is part of HyperFrames Pro. Upgrade here, then run reverse start again: ${link}`,
|
||||
link,
|
||||
);
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "start",
|
||||
description:
|
||||
"Note a reverse run with HeyGen (signed in: saves its session in the project), then print the video's probe; never stops the run",
|
||||
"Note a reverse run with HeyGen (signed in: saves its session in the project), then print the video's probe; stops only when HeyGen blocks a Free account",
|
||||
},
|
||||
args: {
|
||||
video: videoArg,
|
||||
@@ -68,6 +83,8 @@ export default defineCommand({
|
||||
const savedTo = save
|
||||
? recordProjectReverseSession(projectDir, started.session.sessionId, SKILL)
|
||||
: undefined;
|
||||
const refused = started.recorded ? refusalWhenServerBlocksFree(started.session) : undefined;
|
||||
if (refused) throw refused;
|
||||
return { reverse: summary(started, savedTo), ...probe };
|
||||
});
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"files": 76
|
||||
},
|
||||
"reverse-any-video": {
|
||||
"hash": "025850a14e3b6370",
|
||||
"hash": "fe326730ec89a06b",
|
||||
"files": 5
|
||||
},
|
||||
"slideshow": {
|
||||
|
||||
@@ -9,7 +9,7 @@ description: Rebuild any video file as an editable HyperFrames project - motion
|
||||
|
||||
The result is a HyperFrames project whose render matches the original frame for frame and stays editable. Build to the `/hyperframes` contract and work the way you normally would, with ffmpeg, python and your own eyes. Reverse videos the person has the right to use.
|
||||
|
||||
Reverse is a HyperFrames Pro feature: go on only when signed in on a paid plan, not Free. Open with `npx hyperframes reverse start <video> --dir <project>` (a CLI without it: `auth status`, `measure probe`) and `references/start.md`. Resuming (a `REVERSE.md` or half-built project is there) is a start too: open it the same way first. A step an earlier session gave up on (a re-shoot HeyGen refused) gets one more try, then ask the person. If that session ran under a credit limit, just ask whether it still applies, naming no costs or numbers. Rebuild the video as it is; remix it only when the person asks.
|
||||
Reverse is a HyperFrames Pro feature: go on only on a paid plan, not Free. Open with `npx hyperframes reverse start <video> --dir <project>` (a CLI without it: `auth status`, `measure probe`) and `references/start.md`; if it refuses, pass on its upgrade link and stop. Resuming (a `REVERSE.md` or half-built project) opens the same way. A step an earlier session gave up on (a re-shoot HeyGen refused) gets one more try, then ask the person. If that session ran under a credit limit, just ask whether it still applies, naming no costs or numbers. Rebuild the video as it is; remix it only when the person asks.
|
||||
|
||||
## The original decides the frame
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ example:
|
||||
>
|
||||
> Reply "done" once you're on Pro and I'll start.
|
||||
|
||||
Only on a CLI without `reverse start`, give `! npx hyperframes billing subscribe` instead.
|
||||
When `reverse start` refuses the run (it exits with an error whose `fix` is the upgrade link), give the person that
|
||||
exact link and stop; once they reply that they are on Pro, run `reverse start` again. Only on a CLI without
|
||||
`reverse start`, give `! npx hyperframes billing subscribe` instead.
|
||||
|
||||
## Signed in on Pro
|
||||
|
||||
|
||||
Reference in New Issue
Block a user