@spark-apps/quickpeek 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,21 +1,15 @@
1
- [license-link]: ./LICENSE
2
- [privacy-link]: ./PRIVACY.md
3
- [discord-link]: https://discord.gg/mAjHWE3mSp
4
- [stars-link]: ../../stargazers
5
- [issues-link]: ../../issues
1
+ [coffee-link]: https://buymeacoffee.com/spark88
6
2
 
7
3
  <div align="center">
8
4
 
9
- <img src=imgs/logo.png>
5
+ <img src="https://quickpeek.co/logo.png" alt="QuickPeek" width="220">
10
6
 
11
7
  **Demo videos for your web app with AI**
12
8
 
13
9
  *Because Tutorial Videos are Overrated!*
14
10
 
15
11
  [![npm](https://img.shields.io/npm/v/@spark-apps/quickpeek?style=flat-square&logo=npm&logoColor=white&color=crimson)](https://www.npmjs.com/package/@spark-apps/quickpeek)
16
- [![MIT](https://img.shields.io/badge/License-MIT-blueviolet?style=flat-square)][license-link]
17
- [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?style=flat-square&logo=discord&logoColor=white)][discord-link]
18
- [![GitHub Stars](https://img.shields.io/github/stars/muammar-yacoob/quickpeek?style=flat-square)][stars-link]
12
+ ![MIT](https://img.shields.io/badge/License-MIT-blueviolet?style=flat-square)
19
13
 
20
14
  [![Listed on Sellular](https://sellular.online/badge/quickpeek.svg)](https://sellular.online/badge/quickpeek/go)
21
15
 
@@ -25,18 +19,36 @@
25
19
 
26
20
  ## Quick Start
27
21
 
22
+ Nothing to install:
23
+
24
+ ```bash
25
+ npx @spark-apps/quickpeek https://your-app.com
26
+ ```
27
+
28
+ Or install it for repeat use:
29
+
28
30
  ```bash
29
31
  npm install -g @spark-apps/quickpeek
30
32
  qp localhost:3000
31
33
  ```
32
34
 
33
- **Requirements:** [ffmpeg](https://ffmpeg.org/download.html), [Groq API key](https://console.groq.com/keys) (free)
35
+ **Requirements:** [ffmpeg](https://ffmpeg.org/download.html). Nothing else. The AI
36
+ runs through QuickPeek's hosted relay, so there is no API key to get.
37
+
38
+ **Free tier:** 5 videos on an unconfirmed email, then 10 a day once you confirm
39
+ it. No card either way. The CLI asks for an address on the first run because the
40
+ AI planner runs on our servers and is metered per account; over
41
+ [MCP](#mcp-server) no account is needed at all, because your own model does the
42
+ writing. A free video of 20 seconds or under comes out clean; a longer free video
43
+ carries a QuickPeek watermark. The hosted web recorder at quickpeek.co is
44
+ deliberately different: there, free videos are capped at 20 seconds and are
45
+ always watermarked. https://quickpeek.co/pricing sets the two side by side.
34
46
 
35
47
  ## Features
36
48
 
37
49
  | Feature | Description |
38
50
  |---------|-------------|
39
- | **AI Planning** | Groq AI analyzes your app and generates demo steps |
51
+ | **AI Planning** | AI analyzes your app and generates demo steps |
40
52
  | **Script Mode** | Provide your own narration script, AI maps it to UI actions |
41
53
  | **Capture Mode** | Record your own interactions → replayable plan.json |
42
54
  | **Voice Over** | Free Edge TTS narration in 60+ languages |
@@ -47,13 +59,13 @@ qp localhost:3000
47
59
 
48
60
  ## Examples
49
61
 
50
- ### Basic — AI generates the plan
62
+ ### Basic: AI generates the plan
51
63
 
52
64
  ```bash
53
65
  qp localhost:3000
54
66
  ```
55
67
 
56
- ### Script mode — you write the narration
68
+ ### Script mode: you write the narration
57
69
 
58
70
  ```bash
59
71
  qp localhost:3000 --script narration.txt
@@ -63,12 +75,12 @@ Script file format (one caption per line, `#` for comments):
63
75
 
64
76
  ```
65
77
  # My app demo
66
- We start on the dashboard — your main workspace.
78
+ We start on the dashboard, your main workspace.
67
79
  Let's search for a customer by typing their name.
68
80
  Click the result to open their profile.
69
81
  ```
70
82
 
71
- ### Capture mode — record your own interactions
83
+ ### Capture mode: record your own interactions
72
84
 
73
85
  ```bash
74
86
  qp capture localhost:3000
@@ -122,7 +134,7 @@ Config is auto-generated on first run. Edit `quickpeek.config.json` to customize
122
134
  | `pt` | Portuguese | `hi` | Hindi |
123
135
  | `it` | Italian | `ru` | Russian |
124
136
 
125
- See `src/voices.ts` for all 60+ supported languages.
137
+ The full list comes back from the `list_voices` tool on the MCP server.
126
138
 
127
139
  </details>
128
140
 
@@ -131,8 +143,6 @@ See `src/voices.ts` for all 60+ supported languages.
131
143
 
132
144
  Edit `captions.css` (auto-generated) to customize:
133
145
 
134
- ![Classic Yellow](imgs/caption-classic.png)
135
-
136
146
  ```css
137
147
  .caption {
138
148
  font-family: Arial, sans-serif;
@@ -177,6 +187,40 @@ Edit `demo/plan.json` to customize your demo. Each step uses one of these action
177
187
 
178
188
  **Target shortcuts:** For buttons and links, use simplified text like `"download"` or `"extract frame"` instead of CSS selectors.
179
189
 
190
+ ## MCP Server
191
+
192
+ QuickPeek ships an MCP server (`quickpeek-mcp`) so an AI agent can record a demo
193
+ directly, with no shelling out to the CLI.
194
+
195
+ ```bash
196
+ claude mcp add quickpeek -- npx -y --ignore-scripts --package=@spark-apps/quickpeek quickpeek-mcp
197
+ ```
198
+
199
+ Or add it by hand, for any MCP client:
200
+
201
+ ```json
202
+ {
203
+ "mcpServers": {
204
+ "quickpeek": {
205
+ "command": "npx",
206
+ "args": ["-y", "--ignore-scripts", "--package=@spark-apps/quickpeek", "quickpeek-mcp"]
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ Published on the [official MCP registry](https://registry.modelcontextprotocol.io)
213
+ as `co.quickpeek/quickpeek`.
214
+
215
+ **9 tools:** `generate_demo`, `generate_demo_from_script`, `list_demos`,
216
+ `get_demo_config`, `list_voices`, `open_in_editor`, `check_account`, and the
217
+ `start_auth` / `finish_auth` pair for recording an app you have to log into.
218
+
219
+ The server never calls an AI itself. It hands the planning and narration prompts
220
+ back to you, the calling model, and takes your answer as a parameter, so a demo
221
+ costs you no QuickPeek quota and no second-rate model. That is also why this path
222
+ needs no account at all for the first 5 videos, where the CLI asks for an email.
223
+
180
224
  ## Output
181
225
 
182
226
  | File | Description |
@@ -190,12 +234,10 @@ Edit `demo/plan.json` to customize your demo. Each step uses one of these action
190
234
 
191
235
  ## 🌱 Support & Contributions
192
236
 
193
- ⭐ **Star the repo** & I power up like Mario 🍄
194
237
  ☕ **Devs run on coffee** - [Buy me one?][coffee-link]
195
- 💖 **Sponsor** [Your support][stars-link] helps maintain and improve the tool<br>
196
238
  💰 **Crypto tips welcome** - [Tip in crypto](https://tip.md/muammar-yacoob)
197
- 🤝 **Contributions are welcome** - [🍴 Fork][fork-link], improve, PR!
198
- 🎥 **Need help?** <img src="https://img.icons8.com/color/20/youtube-play.png" alt="YouTube" width="20" height="20" style="vertical-align: middle;"> [Setup Tutorial][vid-link] • <img src="https://img.icons8.com/color/20/discord--v2.png" alt="Discord" width="20" height="20" style="vertical-align: middle;"> [Join Discord][discord-link]
239
+ 🎬 **See it work** - [Watch a demo](https://www.youtube.com/shorts/HaibrNvJknY)
240
+ 🐛 **Found a bug or want a feature?** Email [support@quickpeek.co](mailto:support@quickpeek.co) - the repo is private, so email is the way in.
199
241
 
200
242
  <div align="center">
201
243
 
@@ -16,7 +16,7 @@
16
16
  "artist": "HoliznaCC0",
17
17
  "license": "CC0 1.0 Universal",
18
18
  "source": "https://freemusicarchive.org/music/holiznacc0/background-music",
19
- "mood": "gentle",
19
+ "mood": "corporate",
20
20
  "duration": 91.777
21
21
  },
22
22
  {
package/dist/index.d.mts CHANGED
@@ -1,17 +1,11 @@
1
- import { U as UserTier, C as CaptionStyle, a as CaptionPosition, b as Config, V as VoiceRate } from './voices-CPpnWn39.mjs';
2
- export { A as AIError, c as AIResponse, d as AIResult, e as CLI_BACKOFF_MS, f as CONFIG_FILE, g as CaptionPreset, h as CaptionWordStyle, i as ChatOptions, D as DEFAULT_CAPTIONS, j as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, N as NO_CLIP_OUTRO, P as PLAN_MAX_TOKENS, n as Plan, R as RATE_FACTOR, o as RateLimitInfo, p as RetryNotice, S as SERVER_BACKOFF_MS, q as SIZE_PRESETS, r as SparkStatus, s as SparkSubscription, t as SparkTrial, u as Step, v as SuggestedAction, T as Tier, w as VERSION, x as VIDEO_PROFILES, y as VideoProfileName, z as VideoSize, B as VoiceGender, F as applyProfile, G as ariaLabelSelector, J as buildSystemPrompt, K as buildUserPrompt, L as callAI, M as callAIViaRelay, O as crawlPage, Q as deStock, W as defaultCaptionsFor, X as getErrorMessage, Y as getLanguageName, Z as getStatus, _ as getStatusCached, $ as getTierByEmail, a0 as gotoSettled, a1 as hasTrialRemaining, a2 as hasVoice, a3 as idSelector, a4 as internationalVoiceFor, a5 as isCanceling, a6 as isExcludedLink, a7 as isPaid, a8 as isVerified, a9 as normalizeUrl, aa as parseAIPlanResponse, ab as parsePartial, ac as pricingUrl, ad as rgbaToHex, ae as runPooled, af as shouldSkipLink, ag as showPaywall, ah as stripLongDashes, ai as toTitleCase, aj as voiceFor, ak as waitForPlaceholdersGone } from './voices-CPpnWn39.mjs';
1
+ import { U as UserTier, C as CaptionStyle, a as CaptionPosition, b as Config, V as VoiceRate } from './voices-5AFt1GDp.mjs';
2
+ export { A as AIError, c as AIResponse, d as AIResult, e as CLI_BACKOFF_MS, f as CONFIG_FILE, g as CaptionPreset, h as CaptionWordStyle, i as ChatOptions, D as DEFAULT_CAPTIONS, j as DEFAULT_CONFIG, E as EXCLUDED_LINK_PATTERNS, k as ElementInfo, l as ElementType, H as HighlightMode, I as INTERACTIVE_SELECTORS, m as INTERNATIONAL_VOICE, N as NO_CLIP_OUTRO, P as PLAN_MAX_TOKENS, n as Plan, R as RATE_FACTOR, o as RELAY_BASE, p as RateLimitInfo, q as RetryNotice, S as SERVER_BACKOFF_MS, r as SIZE_PRESETS, s as SparkStatus, t as SparkSubscription, u as SparkTrial, v as Step, w as SuggestedAction, T as Tier, x as VERSION, y as VIDEO_PROFILES, z as VideoProfileName, B as VideoSize, F as VoiceGender, G as applyProfile, J as ariaLabelSelector, K as buildSystemPrompt, L as buildUserPrompt, M as callAI, O as callAIForPlan, Q as callAIViaRelay, W as crawlPage, X as deStock, Y as defaultCaptionsFor, Z as getErrorMessage, _ as getLanguageName, $ as getOrRefreshRelayToken, a0 as getStatus, a1 as getStatusCached, a2 as getTierByEmail, a3 as gotoSettled, a4 as hasTrialRemaining, a5 as hasVoice, a6 as idSelector, a7 as internationalVoiceFor, a8 as isCanceling, a9 as isExcludedLink, aa as isPaid, ab as isVerified, ac as normalizeUrl, ad as parseAIPlanResponse, ae as parsePartial, af as pricingUrl, ag as rgbaToHex, ah as runPooled, ai as shouldSkipLink, aj as showPaywall, ak as stripLongDashes, al as toTitleCase, am as voiceFor, an as waitForPlaceholdersGone } from './voices-5AFt1GDp.mjs';
3
3
  export { MALE_VOICES, MULTILINGUAL_VOICES, WIDE_VOICES, hexToAss as hexToASS, windowsDrivePathToWsl as windowsPathToWSL } from '@spark-apps/video-kit';
4
4
  import 'playwright';
5
5
 
6
6
  /**
7
- * Billing utilities — watermark decisions and upgrade URL generation
7
+ * Billing utilities — upgrade URL generation
8
8
  */
9
-
10
- /**
11
- * Check if watermark should be applied
12
- * Watermark applies to free tier videos longer than 20 seconds
13
- */
14
- declare function shouldWatermark(tier: UserTier, videoDurationSeconds: number): boolean;
15
9
  /**
16
10
  * Get upgrade URL for pricing page
17
11
  */
@@ -73,6 +67,38 @@ interface ComposeOptions {
73
67
  stepTransitions?: StepTransition[] | undefined;
74
68
  /** 'off' pins the deliverable encode to libx264; anything else probes for a GPU. */
75
69
  hwaccel?: 'auto' | 'off' | undefined;
70
+ /**
71
+ * The tempo pass that will run AFTER this compose, if any.
72
+ *
73
+ * That pass speeds the finished file up, and it cannot tell a narrator from
74
+ * a song: a demo scored with a track came out with the music playing 1.2x,
75
+ * which is audible on anything with a beat and is simply wrong - the voice
76
+ * is what the pacing is for. The bed is laid down pre-slowed by the same
77
+ * factor here, so the later speed-up returns it to its natural tempo.
78
+ * atempo preserves pitch in both directions, so the round trip is a tempo
79
+ * change and not a transposition. 1 (the default) changes nothing.
80
+ */
81
+ tempo?: number | undefined;
82
+ /**
83
+ * Where the music bed starts, in seconds on this compose's timeline.
84
+ *
85
+ * 0 (the default) scores the whole video. Set to the closing card's start
86
+ * and the demo plays dry, with the song arriving as the card does - which
87
+ * is what you want when the track is the product's own and is meant to be
88
+ * heard rather than ducked under a narrator for a minute first.
89
+ */
90
+ musicStartSecs?: number | undefined;
91
+ /**
92
+ * Length of the music file, in seconds, so its END can be landed on the
93
+ * video's end.
94
+ *
95
+ * A song has an ending, and a song cut off two thirds through does not: it
96
+ * stops. Given the track's length, the bed is seeked so that its last note
97
+ * falls on the last frame, which is what makes a closing card feel closed
98
+ * rather than interrupted. Omitted, the bed plays from its beginning as
99
+ * before.
100
+ */
101
+ musicDurationSecs?: number | undefined;
76
102
  }
77
103
  interface ConcatVideoOptions {
78
104
  inputPath: string;
@@ -821,6 +847,19 @@ declare function resolveRate(config: Config): VoiceRate;
821
847
  * engine to unescape an entity it may not have escaped itself.
822
848
  */
823
849
  declare function speakSymbols(text: string): string;
850
+ /**
851
+ * A caption as the voice should receive it.
852
+ *
853
+ * Order is load-bearing and each pass documents why above it: symbols before
854
+ * anything (a bare "&" invalidates the SSML), domains before toSpokenForm
855
+ * (matching "vidlet dot app" cannot tell an address from prose), acronyms
856
+ * after (so a TLD spelled here is not re-spelled), numbers last.
857
+ *
858
+ * Exported so the shaping can be asserted directly. It used to be one inline
859
+ * expression, which meant the only way to test a pronunciation was to
860
+ * re-create the chain in the test and hope it stayed in step.
861
+ */
862
+ declare function spokenForm(caption: string): string;
824
863
  declare function generateVoiceover(steps: TTSStep[], config: Config, workDir: string, outputDir: string, logError: (context: string, error: unknown) => Promise<void>, task: (text: string) => {
825
864
  succeed: (text?: string) => void;
826
865
  }, info: (text: string) => void): Promise<AudioResult>;
@@ -904,4 +943,4 @@ declare function buildWatermarkFilters(opts: {
904
943
  endsOnCreditsCard?: boolean;
905
944
  }): string;
906
945
 
907
- export { type ApplyTransitionsOptions, type AudioResult, type BlendTransition, CURSOR_CSS, CURSOR_PNG, CURSOR_SCRIPT, type CaptionCue, CaptionPosition, type CaptionRenderContext, CaptionStyle, type ComposeOptions, type ConcatVideoOptions, Config, EMPTY_SPAN_SPEED, GIF_DEFAULTS, type GifOptions, type LenientParse, MUSIC_BED_LUFS, NARRATION_LEAD_IN_MS, type SilenceSpan, type SpeedUpOptions, type SpliceVideosOptions, type SpokenWord, type StepAudio, type StepSync, type StepTransition, TRIM_EDGE_SILENCE, type TTSStep, type TimeSegment, UserTier, VOICE_LEAD_MS, type VideoOverlay, VoiceRate, type VoiceoverMeta, type WordTiming, alignVoiceoverToRecording, alignWordsToCaption, applyTransitions, atempoChain, buildCompressionFilter, buildConcatArgs, buildGifArgs, buildKeepSegments, buildPaletteArgs, buildRetimeFilter, buildStepTransitions, buildWatermarkFilters, canReuseVoiceover, captionCanvas, composeVideo, compressSilentSpans, concatMedia, concatVideoWithFullFrameOutro, concatVideoWithOutro, convertToGif, coverFilterChain, cutSpansFromVideo, detectBlackSpans, detectFrozenSpans, detectSilences, detectWhiteSpans, estimateWordTimings, extractJsonCandidates, generateASSHeader, generateSilence, generateVoiceover, getAudioCodec, getMediaDuration, getUpgradeUrl, getVideoDimensions, getVideoStreamDuration, hasAudioStream, intersectSpans, isHonestWav, loadExistingVoiceover, loadVoiceoverMeta, mapTimestampsAfterCompression, mergeSpans, parseAIJson, parseWordBoundaries, positionToAlignment, registerFreeUser, renderCaptionsAss, repairUnicodeEscapes, resetVoiceEngine, resolveCaptionFont, resolveCaptions, resolveRate, resolveVoice, retimeSpansInVideo, salvageTruncatedJson, shouldWatermark, speakSymbols, speedUpMiddle, speedUpVideo, spliceVideos, spokenWordTimings, synthesizeSpeech, trimEdgeSilence };
946
+ export { type ApplyTransitionsOptions, type AudioResult, type BlendTransition, CURSOR_CSS, CURSOR_PNG, CURSOR_SCRIPT, type CaptionCue, CaptionPosition, type CaptionRenderContext, CaptionStyle, type ComposeOptions, type ConcatVideoOptions, Config, EMPTY_SPAN_SPEED, GIF_DEFAULTS, type GifOptions, type LenientParse, MUSIC_BED_LUFS, NARRATION_LEAD_IN_MS, type SilenceSpan, type SpeedUpOptions, type SpliceVideosOptions, type SpokenWord, type StepAudio, type StepSync, type StepTransition, TRIM_EDGE_SILENCE, type TTSStep, type TimeSegment, UserTier, VOICE_LEAD_MS, type VideoOverlay, VoiceRate, type VoiceoverMeta, type WordTiming, alignVoiceoverToRecording, alignWordsToCaption, applyTransitions, atempoChain, buildCompressionFilter, buildConcatArgs, buildGifArgs, buildKeepSegments, buildPaletteArgs, buildRetimeFilter, buildStepTransitions, buildWatermarkFilters, canReuseVoiceover, captionCanvas, composeVideo, compressSilentSpans, concatMedia, concatVideoWithFullFrameOutro, concatVideoWithOutro, convertToGif, coverFilterChain, cutSpansFromVideo, detectBlackSpans, detectFrozenSpans, detectSilences, detectWhiteSpans, estimateWordTimings, extractJsonCandidates, generateASSHeader, generateSilence, generateVoiceover, getAudioCodec, getMediaDuration, getUpgradeUrl, getVideoDimensions, getVideoStreamDuration, hasAudioStream, intersectSpans, isHonestWav, loadExistingVoiceover, loadVoiceoverMeta, mapTimestampsAfterCompression, mergeSpans, parseAIJson, parseWordBoundaries, positionToAlignment, registerFreeUser, renderCaptionsAss, repairUnicodeEscapes, resetVoiceEngine, resolveCaptionFont, resolveCaptions, resolveRate, resolveVoice, retimeSpansInVideo, salvageTruncatedJson, speakSymbols, speedUpMiddle, speedUpVideo, spliceVideos, spokenForm, spokenWordTimings, synthesizeSpeech, trimEdgeSilence };