@khemsok/tunl 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.
Files changed (2) hide show
  1. package/dist/cli.js +288 -63
  2. package/package.json +6 -1
package/dist/cli.js CHANGED
@@ -27633,6 +27633,11 @@ var init_chunk_bdqvmfwv = __esm(async () => {
27633
27633
  import_react_devtools_core.default.connectToDevTools();
27634
27634
  });
27635
27635
 
27636
+ // src/cli.tsx
27637
+ import { existsSync as existsSync9, unlinkSync as unlinkSync4 } from "fs";
27638
+ import { homedir as homedir5 } from "os";
27639
+ import { join as join6 } from "path";
27640
+
27636
27641
  // node_modules/@opentui/core/index-qr7b6cvh.js
27637
27642
  import { Buffer as Buffer2 } from "buffer";
27638
27643
  import { EventEmitter } from "events";
@@ -55095,9 +55100,6 @@ var useTerminalDimensions = () => {
55095
55100
  return dimensions;
55096
55101
  };
55097
55102
 
55098
- // src/cli.tsx
55099
- import { execSync as execSync3 } from "child_process";
55100
-
55101
55103
  // src/app.tsx
55102
55104
  var import_react21 = __toESM(require_react(), 1);
55103
55105
 
@@ -56692,14 +56694,136 @@ function recordSession(durationMinutes) {
56692
56694
  }
56693
56695
 
56694
56696
  // src/lib/blocker.ts
56697
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
56698
+ import { execSync as execSync2 } from "child_process";
56699
+ import { homedir as homedir3 } from "os";
56700
+ import { join as join4 } from "path";
56701
+
56702
+ // src/lib/pf.ts
56695
56703
  import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
56696
56704
  import { execSync } from "child_process";
56697
56705
  import { homedir as homedir2 } from "os";
56698
56706
  import { join as join3 } from "path";
