codex-blocker 0.1.2 → 0.1.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 +7 -7
- package/dist/bin.js +493 -17
- package/dist/{chunk-KNFNSOAX.js → chunk-OIDV724O.js} +528 -42
- package/dist/server.d.ts +46 -7
- package/dist/server.js +5 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# codex-blocker
|
|
2
2
|
|
|
3
|
-
CLI tool and server for Codex Blocker
|
|
3
|
+
CLI tool and server for Codex Blocker -- block distracting websites unless Codex is actively running.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -41,18 +41,18 @@ npx codex-blocker --version
|
|
|
41
41
|
|
|
42
42
|
## How It Works
|
|
43
43
|
|
|
44
|
-
1. **Codex sessions**
|
|
45
|
-
to detect activity. It marks a session
|
|
44
|
+
1. **Codex sessions** -- The server tails Codex session logs under `~/.codex/sessions`
|
|
45
|
+
to detect activity. It marks a session "working" on your prompt and on intermediate
|
|
46
46
|
assistant/tool activity, marks `waiting_for_input` when Codex emits
|
|
47
|
-
`request_user_input`, and marks
|
|
47
|
+
`request_user_input`, and marks "idle" when it sees a terminal assistant reply
|
|
48
48
|
(`phase: "final_answer"`), with legacy fallback support for older Codex logs.
|
|
49
49
|
|
|
50
|
-
2. **Server**
|
|
50
|
+
2. **Server** -- Runs on localhost and:
|
|
51
51
|
- Tracks active Codex sessions
|
|
52
52
|
- Marks sessions "working" when new log lines arrive
|
|
53
53
|
- Broadcasts state via WebSocket to the Chrome extension
|
|
54
54
|
|
|
55
|
-
3. **Extension**
|
|
55
|
+
3. **Extension** -- Connects to the server and:
|
|
56
56
|
- Blocks configured sites when no sessions are working, or when any session is waiting for user input
|
|
57
57
|
- Shows a modal overlay (soft block, not network block)
|
|
58
58
|
- Updates in real-time without page refresh
|
|
@@ -82,7 +82,7 @@ Connect to `ws://localhost:8765/ws` to receive real-time state updates:
|
|
|
82
82
|
## Programmatic Usage
|
|
83
83
|
|
|
84
84
|
```typescript
|
|
85
|
-
import { startServer } from
|
|
85
|
+
import { startServer } from "codex-blocker";
|
|
86
86
|
|
|
87
87
|
// Start on default port (8765)
|
|
88
88
|
startServer();
|
package/dist/bin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_PORT,
|
|
4
4
|
startServer
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OIDV724O.js";
|
|
6
6
|
|
|
7
7
|
// src/bin.ts
|
|
8
8
|
import { createRequire } from "module";
|
|
@@ -39,10 +39,452 @@ function removeCodexSetup() {
|
|
|
39
39
|
console.log("No Codex hooks to remove.");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// src/mobile-network.ts
|
|
43
|
+
import { execFile } from "child_process";
|
|
44
|
+
import { existsSync as existsSync2 } from "fs";
|
|
45
|
+
import { promisify } from "util";
|
|
46
|
+
var execFileAsync = promisify(execFile);
|
|
47
|
+
var WSL_POWERSHELL_PATH = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
|
|
48
|
+
var IPV4_MATCH = /\b((?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3})\b/;
|
|
49
|
+
var ROUTE_SRC_IPV4_MATCH = /\bsrc\s+((?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3})\b/;
|
|
50
|
+
function parseFirstIpv4Candidate(raw) {
|
|
51
|
+
const match = raw.match(IPV4_MATCH);
|
|
52
|
+
return match ? match[1] : null;
|
|
53
|
+
}
|
|
54
|
+
function parseRouteSourceIpv4(raw) {
|
|
55
|
+
const match = raw.match(ROUTE_SRC_IPV4_MATCH);
|
|
56
|
+
return match ? match[1] : null;
|
|
57
|
+
}
|
|
58
|
+
function detectWsl() {
|
|
59
|
+
if (process.platform !== "linux") return false;
|
|
60
|
+
return Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
|
|
61
|
+
}
|
|
62
|
+
function getPowerShellExecutable() {
|
|
63
|
+
if (process.platform === "win32") {
|
|
64
|
+
return "powershell.exe";
|
|
65
|
+
}
|
|
66
|
+
if (detectWsl() && existsSync2(WSL_POWERSHELL_PATH)) {
|
|
67
|
+
return WSL_POWERSHELL_PATH;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
async function runCommand(command, args2) {
|
|
72
|
+
try {
|
|
73
|
+
const { stdout, stderr } = await execFileAsync(command, args2, {
|
|
74
|
+
maxBuffer: 1024 * 1024
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
code: 0,
|
|
78
|
+
stdout,
|
|
79
|
+
stderr
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const failure = error;
|
|
83
|
+
return {
|
|
84
|
+
code: typeof failure.code === "number" ? failure.code : 1,
|
|
85
|
+
stdout: failure.stdout ?? "",
|
|
86
|
+
stderr: failure.stderr ?? failure.message
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function checkDiscovery(url) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: "GET",
|
|
94
|
+
headers: { Accept: "application/json" },
|
|
95
|
+
signal: AbortSignal.timeout(2e3)
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
details: `${response.status} ${response.statusText}`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
details: "reachable"
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
details: message
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function resolvePortProxyConnectAddress() {
|
|
116
|
+
if (!detectWsl()) {
|
|
117
|
+
return "127.0.0.1";
|
|
118
|
+
}
|
|
119
|
+
const routeProbe = await runCommand("sh", [
|
|
120
|
+
"-lc",
|
|
121
|
+
"ip -4 route get 1.1.1.1 2>/dev/null || ip -4 route get 8.8.8.8 2>/dev/null"
|
|
122
|
+
]);
|
|
123
|
+
if (routeProbe.code === 0) {
|
|
124
|
+
const routeSrc = parseRouteSourceIpv4(routeProbe.stdout);
|
|
125
|
+
if (routeSrc) {
|
|
126
|
+
return routeSrc;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const probe = await runCommand("sh", ["-lc", "hostname -I"]);
|
|
130
|
+
if (probe.code !== 0) {
|
|
131
|
+
return "127.0.0.1";
|
|
132
|
+
}
|
|
133
|
+
const ip = parseFirstIpv4Candidate(probe.stdout);
|
|
134
|
+
return ip ?? "127.0.0.1";
|
|
135
|
+
}
|
|
136
|
+
function parseWindowsDiagnostics(rawJson) {
|
|
137
|
+
try {
|
|
138
|
+
const data = JSON.parse(rawJson);
|
|
139
|
+
return {
|
|
140
|
+
profileName: typeof data.profileName === "string" ? data.profileName : null,
|
|
141
|
+
interfaceAlias: typeof data.interfaceAlias === "string" ? data.interfaceAlias : null,
|
|
142
|
+
networkCategory: data.networkCategory === "Public" || data.networkCategory === "Private" || data.networkCategory === "DomainAuthenticated" ? data.networkCategory : "Unknown",
|
|
143
|
+
wifiIp: typeof data.wifiIp === "string" ? data.wifiIp : null,
|
|
144
|
+
hasPortProxy: Boolean(data.hasPortProxy),
|
|
145
|
+
portProxyTarget: typeof data.portProxyTarget === "string" ? data.portProxyTarget : null,
|
|
146
|
+
hasPrivateRule: Boolean(data.hasPrivateRule),
|
|
147
|
+
hasPublicRule: Boolean(data.hasPublicRule),
|
|
148
|
+
localhostReachable: Boolean(data.localhostReachable),
|
|
149
|
+
lanReachable: Boolean(data.lanReachable)
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function toPowerShellEncodedCommand(script) {
|
|
156
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
157
|
+
}
|
|
158
|
+
function buildWindowsDoctorScript(port) {
|
|
159
|
+
return `
|
|
160
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
161
|
+
$port = ${port}
|
|
162
|
+
|
|
163
|
+
$profile = Get-NetConnectionProfile | Where-Object { $_.IPv4Connectivity -ne 'Disconnected' } | Select-Object -First 1
|
|
164
|
+
$wifiIp = $null
|
|
165
|
+
if ($profile) {
|
|
166
|
+
$wifiIp = Get-NetIPAddress -AddressFamily IPv4 |
|
|
167
|
+
Where-Object { $_.InterfaceAlias -eq $profile.InterfaceAlias -and $_.IPAddress -notlike '169.254*' } |
|
|
168
|
+
Select-Object -First 1 -ExpandProperty IPAddress
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
$proxyTable = netsh interface portproxy show v4tov4 | Out-String
|
|
172
|
+
$proxyLine = ($proxyTable -split "\\r?\\n") |
|
|
173
|
+
Where-Object { $_ -match ("^\\s*0\\.0\\.0\\.0\\s+" + $port + "\\s+") } |
|
|
174
|
+
Select-Object -First 1
|
|
175
|
+
$hasPortProxy = $false
|
|
176
|
+
$portProxyTarget = $null
|
|
177
|
+
if ($proxyLine) {
|
|
178
|
+
$parts = ($proxyLine -split "\\s+") | Where-Object { $_ -ne "" }
|
|
179
|
+
if ($parts.Count -ge 4 -and [string]$parts[3] -eq [string]$port) {
|
|
180
|
+
$hasPortProxy = $true
|
|
181
|
+
$portProxyTarget = [string]$parts[2]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
$privateRule = Get-NetFirewallRule -DisplayName "Codex Blocker $port Private LocalSubnet" -ErrorAction SilentlyContinue
|
|
186
|
+
$publicRule = Get-NetFirewallRule -DisplayName "Codex Blocker $port Public LocalSubnet" -ErrorAction SilentlyContinue
|
|
187
|
+
|
|
188
|
+
$localhostReachable = $false
|
|
189
|
+
try {
|
|
190
|
+
Invoke-RestMethod -Uri ("http://127.0.0.1:" + $port + "/mobile/discovery") -Method Get -TimeoutSec 2 | Out-Null
|
|
191
|
+
$localhostReachable = $true
|
|
192
|
+
} catch {}
|
|
193
|
+
|
|
194
|
+
$lanReachable = $false
|
|
195
|
+
if ($wifiIp) {
|
|
196
|
+
try {
|
|
197
|
+
Invoke-RestMethod -Uri ("http://" + $wifiIp + ":" + $port + "/mobile/discovery") -Method Get -TimeoutSec 2 | Out-Null
|
|
198
|
+
$lanReachable = $true
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
[pscustomobject]@{
|
|
203
|
+
profileName = if ($profile) { $profile.Name } else { $null }
|
|
204
|
+
interfaceAlias = if ($profile) { $profile.InterfaceAlias } else { $null }
|
|
205
|
+
networkCategory = if ($profile) { [string]$profile.NetworkCategory } else { "Unknown" }
|
|
206
|
+
wifiIp = $wifiIp
|
|
207
|
+
hasPortProxy = [bool]$hasPortProxy
|
|
208
|
+
portProxyTarget = $portProxyTarget
|
|
209
|
+
hasPrivateRule = [bool]$privateRule
|
|
210
|
+
hasPublicRule = [bool]$publicRule
|
|
211
|
+
localhostReachable = $localhostReachable
|
|
212
|
+
lanReachable = $lanReachable
|
|
213
|
+
} | ConvertTo-Json -Compress
|
|
214
|
+
`.trim();
|
|
215
|
+
}
|
|
216
|
+
function buildWindowsFixScript(port, allowPublicFirewallRule, connectAddress) {
|
|
217
|
+
const allowPublicLiteral = allowPublicFirewallRule ? "$true" : "$false";
|
|
218
|
+
const elevatedCommands = `
|
|
219
|
+
$ErrorActionPreference = 'Stop'
|
|
220
|
+
$port = ${port}
|
|
221
|
+
$allowPublic = ${allowPublicLiteral}
|
|
222
|
+
$connectAddress = "${connectAddress}"
|
|
223
|
+
|
|
224
|
+
netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=$port 2>$null | Out-Null
|
|
225
|
+
netsh interface portproxy delete v4tov4 listenaddress=127.0.0.1 listenport=$port 2>$null | Out-Null
|
|
226
|
+
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=$port connectaddress=$connectAddress connectport=$port
|
|
227
|
+
|
|
228
|
+
if (-not $allowPublic) {
|
|
229
|
+
Remove-NetFirewallRule -DisplayName "Codex Blocker $port Public LocalSubnet" -ErrorAction SilentlyContinue | Out-Null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
$profiles = @('Private')
|
|
233
|
+
if ($allowPublic) {
|
|
234
|
+
$profiles += 'Public'
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
foreach ($profile in $profiles) {
|
|
238
|
+
$ruleName = "Codex Blocker $port $profile LocalSubnet"
|
|
239
|
+
if (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue) {
|
|
240
|
+
Remove-NetFirewallRule -DisplayName $ruleName | Out-Null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $port -Profile $profile -RemoteAddress LocalSubnet | Out-Null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
Write-Output "Configured portproxy + firewall for port $port (allowPublic=$allowPublic, connectAddress=$connectAddress)"
|
|
247
|
+
netsh interface portproxy show v4tov4
|
|
248
|
+
`.trim();
|
|
249
|
+
return buildWindowsElevatedScript(elevatedCommands);
|
|
250
|
+
}
|
|
251
|
+
function buildWindowsRemoveScript(port) {
|
|
252
|
+
const elevatedCommands = `
|
|
253
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
254
|
+
$port = ${port}
|
|
255
|
+
|
|
256
|
+
netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=$port 2>$null | Out-Null
|
|
257
|
+
netsh interface portproxy delete v4tov4 listenaddress=127.0.0.1 listenport=$port 2>$null | Out-Null
|
|
258
|
+
netsh interface portproxy delete v4tov4 listenaddress=:: listenport=$port 2>$null | Out-Null
|
|
259
|
+
|
|
260
|
+
Remove-NetFirewallRule -DisplayName "Codex Blocker $port Private LocalSubnet" -ErrorAction SilentlyContinue | Out-Null
|
|
261
|
+
Remove-NetFirewallRule -DisplayName "Codex Blocker $port Public LocalSubnet" -ErrorAction SilentlyContinue | Out-Null
|
|
262
|
+
Remove-NetFirewallRule -DisplayName "Codex Blocker $port" -ErrorAction SilentlyContinue | Out-Null
|
|
263
|
+
|
|
264
|
+
Write-Output "Removed Codex Blocker networking setup for port $port"
|
|
265
|
+
netsh interface portproxy show v4tov4
|
|
266
|
+
`.trim();
|
|
267
|
+
return buildWindowsElevatedScript(elevatedCommands);
|
|
268
|
+
}
|
|
269
|
+
function buildWindowsElevatedScript(elevatedCommands) {
|
|
270
|
+
const encoded = toPowerShellEncodedCommand(elevatedCommands);
|
|
271
|
+
return `
|
|
272
|
+
$ErrorActionPreference = 'Stop'
|
|
273
|
+
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
274
|
+
|
|
275
|
+
if (-not $isAdmin) {
|
|
276
|
+
$proc = Start-Process -FilePath "powershell.exe" -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded}" -Wait -PassThru
|
|
277
|
+
exit $proc.ExitCode
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
${elevatedCommands}
|
|
281
|
+
`.trim();
|
|
282
|
+
}
|
|
283
|
+
async function runPowerShellScript(powershellExe, script) {
|
|
284
|
+
return runCommand(powershellExe, [
|
|
285
|
+
"-NoProfile",
|
|
286
|
+
"-ExecutionPolicy",
|
|
287
|
+
"Bypass",
|
|
288
|
+
"-Command",
|
|
289
|
+
script
|
|
290
|
+
]);
|
|
291
|
+
}
|
|
292
|
+
function assessWindowsDiagnostics(diagnostics, port, options) {
|
|
293
|
+
const allowPublicFirewallRule = options?.allowPublicFirewallRule ?? false;
|
|
294
|
+
const checks = [];
|
|
295
|
+
checks.push({
|
|
296
|
+
name: "Port proxy (0.0.0.0 -> target)",
|
|
297
|
+
ok: diagnostics.hasPortProxy,
|
|
298
|
+
details: diagnostics.hasPortProxy ? `configured for TCP ${port} -> ${diagnostics.portProxyTarget ?? "unknown"}` : `missing for TCP ${port}`
|
|
299
|
+
});
|
|
300
|
+
const category = diagnostics.networkCategory;
|
|
301
|
+
let profileRuleOk = false;
|
|
302
|
+
let profileDetails = `private=${diagnostics.hasPrivateRule}, public=${diagnostics.hasPublicRule}`;
|
|
303
|
+
if (category === "Public") {
|
|
304
|
+
profileRuleOk = allowPublicFirewallRule ? diagnostics.hasPublicRule : false;
|
|
305
|
+
profileDetails = allowPublicFirewallRule ? `private=${diagnostics.hasPrivateRule}, public=${diagnostics.hasPublicRule}` : "public profile is locked down by default; pass --allow-public if you need LAN access on public Wi-Fi";
|
|
306
|
+
} else if (category === "Private") {
|
|
307
|
+
profileRuleOk = diagnostics.hasPrivateRule;
|
|
308
|
+
} else {
|
|
309
|
+
profileRuleOk = diagnostics.hasPrivateRule || diagnostics.hasPublicRule;
|
|
310
|
+
}
|
|
311
|
+
checks.push({
|
|
312
|
+
name: `Firewall rule for ${category} profile`,
|
|
313
|
+
ok: profileRuleOk,
|
|
314
|
+
details: profileDetails
|
|
315
|
+
});
|
|
316
|
+
checks.push({
|
|
317
|
+
name: "Windows loopback access",
|
|
318
|
+
ok: diagnostics.localhostReachable,
|
|
319
|
+
details: diagnostics.localhostReachable ? "http://127.0.0.1 reachable" : "http://127.0.0.1 unreachable"
|
|
320
|
+
});
|
|
321
|
+
checks.push({
|
|
322
|
+
name: "Windows LAN access",
|
|
323
|
+
ok: diagnostics.lanReachable,
|
|
324
|
+
details: diagnostics.wifiIp ? diagnostics.lanReachable ? `http://${diagnostics.wifiIp}:${port} reachable` : `http://${diagnostics.wifiIp}:${port} unreachable` : "Wi-Fi IPv4 not detected"
|
|
325
|
+
});
|
|
326
|
+
const recommendations = [];
|
|
327
|
+
const fixCommand = allowPublicFirewallRule ? `Run: npx codex-blocker mobile:fix --port ${port} --allow-public` : `Run: npx codex-blocker mobile:fix --port ${port}`;
|
|
328
|
+
if (!diagnostics.hasPortProxy || !profileRuleOk) {
|
|
329
|
+
recommendations.push(
|
|
330
|
+
category === "Public" && !allowPublicFirewallRule ? `Run: npx codex-blocker mobile:fix --port ${port} --allow-public` : fixCommand
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (category === "Public" && !allowPublicFirewallRule) {
|
|
334
|
+
recommendations.push(
|
|
335
|
+
"Public Wi-Fi profile detected. Either switch this network to Private or use --allow-public for mobile LAN access."
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
if (diagnostics.localhostReachable && diagnostics.hasPortProxy && profileRuleOk && !diagnostics.lanReachable) {
|
|
339
|
+
recommendations.push(
|
|
340
|
+
"Check router/client isolation or guest Wi-Fi settings (phone may be blocked from peer LAN devices)."
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const ok = checks.every((check) => check.ok);
|
|
344
|
+
return {
|
|
345
|
+
checks,
|
|
346
|
+
recommendations,
|
|
347
|
+
ok
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async function runMobileDoctor(port, options) {
|
|
351
|
+
const allowPublicFirewallRule = options?.allowPublicFirewallRule ?? false;
|
|
352
|
+
const report = {
|
|
353
|
+
ok: true,
|
|
354
|
+
checks: [],
|
|
355
|
+
recommendations: []
|
|
356
|
+
};
|
|
357
|
+
const localCheck = await checkDiscovery(`http://127.0.0.1:${port}/mobile/discovery`);
|
|
358
|
+
report.checks.push({
|
|
359
|
+
name: "Local server from current shell",
|
|
360
|
+
ok: localCheck.ok,
|
|
361
|
+
details: localCheck.details
|
|
362
|
+
});
|
|
363
|
+
if (!localCheck.ok) {
|
|
364
|
+
report.recommendations.push("Start the server: npx codex-blocker");
|
|
365
|
+
}
|
|
366
|
+
const powershellExe = getPowerShellExecutable();
|
|
367
|
+
if (!powershellExe) {
|
|
368
|
+
report.recommendations.push(
|
|
369
|
+
"PowerShell not found. Windows-specific diagnostics are unavailable in this environment."
|
|
370
|
+
);
|
|
371
|
+
} else {
|
|
372
|
+
const doctorScript = buildWindowsDoctorScript(port);
|
|
373
|
+
const doctorResult = await runPowerShellScript(powershellExe, doctorScript);
|
|
374
|
+
if (doctorResult.code !== 0) {
|
|
375
|
+
report.checks.push({
|
|
376
|
+
name: "Windows diagnostics execution",
|
|
377
|
+
ok: false,
|
|
378
|
+
details: doctorResult.stderr.trim() || "failed"
|
|
379
|
+
});
|
|
380
|
+
report.recommendations.push(
|
|
381
|
+
"Failed to query Windows networking state. Try running mobile:doctor from a Windows terminal."
|
|
382
|
+
);
|
|
383
|
+
} else {
|
|
384
|
+
const diagnostics = parseWindowsDiagnostics(doctorResult.stdout);
|
|
385
|
+
if (!diagnostics) {
|
|
386
|
+
report.checks.push({
|
|
387
|
+
name: "Windows diagnostics parsing",
|
|
388
|
+
ok: false,
|
|
389
|
+
details: "Unexpected PowerShell output"
|
|
390
|
+
});
|
|
391
|
+
report.recommendations.push(
|
|
392
|
+
"Unable to parse Windows diagnostics output. Re-run with a clean shell."
|
|
393
|
+
);
|
|
394
|
+
} else {
|
|
395
|
+
const windowsAssessment = assessWindowsDiagnostics(diagnostics, port, {
|
|
396
|
+
allowPublicFirewallRule
|
|
397
|
+
});
|
|
398
|
+
for (const check of windowsAssessment.checks) {
|
|
399
|
+
report.checks.push(check);
|
|
400
|
+
}
|
|
401
|
+
report.recommendations.push(...windowsAssessment.recommendations);
|
|
402
|
+
if (localCheck.ok && !diagnostics.localhostReachable) {
|
|
403
|
+
report.recommendations.push(
|
|
404
|
+
"Windows cannot reach the forwarded localhost port. Re-run mobile:fix so portproxy can target the current WSL IP."
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
report.ok = report.checks.every((check) => check.ok);
|
|
411
|
+
console.log("\nCodex Blocker Mobile Doctor");
|
|
412
|
+
console.log(`Port: ${port}`);
|
|
413
|
+
if (detectWsl()) {
|
|
414
|
+
console.log("Environment: WSL");
|
|
415
|
+
}
|
|
416
|
+
console.log("");
|
|
417
|
+
for (const check of report.checks) {
|
|
418
|
+
const icon = check.ok ? "[OK]" : "[FAIL]";
|
|
419
|
+
console.log(`${icon} ${check.name} - ${check.details}`);
|
|
420
|
+
}
|
|
421
|
+
console.log("");
|
|
422
|
+
if (report.recommendations.length > 0) {
|
|
423
|
+
console.log("Recommendations:");
|
|
424
|
+
for (const recommendation of report.recommendations) {
|
|
425
|
+
console.log(`- ${recommendation}`);
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
console.log("No issues detected.");
|
|
429
|
+
}
|
|
430
|
+
console.log("");
|
|
431
|
+
return report.ok;
|
|
432
|
+
}
|
|
433
|
+
async function runMobileFix(port, options) {
|
|
434
|
+
return runMobileFixWithOptions(port, options);
|
|
435
|
+
}
|
|
436
|
+
async function runMobileFixWithOptions(port, options) {
|
|
437
|
+
const allowPublicFirewallRule = options?.allowPublicFirewallRule ?? false;
|
|
438
|
+
const powershellExe = getPowerShellExecutable();
|
|
439
|
+
if (!powershellExe) {
|
|
440
|
+
console.error("PowerShell is required for mobile:fix but was not found.");
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
const connectAddress = await resolvePortProxyConnectAddress();
|
|
444
|
+
const script = buildWindowsFixScript(port, allowPublicFirewallRule, connectAddress);
|
|
445
|
+
const result = await runPowerShellScript(powershellExe, script);
|
|
446
|
+
if (result.code !== 0) {
|
|
447
|
+
const stderr = result.stderr.trim() || "unknown error";
|
|
448
|
+
console.error(`mobile:fix failed: ${stderr}`);
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
const stdout = result.stdout.trim();
|
|
452
|
+
if (stdout) {
|
|
453
|
+
console.log(stdout);
|
|
454
|
+
}
|
|
455
|
+
console.log("\nRunning doctor after fix...\n");
|
|
456
|
+
return runMobileDoctor(port, { allowPublicFirewallRule });
|
|
457
|
+
}
|
|
458
|
+
async function runMobileRemove(port) {
|
|
459
|
+
const powershellExe = getPowerShellExecutable();
|
|
460
|
+
if (!powershellExe) {
|
|
461
|
+
console.error("PowerShell is required for mobile:remove but was not found.");
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const script = buildWindowsRemoveScript(port);
|
|
465
|
+
const result = await runPowerShellScript(powershellExe, script);
|
|
466
|
+
if (result.code !== 0) {
|
|
467
|
+
const stderr = result.stderr.trim() || "unknown error";
|
|
468
|
+
console.error(`mobile:remove failed: ${stderr}`);
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
const stdout = result.stdout.trim();
|
|
472
|
+
if (stdout) {
|
|
473
|
+
console.log(stdout);
|
|
474
|
+
}
|
|
475
|
+
console.log("\nRunning doctor after remove...\n");
|
|
476
|
+
await runMobileDoctor(port);
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
|
|
42
480
|
// src/bin.ts
|
|
43
481
|
var require2 = createRequire(import.meta.url);
|
|
44
482
|
var { version } = require2("../package.json");
|
|
45
483
|
var args = process.argv.slice(2);
|
|
484
|
+
function isWindowsOrWslRuntime() {
|
|
485
|
+
if (process.platform === "win32") return true;
|
|
486
|
+
return Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
|
|
487
|
+
}
|
|
46
488
|
function prompt(question) {
|
|
47
489
|
const rl = createInterface({
|
|
48
490
|
input: process.stdin,
|
|
@@ -61,19 +503,42 @@ Codex Blocker - Block distracting sites when Codex isn't working
|
|
|
61
503
|
|
|
62
504
|
Usage:
|
|
63
505
|
npx codex-blocker [options]
|
|
506
|
+
npx codex-blocker mobile:doctor [options]
|
|
507
|
+
npx codex-blocker mobile:fix [options]
|
|
508
|
+
npx codex-blocker mobile:remove [options]
|
|
64
509
|
|
|
65
510
|
Options:
|
|
66
|
-
--setup
|
|
67
|
-
--remove
|
|
68
|
-
--port
|
|
69
|
-
--
|
|
70
|
-
--
|
|
511
|
+
--setup Show Codex setup info
|
|
512
|
+
--remove Remove Codex setup (no-op)
|
|
513
|
+
--port Server port (default: ${DEFAULT_PORT})
|
|
514
|
+
--bind Server bind host
|
|
515
|
+
--extension-only Disable mobile LAN discovery and pairing endpoints
|
|
516
|
+
--mobile-name Mobile discovery name
|
|
517
|
+
--allow-public Allow Public-profile firewall access for mobile:fix
|
|
518
|
+
--version Show version
|
|
519
|
+
--help Show this help message
|
|
71
520
|
|
|
72
521
|
Examples:
|
|
73
522
|
npx codex-blocker # Start the server
|
|
74
523
|
npx codex-blocker --port 9000
|
|
524
|
+
npx codex-blocker --extension-only
|
|
525
|
+
npx codex-blocker mobile:doctor
|
|
75
526
|
`);
|
|
76
527
|
}
|
|
528
|
+
function readOption(name) {
|
|
529
|
+
const index = args.indexOf(name);
|
|
530
|
+
return index === -1 ? void 0 : args[index + 1];
|
|
531
|
+
}
|
|
532
|
+
function readPort() {
|
|
533
|
+
const rawPort = readOption("--port");
|
|
534
|
+
if (!rawPort) return DEFAULT_PORT;
|
|
535
|
+
const parsed = parseInt(rawPort, 10);
|
|
536
|
+
if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {
|
|
537
|
+
return parsed;
|
|
538
|
+
}
|
|
539
|
+
console.error("Invalid port number");
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
77
542
|
async function main() {
|
|
78
543
|
if (args.includes("--help") || args.includes("-h")) {
|
|
79
544
|
printHelp();
|
|
@@ -91,16 +556,20 @@ async function main() {
|
|
|
91
556
|
removeCodexSetup();
|
|
92
557
|
process.exit(0);
|
|
93
558
|
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
559
|
+
const port = readPort();
|
|
560
|
+
const command = args[0];
|
|
561
|
+
if (command === "mobile:doctor") {
|
|
562
|
+
process.exit(await runMobileDoctor(port, {
|
|
563
|
+
allowPublicFirewallRule: args.includes("--allow-public")
|
|
564
|
+
}) ? 0 : 1);
|
|
565
|
+
}
|
|
566
|
+
if (command === "mobile:fix") {
|
|
567
|
+
process.exit(await runMobileFix(port, {
|
|
568
|
+
allowPublicFirewallRule: args.includes("--allow-public")
|
|
569
|
+
}) ? 0 : 1);
|
|
570
|
+
}
|
|
571
|
+
if (command === "mobile:remove") {
|
|
572
|
+
process.exit(await runMobileRemove(port) ? 0 : 1);
|
|
104
573
|
}
|
|
105
574
|
if (!isCodexAvailable()) {
|
|
106
575
|
console.log("Codex sessions directory not found yet.");
|
|
@@ -109,6 +578,13 @@ async function main() {
|
|
|
109
578
|
console.log("");
|
|
110
579
|
}
|
|
111
580
|
}
|
|
112
|
-
|
|
581
|
+
const extensionOnly = args.includes("--extension-only");
|
|
582
|
+
const bindHost = readOption("--bind") ?? (extensionOnly ? "127.0.0.1" : isWindowsOrWslRuntime() ? "0.0.0.0" : "127.0.0.1");
|
|
583
|
+
startServer(port, {
|
|
584
|
+
mobile: !extensionOnly,
|
|
585
|
+
bindHost,
|
|
586
|
+
mobileServiceName: readOption("--mobile-name"),
|
|
587
|
+
mobileQrOutput: !extensionOnly
|
|
588
|
+
});
|
|
113
589
|
}
|
|
114
590
|
main();
|