{"id":50838762,"url":"https://github.com/vimalyad/lurkr","last_synced_at":"2026-06-14T06:00:31.622Z","repository":{"id":364635235,"uuid":"1268664906","full_name":"vimalyad/lurkr","owner":"vimalyad","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-13T21:24:32.000Z","size":57,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T21:26:50.572Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vimalyad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T19:48:59.000Z","updated_at":"2026-06-13T21:24:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vimalyad/lurkr","commit_stats":null,"previous_names":["vimalyad/lurkr"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/vimalyad/lurkr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimalyad%2Flurkr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimalyad%2Flurkr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimalyad%2Flurkr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimalyad%2Flurkr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vimalyad","download_url":"https://codeload.github.com/vimalyad/lurkr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimalyad%2Flurkr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34310801,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-14T06:00:20.252Z","updated_at":"2026-06-14T06:00:31.592Z","avatar_url":"https://github.com/vimalyad.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/icon-only.png\" alt=\"Lurkr\" width=\"116\" height=\"116\" /\u003e\n\n# Lurkr\n\n**Always watching, never blinking — the intelligence team that never sleeps.**\n\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Vite-6-646CFF?logo=vite\u0026logoColor=white\" alt=\"Vite\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/React-19-61DAFB?logo=react\u0026logoColor=black\" alt=\"React\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Capacitor-7-119EFF?logo=capacitor\u0026logoColor=white\" alt=\"Capacitor\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Express-4-000000?logo=express\u0026logoColor=white\" alt=\"Express\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Neon-Postgres-00E599?logo=postgresql\u0026logoColor=white\" alt=\"Neon\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OpenRouter-LLM-8A63D2\" alt=\"OpenRouter\" /\u003e\n\u003c/p\u003e\n\n\u003c/div\u003e\n\n---\n\nLurkr is a multi-agent market intelligence tool. You describe **your own** startup or idea;\nLurkr finds the real competitors in that space, gathers live data on them, and a team of AI\nagents turns it into a personalized brief — the single biggest **threat** and **opportunity**\nfor your product, each with a recommended action.\n\n## How it works\n\n```\nGuided intake:  Product → Key features (optional) → Confirm\n        │\n        ▼\n  Discovery AI ──────────► finds the real competitors in your space\n        │\n        ▼\n  Gather (live data) ────► web search (Tavily) + news (Google News) per competitor\n        │\n        ▼\n  Marketing · Product · Sales AI  (run in parallel, grounded in the live signals)\n        │\n        ▼\n  Strategy AI ───────────► personalized brief: biggest THREAT + OPPORTUNITY + watch items\n                           (persisted to Postgres / Neon)\n```\n\nThe three analysts run in parallel; their findings feed the Strategy agent, which synthesizes\nthe brief for *your* product.\n\n## Stack\n\nLurkr is split into a static frontend (packaged as an Android APK via Capacitor) and an\nExpress backend that holds the API keys (runs locally in dev, hosted on Render in production —\nnever shipped inside the APK):\n\n- **Frontend:** Vite + React + Tailwind CSS v4 → static `dist/` (→ Capacitor APK)\n- **Backend:** Express (`server/`), reuses `src/lib/*`; hosted on **Render**\n- **OpenRouter** (OpenAI-compatible) — LLM calls (analysts on a fast model, Strategy/Discovery on a stronger one)\n- **Tavily** + **Google News RSS** — live competitor signals\n- **Neon** (Postgres) — accounts + every user's ideas and cached analyses\n- **Auth** — roll-our-own on Neon: scrypt passwords + HS256 JWTs (zero backend deps); Google sign-in via GIS on the web and a native account picker (`@capgo/capacitor-social-login`) in the APK\n- **Daily refresh** — a GitHub Actions schedule re-runs opted-in ideas at 04:00 IST\n\n## Getting started (local dev)\n\n```bash\nnpm install\ncp .env.example .env.local   # then fill in your keys (used by the backend)\n\n# two processes:\nnpm run server   # Express backend on :8787 (holds the keys)\nnpm run dev      # Vite dev on :5173 (proxies /api → :8787)\n```\n\nOpen http://localhost:5173 (or `http://\u003cLAN_IP\u003e:5173` from your phone), describe your\nidea, **Find my competitors**, then **Run intelligence sweep**.\n\nEnv vars (see `.env.example`, read by the backend):\n\n| Var | Used for | Get one |\n|---|---|---|\n| `OPENROUTER_API_KEY` | all LLM calls | openrouter.ai |\n| `TAVILY_API_KEY` | live web-search signals | tavily.com (free tier) |\n| `DATABASE_URL` | accounts + saved ideas (**required**) | neon.tech (free Postgres) |\n| `JWT_SECRET` | signing session tokens | `openssl rand -hex 32` |\n| `GOOGLE_CLIENT_ID` | Google sign-in | Google Cloud Console (OAuth web client) |\n| `CRON_SECRET` | locks the daily-refresh endpoint | `openssl rand -hex 32` (also a GH secret) |\n\n\u003e Email verification + password reset are **disabled for now** (they need a verified\n\u003e sending domain — we need to buy one first). Email/password signup only enforces a\n\u003e unique email until then; no email provider is configured.\n\nFrontend build-time vars (set in CI / `vite build` env, not in `.env.local`):\n`VITE_API_URL` (backend base URL) and `VITE_GOOGLE_CLIENT_ID` (Google button).\n\n## Project layout\n\n- `index.html`, `src/main.jsx`, `src/App.jsx` — the Vite frontend (guided intake + dashboard)\n- `src/index.css` — Tailwind v4 + the design system\n- `src/App.jsx` — auth gate (session check, verify/reset links) → `AuthScreen` or `Dashboard`\n- `src/auth/AuthScreen.jsx` — sign in / sign up + Google button\n- `src/Dashboard.jsx` — the sweep UI, \"My Ideas\" view, daily-refresh toggle\n- `server/index.mjs` — Express backend: auth (`/api/auth/*`, `/api/me`), pipeline (`/api/discover`, `/api/gather`, `/api/agent/:id`, `/api/strategy`), ideas (`/api/ideas*`), `/api/cron/daily-refresh`, `/health`\n- `src/lib/auth.js` — scrypt + JWT + Google verify\n- `src/lib/db.js` — Neon: users, ideas, analyses (cache), usage_events\n- `src/lib/pipeline.js` — server-side full sweep (used by the daily cron)\n- `src/lib/agents.js` / `openrouter.js` / `gather.js` / `sources/` — agents + signals\n- `.github/workflows/daily-refresh.yml` — 04:00 IST scheduled refresh trigger\n- `scripts/optimize-prompts.mjs` — offline prompt-optimization harness\n- `render.yaml` — Render Blueprint for the backend\n- `android/` — Capacitor Android project\n\n## Backend hosting (Render)\n\nThe backend deploys to Render from `render.yaml` (Blueprint). Set all the env vars from the\ntable above in the Render dashboard. It auto-deploys on push to `main`.\n\n## Accounts, persistence \u0026 daily refresh\n\n- **Hard auth gate.** No session → sign in (Google, or email + password). Sessions are\n  HS256 JWTs in `localStorage`. *Email verification + password reset are deferred until a\n  sending domain is purchased; for now signup only requires a unique email.*\n- **Per-user ideas.** Every sweep is saved under the user's idea (repeat searches of the\n  same idea append a new analysis). \"My Ideas\" lists them; opening one serves the latest\n  cached (stale-while-present) analysis. A cache miss just runs a live sweep, as before.\n- **Daily refresh.** Toggle it per idea. `daily-refresh.yml` fires at 22:30 UTC (04:00 IST),\n  calls the `CRON_SECRET`-protected `/api/cron/daily-refresh`, which re-runs the full sweep\n  for every opted-in idea and caches the result. `CRON_SECRET` must match in **both** the\n  GitHub repo secrets and the Render env.\n- **Usage** is logged to `usage_events` (no billing yet — groundwork for usage-based pricing).\n\n### Google sign-in setup\n\nWe use the **Google Identity Services ID-token flow**, so **no client secret is involved\nanywhere** — only the OAuth **client ID**.\n\n1. Google Cloud Console → APIs \u0026 Services → Credentials → **Create OAuth client ID** →\n   *Web application*.\n2. Add **Authorized JavaScript origins**:\n   - `https://vimalyad.github.io` (the hosted frontend)\n   - `http://localhost:5173` (local dev)\n3. Use the **Client ID** in two places (the same value):\n   - `GOOGLE_CLIENT_ID` — backend env (Render). Used only to check the ID-token audience.\n   - `VITE_GOOGLE_CLIENT_ID` — build-time env (GitHub secret). Renders the sign-in button.\n\nThe client **secret is not used** and should not be added to Render, GitHub, or `.env` — the\nbrowser receives a signed ID token (`response.credential`) which the backend verifies against\nGoogle's `tokeninfo` endpoint. A secret would only be needed for the server-side\nauthorization-code flow, which we don't use.\n\n**Native (APK) Google sign-in.** Google blocks its web sign-in inside Android WebViews, so the\napp uses native sign-in via `@capgo/capacitor-social-login` (the Android account picker); the web\nbuild keeps the GIS button. This needs a **second OAuth client of type _Android_** in Google\nCloud, registered with the package name and the release signing-cert SHA-1:\n- Package: `com.lurkr.app`\n- SHA-1 (release keystore): `openssl pkcs12 -in lurkr-release.p12 -nokeys | openssl x509 -noout -fingerprint -sha1`\n\nThe **Web** client ID stays the single source of truth — it is the plugin's `webClientId`, and the\nbackend still verifies every token's audience against it (`GOOGLE_CLIENT_ID`). The Android client\nexists only so Google issues a token to the app; its own ID isn't referenced in code. The native\nplugin requires `minSdkVersion 24`.\n\n## Android app — automated, build-once\n\nThe APK is a thin shell: it loads the **live frontend from GitHub Pages**\n(`capacitor.config.json` → `server.url`) rather than bundling its own copy. Two GitHub\nActions pipelines run on every push/merge to `main`:\n\n1. **`deploy.yml`** — builds the frontend (with the Render API URL baked in) and publishes it\n   to **GitHub Pages** (`https://vimalyad.github.io/lurkr/`). Installed apps pick up the change\n   on next launch — **no reinstall**.\n2. **`release-apk.yml`** — builds a **signed release APK** and attaches it to a\n   **GitHub Release** (`/releases`), so there's always a fresh installable download. The\n   version bumps automatically (`1.0.\u003crun#\u003e`); installs update in place.\n\nSo day-to-day you just `git push` — the app updates itself. You only ever build/install an\nAPK by hand if you want to (CI does it for you).\n\n**Install for users:** open the repo's **Releases** page → download the latest\n`lurkr-vX.Y.Z.apk` → on the phone, allow \"install unknown apps\" → open it.\n\n### Signing key (one-time setup, already done)\n\nRelease builds are signed with a PKCS12 keystore stored in repo **Secrets**\n(`ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`,\n`ANDROID_KEY_PASSWORD`). A local backup lives at `lurkr-release.p12` (+ its password file),\ngitignored. **Back this up somewhere safe** — if the key is lost, you can't ship in-place\nupdates to already-installed apps. Custom launcher icon via\n`npx @capacitor/assets generate` from `assets/icon-only.png`.\n\n## Roadmap\n\n- **Scheduling (GitHub Actions)** — automatic re-sweeps, so it's always watching\n- **Alerts (Resend)** — email when a new high-urgency threat appears\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvimalyad%2Flurkr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvimalyad%2Flurkr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvimalyad%2Flurkr/lists"}