playwright-director 0.2.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.
Files changed (122) hide show
  1. package/README.md +860 -0
  2. package/dist/Tutorial.d.ts +108 -0
  3. package/dist/Tutorial.d.ts.map +1 -0
  4. package/dist/Tutorial.js +767 -0
  5. package/dist/Tutorial.js.map +1 -0
  6. package/dist/bin/export-transcript.d.ts +3 -0
  7. package/dist/bin/export-transcript.d.ts.map +1 -0
  8. package/dist/bin/export-transcript.js +111 -0
  9. package/dist/bin/export-transcript.js.map +1 -0
  10. package/dist/cursor.d.ts +24 -0
  11. package/dist/cursor.d.ts.map +1 -0
  12. package/dist/cursor.js +96 -0
  13. package/dist/cursor.js.map +1 -0
  14. package/dist/index.d.ts +16 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +3 -0
  19. package/dist/init.d.ts.map +1 -0
  20. package/dist/init.js +183 -0
  21. package/dist/init.js.map +1 -0
  22. package/dist/merge.d.ts +26 -0
  23. package/dist/merge.d.ts.map +1 -0
  24. package/dist/merge.js +65 -0
  25. package/dist/merge.js.map +1 -0
  26. package/dist/music.d.ts +29 -0
  27. package/dist/music.d.ts.map +1 -0
  28. package/dist/music.js +107 -0
  29. package/dist/music.js.map +1 -0
  30. package/dist/overlay-html.d.ts +44 -0
  31. package/dist/overlay-html.d.ts.map +1 -0
  32. package/dist/overlay-html.js +150 -0
  33. package/dist/overlay-html.js.map +1 -0
  34. package/dist/overlay.d.ts +39 -0
  35. package/dist/overlay.d.ts.map +1 -0
  36. package/dist/overlay.js +134 -0
  37. package/dist/overlay.js.map +1 -0
  38. package/dist/postinstall.d.ts +3 -0
  39. package/dist/postinstall.d.ts.map +1 -0
  40. package/dist/postinstall.js +38 -0
  41. package/dist/postinstall.js.map +1 -0
  42. package/dist/reporter.d.ts +22 -0
  43. package/dist/reporter.d.ts.map +1 -0
  44. package/dist/reporter.js +166 -0
  45. package/dist/reporter.js.map +1 -0
  46. package/dist/site/build-site.d.ts +2 -0
  47. package/dist/site/build-site.d.ts.map +1 -0
  48. package/dist/site/build-site.js +82 -0
  49. package/dist/site/build-site.js.map +1 -0
  50. package/dist/site/embed.d.ts +48 -0
  51. package/dist/site/embed.d.ts.map +1 -0
  52. package/dist/site/embed.js +45 -0
  53. package/dist/site/embed.js.map +1 -0
  54. package/dist/site/generate-manifest.d.ts +15 -0
  55. package/dist/site/generate-manifest.d.ts.map +1 -0
  56. package/dist/site/generate-manifest.js +106 -0
  57. package/dist/site/generate-manifest.js.map +1 -0
  58. package/dist/site/scaffold.d.ts +3 -0
  59. package/dist/site/scaffold.d.ts.map +1 -0
  60. package/dist/site/scaffold.js +70 -0
  61. package/dist/site/scaffold.js.map +1 -0
  62. package/dist/site/scan-tutorials.d.ts +12 -0
  63. package/dist/site/scan-tutorials.d.ts.map +1 -0
  64. package/dist/site/scan-tutorials.js +54 -0
  65. package/dist/site/scan-tutorials.js.map +1 -0
  66. package/dist/site/types.d.ts +75 -0
  67. package/dist/site/types.d.ts.map +1 -0
  68. package/dist/site/types.js +2 -0
  69. package/dist/site/types.js.map +1 -0
  70. package/dist/skill-stamp.d.ts +21 -0
  71. package/dist/skill-stamp.d.ts.map +1 -0
  72. package/dist/skill-stamp.js +39 -0
  73. package/dist/skill-stamp.js.map +1 -0
  74. package/dist/slugify.d.ts +19 -0
  75. package/dist/slugify.d.ts.map +1 -0
  76. package/dist/slugify.js +29 -0
  77. package/dist/slugify.js.map +1 -0
  78. package/dist/stage-presets.d.ts +51 -0
  79. package/dist/stage-presets.d.ts.map +1 -0
  80. package/dist/stage-presets.js +45 -0
  81. package/dist/stage-presets.js.map +1 -0
  82. package/dist/styles.css +637 -0
  83. package/dist/timeline.d.ts +87 -0
  84. package/dist/timeline.d.ts.map +1 -0
  85. package/dist/timeline.js +108 -0
  86. package/dist/timeline.js.map +1 -0
  87. package/dist/transcript.d.ts +87 -0
  88. package/dist/transcript.d.ts.map +1 -0
  89. package/dist/transcript.js +291 -0
  90. package/dist/transcript.js.map +1 -0
  91. package/dist/tts-provider.d.ts +69 -0
  92. package/dist/tts-provider.d.ts.map +1 -0
  93. package/dist/tts-provider.js +182 -0
  94. package/dist/tts-provider.js.map +1 -0
  95. package/dist/types.d.ts +109 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +2 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/voice-prerender.d.ts +41 -0
  100. package/dist/voice-prerender.d.ts.map +1 -0
  101. package/dist/voice-prerender.js +122 -0
  102. package/dist/voice-prerender.js.map +1 -0
  103. package/dist/voice.d.ts +76 -0
  104. package/dist/voice.d.ts.map +1 -0
  105. package/dist/voice.js +244 -0
  106. package/dist/voice.js.map +1 -0
  107. package/package.json +87 -0
  108. package/skills/tutorialize/SKILL.md +54 -0
  109. package/skills/tutorialize/agent.md +58 -0
  110. package/skills/tutorialize/references/api.md +489 -0
  111. package/skills/tutorialize/references/storytelling.md +193 -0
  112. package/src/styles.css +637 -0
  113. package/templates/site/public/.gitkeep +0 -0
  114. package/templates/site/public/widget.js +273 -0
  115. package/templates/site/src/components/TutorialsHome.astro +141 -0
  116. package/templates/site/src/components/VideoCard.astro +192 -0
  117. package/templates/site/src/components/VideoModal.astro +170 -0
  118. package/templates/site/src/data/.gitkeep +0 -0
  119. package/templates/site/src/layouts/Base.astro +56 -0
  120. package/templates/site/src/pages/[slug].astro +745 -0
  121. package/templates/site/src/pages/index.astro +5 -0
  122. package/templates/site/src/styles/tutorials.css +303 -0