56707
+ var PF_ANCHOR_PATH = "/etc/pf.anchors/tunl";
56708
+ var PF_CONF_PATH = "/etc/pf.conf";
56709
+ var PF_TOKEN_PATH = join3(homedir2(), ".tunl-pf-token");
56710
+ var ANCHOR_NAME = "tunl";
56711
+ function isValidDomain(domain) {
56712
+ return /^[a-zA-Z0-9.-]+$/.test(domain);
56713
+ }
56714
+ function digRecords(domain, recordType) {
56715
+ try {
56716
+ return execSync(`dig +short ${recordType} ${domain} 2>/dev/null`, {
56717
+ encoding: "utf-8",
56718
+ timeout: 5000
56719
+ }).trim().split(`
56720
+ `);
56721
+ } catch {
56722
+ return [];
56723
+ }
56724
+ }
56725
+ function resolveIPs(domain) {
56726
+ if (!isValidDomain(domain))
56727
+ return [];
56728
+ const v4 = digRecords(domain, "A").filter((line) => /^\d+\.\d+\.\d+\.\d+$/.test(line));
56729
+ const v6 = digRecords(domain, "AAAA").filter((line) => line.includes(":"));
56730
+ return [...v4, ...v6];
56731
+ }
56732
+ function generateRules(sites) {
56733
+ const lines = [
56734
+ "# tunl packet filter rules",
56735
+ "set block-policy drop",
56736
+ 'set fingerprints "/etc/pf.os"',
56737
+ "set skip on lo0",
56738
+ ""
56739
+ ];
56740
+ const allDomains = sites.flatMap((s) => [s, `www.${s}`]);
56741
+ const seenIPs = new Set;
56742
+ for (const domain of allDomains) {
56743
+ const ips = resolveIPs(domain);
56744
+ for (const ip of ips) {
56745
+ if (seenIPs.has(ip))
56746
+ continue;
56747
+ seenIPs.add(ip);
56748
+ lines.push(`block return out proto tcp from any to ${ip}`);
56749
+ lines.push(`block return out proto udp from any to ${ip}`);
56750
+ }
56751
+ }
56752
+ return lines.join(`
56753
+ `) + `
56754
+ `;
56755
+ }
56756
+ function addAnchorToConf() {
56757
+ const conf = readFileSync2(PF_CONF_PATH, "utf-8");
56758
+ if (conf.includes(`/etc/pf.anchors/${ANCHOR_NAME}`))
56759
+ return;
56760
+ const addition = `
56761
+ anchor "${ANCHOR_NAME}"
56762
+ load anchor "${ANCHOR_NAME}" from "/etc/pf.anchors/${ANCHOR_NAME}"
56763
+ `;
56764
+ const newConf = conf.trimEnd() + addition;
56765
+ const tmpConf = join3(homedir2(), ".tunl-pf-conf.tmp");
56766
+ writeFileSync2(tmpConf, newConf);
56767
+ execSync(`sudo cp "${tmpConf}" ${PF_CONF_PATH}`);
56768
+ unlinkSync(tmpConf);
56769
+ }
56770
+ function removeAnchorFromConf() {
56771
+ const conf = readFileSync2(PF_CONF_PATH, "utf-8");
56772
+ if (!conf.includes(ANCHOR_NAME))
56773
+ return;
56774
+ const cleaned = conf.split(`
56775
+ `).filter((line) => !line.includes(ANCHOR_NAME)).join(`
56776
+ `).replace(/\n{3,}/g, `
56777
+
56778
+ `).trimEnd() + `
56779
+ `;
56780
+ const tmpConf = join3(homedir2(), ".tunl-pf-conf.tmp");
56781
+ writeFileSync2(tmpConf, cleaned);
56782
+ execSync(`sudo cp "${tmpConf}" ${PF_CONF_PATH}`);
56783
+ unlinkSync(tmpConf);
56784
+ }
56785
+ function blockPF(sites) {
56786
+ const rules = generateRules(sites);
56787
+ const tmpAnchor = join3(homedir2(), ".tunl-pf-anchor.tmp");
56788
+ writeFileSync2(tmpAnchor, rules);
56789
+ execSync(`sudo cp "${tmpAnchor}" ${PF_ANCHOR_PATH}`);
56790
+ unlinkSync(tmpAnchor);
56791
+ addAnchorToConf();
56792
+ const output = execSync(`sudo pfctl -E -f ${PF_CONF_PATH} -F states 2>&1`, { encoding: "utf-8" });
56793
+ const tokenMatch = output.match(/Token\s*:\s*(\S+)/);
56794
+ if (tokenMatch) {
56795
+ writeFileSync2(PF_TOKEN_PATH, tokenMatch[1]);
56796
+ }
56797
+ }
56798
+ function unblockPF() {
56799
+ try {
56800
+ const tmpEmpty = join3(homedir2(), ".tunl-pf-empty.tmp");
56801
+ writeFileSync2(tmpEmpty, "");
56802
+ execSync(`sudo cp "${tmpEmpty}" ${PF_ANCHOR_PATH}`);
56803
+ unlinkSync(tmpEmpty);
56804
+ } catch {}
56805
+ try {
56806
+ removeAnchorFromConf();
56807
+ } catch {}
56808
+ try {
56809
+ if (existsSync5(PF_TOKEN_PATH)) {
56810
+ const token = readFileSync2(PF_TOKEN_PATH, "utf-8").trim();
56811
+ if (token) {
56812
+ execSync(`sudo pfctl -X ${token} -f ${PF_CONF_PATH} 2>/dev/null`);
56813
+ }
56814
+ unlinkSync(PF_TOKEN_PATH);
56815
+ }
56816
+ } catch {}
56817
+ }
56818
+ function hasStalePFBlock() {
56819
+ return existsSync5(PF_TOKEN_PATH);
56820
+ }
56821
+
56822
+ // src/lib/blocker.ts
56699
56823
  var HOSTS_PATH = "/etc/hosts";
56700
56824
  var START_MARKER = "# --- tunl start ---";
56701
56825
  var END_MARKER = "# --- tunl end ---";
56702
- var PID_PATH = join3(homedir2(), ".tunl.pid");
56826
+ var PID_PATH = join4(homedir3(), ".tunl.pid");
56703
56827
  function validateSite(site) {
56704
56828
  return /^[a-zA-Z0-9.-]+$/.test(site);
56705
56829
  }
@@ -56712,41 +56836,41 @@ function blockHosts(sites) {
56712
56836
  `:: www.${s}`
56713
56837
  ]).join(`
56714
56838
  `);
56715
- const currentHosts = readFileSync2(HOSTS_PATH, "utf-8").trimEnd();
56839
+ const currentHosts = readFileSync3(HOSTS_PATH, "utf-8").trimEnd();
56716
56840
  const block = `${currentHosts}
56717
56841
  ${START_MARKER}
56718
56842
  ${entries}
56719
56843
  ${END_MARKER}
56720
56844
  `;
