Compositions referencing assets outside the project directory (via ../
paths) produced broken published projects — those files were never
included in the ZIP archive.
localizeExternalAssets() now scans all HTML and CSS files in the archive
for src, href, and url() references that resolve outside the project
dir. For each, it copies the file into the archive under _ext/ and
rewrites the reference to point there.
Handles: src/href attributes, <style> url(), inline style url(),
standalone CSS url(), sub-composition HTML files, deduplication of
the same asset referenced from multiple files.
Shared primitives (CSS_URL_RE, isNonRelativeUrl, isPathInside) extracted
into core/compiler/assetPaths.ts — single source of truth across core,
producer, and CLI.
## Summary
- Replace the flat 30s upload timeout with a size-adaptive calculation: `max(120s, bytes / 500KB/s)`
- Metadata requests (presigned URL, complete) keep the original 30s timeout
- Companion to the backend change removing the 64 MB upload limit in experiment-framework
## Context
With the backend size limit removed, large projects (78 MB+) need proportionally longer to upload. A 78 MB project now gets ~164s, a 500 MB project ~17 min. The old 30s timeout would abort any upload over ~15 MB on a typical connection.
## Test plan
- [x] All 4 existing vitest tests pass
- [x] Build succeeds, no type errors
- [x] Lint + format pass (oxlint + oxfmt)
- [x] Timeout values verified for 10/78/200/500/1000 MB archives
## Summary
This PR adds `hyperframes publish` as the OSS handoff into the persisted HyperFrames publish flow.
Instead of opening a local tunnel, the CLI now:
1. zips the local project
2. uploads it to the HeyGen publish backend
3. gets back a stable `hyperframes.dev` project URL plus claim token
4. prints a claimable URL for the user
Example output:
```bash
$ hyperframes publish
Project my-video
Files 12
Public https://hyperframes.dev/p/hfp_123?claim_token=...
Open the URL on hyperframes.dev to claim the project and continue editing.
```
## User Flow
The intended user flow is:
1. Run `hyperframes publish` from a local HyperFrames project.
2. The CLI uploads the project as a zip to the publish API.
3. The CLI prints a stable `hyperframes.dev` URL with the claim token attached.
4. The user opens that URL in the browser.
5. `hyperframes.dev` uses that URL to claim the published project and import it into the web app.
6. The user continues editing from a normal web session.
So the CLI is only responsible for packaging, upload, and printing the URL. The browser-side claim/import flow lives in the backend and web app stack.
## Routing
This PR does not expose a separate user-facing canary mode.
The CLI posts to the normal publish API host:
- `https://api2.heygen.com/v1/hyperframes/projects/publish`
Backend routing behavior is handled server-side. If the default path routes through canary, it does so without a dedicated CLI flag; if that path is unavailable, traffic falls back to prod behavior on the backend side.
## What Changed
| File | Role |
|---|---|
| `packages/cli/src/commands/publish.ts` | Adds the `hyperframes publish` command, confirmation prompt, lint-before-upload behavior, and user-facing output. |
| `packages/cli/src/utils/publishProject.ts` | Zips the local project, filters ignored files/directories, posts the archive to the publish API, and returns the published project metadata. |
| `packages/cli/src/utils/publishProject.test.ts` | Covers archive creation and successful upload response parsing. |
| `packages/cli/src/cli.ts` | Registers the new `publish` command. |
| `packages/cli/src/help.ts` | Adds `publish` to root help and examples. |
| `docs/packages/cli.mdx` | Documents the persisted publish flow. |
## Important Behavior
- Requires `index.html` at the project root.
- Ignores hidden files and common non-project directories like `.git`, `node_modules`, `dist`, `.next`, and `coverage`.
- Lints the project before upload and prints findings, but does not block publish on warnings.
- Does **not** keep a local process alive after upload.
- Does **not** open a public tunnel.
- Does **not** require HeyGen OAuth inside the CLI.
## Why This Shape
This keeps the OSS CLI simple and matches the current product direction:
- project persistence lives in HeyGen's backend
- the public URL comes from the persisted project row
- claiming/importing happens on `hyperframes.dev`
- the CLI should not own browser auth or long-lived sharing infrastructure
## Verification
In the earlier PR worktree, this flow was verified locally with the CLI build/test path and with real backend integration.
In this cleanup worktree, the narrow code/doc change was verified by inspection, but the repo-level commands are currently blocked here by missing local tool binaries and typings in the worktree environment:
- `bun run --filter @hyperframes/cli test` -> `vitest: command not found`
- `bun run --filter @hyperframes/cli typecheck` -> local dependency/type resolution failures outside this diff
- `bun run --filter @hyperframes/cli build` -> `tsx: command not found`
## Notes
This PR only covers the OSS CLI side of the flow.
The full end-to-end experience depends on the corresponding backend and `hyperframes.dev` changes that store published projects, return the stable URL, and support claim/import in the web app.