versioncam 0.1.1

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.
Files changed (182) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE.md +105 -0
  3. package/README.md +463 -0
  4. package/bin/versioncam.js +29 -0
  5. package/dist/.types-render/render-page/draw.d.ts +28 -0
  6. package/dist/.types-render/render-page/main.d.ts +24 -0
  7. package/dist/.types-render/render-page/theme.d.ts +37 -0
  8. package/dist/app-server.d.ts +59 -0
  9. package/dist/app-server.js +328 -0
  10. package/dist/app-server.js.map +1 -0
  11. package/dist/cli/app.d.ts +13 -0
  12. package/dist/cli/app.js +21 -0
  13. package/dist/cli/app.js.map +1 -0
  14. package/dist/cli/commands/check.d.ts +8 -0
  15. package/dist/cli/commands/check.js +51 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/commands/doctor.d.ts +8 -0
  18. package/dist/cli/commands/doctor.js +130 -0
  19. package/dist/cli/commands/doctor.js.map +1 -0
  20. package/dist/cli/commands/dsl.d.ts +16 -0
  21. package/dist/cli/commands/dsl.js +22 -0
  22. package/dist/cli/commands/dsl.js.map +1 -0
  23. package/dist/cli/commands/frame.d.ts +8 -0
  24. package/dist/cli/commands/frame.js +72 -0
  25. package/dist/cli/commands/frame.js.map +1 -0
  26. package/dist/cli/commands/init.d.ts +23 -0
  27. package/dist/cli/commands/init.js +109 -0
  28. package/dist/cli/commands/init.js.map +1 -0
  29. package/dist/cli/commands/inspect.d.ts +1 -0
  30. package/dist/cli/commands/inspect.js +32 -0
  31. package/dist/cli/commands/inspect.js.map +1 -0
  32. package/dist/cli/commands/install.d.ts +33 -0
  33. package/dist/cli/commands/install.js +66 -0
  34. package/dist/cli/commands/install.js.map +1 -0
  35. package/dist/cli/commands/login.d.ts +10 -0
  36. package/dist/cli/commands/login.js +49 -0
  37. package/dist/cli/commands/login.js.map +1 -0
  38. package/dist/cli/commands/measure.d.ts +1 -0
  39. package/dist/cli/commands/measure.js +36 -0
  40. package/dist/cli/commands/measure.js.map +1 -0
  41. package/dist/cli/commands/open-app.d.ts +14 -0
  42. package/dist/cli/commands/open-app.js +45 -0
  43. package/dist/cli/commands/open-app.js.map +1 -0
  44. package/dist/cli/commands/preview.d.ts +8 -0
  45. package/dist/cli/commands/preview.js +55 -0
  46. package/dist/cli/commands/preview.js.map +1 -0
  47. package/dist/cli/commands/record.d.ts +10 -0
  48. package/dist/cli/commands/record.js +86 -0
  49. package/dist/cli/commands/record.js.map +1 -0
  50. package/dist/cli/commands/render.d.ts +1 -0
  51. package/dist/cli/commands/render.js +93 -0
  52. package/dist/cli/commands/render.js.map +1 -0
  53. package/dist/cli/commands/review.d.ts +6 -0
  54. package/dist/cli/commands/review.js +89 -0
  55. package/dist/cli/commands/review.js.map +1 -0
  56. package/dist/cli/commands/sheet.d.ts +1 -0
  57. package/dist/cli/commands/sheet.js +48 -0
  58. package/dist/cli/commands/sheet.js.map +1 -0
  59. package/dist/cli/commands/stability.d.ts +14 -0
  60. package/dist/cli/commands/stability.js +110 -0
  61. package/dist/cli/commands/stability.js.map +1 -0
  62. package/dist/cli/main.d.ts +2 -0
  63. package/dist/cli/main.js +69 -0
  64. package/dist/cli/main.js.map +1 -0
  65. package/dist/cli/usage.d.ts +10 -0
  66. package/dist/cli/usage.js +46 -0
  67. package/dist/cli/usage.js.map +1 -0
  68. package/dist/config.d.ts +250 -0
  69. package/dist/config.js +154 -0
  70. package/dist/config.js.map +1 -0
  71. package/dist/core/camera.d.ts +30 -0
  72. package/dist/core/camera.js +94 -0
  73. package/dist/core/camera.js.map +1 -0
  74. package/dist/core/compose.d.ts +38 -0
  75. package/dist/core/compose.js +81 -0
  76. package/dist/core/compose.js.map +1 -0
  77. package/dist/core/cursor.d.ts +38 -0
  78. package/dist/core/cursor.js +103 -0
  79. package/dist/core/cursor.js.map +1 -0
  80. package/dist/core/easing.d.ts +15 -0
  81. package/dist/core/easing.js +33 -0
  82. package/dist/core/easing.js.map +1 -0
  83. package/dist/core/loop.d.ts +30 -0
  84. package/dist/core/loop.js +96 -0
  85. package/dist/core/loop.js.map +1 -0
  86. package/dist/core/motion-defaults.d.ts +61 -0
  87. package/dist/core/motion-defaults.js +62 -0
  88. package/dist/core/motion-defaults.js.map +1 -0
  89. package/dist/core/rng.d.ts +13 -0
  90. package/dist/core/rng.js +27 -0
  91. package/dist/core/rng.js.map +1 -0
  92. package/dist/core/sse.d.ts +15 -0
  93. package/dist/core/sse.js +16 -0
  94. package/dist/core/sse.js.map +1 -0
  95. package/dist/core/timeline.d.ts +146 -0
  96. package/dist/core/timeline.js +81 -0
  97. package/dist/core/timeline.js.map +1 -0
  98. package/dist/core/timing.d.ts +31 -0
  99. package/dist/core/timing.js +29 -0
  100. package/dist/core/timing.js.map +1 -0
  101. package/dist/core/typing.d.ts +12 -0
  102. package/dist/core/typing.js +35 -0
  103. package/dist/core/typing.js.map +1 -0
  104. package/dist/driver/clip.d.ts +71 -0
  105. package/dist/driver/clip.js +120 -0
  106. package/dist/driver/clip.js.map +1 -0
  107. package/dist/driver/compare.d.ts +34 -0
  108. package/dist/driver/compare.js +40 -0
  109. package/dist/driver/compare.js.map +1 -0
  110. package/dist/driver/gate.d.ts +36 -0
  111. package/dist/driver/gate.js +27 -0
  112. package/dist/driver/gate.js.map +1 -0
  113. package/dist/driver/launch.d.ts +43 -0
  114. package/dist/driver/launch.js +47 -0
  115. package/dist/driver/launch.js.map +1 -0
  116. package/dist/driver/page-hooks.d.ts +72 -0
  117. package/dist/driver/page-hooks.js +129 -0
  118. package/dist/driver/page-hooks.js.map +1 -0
  119. package/dist/driver/reports.d.ts +34 -0
  120. package/dist/driver/reports.js +42 -0
  121. package/dist/driver/reports.js.map +1 -0
  122. package/dist/driver/session.d.ts +285 -0
  123. package/dist/driver/session.js +773 -0
  124. package/dist/driver/session.js.map +1 -0
  125. package/dist/driver/settle.d.ts +41 -0
  126. package/dist/driver/settle.js +82 -0
  127. package/dist/driver/settle.js.map +1 -0
  128. package/dist/env.d.ts +11 -0
  129. package/dist/env.js +41 -0
  130. package/dist/env.js.map +1 -0
  131. package/dist/fixtures.d.ts +13 -0
  132. package/dist/fixtures.js +13 -0
  133. package/dist/fixtures.js.map +1 -0
  134. package/dist/index.d.ts +30 -0
  135. package/dist/index.js +16 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/inspect/inspect.d.ts +70 -0
  138. package/dist/inspect/inspect.js +176 -0
  139. package/dist/inspect/inspect.js.map +1 -0
  140. package/dist/inspect/measure.d.ts +40 -0
  141. package/dist/inspect/measure.js +107 -0
  142. package/dist/inspect/measure.js.map +1 -0
  143. package/dist/loader.d.ts +28 -0
  144. package/dist/loader.js +143 -0
  145. package/dist/loader.js.map +1 -0
  146. package/dist/page/assets/index-DUom5amc.js +1 -0
  147. package/dist/page/index.html +18 -0
  148. package/dist/render/encode.d.ts +40 -0
  149. package/dist/render/encode.js +183 -0
  150. package/dist/render/encode.js.map +1 -0
  151. package/dist/render/ffmpeg.d.ts +13 -0
  152. package/dist/render/ffmpeg.js +72 -0
  153. package/dist/render/ffmpeg.js.map +1 -0
  154. package/dist/render/presentation.d.ts +19 -0
  155. package/dist/render/presentation.js +27 -0
  156. package/dist/render/presentation.js.map +1 -0
  157. package/dist/render/render.d.ts +59 -0
  158. package/dist/render/render.js +144 -0
  159. package/dist/render/render.js.map +1 -0
  160. package/dist/render/sampling.d.ts +47 -0
  161. package/dist/render/sampling.js +129 -0
  162. package/dist/render/sampling.js.map +1 -0
  163. package/dist/render/sequence.d.ts +24 -0
  164. package/dist/render/sequence.js +105 -0
  165. package/dist/render/sequence.js.map +1 -0
  166. package/dist/render/serve.d.ts +35 -0
  167. package/dist/render/serve.js +124 -0
  168. package/dist/render/serve.js.map +1 -0
  169. package/dist/review/review.d.ts +54 -0
  170. package/dist/review/review.js +229 -0
  171. package/dist/review/review.js.map +1 -0
  172. package/dist/scene.d.ts +23 -0
  173. package/dist/scene.js +2 -0
  174. package/dist/scene.js.map +1 -0
  175. package/dsl.md +119 -0
  176. package/package.json +76 -0
  177. package/plugin/.claude-plugin/plugin.json +9 -0
  178. package/plugin/README.md +105 -0
  179. package/plugin/agents/versioncam-reviewer.md +63 -0
  180. package/plugin/skills/versioncam/SKILL.md +235 -0
  181. package/plugin/skills/versioncam/authoring.md +226 -0
  182. package/plugin/skills/versioncam/onboarding.md +199 -0
