Library
Everything you've made and kept. Drag one onto a studio to use it again.
Nothing saved for this project yet. Anything you make and keep lands here.
Projects
{{ projects.length }}/{{ projectCap }} used on your {{ planLabel }} plan · {{ paidExtraDomains }} add-on{{ paidExtraDomains === 1 ? '' : 's' }} (${{ paidExtraDomains * 15 }}/mo)
{{ p.url || 'No URL set' }}
Preparing brand kit…
{{ projects.length }} of {{ projectCap }} project{{ projectCap === 1 ? '' : 's' }} used · {{ paidExtraDomains }} paid add-on{{ paidExtraDomains === 1 ? '' : 's' }}
You're at your plan's {{ projectCap }} — adding opens an upgrade or a $15/mo add-on.
{{ currentPlan === 'free' ? 'Adding more sites needs Pro or Serial' : "You're at your project limit" }}
{{ currentPlan === 'free' ? 'Your Free plan includes 1 project. Upgrade to add this project.' : ('Your ' + planLabel + ' plan includes ' + projectCap + ' project' + (projectCap === 1 ? '' : 's') + '. Upgrade to a plan with more.') }}
Billed by Stripe. Manage or cancel any time in Settings → Subscription.
This is your {{ ordinal(projects.length + 1) }} project
Your {{ planLabel }} plan includes {{ planBaseDomains }} project{{ planBaseDomains === 1 ? '' : 's' }}. Adding this one is an extra-project add-on — +$15/mo, added to your subscription and prorated from today.
You already have {{ paidExtraDomains }} paid add-on{{ paidExtraDomains === 1 ? '' : 's' }} (${{ paidExtraDomains * 15 }}/mo). This makes {{ paidExtraDomains + 1 }} (${{ (paidExtraDomains + 1) * 15 }}/mo).
Billed by Stripe against the card on your subscription. Remove the project later and the add-on decrements automatically.
Choose a new password for your account.
{{ auth.error }}
{{ auth.notice }}
Your build-in-public sidekick.
Free to start, no card. Create your account and confirm the email we send, opening the link in this same browser. Your update and its drafts are then saved into your project, ready for you to approve. Nothing is posted until you do.
Free to start, no card. You came from the weekly page for {{ shippedIntent.repo }}. Create your account and confirm the email we send, opening the link in this same browser: the next screen has {{ shippedIntent.site ? 'its site filled in' : 'it noted' }}, so FoxPlug can write the same weekly page for your own repository.
Free to start. Once you are in, your new project setup opens{{ launchIntent.name ? ' with ' + launchIntent.name + ' filled in' : (launchIntent.typeLabel ? ' with the ' + launchIntent.typeLabel + ' category noted' : '') }}.
Free to start, no card. Three steps:
or use your email
{{ auth.error }}
{{ auth.notice }}
This is exactly what will appear on your LinkedIn profile. Nothing is posted until you press Post now.
{{ (li.preview && li.preview.name) || 'You' }}
Public post, from your own LinkedIn profile
{{ li.preview ? li.preview.text : (li.draft && li.draft.content) }}
Text only: the image on this draft is not attached.
{{ li.preview.chars }} of 3,000 characters
{{ li.notice }}
Loading the preview…
{{ li.error }}
{{ (li.preview && li.preview.expired) ? 'Your LinkedIn connection has expired. Reconnect to post.' : (li.error || 'Connect your LinkedIn account to post this from your profile. LinkedIn will ask for permission to publish on your profile. Nothing is posted until you press Post now.') }}
Posted as you, from your own LinkedIn profile. FoxPlug never posts on its own or on a schedule.
You came from the weekly page for {{ shippedIntent.repo }}.
That repository is yours, so its site is filled in below. Save it and FoxPlug writes that page for you every week, from your own merged work.
FoxPlug writes that page for your own repository every week. The site of {{ shippedIntent.repo }} is filled in below; change it to your own product's address if that repository is not yours. Connect GitHub so FoxPlug can read your merged work.
You came from the {{ launchIntent.name || launchIntent.typeLabel || 'FoxPlug' }} launch page.
{{ launchIntent.site ? 'Its address is filled in below. Change it if your product lives somewhere else, then save.' : 'Add the address of your product below.' }} Category noted: {{ launchIntent.typeLabel }}.
FoxPlug reads your site to learn what is already there — your pages and the tools it runs on — so it can tell you when something new goes live.
{{ urlGate.problem }}
Wrong account?
{{ urlGate.stage }}…
{{ urlGate.note }}
{{ urlGate.total ? (urlGate.saved + ' of ' + urlGate.total + ' pages saved') : 'Looking for your pages' }}
On your homepage: {{ urlGate.stack.join(', ') }}
{{ urlGate.error }}
Your homepage builds itself after it opens, so we couldn't read its pages yet. We saved nothing as your starting point on purpose — otherwise every page would later look brand new.
This is your starting point. From here on, when a new page or a new tool shows up on your site, FoxPlug notices.
This is your starting point.
Your stack
None of the tools we recognise (payments, live chat, analytics, error tracking, booking, search) showed up on your homepage.
Your first post — a draft, from what we just read
{{ urlGateDraft.content }}
It is saved in your Drafts either way — nothing posts on its own.
We read your site, but couldn't write your first post just now — {{ urlGate.firstPostProblem }}. Nothing was made up to fill the gap. Your pages are saved; try again from Drafts.
{{ banner }}
Your update is saved. Last step: connect {{ tryConnect.repo || 'your repo' }}
Your update and its {{ tryConnect.drafts }} draft{{ tryConnect.drafts === 1 ? '' : 's' }} are in {{ tryConnect.projectName || 'your project' }}, ready for you to approve below. Connect GitHub so every new push writes the next update for you. Nothing is posted until you approve it.
One-click GitHub connect is being switched on. Your public repo is already being read for new updates.
{{ githubConnectMsg }}
Connected. New updates will keep coming.
GitHub is connected to {{ tryConnect.projectName || 'your project' }} for {{ tryConnect.repo }}. When you push, FoxPlug writes the next update and its drafts for you to approve. Nothing is posted until you do.
You're on {{ planLabel }} — nice. 🎉
A few things worth setting up now to get the most out of it:
You've reached your project limit
The {{ planLabel }} plan includes {{ projectCap }} project{{ projectCap === 1 ? '' : 's' }}. Upgrade to run more products from one place.
You came from the {{ launchIntent.name || launchIntent.typeLabel || 'FoxPlug' }} launch page{{ launchIntent.site ? ', so its address is filled in. Change anything that is not yours.' : '.' }} Category noted: {{ launchIntent.typeLabel }}.
Add a project
Each project is its own product — its milestones, drafts and posts stay separate and every post is grounded in that product.
You came to make a promo video — let's set it up. Connect your project and every update you ship becomes ready-to-post content: branded image cards and platform-tailored text now, with narrated videos and GIFs rolling out to you first.
Pick where you write your code. FoxPlug works with all of them — your code already lives on GitHub, and we turn every push into your story.
{{ buildToolBridgeLine }}
One-click GitHub connect is being switched on. For now you can set your product up just below.
{{ githubConnectMsg }}
{{ gitlabConnectMsg }}
{{ bitbucketConnectMsg }}
Read-only access to your code. Your access is encrypted at rest — we never store it in plain text and never write to your repos.
What do you build with?
Optional — it helps us decide what to integrate next.
No GitHub yet? You can paste your product address and we'll read your page to fill in the rest — then connect your code any time in Settings.
{{ WIZARD_STEPS[wizardStep - 1].blurb }}
Paste your product URL
We'll read your page and fill in the rest — name, one-liner, logo and brand colour. Everything stays editable, and you can skip straight to typing it yourself.
{{ wizardDetectNote }}
Looks like you publish on {{ platformLabel(wizard.blog_platform) }}. We've preselected {{ destinationLabel(wizard.publish_destination) }} as your publish destination — change it any time in Drafts. We'll remember this as a hint for where to publish.
Pulling your brand…
Got your logo and colors ✓
Where do you post?
Add the places you share updates and we'll pre-fill your promos later. Optional — skip any you don't use.
Your promo power
{{ completenessMeter.pct }}%The more you connect, the more we can make for you. This never affects your free launch video.
What's your stack?
Pick the tools you build with. We'll only show you the connectors that matter — and use the rest to decide what to build next.
Connect one to begin
Connect a tool and we'll pull your real work in and write the first post on it. You can do this in Settings any time too.
{{ t.scopeHint }}
{{ ITOK_EXISTING_ONLY_NOTE[t.itok] }}
Coming soon for your stack — we know you want these
No tools picked. You can connect a tool in Settings whenever you're ready — or skip ahead and see a sample.
Already have a changelog? We'll pull it in.
Paste your changelog or blog address. We fetch your past entries and add them as history, so your first posts already know your story. WordPress: add /feed. Ghost: add /rss. Substack: add /feed. Not sure? Just paste the page address.
{{ changelogImportResult.text }}
Here's FoxPlug on your work Sample
This is a sample so you can see the shape of it. Connect a tool in Settings and your real milestones replace it.
We turned your shipped work into your first post. Review and use it in Drafts whenever you're ready.
Milestone
{{ payoffMilestone.title }}
{{ payoffMilestone.description }}
Draft post
{{ payoffDraftText }}
You're set up
Connect a tool and your first milestone — with a post waiting on it — shows up in Drafts.
Connect your repo and we'll turn what you've already shipped into ready-to-post updates. Paste a public repo — no token needed.
Checking…
✓ {{ firstRun.check.repo }} · {{ firstRun.check.commits }} recent commits
✓ {{ firstRun.check.repo }} — but no commits in the last 30 days, so there'd be nothing to write up yet.
{{ firstRun.check.error }}
{{ firstRun.msg }}
Private repo?
We only read. Nothing is posted — every draft lands in your queue for you to read first.
How this works
We find your project's advancements and make posts, videos, and images for you to share. Nothing ever posts itself — everything's yours to use.
Choose your voice
By default your posts read “I just released the…”. Switch to “we” if you'd rather they sound like something your team accomplished.
Auto-upload to the FoxSaaS YouTube channel is paused — the access token has expired (these are Testing-mode tokens and lapse about weekly, this is expected).
Fix: re-run the YouTube OAuth consent for the FoxSaaS channel and paste a fresh YOUTUBE_REFRESH_TOKEN into the Supabase Edge Function secrets. New promos still render, get their /v/ page and GIF, and post — they just won't have a YouTube link until this is renewed. Last checked {{ youtubeHealth.checked_at }}.
Let us turn your breakthroughs into visual updates — images, GIFs, even promo clips. Add Pinterest, TikTok, or Instagram (personal or business) in Settings so we know where to make them for.
Four steps from here to a published update. Each ticks off on its own as you go.
All four done — you're shipping.
Quick — what's your product called?
We'll use this in your posts so the first draft sounds like your product, not your repo.
Your kit is saved 🎉
Want this on every release? One step and FoxPlug auto-generates a kit each time you ship.
1 · Generate your first kit
Paste your product's site — FoxPlug builds your launch kit now.
{{ giftFirstRun.gift_lead === 'gif' ? '2' : '1' }} · Auto-generate on every release
Connect GitHub (or your changelog) — each update you ship becomes a kit automatically.
{{ githubConnectMsg }}
{{ approvedBand(ad).line }}
{{ displayContent(ad) }}
Approved. Now post it — that part is yours.
FoxPlug has not sent this and will not. It is written and waiting; copy it, open the platform, and press post there.
{{ displayContent(approvedNowDraft) }}
{{ milestoneHeadline(todayShare.milestone) }}
{{ todayShare.why }}
{{ displayContent(todayShare.draft) }}
{{ displayContent(o) }}
Nothing has been written for this project yet — FoxPlug writes a post when it spots a moment worth one.
When you capture work, a post gets written for it and the best one appears here.
{{ todayNext.headline }}
{{ todayNext.why }}
The rule: a post FoxPlug wrote more than 30 days ago that has not been sent comes off the queue. The text is untouched and any of them can go back.
Nothing retired to show.
{{ r.content }}
{{ diagTop.question }}
{{ diagTop.because }}
{{ fig.value }}
{{ fig.label }}
{{ fig.window }}{{ fig.scope.label }}
{{ diagTop.figures.find(f => !f.scope.site).scope.note }}
That was the only thing your figures raised.
{{ fnd.question }}
{{ fig.value }} {{ fig.label }}{{ fig.scope.label }} ·
Nothing to ask yet — no figures have come in.
Connected, but nothing pulled yet: {{ diagMissing.connectedButNoFigures.join(', ') }}.
No analytics source is connected for this domain yet.
Not connected: ,
{{ diagDrawer.title }}
{{ diagDrawer.question }}
Reading your page…
{{ diagDrawerError }}
It leads with
“{{ diagDrawer.data.leads_with }}”
The page has no heading of its own — a visitor lands with nothing to read first.
…then: “{{ diagDrawer.data.supporting_line }}”
It appears to be addressed to
{{ diagDrawer.data.addressed_to.join(", ") }}
Nobody in particular — the top of the page names no audience.
{{ diagDrawer.data.speaks_to_you_directly ? "It does speak to the reader directly (“you”)." : "It never says “you”." }}
It asks the visitor to
“{{ diagDrawer.data.primary_ask }}”
Nothing — there is no call to action near the top of the page.
Also asks: {{ diagDrawer.data.other_asks.join(", ") }}
Read from {{ diagDrawer.data.host }} just now. Read-only — nothing on your site was changed.
{{ diagDrawer.data.people }}
were people
{{ diagDrawer.data.bots }}
were automated
Of {{ diagDrawer.data.total }} sessions in the window, {{ diagDrawer.data.people }} were people. Judge everything else against that number, not the total.
Cannot be separated
{{ b.label }} ({{ b.value }}), — reported across every session at once, people and automated together. Treat them as a mix, not as what a person did.
Worked out from figures already pulled. Nothing was requested from anyone.
Where arrivals came from in the first half of the window, against the second.
No source was recorded in either half — the arrivals came in without a referrer.
Worked out from figures already pulled. Nothing was requested from anyone.
How {{ (activeProject && activeProject.name) || 'this project' }} is tracking against its plan.
{{ planDrift.plan.plan_text }}
Time frame: {{ planDrift.plan.horizon }}
No plan set for this project yet.
{{ laneEmptyNote(board.filter) }}
{{ sweepNote() }}
{{ deadDestinationCount() }} destination{{ deadDestinationCount() === 1 ? '' : 's' }} no longer resolve, so those cards render without a button.
{{ board.suppressed.count }} item{{ board.suppressed.count === 1 ? '' : 's' }} held back for having nowhere to send you.
Nothing on this board has been generated or posted. It is a list, not a queue.
{{ fsMem.clusters.length }} of {{ fsMem.clusterTotal }} shown.
Nothing is set until you press the button.
You shipped {{ silence.shipped }} thing{{ silence.shipped === 1 ? '' : 's' }} worth talking about in the last {{ silence.window_days }} days. {{ silence.posted }} {{ silence.posted === 1 ? 'has' : 'have' }} been posted so far.
{{ silence.silent }} {{ silence.silent === 1 ? 'has' : 'have' }} not been posted anywhere yet.
Nothing is going unnoticed right now.
Autopilot writes the draft and stops there. Nothing goes out until you read it and press Publish.
{{ q.question }}
What's the reason?
{{ fsMem.questionError }}
{{ a.competitor }} {{ fsAdRunFor(a) }}
name match only{{ a.headline }}
{{ fsAdShort(a.body) }}
{{ fsMem.adGen.error }}
Run them like this
{{ fsMem.adGen.plan.never }}
{{ fsMem.adGen.caveat }}
{{ v.angle }}
{{ v.headline }}
{{ v.subline }}
{{ v.why_this_angle }}
{{ iss.policy }}
{{ iss.quote }}
{{ iss.fix }}
Nothing here trips the common automated-review rules.
Worth doing, not blocking
{{ n.fix }}
{{ fsAdCompliance(v).caveat }}
{{ r.kind === 'generated' ? 'Yours' : (r.source_competitor || 'Competitor') }} · {{ fsAdRunFor(r).replace('has run this ad for ', '') }}
{{ r.headline }}
{{ fsAdShort(r.body) }}
{{ c.visits }} visit{{ c.visits === 1 ? '' : 's' }} · {{ c.conversions }} conversion{{ c.conversions === 1 ? '' : 's' }}
{{ c.conversion_rate }}% {{ c.audience.name }}| Creative | Visits | Conv. | Rate |
|---|---|---|---|
| {{ p.headline }} | {{ p.visits }} | {{ p.conversions }} | {{ p.conversion_rate === null ? '—' : p.conversion_rate + '%' }} |
{{ c.verdict }}
${{ c.cost_per_conversion }} per conversion, against a ${{ c.budget_daily }}/day budget.
Tell us what you have spent and this becomes a cost per conversion.
{{ c.unattributed_visits }} visit{{ c.unattributed_visits === 1 ? '' : 's' }} could not be tied to a specific creative.
Too early to call it
{{ fsMem.results.refine[c.campaign_id].reason }}
Winner: {{ fsMem.results.refine[c.campaign_id].winner.headline }}
{{ fsMem.results.refine[c.campaign_id].reason }}
{{ fsMem.results.refine[c.campaign_id].confidence }}
Drop these
{{ fsMem.results.refine[c.campaign_id].recommendations.kill.why }}
Move ${{ fsMem.results.refine[c.campaign_id].recommendations.budget_shift.move_usd_per_day }}/day to "{{ fsMem.results.refine[c.campaign_id].recommendations.budget_shift.to }}"
{{ fsMem.results.refine[c.campaign_id].recommendations.budget_shift.expected }}
{{ fsMem.results.refine[c.campaign_id].recommendations.budget_shift.never }}
Next round, built on your winner
{{ fsMem.results.refine[c.campaign_id].recommendations.next_round.why }}
{{ fsMem.results.what }}
{{ fsMem.campaign.error }}
✓✗ {{ c.label }}
{{ c.detail }}
{{ c.fix }}
Everything is ready. Here is where it goes.
Open {{ fsMem.campaign.handoff.open_here.label }} →{{ fsMem.campaign.handoff.open_here.note }}
Download the creative
Paste blocks
{{ String(key).replace(/_/g, ' ') }}
{{ val }}
Then, in order
Before you spend anything — install tracking
{{ fsMem.campaign.handoff.tracking.where_to_paste }}
{{ fsMem.campaign.handoff.tracking.snippet }}
{{ fsMem.campaign.handoff.tracking.get_pixel_id.label }} → — {{ fsMem.campaign.handoff.tracking.get_pixel_id.how }}
{{ fsMem.campaign.handoff.tracking.conversion }}
{{ fsMem.campaign.handoff.tracking.what_we_see }}
{{ fsMem.campaign.handoff.never }}
Reading what fits you…
{{ fsMem.error }}
Nothing that fits your constraints right now.
{{ fsMem.constraintCount }} thing{{ fsMem.constraintCount === 1 ? '' : 's' }} you ruled out {{ fsMem.constraintCount === 1 ? 'is' : 'are' }} being honoured. Ask for more when you want a fresh pass.
{{ planDrift.drift.suggested_plan }}
{{ planDrift.drift.note }}
Biggest drop: {{ planDrift.drift.bottleneck }}
Seed posts approved: {{ driftSeedConversion().approved }} of {{ driftSeedConversion().created }}
{{ c.unlocks }}
No drift check yet.
{{ formatDrawer.m ? (formatDrawer.m.title || 'this moment') : 'Reading this moment…' }}
Reading it now…
{{ formatDrawer.error }}
{{ formatDrawer.m.title }}
{{ formatDrawer.m.why_it_matters }}
{{ formatDrawer.m.source || 'your own work' }} · {{ fmt(formatDrawer.m.created_at) }}
Who decides how this is shown
What it should look like
Showing it as
{{ formatChoice(formatDrawer.m).label }} · {{ formatChoice(formatDrawer.m).mine ? 'your call' : 'FoxSense’s call' }}
{{ formatChoice(formatDrawer.m).reason }}
Saved. Writing the reason for this format…
What happens next
Nothing gets made. Held moments stay off the board and no draft is written for them. Choose a format above if you want something made from it.
This one can be made now, from {{ shotAvail[formatDrawer.m.id].source }}. You get the real screenshot with a caption in your own words — and a callout box only where the walkthrough recorded a confident one. No box is invented.
An annotated shot needs a real screenshot of your product, and there isn't one — {{ (shotAvail[formatDrawer.m.id] && shotAvail[formatDrawer.m.id].why) || 'nothing has been captured for this project yet' }}. Nothing will be faked to fill the gap. The branded card is what can be made instead, and the button below makes that.
Nothing downstream reads this choice yet. Two makers exist — the branded card, and the annotated shot for the Annotated shot choice — and neither is driven by {{ formatChoice(formatDrawer.m).label }}. The button below makes the branded card.
Annotated shot
Branded card
Making visuals is on Pro. Your format choice is still recorded.
{{ visualMsg[formatDrawer.m.id] }}
Nothing is posted from here. Choosing a format only decides how it gets shown.
{{ connectDrawer.lead }}
Where your posts go
You have not chosen any channels yet.
{{ chanLabel(ch.key) }}
Connected ✓ You post this one This oneWhere your work comes from
{{ tk.name }}
Connected ✓ Not syncing This one{{ tk.title }}
{{ itokIssue[tk.key].reason }}
{{ itokResult[tk.key].text }}
{{ ITOK_EXISTING_ONLY_NOTE[tk.key] }}
Want somewhere else? — a channel you have not chosen is never posted to.
Close this and you are back on the board, exactly where you were.
{{ foxsense.offer.actionText }}
Writing it now…
{{ foxsense.offer.error }}
One update → a post or thread. Pick a format, generate, edit & post.
Promo + launch films, every ratio.
Shareable images from a win or a Library image.
Looping GIFs from images or a video's ending.
Transparent stickers for Giphy, iMessage, Slack.
One update → a full long-form article.
Write release notes → your public changelog.
This moment looks press-worthy — pitch it?
The moment · {{ pressCardTypeLabel(pressWorthyMilestone) }}
{{ milestoneHeadline(pressWorthyMilestone) || pressWorthyMilestone.title }}
{{ pressCardSubline(pressWorthyMilestone) }}
Weekly shipped pages you follow
{{ currentShareView.tip }}
Connect a source or log a win to begin.
You're shipping — nothing's risen to share-worthy yet. Keep going; we'll glow a card the moment something does.
{{ projectProgressLine }}
{{ milestoneHeadline(m) }}
{{ m.why_it_matters || m.description }}
{{ m.why_it_matters }}
▶ Video ready — open page · also in your Studio
Still rendering — it'll appear in your Studio shortly.
{{ visualMsg[m.id] }}
Text
Visual
Threads
{{ formatDecline(m).headline }}
{{ formatDecline(m).reason }}
GIFs are a Serial feature
Turn your work into branded looping GIFs — from an image, a few images, or a video's ending — ready to share to the GIF networks behind Slack, Discord, Facebook, and iMessage, so people see your product again and again before they ever reach your site.
Serial also gives you:
Make a {{ pickerNoun }} — pick a source.
From your Library
— none yet
From a visual-worthy win
— none yet
{{ projectHasMilestones ? 'Nothing here yet — add an image or clip in your Library, or ship a launch / milestone.' : 'No wins logged yet in this project. Add an asset in your Library, or log a win.' }}
GIF Studio
Turn an image into a typewriter-caption GIF for the pickers in X, Slack, Discord & more.
Why upload your GIF to Giphy?
Uploaded and tagged on Giphy, your GIF gets indexed on Giphy and Tenor — the same libraries that power the GIF pickers in Slack, Discord and LinkedIn. That means it keeps resurfacing in those pickers for months, quietly earning passive reach long after you posted it.
Read the guide →Source
No source yet — make a GIF from a video ending or a Library image.
…or pick one you own
{{ gifStudio.caption.length >= 24 ? 'Max length reached — 24 characters.' : ((24 - gifStudio.caption.length) + ' characters left (max 24).') }}