common/ had become the backend's library: 30 of its 41 modules were imported by nothing in the frontend, so the name no longer said what belonged there. It now holds only what the frontend or the build imports too; backend-only code moved to server/, flat except for datasets/ (the dataset engine and its readers). The *-db/ dataset directories stay in common/: self-hosting docs and Docker volumes mount those paths. frontend/utils/ keeps its app-wide modules at the root and gains ip/ (IP / ASN shaping and validation), report/ (the report pipeline) and features/ (the pure layer of a single feature). Paths only — no logic changes. The Dockerfile copies server/, and the AGENTS.md files say where a new module goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.2 KiB
AGENTS.md
Single source of truth for anyone — human or AI — contributing to MyIP. Area-specific details: @frontend/AGENTS.md (Vue SPA) · @api/AGENTS.md (Express API).
Local skill discovery
Before choosing a workflow for a project task, check the repository-root
.skills/ directory. It contains local skills at .skills/<name>/SKILL.md
and may not appear in the agent's automatically supplied skill list.
- If the directory exists, discover its skill files with
rg --files --hidden --no-ignore .skills -g SKILL.md(it is gitignored). - Read each skill's frontmatter
nameanddescription; read the fullSKILL.mdfor any skill that applies, then follow its workflow. - Complete this discovery before choosing a fallback tool or telling the user that a project capability or service is unavailable. Load supporting files only when the selected skill requires them.
Overview
MyIP (IPCheck.ing) is an open-source IP toolbox: IP lookup, connectivity tests, WebRTC / DNS-leak detection, speed test, MTR, Whois, ASN profile, security checklist, browser fingerprint, anonymity checks, persona check, IP calculator, and more. Single repo, two halves: a Vue 3 SPA front-end and an Express 5 back-end API.
Stack
| Layer | Technology |
|---|---|
| Frontend | Vue 3 (<script setup>) · Pinia · vue-router (HTML5 history) · vue-i18n (locales registered in common/locale-registry.js) |
| Build | Vite + @vitejs/plugin-vue; Tailwind CSS v4 + tw-animate-css |
| UI | shadcn-vue copy-in primitives (reka-ui) · lucide icons · circle-flags via @iconify/vue · vaul-vue drawer · vue-sonner toast |
| Backend | Express 5 |
| Logger | pino singleton at server/logger.js (+ pino-http, opt-in) |
| Auth | Firebase Auth (optional, env-gated) |
| Error monitoring | Sentry — optional & env-gated on both halves: @sentry/vue (no VITE_SENTRY_DSN_FRONTEND, no Sentry in the build — see frontend/AGENTS.md) + @sentry/node (no SENTRY_DSN_BACKEND, never loaded — see api/AGENTS.md) |
| PWA | manifest.webmanifest only — installable but online-only, no service worker |
| Tests | Node built-in test runner (node --test) |
| Runtime libs | chart.js · chartjs-chart-geo · @cloudflare/speedtest · maxmind · whoiser · thumbmarkjs · ua-parser-js · detect-gpu · @vueuse/core |
Commands
| Command | What it does |
|---|---|
pnpm dev |
Vite + backend (nodemon) together |
pnpm build |
Front-end production build |
pnpm preview |
Vite preview of the build output |
pnpm start |
Built front-end + backend |
pnpm test |
Run all tests/*.test.js specs |
pnpm check |
test + build — the pre-commit self-check |
pnpm only (pinned via packageManager); pnpm-lock.yaml is committed and
pnpm-workspace.yaml holds the allowBuilds install-script approvals. Never
use npm / yarn — they'd produce a competing lockfile.
Project layout
.
├── AGENTS.md / CLAUDE.md ← this file + Claude pointer to it
├── frontend/ ← Vue 3 SPA (see frontend/AGENTS.md)
├── api/ ← Express handlers (see api/AGENTS.md)
├── server/ ← backend-only code (logger / guards / …;
│ datasets/ = the offline dataset engine)
├── common/ ← code the frontend or build imports too
│ (valid-ip / fetch-with-timeout / …) + the
│ dataset dirs (`*-db/`, a self-host mount point)
├── tests/ ← Node test runner specs
├── backend-server.js ← Express app (default port 11966)
├── sentry-instrument.js ← backend Sentry bootstrap via `node --import`;
│ no-op without SENTRY_DSN_BACKEND
├── frontend-server.js ← static server for `pnpm start` (+ SPA fallback)
├── ecosystem.config.cjs ← pm2 definitions (carries the `--import` flag)
├── index.html ← Vite entry
├── vite.config.js / jsconfig.json (alias @ → frontend/) / package.json
Conventions
Language
- JavaScript only. New files are
.js/.vue; nolang="ts", no TypeScript migration. - English by default for code comments, commit messages, and AGENTS.md. Locale packs obviously carry their own language; planning docs are free.
Functions
- New functions use
constarrow syntax (const fn = async () => {}), notfunctiondeclarations. Object methods keep shorthand. Arrow consts aren't hoisted — declare before use. Applies to new / rewritten code only; don't mass-convert existing declarations.
Comments
- Every new file opens with a header comment stating its purpose —
except
frontend/components/ui/, which holds shadcn-vue CLI output kept verbatim so it can be re-synced (see frontend/AGENTS.md). - Large templates / functions carry block comments per meaningful region.
- Comments describe the code as it is now — no changelog narration
(
previously…,…fixes that); git history covers the past. A comment stays shorter than the code it explains.
i18n coverage
- Copy-surfacing features land in every
fulllocale in the same change — includingfrontend/data/changelog.jsonentries (tests/changelog.test.jsandtests/locale-packs.test.jsenforce it againsten).betalocales are free to lag: their gaps resolve down the fallback chain incommon/locale-registry.js, which is also where a new language is registered. - Contributor-facing walkthrough: TRANSLATING.md — keep it in
step when the registry, the pack gate or
pnpm i18n-statuschanges.
Logging (backend)
- Always the shared logger (
server/logger.js) in backend files; bareconsole.*is banned there (frontend keeps usingconsole.*). - Pino first-arg-is-context:
logger.error({ err, ip }, 'short message'). - Env knobs:
LOG_LEVEL(default info),LOG_FORMAT=jsonfor shippers,LOG_HTTP=trueto mountpino-httpon/api(off by default; handlers never log "received request" lines themselves). NoNODE_ENVanywhere. - Startup-only lines lead with an emoji (🚀 listening · 📦 ready · 📥 downloading · 🛡️ security · 🐢 throttling · 🗓️ schedule · ⚠️ recoverable · ❌ failure); per-request logs stay plain.
Testing
- Any non-visual logic exercisable without a network call — pure functions,
composables with mockable inputs, transforms, validators — ships with a
spec in
tests/, in the same change (don't defer; update affected tests when behavior shifts). - UI rendering, real network behavior, and browser APIs are out of scope.
pnpm checkmust be green before handing off.
Security & Boundaries
Access control and timeouts live in shared middleware, not handlers (details in @api/AGENTS.md):
requireRefereris global on/api/*;requirePublicIP()per-route — handlers never repeat these checks.- Every upstream HTTP call goes through
fetchUpstream(common/fetch-with-timeout.js, 8s timeout). Never a barefetch()inapi/.
Workflow
- Branch discipline —
devin,devout.mainonly moves via dev → main PRs. From a worktree, fast-forward dev withgit push . HEAD:dev(repo hasreceive.denyCurrentBranch=updateInstead), notgit update-ref. - No commits without explicit user approval — AI edits → user reviews → user tests → user says "commit". Even with tests green, visual changes need user eyes before landing.
- One concern per commit, message style per
git log(Feat(xxx):/Fix(ui):/Refactor(xxx):/Style:/Chore:), AI adds itself as co-author. - Self-test before handing off (
pnpm check); if a change is visual and headless-unverifiable, say so explicitly. - On every commit, scan AGENTS.md (root + relevant sub-file) for staleness — conventions, renames, flipped rules, dead examples get fixed in the same commit. Doc drift is this file's main failure mode.
If local-context.md exists in the workspace root, read it too — it lists machine-local Knowledge Hub paths (not in git).