@@ -0,0 +1,130 @@
1
+ import { chromium } from "playwright";
2
+ import { existsSync } from "node:fs";
3
+ import { ffmpegStatus } from "../../render/ffmpeg.js";
4
+ import { ensureApp, probe } from "../../app-server.js";
5
+ import { NO_CONFIG, findConfigFile, loadConfig } from "../../loader.js";
6
+ const NOT_SERVING = "Start the app, point `baseUrl` at where it is served, or add `webServer` to the config.";
7
+ /**
8
+ * Is the app the config points at serving — and if not, can it be started?
9
+ *
10
+ * Not a property of the machine, unlike every other check here, but it is the
11
+ * next thing that stops a recording and the cheapest one to find out about.
12
+ * An authoring loop asks this before it writes a clip; without it the first
13
+ * sign of a stopped dev server is a clip failing on a locator.
14
+ *
15
+ * With a `webServer`, this is also the rehearsal for every other command: it
16
+ * starts the app exactly the way `record` would, reports how long that took,
17
+ * and stops it again, so a repository finds out its start command is wrong
18
+ * here rather than halfway into a clip. Without one, any HTTP answer at all
19
+ * counts as up, including a 401 or a redirect — an app behind a sign-in
20
+ * answers that way and records perfectly well.
21
+ */
22
+ async function appCheck(cwd) {
23
+ let config;
24
+ try {
25
+ config = await loadConfig(cwd);
26
+ }
27
+ catch (error) {
28
+ return {
29
+ name: "app",
30
+ ok: false,
31
+ detail: `config did not load: ${error.message}`,
32
+ fix: "Fix versioncam.config.ts; every other command loads it too.",
33
+ };
34
+ }
35
+ if (!config.webServer) {
36
+ const answer = await probe(config.baseUrl);
37
+ return answer.up
38
+ ? {
39
+ name: "app",
40
+ ok: true,
41
+ detail: `already serving at ${config.baseUrl} — HTTP ${answer.status}`,
42
+ }
43
+ : {
44
+ name: "app",
45
+ ok: false,
46
+ detail: `${config.baseUrl} — ${answer.reason}`,
47
+ fix: NOT_SERVING,
48
+ };
49
+ }
50
+ try {
51
+ const app = await ensureApp(config);
52
+ await app.stop();
53
+ return {
54
+ name: "app",
55
+ ok: true,
56
+ detail: app.started ? `${app.detail} (stopped again)` : app.detail,
57
+ };
58
+ }
59
+ catch (error) {
60
+ // The first line says what went wrong; the rest is the server's own
61
+ // output, which is usually where the reason is.
62
+ const [first, ...rest] = error.message.split("\n");
63
+ return { name: "app", ok: false, detail: first, fix: rest.join("\n") };
64
+ }
65
+ }
66
+ /**
67
+ * Can this machine record and encode?
68
+ *
69
+ * Reports every check rather than stopping at the first failure, because the
70
+ * usual case on a new machine is that two or three things are missing at once
71
+ * and being told about them one run at a time is tedious.
72
+ */
73
+ export async function doctor() {
74
+ const checks = [];
75
+ const major = Number(process.versions.node.split(".")[0]);
76
+ checks.push({
77
+ name: "node",
78
+ ok: major >= 22,
79
+ detail: `v${process.versions.node}`,
80
+ fix: "Install Node 22 or newer.",
81
+ });
82
+ let chromiumPath = "";
83
+ try {
84
+ chromiumPath = chromium.executablePath();
85
+ }
86
+ catch {
87
+ chromiumPath = "";
88
+ }
89
+ checks.push({
90
+ name: "chromium",
91
+ ok: chromiumPath !== "" && existsSync(chromiumPath),
92
+ detail: chromiumPath || "not installed",
93
+ fix: "npx versioncam install",
94
+ });
95
+ const { ffmpeg, ffprobe } = ffmpegStatus();
96
+ checks.push({
97
+ name: "ffmpeg",
98
+ ok: ffmpeg !== null,
99
+ detail: ffmpeg ?? "not found",
100
+ fix: "brew install ffmpeg (or apt install ffmpeg)",
101
+ });
102
+ checks.push({
103
+ name: "ffprobe",
104
+ ok: ffprobe !== null,
105
+ detail: ffprobe ?? "not found",
106
+ fix: "Installs with ffmpeg; ffmpeg-static does not include it.",
107
+ });
108
+ const config = findConfigFile(process.cwd());
109
+ checks.push({
110
+ name: "config",
111
+ ok: config !== null,
112
+ detail: config ?? `no versioncam.config.ts in ${process.cwd()}`,
113
+ fix: NO_CONFIG,
114
+ });
115
+ // Only with a config: without one there is no `baseUrl` to try, and saying
116
+ // so twice would be noise.
117
+ if (config !== null)
118
+ checks.push(await appCheck(process.cwd()));
119
+ for (const check of checks) {
120
+ process.stdout.write(`${check.ok ? "✓" : "✗"} ${check.name.padEnd(9)} ${check.detail}\n`);
121
+ if (!check.ok && check.fix) {
122
+ for (const line of check.fix.split("\n")) {
123
+ process.stdout.write(` ${line}\n`);
124
+ }
125
+ }
126
+ }
127
+ const failed = checks.filter((c) => !c.ok);
128
+ return failed.length === 0 ? 0 : 1;
129
+ }
130
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Where `dsl.md` sits relative to this module.
3
+ *
4
+ * Three levels up from `cli/commands/` reaches the package root from both
5
+ * `src` and `dist`, which are the same depth — so the built CLI and a test
6
+ * importing the source find the same file.
7
+ */
8
+ export declare const DSL_FILE: string;
9
+ /**
10
+ * Print the DSL reference.
11
+ *
12
+ * It exists as a command because the thing that needs it most has no way to
13
+ * browse a repository: an agent writing a clip asks the tool what the tool can
14
+ * do. A test holds the file and the class to the same list.
15
+ */
16
+ export declare function dsl(): Promise<number>;
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ /**
4
+ * Where `dsl.md` sits relative to this module.
5
+ *
6
+ * Three levels up from `cli/commands/` reaches the package root from both
7
+ * `src` and `dist`, which are the same depth — so the built CLI and a test
8
+ * importing the source find the same file.
9
+ */
10
+ export const DSL_FILE = fileURLToPath(new URL("../../../dsl.md", import.meta.url));
11
+ /**
12
+ * Print the DSL reference.
13
+ *
14
+ * It exists as a command because the thing that needs it most has no way to
15
+ * browse a repository: an agent writing a clip asks the tool what the tool can
16
+ * do. A test holds the file and the class to the same list.
17
+ */
18
+ export async function dsl() {
19
+ process.stdout.write(readFileSync(DSL_FILE, "utf8"));
20
+ return 0;
21
+ }
22
+ //# sourceMappingURL=dsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsl.js","sourceRoot":"","sources":["../../../src/cli/commands/dsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,aAAa,CACnC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAC5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * One frame of a recording, at the size it will be published.
3
+ *
4
+ * The command a reviewer reaches for when a contact sheet's tile is too small
5
+ * to be sure — is that caption legible, is that control inside the camera —
6
+ * and the answer has to be the real frame rather than a scaled-down one.
7
+ */
8
+ export declare function frame(argv: string[]): Promise<number>;
@@ -0,0 +1,72 @@
1
+ import { parseArgs } from "node:util";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { launchBrowser } from "../../driver/launch.js";
5
+ import { loadConfig } from "../../loader.js";
6
+ import { readTimeline, renderFrame } from "../../render/render.js";
7
+ import { frameAtMark } from "../../render/sampling.js";
8
+ /**
9
+ * One frame of a recording, at the size it will be published.
10
+ *
11
+ * The command a reviewer reaches for when a contact sheet's tile is too small
12
+ * to be sure — is that caption legible, is that control inside the camera —
13
+ * and the answer has to be the real frame rather than a scaled-down one.
14
+ */
15
+ export async function frame(argv) {
16
+ const { positionals, values } = parseArgs({
17
+ args: argv,
18
+ options: { t: { type: "string" }, at: { type: "string" } },
19
+ strict: false,
20
+ allowPositionals: true,
21
+ });
22
+ const [id, nth] = positionals;
23
+ const byTime = values.t;
24
+ const byBeat = values.at;
25
+ if (!id ||
26
+ (nth === undefined && byTime === undefined && byBeat === undefined)) {
27
+ process.stderr.write("Usage: versioncam frame <id> <n> (or --t <seconds>, or --at <beat label>)\n");
28
+ return 1;
29
+ }
30
+ const config = await loadConfig(process.cwd());
31
+ const dir = join(config.recordingsDir, id);
32
+ if (!existsSync(join(dir, "timeline.json"))) {
33
+ process.stderr.write(`No recording for "${id}". Run \`versioncam record ${id}\` first.\n`);
34
+ return 1;
35
+ }
36
+ // Seconds and beats are converted here rather than in the renderer: the
37
+ // frame rate and the marks are the recording's, and the printed file name
38
+ // should say which frame the time or the beat turned out to be.
39
+ const timeline = await readTimeline(config, id);
40
+ let wanted;
41
+ if (byBeat !== undefined) {
42
+ const beat = frameAtMark(timeline, byBeat, config.motion.crossfadeFrames);
43
+ if ("error" in beat) {
44
+ process.stderr.write(`${beat.error}\n`);
45
+ return 1;
46
+ }
47
+ wanted = beat.frame;
48
+ }
49
+ else {
50
+ wanted =
51
+ byTime !== undefined
52
+ ? Math.round(Number(byTime) * timeline.fps)
53
+ : Number(nth);
54
+ }
55
+ if (!Number.isFinite(wanted)) {
56
+ process.stderr.write(`Not a frame: ${byTime ?? nth}\n`);
57
+ return 1;
58
+ }
59
+ const browser = await launchBrowser();
60
+ try {
61
+ const png = await renderFrame(browser, config, { kind: "recording", id, dir }, wanted);
62
+ mkdirSync(config.outputDir, { recursive: true });
63
+ const file = join(config.outputDir, `${id}-frame-${wanted}.png`);
64
+ writeFileSync(file, png);
65
+ process.stdout.write(`${file}\n`);
66
+ return 0;
67
+ }
68
+ finally {
69
+ await browser.close();
70
+ }
71
+ }
72
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../../../src/cli/commands/frame.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Install the skill, and nothing else.
3
+ *
4
+ * The skill is the whole product surface for an agent: one directory — the
5
+ * orchestration, the authoring brief, the first-run brief — and the reviewer's
6
+ * file. No config stub, because the first run writes the real one from the
7
+ * repository, and a stub is a config somebody has to finish by hand.
8
+ *
9
+ * By default into `.claude/`, where Claude Code finds both: the skill under
10
+ * `skills/versioncam/`, the reviewer under `agents/`, where it registers as an
11
+ * agent whose tools are limited to looking. `--into <dir>` is for a host that
12
+ * reads skills from elsewhere — which path that is gets checked against the
13
+ * host's own documentation by whoever needs it, not guessed here — and there
14
+ * the reviewer goes inside the skill's directory, where the skill looks for it
15
+ * on a host that cannot register agents.
16
+ *
17
+ * It refuses to overwrite rather than merging, and it refuses *everything* if
18
+ * any one destination exists. A half-installed set is the bad outcome: a new
19
+ * skill beside an old reviewer file fails in the middle of a round instead of
20
+ * at the copy. And the prompts are the part of this package people most want
21
+ * to edit, so a second `init` must never silently discard their edits.
22
+ */
23
+ export declare function init(argv: string[]): Promise<number>;
@@ -0,0 +1,109 @@
1
+ import { parseArgs } from "node:util";
2
+ import { existsSync } from "node:fs";
3
+ import { copyFile, mkdir, readdir } from "node:fs/promises";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ /**
7
+ * The plugin directory inside this package, as installed.
8
+ *
9
+ * `dist/cli/commands/init.js` sits three levels under the package root, and
10
+ * `plugin/` is shipped beside `dist/` by the `files` list in package.json.
11
+ */
12
+ const PLUGIN_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "plugin");
13
+ /** What `init --claude` wrote before the package was published, which the new skill replaces. */
14
+ const OLD_INSTALL = [
15
+ join(".claude", "skills", "demos-author"),
16
+ join(".claude", "agents", "demo-author.md"),
17
+ join(".claude", "agents", "demo-reviewer.md"),
18
+ ];
19
+ /** Every file under `dir`, as paths relative to it. */
20
+ async function filesUnder(dir) {
21
+ const entries = await readdir(dir, { recursive: true, withFileTypes: true });
22
+ return entries
23
+ .filter((e) => e.isFile())
24
+ .map((e) => relative(dir, join(e.parentPath, e.name)))
25
+ .sort();
26
+ }
27
+ /**
28
+ * Install the skill, and nothing else.
29
+ *
30
+ * The skill is the whole product surface for an agent: one directory — the
31
+ * orchestration, the authoring brief, the first-run brief — and the reviewer's
32
+ * file. No config stub, because the first run writes the real one from the
33
+ * repository, and a stub is a config somebody has to finish by hand.
34
+ *
35
+ * By default into `.claude/`, where Claude Code finds both: the skill under
36
+ * `skills/versioncam/`, the reviewer under `agents/`, where it registers as an
37
+ * agent whose tools are limited to looking. `--into <dir>` is for a host that
38
+ * reads skills from elsewhere — which path that is gets checked against the
39
+ * host's own documentation by whoever needs it, not guessed here — and there
40
+ * the reviewer goes inside the skill's directory, where the skill looks for it
41
+ * on a host that cannot register agents.
42
+ *
43
+ * It refuses to overwrite rather than merging, and it refuses *everything* if
44
+ * any one destination exists. A half-installed set is the bad outcome: a new
45
+ * skill beside an old reviewer file fails in the middle of a round instead of
46
+ * at the copy. And the prompts are the part of this package people most want
47
+ * to edit, so a second `init` must never silently discard their edits.
48
+ */
49
+ export async function init(argv) {
50
+ const { values } = parseArgs({
51
+ args: argv,
52
+ options: {
53
+ force: { type: "boolean", default: false },
54
+ into: { type: "string" },
55
+ claude: { type: "boolean", default: false },
56
+ },
57
+ strict: false,
58
+ allowPositionals: true,
59
+ });
60
+ if (values.claude === true) {
61
+ process.stderr.write("`--claude` is gone: `versioncam init` installs into .claude/ by default, and `--into <dir>` puts the skill somewhere else.\n");
62
+ return 1;
63
+ }
64
+ const skillDir = join(PLUGIN_DIR, "skills", "versioncam");
65
+ const reviewer = join(PLUGIN_DIR, "agents", "versioncam-reviewer.md");
66
+ if (!existsSync(skillDir) || !existsSync(reviewer)) {
67
+ process.stderr.write(`No skill at ${skillDir}.\n` +
68
+ "Reinstall versioncam, or run this from an installed copy.\n");
69
+ return 1;
70
+ }
71
+ const cwd = process.cwd();
72
+ const into = values.into;
73
+ const skillTarget = into
74
+ ? resolve(cwd, into, "versioncam")
75
+ : join(cwd, ".claude", "skills", "versioncam");
76
+ const reviewerTarget = into
77
+ ? join(skillTarget, "versioncam-reviewer.md")
78
+ : join(cwd, ".claude", "agents", "versioncam-reviewer.md");
79
+ const copies = (await filesUnder(skillDir)).map((file) => ({
80
+ from: join(skillDir, file),
81
+ to: join(skillTarget, file),
82
+ }));
83
+ copies.push({ from: reviewer, to: reviewerTarget });
84
+ const existing = copies.filter((c) => existsSync(c.to));
85
+ if (existing.length > 0 && values.force !== true) {
86
+ process.stderr.write(`Already there:\n` +
87
+ existing.map((c) => ` ${relative(cwd, c.to)}\n`).join("") +
88
+ "Nothing was copied. Pass --force to overwrite.\n");
89
+ return 1;
90
+ }
91
+ for (const { from, to } of copies) {
92
+ await mkdir(dirname(to), { recursive: true });
93
+ await copyFile(from, to);
94
+ process.stdout.write(`${relative(cwd, to)}\n`);
95
+ }
96
+ if (into) {
97
+ process.stdout.write(`\nThe reviewer is ${relative(cwd, reviewerTarget)}. A host that can register agents should register it as versioncam-reviewer; one that cannot, the skill reads it from there.\n`);
98
+ }
99
+ // Two skills answering the same request is worse than either alone.
100
+ const old = OLD_INSTALL.filter((path) => existsSync(join(cwd, path)));
101
+ if (old.length > 0) {
102
+ process.stdout.write(`\nAn older install is still here, and replaced by this one — remove it:\n` +
103
+ old.map((path) => ` ${path}\n`).join(""));
104
+ }
105
+ process.stdout.write(`\nInstalled. Ask your agent: /versioncam "<what the clip should show>"\n` +
106
+ "The first run sets this repository up. Documentation: https://version.cam\n");
107
+ return 0;
108
+ }
109
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function inspect(argv: string[]): Promise<number>;
@@ -0,0 +1,32 @@
1
+ import { parseArgs } from "node:util";
2
+ import { launchBrowser } from "../../driver/launch.js";
3
+ import { loadConfig } from "../../loader.js";
4
+ import { formatInventory, inspectPage } from "../../inspect/inspect.js";
5
+ import { drivingApp } from "../app.js";
6
+ import { openAppPage } from "./open-app.js";
7
+ export async function inspect(argv) {
8
+ const { positionals, values } = parseArgs({
9
+ args: argv,
10
+ options: { json: { type: "boolean" } },
11
+ strict: false,
12
+ allowPositionals: true,
13
+ });
14
+ const config = await loadConfig(process.cwd());
15
+ const path = positionals[0] ?? "/";
16
+ return drivingApp(config, async () => {
17
+ const browser = await launchBrowser();
18
+ try {
19
+ const { page, context } = await openAppPage(browser, config, path);
20
+ const inventory = await inspectPage(page);
21
+ process.stdout.write(values.json
22
+ ? `${JSON.stringify(inventory, null, 2)}\n`
23
+ : `${formatInventory(inventory)}\n`);
24
+ await context.close();
25
+ return 0;
26
+ }
27
+ finally {
28
+ await browser.close();
29
+ }
30
+ });
31
+ }
32
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../../src/cli/commands/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QACxC,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;QACtC,MAAM,EAAE,KAAK;QACb,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAI,WAAW,CAAC,CAAC,CAAwB,IAAI,GAAG,CAAC;IAE3D,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;YACnE,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,MAAM,CAAC,IAAI;gBACT,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;gBAC3C,CAAC,CAAC,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,CACtC,CAAC;YACF,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC;QACX,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The Playwright this package drives: its version, and the CLI that ships
3
+ * with it.
4
+ *
5
+ * Resolved from versioncam's own dependency rather than the app's, because
6
+ * they need not agree. Playwright is pinned exactly here — a new Chromium
7
+ * changes every captured pixel, so an upgrade is an event with a changelog
8
+ * entry, never a range — while the app may have a Playwright of its own for
9
+ * its tests, at any version. `npx playwright install` resolves the app's and
10
+ * installs the Chromium *it* expects, which is how a machine ends up with a
11
+ * browser and `doctor` still saying there is none.
12
+ */
13
+ export declare function pinnedPlaywright(): {
14
+ version: string;
15
+ cli: string;
16
+ };
17
+ /**
18
+ * The command `versioncam install` runs, as argv.
19
+ *
20
+ * Both Chromium builds, deliberately: recording is headless and uses the
21
+ * headless shell, while `login` and `preview` open a window a person uses
22
+ * and need the full browser.
23
+ */
24
+ export declare function installArgv(node: string, cli: string, withDeps: boolean): string[];
25
+ /**
26
+ * Install the Chromium this version of versioncam records with.
27
+ *
28
+ * `--with-deps` adds the system libraries Chromium needs on a bare Linux
29
+ * image, which is the usual state of a CI runner and asks for root there.
30
+ * The command it runs is printed first: in a CI log, which Playwright
31
+ * installed which browser is the first question when something differs.
32
+ */
33
+ export declare function install(argv: string[]): Promise<number>;
@@ -0,0 +1,66 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import { parseArgs } from "node:util";
5
+ /**
6
+ * The Playwright this package drives: its version, and the CLI that ships
7
+ * with it.
8
+ *
9
+ * Resolved from versioncam's own dependency rather than the app's, because
10
+ * they need not agree. Playwright is pinned exactly here — a new Chromium
11
+ * changes every captured pixel, so an upgrade is an event with a changelog
12
+ * entry, never a range — while the app may have a Playwright of its own for
13
+ * its tests, at any version. `npx playwright install` resolves the app's and
14
+ * installs the Chromium *it* expects, which is how a machine ends up with a
15
+ * browser and `doctor` still saying there is none.
16
+ */
17
+ export function pinnedPlaywright() {
18
+ const require = createRequire(import.meta.url);
19
+ const manifest = require.resolve("playwright/package.json");
20
+ const { version } = require(manifest);
21
+ return { version, cli: join(dirname(manifest), "cli.js") };
22
+ }
23
+ /**
24
+ * The command `versioncam install` runs, as argv.
25
+ *
26
+ * Both Chromium builds, deliberately: recording is headless and uses the
27
+ * headless shell, while `login` and `preview` open a window a person uses
28
+ * and need the full browser.
29
+ */
30
+ export function installArgv(node, cli, withDeps) {
31
+ return [
32
+ node,
33
+ cli,
34
+ "install",
35
+ ...(withDeps ? ["--with-deps"] : []),
36
+ "chromium",
37
+ ];
38
+ }
39
+ /**
40
+ * Install the Chromium this version of versioncam records with.
41
+ *
42
+ * `--with-deps` adds the system libraries Chromium needs on a bare Linux
43
+ * image, which is the usual state of a CI runner and asks for root there.
44
+ * The command it runs is printed first: in a CI log, which Playwright
45
+ * installed which browser is the first question when something differs.
46
+ */
47
+ export async function install(argv) {
48
+ const { values } = parseArgs({
49
+ args: argv,
50
+ options: { "with-deps": { type: "boolean" } },
51
+ strict: false,
52
+ });
53
+ const playwright = pinnedPlaywright();
54
+ const [command, ...args] = installArgv(process.execPath, playwright.cli, values["with-deps"] === true);
55
+ process.stdout.write(`Chromium for Playwright ${playwright.version}, the version versioncam records with:\n` +
56
+ ` ${[command, ...args].join(" ")}\n`);
57
+ return new Promise((resolve) => {
58
+ const child = spawn(command, args, { stdio: "inherit" });
59
+ child.on("error", (error) => {
60
+ process.stderr.write(`Could not run the installer: ${error.message}\n`);
61
+ resolve(1);
62
+ });
63
+ child.on("exit", (code) => resolve(code ?? 1));
64
+ });
65
+ }
66
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC5D,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAwB,CAAC;IAC7D,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,GAAW,EACX,QAAiB;IAEjB,OAAO;QACL,IAAI;QACJ,GAAG;QACH,SAAS;QACT,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,UAAU;KACX,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc;IAC1C,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;QAC7C,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;IACtC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,WAAW,CACpC,OAAO,CAAC,QAAQ,EAChB,UAAU,CAAC,GAAG,EACd,MAAM,CAAC,WAAW,CAAC,KAAK,IAAI,CAC7B,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2BAA2B,UAAU,CAAC,OAAO,0CAA0C;QACrF,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CACxC,CAAC;IACF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACzD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACxE,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sign in once, by hand, and keep the session.
3
+ *
4
+ * Every app authenticates differently, and scripting a sign-in means putting
5
+ * credentials somewhere a recorder can read them. A saved browser session
6
+ * avoids both: a person signs in the way they always do, and recordings reuse
7
+ * the result. It is still a credential — the config's path should be
8
+ * gitignored, and it is inside the recorder's own output directory by default.
9
+ */
10
+ export declare function login(): Promise<number>;
@@ -0,0 +1,49 @@
1
+ import { launchBrowser } from "../../driver/launch.js";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { loadConfig } from "../../loader.js";
5
+ import { drivingApp } from "../app.js";
6
+ /**
7
+ * Sign in once, by hand, and keep the session.
8
+ *
9
+ * Every app authenticates differently, and scripting a sign-in means putting
10
+ * credentials somewhere a recorder can read them. A saved browser session
11
+ * avoids both: a person signs in the way they always do, and recordings reuse
12
+ * the result. It is still a credential — the config's path should be
13
+ * gitignored, and it is inside the recorder's own output directory by default.
14
+ */
15
+ export async function login() {
16
+ const config = await loadConfig(process.cwd());
17
+ const auth = config.auth;
18
+ if (!auth?.storageState) {
19
+ process.stderr.write('`versioncam login` needs `auth: { storageState: "..." }` in the config.\n' +
20
+ "An app that signs in with a `login` hook does not need this command.\n");
21
+ return 1;
22
+ }
23
+ const storageState = auth.storageState;
24
+ return drivingApp(config, async () => {
25
+ const browser = await launchBrowser({ headless: false });
26
+ try {
27
+ const context = await browser.newContext({ viewport: config.viewport });
28
+ const page = await context.newPage();
29
+ await page.goto(config.baseUrl);
30
+ process.stdout.write(`Sign in to ${config.baseUrl} in the window that opened.\n` +
31
+ "Press Enter here when you are done. ");
32
+ await new Promise((resolve) => {
33
+ process.stdin.resume();
34
+ process.stdin.once("data", () => {
35
+ process.stdin.pause();
36
+ resolve();
37
+ });
38
+ });
39
+ await mkdir(dirname(storageState), { recursive: true });
40
+ await context.storageState({ path: storageState });
41
+ }
42
+ finally {
43
+ await browser.close();
44
+ }
45
+ process.stdout.write(`\nSaved the session to ${storageState}\n`);
46
+ return 0;
47
+ });
48
+ }
49
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/cli/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2EAA2E;YACzE,wEAAwE,CAC3E,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YACxE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,cAAc,MAAM,CAAC,OAAO,+BAA+B;gBACzD,sCAAsC,CACzC,CAAC;YACF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACvB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;oBAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,YAAY,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function measure(argv: string[]): Promise<number>;