sing-box-cli 0.0.23__tar.gz → 0.0.25__tar.gz

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 (27) hide show
  1. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/CHANGELOG.md +12 -0
  2. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/PKG-INFO +1 -3
  3. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/pyproject.toml +1 -3
  4. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/__init__.py +1 -1
  5. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/monitor.py +158 -51
  6. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/uv.lock +4 -82
  7. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/.github/dependabot.yml +0 -0
  8. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/.github/workflows/main.yml +0 -0
  9. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/.gitignore +0 -0
  10. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/.pre-commit-config.yaml +0 -0
  11. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/LICENSE +0 -0
  12. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/README.md +0 -0
  13. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/assets/image.png +0 -0
  14. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/scripts/bump.sh +0 -0
  15. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/scripts/format.sh +0 -0
  16. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/scripts/lint.sh +0 -0
  17. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/scripts/pre-commit.sh +0 -0
  18. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/client.py +0 -0
  19. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/config.py +0 -0
  20. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/connections.py +0 -0
  21. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/main.py +0 -0
  22. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/policy.py +0 -0
  23. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/py.typed +0 -0
  24. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/run.py +0 -0
  25. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/service.py +0 -0
  26. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/src/sing_box_service/widget.py +0 -0
  27. {sing_box_cli-0.0.23 → sing_box_cli-0.0.25}/tests/test_config.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.0.25 - 2025-03-20
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ - Set fixed interval to avoid repeated traffic data
10
+
11
+ ## 0.0.24 - 2025-03-19
12
+
13
+ ### 🚜 Refactor
14
+
15
+ - Smooth traffic data
16
+
5
17
  ## 0.0.23 - 2025-03-19
6
18
 
7
19
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sing-box-cli
3
- Version: 0.0.23
3
+ Version: 0.0.25
4
4
  Summary: cross-platform sing-box-service manager.
5
5
  Project-URL: Homepage, https://github.com/aittcuszz/sing-box-service
6
6
  Project-URL: Issues, https://github.com/atticuszz/sing-box-service/issues
@@ -39,11 +39,9 @@ Classifier: Programming Language :: Python :: 3.12
39
39
  Classifier: Programming Language :: Python :: 3.13
40
40
  Requires-Python: >=3.10
41
41
  Requires-Dist: httpx>=0.28.1
42
- Requires-Dist: numpy>=2.2.4
43
42
  Requires-Dist: plotext>=5.3.2
44
43
  Requires-Dist: prompt-toolkit>=3.0.50
45
44
  Requires-Dist: psutil>=6.1.1
46
- Requires-Dist: scipy>=1.15.2
47
45
  Requires-Dist: typer>=0.15.1
48
46
  Description-Content-Type: text/markdown
49
47
 
@@ -25,11 +25,9 @@ maintainers = [{ name = "A.J.Zeller", email = "zhouge1831@gmail.com" }]
25
25
  dynamic = ["version"] # https://hatch.pypa.io/latest/config/metadata/#version
26
26
  dependencies = [
27
27
  "httpx>=0.28.1",
28
- "numpy>=2.2.4",
29
28
  "plotext>=5.3.2",
30
29
  "prompt-toolkit>=3.0.50",
31
30
  "psutil>=6.1.1",
32
- "scipy>=1.15.2",
33
31
  "typer>=0.15.1",
34
32
  ]
35
33
 
@@ -214,7 +212,7 @@ sort_commits = "oldest"
214
212
 
215
213
 
216
214
  [tool.bumpversion]
217
- current_version = "0.0.23"
215
+ current_version = "0.0.25"
218
216
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
219
217
  serialize = ["{major}.{minor}.{patch}"]
220
218
  search = "{current_version}"
@@ -2,4 +2,4 @@ from .main import main
2
2
 
3
3
  __all__ = ["main"]
4
4
 
5
- __version__ = "0.0.23"
5
+ __version__ = "0.0.25"
@@ -6,7 +6,6 @@ from datetime import datetime
6
6
  from enum import Enum
7
7
  from typing import Any
8
8
 
9
- import numpy as np
10
9
  import plotext as plt # type: ignore
