lockin-mcp 1.0.0
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 +349 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon.svg +4 -0
- package/dist/blocker.d.ts +23 -0
- package/dist/blocker.d.ts.map +1 -0
- package/dist/blocker.js +184 -0
- package/dist/blocker.js.map +1 -0
- package/dist/brand.d.ts +12 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +12 -0
- package/dist/brand.js.map +1 -0
- package/dist/hosts-manager.d.ts +14 -0
- package/dist/hosts-manager.d.ts.map +1 -0
- package/dist/hosts-manager.js +233 -0
- package/dist/hosts-manager.js.map +1 -0
- package/dist/hosts-manager.test.d.ts +2 -0
- package/dist/hosts-manager.test.d.ts.map +1 -0
- package/dist/hosts-manager.test.js +19 -0
- package/dist/hosts-manager.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +296 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +3 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +15 -0
- package/dist/install.js.map +1 -0
- package/dist/installer/agent-instructions.d.ts +25 -0
- package/dist/installer/agent-instructions.d.ts.map +1 -0
- package/dist/installer/agent-instructions.js +36 -0
- package/dist/installer/agent-instructions.js.map +1 -0
- package/dist/installer/agents.d.ts +9 -0
- package/dist/installer/agents.d.ts.map +1 -0
- package/dist/installer/agents.js +223 -0
- package/dist/installer/agents.js.map +1 -0
- package/dist/installer/background-service.d.ts +11 -0
- package/dist/installer/background-service.d.ts.map +1 -0
- package/dist/installer/background-service.js +47 -0
- package/dist/installer/background-service.js.map +1 -0
- package/dist/installer/clipboard.d.ts +2 -0
- package/dist/installer/clipboard.d.ts.map +1 -0
- package/dist/installer/clipboard.js +28 -0
- package/dist/installer/clipboard.js.map +1 -0
- package/dist/installer/daemon-linux.d.ts +10 -0
- package/dist/installer/daemon-linux.d.ts.map +1 -0
- package/dist/installer/daemon-linux.js +66 -0
- package/dist/installer/daemon-linux.js.map +1 -0
- package/dist/installer/daemon-windows.d.ts +10 -0
- package/dist/installer/daemon-windows.d.ts.map +1 -0
- package/dist/installer/daemon-windows.js +88 -0
- package/dist/installer/daemon-windows.js.map +1 -0
- package/dist/installer/daemon.d.ts +10 -0
- package/dist/installer/daemon.d.ts.map +1 -0
- package/dist/installer/daemon.js +96 -0
- package/dist/installer/daemon.js.map +1 -0
- package/dist/installer/hosts-sudo.d.ts +6 -0
- package/dist/installer/hosts-sudo.d.ts.map +1 -0
- package/dist/installer/hosts-sudo.js +111 -0
- package/dist/installer/hosts-sudo.js.map +1 -0
- package/dist/installer/legacy-tunnels.d.ts +2 -0
- package/dist/installer/legacy-tunnels.d.ts.map +1 -0
- package/dist/installer/legacy-tunnels.js +27 -0
- package/dist/installer/legacy-tunnels.js.map +1 -0
- package/dist/installer/license.d.ts +9 -0
- package/dist/installer/license.d.ts.map +1 -0
- package/dist/installer/license.js +193 -0
- package/dist/installer/license.js.map +1 -0
- package/dist/installer/mcp-cleanup.d.ts +3 -0
- package/dist/installer/mcp-cleanup.d.ts.map +1 -0
- package/dist/installer/mcp-cleanup.js +28 -0
- package/dist/installer/mcp-cleanup.js.map +1 -0
- package/dist/installer/platform.d.ts +7 -0
- package/dist/installer/platform.d.ts.map +1 -0
- package/dist/installer/platform.js +76 -0
- package/dist/installer/platform.js.map +1 -0
- package/dist/installer/poke-daemon.d.ts +14 -0
- package/dist/installer/poke-daemon.d.ts.map +1 -0
- package/dist/installer/poke-daemon.js +161 -0
- package/dist/installer/poke-daemon.js.map +1 -0
- package/dist/installer/poke.d.ts +43 -0
- package/dist/installer/poke.d.ts.map +1 -0
- package/dist/installer/poke.js +154 -0
- package/dist/installer/poke.js.map +1 -0
- package/dist/installer/port.d.ts +6 -0
- package/dist/installer/port.d.ts.map +1 -0
- package/dist/installer/port.js +76 -0
- package/dist/installer/port.js.map +1 -0
- package/dist/installer/presets.d.ts +5 -0
- package/dist/installer/presets.d.ts.map +1 -0
- package/dist/installer/presets.js +22 -0
- package/dist/installer/presets.js.map +1 -0
- package/dist/installer/prompts.d.ts +8 -0
- package/dist/installer/prompts.d.ts.map +1 -0
- package/dist/installer/prompts.js +128 -0
- package/dist/installer/prompts.js.map +1 -0
- package/dist/installer/relay-credentials.d.ts +26 -0
- package/dist/installer/relay-credentials.d.ts.map +1 -0
- package/dist/installer/relay-credentials.js +75 -0
- package/dist/installer/relay-credentials.js.map +1 -0
- package/dist/installer/relay-provision.d.ts +26 -0
- package/dist/installer/relay-provision.d.ts.map +1 -0
- package/dist/installer/relay-provision.js +57 -0
- package/dist/installer/relay-provision.js.map +1 -0
- package/dist/installer/run.d.ts +2 -0
- package/dist/installer/run.d.ts.map +1 -0
- package/dist/installer/run.js +365 -0
- package/dist/installer/run.js.map +1 -0
- package/dist/installer/tty.d.ts +7 -0
- package/dist/installer/tty.d.ts.map +1 -0
- package/dist/installer/tty.js +37 -0
- package/dist/installer/tty.js.map +1 -0
- package/dist/installer/tui.d.ts +14 -0
- package/dist/installer/tui.d.ts.map +1 -0
- package/dist/installer/tui.js +150 -0
- package/dist/installer/tui.js.map +1 -0
- package/dist/installer/tunnel-daemon.d.ts +13 -0
- package/dist/installer/tunnel-daemon.d.ts.map +1 -0
- package/dist/installer/tunnel-daemon.js +129 -0
- package/dist/installer/tunnel-daemon.js.map +1 -0
- package/dist/installer/tunnel-provision.d.ts +14 -0
- package/dist/installer/tunnel-provision.d.ts.map +1 -0
- package/dist/installer/tunnel-provision.js +31 -0
- package/dist/installer/tunnel-provision.js.map +1 -0
- package/dist/installer/tunnel.d.ts +12 -0
- package/dist/installer/tunnel.d.ts.map +1 -0
- package/dist/installer/tunnel.js +91 -0
- package/dist/installer/tunnel.js.map +1 -0
- package/dist/installer/types.d.ts +59 -0
- package/dist/installer/types.d.ts.map +1 -0
- package/dist/installer/types.js +2 -0
- package/dist/installer/types.js.map +1 -0
- package/dist/license-check.d.ts +12 -0
- package/dist/license-check.d.ts.map +1 -0
- package/dist/license-check.js +59 -0
- package/dist/license-check.js.map +1 -0
- package/dist/license-guard.d.ts +14 -0
- package/dist/license-guard.d.ts.map +1 -0
- package/dist/license-guard.js +56 -0
- package/dist/license-guard.js.map +1 -0
- package/dist/license-guard.test.d.ts +2 -0
- package/dist/license-guard.test.d.ts.map +1 -0
- package/dist/license-guard.test.js +34 -0
- package/dist/license-guard.test.js.map +1 -0
- package/dist/relay-client.d.ts +32 -0
- package/dist/relay-client.d.ts.map +1 -0
- package/dist/relay-client.js +249 -0
- package/dist/relay-client.js.map +1 -0
- package/dist/relay-types.d.ts +30 -0
- package/dist/relay-types.d.ts.map +1 -0
- package/dist/relay-types.js +2 -0
- package/dist/relay-types.js.map +1 -0
- package/dist/relay-urls.d.ts +22 -0
- package/dist/relay-urls.d.ts.map +1 -0
- package/dist/relay-urls.js +57 -0
- package/dist/relay-urls.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +164 -0
- package/dist/server.js.map +1 -0
- package/dist/state.d.ts +12 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +91 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/dist/types.test.d.ts +2 -0
- package/dist/types.test.d.ts.map +1 -0
- package/dist/types.test.js +13 -0
- package/dist/types.test.js.map +1 -0
- package/install.ps1 +41 -0
- package/install.sh +39 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# LockIn MCP
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that blocks distracting websites at the **system level** by managing entries in `/etc/hosts`. Use it with [Poke](https://poke.com), Claude Desktop, or ChatGPT custom MCP connectors to stay focused.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `block_domains` | Block a list of domains (e.g. `youtube.com`, `x.com`, `tiktok.com`) |
|
|
10
|
+
| `unblock_domains` | Permanently unblock specific domains |
|
|
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 |
|
|
13
|
+
| `get_block_status` | Show what is blocked, temporarily allowed, and system readiness |
|
|
14
|
+
|
|
15
|
+
Blocking redirects domains to `127.0.0.1` and `::1`, including `www.` variants.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- **macOS / Linux / Windows** (hosts-based blocking; macOS uses `dscacheutil` for DNS flush)
|
|
20
|
+
- **Node.js 18+**
|
|
21
|
+
- **Administrator privileges** to modify `/etc/hosts` (see below)
|
|
22
|
+
|
|
23
|
+
## Web App & Pro Licensing
|
|
24
|
+
|
|
25
|
+
The `web/` directory contains the landing page, user accounts, Stripe checkout, and license API.
|
|
26
|
+
|
|
27
|
+
### Quick start (local)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Terminal 1 — web app
|
|
31
|
+
cd web
|
|
32
|
+
cp ../.env.example .env.local # edit with your Stripe keys
|
|
33
|
+
npm install
|
|
34
|
+
npm run dev # http://localhost:3001
|
|
35
|
+
|
|
36
|
+
# Terminal 2 — CLI installer (points at local API by default)
|
|
37
|
+
export MDB_APP_URL=http://localhost:3001
|
|
38
|
+
export MDB_LICENSE_API_URL=http://localhost:3001/api/v1
|
|
39
|
+
npm run build
|
|
40
|
+
npm run install:local
|
|
41
|
+
# or from GitHub (no clone):
|
|
42
|
+
# npx -y lockin-mcp mdb-install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Architecture
|
|
46
|
+
|
|
47
|
+
| Component | Purpose |
|
|
48
|
+
|-----------|---------|
|
|
49
|
+
| Landing page (`/`) | Features, how-it-works, pricing |
|
|
50
|
+
| Device auth (`/device`) | Browser login for CLI OAuth device flow |
|
|
51
|
+
| Dashboard (`/dashboard`) | License keys, Pro status, install instructions |
|
|
52
|
+
| API (`/api/v1/*`) | License verify, OAuth device flow, token status |
|
|
53
|
+
| Stripe webhook | Pro upgrade + license key generation |
|
|
54
|
+
|
|
55
|
+
### CLI auth handshake
|
|
56
|
+
|
|
57
|
+
1. Installer calls `POST /api/v1/oauth/device` → receives `user_code` + `device_code`
|
|
58
|
+
2. Browser opens `/device?code=ABCD-1234`
|
|
59
|
+
3. User signs in and approves (requires Pro)
|
|
60
|
+
4. Installer polls `POST /api/v1/oauth/token` → receives `access_token`
|
|
61
|
+
5. Daemon verifies token via `POST /api/v1/license/status` on startup and hourly
|
|
62
|
+
|
|
63
|
+
### Environment variables
|
|
64
|
+
|
|
65
|
+
See [`.env.example`](.env.example) for all configuration options.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## One-Line Installer
|
|
70
|
+
|
|
71
|
+
The fastest way to set up blocking, tunneling, and Poke integration:
|
|
72
|
+
|
|
73
|
+
### macOS / Linux (curl)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
curl -fsSL https://raw.githubusercontent.com/Kiog-Aser/mac-distraction-blocker-mcp/main/install.sh | bash
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Windows (PowerShell)
|
|
80
|
+
|
|
81
|
+
```powershell
|
|
82
|
+
iwr -useb https://raw.githubusercontent.com/Kiog-Aser/mac-distraction-blocker-mcp/main/install.ps1 | iex
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### npm
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx -y lockin-mcp mdb-install
|
|
89
|
+
# or from a local clone:
|
|
90
|
+
npm run install:local
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### What the installer does
|
|
94
|
+
|
|
95
|
+
1. **Detects your platform** (macOS, Linux, or Windows)
|
|
96
|
+
2. **Verifies your $9.99 Pro license** via browser login or license key
|
|
97
|
+
3. **Prompts for distraction sites** — popular presets pre-selected (YouTube, X, TikTok, Instagram, Reddit) plus custom domains
|
|
98
|
+
4. **Applies initial blocks** to `/etc/hosts`
|
|
99
|
+
5. **Starts the MCP HTTP server** locally with an outbound relay connection (background LaunchAgent on macOS)
|
|
100
|
+
6. **Provisions a stable relay URL** (`https://relay.lockinmcp.com/device/{id}/mcp`) so Poke, Claude, and ChatGPT can reach your Mac
|
|
101
|
+
7. **Connects your AI agent** — Poke, Claude Desktop, or ChatGPT (URL + API key from manifest)
|
|
102
|
+
|
|
103
|
+
### Installer flags (non-interactive)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
curl -fsSL .../install.sh | bash -s -- --yes --browser-login --poke-recipe
|
|
107
|
+
npx -y lockin-mcp mdb-install --license-key mdb_pro_xxx --sites all --poke-recipe
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Flag | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `--yes` | Non-interactive with defaults |
|
|
113
|
+
| `--license-key <key>` | Pro license key |
|
|
114
|
+
| `--browser-login` | Verify license via browser device flow (default) |
|
|
115
|
+
| `--skip-tunnel` | Local MCP only (no relay URL) |
|
|
116
|
+
| `--sites all` | Block all presets |
|
|
117
|
+
| `--custom-domains a.com,b.com` | Extra domains |
|
|
118
|
+
| `--poke-recipe` | Generate distraction-coach recipe URL |
|
|
119
|
+
| `--mcp-auth-token <token>` | Custom bearer token for tunneled MCP |
|
|
120
|
+
|
|
121
|
+
Setup details are saved to `~/.mac-distraction-blocker/setup-manifest.json`.
|
|
122
|
+
|
|
123
|
+
### Pro licensing ($9.99 lifetime — 50% off $19.99)
|
|
124
|
+
|
|
125
|
+
- **Purchase**: Sign up (email or Google/GitHub), verify email, pay via Stripe
|
|
126
|
+
- **License key**: Available on dashboard after purchase; pass `--license-key` to installer
|
|
127
|
+
- **Browser login**: Default installer flow — OAuth device authorization in your browser
|
|
128
|
+
- **Auth**: Google and GitHub OAuth supported; email/password requires verification before checkout
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Quick Start (manual)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git clone <this-repo>
|
|
136
|
+
cd mac-distraction-blocker-mcp
|
|
137
|
+
npm install
|
|
138
|
+
npm run build
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Run locally (stdio — advanced)
|
|
142
|
+
|
|
143
|
+
For MCP clients that spawn a local subprocess:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
node dist/index.js --stdio
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## MCP Client Configuration (after installer)
|
|
150
|
+
|
|
151
|
+
Your MCP URL and API key are in `~/.mac-distraction-blocker/setup-manifest.json`:
|
|
152
|
+
|
|
153
|
+
- **MCP URL:** from manifest — e.g. `https://relay.lockinmcp.com/device/abc123/mcp` (Poke, Claude, ChatGPT)
|
|
154
|
+
- **API key:** `mdb_…`
|
|
155
|
+
|
|
156
|
+
### Poke
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx poke@latest login
|
|
160
|
+
npx poke@latest mcp add 'https://YOUR_RELAY_HOST/device/YOUR_ID/mcp' -n 'LockIn MCP' --api-key 'mdb_...'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or set URL + API key in [poke.com/integrations](https://poke.com/integrations).
|
|
164
|
+
|
|
165
|
+
### Claude Desktop
|
|
166
|
+
|
|
167
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"mcpServers": {
|
|
172
|
+
"lockin-mcp": {
|
|
173
|
+
"url": "https://YOUR_TUNNEL_HOST/mcp",
|
|
174
|
+
"headers": {
|
|
175
|
+
"Authorization": "Bearer mdb_..."
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### ChatGPT
|
|
183
|
+
|
|
184
|
+
1. Open ChatGPT → Settings → Connectors
|
|
185
|
+
2. Add MCP server URL from your manifest (e.g. `https://….trycloudflare.com/mcp`)
|
|
186
|
+
3. Paste API key when prompted: `mdb_...`
|
|
187
|
+
|
|
188
|
+
### Relay deployment (operators)
|
|
189
|
+
|
|
190
|
+
The `relay/` package is a Cloudflare Worker + Durable Object that bridges remote MCP HTTP requests to each Mac daemon over an outbound WebSocket.
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
cd relay
|
|
194
|
+
npm install
|
|
195
|
+
npx wrangler secret put REGISTRATION_SECRET
|
|
196
|
+
npx wrangler secret put MDB_JWT_SECRET # optional, same as web app
|
|
197
|
+
npm run deploy
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Set `MDB_RELAY_URL`, `MDB_RELAY_PUBLIC_URL`, and `REGISTRATION_SECRET` in `web/.env.local` and sync to Convex via `./scripts/sync-convex-env.sh`.
|
|
201
|
+
|
|
202
|
+
## Privileges & `/etc/hosts`
|
|
203
|
+
|
|
204
|
+
The server writes marked entries between:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
# mac-distraction-blocker-mcp BEGIN
|
|
208
|
+
...
|
|
209
|
+
# mac-distraction-blocker-mcp END
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Only those lines are touched; the rest of `/etc/hosts` is preserved.
|
|
213
|
+
|
|
214
|
+
### Option A: Run the server with `sudo` (simplest)
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
sudo node dist/index.js --http --port 3000
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
For stdio MCP clients, configure the client to launch with `sudo` (see client config examples below).
|
|
221
|
+
|
|
222
|
+
### Option B: Passwordless `sudo` for hosts updates (recommended)
|
|
223
|
+
|
|
224
|
+
The server falls back to `sudo cp` when it cannot write `/etc/hosts` directly. To avoid prompts during tool calls, allow your user to copy a temp file into hosts without a password:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
sudo visudo -f /etc/sudoers.d/mac-distraction-blocker
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Add (replace `YOUR_USER` with your macOS username):
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
YOUR_USER ALL=(ALL) NOPASSWD: /bin/cp /tmp/mac-distraction-blocker-hosts-* /etc/hosts
|
|
234
|
+
YOUR_USER ALL=(ALL) NOPASSWD: /usr/sbin/dscacheutil -flushcache
|
|
235
|
+
YOUR_USER ALL=(ALL) NOPASSWD: /usr/bin/killall -HUP mDNSResponder
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Verify permissions
|
|
239
|
+
|
|
240
|
+
Call `get_block_status` — `hostsFileWritable: true` means the server can update blocking without extra setup.
|
|
241
|
+
|
|
242
|
+
## MCP Client Configuration
|
|
243
|
+
|
|
244
|
+
See **MCP Client Configuration (after installer)** above.
|
|
245
|
+
|
|
246
|
+
### Legacy: local stdio (Claude Desktop)
|
|
247
|
+
|
|
248
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"mcpServers": {
|
|
253
|
+
"lockin-mcp": {
|
|
254
|
+
"command": "node",
|
|
255
|
+
"args": ["/absolute/path/to/mac-distraction-blocker-mcp/dist/index.js", "--stdio"]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Hooking up to [poke.com/kitchen](https://poke.com/kitchen)
|
|
262
|
+
|
|
263
|
+
The installer tunnels your local MCP over HTTPS so Poke can connect remotely — no extra terminal tab.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
npx poke@latest login
|
|
267
|
+
npx poke@latest mcp add 'https://YOUR_RELAY_HOST/device/YOUR_ID/mcp' -n 'LockIn MCP' --api-key 'mdb_...'
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The installer runs this automatically when you choose Poke. Credentials are in `setup-manifest.json`.
|
|
271
|
+
|
|
272
|
+
### Recipe / Kitchen (recommended)
|
|
273
|
+
|
|
274
|
+
**Install the official LockIn recipe** for the best Poke experience:
|
|
275
|
+
|
|
276
|
+
https://poke.com/r/KQ7myvC_Xpo
|
|
277
|
+
|
|
278
|
+
The recipe connects your MCP tools and coaches you through focus sessions. You can also build custom recipes in [poke.com/kitchen](https://poke.com/kitchen) using `block_domains`, `get_block_status`, etc.
|
|
279
|
+
|
|
280
|
+
## Tool Examples
|
|
281
|
+
|
|
282
|
+
### Block sites
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"domains": ["youtube.com", "x.com", "tiktok.com", "reddit.com"]
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Unblock one site
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"domains": ["youtube.com"]
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 15-minute break
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"domains": ["youtube.com"],
|
|
303
|
+
"duration_seconds": 900
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Check status
|
|
308
|
+
|
|
309
|
+
No arguments. Returns blocked list, temporary allowances with expiry timestamps, and whether hosts is writable.
|
|
310
|
+
|
|
311
|
+
## How it works
|
|
312
|
+
|
|
313
|
+
```mermaid
|
|
314
|
+
flowchart LR
|
|
315
|
+
A[MCP Client / Poke] --> R[Relay Worker]
|
|
316
|
+
R --> B[MCP Server on Mac]
|
|
317
|
+
B --> C[State JSON\n~/.mac-distraction-blocker/]
|
|
318
|
+
B --> D[/etc/hosts]
|
|
319
|
+
B --> E[DNS cache flush]
|
|
320
|
+
C --> B
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
1. **State** — Block list and temporary unblocks live in `~/.mac-distraction-blocker/state.json`
|
|
324
|
+
2. **Hosts sync** — Active blocks are written to `/etc/hosts` with a managed marker
|
|
325
|
+
3. **Expiry watcher** — A background timer re-applies blocks when temporary unblocks expire (server process must be running)
|
|
326
|
+
4. **DNS flush** — Best-effort `dscacheutil` / `mDNSResponder` refresh after changes
|
|
327
|
+
|
|
328
|
+
## Development
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
npm install
|
|
332
|
+
npm run dev # watch TypeScript
|
|
333
|
+
npm run build
|
|
334
|
+
npm run lint
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Troubleshooting
|
|
338
|
+
|
|
339
|
+
| Issue | Fix |
|
|
340
|
+
|-------|-----|
|
|
341
|
+
| `EACCES` on block/unblock | Run with `sudo` or configure passwordless sudo (see above) |
|
|
342
|
+
| Site still loads | Hard-refresh browser; DNS cache may take a moment. Try private window. |
|
|
343
|
+
| Temp unblock didn't re-block | Keep the MCP server process running (expiry watcher runs in-process) |
|
|
344
|
+
| Poke can't connect | Check manifest relay URL + `/health` returns `lockin-mcp`; verify relay device is online |
|
|
345
|
+
| Wrong platform | Designed for macOS; Linux may work with `/etc/hosts` but DNS flush differs |
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
MIT
|
|
Binary file
|
|
Binary file
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="#111827">
|
|
2
|
+
<rect width="256" height="256" rx="48" fill="#111827"/>
|
|
3
|
+
<path fill="#ffffff" d="M 144 224 L 43.598 224 L 144 123.598 Z M 224 183.5 L 176 224 L 176 48 L 16 48 L 56 8 L 224 8 Z M 16 180.402 L 16 96 L 100.402 96 Z"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type BlockStatusResult, type BlockerState, type DomainStatus } from "./types.js";
|
|
2
|
+
export declare function startExpiryWatcher(intervalMs?: number): void;
|
|
3
|
+
export declare function stopExpiryWatcher(): void;
|
|
4
|
+
export declare function blockDomains(domains: string[]): Promise<{
|
|
5
|
+
blocked: string[];
|
|
6
|
+
message: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function unblockDomains(domains: string[]): Promise<{
|
|
9
|
+
unblocked: string[];
|
|
10
|
+
message: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function temporarilyUnblockDomains(domains: string[], durationSeconds: number): Promise<{
|
|
13
|
+
temporarilyUnblocked: Array<{
|
|
14
|
+
domain: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
}>;
|
|
17
|
+
message: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function getBlockStatus(): Promise<BlockStatusResult>;
|
|
20
|
+
export declare function listAllKnownDomains(): Promise<string[]>;
|
|
21
|
+
export declare function describeDomain(domain: string, state: BlockerState): DomainStatus;
|
|
22
|
+
export declare function isCurrentlyBlocked(domain: string): Promise<boolean>;
|
|
23
|
+
//# sourceMappingURL=blocker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocker.d.ts","sourceRoot":"","sources":["../src/blocker.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,YAAY,EAGlB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,kBAAkB,CAChC,UAAU,SAAS,GAClB,IAAI,CAUN;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAsCD,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CA4CD;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC/D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CA8BD;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EAAE,EACjB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC;IACT,oBAAoB,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CA4CD;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAiCjE;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAG7D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,CAgBhF;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGzE"}
|
package/dist/blocker.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { canWriteHostsFile, syncHostsFile, } from "./hosts-manager.js";
|
|
2
|
+
import { dedupeDomains, getDomainsToBlockInHosts, getActiveTemporaryUnblock, isDomainBlocked, loadState, pruneExpiredUnblocks, saveState, appendAuditEntry, } from "./state.js";
|
|
3
|
+
import { isValidDomain, normalizeDomain, } from "./types.js";
|
|
4
|
+
let expiryTimer;
|
|
5
|
+
let lastSyncedAt = null;
|
|
6
|
+
export function startExpiryWatcher(intervalMs = 10_000) {
|
|
7
|
+
if (expiryTimer) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
expiryTimer = setInterval(() => {
|
|
11
|
+
void refreshExpiredUnblocks();
|
|
12
|
+
}, intervalMs);
|
|
13
|
+
expiryTimer.unref();
|
|
14
|
+
}
|
|
15
|
+
export function stopExpiryWatcher() {
|
|
16
|
+
if (expiryTimer) {
|
|
17
|
+
clearInterval(expiryTimer);
|
|
18
|
+
expiryTimer = undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function refreshExpiredUnblocks() {
|
|
22
|
+
const state = await loadState();
|
|
23
|
+
const pruned = pruneExpiredUnblocks(state.temporaryUnblocks);
|
|
24
|
+
if (pruned.length === state.temporaryUnblocks.length) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const nextState = {
|
|
28
|
+
...state,
|
|
29
|
+
temporaryUnblocks: pruned,
|
|
30
|
+
};
|
|
31
|
+
await saveState(nextState);
|
|
32
|
+
await applyHostsFromState(nextState);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
async function applyHostsFromState(state) {
|
|
36
|
+
const domains = getDomainsToBlockInHosts(state);
|
|
37
|
+
const sync = await syncHostsFile(domains);
|
|
38
|
+
if (sync.method !== "unchanged") {
|
|
39
|
+
lastSyncedAt = Date.now();
|
|
40
|
+
}
|
|
41
|
+
return sync.method;
|
|
42
|
+
}
|
|
43
|
+
function validateDomains(domains) {
|
|
44
|
+
const normalized = dedupeDomains(domains);
|
|
45
|
+
const invalid = normalized.filter((domain) => !isValidDomain(domain));
|
|
46
|
+
if (invalid.length > 0) {
|
|
47
|
+
throw new Error(`Invalid domain(s): ${invalid.join(", ")}`);
|
|
48
|
+
}
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
export async function blockDomains(domains) {
|
|
52
|
+
const validated = validateDomains(domains);
|
|
53
|
+
const state = await loadState();
|
|
54
|
+
const blockedSet = new Set(state.blockedDomains);
|
|
55
|
+
const newDomains = validated.filter((domain) => !blockedSet.has(domain));
|
|
56
|
+
for (const domain of validated) {
|
|
57
|
+
blockedSet.add(domain);
|
|
58
|
+
}
|
|
59
|
+
const nextState = appendAuditEntry({
|
|
60
|
+
blockedDomains: [...blockedSet].sort(),
|
|
61
|
+
temporaryUnblocks: state.temporaryUnblocks.filter((entry) => !validated.includes(entry.domain)),
|
|
62
|
+
updatedAt: Date.now(),
|
|
63
|
+
}, "block_domains", validated);
|
|
64
|
+
const stateUnchanged = newDomains.length === 0 &&
|
|
65
|
+
nextState.blockedDomains.length === state.blockedDomains.length &&
|
|
66
|
+
nextState.temporaryUnblocks.length === state.temporaryUnblocks.length;
|
|
67
|
+
if (!stateUnchanged) {
|
|
68
|
+
await saveState(nextState);
|
|
69
|
+
}
|
|
70
|
+
const method = await applyHostsFromState(nextState);
|
|
71
|
+
if (method === "unchanged" && newDomains.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
blocked: validated,
|
|
74
|
+
message: `${validated.length} domain(s) already blocked.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
blocked: validated,
|
|
79
|
+
message: `Blocked ${validated.length} domain(s) via /etc/hosts (${method}).`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export async function unblockDomains(domains) {
|
|
83
|
+
const validated = validateDomains(domains);
|
|
84
|
+
const state = await loadState();
|
|
85
|
+
const removeSet = new Set(validated);
|
|
86
|
+
const unblocked = state.blockedDomains.filter((domain) => removeSet.has(domain));
|
|
87
|
+
const nextState = appendAuditEntry({
|
|
88
|
+
blockedDomains: state.blockedDomains.filter((domain) => !removeSet.has(domain)),
|
|
89
|
+
temporaryUnblocks: state.temporaryUnblocks.filter((entry) => !removeSet.has(entry.domain)),
|
|
90
|
+
updatedAt: Date.now(),
|
|
91
|
+
}, "unblock_domains", unblocked);
|
|
92
|
+
await saveState(nextState);
|
|
93
|
+
const method = await applyHostsFromState(nextState);
|
|
94
|
+
return {
|
|
95
|
+
unblocked,
|
|
96
|
+
message: `Unblocked ${unblocked.length} domain(s) and updated /etc/hosts (${method}).`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export async function temporarilyUnblockDomains(domains, durationSeconds) {
|
|
100
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
|
|
101
|
+
throw new Error("duration_seconds must be a positive number");
|
|
102
|
+
}
|
|
103
|
+
const validated = validateDomains(domains);
|
|
104
|
+
const state = await loadState();
|
|
105
|
+
const expiresAt = Date.now() + durationSeconds * 1000;
|
|
106
|
+
const temporaryUnblocks = [...state.temporaryUnblocks];
|
|
107
|
+
for (const domain of validated) {
|
|
108
|
+
if (!state.blockedDomains.includes(domain)) {
|
|
109
|
+
throw new Error(`Cannot temporarily unblock "${domain}" because it is not in the blocked list`);
|
|
110
|
+
}
|
|
111
|
+
const existingIndex = temporaryUnblocks.findIndex((entry) => entry.domain === domain);
|
|
112
|
+
const entry = { domain, expiresAt };
|
|
113
|
+
if (existingIndex >= 0) {
|
|
114
|
+
temporaryUnblocks[existingIndex] = entry;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
temporaryUnblocks.push(entry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const nextState = appendAuditEntry({
|
|
121
|
+
...state,
|
|
122
|
+
temporaryUnblocks,
|
|
123
|
+
}, "temporarily_unblock_domains", validated);
|
|
124
|
+
await saveState(nextState);
|
|
125
|
+
const method = await applyHostsFromState(nextState);
|
|
126
|
+
return {
|
|
127
|
+
temporarilyUnblocked: validated.map((domain) => ({ domain, expiresAt })),
|
|
128
|
+
message: `Temporarily unblocked ${validated.length} domain(s) for ${durationSeconds}s (${method}).`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export async function getBlockStatus() {
|
|
132
|
+
await refreshExpiredUnblocks();
|
|
133
|
+
const state = await loadState();
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const hostsFileWritable = await canWriteHostsFile();
|
|
136
|
+
const blocked = [];
|
|
137
|
+
const temporarilyUnblocked = [];
|
|
138
|
+
for (const domain of state.blockedDomains) {
|
|
139
|
+
const temp = getActiveTemporaryUnblock(domain, state.temporaryUnblocks, now);
|
|
140
|
+
if (temp) {
|
|
141
|
+
temporarilyUnblocked.push({
|
|
142
|
+
domain,
|
|
143
|
+
status: "temporarily_unblocked",
|
|
144
|
+
expiresAt: temp.expiresAt,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
blocked.push({
|
|
149
|
+
domain,
|
|
150
|
+
status: "blocked",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
blocked,
|
|
155
|
+
temporarilyUnblocked,
|
|
156
|
+
notBlocked: [],
|
|
157
|
+
hostsFileWritable,
|
|
158
|
+
lastSyncedAt,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export async function listAllKnownDomains() {
|
|
162
|
+
const state = await loadState();
|
|
163
|
+
return state.blockedDomains;
|
|
164
|
+
}
|
|
165
|
+
export function describeDomain(domain, state) {
|
|
166
|
+
const normalized = normalizeDomain(domain);
|
|
167
|
+
if (!state.blockedDomains.includes(normalized)) {
|
|
168
|
+
return { domain: normalized, status: "not_blocked" };
|
|
169
|
+
}
|
|
170
|
+
const temp = getActiveTemporaryUnblock(normalized, state.temporaryUnblocks);
|
|
171
|
+
if (temp) {
|
|
172
|
+
return {
|
|
173
|
+
domain: normalized,
|
|
174
|
+
status: "temporarily_unblocked",
|
|
175
|
+
expiresAt: temp.expiresAt,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return { domain: normalized, status: "blocked" };
|
|
179
|
+
}
|
|
180
|
+
export async function isCurrentlyBlocked(domain) {
|
|
181
|
+
const state = await loadState();
|
|
182
|
+
return isDomainBlocked(domain, state);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=blocker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocker.js","sourceRoot":"","sources":["../src/blocker.ts"],"names":[],"mappings":"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"}
|
package/dist/brand.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const APP_NAME = "LockIn MCP";
|
|
2
|
+
export declare const APP_SLUG = "lockin-mcp";
|
|
3
|
+
export declare const APP_TAGLINE = "Block distractions. Lock in focus.";
|
|
4
|
+
export declare const APP_DOMAIN = "lockinmcp.com";
|
|
5
|
+
export declare const RELAY_HOST = "relay.lockinmcp.com";
|
|
6
|
+
export declare const RELAY_PUBLIC_ORIGIN = "https://relay.lockinmcp.com";
|
|
7
|
+
export declare const LICENSE_PRICE_ORIGINAL = "$19.99";
|
|
8
|
+
export declare const LICENSE_PRICE = "$9.99";
|
|
9
|
+
export declare const LICENSE_DISCOUNT_LABEL = "50% off \u2014 forever";
|
|
10
|
+
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
|
+
//# sourceMappingURL=brand.d.ts.map
|
|
@@ -0,0 +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,mCAAmC,CAAC"}
|
package/dist/brand.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const APP_NAME = "LockIn MCP";
|
|
2
|
+
export const APP_SLUG = "lockin-mcp";
|
|
3
|
+
export const APP_TAGLINE = "Block distractions. Lock in focus.";
|
|
4
|
+
export const APP_DOMAIN = "lockinmcp.com";
|
|
5
|
+
export const RELAY_HOST = `relay.${APP_DOMAIN}`;
|
|
6
|
+
export const RELAY_PUBLIC_ORIGIN = `https://${RELAY_HOST}`;
|
|
7
|
+
export const LICENSE_PRICE_ORIGINAL = "$19.99";
|
|
8
|
+
export const LICENSE_PRICE = "$9.99";
|
|
9
|
+
export const LICENSE_DISCOUNT_LABEL = "50% off — forever";
|
|
10
|
+
export const POKE_LOCKIN_RECIPE_URL = "https://poke.com/r/KQ7myvC_Xpo";
|
|
11
|
+
export const ICON_URL = `https://${APP_DOMAIN}/icon.png`;
|
|
12
|
+
//# sourceMappingURL=brand.js.map
|
|
@@ -0,0 +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,WAAW,UAAU,WAAW,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function canWriteHostsFile(): Promise<boolean>;
|
|
2
|
+
export declare function readHostsFile(): Promise<string>;
|
|
3
|
+
export declare function stripManagedEntries(hostsContent: string): string;
|
|
4
|
+
export declare function buildManagedSection(domains: string[]): string;
|
|
5
|
+
export declare function composeHostsFile(existingContent: string, blockedDomains: string[]): string;
|
|
6
|
+
export declare function writeHostsFile(content: string): Promise<"direct" | "sudo">;
|
|
7
|
+
export declare function flushDnsCache(): Promise<void>;
|
|
8
|
+
export declare function syncHostsFile(blockedDomains: string[]): Promise<{
|
|
9
|
+
method: "direct" | "sudo" | "unchanged";
|
|
10
|
+
blockedCount: number;
|
|
11
|
+
}>;
|
|
12
|
+
/** Reconcile hosts file with saved state on daemon startup. */
|
|
13
|
+
export declare function reconcileHostsOnStartup(): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=hosts-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosts-manager.d.ts","sourceRoot":"","sources":["../src/hosts-manager.ts"],"names":[],"mappings":"AA4DA,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,CAyB7D;AAED,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"}
|