Built by Cedric V. — architecting lightweight, sovereign systems for modern entrepreneurs.
OwnPulse is the ultimate tool for solopreneurs to pilot their business. It provides a centralized, private view of your business growth and health, bridging the gap between lead acquisition, marketing performance, and financial sustainability.
- Smart Capture: Instantly save leads from LinkedIn, Threads, and Instagram via the dedicated Chrome Extension.
- Pipeline Management: Move leads through custom stages with a high-velocity, debounced interface.
- Bi-directional Linking: Connect sales directly to contacts for a 360° view of customer history.
- Duplicate Merge: Merge accidentally duplicated leads from the contact page or by multi-selecting rows in the leads table — fields, lists, notes, tasks, sales, and activities are consolidated and duplicates removed.
- Acquisition Analysis: Track exactly which social channels are driving your customers.
- Conversion Velocity: Measure the time-to-conversion from first contact to first sale.
- Offer Mastery: Manage complex service offers with integrated work-time calculators and automatic margin tracking.
- Profitability Dashboard: Visualize real vs. theoretical hourly rates and sales goals progress per offer.
- Retention Tracking: Visualize returning vs. new customer ratios to optimize long-term growth.
- Revenue vs. Reality: Track total sales against professional expenses and actual remuneration.
- Net Profit Simulation: Real-time visibility into your true margin after social contributions and taxes.
- Runway Analysis: Know exactly how many months of operation you have left based on current cash flow.
📋 Sécurité & restes à faire : voir SECURITY.md (état vérifié, rotation de clé, test multi-utilisateur, MFA…).
- Frontend/Backend: Next.js 16 (App Router, Turbopack)
- Database: Supabase (PostgreSQL, Real-time sync, Row-Level Security)
- Styling & Components: Tailwind CSS + Shadcn UI
- i18n: Custom localized experience (FR/EN) with persistent settings.
- Extension: Chrome Manifest V3 for secure DOM scraping.
- Create a free project on Supabase.
- Execute the SQL schema found in
/supabase/schema.sqlto initialize yourcontacts,companies, andtaskstables (already security-hardened: RLS owner-scoped, anonymous access limited tocontact_urlsview +capture_contactRPC). - Run the incremental migrations in order:
fix_missing_tables.sql,migration_social_fields.sql,migration_marketing_cfo.sql,migration_acquisition_channels.sql,migration_offers_enhancement.sql,add_tax_social_settings.sql,add_vat_setting.sql,migration_sales_company_link.sql,migration_sales_quantity_decimal.sql,migration_contact_activities.sql,migration_merge_contacts.sql(andsupabase/seed_generic.sqlfor neutral demo data). - Retrieve your
SUPABASE_URLandSUPABASE_ANON_KEY. - Disable public signup: Authentication > Providers > Email > "Allow new users to sign up" = OFF, and enable 2FA/MFA on your account.
schema.sql or the migrations on an existing project — they are initialization scripts. Existing projects are updated with hardening_security_rls.sql and hardening_multi_user_rls.sql instead.
Note: Supabase's Data API no longer exposes newly created public tables automatically by default on new projects starting May 30, 2026. Keep explicit GRANT statements alongside each CREATE TABLE migration.
Security hardening (required for GDPR/LPD): new projects are already hardened by schema.sql (see above). Existing projects: run hardening_security_rls.sql then hardening_multi_user_rls.sql once. They revoke anonymous access to personal/financial data, add user_id ownership (multi-user), and expose only the minimal contact_urls view + capture_contact RPC for the extension. Then, in the Supabase dashboard:
- Authentication > Providers > Email > "Allow new users to sign up" = OFF (otherwise anyone can create an account and read all data).
- Enable 2FA/MFA on your account.
- Rotate the anon key if it was ever shared outside the extension.
- Clone the repository.
- Go to the dashboard directory:
cd dashboard. - Create a
.env.localfile:NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
- Install and run:
npm install npm run dev
- Go to
chrome://extensions/. - Enable Developer Mode.
- Click Load unpacked and select the
/extensionfolder from this repository. - After updating the extension, click Reload on the OwnPulse extension and refresh the social profile tab so the new content script is loaded.
- Update the extension's configuration (in
popup.jsorcontent.js) with your Supabase endpoint.
The extension reports Already in CRM when the profile URL is already captured. On the dashboard, search by the profile URL or username: the All Leads search includes LinkedIn, Threads, and Instagram URLs. If a profile is already present but not visible on the current page, it may be on another paginated results page.
- Keep
eslintaligned witheslint-config-nextcompatibility, not simply with the latest ESLint major. - If a new ESLint major is released before
eslint-config-nextfully supports it, keep the last compatible ESLint major in place. - For this project,
npm run lintmust remain green before and after dependency upgrades; ifeslintandeslint-config-nextdiverge, prefer keeping Next.js tooling compatibility first. - Revisit ESLint major upgrades as a dedicated maintenance task after Next.js and
eslint-config-nexthave published compatible releases.
Dependency & security bots:
- Renovate is the only dependency bot on this repo (
renovate.jsonat the repo root). It handles both regular updates and security-related bumps (GitHub Security Advisories), asconfig:recommendedkeeps it active across all manifests. - Dependabot is disabled: no
.github/dependabot.ymlexists and GitHub's Automated security fixes setting is turned off, so Dependabot will not open PRs. - Security-sensitive dependency floors live in
dashboard/package.jsonoverrides(e.g.next,postcss,hono,@hono/node-server,sharp,fast-uri,body-parser). When a new advisory affects a transitive dependency, bump the matching override floor to the patched version and verify withnpm audit(target:found 0 vulnerabilities) +npm run lint+npm run build.
OwnPulse stores personal data (contacts, notes, finances) and business secrets (offers, rates). Baseline practices:
- Data access: the dashboard talks to Supabase only with the user session (
authenticatedrole); RLS is the single enforcement point — keep policies aligned withhardening_security_rls.sql. The extension uses the publishable key and is restricted to thecapture_contactRPC (whitelisted INSERT) + a minimalcontact_urlsview (no read of emails/phones/notes). - Multi-user: if more than one account will ever use the app, run
hardening_multi_user_rls.sql— every row gets auser_id, all tables are scoped toauth.uid(), and anonymous captures land as "unclaimed" contacts that the first editor claims. Current data is backfilled to the first created user. - No secret keys in the repo: only publishable keys appear in
extension/content.js; theservice_rolekey must never be committed, used in the browser, or put in the extension. - Env vars: copy
dashboard/.env.local.example→.env.local; never commit.env.local(already gitignored). - HTTP headers:
dashboard/next.config.tssets CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and disables theX-Powered-Bybanner. - Git hygiene: gitleaks pre-commit hook (
.pre-commit-config.yaml) blocks secret commits.
The /prospecting workspace is designed for a configurable daily outreach goal (10 distinct leads by default). The goal can be changed under Settings → General → Objectif quotidien de prospection. It prioritizes the existing contact statuses Warm, Interested, and Engaged as the warm-to-hot queue; it does not introduce a duplicate temperature field. contacts.notes, first_contact_date, acquisition_channel, and the existing tasks table keep their original meanings.
The workflow is explicit: open the available channel, contact the person, then click J’ai contacté cette personne and confirm the channel actually used. Available outreach channels are MP LinkedIn, e-mail, WhatsApp, SMS, appel, MP Instagram, Threads, and autre. The quick actions open LinkedIn, e-mail, phone, WhatsApp, or SMS when the corresponding contact data is available.
Run migration_contact_activities.sql once on an existing project. It adds the owner-scoped contact_activities history for the outreach channel, outcome, and short note, including the SMS channel constraint. A follow-up entered from the workspace creates an existing-style task. Multiple activities for the same lead on one day count as one distinct person toward the daily goal.
On the Supabase free plan, projects are paused after 7 days of inactivity. A zero-cost Cloudflare Worker (keepalive-worker/) keeps the project active every 6 hours via cron, fully independent of repo activity and GitHub.
Each run sends two pings (belt-and-suspenders):
- A real Postgres query —
GET /rest/v1/contact_urls?select=id&limit=1with the anon key: the unambiguous activity signal (REST API + Postgres engine), i.e. the most conservative guarantee against pausing. - The health endpoint fallback —
GET /auth/v1/healthwith the publishable key header (Supabase requires it): keeps the watcher green even if the table/view is renamed or its grants change.
Deploy (one time, ~3 min): paste each line separately:
cd keepalive-workernpx --yes wrangler@latest login— authorize your Cloudflare account.echo "https://<project-ref>.supabase.co" | npx --yes wrangler@latest secret put SUPABASE_URL— replace the URL (no trailing slash).echo "<publishable-anon-key>" | npx --yes wrangler@latest secret put SUPABASE_ANON_KEY— anon key from Supabase dashboard > API.npx --yes wrangler@latest deploy— deploys the worker and registers the cron trigger.
The echo "..." | form skips wrangler's interactive prompt, so the block is safe to copy-paste (inline # comments break interactive shells).
Verify: curl "https://ownpulse-supabase-keepalive.<your-subdomain>.workers.dev/ping" → OK. Scheduled runs are visible under Workers & Pages > ownpulse-supabase-keepalive > Logs.
Caveat: the ping view (default contact_urls, from hardening_security_rls.sql) must be exposed to anon with an explicit GRANT — new Supabase projects no longer expose public objects to the Data API by default (see AGENTS.md). If you need a different frequency, edit [triggers] crons in keepalive-worker/wrangler.toml and redeploy.
When the same person is captured twice (e.g. once via Instagram, once via LinkedIn), merge the duplicates from either place:
- Contact page: from the contact's detail page → Quick Actions → Fusionner des contacts (search the duplicate, then choose which record to keep).
- Leads table: on the home page, tick the checkboxes of the duplicate rows (2+), then Fusionner in the selection bar and choose the record to keep.
Run migration_merge_contacts.sql once on an existing project. It adds the merge_contacts(p_primary_id, p_duplicate_ids) RPC (SECURITY DEFINER, authenticated only):
- Field policy: the kept (primary) contact's non-empty values win; its empty fields are filled from the duplicate(s). Lists are unioned (case-insensitive dedup) and notes are appended with a
--- Fusionné depuis ... ---trace marker. - Related records: tasks, sales, and prospecting activities are re-attached to the kept contact; the duplicate row is deleted.
- Ownership: only contacts owned by the caller, or unclaimed rows (
user_idNULL, anonymous extension captures), can be merged. The kept contact becomes claimed (user_id = auth.uid()). - The merge runs in a single transaction: any validation error rolls everything back.
The dedup contact_urls view automatically stops listing the deleted duplicate's profile URLs, and the kept contact retains both profile URLs (LinkedIn + Instagram) so the extension will report Already in CRM for both.
- 📡 Capture: The Chrome extension scrapes the LinkedIn, Threads, or Instagram profile DOM securely.
- ⚡ Sync: Data is sent instantly to your private Supabase PostgreSQL instance.
- 🛰️ Pulse: Manage your relationships and pipeline stages via the OwnPulse dashboard.
IMPORTANT: READ THIS BEFORE USE. This project is for educational and personal use only. It is not affiliated with, authorized, maintained, sponsored, or endorsed by LinkedIn or its affiliates.
- Compliance: Using third-party extensions to modify platform behavior may violate Terms of Service of the respective platforms (LinkedIn, Threads, Instagram).
- Risk: Use this software at your own risk. The author is not responsible for any account warnings or suspensions.
- No Warranty: This software is provided "as is", without warranty of any kind.
Distributed under the BSD 3-Clause License. See LICENSE for more information. This license protects the author's name from being used for endorsement of derivative works while allowing full freedom for personal and commercial use.