11
10
  from rich.console import Console
12
11
  from rich.layout import Layout
@@ -14,7 +13,6 @@ from rich.live import Live
14
13
  from rich.panel import Panel
15
14
  from rich.table import Table
16
15
  from rich.text import Text
17
- from scipy.interpolate import PchipInterpolator, make_interp_spline
18
16
 
19
17
  from .client import SingBoxAPIClient
20
18
  from .widget import RichPlotMixin, create_header_panel
@@ -197,44 +195,157 @@ def format_chain(chains: list[str]) -> str:
197
195
  raise ValueError(f"Invalid chains data: {chains}")
198
196
 
199
197
 
200
- def interpolate_data(
201
- data: list[float], method: str = "linear", factor: int = 5
198
+ def advanced_smooth_bezier(
199
+ data: list[float], tension: float = 1, points_per_segment: int = 50
202
200
  ) -> tuple[list[float], list[float]]:
203
201
  """
204
- Interpolate data points to create a smoother curve.
202
+ Create a highly smooth curve from data using advanced Bezier techniques.
205
203
 
206
204
  Args:
207
- data: List of data points to interpolate
208
- method: Interpolation method ('spline', 'pchip', or 'linear')
205
+ data: Original data points
206
+ tension: Controls curve tension (0.0-1.0, higher = smoother curves)
207
+ points_per_segment: Number of points to generate between each original data point
209
208
 
210
209
  Returns:
211
- Tuple of (x_new, y_new) containing interpolated data points
210
+ Tuple of (x_values, y_values) containing smoothed curve points
212
211
  """
213
212
  if len(data) < 3:
214
- # Not enough points for interpolation, return original data
215
213
  return list(range(len(data))), data
216
214
 
217
- x = np.array(range(len(data)))
218
- y = np.array(data)
215
+ # Create original x values
216
+ x_orig = list(range(len(data)))
217
+ y_orig = data
218
+
219
+ # Pre-process data to avoid extreme jumps
220
+ # Apply light smoothing to input data first
221
+ smoothed_input = []
222
+ window_size = 3
223
+
224
+ for i in range(len(y_orig)):
225
+ # Use window averaging for initial smoothing
226
+ window_start = max(0, i - window_size // 2)
227
+ window_end = min(len(y_orig), i + window_size // 2 + 1)
228
+ window = y_orig[window_start:window_end]
229
+ # Weighted average with center point having more influence
230
+ weights = [
231
+ 0.5 + 0.5 * (1 - abs(i - j) / (window_size / 2))
232
+ for j in range(window_start, window_end)
233
+ ]
234
+ smoothed_input.append(
235
+ sum(w * v for w, v in zip(weights, window, strict=True)) / sum(weights)
236
+ )
219
237
 
220
- # Create new x values for interpolated points
221
- points = len(data) * factor
222
- x_new = np.linspace(x.min(), x.max(), points)
238
+ # Replace original data with pre-smoothed data
239
+ y_orig = smoothed_input
240
+
241
+ x_smooth = []
242
+ y_smooth = []
243
+
244
+ # Calculate control points for the entire curve first
245
+ control_points_x1 = []
246
+ control_points_y1 = []
247
+ control_points_x2 = []
248
+ control_points_y2 = []
249
+
250
+ for i in range(len(data)):
251
+ # For each point, calculate control points
252
+ if i == 0: # First point
253
+ # Control points for first segment
254
+ ctrl_x1 = x_orig[i]
255
+ ctrl_y1 = y_orig[i]
256
+
257
+ # Direction to next point
258
+ dx = x_orig[i + 1] - x_orig[i]
259
+ dy = y_orig[i + 1] - y_orig[i]
260
+
261
+ ctrl_x2 = x_orig[i] + dx * tension / 2
262
+ ctrl_y2 = y_orig[i] + dy * tension / 2
263
+
264
+ elif i == len(data) - 1: # Last point
265
+ # Control points for last segment
266
+ # Direction from previous point
267
+ dx = x_orig[i] - x_orig[i - 1]
268
+ dy = y_orig[i] - y_orig[i - 1]
269
+
270
+ ctrl_x1 = int(x_orig[i] - dx * tension / 2)
271
+ ctrl_y1 = int(y_orig[i] - dy * tension / 2)
272
+
273
+ ctrl_x2 = x_orig[i]
274
+ ctrl_y2 = y_orig[i]
275
+
276
+ else: # Middle points
277
+ # Calculate slopes of adjacent segments
278
+ dx1 = x_orig[i] - x_orig[i - 1]
279
+ dy1 = y_orig[i] - y_orig[i - 1]
280
+ dx2 = x_orig[i + 1] - x_orig[i]
281
+ dy2 = y_orig[i + 1] - y_orig[i]
282
+
283
+ # Average slope, weighted by segment length
284
+ len1 = (dx1**2 + dy1**2) ** 0.5
285
+ len2 = (dx2**2 + dy2**2) ** 0.5
286
+ total_len = len1 + len2
287
+
288
+ # Normalized direction vectors
289
+ if len1 > 0:
290
+ nx1, ny1 = dx1 / len1, dy1 / len1
291
+ else:
292
+ nx1, ny1 = 0, 0
223
293
 
224
- if method == "spline":
225
- # Cubic spline interpolation
226
- spl = make_interp_spline(x, y, k=3) # k=3 for cubic spline
227
- y_new = spl(x_new)
228
- elif method == "pchip":
229
- # PCHIP interpolation (Piecewise Cubic Hermite Interpolating Polynomial)
230
- # This preserves monotonicity and doesn't overshoot
231
- pchip = PchipInterpolator(x, y)
232
- y_new = pchip(x_new)
233
- else: # 'linear' or any other value
234
- # Simple linear interpolation
235
- y_new = np.interp(x_new, x, y)
294
+ if len2 > 0:
295
+ nx2, ny2 = dx2 / len2, dy2 / len2
296
+ else:
297
+ nx2, ny2 = 0, 0
298
+
299
+ # Weighted average direction
300
+ avg_nx = (nx1 * len1 + nx2 * len2) / total_len if total_len > 0 else 0
301
+ avg_ny = (ny1 * len1 + ny2 * len2) / total_len if total_len > 0 else 0
302
+
303
+ # Scale to create control points
304
+ scale = min(len1, len2) * tension
305
+
306
+ # Set control points
307
+ ctrl_x1 = x_orig[i] - avg_nx * scale
308
+ ctrl_y1 = y_orig[i] - avg_ny * scale
309
+ ctrl_x2 = x_orig[i] + avg_nx * scale
310
+ ctrl_y2 = y_orig[i] + avg_ny * scale
311
+
312
+ control_points_x1.append(ctrl_x1)
313
+ control_points_y1.append(ctrl_y1)
314
+ control_points_x2.append(ctrl_x2)
315
+ control_points_y2.append(ctrl_y2)
316
+
317
+ # Generate the curve points using the control points
318
+ for i in range(len(data) - 1):
319
+ # Start and end points
320
+ x0, y0 = x_orig[i], y_orig[i]
321
+ x1, y1 = x_orig[i + 1], y_orig[i + 1]
322
+
323
+ # Control points for this segment
324
+ cx1, cy1 = control_points_x2[i], control_points_y2[i]
325
+ cx2, cy2 = control_points_x1[i + 1], control_points_y1[i + 1]
326
+
327
+ # Generate points along this Bezier segment
328
+ for t in range(points_per_segment + 1):
329
+ t_norm = t / points_per_segment
330
+
331
+ # Cubic Bezier formula
332
+ x_point = (
333
+ (1 - t_norm) ** 3 * x0
334
+ + 3 * (1 - t_norm) ** 2 * t_norm * cx1
335
+ + 3 * (1 - t_norm) * t_norm**2 * cx2
336
+ + t_norm**3 * x1
337
+ )
338
+ y_point = (
339
+ (1 - t_norm) ** 3 * y0
340
+ + 3 * (1 - t_norm) ** 2 * t_norm * cy1
341
+ + 3 * (1 - t_norm) * t_norm**2 * cy2
342
+ + t_norm**3 * y1
343
+ )
236
344
 
237
- return list(x_new), list(y_new)
345
+ x_smooth.append(x_point)
346
+ y_smooth.append(y_point)
347
+
348
+ return x_smooth, y_smooth
238
349
 
239
350
 
240
351
  class TrafficGraph(RichPlotMixin):
@@ -270,14 +381,14 @@ class TrafficGraph(RichPlotMixin):
270
381
  self.upload_speeds.append(traffic_data.get("up", 0))
271
382
  self.download_speeds.append(traffic_data.get("down", 0))
272
383
 
273
- def make_plot(self, width: int, height: int, interp_method: str = "pchip") -> str:
384
+ def make_plot(self, width: int, height: int, tension: float = 0.8) -> str:
274
385
  """
275
- Create the plot with interpolated data.
386
+ Create the plot with highly smoothed curves.
276
387
 
277
388
  Args:
278
389
  width: Width of the plot
279
390
  height: Height of the plot
280
- interp_method: Interpolation method ('spline', 'pchip', or 'linear')
391
+ tension: Controls curve smoothness (0.0-1.0)
281
392
 
282
393
  Returns:
283
394
  String representation of the plot
@@ -294,21 +405,22 @@ class TrafficGraph(RichPlotMixin):
294
405
  # Set titles and legend
295
406
  plt.xlabel("Time")
296
407
  plt.ylabel("Speed")
408
+ plt.grid(False) # Disable grid for cleaner look
297
409
 
298
410
  # Get the data for plotting
299
411
  y_upload = list(self.upload_speeds)
300
412
  y_download = list(self.download_speeds)
301
413
 
302
- if len(y_upload) >= 2 and len(y_download) >= 2:
303
- # Interpolate data
304
- x_upload, y_upload_smooth = interpolate_data(
305
- y_upload, interp_method, self.interp_factor
414
+ if len(y_upload) >= 3 and len(y_download) >= 3:
415
+ # Apply advanced smoothing
416
+ x_upload, y_upload_smooth = advanced_smooth_bezier(
417
+ y_upload, tension=tension, points_per_segment=30
306
418
  )
307
- x_download, y_download_smooth = interpolate_data(
308
- y_download, interp_method, self.interp_factor
419
+ x_download, y_download_smooth = advanced_smooth_bezier(
420
+ y_download, tension=tension, points_per_segment=30
309
421
  )
310
422
 
311
- # Plot upload and download data with interpolated values
423
+ # Fill areas below curves
312
424
  plt.plot(
313
425
  x_upload,
314
426
  y_upload_smooth,
@@ -321,24 +433,18 @@ class TrafficGraph(RichPlotMixin):
321
433
  y_download_smooth,
322
434
  marker=".",
323
435
  label="Download",
324
- color=(68, 180, 255), # Blue
436
+ color=(68, 180, 255), # Blue color like in the image
325
437
  )
326
438
 
327
- # Calculate and set y-axis ticks with a better algorithm
439
+ # Calculate and set y-axis ticks
328
440
  all_y_values = y_upload_smooth + y_download_smooth
329
441
  max_y_value = max(all_y_values) if all_y_values else 1.0
330
-
331
- # Ensure we have a reasonable minimum value
332
442
  max_y_value = max(max_y_value, 1.0)
333
443
 
334
- # Round up to a nice number for the maximum tick
335
- # Find the magnitude of the value (1, 10, 100, 1000, etc.)
444
+ # Find a nice maximum value
336
445
  magnitude = 10 ** math.floor(math.log10(max_y_value))
337
-
338
- # Scale to a number between 1-10
339
446
  scaled = max_y_value / magnitude
340
447
 
341
- # Round up to a nice number (1, 2, 5, 10)
342
448
  if scaled <= 1:
343
449
  nice_max = 1
344
450
  elif scaled <= 2:
@@ -348,14 +454,11 @@ class TrafficGraph(RichPlotMixin):
348
454
  else:
349
455
  nice_max = 10
350
456
 
351
- # Final nice maximum value
352
457
  nice_max_value = nice_max * magnitude
353
458
 
354
459
  # Create ticks with appropriate intervals
355
- num_ticks = 8 # Will give 7 intervals
460
+ num_ticks = 5 # Fewer ticks for cleaner look
356
461
  y_ticks = [i * (nice_max_value / (num_ticks - 1)) for i in range(num_ticks)]
357
-
358
- # Create labels
359
462
  y_labels = [format_speed(val) for val in y_ticks]
360
463
 
361
464
  # Apply ticks to the plot
@@ -380,7 +483,7 @@ class ResourceVisualizer:
380
483
 
381
484
  # For traffic graph
382
485
  self.traffic_graph = TrafficGraph(
383
- max_points=120 # Store 2 minutes of data
486
+ max_points=60 # Store 1 minutes of data
384
487
  )
385
488
 
386
489
  def create_traffic_table(self, traffic_data: dict[str, Any]) -> Table:
@@ -539,6 +642,8 @@ class ResourceMonitor:
539
642
  self.running = False
540
643
  self.current_traffic = {"up": 0, "down": 0} # in bytes
541
644
  self.current_memory = {"inuse": 0, "total": 0} # in bytes
645
+ # 1.0 fix interval for avoiding repeated data
646
+ self.task_interval = RefreshRate.SLOW.value
542
647
 
543
648
  async def monitor_traffic(self) -> None:
544
649
  """Monitor traffic stream from the API."""
@@ -547,6 +652,7 @@ class ResourceMonitor:
547
652
  self.current_traffic = traffic_data
548
653
  if not self.running:
549
654
  break
655
+ await asyncio.sleep(self.task_interval)
550
656
  except asyncio.CancelledError:
551
657
  pass
552
658
  except Exception as e:
@@ -559,6 +665,7 @@ class ResourceMonitor:
559
665
  self.current_memory = memory_data
560
666
  if not self.running:
561
667
  break
668
+ await asyncio.sleep(self.task_interval)
562
669
  except asyncio.CancelledError:
563
670
  pass
564
671
  except Exception as e:
@@ -2,8 +2,10 @@ version = 1
2
2
  revision = 1
3
3
  requires-python = ">=3.10"
4
4
  resolution-markers = [
5
- "sys_platform == 'win32'",
6
- "sys_platform == 'linux'",
5
+ "python_full_version >= '3.12' and sys_platform == 'win32'",
6
+ "python_full_version < '3.12' and sys_platform == 'win32'",
7
+ "python_full_version >= '3.12' and sys_platform == 'linux'",
8
+ "python_full_version < '3.12' and sys_platform == 'linux'",
7
9
  ]
8
10
  supported-markers = [
9
11
  "sys_platform == 'win32'",
@@ -277,46 +279,6 @@ wheels = [
277
279
  { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
278
280
  ]
279
281
 
280
- [[package]]
281
- name = "numpy"
282
- version = "2.2.4"
283
- source = { registry = "https://pypi.org/simple" }
284
- sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 }
285
- wheels = [
286
- { url = "https://files.pythonhosted.org/packages/4c/22/fb1be710a14434c09080dd4a0acc08939f612ec02efcb04b9e210474782d/numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2", size = 14368066 },
287
- { url = "https://files.pythonhosted.org/packages/c2/07/2e5cc71193e3ef3a219ffcf6ca4858e46ea2be09c026ddd480d596b32867/numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020", size = 16417040 },
288
- { url = "https://files.pythonhosted.org/packages/1a/97/3b1537776ad9a6d1a41813818343745e8dd928a2916d4c9edcd9a8af1dac/numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3", size = 15879862 },
289
- { url = "https://files.pythonhosted.org/packages/b0/b7/4472f603dd45ef36ff3d8e84e84fe02d9467c78f92cc121633dce6da307b/numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017", size = 18206032 },
290
- { url = "https://files.pythonhosted.org/packages/0d/bd/6a092963fb82e6c5aa0d0440635827bbb2910da229545473bbb58c537ed3/numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a", size = 6608517 },
291
- { url = "https://files.pythonhosted.org/packages/01/e3/cb04627bc2a1638948bc13e818df26495aa18e20d5be1ed95ab2b10b6847/numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542", size = 12943498 },
292
- { url = "https://files.pythonhosted.org/packages/d5/ee/96457c943265de9fadeb3d2ffdbab003f7fba13d971084a9876affcda095/numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1", size = 14373079 },
293
- { url = "https://files.pythonhosted.org/packages/c5/5c/ceefca458559f0ccc7a982319f37ed07b0d7b526964ae6cc61f8ad1b6119/numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5", size = 16428819 },
294
- { url = "https://files.pythonhosted.org/packages/22/31/9b2ac8eee99e001eb6add9fa27514ef5e9faf176169057a12860af52704c/numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687", size = 15881470 },
295
- { url = "https://files.pythonhosted.org/packages/f0/dc/8569b5f25ff30484b555ad8a3f537e0225d091abec386c9420cf5f7a2976/numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6", size = 18218144 },
296
- { url = "https://files.pythonhosted.org/packages/5e/05/463c023a39bdeb9bb43a99e7dee2c664cb68d5bb87d14f92482b9f6011cc/numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09", size = 6606368 },
297
- { url = "https://files.pythonhosted.org/packages/8b/72/10c1d2d82101c468a28adc35de6c77b308f288cfd0b88e1070f15b98e00c/numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91", size = 12947526 },
298
- { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 },
299
- { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 },
300
- { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 },
301
- { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 },
302
- { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 },
303
- { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 },
304
- { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 },
305
- { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 },
306
- { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 },
307
- { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 },
308
- { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 },
309
- { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 },
310
- { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 },
311
- { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 },
312
- { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 },
313
- { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 },
314
- { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 },
315
- { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 },
316
- { url = "https://files.pythonhosted.org/packages/54/f5/ab0d2f48b490535c7a80e05da4a98902b632369efc04f0e47bb31ca97d8f/numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d", size = 16233350 },
317
- { url = "https://files.pythonhosted.org/packages/3b/3a/2f6d8c1f8e45d496bca6baaec93208035faeb40d5735c25afac092ec9a12/numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d", size = 12857565 },
318
- ]
319
-
320
282
  [[package]]
321
283
  name = "optype"
322
284
  version = "0.9.2"
@@ -632,42 +594,6 @@ wheels = [
632
594
  { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 },
633
595
  ]
634
596
 
635
- [[package]]
636
- name = "scipy"
637
- version = "1.15.2"
638
- source = { registry = "https://pypi.org/simple" }
639
- dependencies = [
640
- { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
641
- ]
642
- sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 }
643
- wheels = [
644
- { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 },
645
- { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 },
646
- { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 },
647
- { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 },
648
- { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 },
649
- { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 },
650
- { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 },
651
- { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 },
652
- { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 },
653
- { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 },
654
- { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 },
655
- { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 },
656
- { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 },
657
- { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 },
658
- { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 },
659
- { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 },
660
- { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 },
661
- { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 },
662
- { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 },
663
- { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 },
664
- { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 },
665
- { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 },
666
- { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 },
667
- { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 },
668
- { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 },
669
- ]
670
-
671
597
  [[package]]
672
598
  name = "scipy-stubs"
673
599
  version = "1.15.2.1"
@@ -694,11 +620,9 @@ name = "sing-box-cli"
694
620
  source = { editable = "." }
695
621
  dependencies = [
696
622
  { name = "httpx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
697
- { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
698
623
  { name = "plotext", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
699
624
  { name = "prompt-toolkit", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
700
625
  { name = "psutil", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
701
- { name = "scipy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
702
626
  { name = "typer", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
703
627
  ]
704
628
 
@@ -718,11 +642,9 @@ dev = [
718
642
  [package.metadata]
719
643
  requires-dist = [
720
644
  { name = "httpx", specifier = ">=0.28.1" },
721
- { name = "numpy", specifier = ">=2.2.4" },
722
645
  { name = "plotext", specifier = ">=5.3.2" },
723
646
  { name = "prompt-toolkit", specifier = ">=3.0.50" },
724
647
  { name = "psutil", specifier = ">=6.1.1" },
725
- { name = "scipy", specifier = ">=1.15.2" },
726
648
  { name = "typer", specifier = ">=0.15.1" },
727
649
  ]
728
650
 
File without changes
File without changes
File without changes