@navipartner/demo-video 0.4.0 → 0.5.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/README.md CHANGED
@@ -1,109 +1,135 @@
1
- # @navipartner/demo-video
2
-
3
- Marketing demo videos of a React app, filmed by Playwright on the app's own
4
- e2e mocks, in the NaviPartner house look, with any customer's brand. For
5
- pnpm repos on GitHub Actions.
6
-
7
- ## Setup
8
-
9
- The package is public on npm, so nothing needs a token or registry setup.
10
-
11
- ### Once per project repo
12
-
13
- From the repo root:
14
-
15
- npx --ignore-scripts @navipartner/demo-video init
16
-
17
- It sets up `.gitignore`, the skill, the `demo video` label and
18
- `.github/workflows/demo-video.yml`. Safe to re-run; `--dry-run` previews.
19
- (`--ignore-scripts` skips downloading ffmpeg, which `init` doesn't need.)
20
-
21
- ### Per app
22
-
23
- npx --ignore-scripts @navipartner/demo-video init --app apps/<name>
24
- pnpm install
25
-
26
- Then ask Claude for a demo video of the app. It finishes
27
- `demo/demo.config.ts` from the e2e tests and writes the app's primary story.
28
- The app needs `@playwright/test` 1.59 or later.
29
-
30
- ## Getting a video
31
-
32
- | Way | Who | Films | Where the video lands |
33
- |---|---|---|---|
34
- | **Run workflow form:** Actions → Demo video → Run workflow; fill in App, Story, Brand (each may be empty) | anyone who can write to the repo | an existing story with a brand, from the branch chosen | the run's Summary page |
35
- | **PR comment:** `/demo-video checkout in acme branding` | members, on any PR | an existing story with a brand, from the PR's branch | a reply on the PR |
36
- | **The `demo video` label** on a PR | whoever labels it | every story the PR adds or changes, on every push | a comment on the PR |
37
- | **Locally:** `pnpm --dir apps/<name> exec demo-video render [--story <name>] [--brand <name>]` | a developer | any story | `demo/out/` |
38
-
39
- - An empty story means the app's primary story; an empty brand, the app's
40
- default. A request that doesn't name one existing story, or names a brand
41
- that doesn't exist, gets the list of stories and brands instead (the form
42
- run is then marked failed, so a typo is noticed).
43
- - The form and the comment only choose: they never write or change a story.
44
- New stories are written by a developer, locally (ask Claude, which uses
45
- the skill), and reviewed through the label. A PR has no default video.
46
-
47
- ## Stories and brands
48
-
49
- - **Stories** are `<app>/demo/stories/<name>.json`, built only from what the
50
- e2e mocks return. One per app is `"primary": true`: the journey the app
51
- exists for.
52
- - **Variants** (`checkout.acme.json`) are a story in a customer's words:
53
- the base story's steps, with its own title, closing and card text, filmed
54
- with one brand. Asking for the story in that brand films the variant.
55
- - **Brands** are `<app>/demo/brands/<name>.json` plus a folder of assets;
56
- the engine ships `navipartner`, the default.
57
-
58
- Claude writes both with the skill (`skills/np-demo-video/`), which also holds
59
- the look and every field.
60
-
61
- ## Commands
62
-
63
- Run in the app: `pnpm --dir apps/<name> exec demo-video <command>`.
64
-
65
- | Command | |
66
- |---|---|
67
- | `list` | The stories, primary first. |
68
- | `check` | Dry run in seconds. Fails on anything that would film badly (a zoom on the wrong area, a click whose result is off-screen, code or unproven numbers on a card) and names the step. |
69
- | `render` | Films the story frame by frame (60 fps, about 5× its length) to `demo/out/<story>-<brand>-demo.mp4`, with a contact sheet (`review/sheet.jpg`, one frame a second) and ten stills. |
70
- | `record`, `encode`, `stills` | `render` in parts: record only, encode the last take, redo the stills. |
71
-
72
- `--story <name>` (default: primary), `--brand <name>` (default:
73
- `navipartner`), `--draft` (`render` at 30 fps in about half the time, while
74
- tuning), `--json` (machine-readable output).
75
-
76
- ## Developing and releasing
77
-
78
- pnpm install
79
- pnpm test && pnpm typecheck && pnpm build
80
-
81
- **Release:** bump `version` in `package.json` on `main`, then
82
- `git tag vX.Y.Z && git push origin vX.Y.Z`. That publishes it to npm as
83
- `latest` and moves the `v0` tag, which every project's workflow follows.
84
- Running `release.yml` by hand publishes a `next` prerelease.
85
-
86
- **The first release, once, by an npm owner of the `navipartner` org:**
87
- trusted publishing can only be set up for a package that exists.
88
-
89
- 1. On main at the release commit: `pnpm install && pnpm pack`, then
90
- `node scripts/check-public.mjs navipartner-demo-video-*.tgz`.
91
- 2. `npm login`, then `npm publish navipartner-demo-video-*.tgz --access public`.
92
- 3. On npmjs.com, the package → Settings → Trusted publisher: GitHub Actions,
93
- repository `navipartner/demo-video`, workflow `release.yml`. Then set
94
- "Publishing access" to require trusted publishing (no tokens).
95
- 4. Push the tag as usual: `release.yml` sees the version is already on npm,
96
- skips publishing and moves `v0`.
97
-
98
- The package is public; this repo is not. `release.yml` publishes through npm
99
- trusted publishing (no npm token is stored), and only after
100
- `scripts/check-public.mjs` has checked the tarball: only the allowed files,
101
- no source maps, nothing shaped like a secret, an email or a local path. CI
102
- runs the same check on every PR. Names it can't catch: keep customers and
103
- people out of the skill and the README.
104
-
105
- Projects get a new CLI only with `pnpm up -r --latest @navipartner/demo-video`
106
- (their lockfile pins it), but their workflow follows `v0` at once. So
107
- `render.yml` and `request.yml` must work with every CLI
108
- from 0.4 (older ones get a reply asking for the update); a change that
109
- can't is `v1`.
1
+ # @navipartner/demo-video
2
+
3
+ Marketing demo videos of a React app, in the NaviPartner house look, with any
4
+ customer's brand.
5
+
6
+ - Filmed by Playwright on the app's **own e2e mocks**, unchanged.
7
+ - For **pnpm** repos on **GitHub Actions**. Needs `@playwright/test` 1.59+.
8
+
9
+ ## Setup
10
+
11
+ From the repo root:
12
+
13
+ npx --ignore-scripts @navipartner/demo-video init
14
+ pnpm install
15
+
16
+ - Sets up the repo: `.gitignore`, the skill, the `demo video` label,
17
+ `.github/workflows/demo-video.yml`.
18
+ - Finds the apps: folders with a `playwright.config` and a `package.json`.
19
+ - **One app** (most repos): set up too. Done.
20
+ - **Several apps:** listed. Run `init --app <folder>` for each one you want.
21
+ - Safe to re-run. `--dry-run` previews.
22
+ - Then ask Claude for a demo video of the app: it finishes
23
+ `demo/demo.config.ts` and writes the primary story.
24
+
25
+ ## Getting a video
26
+
27
+ | Way | Who | Films | Video lands |
28
+ |---|---|---|---|
29
+ | **Run workflow form** (Actions → Demo video) | repo writers | a story + brand, from the chosen branch | the run's Summary |
30
+ | **PR comment** `/demo-video checkout in acme branding` | members | a story + brand, from the PR branch | reply on the PR |
31
+ | **Issue comment**, also from **Linear** | members, Linear's synced thread | a story + brand, from the default branch | reply on the issue (and in Linear) |
32
+ | **`demo video` label** on a PR | whoever labels | every story the PR adds or changes | comment on the PR |
33
+ | **Locally** `pnpm exec demo-video render` | a developer | any story | `demo/out/` |
34
+
35
+ - **Pitch draft** for a customer without a brand here: ask @Linear (below).
36
+ - The comment's first line is the request; `/demo-video help` lists the
37
+ stories and brands.
38
+ - No story → the primary story. No brand → the app's default.
39
+ - Unknown story or brand → a reply with the menu of stories and brands.
40
+ - Requests only **choose** a story. New stories come from a developer's PR.
41
+
42
+ ### From Linear
43
+
44
+ Through Linear's GitHub Issues Sync, both ways.
45
+
46
+ 1. **Once, a Linear admin:** Settings → Integrations → GitHub → link the team
47
+ to the repo with **Issues Sync**, two-way.
48
+ 2. In the issue's **synced thread**: `/demo-video help` for the menu, or
49
+ e.g. `/demo-video checkout in acme branding`.
50
+ 3. The reply syncs back with the video link (opens on GitHub: needs repo
51
+ read access).
52
+
53
+ ### Pitch drafts (in Linear)
54
+
55
+ An existing story in a customer's branding before they have a brand here,
56
+ for a sales pitch to that customer.
57
+
58
+ - **Ask:** on a Linear issue, assign or mention @Linear: "pitch draft of
59
+ checkout for Hotel Royal (hotelroyal.dk)". Linear's GitHub integration
60
+ needs code access to the repo.
61
+ - **What happens:** in a coding session, Linear's agent follows the skill's
62
+ `pitch.md`. It reads what the app needs (`brandNeeds`), researches the
63
+ brand from the customer's own website, and runs `demo-video pitch`, which
64
+ checks it, downloads the images and films at 30 fps. The MP4 and its
65
+ contact sheet are attached to the issue.
66
+ - **Nothing kept:** no commit, no PR, no brand in the repo. The session
67
+ works in `/tmp/pitch` and deletes it.
68
+ - **For that customer only:** Google Fonts stand-ins, and a person checks it
69
+ before sending. Any other use needs their permission and a brand in a PR.
70
+ - **Cost and time:** Linear AI credits, no API key. Linear's sandbox films
71
+ slowly: about 8× the video's length at 30 fps (a 75 s story ≈ 10 min).
72
+ - A `/demo-video … pitch …` comment on GitHub replies with these steps.
73
+
74
+ ## Stories and brands
75
+
76
+ - **Story:** `demo/stories/<name>.json`, built only from what the mocks
77
+ return. One per app is `"primary": true`.
78
+ - **Variant:** `checkout.acme.json`, the base story's steps in one
79
+ customer's words and brand.
80
+ - **Brand:** `demo/brands/<name>.json` plus a folder of assets. The engine
81
+ ships `navipartner`, the default.
82
+ - **Brand needs:** the adapter's `brandNeeds` lists what this app takes from
83
+ a brand (a background image, a secondary colour…). `check` fails on a
84
+ brand that lacks one.
85
+ - The skill (`skills/np-demo-video/`) has the look and every field.
86
+
87
+ ## Commands
88
+
89
+ In the app's folder: `pnpm exec demo-video <command>` (no command lists
90
+ them all).
91
+
92
+ | Command | Does |
93
+ |---|---|
94
+ | `list` | The stories, primary first. |
95
+ | `check` | Dry run in seconds. Fails on anything that would film badly, and names the step. |
96
+ | `render` | Films to `demo/out/<story>-<brand>-demo.mp4`, plus a contact sheet and ten stills. |
97
+ | `brand-needs` | What a brand must give this app. |
98
+ | `pitch <proposal.json>` | A pitch draft from a researched brand: check, film at 30 fps, video out of the repo. |
99
+
100
+ Flags:
101
+
102
+ - `--story <name>`: default the primary story.
103
+ - `--brand <name>`: default `navipartner`.
104
+ - `--draft`: 30 fps, about twice as fast, for tuning.
105
+ - `--brands <dir>`: a brands folder outside the repo, searched first.
106
+
107
+ ## Developing
108
+
109
+ pnpm install
110
+ pnpm test && pnpm typecheck && pnpm build
111
+
112
+ ## Releasing
113
+
114
+ 1. Bump `version` in `package.json` on `main`.
115
+ 2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
116
+
117
+ - CI publishes to npm as `latest` (trusted publishing, no npm token) and
118
+ moves the `v0` tag.
119
+ - Running `release.yml` by hand publishes a `next` prerelease.
120
+
121
+ **What goes public:**
122
+
123
+ - The package is public; this repo is not.
124
+ - `scripts/check-public.mjs` checks every tarball, in CI and before
125
+ publishing: only allowed files, no source maps, nothing shaped like a
126
+ secret, email or local path.
127
+ - It can't catch names: keep customers and people out of the skill and the
128
+ README.
129
+
130
+ **Compatibility:**
131
+
132
+ - Projects pin the CLI in their lockfile, but their workflow follows `v0`
133
+ at once.
134
+ - So `render.yml` and `request.yml` must work with every CLI from 0.4 (older
135
+ ones get a reply asking for the update). Anything else is `v1`.
package/bin/args.d.mts CHANGED
@@ -1,30 +1,39 @@
1
- export interface DemoArgs {
2
- command: "check" | "render" | "record" | "encode" | "stills" | "list";
3
- appDir: string;
4
- demoDir: string;
5
- adapterPath: string;
6
- workDir: string;
7
- runnerDir: string;
8
- configPath: string;
9
- outDir: string;
10
- storyboardPath: string | null;
11
- brand: string | null;
12
- json: boolean;
13
- draft: boolean;
14
- }
15
- export const COMMANDS: readonly string[];
16
- export function parseArgs(argv: string[], cwd: string): DemoArgs;
17
- export interface StoryEntry {
18
- name: string;
19
- description: string | null;
20
- primary: boolean;
21
- /** A variant: the story it extends, and the brand it is for. */
22
- extends?: string;
23
- brand?: string | null;
24
- }
25
- export function listStories(demoDir: string): StoryEntry[];
26
- export function resolveStoryboard(
27
- value: string,
28
- cwd: string,
29
- demoDir: string,
30
- ): string;
1
+ export interface DemoArgs {
2
+ command:
3
+ | "check"
4
+ | "render"
5
+ | "record"
6
+ | "encode"
7
+ | "stills"
8
+ | "list"
9
+ | "brand-needs";
10
+ appDir: string;
11
+ demoDir: string;
12
+ adapterPath: string;
13
+ workDir: string;
14
+ runnerDir: string;
15
+ configPath: string;
16
+ outDir: string;
17
+ storyboardPath: string | null;
18
+ brand: string | null;
19
+ /** `--brands`: a brands folder outside the repo, searched first. */
20
+ brandsDir: string | null;
21
+ json: boolean;
22
+ draft: boolean;
23
+ }
24
+ export const COMMANDS: readonly string[];
25
+ export function parseArgs(argv: string[], cwd: string): DemoArgs;
26
+ export interface StoryEntry {
27
+ name: string;
28
+ description: string | null;
29
+ primary: boolean;
30
+ /** A variant: the story it extends, and the brand it is for. */
31
+ extends?: string;
32
+ brand?: string | null;
33
+ }
34
+ export function listStories(demoDir: string): StoryEntry[];
35
+ export function resolveStoryboard(
36
+ value: string,
37
+ cwd: string,
38
+ demoDir: string,
39
+ ): string;