lockin-mcp 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -45
- package/dist/blocker.js +2 -2
- package/dist/blocker.js.map +1 -1
- package/dist/brand.d.ts +2 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/hosts-manager.d.ts.map +1 -1
- package/dist/hosts-manager.js +24 -11
- package/dist/hosts-manager.js.map +1 -1
- package/dist/hosts-manager.test.js +22 -3
- package/dist/hosts-manager.test.js.map +1 -1
- package/dist/index.js +37 -12
- package/dist/index.js.map +1 -1
- package/dist/install.js +3 -3
- package/dist/install.js.map +1 -1
- package/dist/installer/agent-instructions.d.ts +15 -1
- package/dist/installer/agent-instructions.d.ts.map +1 -1
- package/dist/installer/agent-instructions.js +29 -7
- package/dist/installer/agent-instructions.js.map +1 -1
- package/dist/installer/agents.d.ts.map +1 -1
- package/dist/installer/agents.js +70 -79
- package/dist/installer/agents.js.map +1 -1
- package/dist/installer/claude-desktop-config.d.ts +19 -0
- package/dist/installer/claude-desktop-config.d.ts.map +1 -0
- package/dist/installer/claude-desktop-config.js +70 -0
- package/dist/installer/claude-desktop-config.js.map +1 -0
- package/dist/installer/claude-desktop-config.test.d.ts +2 -0
- package/dist/installer/claude-desktop-config.test.d.ts.map +1 -0
- package/dist/installer/claude-desktop-config.test.js +49 -0
- package/dist/installer/claude-desktop-config.test.js.map +1 -0
- package/dist/installer/data-dir.d.ts +7 -0
- package/dist/installer/data-dir.d.ts.map +1 -0
- package/dist/installer/data-dir.js +38 -0
- package/dist/installer/data-dir.js.map +1 -0
- package/dist/installer/data-dir.test.d.ts +2 -0
- package/dist/installer/data-dir.test.d.ts.map +1 -0
- package/dist/installer/data-dir.test.js +59 -0
- package/dist/installer/data-dir.test.js.map +1 -0
- package/dist/installer/install-log.d.ts +14 -0
- package/dist/installer/install-log.d.ts.map +1 -0
- package/dist/installer/install-log.js +100 -0
- package/dist/installer/install-log.js.map +1 -0
- package/dist/installer/platform.d.ts.map +1 -1
- package/dist/installer/platform.js +2 -2
- package/dist/installer/platform.js.map +1 -1
- package/dist/installer/poke.d.ts.map +1 -1
- package/dist/installer/poke.js +5 -13
- package/dist/installer/poke.js.map +1 -1
- package/dist/installer/port.d.ts.map +1 -1
- package/dist/installer/port.js +5 -4
- package/dist/installer/port.js.map +1 -1
- package/dist/installer/relay-provision.d.ts.map +1 -1
- package/dist/installer/relay-provision.js +62 -25
- package/dist/installer/relay-provision.js.map +1 -1
- package/dist/installer/run.d.ts.map +1 -1
- package/dist/installer/run.js +37 -34
- package/dist/installer/run.js.map +1 -1
- package/dist/installer/tui.d.ts.map +1 -1
- package/dist/installer/tui.js +5 -3
- package/dist/installer/tui.js.map +1 -1
- package/dist/installer/tunnel.d.ts.map +1 -1
- package/dist/installer/tunnel.js +7 -2
- package/dist/installer/tunnel.js.map +1 -1
- package/dist/installer/user-errors.d.ts +8 -0
- package/dist/installer/user-errors.d.ts.map +1 -0
- package/dist/installer/user-errors.js +61 -0
- package/dist/installer/user-errors.js.map +1 -0
- package/dist/installer/user-errors.test.d.ts +2 -0
- package/dist/installer/user-errors.test.d.ts.map +1 -0
- package/dist/installer/user-errors.test.js +15 -0
- package/dist/installer/user-errors.test.js.map +1 -0
- package/dist/local-http.d.ts +32 -0
- package/dist/local-http.d.ts.map +1 -0
- package/dist/local-http.js +140 -0
- package/dist/local-http.js.map +1 -0
- package/dist/local-http.test.d.ts +2 -0
- package/dist/local-http.test.d.ts.map +1 -0
- package/dist/local-http.test.js +13 -0
- package/dist/local-http.test.js.map +1 -0
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js +26 -23
- package/dist/relay-client.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -4
- package/dist/state.js.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -2
- package/dist/types.js.map +1 -1
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that b
|
|
|
9
9
|
| `block_domains` | Block a list of domains (e.g. `youtube.com`, `x.com`, `tiktok.com`) |
|
|
10
10
|
| `unblock_domains` | Permanently unblock specific domains |
|
|
11
11
|
| `temporarily_unblock_domains` | Allow access for a limited time, then auto re-block |
|
|
12
|
-
| `enter_focus_mode` | Block common distraction sites in one action |
|
|
12
|
+
| `enter_focus_mode` | Block common distraction sites in one action (YouTube, X, TikTok, Instagram, Reddit, Facebook, Netflix, Twitch) |
|
|
13
13
|
| `get_block_status` | Show what is blocked, temporarily allowed, and system readiness |
|
|
14
14
|
|
|
15
15
|
Blocking redirects domains to `127.0.0.1` and `::1`, including `www.` variants.
|
|
16
16
|
|
|
17
|
+
**Pro license required:** `block_domains`, `unblock_domains`, `temporarily_unblock_domains`, and `enter_focus_mode` require an active Pro license. `get_block_status` is read-only and works without Pro. For local development only, set `MDB_SKIP_LICENSE_CHECK=1`.
|
|
18
|
+
|
|
17
19
|
## Requirements
|
|
18
20
|
|
|
19
21
|
- **macOS / Linux / Windows** (hosts-based blocking; macOS uses `dscacheutil` for DNS flush)
|
|
@@ -49,6 +51,9 @@ npm run install:local
|
|
|
49
51
|
| Landing page (`/`) | Features, how-it-works, pricing |
|
|
50
52
|
| Device auth (`/device`) | Browser login for CLI OAuth device flow |
|
|
51
53
|
| Dashboard (`/dashboard`) | License keys, Pro status, install instructions |
|
|
54
|
+
| Report (`/report`) | Bug reports and feature requests (rate-limited; not indexed) |
|
|
55
|
+
| MCP OAuth consent (`/oauth/consent`) | Browser approval for ChatGPT / Claude web MCP connectors |
|
|
56
|
+
| Checkout (`/checkout`) | Stripe checkout (requires verified email) |
|
|
52
57
|
| API (`/api/v1/*`) | License verify, OAuth device flow, token status |
|
|
53
58
|
| Stripe webhook | Pro upgrade + license key generation |
|
|
54
59
|
|
|
@@ -60,6 +65,14 @@ npm run install:local
|
|
|
60
65
|
4. Installer polls `POST /api/v1/oauth/token` → receives `access_token`
|
|
61
66
|
5. Daemon verifies token via `POST /api/v1/license/status` on startup and hourly
|
|
62
67
|
|
|
68
|
+
### MCP OAuth (ChatGPT, Claude web)
|
|
69
|
+
|
|
70
|
+
1. MCP client registers via DCR at `POST /api/v1/oauth/register` (proxied from relay at `POST /register`)
|
|
71
|
+
2. Client discovers metadata from `/.well-known/oauth-authorization-server` (web app and relay)
|
|
72
|
+
3. User authorizes at `/api/v1/oauth/authorize` → browser consent at `/oauth/consent`
|
|
73
|
+
4. Client exchanges code at `POST /api/v1/oauth/token`
|
|
74
|
+
5. MCP requests to relay use OAuth access tokens or the device `mdb_*` API key
|
|
75
|
+
|
|
63
76
|
### Environment variables
|
|
64
77
|
|
|
65
78
|
See [`.env.example`](.env.example) for all configuration options.
|
|
@@ -76,14 +89,37 @@ openssl rand -base64 32 # MDB_SERVER_SECRET
|
|
|
76
89
|
openssl rand -base64 32 # REGISTRATION_SECRET (relay)
|
|
77
90
|
```
|
|
78
91
|
|
|
79
|
-
Set
|
|
92
|
+
Set **Vercel** and **Convex production** env vars separately in each dashboard — do **not** copy `MDB_SERVER_SECRET` from your laptop with a sync script (local dev secrets are different from production).
|
|
93
|
+
|
|
94
|
+
| Secret | Vercel | Convex prod |
|
|
95
|
+
|--------|--------|-------------|
|
|
96
|
+
| `MDB_SERVER_SECRET` | Yes — same value on both | Yes — `npx convex env set MDB_SERVER_SECRET '…' --prod` once |
|
|
97
|
+
| `MDB_JWT_SECRET` | No | Yes — Convex only |
|
|
98
|
+
| `STRIPE_*` | Yes | No |
|
|
80
99
|
|
|
81
100
|
#### 2. Convex production
|
|
82
101
|
|
|
83
102
|
```bash
|
|
84
103
|
cd web
|
|
85
104
|
npx convex deploy # production only — not convex dev
|
|
86
|
-
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Set env vars in the [Convex dashboard](https://dashboard.convex.dev) → **Production** → Settings → Environment Variables. **Do not** run `./scripts/sync-convex-env.sh` for production — it only targets your dev deployment and `.env.local` often has `localhost` URLs.
|
|
108
|
+
|
|
109
|
+
| Convex prod variable | Value |
|
|
110
|
+
|---------------------|--------|
|
|
111
|
+
| `MDB_APP_URL` | `https://www.lockinmcp.com` |
|
|
112
|
+
| `MDB_SERVER_SECRET` | Same string as Vercel |
|
|
113
|
+
| `MDB_JWT_SECRET` | Production JWT secret (Convex only) |
|
|
114
|
+
| `GOOGLE_*` / `GITHUB_*` | Production OAuth app credentials |
|
|
115
|
+
| `RESEND_API_KEY` / `RESEND_FROM_EMAIL` | Production Resend |
|
|
116
|
+
| `MDB_RELAY_URL` / `MDB_RELAY_PUBLIC_URL` | `https://relay.lockinmcp.com` |
|
|
117
|
+
| `REGISTRATION_SECRET` | Same as relay Worker |
|
|
118
|
+
|
|
119
|
+
One-off CLI (paste production values, not from `.env.local`):
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cd web && npx convex env set MDB_APP_URL 'https://www.lockinmcp.com' --prod
|
|
87
123
|
```
|
|
88
124
|
|
|
89
125
|
#### 3. Stripe live mode
|
|
@@ -94,30 +130,52 @@ In [Stripe Dashboard](https://dashboard.stripe.com) (toggle **Live**):
|
|
|
94
130
|
|----------|-----------------|
|
|
95
131
|
| `STRIPE_SECRET_KEY` | Developers → API keys → `sk_live_...` |
|
|
96
132
|
| `STRIPE_PRICE_ID` | Products → one-time $9.99 price → `price_...` |
|
|
97
|
-
| `STRIPE_WEBHOOK_SECRET` | Developers → Webhooks → endpoint `https://lockinmcp.com/api/stripe/webhook` → `whsec_...` |
|
|
133
|
+
| `STRIPE_WEBHOOK_SECRET` | Developers → Webhooks → endpoint `https://www.lockinmcp.com/api/stripe/webhook` → `whsec_...` |
|
|
98
134
|
| `STRIPE_PROMOTION_CODE_ID` | Optional — locks one promo at checkout; leave unset so customers can enter any code (e.g. `LOCKIN50`) |
|
|
99
135
|
|
|
100
|
-
Webhook events: **`checkout.session.completed`**
|
|
136
|
+
Webhook events: **`checkout.session.completed`**, **`checkout.session.created`**, **`checkout.session.expired`** (follow-up scheduling backup)
|
|
137
|
+
|
|
138
|
+
#### Abandoned checkout follow-up (24h email)
|
|
139
|
+
|
|
140
|
+
When a signed-in user starts Stripe Checkout but does not pay, a follow-up email is scheduled via Convex (`ctx.scheduler.runAfter`). After deploy:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd web && npx convex deploy
|
|
144
|
+
./scripts/backfill-oauth-email-verified.sh --prod # one-time OAuth emailVerified backfill
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Verify production:**
|
|
148
|
+
|
|
149
|
+
1. Convex `MDB_APP_URL` is `https://www.lockinmcp.com` (not `localhost`).
|
|
150
|
+
2. Vercel `MDB_SERVER_SECRET` === Convex `MDB_SERVER_SECRET`.
|
|
151
|
+
2. Stripe webhook enables `checkout.session.created` and `checkout.session.expired`.
|
|
152
|
+
3. Preview email: `./scripts/send-abandoned-checkout-preview.sh you@example.com`
|
|
153
|
+
4. Dev smoke test (60s delay): `npx convex env set CHECKOUT_FOLLOW_UP_DELAY_MS 60000` then `./scripts/test-abandoned-checkout-schedule.sh <userId>`
|
|
154
|
+
5. Inspect jobs: `./scripts/list-checkout-follow-ups.sh scheduled`
|
|
155
|
+
|
|
156
|
+
Optional Convex env: `CHECKOUT_FOLLOW_UP_DELAY_MS` (default `86400000` = 24h).
|
|
101
157
|
|
|
102
158
|
#### 4. OAuth (production callbacks)
|
|
103
159
|
|
|
104
160
|
| Provider | Redirect URI |
|
|
105
161
|
|----------|--------------|
|
|
106
|
-
| Google | `https://lockinmcp.com/api/auth/oauth/google/callback` |
|
|
107
|
-
| GitHub | `https://lockinmcp.com/api/auth/oauth/github/callback` |
|
|
162
|
+
| Google | `https://www.lockinmcp.com/api/auth/oauth/google/callback` |
|
|
163
|
+
| GitHub | `https://www.lockinmcp.com/api/auth/oauth/github/callback` |
|
|
108
164
|
|
|
109
|
-
Add `GOOGLE_*` / `GITHUB_*` to
|
|
165
|
+
Add `GOOGLE_*` / `GITHUB_*` to Vercel and Convex production dashboards (not dev keys from `.env.local`).
|
|
110
166
|
|
|
111
167
|
#### 5. App URLs (hosting + Convex)
|
|
112
168
|
|
|
113
169
|
```bash
|
|
114
|
-
MDB_APP_URL=https://lockinmcp.com
|
|
115
|
-
NEXT_PUBLIC_APP_URL=https://lockinmcp.com
|
|
116
|
-
MDB_LICENSE_API_URL=https://lockinmcp.com/api/v1
|
|
170
|
+
MDB_APP_URL=https://www.lockinmcp.com
|
|
171
|
+
NEXT_PUBLIC_APP_URL=https://www.lockinmcp.com
|
|
172
|
+
MDB_LICENSE_API_URL=https://www.lockinmcp.com/api/v1
|
|
117
173
|
MDB_RELAY_PUBLIC_URL=https://relay.lockinmcp.com
|
|
118
174
|
MDB_RELAY_URL=https://relay.lockinmcp.com
|
|
119
175
|
```
|
|
120
176
|
|
|
177
|
+
The apex domain (`lockinmcp.com`) redirects to `www` in production.
|
|
178
|
+
|
|
121
179
|
#### 6. Deploy web app
|
|
122
180
|
|
|
123
181
|
Deploy the `web/` Next.js app to your host (Vercel, etc.) with all env vars from `.env.example`.
|
|
@@ -126,7 +184,7 @@ Deploy the `web/` Next.js app to your host (Vercel, etc.) with all env vars from
|
|
|
126
184
|
|
|
127
185
|
1. Sign up → verify email → checkout with a real card (or Stripe test in test mode first)
|
|
128
186
|
2. Confirm `/dashboard` shows Pro + license key
|
|
129
|
-
3. Run installer: `curl -fsSL https://lockinmcp.com/install | bash`
|
|
187
|
+
3. Run installer: `curl -fsSL https://www.lockinmcp.com/install | bash`
|
|
130
188
|
4. Approve device at `/device` → confirm MCP tunnel provisions
|
|
131
189
|
|
|
132
190
|
---
|
|
@@ -138,23 +196,31 @@ The fastest way to set up blocking, tunneling, and Poke integration:
|
|
|
138
196
|
### macOS / Linux (curl)
|
|
139
197
|
|
|
140
198
|
```bash
|
|
141
|
-
curl -fsSL https://raw.githubusercontent.com/Kiog-Aser/
|
|
199
|
+
curl -fsSL https://raw.githubusercontent.com/Kiog-Aser/LockIn/main/install.sh | bash
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Or use the hosted installer (recommended):
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
curl -fsSL https://www.lockinmcp.com/install | bash
|
|
142
206
|
```
|
|
143
207
|
|
|
144
208
|
### Windows (PowerShell)
|
|
145
209
|
|
|
146
210
|
```powershell
|
|
147
|
-
iwr -useb https://raw.githubusercontent.com/Kiog-Aser/
|
|
211
|
+
iwr -useb https://raw.githubusercontent.com/Kiog-Aser/LockIn/main/install.ps1 | iex
|
|
148
212
|
```
|
|
149
213
|
|
|
150
214
|
### npm
|
|
151
215
|
|
|
152
216
|
```bash
|
|
153
|
-
npx -y lockin-mcp install
|
|
217
|
+
npx -y lockin-mcp@1.0.4 install
|
|
154
218
|
# or from a local clone:
|
|
155
219
|
npm run install:local
|
|
156
220
|
```
|
|
157
221
|
|
|
222
|
+
The npm package `lockin-mcp` (v1.0.4) replaces the legacy `mac-distraction-blocker-mcp` name. Legacy bin aliases (`mac-distraction-blocker-mcp`, `mdb-install`) still work.
|
|
223
|
+
|
|
158
224
|
### What the installer does
|
|
159
225
|
|
|
160
226
|
1. **Detects your platform** (macOS, Linux, or Windows)
|
|
@@ -163,7 +229,7 @@ npm run install:local
|
|
|
163
229
|
4. **Applies initial blocks** to `/etc/hosts`
|
|
164
230
|
5. **Starts the MCP HTTP server** locally with an outbound relay connection (background LaunchAgent on macOS)
|
|
165
231
|
6. **Provisions a stable relay URL** (`https://relay.lockinmcp.com/device/{id}/mcp`) so Poke, Claude, and ChatGPT can reach your Mac
|
|
166
|
-
7. **Connects your AI agent** — Poke
|
|
232
|
+
7. **Connects your AI agent** — Poke and Claude Desktop use your API key; Claude (web) and ChatGPT use OAuth via lockinmcp.com
|
|
167
233
|
|
|
168
234
|
### Installer flags (non-interactive)
|
|
169
235
|
|
|
@@ -183,22 +249,23 @@ npx -y lockin-mcp install --license-key lockin_pro_xxx --sites all --poke-recipe
|
|
|
183
249
|
| `--poke-recipe` | Generate distraction-coach recipe URL |
|
|
184
250
|
| `--mcp-auth-token <token>` | Custom bearer token for tunneled MCP |
|
|
185
251
|
|
|
186
|
-
Setup details are saved to `~/.
|
|
252
|
+
Setup details are saved to `~/.lockin/setup-manifest.json`.
|
|
187
253
|
|
|
188
254
|
### Pro licensing ($9.99 lifetime — 50% off $19.99)
|
|
189
255
|
|
|
190
|
-
- **Purchase**:
|
|
256
|
+
- **Purchase**: Create an account at `/login` (email or Google/GitHub; `/signup` redirects here), verify email, pay via Stripe
|
|
191
257
|
- **License key**: Available on dashboard after purchase; pass `--license-key` to installer
|
|
192
258
|
- **Browser login**: Default installer flow — OAuth device authorization in your browser
|
|
193
259
|
- **Auth**: Google and GitHub OAuth supported; email/password requires verification before checkout
|
|
260
|
+
- **Support:** Bug reports and feature requests at `/report` (also linked in the site footer)
|
|
194
261
|
|
|
195
262
|
---
|
|
196
263
|
|
|
197
264
|
## Quick Start (manual)
|
|
198
265
|
|
|
199
266
|
```bash
|
|
200
|
-
git clone
|
|
201
|
-
cd
|
|
267
|
+
git clone https://github.com/Kiog-Aser/LockIn
|
|
268
|
+
cd LockIn
|
|
202
269
|
npm install
|
|
203
270
|
npm run build
|
|
204
271
|
```
|
|
@@ -213,7 +280,7 @@ node dist/index.js --stdio
|
|
|
213
280
|
|
|
214
281
|
## MCP Client Configuration (after installer)
|
|
215
282
|
|
|
216
|
-
Your MCP URL and API key are in `~/.
|
|
283
|
+
Your MCP URL and API key are in `~/.lockin/setup-manifest.json`:
|
|
217
284
|
|
|
218
285
|
- **MCP URL:** from manifest — e.g. `https://relay.lockinmcp.com/device/abc123/mcp` (Poke, Claude, ChatGPT)
|
|
219
286
|
- **API key:** `mdb_…`
|
|
@@ -227,28 +294,59 @@ npx poke@latest mcp add 'https://YOUR_RELAY_HOST/device/YOUR_ID/mcp' -n 'LockIn
|
|
|
227
294
|
|
|
228
295
|
Or set URL + API key in [poke.com/integrations](https://poke.com/integrations).
|
|
229
296
|
|
|
297
|
+
### Claude (claude.ai)
|
|
298
|
+
|
|
299
|
+
The installer opens a link that **pre-fills the connector name and MCP URL** (`connectorName` + `connectorUrl`). Claude web uses **OAuth** — do not paste your API key in Advanced settings. See the [connector setup guide](https://lockinmcp.com/docs/connectors#claude-web).
|
|
300
|
+
|
|
301
|
+
1. Choose **Claude (claude.ai)** in the installer (or build the link with `connectorName=LockIn MCP` and your encoded MCP URL)
|
|
302
|
+
2. Confirm the pre-filled MCP server URL (e.g. `https://relay.lockinmcp.com/device/abc123/mcp`)
|
|
303
|
+
3. Leave Advanced OAuth settings empty. Click Add, then sign in on lockinmcp.com when Claude prompts (Pro license required)
|
|
304
|
+
4. Start a new chat with LockIn enabled
|
|
305
|
+
|
|
306
|
+
For Bearer/API-key auth, use **Claude Desktop** instead — the installer can write `claude_desktop_config.json` automatically.
|
|
307
|
+
|
|
230
308
|
### Claude Desktop
|
|
231
309
|
|
|
232
|
-
|
|
310
|
+
Claude Desktop only supports **stdio** MCP servers in `claude_desktop_config.json` — not direct `url` entries. The installer merges a config that uses [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) to bridge your relay URL (requires Node.js 18+).
|
|
311
|
+
|
|
312
|
+
**Config paths:**
|
|
313
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
314
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
315
|
+
- Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
316
|
+
|
|
317
|
+
Manual example:
|
|
233
318
|
|
|
234
319
|
```json
|
|
235
320
|
{
|
|
236
321
|
"mcpServers": {
|
|
237
322
|
"lockin-mcp": {
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
"
|
|
323
|
+
"command": "npx",
|
|
324
|
+
"args": [
|
|
325
|
+
"-y",
|
|
326
|
+
"mcp-remote@latest",
|
|
327
|
+
"https://relay.lockinmcp.com/device/YOUR_ID/mcp",
|
|
328
|
+
"--header",
|
|
329
|
+
"Authorization:${LOCKIN_MCP_AUTH_HEADER}"
|
|
330
|
+
],
|
|
331
|
+
"env": {
|
|
332
|
+
"LOCKIN_MCP_AUTH_HEADER": "Bearer mdb_..."
|
|
241
333
|
}
|
|
242
334
|
}
|
|
243
335
|
}
|
|
244
336
|
}
|
|
245
337
|
```
|
|
246
338
|
|
|
339
|
+
On Windows, use `"command": "npx.cmd"` instead of `"npx"`. Quit Claude Desktop completely before editing, then reopen.
|
|
340
|
+
|
|
247
341
|
### ChatGPT
|
|
248
342
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
343
|
+
Custom MCP connectors require **Developer mode** under Advanced settings. ChatGPT cannot accept credentials from an external link — copy from the installer or [setup guide](https://lockinmcp.com/docs/connectors#chatgpt).
|
|
344
|
+
|
|
345
|
+
1. Open [ChatGPT → Settings → Connectors → Advanced](https://chatgpt.com/#settings/Connectors/Advanced)
|
|
346
|
+
2. Enable **Developer mode**
|
|
347
|
+
3. Add connector → paste MCP server URL from your manifest
|
|
348
|
+
4. Set Authentication to OAuth and leave DCR enabled — sign in on lockinmcp.com when prompted (do not paste your API key in OAuth fields)
|
|
349
|
+
5. Start a new chat and enable the LockIn connector
|
|
252
350
|
|
|
253
351
|
### Relay deployment (operators)
|
|
254
352
|
|
|
@@ -262,18 +360,20 @@ npx wrangler secret put MDB_JWT_SECRET # optional, same as web app
|
|
|
262
360
|
npm run deploy
|
|
263
361
|
```
|
|
264
362
|
|
|
265
|
-
Set
|
|
363
|
+
Set relay vars on **Convex production** in the dashboard. For local dev only: `web/.env.local` + `./scripts/sync-convex-env.sh` (dev deployment).
|
|
266
364
|
|
|
267
365
|
## Privileges & `/etc/hosts`
|
|
268
366
|
|
|
269
367
|
The server writes marked entries between:
|
|
270
368
|
|
|
271
369
|
```
|
|
272
|
-
#
|
|
370
|
+
# lockin-mcp BEGIN
|
|
273
371
|
...
|
|
274
|
-
#
|
|
372
|
+
# lockin-mcp END
|
|
275
373
|
```
|
|
276
374
|
|
|
375
|
+
Legacy installs may still show `# mac-distraction-blocker-mcp` markers; those are stripped on the next hosts sync.
|
|
376
|
+
|
|
277
377
|
Only those lines are touched; the rest of `/etc/hosts` is preserved.
|
|
278
378
|
|
|
279
379
|
### Option A: Run the server with `sudo` (simplest)
|
|
@@ -286,28 +386,26 @@ For stdio MCP clients, configure the client to launch with `sudo` (see client co
|
|
|
286
386
|
|
|
287
387
|
### Option B: Passwordless `sudo` for hosts updates (recommended)
|
|
288
388
|
|
|
289
|
-
The
|
|
389
|
+
The installer creates `~/.lockin/update-hosts.sh` and a sudoers entry at `/etc/sudoers.d/lockin-mcp-hosts` that allows your user to run that script without a password. Temp files use the prefix `lockin-mcp-hosts-{pid}`.
|
|
390
|
+
|
|
391
|
+
If you installed manually, re-run the installer or configure sudoers to allow (replace `YOUR_USER` with your macOS username):
|
|
290
392
|
|
|
291
393
|
```bash
|
|
292
|
-
sudo visudo -f /etc/sudoers.d/
|
|
394
|
+
sudo visudo -f /etc/sudoers.d/lockin-mcp-hosts
|
|
293
395
|
```
|
|
294
396
|
|
|
295
|
-
Add
|
|
397
|
+
Add:
|
|
296
398
|
|
|
297
399
|
```
|
|
298
|
-
YOUR_USER ALL=(ALL) NOPASSWD: /
|
|
299
|
-
YOUR_USER ALL=(ALL) NOPASSWD: /usr/sbin/dscacheutil -flushcache
|
|
300
|
-
YOUR_USER ALL=(ALL) NOPASSWD: /usr/bin/killall -HUP mDNSResponder
|
|
400
|
+
YOUR_USER ALL=(ALL) NOPASSWD: /Users/YOUR_USER/.lockin/update-hosts.sh
|
|
301
401
|
```
|
|
302
402
|
|
|
403
|
+
(Adjust paths for Linux/Windows as needed.)
|
|
404
|
+
|
|
303
405
|
### Verify permissions
|
|
304
406
|
|
|
305
407
|
Call `get_block_status` — `hostsFileWritable: true` means the server can update blocking without extra setup.
|
|
306
408
|
|
|
307
|
-
## MCP Client Configuration
|
|
308
|
-
|
|
309
|
-
See **MCP Client Configuration (after installer)** above.
|
|
310
|
-
|
|
311
409
|
### Legacy: local stdio (Claude Desktop)
|
|
312
410
|
|
|
313
411
|
`~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
@@ -317,12 +415,20 @@ See **MCP Client Configuration (after installer)** above.
|
|
|
317
415
|
"mcpServers": {
|
|
318
416
|
"lockin-mcp": {
|
|
319
417
|
"command": "node",
|
|
320
|
-
"args": ["/absolute/path/to/
|
|
418
|
+
"args": ["/absolute/path/to/LockIn/dist/index.js", "--stdio"]
|
|
321
419
|
}
|
|
322
420
|
}
|
|
323
421
|
}
|
|
324
422
|
```
|
|
325
423
|
|
|
424
|
+
## Security
|
|
425
|
+
|
|
426
|
+
- **MCP bearer auth:** HTTP MCP requires `MDB_MCP_AUTH_TOKEN` or `--mcp-auth-token` (`mdb_*` tokens). Relay-backed HTTP refuses to start without a token.
|
|
427
|
+
- **API rate limiting:** Login, signup, OAuth (account + MCP), license verify, checkout, consent, and user-report routes are rate-limited.
|
|
428
|
+
- **User reports:** `POST /api/report` accepts bug/feature submissions (5 per IP per hour). Requires `RESEND_API_KEY` in production; falls back to Convex logs in dev.
|
|
429
|
+
- **Relay tokens:** Device tokens are hashed at rest on the Cloudflare worker (SHA-256).
|
|
430
|
+
- **Setup manifest:** Written with `0o600` permissions; contains your relay URL and API key — do not commit or share.
|
|
431
|
+
|
|
326
432
|
## Hooking up to [poke.com/kitchen](https://poke.com/kitchen)
|
|
327
433
|
|
|
328
434
|
The installer tunnels your local MCP over HTTPS so Poke can connect remotely — no extra terminal tab.
|
|
@@ -379,13 +485,13 @@ No arguments. Returns blocked list, temporary allowances with expiry timestamps,
|
|
|
379
485
|
flowchart LR
|
|
380
486
|
A[MCP Client / Poke] --> R[Relay Worker]
|
|
381
487
|
R --> B[MCP Server on Mac]
|
|
382
|
-
B --> C[State JSON\n~/.
|
|
488
|
+
B --> C[State JSON\n~/.lockin/]
|
|
383
489
|
B --> D[/etc/hosts]
|
|
384
490
|
B --> E[DNS cache flush]
|
|
385
491
|
C --> B
|
|
386
492
|
```
|
|
387
493
|
|
|
388
|
-
1. **State** — Block list and temporary unblocks live in `~/.
|
|
494
|
+
1. **State** — Block list and temporary unblocks live in `~/.lockin/state.json`
|
|
389
495
|
2. **Hosts sync** — Active blocks are written to `/etc/hosts` with a managed marker
|
|
390
496
|
3. **Expiry watcher** — A background timer re-applies blocks when temporary unblocks expire (server process must be running)
|
|
391
497
|
4. **DNS flush** — Best-effort `dscacheutil` / `mDNSResponder` refresh after changes
|
|
@@ -397,8 +503,13 @@ npm install
|
|
|
397
503
|
npm run dev # watch TypeScript
|
|
398
504
|
npm run build
|
|
399
505
|
npm run lint
|
|
506
|
+
npm test # Vitest (root MCP tests)
|
|
507
|
+
cd web && npm test # web rate-limit tests
|
|
508
|
+
cd relay && npm test # relay token hashing tests
|
|
400
509
|
```
|
|
401
510
|
|
|
511
|
+
CI (`.github/workflows/ci.yml`) runs lint, test, build, and `npm audit --audit-level=high` for the MCP server, web app, and relay worker.
|
|
512
|
+
|
|
402
513
|
## Troubleshooting
|
|
403
514
|
|
|
404
515
|
| Issue | Fix |
|
|
@@ -407,7 +518,7 @@ npm run lint
|
|
|
407
518
|
| Site still loads | Hard-refresh browser; DNS cache may take a moment. Try private window. |
|
|
408
519
|
| Temp unblock didn't re-block | Keep the MCP server process running (expiry watcher runs in-process) |
|
|
409
520
|
| Poke can't connect | Check manifest relay URL + `/health` returns `lockin-mcp`; verify relay device is online |
|
|
410
|
-
| Wrong platform |
|
|
521
|
+
| Wrong platform | macOS, Linux, and Windows are supported; DNS flush behavior differs by OS |
|
|
411
522
|
|
|
412
523
|
## License
|
|
413
524
|
|
package/dist/blocker.js
CHANGED
|
@@ -71,12 +71,12 @@ export async function blockDomains(domains) {
|
|
|
71
71
|
if (method === "unchanged" && newDomains.length === 0) {
|
|
72
72
|
return {
|
|
73
73
|
blocked: validated,
|
|
74
|
-
message: `${validated.length}
|
|
74
|
+
message: `${validated.length} site(s) already blocked.`,
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
return {
|
|
78
78
|
blocked: validated,
|
|
79
|
-
message: `Blocked ${validated.length}
|
|
79
|
+
message: `Blocked ${validated.length} site(s) via /etc/hosts (${method}).`,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
export async function unblockDomains(domains) {
|
package/dist/blocker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocker.js","sourceRoot":"","sources":["../src/blocker.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"blocker.js","sourceRoot":"","sources":["../src/blocker.ts"],"names":[],"mappings":"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"}
|
package/dist/brand.d.ts
CHANGED
|
@@ -2,11 +2,12 @@ export declare const APP_NAME = "LockIn MCP";
|
|
|
2
2
|
export declare const APP_SLUG = "lockin-mcp";
|
|
3
3
|
export declare const APP_TAGLINE = "Block distractions. Lock in focus.";
|
|
4
4
|
export declare const APP_DOMAIN = "lockinmcp.com";
|
|
5
|
+
export declare const CANONICAL_ORIGIN = "https://www.lockinmcp.com";
|
|
5
6
|
export declare const RELAY_HOST = "relay.lockinmcp.com";
|
|
6
7
|
export declare const RELAY_PUBLIC_ORIGIN = "https://relay.lockinmcp.com";
|
|
7
8
|
export declare const LICENSE_PRICE_ORIGINAL = "$19.99";
|
|
8
9
|
export declare const LICENSE_PRICE = "$9.99";
|
|
9
10
|
export declare const LICENSE_DISCOUNT_LABEL = "50% off \u2014 forever";
|
|
10
11
|
export declare const POKE_LOCKIN_RECIPE_URL = "https://poke.com/r/KQ7myvC_Xpo";
|
|
11
|
-
export declare const ICON_URL = "https://lockinmcp.com/icon.png";
|
|
12
|
+
export declare const ICON_URL = "https://www.lockinmcp.com/icon.png";
|
|
12
13
|
//# sourceMappingURL=brand.d.ts.map
|
package/dist/brand.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,eAAe,CAAC;AACrC,eAAO,MAAM,QAAQ,eAAe,CAAC;AACrC,eAAO,MAAM,WAAW,uCAAuC,CAAC;AAChE,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAC1C,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAChD,eAAO,MAAM,mBAAmB,gCAA0B,CAAC;AAC3D,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAC/C,eAAO,MAAM,aAAa,UAAU,CAAC;AACrC,eAAO,MAAM,sBAAsB,2BAAsB,CAAC;AAC1D,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AACvE,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,QAAQ,eAAe,CAAC;AACrC,eAAO,MAAM,QAAQ,eAAe,CAAC;AACrC,eAAO,MAAM,WAAW,uCAAuC,CAAC;AAChE,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAC1C,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAC5D,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAChD,eAAO,MAAM,mBAAmB,gCAA0B,CAAC;AAC3D,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAC/C,eAAO,MAAM,aAAa,UAAU,CAAC;AACrC,eAAO,MAAM,sBAAsB,2BAAsB,CAAC;AAC1D,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AACvE,eAAO,MAAM,QAAQ,uCAAiC,CAAC"}
|
package/dist/brand.js
CHANGED
|
@@ -2,11 +2,12 @@ export const APP_NAME = "LockIn MCP";
|
|
|
2
2
|
export const APP_SLUG = "lockin-mcp";
|
|
3
3
|
export const APP_TAGLINE = "Block distractions. Lock in focus.";
|
|
4
4
|
export const APP_DOMAIN = "lockinmcp.com";
|
|
5
|
+
export const CANONICAL_ORIGIN = "https://www.lockinmcp.com";
|
|
5
6
|
export const RELAY_HOST = `relay.${APP_DOMAIN}`;
|
|
6
7
|
export const RELAY_PUBLIC_ORIGIN = `https://${RELAY_HOST}`;
|
|
7
8
|
export const LICENSE_PRICE_ORIGINAL = "$19.99";
|
|
8
9
|
export const LICENSE_PRICE = "$9.99";
|
|
9
10
|
export const LICENSE_DISCOUNT_LABEL = "50% off — forever";
|
|
10
11
|
export const POKE_LOCKIN_RECIPE_URL = "https://poke.com/r/KQ7myvC_Xpo";
|
|
11
|
-
export const ICON_URL =
|
|
12
|
+
export const ICON_URL = `${CANONICAL_ORIGIN}/icon.png`;
|
|
12
13
|
//# sourceMappingURL=brand.js.map
|
package/dist/brand.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAChE,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC;AAC1C,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,UAAU,EAAE,CAAC;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,UAAU,EAAE,CAAC;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AACrC,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AACvE,MAAM,CAAC,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"brand.js","sourceRoot":"","sources":["../src/brand.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAChE,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC;AAC1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AAC5D,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,UAAU,EAAE,CAAC;AAChD,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,UAAU,EAAE,CAAC;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AACrC,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAC1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,gCAAgC,CAAC;AACvE,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,gBAAgB,WAAW,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Production app origin when MDB_APP_URL is unset (e.g. `npx lockin-mcp install`). */
|
|
2
|
-
export declare const DEFAULT_APP_URL = "https://lockinmcp.com";
|
|
2
|
+
export declare const DEFAULT_APP_URL = "https://www.lockinmcp.com";
|
|
3
3
|
export declare function getAppUrl(): string;
|
|
4
4
|
export declare function getLicenseApiUrl(): string;
|
|
5
5
|
export declare function getPurchaseUrl(): string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,uFAAuF;AACvF,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,uFAAuF;AACvF,eAAO,MAAM,eAAe,8BAAmB,CAAC;AAEhD,wBAAgB,SAAS,IAAI,MAAM,CAMlC;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAMzC;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CANONICAL_ORIGIN } from "./brand.js";
|
|
2
2
|
/** Production app origin when MDB_APP_URL is unset (e.g. `npx lockin-mcp install`). */
|
|
3
|
-
export const DEFAULT_APP_URL =
|
|
3
|
+
export const DEFAULT_APP_URL = CANONICAL_ORIGIN;
|
|
4
4
|
export function getAppUrl() {
|
|
5
5
|
return (process.env.MDB_APP_URL ??
|
|
6
6
|
process.env.NEXT_PUBLIC_APP_URL ??
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,uFAAuF;AACvF,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEhD,MAAM,UAAU,SAAS;IACvB,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,WAAW;QACvB,OAAO,CAAC,GAAG,CAAC,mBAAmB;QAC/B,eAAe,CAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACnD,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,GAAG,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,2BAA2B,CAAC;AACtE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosts-manager.d.ts","sourceRoot":"","sources":["../src/hosts-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hosts-manager.d.ts","sourceRoot":"","sources":["../src/hosts-manager.ts"],"names":[],"mappings":"AA8DA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS1D;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CA+BhE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAwB7D;AAkBD,wBAAgB,gBAAgB,CAC9B,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,CAOR;AA6BD,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,CAehF;AA2CD,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAcnD;AAED,wBAAsB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACrE,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAiBD;AAED,+DAA+D;AAC/D,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAK7D"}
|
package/dist/hosts-manager.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { BLOCK_IP, BLOCK_IP_V6, MARKER, expandDomainVariants, getHostsPath, } from "./types.js";
|
|
6
|
+
import { BLOCK_IP, BLOCK_IP_V6, HOSTS_MARKERS, MARKER, expandDomainVariants, getHostsPath, normalizeDomain, } from "./types.js";
|
|
7
7
|
import { ensureHostsUpdaterScript, writeHostsViaPasswordlessScript, } from "./installer/hosts-sudo.js";
|
|
8
8
|
import { commandExists } from "./installer/platform.js";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
@@ -58,7 +58,7 @@ export function stripManagedEntries(hostsContent) {
|
|
|
58
58
|
const kept = [];
|
|
59
59
|
let skipping = false;
|
|
60
60
|
for (const line of lines) {
|
|
61
|
-
if (line.includes(
|
|
61
|
+
if (HOSTS_MARKERS.some((marker) => line.includes(marker))) {
|
|
62
62
|
skipping = true;
|
|
63
63
|
continue;
|
|
64
64
|
}
|
|
@@ -80,27 +80,40 @@ export function stripManagedEntries(hostsContent) {
|
|
|
80
80
|
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
81
81
|
}
|
|
82
82
|
export function buildManagedSection(domains) {
|
|
83
|
-
|
|
83
|
+
const logicalSites = dedupeLogicalSites(domains);
|
|
84
|
+
if (logicalSites.length === 0) {
|
|
84
85
|
return "";
|
|
85
86
|
}
|
|
86
87
|
const lines = [
|
|
87
88
|
"",
|
|
88
89
|
`${MARKER} BEGIN`,
|
|
89
90
|
"# Managed by LockIn MCP — do not edit manually",
|
|
91
|
+
"# Each site lists apex + www (IPv4 + IPv6) so redirects cannot bypass the block.",
|
|
90
92
|
];
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
for (const site of logicalSites) {
|
|
94
|
+
const hostnames = [...new Set(expandDomainVariants(site))].sort();
|
|
95
|
+
lines.push(`# ${site}`);
|
|
96
|
+
for (const host of hostnames) {
|
|
97
|
+
lines.push(`${BLOCK_IP} ${host}`);
|
|
98
|
+
lines.push(`${BLOCK_IP_V6} ${host}`);
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
|
-
for (const host of [...uniqueHosts].sort()) {
|
|
98
|
-
lines.push(`${BLOCK_IP} ${host}`);
|
|
99
|
-
lines.push(`${BLOCK_IP_V6} ${host}`);
|
|
100
|
-
}
|
|
101
101
|
lines.push(`${MARKER} END`);
|
|
102
102
|
return lines.join("\n");
|
|
103
103
|
}
|
|
104
|
+
function dedupeLogicalSites(domains) {
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const result = [];
|
|
107
|
+
for (const domain of domains) {
|
|
108
|
+
const normalized = normalizeDomain(domain);
|
|
109
|
+
if (!normalized || seen.has(normalized)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
seen.add(normalized);
|
|
113
|
+
result.push(normalized);
|
|
114
|
+
}
|
|
115
|
+
return result.sort();
|
|
116
|
+
}
|
|
104
117
|
export function composeHostsFile(existingContent, blockedDomains) {
|
|
105
118
|
const base = stripManagedEntries(existingContent);
|
|
106
119
|
const managed = buildManagedSection(blockedDomains);
|