56721
- const tmpPath = join3(homedir2(), ".tunl-block.tmp");
56722
- writeFileSync2(tmpPath, block);
56723
- execSync(`sudo cp "${tmpPath}" ${HOSTS_PATH}`);
56724
- unlinkSync(tmpPath);
56845
+ const tmpPath = join4(homedir3(), ".tunl-block.tmp");
56846
+ writeFileSync3(tmpPath, block);
56847
+ execSync2(`sudo cp "${tmpPath}" ${HOSTS_PATH}`);
56848
+ unlinkSync2(tmpPath);
56725
56849
  }
56726
56850
  function unblockHosts() {
56727
56851
  try {
56728
- const hosts = readFileSync2(HOSTS_PATH, "utf-8");
56852
+ const hosts = readFileSync3(HOSTS_PATH, "utf-8");
56729
56853
  const startIdx = hosts.indexOf(START_MARKER);
56730
56854
  const endIdx = hosts.indexOf(END_MARKER);
56731
56855
  if (startIdx === -1 || endIdx === -1)
56732
56856
  return;
56733
56857
  const cleaned = (hosts.slice(0, startIdx) + hosts.slice(endIdx + END_MARKER.length)).trimEnd() + `
56734
56858
  `;
56735
- const tmpPath = join3(homedir2(), ".tunl-hosts.tmp");
56736
- writeFileSync2(tmpPath, cleaned);
56737
- execSync(`sudo cp "${tmpPath}" ${HOSTS_PATH}`);
56738
- unlinkSync(tmpPath);
56859
+ const tmpPath = join4(homedir3(), ".tunl-hosts.tmp");
56860
+ writeFileSync3(tmpPath, cleaned);
56861
+ execSync2(`sudo cp "${tmpPath}" ${HOSTS_PATH}`);
56862
+ unlinkSync2(tmpPath);
56739
56863
  } catch {}
56740
56864
  }
56741
56865
  function flushDNS() {
56742
56866
  try {
56743
- execSync("sudo dscacheutil -flushcache 2>/dev/null");
56867
+ execSync2("sudo dscacheutil -flushcache 2>/dev/null");
56744
56868
  } catch {}
56745
56869
  try {
56746
- execSync("sudo killall -HUP mDNSResponder 2>/dev/null");
56870
+ execSync2("sudo killall -HUP mDNSResponder 2>/dev/null");
56747
56871
  } catch {}
56748
56872
  try {
56749
- execSync("sudo killall mDNSResponderHelper 2>/dev/null");
56873
+ execSync2("sudo killall mDNSResponderHelper 2>/dev/null");
56750
56874
  } catch {}
56751
56875
  }
