@navipartner/demo-video 0.5.0 → 0.6.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,135 +1,132 @@
1
1
  # @navipartner/demo-video
2
2
 
3
- Marketing demo videos of a React app, in the NaviPartner house look, with any
4
- customer's brand.
3
+ Marketing demo videos of React apps.
5
4
 
6
- - Filmed by Playwright on the app's **own e2e mocks**, unchanged.
7
- - For **pnpm** repos on **GitHub Actions**. Needs `@playwright/test` 1.59+.
5
+ - Filmed on the app's own e2e mocks.
6
+ - NaviPartner look: camera pushes, serif cards.
7
+ - NaviPartner's brand, the only one kept.
8
+ - Customer brands researched per video, never saved.
9
+ - Smooth 60 fps, frame by frame.
10
+ - pnpm repos, GitHub Actions, `@playwright/test` 1.59+.
8
11
 
9
12
  ## Setup
10
13
 
11
- From the repo root:
12
-
13
14
  npx --ignore-scripts @navipartner/demo-video init
14
15
  pnpm install
15
16
 
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.
17
+ - Adds the skill, label and workflow.
18
+ - Finds apps with a `playwright.config`.
19
+ - One app: set up directly.
20
+ - Several apps: `init --app <folder>` each.
21
+ - Safe to re-run; `--dry-run` previews.
22
+ - Then ask Claude for a video.
23
+
24
+ ## Get a video
25
+
26
+ | Ask | Who | You get |
27
+ |---|---|---|
28
+ | @Linear "video of checkout from np-pos for Northwind (northwind.example)" | Linear users | that story in Northwind's researched brand, attached |
29
+ | @Linear "video of checkout from np-pos" | Linear users | that story, NaviPartner brand, attached |
30
+ | `/demo-video` on a PR | members | primary story, from the PR branch |
31
+ | `/demo-video checkout` | members | that story |
32
+ | `/demo-video checkout in danish` | members | its translation |
33
+ | `/demo-video help` | members | menu of stories |
34
+ | `demo video` label on a PR | whoever labels | every changed story, each push |
35
+ | Actions → Demo video → Run workflow | repo writers | any story, any branch |
36
+ | `pnpm exec demo-video render` | developers | any story, locally |
37
+
38
+ - Requests only pick existing stories.
39
+ - New stories come via PRs.
40
+ - PR comments: first line is the request.
41
+ - Unknown names get the menu.
42
+ - Customer branding only through @Linear.
43
+
44
+ ### In Linear
45
+
46
+ - Ask @Linear in plain words.
47
+ - Name the repo, story and customer.
48
+ - Follows the skill's `linear.md`.
49
+ - NaviPartner itself: 60 fps video.
50
+ - A customer: a pitch draft.
51
+ - MP4 and contact sheet attached.
52
+ - Nothing committed, nothing kept.
53
+ - Linear AI credits, no API key.
54
+ - Slow sandbox: ~15× video length.
55
+
56
+ ### Pitch drafts
57
+
58
+ - Researched from the customer's own website.
59
+ - Checked, then filmed at 30 fps.
60
+ - Google Fonts stand-ins only.
61
+ - That customer only; check before sending.
62
+ - Other use needs their written permission.
73
63
 
74
64
  ## Stories and brands
75
65
 
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.
66
+ - **Story:** `demo/stories/<name>.json`, scenes as JSON.
67
+ - Built only from the mocks.
68
+ - One story per app is primary.
69
+ - **Translation:** its own story, e.g. `checkout-da.json`.
70
+ - Same flow, cards in that language.
71
+ - **Brands:** only `navipartner`, shipped with the engine.
72
+ - No `demo/brands/` in repos: `check` refuses it.
73
+ - Stories never name a brand.
74
+ - **Brand needs:** the adapter lists what a brand gives.
75
+ - Pitch research fills exactly those.
76
+ - The skill has fields and the look.
86
77
 
87
78
  ## Commands
88
79
 
89
- In the app's folder: `pnpm exec demo-video <command>` (no command lists
90
- them all).
80
+ Run in the app's folder: `pnpm exec demo-video <command>`.
91
81
 
92
82
  | Command | Does |
