@biggora/claude-plugins 1.1.1 → 1.2.2
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/.claude/settings.local.json +3 -1
- package/README.md +24 -17
- package/package.json +1 -1
- package/registry/registry.json +319 -244
- package/specs/coding.md +24 -0
- package/specs/pod.md +2 -0
- package/src/skills/captcha/README.md +221 -0
- package/src/skills/captcha/SKILL.md +355 -0
- package/src/skills/captcha/references/captcha-types.md +254 -0
- package/src/skills/captcha/references/services.md +172 -0
- package/src/skills/captcha/references/stealth.md +238 -0
- package/src/skills/captcha/scripts/solve_captcha.py +323 -0
- package/src/skills/captcha/scripts/solve_image_grid.py +350 -0
- package/src/skills/codex-cli/SKILL.md +21 -11
- package/src/skills/gemini-cli/SKILL.md +27 -13
- package/src/skills/gemini-cli/references/commands.md +21 -14
- package/src/skills/gemini-cli/references/configuration.md +23 -18
- package/src/skills/gemini-cli/references/headless-and-scripting.md +7 -17
- package/src/skills/gemini-cli/references/mcp-and-extensions.md +12 -6
- package/src/skills/google-merchant-api/SKILL.md +581 -0
- package/src/skills/google-merchant-api/references/accounts.md +247 -0
- package/src/skills/google-merchant-api/references/content-api-legacy.md +216 -0
- package/src/skills/google-merchant-api/references/datasources.md +233 -0
- package/src/skills/google-merchant-api/references/inventories.md +201 -0
- package/src/skills/google-merchant-api/references/migration.md +267 -0
- package/src/skills/google-merchant-api/references/products.md +316 -0
- package/src/skills/google-merchant-api/references/promotions.md +201 -0
- package/src/skills/google-merchant-api/references/reports.md +240 -0
- package/src/skills/lv-aggregators-api/SKILL.md +113 -0
- package/src/skills/lv-aggregators-api/references/integration-guide.md +368 -0
- package/src/skills/lv-aggregators-api/references/kurpirkt.md +103 -0
- package/src/skills/lv-aggregators-api/references/salidzini.md +122 -0
- package/src/skills/notebook-lm/SKILL.md +1 -1
- package/src/skills/screen-recording/SKILL.md +243 -213
- package/src/skills/screen-recording/references/design-patterns.md +4 -2
- package/src/skills/screen-recording/references/ffmpeg-recording.md +473 -0
- package/src/skills/screen-recording/references/{approach1-programmatic.md → programmatic-generation.md} +45 -22
- package/src/skills/screen-recording/references/python-fallback.md +222 -0
- package/src/skills/tailwindcss-best-practices/SKILL.md +180 -0
- package/src/skills/tailwindcss-best-practices/references/best-practices-utility-patterns.md +87 -0
- package/src/skills/tailwindcss-best-practices/references/core-installation.md +109 -0
- package/src/skills/tailwindcss-best-practices/references/core-preflight.md +200 -0
- package/src/skills/tailwindcss-best-practices/references/core-responsive.md +163 -0
- package/src/skills/tailwindcss-best-practices/references/core-source-detection.md +114 -0
- package/src/skills/tailwindcss-best-practices/references/core-theme.md +108 -0
- package/src/skills/tailwindcss-best-practices/references/core-utility-classes.md +59 -0
- package/src/skills/tailwindcss-best-practices/references/core-variants.md +204 -0
- package/src/skills/tailwindcss-best-practices/references/effects-form-controls.md +76 -0
- package/src/skills/tailwindcss-best-practices/references/effects-mask.md +91 -0
- package/src/skills/tailwindcss-best-practices/references/effects-scroll-snap.md +59 -0
- package/src/skills/tailwindcss-best-practices/references/effects-text-shadow.md +78 -0
- package/src/skills/tailwindcss-best-practices/references/effects-transition-animation.md +80 -0
- package/src/skills/tailwindcss-best-practices/references/effects-visibility-interactivity.md +82 -0
- package/src/skills/tailwindcss-best-practices/references/features-content-detection.md +175 -0
- package/src/skills/tailwindcss-best-practices/references/features-custom-styles.md +203 -0
- package/src/skills/tailwindcss-best-practices/references/features-dark-mode.md +137 -0
- package/src/skills/tailwindcss-best-practices/references/features-functions-directives.md +241 -0
- package/src/skills/tailwindcss-best-practices/references/features-upgrade.md +160 -0
- package/src/skills/tailwindcss-best-practices/references/layout-aspect-ratio.md +39 -0
- package/src/skills/tailwindcss-best-practices/references/layout-columns.md +80 -0
- package/src/skills/tailwindcss-best-practices/references/layout-display.md +110 -0
- package/src/skills/tailwindcss-best-practices/references/layout-flexbox.md +112 -0
- package/src/skills/tailwindcss-best-practices/references/layout-grid.md +87 -0
- package/src/skills/tailwindcss-best-practices/references/layout-height.md +97 -0
- package/src/skills/tailwindcss-best-practices/references/layout-inset.md +103 -0
- package/src/skills/tailwindcss-best-practices/references/layout-logical-properties.md +92 -0
- package/src/skills/tailwindcss-best-practices/references/layout-margin.md +126 -0
- package/src/skills/tailwindcss-best-practices/references/layout-min-max-sizing.md +63 -0
- package/src/skills/tailwindcss-best-practices/references/layout-object-fit-position.md +64 -0
- package/src/skills/tailwindcss-best-practices/references/layout-overflow.md +57 -0
- package/src/skills/tailwindcss-best-practices/references/layout-padding.md +77 -0
- package/src/skills/tailwindcss-best-practices/references/layout-position.md +85 -0
- package/src/skills/tailwindcss-best-practices/references/layout-tables.md +67 -0
- package/src/skills/tailwindcss-best-practices/references/layout-width.md +102 -0
- package/src/skills/tailwindcss-best-practices/references/transform-base.md +68 -0
- package/src/skills/tailwindcss-best-practices/references/transform-rotate.md +70 -0
- package/src/skills/tailwindcss-best-practices/references/transform-scale.md +83 -0
- package/src/skills/tailwindcss-best-practices/references/transform-skew.md +62 -0
- package/src/skills/tailwindcss-best-practices/references/transform-translate.md +77 -0
- package/src/skills/tailwindcss-best-practices/references/typography-font-text.md +142 -0
- package/src/skills/tailwindcss-best-practices/references/typography-list-style.md +65 -0
- package/src/skills/tailwindcss-best-practices/references/typography-text-align.md +60 -0
- package/src/skills/tailwindcss-best-practices/references/visual-background.md +76 -0
- package/src/skills/tailwindcss-best-practices/references/visual-border.md +108 -0
- package/src/skills/tailwindcss-best-practices/references/visual-effects.md +111 -0
- package/src/skills/tailwindcss-best-practices/references/visual-svg.md +82 -0
- package/src/skills/test-mobile-app/SKILL.md +11 -6
- package/src/skills/test-mobile-app/scripts/analyze_apk.py +15 -4
- package/src/skills/test-mobile-app/scripts/check_environment.py +5 -5
- package/src/skills/test-mobile-app/scripts/run_tests.py +1 -1
- package/src/skills/test-web-ui/SKILL.md +264 -84
- package/src/skills/test-web-ui/scripts/discover.py +25 -12
- package/src/skills/test-web-ui/scripts/run_tests.py +3 -2
- package/src/skills/tm-search/SKILL.md +242 -106
- package/src/skills/tm-search/references/scraping-fallback.md +60 -95
- package/src/skills/tm-search/scripts/tm_search.py +453 -375
- package/src/skills/vite-best-practices/SKILL.md +115 -0
- package/src/skills/vite-best-practices/references/build-and-ssr.md +255 -0
- package/src/skills/vite-best-practices/references/core-config.md +231 -0
- package/src/skills/vite-best-practices/references/core-features.md +222 -0
- package/src/skills/vite-best-practices/references/core-plugin-api.md +294 -0
- package/src/skills/vite-best-practices/references/environment-api.md +108 -0
- package/src/skills/vite-best-practices/references/rolldown-migration.md +242 -0
- package/src/skills/screen-recording/references/approach2-xvfb.md +0 -232
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vite-rolldown
|
|
3
|
+
description: Vite 8 Rolldown bundler and Oxc transformer migration from Vite 7
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rolldown Migration (Vite 8)
|
|
7
|
+
|
|
8
|
+
Vite 8 (stable March 2026) replaces esbuild+Rollup with Rolldown, a unified Rust-based bundler. Performance gains of 10-30x for production builds are typical.
|
|
9
|
+
|
|
10
|
+
## What Changed
|
|
11
|
+
|
|
12
|
+
| Before (Vite 7) | After (Vite 8) | Compat Layer |
|
|
13
|
+
|-----------------|----------------|--------------|
|
|
14
|
+
| esbuild (dev transform) | Oxc Transformer | `esbuild` auto-converts to `oxc` |
|
|
15
|
+
| esbuild (dep pre-bundling) | Rolldown | `esbuildOptions` auto-converts |
|
|
16
|
+
| esbuild (JS minification) | Oxc Minifier | `minify: 'esbuild'` = fallback |
|
|
17
|
+
| esbuild (CSS minification) | Lightning CSS | `cssMinify: 'esbuild'` = fallback |
|
|
18
|
+
| Rollup (production build) | Rolldown | `rollupOptions` auto-converts |
|
|
19
|
+
| `rollupOptions` | `rolldownOptions` | deprecated, auto-converts |
|
|
20
|
+
| `esbuild` option | `oxc` option | deprecated, auto-converts |
|
|
21
|
+
|
|
22
|
+
**Important:** A compatibility layer auto-converts old config names, so many projects upgrade with zero config changes. However, both `esbuild` and `rollupOptions` are deprecated and will be removed in a future version. Always use the new names in new code.
|
|
23
|
+
|
|
24
|
+
## Config Migration
|
|
25
|
+
|
|
26
|
+
### rollupOptions → rolldownOptions
|
|
27
|
+
|
|
28
|
+
Direct rename. Internal structure (`external`, `output.globals`) stays the same:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Before (Vite 7)
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
build: {
|
|
34
|
+
rollupOptions: {
|
|
35
|
+
external: ['vue'],
|
|
36
|
+
output: { globals: { vue: 'Vue' } },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// After (Vite 8)
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
build: {
|
|
44
|
+
rolldownOptions: {
|
|
45
|
+
external: ['vue'],
|
|
46
|
+
output: { globals: { vue: 'Vue' } },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Also applies to `worker.rollupOptions` → `worker.rolldownOptions`.
|
|
53
|
+
|
|
54
|
+
### esbuild → oxc (JavaScript Transforms)
|
|
55
|
+
|
|
56
|
+
Complete mapping of esbuild JSX options to Oxc:
|
|
57
|
+
|
|
58
|
+
| esbuild | oxc equivalent |
|
|
59
|
+
|---------|---------------|
|
|
60
|
+
| `jsx: 'transform'` | `jsx: { runtime: 'classic' }` |
|
|
61
|
+
| `jsx: 'automatic'` | `jsx: { runtime: 'automatic' }` |
|
|
62
|
+
| `jsx: 'preserve'` | `jsx: 'preserve'` |
|
|
63
|
+
| `jsxFactory: 'h'` | `jsx.pragma: 'h'` |
|
|
64
|
+
| `jsxFragment: 'Fragment'` | `jsx.pragmaFrag: 'Fragment'` |
|
|
65
|
+
| `jsxImportSource: 'react'` | `jsx.importSource: 'react'` |
|
|
66
|
+
| `jsxDev` | `jsx.development` |
|
|
67
|
+
| `jsxSideEffects` | `jsx.pure` |
|
|
68
|
+
| `jsxInject` | `jsxInject` (same) |
|
|
69
|
+
| `include`/`exclude` | `include`/`exclude` (same) |
|
|
70
|
+
| `define` | `define` (same) |
|
|
71
|
+
|
|
72
|
+
**Not supported in Oxc:** `esbuild.supported` option.
|
|
73
|
+
|
|
74
|
+
**Not supported:** Native decorator lowering — use Babel or SWC plugins.
|
|
75
|
+
|
|
76
|
+
**Example — Preact classic JSX:**
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Before (Vite 7)
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
esbuild: {
|
|
82
|
+
jsxFactory: 'h',
|
|
83
|
+
jsxFragment: 'Fragment',
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// After (Vite 8)
|
|
88
|
+
export default defineConfig({
|
|
89
|
+
oxc: {
|
|
90
|
+
jsx: {
|
|
91
|
+
runtime: 'classic',
|
|
92
|
+
pragma: 'h',
|
|
93
|
+
pragmaFrag: 'Fragment',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Example — React automatic:**
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
export default defineConfig({
|
|
103
|
+
oxc: {
|
|
104
|
+
jsx: {
|
|
105
|
+
runtime: 'automatic',
|
|
106
|
+
importSource: 'react',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### optimizeDeps.esbuildOptions → optimizeDeps.rolldownOptions
|
|
113
|
+
|
|
114
|
+
Auto-converted options:
|
|
115
|
+
|
|
116
|
+
| esbuildOptions | rolldownOptions equivalent |
|
|
117
|
+
|---------------|--------------------------|
|
|
118
|
+
| `minify` | `output.minify` |
|
|
119
|
+
| `treeShaking` | `treeshake` |
|
|
120
|
+
| `define` | `transform.define` |
|
|
121
|
+
| `loader` | `moduleTypes` |
|
|
122
|
+
| `preserveSymlinks` | `!resolve.symlinks` |
|
|
123
|
+
| `resolveExtensions` | `resolve.extensions` |
|
|
124
|
+
| `mainFields` | `resolve.mainFields` |
|
|
125
|
+
| `conditions` | `resolve.conditionNames` |
|
|
126
|
+
| `keepNames` | `output.keepNames` |
|
|
127
|
+
| `platform` | `platform` |
|
|
128
|
+
| `plugins` | `plugins` (partial support) |
|
|
129
|
+
|
|
130
|
+
### Minification
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// Vite 8 default: Oxc minifier
|
|
134
|
+
build: { minify: true } // uses Oxc
|
|
135
|
+
|
|
136
|
+
// Fallback to esbuild (requires installing esbuild)
|
|
137
|
+
build: { minify: 'esbuild' }
|
|
138
|
+
|
|
139
|
+
// CSS: Lightning CSS is default
|
|
140
|
+
build: { cssMinify: true } // uses Lightning CSS
|
|
141
|
+
build: { cssMinify: 'esbuild' } // fallback (requires esbuild)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Not supported by Oxc minifier:** `mangleProps`, `reserveProps`, `mangleQuoted`, `mangleCache`.
|
|
145
|
+
|
|
146
|
+
### esbuild.banner/footer
|
|
147
|
+
|
|
148
|
+
No direct equivalent. Use a custom plugin with the `transform` hook instead.
|
|
149
|
+
|
|
150
|
+
## Breaking Changes
|
|
151
|
+
|
|
152
|
+
### manualChunks
|
|
153
|
+
|
|
154
|
+
- Object form **removed**
|
|
155
|
+
- Function form **deprecated** → use `codeSplitting` option instead
|
|
156
|
+
|
|
157
|
+
### CommonJS Interop
|
|
158
|
+
|
|
159
|
+
The `default` import from CJS modules follows new rules. If the importer is `.mjs`/`.mts`, or the closest `package.json` has `"type": "module"`, or `module.exports.__esModule` is not true, then `default` = `module.exports` (not `module.exports.default`).
|
|
160
|
+
|
|
161
|
+
Temporary workaround: `legacy.inconsistentCjsInterop: true`.
|
|
162
|
+
|
|
163
|
+
### Format Sniffing Removed
|
|
164
|
+
|
|
165
|
+
Vite no longer sniffs file content to choose between `browser` and `module` fields. `resolve.mainFields` order is always respected.
|
|
166
|
+
|
|
167
|
+
### build() Error Handling
|
|
168
|
+
|
|
169
|
+
`build()` now throws `BundleError` (not raw error) with `.errors?: RolldownError[]` array.
|
|
170
|
+
|
|
171
|
+
### Removed Config Options
|
|
172
|
+
|
|
173
|
+
| Removed | Replacement |
|
|
174
|
+
|---------|-------------|
|
|
175
|
+
| `build.commonjsOptions` | No-op (Rolldown handles CJS natively) |
|
|
176
|
+
| `build.dynamicImportVarsOptions.warnOnError` | No-op |
|
|
177
|
+
| `resolve.alias[].customResolver` | Use custom plugin with `resolveId` hook |
|
|
178
|
+
| `rollupOptions.output.manualChunks` (object form) | Use function form or `codeSplitting` |
|
|
179
|
+
| `rollupOptions.watch.chokidar` | `rolldownOptions.watch.watcher` |
|
|
180
|
+
|
|
181
|
+
### Unsupported Output Formats
|
|
182
|
+
|
|
183
|
+
`'system'` and `'amd'` output formats are not supported by Rolldown.
|
|
184
|
+
|
|
185
|
+
### Node.js Requirements
|
|
186
|
+
|
|
187
|
+
Node.js 20.19+ or 22.12+ required (same as Vite 7).
|
|
188
|
+
|
|
189
|
+
## Plugin Compatibility
|
|
190
|
+
|
|
191
|
+
Most Vite/Rollup plugins work unchanged because Rolldown supports the Rollup plugin API. If a plugin only works during build:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
{
|
|
195
|
+
...rollupPlugin(),
|
|
196
|
+
enforce: 'post',
|
|
197
|
+
apply: 'build',
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Note:** `moduleParsed` hook is NOT called during dev. Plugins relying on it need adjustment.
|
|
202
|
+
|
|
203
|
+
Detect Rolldown at runtime: `this.meta.rolldownVersion`.
|
|
204
|
+
|
|
205
|
+
## Gradual Migration
|
|
206
|
+
|
|
207
|
+
For large projects, migrate via `rolldown-vite` first:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Step 1: Test with rolldown-vite (still Vite 7)
|
|
211
|
+
pnpm add -D rolldown-vite
|
|
212
|
+
|
|
213
|
+
# Replace vite import in config
|
|
214
|
+
import { defineConfig } from 'rolldown-vite'
|
|
215
|
+
|
|
216
|
+
# Step 2: Once stable, upgrade to Vite 8
|
|
217
|
+
pnpm add -D vite@8
|
|
218
|
+
|
|
219
|
+
# Revert the import
|
|
220
|
+
import { defineConfig } from 'vite'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Overriding Vite in Frameworks
|
|
224
|
+
|
|
225
|
+
When framework depends on older Vite:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"pnpm": {
|
|
230
|
+
"overrides": {
|
|
231
|
+
"vite": "8.0.0"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
<!--
|
|
238
|
+
Source references:
|
|
239
|
+
- https://vite.dev/blog/announcing-vite8
|
|
240
|
+
- https://vite.dev/guide/migration
|
|
241
|
+
- https://vite.dev/config/shared-options#oxc
|
|
242
|
+
-->
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# Approach 2: Virtual Display Recording (Xvfb + FFmpeg x11grab)
|
|
2
|
-
|
|
3
|
-
Capture a real running application on a virtual screen.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
Xvfb :99 ←── virtual display (invisible, in RAM)
|
|
9
|
-
↑
|
|
10
|
-
Your app runs here (DISPLAY=:99)
|
|
11
|
-
↓
|
|
12
|
-
FFmpeg x11grab ←── records the virtual display → MP4
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Full Working Template
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
#!/usr/bin/env python3
|
|
19
|
-
"""
|
|
20
|
-
Virtual Display Screen Recorder
|
|
21
|
-
Launches a real app on a virtual display and records it.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import subprocess, os, time, signal, shutil
|
|
25
|
-
|
|
26
|
-
WIDTH, HEIGHT = 1280, 720
|
|
27
|
-
DISPLAY_NUM = ":99"
|
|
28
|
-
FPS = 24
|
|
29
|
-
DURATION = 30 # seconds to record
|
|
30
|
-
OUTPUT = "/home/claude/recording.mp4"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def start_virtual_display():
|
|
34
|
-
"""Start Xvfb virtual display"""
|
|
35
|
-
proc = subprocess.Popen([
|
|
36
|
-
"Xvfb", DISPLAY_NUM,
|
|
37
|
-
"-screen", "0", f"{WIDTH}x{HEIGHT}x24",
|
|
38
|
-
"-ac", "-nolisten", "tcp"
|
|
39
|
-
], stderr=subprocess.DEVNULL)
|
|
40
|
-
time.sleep(1.0) # wait for display to initialize
|
|
41
|
-
print(f"✅ Virtual display {DISPLAY_NUM} started (PID {proc.pid})")
|
|
42
|
-
return proc
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def start_recording(output_path, duration=None):
|
|
46
|
-
"""Start FFmpeg recording of virtual display"""
|
|
47
|
-
cmd = [
|
|
48
|
-
"ffmpeg",
|
|
49
|
-
"-f", "x11grab",
|
|
50
|
-
"-video_size", f"{WIDTH}x{HEIGHT}",
|
|
51
|
-
"-framerate", str(FPS),
|
|
52
|
-
"-i", DISPLAY_NUM,
|
|
53
|
-
"-c:v", "libx264",
|
|
54
|
-
"-preset", "ultrafast",
|
|
55
|
-
"-pix_fmt", "yuv420p",
|
|
56
|
-
]
|
|
57
|
-
if duration:
|
|
58
|
-
cmd.extend(["-t", str(duration)])
|
|
59
|
-
cmd.extend([output_path, "-y", "-loglevel", "quiet"])
|
|
60
|
-
|
|
61
|
-
proc = subprocess.Popen(cmd)
|
|
62
|
-
time.sleep(0.5)
|
|
63
|
-
print(f"✅ Recording started → {output_path}")
|
|
64
|
-
return proc
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def run_app_on_display(app_command, env_extra=None):
|
|
68
|
-
"""Run an application on the virtual display"""
|
|
69
|
-
env = os.environ.copy()
|
|
70
|
-
env["DISPLAY"] = DISPLAY_NUM
|
|
71
|
-
if env_extra:
|
|
72
|
-
env.update(env_extra)
|
|
73
|
-
proc = subprocess.Popen(app_command, env=env, stderr=subprocess.DEVNULL)
|
|
74
|
-
return proc
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def record_session(app_fn, duration=30, output=OUTPUT):
|
|
78
|
-
"""
|
|
79
|
-
Full recording session:
|
|
80
|
-
1. Start virtual display
|
|
81
|
-
2. Start recording
|
|
82
|
-
3. Run app_fn(display) — your automation code
|
|
83
|
-
4. Stop recording, stop display
|
|
84
|
-
"""
|
|
85
|
-
xvfb = start_virtual_display()
|
|
86
|
-
recorder = start_recording(output, duration)
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
app_fn(DISPLAY_NUM)
|
|
90
|
-
# Wait remaining time or until done
|
|
91
|
-
time.sleep(duration)
|
|
92
|
-
finally:
|
|
93
|
-
recorder.send_signal(signal.SIGINT)
|
|
94
|
-
recorder.wait()
|
|
95
|
-
xvfb.terminate()
|
|
96
|
-
xvfb.wait()
|
|
97
|
-
print(f"✅ Recording saved: {os.path.getsize(output):,} bytes")
|
|
98
|
-
|
|
99
|
-
return output
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# ── EXAMPLE: Record a Python Tkinter app ─────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
def my_tkinter_app_script():
|
|
105
|
-
return '''
|
|
106
|
-
import tkinter as tk
|
|
107
|
-
import time
|
|
108
|
-
|
|
109
|
-
root = tk.Tk()
|
|
110
|
-
root.title("My App Demo")
|
|
111
|
-
root.geometry("1280x720")
|
|
112
|
-
root.configure(bg="#0c0c20")
|
|
113
|
-
|
|
114
|
-
label = tk.Label(root, text="Loading...", font=("Arial", 48),
|
|
115
|
-
bg="#0c0c20", fg="white")
|
|
116
|
-
label.pack(pady=200)
|
|
117
|
-
|
|
118
|
-
def update():
|
|
119
|
-
texts = ["Detecting issues...", "Processing...", "✅ Complete!", "100% Accurate"]
|
|
120
|
-
for i, t in enumerate(texts):
|
|
121
|
-
root.after(i * 2000, lambda t=t: label.configure(text=t))
|
|
122
|
-
root.after(8000, root.destroy)
|
|
123
|
-
|
|
124
|
-
root.after(500, update)
|
|
125
|
-
root.mainloop()
|
|
126
|
-
'''
|
|
127
|
-
|
|
128
|
-
def run_demo(display):
|
|
129
|
-
# Write app to temp file
|
|
130
|
-
with open('/tmp/demo_app.py', 'w') as f:
|
|
131
|
-
f.write(my_tkinter_app_script())
|
|
132
|
-
|
|
133
|
-
env = os.environ.copy()
|
|
134
|
-
env["DISPLAY"] = display
|
|
135
|
-
subprocess.Popen(["python3", "/tmp/demo_app.py"], env=env)
|
|
136
|
-
time.sleep(10) # let app run
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# ── EXAMPLE: Record a Chromium browser session ───────────────────────────────
|
|
140
|
-
|
|
141
|
-
def record_browser(url, duration=20, output=OUTPUT):
|
|
142
|
-
"""Record a browser navigating to a URL"""
|
|
143
|
-
xvfb = start_virtual_display()
|
|
144
|
-
recorder = start_recording(output, duration)
|
|
145
|
-
|
|
146
|
-
env = os.environ.copy()
|
|
147
|
-
env["DISPLAY"] = DISPLAY_NUM
|
|
148
|
-
|
|
149
|
-
# Launch Chromium in window mode (not headless — we WANT it visible)
|
|
150
|
-
subprocess.Popen([
|
|
151
|
-
"chromium", "--no-sandbox",
|
|
152
|
-
f"--window-size={WIDTH},{HEIGHT}",
|
|
153
|
-
"--window-position=0,0",
|
|
154
|
-
"--start-maximized",
|
|
155
|
-
url
|
|
156
|
-
], env=env, stderr=subprocess.DEVNULL)
|
|
157
|
-
|
|
158
|
-
time.sleep(duration)
|
|
159
|
-
recorder.send_signal(signal.SIGINT)
|
|
160
|
-
recorder.wait()
|
|
161
|
-
xvfb.terminate()
|
|
162
|
-
print(f"✅ Browser recording saved: {output}")
|
|
163
|
-
return output
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# ── POST-PROCESSING ───────────────────────────────────────────────────────────
|
|
167
|
-
|
|
168
|
-
def add_narration(video_path, narration_text, output_path):
|
|
169
|
-
"""Add TTS narration to a recorded video"""
|
|
170
|
-
import pyttsx3
|
|
171
|
-
|
|
172
|
-
engine = pyttsx3.init()
|
|
173
|
-
engine.setProperty('rate', 140)
|
|
174
|
-
engine.save_to_file(narration_text, '/tmp/narration.wav')
|
|
175
|
-
engine.runAndWait()
|
|
176
|
-
|
|
177
|
-
subprocess.run([
|
|
178
|
-
'ffmpeg', '-i', '/tmp/narration.wav',
|
|
179
|
-
'-c:a', 'libmp3lame', '-b:a', '128k',
|
|
180
|
-
'/tmp/narration.mp3', '-y', '-loglevel', 'quiet'
|
|
181
|
-
])
|
|
182
|
-
|
|
183
|
-
subprocess.run([
|
|
184
|
-
'ffmpeg', '-i', video_path, '-i', '/tmp/narration.mp3',
|
|
185
|
-
'-c:v', 'copy', '-c:a', 'aac',
|
|
186
|
-
'-shortest', output_path, '-y', '-loglevel', 'quiet'
|
|
187
|
-
])
|
|
188
|
-
return output_path
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def add_overlay_text(video_path, text, position="bottom", output_path=None):
|
|
192
|
-
"""Add text overlay to recorded video using FFmpeg"""
|
|
193
|
-
if not output_path:
|
|
194
|
-
output_path = video_path.replace('.mp4', '_overlay.mp4')
|
|
195
|
-
|
|
196
|
-
if position == "bottom":
|
|
197
|
-
vf = f"drawtext=text='{text}':fontcolor=white:fontsize=24:x=(w-text_w)/2:y=h-th-20:box=1:boxcolor=black@0.5"
|
|
198
|
-
else:
|
|
199
|
-
vf = f"drawtext=text='{text}':fontcolor=white:fontsize=24:x=20:y=20"
|
|
200
|
-
|
|
201
|
-
subprocess.run([
|
|
202
|
-
'ffmpeg', '-i', video_path, '-vf', vf,
|
|
203
|
-
output_path, '-y', '-loglevel', 'quiet'
|
|
204
|
-
])
|
|
205
|
-
return output_path
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if __name__ == "__main__":
|
|
209
|
-
result = record_session(run_demo, duration=12, output=OUTPUT)
|
|
210
|
-
# Add narration
|
|
211
|
-
final = add_narration(result, "This is our automated UI demo.",
|
|
212
|
-
"/home/claude/final_recording.mp4")
|
|
213
|
-
shutil.copy(final, "/mnt/user-data/outputs/recording.mp4")
|
|
214
|
-
print(f"🎉 Done: /mnt/user-data/outputs/recording.mp4")
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## When to Use This Approach
|
|
218
|
-
|
|
219
|
-
✅ When you need to show a REAL running application
|
|
220
|
-
✅ When demonstrating web UI (real browser rendering)
|
|
221
|
-
✅ When the app has complex visual state hard to reproduce with Pillow
|
|
222
|
-
|
|
223
|
-
❌ Don't use for simple text/graphic demos (too slow — use Approach 1)
|
|
224
|
-
❌ Avoid if network access to the app is needed (may be blocked)
|
|
225
|
-
|
|
226
|
-
## Important Notes
|
|
227
|
-
|
|
228
|
-
- Xvfb uses RAM for the framebuffer — 1280x720x24 ≈ 3.5MB per frame
|
|
229
|
-
- Always kill both `ffmpeg` and `Xvfb` in a `finally` block to avoid orphan processes
|
|
230
|
-
- Use `DISPLAY=:99` not `:0` to avoid conflicts with any host display
|
|
231
|
-
- FFmpeg's `-preset ultrafast` is recommended for real-time capture
|
|
232
|
-
- For longer recordings (>60s), consider `-crf 28` to reduce file size
|