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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # codex-blocker
2
2
 
3
- CLI tool and server for Codex Blocker block distracting websites unless Codex is actively running.
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** The server tails Codex session logs under `~/.codex/sessions`
45
- to detect activity. It marks a session working on your prompt and on intermediate
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 idle when it sees a terminal assistant reply
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** Runs on localhost and:
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** Connects to the server and:
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 'codex-blocker';
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-KNFNSOAX.js";
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 Show Codex setup info
67
- --remove Remove Codex setup (no-op)
68
- --port Server port (default: ${DEFAULT_PORT})
69
- --version Show version
70
- --help Show this help message
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
- let port = DEFAULT_PORT;
95
- const portIndex = args.indexOf("--port");
96
- if (portIndex !== -1 && args[portIndex + 1]) {
97
- const parsed = parseInt(args[portIndex + 1], 10);
98
- if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {
99
- port = parsed;
100
- } else {
101
- console.error("Invalid port number");
102
- process.exit(1);
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
- startServer(port);
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();