Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Your product demo, re-rendered on every release. Open source, runs in CI.
Developer toolsTypeScript · ShellGitHub @niqbalOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Demos as code: re-run, diff, heal and re-render narrated product walkthroughs on every release. Playwright + Remotion.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Every SaaS has a help-center graveyard: videos showing last year's UI. Loom, Guidde and Clueso make the first recording easy. Nobody re-records forty videos every sprint.
demo-engine treats a demo like a test:
# demos/invite-teammate.flow.yaml
name: Invite a teammate
url: ${BASE_URL}/settings/team
touches: ["src/settings/team/**"]
steps:
- say: Open Team settings from the sidebar.
click: { role: link, name: Team }
- say: Click Invite and enter your teammate's email.
click: { role: button, name: Invite member }
- say: Pick a role. Admins can manage billing.
select: { target: { label: Role }, option: Admin }
zoom: { scale: 1.6 }
- say: Send the invite. They'll get an email within a minute.
click: { role: button, name: Send invite }
expect: { text: Invite sent }
npx demo-engine build demos --base-url https://staging.example.com
That one command:
unchanged, cosmetic, changed or broken.npm i -D demo-engine
npx playwright install chromium
npx demo-engine init --base-url http://localhost:3000 # demos/, config, GitHub workflow
npx demo-engine build demos # run → diff → heal → narrate → render
open .demo-engine/renders/example-walkthrough/example-walkthrough.mp4
No API keys required. Without keys, healing uses the heuristic matcher and narration reads your say lines with a local voice: Kokoro (neural, open weights, pip install kokoro soundfile) if it is installed, otherwise the macOS system voice, otherwise silence. Add ANTHROPIC_API_KEY or OPENAI_API_KEY for polished scripts and smarter heals, ELEVENLABS_API_KEY for a production voice.
Try the bundled example (public TodoMVC, no app to start):
git clone https://github.com/niqbal/demo-engine && cd demo-engine
npm ci --legacy-peer-deps && npm run build && npx playwright install chromium
cd examples/todomvc && node ../../packages/cli/dist/index.js build demos
None of them is required. Each one gives the engine more visibility into what changed.
| Level | What you add | What you get |
|---|---|---|
| 0 · Nothing | Flow files anywhere, a URL | Run, diff, heal, render. Works against any site you can reach. |
| 1 · Demos in the repo | demos/*.flow.yaml with touches: globs, the GitHub Action | build --since origin/main knows which flows a PR affects and lists the touching files in the report. Healed locators come back as a diff you review. |
| 2 · Anchors | data-demo="invite-button" on a few stable elements, anchor: invite-button in flows | Locators that survive copy changes and redesigns. demo-engine anchors --src src fails CI when a flow references an anchor the source no longer has. |
| 3 · Existing tests | import { test } from "@demo-engine/playwright" and wrap actions in demo.step("…") | Your e2e suite records demos as a by-product. DEMO_ENGINE=1 npx playwright test, then demo-engine render <run-dir>. |
// Level 3: an existing Playwright test, narrated.
import { test, expect } from "@demo-engine/playwright";
test("Invite a teammate", async ({ page, demo }) => {
await page.goto("/settings/team");
const invite = page.getByRole("button", { name: "Invite member" });
await demo.step("Click Invite and enter an email.", { target: invite }, async () => {
await invite.click();
await page.getByLabel("Email").fill("[email protected]");
});
});
A flow is a template. An audience is one prospect or customer: company, logo, industry, the person watching, the rep presenting, the sample data they should see. flow × audience is a personalized run and a personalized video, and every one of them is regenerated when the product changes.
# demos/onboarding.flow.yaml
name: Onboarding
url: ${BASE_URL}/
defaults: { company: your team, task: Write the launch post } # what CI, diff and heal run with
setup: npm run seed:demo -- --tenant $AUDIENCE_ID # Layer 1: real data in the app
personalize: # Layer 2: repaint what cannot be seeded
- { css: "header h1", text: "{{audience.company}}" }
end: { text: Try it with your own data., url: https://example.com/book }
steps:
- say: Add the next thing {{audience.company}} needs done. # Layer 3: narration and cards
type: { target: { placeholder: "What needs to be done?" }, value: "{{audience.task}}" }
expect: { text: "{{audience.task}}" }
# demos/audiences/acme.yaml (or one CSV with dotted headers: contact.first, rep.name)
company: Acme Logistics
industry: Logistics
task: Confirm Q4 carrier contracts
logo: demos/audiences/logos/acme.svg
contact: { first: Priya, title: Head of Operations }
rep: { name: Asad Iqbal, title: Founder, calendar: https://cal.com/demo-engine/15min }
pitch: Ops team of 40 tracks carrier tasks in spreadsheets.
demo-engine build demos --audience acme # one prospect
demo-engine build demos --audiences acme globex initech # several
demo-engine build demos --audiences demos/prospects.csv # from a CRM export
demo-engine build demos --audiences # everything in demos/audiences/
demo-engine audiences # which steps change per audience, and why
How it stays cheap and correct:
defaults:. A flow with no {{audience.*}} in its steps and no personalize: block reuses the default run's frames for every audience; only narration and cards change. Otherwise the browser runs per audience and run.json marks which steps are personalized.industry, contact and pitch.renders/<flow>/<audience>/audience.json stamps what was rendered. When the product and the audience are unchanged, the audience is skipped outright.accent or voice overrides the brand's.Output: renders/<flow>/<audience>/<flow>.mp4 next to the default renders/<flow>/<flow>.mp4. The flow's setup: sees AUDIENCE_ID and AUDIENCE_JSON.
demo-engine init writes .github/workflows/demos.yml. The composite action installs the CLI and Chromium, runs build, uploads renders as an artifact, and posts (or updates) one PR comment:
- uses: niqbal/demo-engine@v0
with:
base-url: ${{ vars.DEMO_BASE_URL }}
since: ${{ github.event.pull_request.base.sha }}
comment: true
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Report on a PR that renamed a button:
| flow | status | video | notes |
|---|---|---|---|
| Invite a teammate | ✏️ passed | .demo-engine/renders/invite-a-teammate/invite-a-teammate.mp4 | 1 changed, 3 unchanged · 1/1 locators healed · 2 touching files changed |
| key | meaning |
|---|---|
name, description | Title card. name also becomes the slug for output paths. |
url | Start URL. ${BASE_URL} is substituted; relative paths are joined to --base-url. |
auth.storageState | Playwright storage state file (cookies, local storage). |
auth.login | A .mjs file exporting async (page, context, { baseUrl }) that signs in before the flow. |
touches | Globs over your repo. Changed files matching them mark the flow as affected. |
setup | Shell command run before the flow (seed a demo tenant). BASE_URL is in the env. |
mask | CSS selectors hidden from the diff: the region is excluded from the pixel comparison and the elements are left out of the content comparison (clocks, live counters). Flow-level or per step. |
ignore | Regexes whose matches are dropped from the content comparison, for volatile text with no selector of its own ("\\d+ ms"). Flow-level or per step. |
viewport, voice | Default 1440×900 at 2×. Voice is provider-specific. |
defaults | Default audience values. Any string in the flow may use {{audience.field}} or {{audience.field | "fallback"}}. |
personalize | DOM rewrites applied before every screenshot: { css, text?, src?, html?, style?, attr?, all? }. |
end | Closing card: text, url. Shown when set, when brand.cta is set, or when the audience names a rep. |
Each step has say plus at most one action: goto, click, dblclick, hover, fill, type, select, check, press, scroll, wait. Optional: expect (text, visible, hidden, url), zoom, mask, pause, highlight. fill/type accept a press to hit Enter afterwards.
Locators are user-facing first: { role, name }, { label }, { placeholder }, { text }, { testid }, { anchor } (data-demo), { css } or a bare CSS string. Any locator can carry fallbacks: [...]. nth disambiguates.
| command | what it does |
|---|---|
init | Scaffold demos/, config and workflow. |
build [targets] | The pipeline. --since <ref>, --only-affected, --no-heal, --write-heals, --bless, --no-render, --no-narrate, --verbatim, --force, --report <dir>, --audience <name>, --audiences [names|files|csv...]. |
run / diff / heal / narrate / render / bless | Each stage on its own. run, narrate and render take --audience. |
audiences [flow] | List audiences and show which steps of a flow change per audience. |
changed --since <ref> | Which flows a change affects, by touches. |
anchors --src <dir> | Anchors referenced by flows but missing from the source. |
doctor | Browser, providers, keys. |
Targets are flow files, directories of *.flow.yaml, or run directories recorded by the Playwright fixture.
--write-heals. In CI, pair that with a PR-creating step so a reviewer sees the diff.| options | pick with | |
|---|---|---|
| LLM (script polish, heal) | Anthropic, OpenAI, none | llm: in config or ANTHROPIC_API_KEY / OPENAI_API_KEY |
| TTS | ElevenLabs, OpenAI, Kokoro (local neural), macOS say, silent | tts: in config or the matching key. Kokoro: pip install kokoro soundfile (Intel Mac: also brew install espeak-ng and pip install "torch==2.2.2" "numpy<2" "transformers<4.50"), DEMO_ENGINE_KOKORO_PYTHON to pick an interpreter, KOKORO_VOICE (default af_heart) |
| Render | Remotion (bundled Chrome Headless Shell) | nothing to configure |
Remotion is free for individuals and teams of up to three; larger companies need a Remotion company license.
Early. The pipeline is complete and tested end to end on the bundled example; what it needs now is your app. Open an issue with a flow that misbehaves and a run directory, and it gets fixed.
Roadmap: hosted player with analytics, Chrome-extension recorder, interactive HTML output from the same manifest, Slack alerts.
Apache-2.0. Commercial use welcome.