93
83
  |---|---|
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:
84
+ | `init` | sets up a repo or app |
85
+ | `list` | stories, primary first |
86
+ | `check` | dry run in seconds, names problems |
87
+ | `render` | MP4, contact sheet, ten stills |
88
+ | `record` / `encode` / `stills` | render in parts |
89
+ | `brand-needs` | what a brand must give |
90
+ | `pitch <proposal.json>` | pitch draft from a researched brand |
91
+ | `pick` | what a request means (workflows) |
92
+
93
+ | Flag | Means |
94
+ |---|---|
95
+ | `--story <name>` | default: primary story |
96
+ | `--draft` | 30 fps, twice as fast |
97
+ | `--app <dir>` | app folder, default `.` |
98
+ | `--json` | machine-readable output |
99
+ | `--brand`, `--brands <dir>` | used by `pitch` only |
101
100
 
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.
101
+ Output lands in `demo/out/`. Failures keep Playwright's output in `demo/out/failed/`.
106
102
 
107
- ## Developing
103
+ ## Develop
108
104
 
109
105
  pnpm install
110
- pnpm test && pnpm typecheck && pnpm build
106
+ pnpm test && pnpm typecheck && pnpm lint && pnpm build
107
+
108
+ - CI runs tests, actionlint, smoke render.
111
109
 
112
- ## Releasing
110
+ ## Release
113
111
 
114
- 1. Bump `version` in `package.json` on `main`.
112
+ 1. Bump `version` on main.
115
113
  2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
116
114
 
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.
115
+ - CI publishes to npm (`latest`).
116
+ - Trusted publishing, no npm token.
117
+ - `v0` tag moves along.
118
+ - Manual `release.yml` run: `next` prerelease.
120
119
 
121
- **What goes public:**
120
+ ## Public package, private repo
122
121
 
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.
122
+ - `check-public.mjs` checks every tarball.
123
+ - Allowed files only, no source maps.
124
+ - No secrets, emails or local paths.
125
+ - Names aren't caught: keep customers out.
129
126
 