@@ -0,0 +1,70 @@
1
+ import { cpSync, mkdirSync, writeFileSync, existsSync } from 'fs';
2
+ import { join, dirname, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { buildEmbedFiles } from './embed.js';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ export function scaffold(config, manifest, tempDir) {
8
+ const templateDir = join(__dirname, '..', '..', 'templates', 'site');
9
+ mkdirSync(tempDir, { recursive: true });
10
+ cpSync(templateDir, tempDir, { recursive: true });
11
+ const siteConfigData = {
12
+ title: config.title,
13
+ logo: config.logo,
14
+ primaryColor: config.primaryColor,
15
+ font: config.font,
16
+ baseUrl: config.baseUrl,
17
+ lang: config.lang,
18
+ stepsLayout: config.tutorials?.stepsLayout || 'text',
19
+ showStrip: config.tutorials?.showStrip !== false
20
+ };
21
+ if (config.logo && existsSync(config.logo)) {
22
+ const logoExt = config.logo.split('.').pop();
23
+ mkdirSync(join(tempDir, 'public'), { recursive: true });
24
+ cpSync(config.logo, join(tempDir, 'public', `logo.${logoExt}`));
25
+ siteConfigData.logo = `/logo.${logoExt}`;
26
+ }
27
+ mkdirSync(join(tempDir, 'src', 'data'), { recursive: true });
28
+ writeFileSync(join(tempDir, 'src', 'data', 'site-config.json'), JSON.stringify(siteConfigData, null, 2));
29
+ writeFileSync(join(tempDir, 'src', 'data', 'tutorials.json'), JSON.stringify(manifest, null, 2));
30
+ // In-app widget data: one JSON payload per tutorial + an index, served
31
+ // next to widget.js (template public/) so any app can embed tutorials.
32
+ const embedDir = join(tempDir, 'public', 'embed');
33
+ mkdirSync(embedDir, { recursive: true });
34
+ for (const file of buildEmbedFiles(manifest, config)) {
35
+ writeFileSync(join(tempDir, 'public', file.path), JSON.stringify(file.data, null, 2));
36
+ }
37
+ const videosDir = join(resolve(config.input), 'videos');
38
+ const publicVideos = join(tempDir, 'public', 'videos');
39
+ if (existsSync(videosDir)) {
40
+ mkdirSync(publicVideos, { recursive: true });
41
+ cpSync(videosDir, publicVideos, { recursive: true });
42
+ }
43
+ let siteDirective = '';
44
+ let baseDirective = '';
45
+ if (config.baseUrl && config.baseUrl.startsWith('http')) {
46
+ siteDirective = `site: ${JSON.stringify(config.baseUrl)},`;
47
+ try {
48
+ const basePath = new URL(config.baseUrl).pathname;
49
+ if (basePath && basePath !== '/') {
50
+ baseDirective = `base: ${JSON.stringify(basePath)},`;
51
+ }
52
+ }
53
+ catch { /* invalid URL, skip */ }
54
+ }
55
+ const astroConfig = `import { defineConfig } from 'astro/config';
56
+
57
+ export default defineConfig({
58
+ srcDir: './src',
59
+ publicDir: './public',
60
+ outDir: './dist',
61
+ ${siteDirective}
62
+ ${baseDirective}
63
+ build: { assets: 'assets' },
64
+ });
65
+ `;
66
+ writeFileSync(join(tempDir, 'astro.config.mjs'), astroConfig);
67
+ writeFileSync(join(tempDir, 'package.json'), JSON.stringify({ name: 'tutorial-site-build', type: 'module', private: true }, null, 2));
68
+ return tempDir;
69
+ }
70
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/site/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { TimelineData } from '../timeline.js';
2
+ export interface ScannedTutorial {
3
+ timeline: TimelineData;
4
+ videoFile: string;
5
+ /** Screenshot filenames with their step number, sorted by step number */
6
+ stepScreenshots: {
7
+ n: number;
8
+ file: string;
9
+ }[];
10
+ }
11
+ export declare function scanTutorials(inputDir: string): ScannedTutorial[];
12
+ //# sourceMappingURL=scan-tutorials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-tutorials.d.ts","sourceRoot":"","sources":["../../src/site/scan-tutorials.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,eAAe,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqDjE"}
@@ -0,0 +1,54 @@
1
+ import { readdirSync, readFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ export function scanTutorials(inputDir) {
4
+ const outputDir = join(inputDir, 'output');
5
+ const videosDir = join(inputDir, 'videos');
6
+ const results = [];
7
+ if (!existsSync(videosDir))
8
+ return results;
9
+ const videoFiles = readdirSync(videosDir).filter((f) => f.endsWith('.webm'));
10
+ const allVideoDirFiles = readdirSync(videosDir);
11
+ // Index timelines by testName
12
+ const timelinesByName = new Map();
13
+ if (existsSync(outputDir)) {
14
+ for (const tf of readdirSync(outputDir).filter((f) => f.endsWith('_timeline.json'))) {
15
+ const timeline = JSON.parse(readFileSync(join(outputDir, tf), 'utf-8'));
16
+ timelinesByName.set(timeline.testName, timeline);
17
+ }
18
+ }
19
+ for (const videoFileName of videoFiles) {
20
+ const baseName = videoFileName.replace(/\.webm$/, '');
21
+ const timeline = timelinesByName.get(baseName);
22
+ const stepPattern = new RegExp(`^${escapeRegex(baseName)}-step-(\\d+)\\.(png|jpe?g|webp|avif)$`);
23
+ const steps = [];
24
+ for (const file of allVideoDirFiles) {
25
+ const m = file.match(stepPattern);
26
+ if (m)
27
+ steps.push({ n: parseInt(m[1], 10), file });
28
+ }
29
+ const syntheticTimeline = timeline ?? {
30
+ testName: baseName,
31
+ testTitle: baseName,
32
+ testFile: '',
33
+ projectName: '',
34
+ lang: 'fr',
35
+ totalDurationMs: 0,
36
+ videoTrimMs: 0,
37
+ steps: [],
38
+ videoPath: '',
39
+ mergeCommand: '',
40
+ // No timeline to read the variant from — recognize the common suffixes.
41
+ variant: baseName.match(/-(mobile|tablet)$/)?.[1]
42
+ };
43
+ results.push({
44
+ timeline: syntheticTimeline,
45
+ videoFile: videoFileName,
46
+ stepScreenshots: steps.sort((a, b) => a.n - b.n)
47
+ });
48
+ }
49
+ return results;
50
+ }
51
+ function escapeRegex(s) {
52
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
53
+ }
54
+ //# sourceMappingURL=scan-tutorials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-tutorials.js","sourceRoot":"","sources":["../../src/site/scan-tutorials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ export interface SiteConfig {
2
+ title: string;
3
+ logo?: string;
4
+ primaryColor: string;
5
+ font: string;
6
+ input: string;
7
+ output: string;
8
+ baseUrl: string;
9
+ /** Single lang or comma-separated list, e.g. "fr,en,ar" */
10
+ lang: string;
11
+ tutorials?: {
12
+ categories?: Record<string, {
13
+ icon: string;
14
+ label: string;
15
+ }>;
16
+ ui?: {
17
+ heroTitle?: string;
18
+ heroSubtitle?: string;
19
+ };
20
+ /** Video page step guide layout:
21
+ * - 'text': text-only cards, click to reveal the screenshot (default)
22
+ * - 'cards': numbered text cards with a small screenshot, click to enlarge
23
+ * - 'full': full-width screenshot under each step's text
24
+ * - 'none': no step guide */
25
+ stepsLayout?: 'text' | 'cards' | 'full' | 'none';
26
+ /** Horizontal thumbnail strip under the video info (default true).
27
+ * Independent of stepsLayout; clicking a thumbnail opens the screenshot
28
+ * with its step's title + text. */
29
+ showStrip?: boolean;
30
+ };
31
+ }
32
+ /** One entry of a video page's step-by-step guide, built from the timeline
33
+ * narration merged with the step screenshots. */
34
+ export interface ManifestStep {
35
+ /** Timeline step number; absent for mid-flow context narrations */
36
+ n?: number;
37
+ title?: string;
38
+ /** Narration text spoken during this step */
39
+ text?: string;
40
+ /** Screenshot filename relative to videos/ */
41
+ image?: string;
42
+ /** True for a context narration shown between steps (no number, no image) */
43
+ context?: boolean;
44
+ }
45
+ export interface VideoManifestEntry {
46
+ id: string;
47
+ category: string;
48
+ title: string;
49
+ duration: string;
50
+ premium: boolean;
51
+ /** Filename relative to videos/ */
52
+ file: string;
53
+ feature?: string;
54
+ /** Recording variant (e.g. 'mobile'); absent = regular/desktop */
55
+ variant?: string;
56
+ uploadDate: string;
57
+ dateModified?: string;
58
+ steps: number;
59
+ /** Intro narration (leading context steps), shown under the video title */
60
+ description?: string;
61
+ /** Step-by-step guide rendered on the video page */
62
+ stepsDetail?: ManifestStep[];
63
+ }
64
+ export interface VideoManifest {
65
+ categories: Record<string, {
66
+ icon: string;
67
+ label: string;
68
+ }>;
69
+ videos: VideoManifestEntry[];
70
+ ui: {
71
+ heroTitle: string;
72
+ heroSubtitle: string;
73
+ };
74
+ }
75
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/site/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC7D,EAAE,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACnD;;;;uCAI+B;QAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;QACjD;;4CAEoC;QACpC,SAAS,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACF;AAED;kDACkD;AAClD,MAAM,WAAW,YAAY;IAC5B,mEAAmE;IACnE,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/site/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The skill and agent are copied into a project's `.claude/` directory, so they
3
+ * are snapshots — a package upgrade cannot reach them. This stamp records which
4
+ * version produced each copy, which is what makes staleness detectable instead
5
+ * of silent.
6
+ */
7
+ export interface SkillStamp {
8
+ skill?: string;
9
+ agent?: string;
10
+ }
11
+ export declare const STAMP_FILE = ".playwright-director.json";
12
+ export declare function stampPath(claudeDir: string): string;
13
+ export declare function readStamp(claudeDir: string): SkillStamp;
14
+ export declare function writeStamp(claudeDir: string, stamp: SkillStamp): void;
15
+ export declare function packageVersion(packageRoot: string): string;
16
+ /** What `init` would bring up to date, given what is on disk. */
17
+ export declare function outdated(claudeDir: string, version: string, installed: {
18
+ skill: boolean;
19
+ agent: boolean;
20
+ }): string[];
21
+ //# sourceMappingURL=skill-stamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-stamp.d.ts","sourceRoot":"","sources":["../src/skill-stamp.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,UAAU,8BAA8B,CAAC;AAEtD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAQvD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAErE;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,iEAAiE;AACjE,wBAAgB,QAAQ,CACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC3C,MAAM,EAAE,CAMV"}
@@ -0,0 +1,39 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ export const STAMP_FILE = '.playwright-director.json';
4
+ export function stampPath(claudeDir) {
5
+ return join(claudeDir, STAMP_FILE);
6
+ }
7
+ export function readStamp(claudeDir) {
8
+ const path = stampPath(claudeDir);
9
+ if (!existsSync(path))
10
+ return {};
11
+ try {
12
+ return JSON.parse(readFileSync(path, 'utf-8'));
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export function writeStamp(claudeDir, stamp) {
19
+ writeFileSync(stampPath(claudeDir), `${JSON.stringify(stamp, null, 2)}\n`);
20
+ }
21
+ export function packageVersion(packageRoot) {
22
+ try {
23
+ return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf-8')).version ?? '0.0.0';
24
+ }
25
+ catch {
26
+ return '0.0.0';
27
+ }
28
+ }
29
+ /** What `init` would bring up to date, given what is on disk. */
30
+ export function outdated(claudeDir, version, installed) {
31
+ const stamp = readStamp(claudeDir);
32
+ const stale = [];
33
+ if (installed.skill && stamp.skill !== version)
34
+ stale.push('skill');
35
+ if (installed.agent && stamp.agent !== version)
36
+ stale.push('agent');
37
+ return stale;
38
+ }
39
+ //# sourceMappingURL=skill-stamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-stamp.js","sourceRoot":"","sources":["../src/skill-stamp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAa5B,MAAM,CAAC,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAEtD,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,KAAiB;IAC9D,aAAa,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,WAAmB;IACjD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,OAAO,CAAC;IAChB,CAAC;AACF,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,QAAQ,CACvB,SAAiB,EACjB,OAAe,EACf,SAA6C;IAE7C,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,IAAI,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACd,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Build an SEO-friendly filename slug from a human-readable string.
3
+ *
4
+ * - NFD-normalize then drop every Unicode combining mark — this strips Latin
5
+ * diacritics (é→e) AND the hamza marks that NFD splits off Arabic letters
6
+ * (إ → ا + U+0655 → ا). Without the Arabic pass, titles like
7
+ * "إنشاء أول فاتورة" would emit stray dashes where the marks were.
8
+ * - Lowercase (no-op on Arabic).
9
+ * - Any run of non-letter/non-number → single dash.
10
+ * - Leading / trailing dashes trimmed; consecutive dashes collapsed.
11
+ *
12
+ * Examples:
13
+ * "Créer votre première facture" → "creer-votre-premiere-facture"
14
+ * "Set Up Your Company" → "set-up-your-company"
15
+ * "إنشاء أول فاتورة" → "انشاء-اول-فاتورة"
16
+ * "S'abonner avec BaridPay" → "s-abonner-avec-baridpay"
17
+ */
18
+ export declare function slugify(input: string): string;
19
+ //# sourceMappingURL=slugify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.d.ts","sourceRoot":"","sources":["../src/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Build an SEO-friendly filename slug from a human-readable string.
3
+ *
4
+ * - NFD-normalize then drop every Unicode combining mark — this strips Latin
5
+ * diacritics (é→e) AND the hamza marks that NFD splits off Arabic letters
6
+ * (إ → ا + U+0655 → ا). Without the Arabic pass, titles like
7
+ * "إنشاء أول فاتورة" would emit stray dashes where the marks were.
8
+ * - Lowercase (no-op on Arabic).
9
+ * - Any run of non-letter/non-number → single dash.
10
+ * - Leading / trailing dashes trimmed; consecutive dashes collapsed.
11
+ *
12
+ * Examples:
13
+ * "Créer votre première facture" → "creer-votre-premiere-facture"
14
+ * "Set Up Your Company" → "set-up-your-company"
15
+ * "إنشاء أول فاتورة" → "انشاء-اول-فاتورة"
16
+ * "S'abonner avec BaridPay" → "s-abonner-avec-baridpay"
17
+ */
18
+ export function slugify(input) {
19
+ if (!input)
20
+ return '';
21
+ return input
22
+ .normalize('NFD')
23
+ .replace(/\p{M}+/gu, '') // strip ALL combining marks (Latin + Arabic hamza marks)
24
+ .toLowerCase()
25
+ .replace(/[^\p{L}\p{N}]+/gu, '-') // any non-letter/number → dash
26
+ .replace(/-{2,}/g, '-')
27
+ .replace(/^-+|-+$/g, '');
28
+ }
29
+ //# sourceMappingURL=slugify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.js","sourceRoot":"","sources":["../src/slugify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACpC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,KAAK;SACV,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,yDAAyD;SACjF,WAAW,EAAE;SACb,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC,+BAA+B;SAChE,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,51 @@
1
+ export interface MobileStageOptions {
2
+ /** Video oversampling factor (default 2). Playwright records video at *window*
3
+ * pixels and never upscales, so a phone-sized viewport yields a blurry ~400px-wide
4
+ * video. The scale is applied as `--force-device-scale-factor` + matching
5
+ * `deviceScaleFactor`, and the video size is multiplied accordingly — the page
6
+ * layout (CSS viewport) is unchanged. Set to 1 to record at native CSS pixels. */
7
+ scale?: number;
8
+ /** Extra Chromium args. `test.use()` REPLACES the config's `launchOptions`
9
+ * wholesale, so any arg your playwright.config sets for tutorial runs
10
+ * (e.g. `--autoplay-policy=no-user-gesture-required`) must be repeated here. */
11
+ launchArgs?: string[];
12
+ }
13
+ /**
14
+ * Playwright `test.use()` options for recording the mobile variant of a
15
+ * tutorial: N phone-sized panes side by side. Inert unless
16
+ * `TUTORIAL_VARIANT=mobile`, so the same spec records desktop and mobile.
17
+ *
18
+ * Must be passed to `test.use()` at the top of the spec — the video size is
19
+ * frozen when the browser context is created, a later `setViewportSize`
20
+ * would not resize the recording.
21
+ *
22
+ * The viewport is widened to N phones; in tutorial mode the video is recorded
23
+ * oversampled (see {@link MobileStageOptions.scale}) for a sharp result. Other
24
+ * device traits (userAgent, isMobile…) stay whatever the consuming
25
+ * playwright.config sets.
26
+ *
27
+ * @param scenes number of phones side by side (1 for a single-scene tutorial)
28
+ * @param device a Playwright device name (default 'Pixel 7') or an explicit viewport
29
+ * @param options scale / extra launch args, see {@link MobileStageOptions}
30
+ */
31
+ export declare function mobileStage(scenes?: number, device?: string | {
32
+ width: number;
33
+ height: number;
34
+ }, options?: MobileStageOptions): {
35
+ viewport?: {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ deviceScaleFactor?: number;
40
+ video?: {
41
+ mode: 'on';
42
+ size: {
43
+ width: number;
44
+ height: number;
45
+ };
46
+ };
47
+ launchOptions?: {
48
+ args: string[];
49
+ };
50
+ };
51
+ //# sourceMappingURL=stage-presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage-presets.d.ts","sourceRoot":"","sources":["../src/stage-presets.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IAClC;;;;uFAImF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;qFAEiF;IACjF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAC1B,MAAM,SAAI,EACV,MAAM,GAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAc,EAC9D,OAAO,GAAE,kBAAuB,GAC9B;IACF,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAChE,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACnC,CAwBA"}
@@ -0,0 +1,45 @@
1
+ import { devices } from '@playwright/test';
2
+ /**
3
+ * Playwright `test.use()` options for recording the mobile variant of a
4
+ * tutorial: N phone-sized panes side by side. Inert unless
5
+ * `TUTORIAL_VARIANT=mobile`, so the same spec records desktop and mobile.
6
+ *
7
+ * Must be passed to `test.use()` at the top of the spec — the video size is
8
+ * frozen when the browser context is created, a later `setViewportSize`
9
+ * would not resize the recording.
10
+ *
11
+ * The viewport is widened to N phones; in tutorial mode the video is recorded
12
+ * oversampled (see {@link MobileStageOptions.scale}) for a sharp result. Other
13
+ * device traits (userAgent, isMobile…) stay whatever the consuming
14
+ * playwright.config sets.
15
+ *
16
+ * @param scenes number of phones side by side (1 for a single-scene tutorial)
17
+ * @param device a Playwright device name (default 'Pixel 7') or an explicit viewport
18
+ * @param options scale / extra launch args, see {@link MobileStageOptions}
19
+ */
20
+ export function mobileStage(scenes = 1, device = 'Pixel 7', options = {}) {
21
+ if (process.env.TUTORIAL_VARIANT !== 'mobile')
22
+ return {};
23
+ const base = typeof device === 'string' ? devices[device]?.viewport : device;
24
+ if (!base)
25
+ throw new Error(`[Tutorial] Unknown Playwright device "${device}"`);
26
+ const scale = options.scale ?? 2;
27
+ const viewport = { width: base.width * scenes, height: base.height };
28
+ if (process.env.TUTORIAL_MODE !== 'true')
29
+ return { viewport };
30
+ return {
31
+ viewport,
32
+ // The window must actually render at `scale`× — Playwright pastes frames onto
33
+ // the video canvas without upscaling (gray padding otherwise). The emulated
34
+ // deviceScaleFactor is aligned with the window's so screenshots match.
35
+ deviceScaleFactor: scale,
36
+ launchOptions: {
37
+ args: [`--force-device-scale-factor=${scale}`, ...(options.launchArgs ?? [])]
38
+ },
39
+ video: {
40
+ mode: 'on',
41
+ size: { width: viewport.width * scale, height: viewport.height * scale }
42
+ }
43
+ };
44
+ }
45
+ //# sourceMappingURL=stage-presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage-presets.js","sourceRoot":"","sources":["../src/stage-presets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAe3C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAC1B,MAAM,GAAG,CAAC,EACV,SAAqD,SAAS,EAC9D,UAA8B,EAAE;IAOhC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAEzD,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7E,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,GAAG,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACrE,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,MAAM;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAE9D,OAAO;QACN,QAAQ;QACR,8EAA8E;QAC9E,4EAA4E;QAC5E,uEAAuE;QACvE,iBAAiB,EAAE,KAAK;QACxB,aAAa,EAAE;YACd,IAAI,EAAE,CAAC,+BAA+B,KAAK,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;SAC7E;QACD,KAAK,EAAE;YACN,IAAI,EAAE,IAAa;YACnB,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,EAAE;SACxE;KACD,CAAC;AACH,CAAC"}