codex-blocker 0.1.3 → 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/dist/bin.js +487 -18
- package/dist/{chunk-ZDUKZXM4.js → chunk-OIDV724O.js} +3 -3
- package/dist/server.d.ts +3 -5
- package/dist/server.js +1 -1
- package/package.json +2 -1
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,6 +39,444 @@ 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");
|
|
@@ -65,19 +503,42 @@ Codex Blocker - Block distracting sites when Codex isn't working
|
|
|
65
503
|
|
|
66
504
|
Usage:
|
|
67
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]
|
|
68
509
|
|
|
69
510
|
Options:
|
|
70
|
-
--setup
|
|
71
|
-
--remove
|
|
72
|
-
--port
|
|
73
|
-
--
|
|
74
|
-
--
|
|
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
|
|
75
520
|
|
|
76
521
|
Examples:
|
|
77
522
|
npx codex-blocker # Start the server
|
|
78
523
|
npx codex-blocker --port 9000
|
|
524
|
+
npx codex-blocker --extension-only
|
|
525
|
+
npx codex-blocker mobile:doctor
|
|
79
526
|
`);
|
|
80
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
|
+
}
|
|
81
542
|
async function main() {
|
|
82
543
|
if (args.includes("--help") || args.includes("-h")) {
|
|
83
544
|
printHelp();
|
|
@@ -95,16 +556,20 @@ async function main() {
|
|
|
95
556
|
removeCodexSetup();
|
|
96
557
|
process.exit(0);
|
|
97
558
|
}
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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);
|
|
108
573
|
}
|
|
109
574
|
if (!isCodexAvailable()) {
|
|
110
575
|
console.log("Codex sessions directory not found yet.");
|
|
@@ -113,9 +578,13 @@ async function main() {
|
|
|
113
578
|
console.log("");
|
|
114
579
|
}
|
|
115
580
|
}
|
|
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");
|
|
116
583
|
startServer(port, {
|
|
117
|
-
mobile:
|
|
118
|
-
bindHost
|
|
584
|
+
mobile: !extensionOnly,
|
|
585
|
+
bindHost,
|
|
586
|
+
mobileServiceName: readOption("--mobile-name"),
|
|
587
|
+
mobileQrOutput: !extensionOnly
|
|
119
588
|
});
|
|
120
589
|
}
|
|
121
590
|
main();
|
|
@@ -617,8 +617,8 @@ function isTrustedChromeExtensionOrigin(origin) {
|
|
|
617
617
|
return false;
|
|
618
618
|
}
|
|
619
619
|
}
|
|
620
|
-
function canBootstrapExtensionToken(providedToken, allowExtensionOrigin) {
|
|
621
|
-
return Boolean(providedToken && allowExtensionOrigin);
|
|
620
|
+
function canBootstrapExtensionToken(providedToken, allowExtensionOrigin, clientIp) {
|
|
621
|
+
return Boolean(providedToken && allowExtensionOrigin && isLoopbackClientIp(clientIp));
|
|
622
622
|
}
|
|
623
623
|
function isLoopbackClientIp(clientIp) {
|
|
624
624
|
if (!clientIp) return false;
|
|
@@ -1014,7 +1014,7 @@ ${terminalQr}
|
|
|
1014
1014
|
ws.close(1008, "Unauthorized");
|
|
1015
1015
|
return;
|
|
1016
1016
|
}
|
|
1017
|
-
} else if (canBootstrapExtensionToken(providedToken, allowExtensionOrigin)) {
|
|
1017
|
+
} else if (canBootstrapExtensionToken(providedToken, allowExtensionOrigin, clientIp)) {
|
|
1018
1018
|
authToken = providedToken;
|
|
1019
1019
|
} else {
|
|
1020
1020
|
ws.close(1008, "Unauthorized");
|
package/dist/server.d.ts
CHANGED
|
@@ -3,17 +3,15 @@ interface CodexActivity {
|
|
|
3
3
|
cwd?: string;
|
|
4
4
|
idleTimeoutMs?: number;
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
type StateMessage = {
|
|
7
8
|
type: "state";
|
|
8
9
|
blocked: boolean;
|
|
9
10
|
sessions: number;
|
|
10
11
|
working: number;
|
|
11
12
|
waitingForInput: number;
|
|
12
|
-
} | {
|
|
13
|
-
type: "pong";
|
|
14
13
|
};
|
|
15
|
-
|
|
16
|
-
type StateChangeCallback = (message: ServerMessage) => void;
|
|
14
|
+
type StateChangeCallback = (message: StateMessage) => void;
|
|
17
15
|
declare class SessionState {
|
|
18
16
|
private sessions;
|
|
19
17
|
private listeners;
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-blocker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Automatically blocks distracting websites unless Codex is actively running. Forked from Theo Browne's (T3) Claude Blocker",
|
|
5
5
|
"author": "Adam Blumoff ",
|
|
6
6
|
"repository": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"ws": "^8.18.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@codex-blocker/shared": "workspace:*",
|
|
34
35
|
"@types/node": "^22.10.2",
|
|
35
36
|
"@types/qrcode": "^1.5.5",
|
|
36
37
|
"@types/ws": "^8.5.13",
|