130
- **Compatibility:**
127
+ ## Compatibility
131
128
 
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`.
129
+ - Projects pin the CLI version.
130
+ - Their workflows follow `v0` immediately.
131
+ - Workflows must support CLI 0.4+.
132
+ - Breaking that means `v1`.
package/bin/args.d.mts CHANGED
@@ -27,9 +27,6 @@ export interface StoryEntry {
27
27
  name: string;
28
28
  description: string | null;
29
29
  primary: boolean;
30
- /** A variant: the story it extends, and the brand it is for. */
31
- extends?: string;
32
- brand?: string | null;
33
30
  }
34
31
  export function listStories(demoDir: string): StoryEntry[];
35
32
  export function resolveStoryboard(
package/bin/args.mjs CHANGED
@@ -18,7 +18,7 @@ Without --story, check, render and record use the app's primary story.
18
18
  brand-needs: what a brand must give this app (its adapter's brandNeeds).
19
19
  Setup: demo-video init [--app <dir>] [--dry-run]
20
20
  For a /demo-video request: demo-video pick --request "<text>" [--json] <app dir>...
21
- For a pitch draft (the skill's pitch.md): demo-video pitch <proposal.json> [--app <dir>] [--story <name>] [--out <dir>] [--json]`;
21
+ For a pitch draft (the skill's linear.md): demo-video pitch <proposal.json> [--app <dir>] [--story <name>] [--out <dir>] [--json]`;
22
22
 
23
23
  export const resolveStoryboard = (value, cwd, demoDir) => {
24
24
  if (value.endsWith(".json")) {
@@ -62,10 +62,6 @@ export const listStories = (demoDir) => {
62
62
  name: f.slice(0, -".json".length),
63
63
  description: story.description ?? null,
64
64
  primary: story.primary === true,
65
- // A variant: the base story's flow, for one brand.
66
- ...(story.extends
67
- ? { extends: story.extends, brand: story.brand ?? null }
68
- : {}),
69
65
  };
70
66
  })
71
67
  .sort(
@@ -136,7 +136,7 @@ const runPick = (argv) => {
136
136
  const arg = argv[i];
137
137
  if (arg === "--json") {
138
138
  json = true;
139
- } else if (["--request", "--app", "--story", "--brand"].includes(arg)) {
139
+ } else if (["--request", "--app", "--story"].includes(arg)) {
140
140
  flags[arg.slice(2)] = argv[++i] ?? "";
141
141
  } else {
142
142
  dirs.push(arg);
@@ -144,7 +144,7 @@ const runPick = (argv) => {
144
144
  }
145
145
  if (dirs.length === 0) {
146
146
  console.error(
147
- 'Usage: demo-video pick [--request "<text>" | --app <a> --story <s> --brand <b>] [--json] <app dir>...',
147
+ 'Usage: demo-video pick [--request "<text>" | --app <a> --story <s>] [--json] <app dir>...',
148
148
  );
149
149
  return 2;
150
150
  }
@@ -165,15 +165,13 @@ const runPick = (argv) => {
165
165
  } else if (out.menu) {
166
166
  console.log(out.markdown);
167
167
  } else {
168
- console.log(
169
- `${out.app}: story ${out.story}, brand ${out.brand ?? "(the app's default)"}`,
170
- );
168
+ console.log(`${out.app}: story ${out.story}`);
171
169
  }
172
170
  return 0;
173
171
  };
174
172
 
175
173
  // demo-video pitch <proposal.json> [--app <dir>] [--story <name>] [--out <dir>]
176
- // [--json]: a pitch draft from a researched proposal (the skill's pitch.md).
174
+ // [--json]: a pitch draft from a researched proposal (the skill's linear.md).
177
175
  // It builds the brand in a temp folder (pitch.mjs), checks the story with
178
176
  // it, films it at 30 fps and moves the video and its contact sheet to --out
179
177
  // (default: the proposal's folder). The brand and the repo's copies go.
@@ -345,7 +343,7 @@ const run = () => {
345
343
  } else {
346
344
  for (const s of stories) {
347
345
  console.log(
348
- `${s.primary ? "* " : " "}${s.name}${s.description ? `: ${s.description}` : ""}${s.extends ? ` (${s.extends}, for ${s.brand})` : ""}`,
346
+ `${s.primary ? "* " : " "}${s.name}${s.description ? `: ${s.description}` : ""}`,
349
347
  );
350
348
  }
351
349
  }
package/bin/init.mjs CHANGED
@@ -22,16 +22,15 @@ const engineVersion = () =>
22
22
 
23
23
  export const CALLER_WORKFLOW = `name: Demo video
24
24
  # The "${LABEL}" label films the stories a PR adds or changes. A comment
25
- # "/demo-video [<story>] [in <brand> branding]" on a PR (its branch) or an
26
- # issue (the default branch; also from Linear's synced thread), or Actions →
27
- # Demo video → Run workflow, films an existing story with a brand.
28
- # Pitch drafts for a customer without a brand here are @Linear's (README).
25
+ # "/demo-video [<story>]" on a PR, or Actions → Demo video → Run workflow,
26
+ # films an existing story in NaviPartner's brand. A customer's branding: ask
27
+ # @Linear (the skill's linear.md); repos keep no brands.
29
28
  on:
30
29
  pull_request:
31
30
  types: [labeled, synchronize]
32
31
  issue_comment:
33
32
  types: [created]
34
- # Actions → Demo video → Run workflow: film a story with a brand.
33
+ # Actions → Demo video → Run workflow: film a story.
35
34
  workflow_dispatch:
36
35
  inputs:
37
36
  app:
@@ -40,9 +39,6 @@ on:
40
39
  story:
41
40
  description: "Story name (empty: the app's primary story)"
42
41
  default: ""
43
- brand:
44
- description: "Brand (empty: the app's default)"
45
- default: ""
46
42
  permissions: {}
47
43
  jobs:
48
44
  render:
@@ -52,7 +48,7 @@ jobs:
52
48
  pull-requests: write
53
49
  uses: navipartner/demo-video/.github/workflows/render.yml@v0
54
50
  request:
55
- if: github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/demo-video'))
51
+ if: github.event_name == 'workflow_dispatch' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/demo-video'))
56
52
  permissions:
57
53
  contents: read
58
54
  pull-requests: write
@@ -61,7 +57,6 @@ jobs:
61
57
  with:
62
58
  app: \${{ inputs.app }}
63
59
  story: \${{ inputs.story }}
64
- brand: \${{ inputs.brand }}
65
60
  `;
66
61
 
67
62
  const read = (file) =>
package/bin/pick.d.mts CHANGED
@@ -4,13 +4,11 @@ export interface AppMenu {
4
4
  dir: string;
5
5
  name: string;
6
6
  stories: StoryEntry[];
7
- brands: string[];
8
7
  }
9
8
  export type Pick =
10
9
  | {
11
10
  app: string;
12
11
  story: string;
13
- brand: string | null;
14
12
  menu?: undefined;
15
13
  }
16
14
  | { menu: true; reason: string; apps?: AppMenu[] };
@@ -19,5 +17,5 @@ export function pick(request: string, apps: AppMenu[]): Pick;
19
17
  export function menuMarkdown(apps: AppMenu[], reason: string): string;
20
18
  export function pickExact(
21
19
  apps: AppMenu[],
22
- names: { app?: string; story?: string; brand?: string },
20
+ names: { app?: string; story?: string },
23
21
  ): Pick;