56752
56876
  function blockSites(sites) {
@@ -56754,22 +56878,33 @@ function blockSites(sites) {
56754
56878
  const validSites = sites.filter(validateSite);
56755
56879
  if (validSites.length === 0)
56756
56880
  return;
56757
- writeFileSync2(PID_PATH, String(process.pid));
56881
+ writeFileSync3(PID_PATH, String(process.pid));
56882
+ try {
56883
+ blockPF(validSites);
56884
+ } catch {}
56758
56885
  blockHosts(validSites);
56759
56886
  flushDNS();
56760
56887
  }
56761
56888
  function unblockSites() {
56762
56889
  unblockHosts();
56890
+ try {
56891
+ unblockPF();
56892
+ } catch {}
56763
56893
  flushDNS();
56764
56894
  try {
56765
- if (existsSync5(PID_PATH))
56766
- unlinkSync(PID_PATH);
56895
+ if (existsSync6(PID_PATH))
56896
+ unlinkSync2(PID_PATH);
56767
56897
  } catch {}
56768
56898
  }
56769
56899
  function cleanupStaleBlocks() {
56770
- if (!existsSync5(PID_PATH))
56900
+ if (hasStalePFBlock()) {
56901
+ try {
56902
+ unblockPF();
56903
+ } catch {}
56904
+ }
56905
+ if (!existsSync6(PID_PATH))
56771
56906
  return;
56772
- const pid = parseInt(readFileSync2(PID_PATH, "utf-8").trim());
56907
+ const pid = parseInt(readFileSync3(PID_PATH, "utf-8").trim());
56773
56908
  try {
56774
56909
  process.kill(pid, 0);
56775
56910
  } catch {
@@ -57167,10 +57302,10 @@ function TimerStatusMessage({
57167
57302
  }
57168
57303
 
57169
57304
  // src/lib/browser-dns.ts
57170
- import { existsSync as existsSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
57171
- import { execSync as execSync2 } from "child_process";
57172
- import { homedir as homedir3 } from "os";
57173
- import { join as join4 } from "path";
57305
+ import { existsSync as existsSync7, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
57306
+ import { execSync as execSync3 } from "child_process";
57307
+ import { homedir as homedir4 } from "os";
57308
+ import { join as join5 } from "path";
57174
57309
  var BROWSERS = [
57175
57310
  "com.google.Chrome",
57176
57311
  "com.google.Chrome.canary",
@@ -57180,19 +57315,73 @@ var BROWSERS = [
57180
57315
  "com.vivaldi.Vivaldi",
57181
57316
  "com.operasoftware.Opera"
57182
57317
  ];
57183
- var DNS_SETUP_FLAG = join4(homedir3(), ".tunl-dns-setup");
57318
+ var DNS_SETUP_FLAG = join5(homedir4(), ".tunl-dns-setup");
57184
57319
  function setupBrowserDNS() {
57185
- if (existsSync6(DNS_SETUP_FLAG))
57320
+ if (existsSync7(DNS_SETUP_FLAG))
57186
57321
  return false;
57187
57322
  for (const bundle of BROWSERS) {
57188
57323
  try {
57189
- execSync2(`defaults write ${bundle} BuiltInDnsClientEnabled -bool false 2>/dev/null`);
57190
- execSync2(`defaults write ${bundle} DnsOverHttpsMode -string "off" 2>/dev/null`);
57324
+ execSync3(`defaults write ${bundle} BuiltInDnsClientEnabled -bool false 2>/dev/null`);
57325
+ execSync3(`defaults write ${bundle} DnsOverHttpsMode -string "off" 2>/dev/null`);
57191
57326
  } catch {}
57192
57327
  }
57193
- writeFileSync3(DNS_SETUP_FLAG, new Date().toISOString());
57328
+ writeFileSync4(DNS_SETUP_FLAG, new Date().toISOString());
57194
57329
  return true;
57195
57330
  }
57331
+ function restoreBrowserDNS() {
57332
+ for (const bundle of BROWSERS) {
57333
+ try {
57334
+ execSync3(`defaults delete ${bundle} BuiltInDnsClientEnabled 2>/dev/null`);
57335
+ execSync3(`defaults delete ${bundle} DnsOverHttpsMode 2>/dev/null`);
57336
+ } catch {}
57337
+ }
57338
+ try {
57339
+ unlinkSync3(DNS_SETUP_FLAG);
57340
+ } catch {}
57341
+ }
57342
+
57343
+ // src/lib/sudo-setup.ts
57344
+ import { existsSync as existsSync8, writeFileSync as writeFileSync5 } from "fs";
57345
+ import { execSync as execSync4 } from "child_process";
57346
+ import { userInfo } from "os";
57347
+ function buildRules() {
57348
+ const username = userInfo().username;
57349
+ return [
57350
+ `${username} ALL=(root) NOPASSWD: /bin/cp */.tunl-block.tmp /etc/hosts`,
57351
+ `${username} ALL=(root) NOPASSWD: /bin/cp */.tunl-hosts.tmp /etc/hosts`,
57352
+ `${username} ALL=(root) NOPASSWD: /usr/sbin/dscacheutil -flushcache`,
57353
+ `${username} ALL=(root) NOPASSWD: /usr/bin/killall -HUP mDNSResponder`,
57354
+ `${username} ALL=(root) NOPASSWD: /usr/bin/killall mDNSResponderHelper`,
57355
+ `${username} ALL=(root) NOPASSWD: /sbin/pfctl *`,
57356
+ `${username} ALL=(root) NOPASSWD: /bin/cp */.tunl-pf-anchor.tmp /etc/pf.anchors/tunl`,
57357
+ `${username} ALL=(root) NOPASSWD: /bin/cp */.tunl-pf-empty.tmp /etc/pf.anchors/tunl`,
57358
+ `${username} ALL=(root) NOPASSWD: /bin/cp */.tunl-pf-conf.tmp /etc/pf.conf`
57359
+ ].join(`
57360
+ `);
57361
+ }
57362
+ var SUDOERS_PATH = "/etc/sudoers.d/tunl";
57363
+ function sudoersInstalled() {
57364
+ return existsSync8(SUDOERS_PATH);
57365
+ }
57366
+ function installSudoers() {
57367
+ const rules = buildRules();
57368
+ const content = `# tunl - terminal focus timer
57369
+ # allows site blocking without password prompts
57370
+ ${rules}
57371
+ `;
57372
+ const tmpPath = "/tmp/tunl-sudoers";
57373
+ writeFileSync5(tmpPath, content, { mode: 288 });
57374
+ execSync4(`sudo visudo -cf ${tmpPath}`);
57375
+ execSync4(`sudo install -m 0440 ${tmpPath} ${SUDOERS_PATH}`);
57376
+ execSync4(`rm ${tmpPath}`);
57377
+ }
57378
+ function removeSudoers() {
57379
+ if (existsSync8(SUDOERS_PATH)) {
57380
+ try {
57381
+ execSync4(`sudo rm ${SUDOERS_PATH}`);
57382
+ } catch {}
57383
+ }
57384
+ }
57196
57385
 
57197
57386
  // src/utils/args.ts
57198
57387
  function parseArgs(argv) {
@@ -57203,7 +57392,8 @@ function parseArgs(argv) {
57203
57392
  sites: undefined,
57204
57393
  showConfig: false,
57205
57394
  showStats: false,
57206
- resetConfig: false
57395
+ resetConfig: false,
57396
+ uninstall: false
57207
57397
  };
57208
57398
  for (let i = 0;i < argv.length; i++) {
57209
57399
  const arg = argv[i];
@@ -57224,6 +57414,8 @@ function parseArgs(argv) {
57224
57414
  opts.showStats = true;
57225
57415
  } else if (arg === "--reset") {
57226
57416
  opts.resetConfig = true;
57417
+ } else if (arg === "--uninstall") {
57418
+ opts.uninstall = true;
57227
57419
  } else if (arg === "--help" || arg === "-h") {
57228
57420
  printHelp();
57229
57421
  process.exit(0);
@@ -57244,6 +57436,7 @@ function printHelp() {
57244
57436
  tunl --stats Show focus stats and streaks
57245
57437
  tunl --config Show current saved config
57246
57438
  tunl --reset Reset config (re-run onboarding)
57439
+ tunl --uninstall Remove all tunl system files (sudoers, config, DNS settings)
57247
57440
  tunl --help Show this help
57248
57441
 
57249
57442
  Controls:
@@ -57265,7 +57458,11 @@ async function main2() {
57265
57458
  process.exit(0);
57266
57459
  }
57267
57460
  if (args.resetConfig) {
57268
- await resetConfig();
57461
+ resetConfig();
57462
+ process.exit(0);
57463
+ }
57464
+ if (args.uninstall) {
57465
+ uninstall();
57269
57466
  process.exit(0);
57270
57467
  }
57271
57468
  if (args.sites) {
@@ -57275,34 +57472,24 @@ async function main2() {
57275
57472
  `);
57276
57473
  }
57277
57474
  if (!args.noblock) {
57278
- const needsRestart = setupBrowserDNS();
57279
- if (needsRestart) {
57475
+ setupBrowserDNS();
57476
+ if (!sudoersInstalled()) {
57280
57477
  console.log(`
57281
- \u25C9 tunl \u2014 first-time setup
57478
+ \u25C9 tunl \u2014 setup
57282
57479
  `);
57283
- console.log(" Disabled browser built-in DNS so site blocking works.");
57284
- console.log(" Please restart your browser (Chrome/Arc/Edge) for this to take effect.");
57285
- console.log(` This only needs to happen once.
57480
+ console.log(" tunl needs to install a passwordless sudo rule so it can");
57481
+ console.log(" block sites without prompting you every time.");
57482
+ console.log(` You'll enter your password once \u2014 never again after this.
57286
57483
  `);
57287
- }
57288
- }
57289
- if (!args.noblock) {
57290
- console.log(`
57291
- \u25C9 tunl \u2014 Terminal Focus Timer
57292
- `);
57293
- console.log(" tunl needs sudo access to block distracting sites.");
57294
- console.log(` You'll be prompted for your password.
57295
- `);
57296
- try {
57297
- execSync3("sudo -v", { stdio: "inherit" });
57298
- console.log(`
57299
- \u2713 Ready! Entering focus mode...
57484
+ try {
57485
+ installSudoers();
57486
+ console.log(` \u2713 sudo rule installed. No more password prompts!
57300
57487
  `);
57301
- } catch {
57302
- console.log(`
57303
- \u2715 Could not get sudo access. Running without site blocking.
57488
+ } catch {
57489
+ console.log(` \u2715 Could not install sudo rule. Running without site blocking.
57304
57490
  `);
57305
- args.noblock = true;
57491
+ args.noblock = true;
57492
+ }
57306
57493
  }
57307
57494
  }
57308
57495
  const renderer = await createCliRenderer({
@@ -57345,13 +57532,10 @@ function printConfig() {
57345
57532
  console.log(` First run: ${config.isFirstRun}
57346
57533
  `);
57347
57534
  }
57348
- async function resetConfig() {
57349
- const { unlinkSync: unlinkSync3, existsSync: existsSync4 } = await import("fs");
57350
- const { homedir: homedir4 } = await import("os");
57351
- const { join: join5 } = await import("path");
57352
- const configPath = join5(homedir4(), ".tunl.json");
57353
- if (existsSync4(configPath)) {
57354
- unlinkSync3(configPath);
57535
+ function resetConfig() {
57536
+ const configPath = join6(homedir5(), ".tunl.json");
57537
+ if (existsSync9(configPath)) {
57538
+ unlinkSync4(configPath);
57355
57539
  console.log(`
57356
57540
  \u2713 Config reset. Next run will show onboarding.
57357
57541
  `);
@@ -57361,6 +57545,47 @@ async function resetConfig() {
57361
57545
  `);
57362
57546
  }
57363
57547
  }
57548
+ function uninstall() {
57549
+ console.log(`
57550
+ \u25C9 tunl \u2014 uninstall
57551
+ `);
57552
+ if (sudoersInstalled()) {
57553
+ try {
57554
+ removeSudoers();
57555
+ console.log(" \u2713 Removed sudo rule (/etc/sudoers.d/tunl)");
57556
+ } catch {
57557
+ console.log(" \u2715 Could not remove sudo rule (may need manual sudo)");
57558
+ }
57559
+ } else {
57560
+ console.log(" - No sudo rule found");
57561
+ }
57562
+ const configPath = join6(homedir5(), ".tunl.json");
57563
+ if (existsSync9(configPath)) {
57564
+ unlinkSync4(configPath);
57565
+ console.log(" \u2713 Removed config (~/.tunl.json)");
57566
+ } else {
57567
+ console.log(" - No config found");
57568
+ }
57569
+ const dnsFlag = join6(homedir5(), ".tunl-dns-setup");
57570
+ if (existsSync9(dnsFlag)) {
57571
+ unlinkSync4(dnsFlag);
57572
+ console.log(" \u2713 Removed DNS setup flag");
57573
+ }
57574
+ const pidPath = join6(homedir5(), ".tunl.pid");
57575
+ if (existsSync9(pidPath)) {
57576
+ unlinkSync4(pidPath);
57577
+ console.log(" \u2713 Removed PID file");
57578
+ }
57579
+ try {
57580
+ unblockPF();
57581
+ console.log(" \u2713 Cleaned up firewall rules");
57582
+ } catch {
57583
+ console.log(" - No firewall rules to clean");
57584
+ }
57585
+ restoreBrowserDNS();
57586
+ console.log(" \u2713 Restored browser DNS settings");
57587
+ console.log("\n All clean. Run `bun remove -g @khemsok/tunl` to finish.\n");
57588
+ }
57364
57589
  main2().catch((err) => {
57365
57590
  const renderer = globalThis.__tunl_renderer;
57366
57591
  if (renderer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khemsok/tunl",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Terminal focus timer that blocks distracting sites and progressively reveals animated ASCII art",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,11 @@
12
12
  },
13
13
  "scripts": {
14
14
  "dev": "bun run src/cli.tsx",
15
+ "dev:noblock": "bun run src/cli.tsx --noblock",
16
+ "dev:reset": "bun run src/cli.tsx --reset",
17
+ "dev:stats": "bun run src/cli.tsx --stats",
18
+ "dev:config": "bun run src/cli.tsx --config",
19
+ "dev:uninstall": "bun run src/cli.tsx --uninstall",
15
20
  "build": "bun build src/cli.tsx --outdir dist --target bun",
16
21
  "postinstall": "bun run build"
17
22
  },