mdify-cli 3.1.1__tar.gz → 3.2.1__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 (28) hide show
  1. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/PKG-INFO +1 -1
  2. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/__init__.py +1 -1
  3. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/cli.py +85 -51
  4. mdify_cli-3.2.1/mdify/formatting.py +138 -0
  5. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/PKG-INFO +1 -1
  6. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/pyproject.toml +1 -1
  7. mdify_cli-3.1.1/mdify/formatting.py +0 -29
  8. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/LICENSE +0 -0
  9. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/README.md +0 -0
  10. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/assets/mdify.png +0 -0
  11. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/__main__.py +0 -0
  12. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/container.py +0 -0
  13. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/docling_client.py +0 -0
  14. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/__init__.py +0 -0
  15. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/client.py +0 -0
  16. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/models.py +0 -0
  17. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/remote_container.py +0 -0
  18. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify/ssh/transfer.py +0 -0
  19. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/SOURCES.txt +0 -0
  20. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/dependency_links.txt +0 -0
  21. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/entry_points.txt +0 -0
  22. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/requires.txt +0 -0
  23. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/mdify_cli.egg-info/top_level.txt +0 -0
  24. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/setup.cfg +0 -0
  25. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_cli.py +0 -0
  26. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_container.py +0 -0
  27. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_docling_client.py +0 -0
  28. {mdify_cli-3.1.1 → mdify_cli-3.2.1}/tests/test_ssh_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdify-cli
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Convert PDFs and document images into structured Markdown for LLM workflows
5
5
  Author: tiroq
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """mdify - Convert documents to Markdown via Docling container."""
2
2
 
3
- __version__ = "3.1.1"
3
+ __version__ = "3.2.1"
@@ -275,17 +275,20 @@ def check_for_update(force: bool = False) -> None:
275
275
 
276
276
  if not _compare_versions(__version__, remote_version):
277
277
  if force:
278
- print(f"mdify is up to date (version {__version__})")
278
+ from mdify.formatting import Colorizer
279
+ color = Colorizer(sys.stdout)
280
+ print(color.success(f"✓ mdify is up to date (v{__version__})"))
279
281
  return
280
282
 
281
- print(f"\n{'=' * 50}")
282
- print(f"A new version of mdify-cli is available!")
283
- print(f" Current version: {__version__}")
284
- print(f" Latest version: {remote_version}")
285
- print(f"{'=' * 50}")
286
- print(f"\nTo upgrade, run:")
287
- print(f" pipx upgrade mdify-cli")
288
- print(f" # or: pip install --upgrade mdify-cli\n")
283
+ from mdify.formatting import Colorizer
284
+ color = Colorizer(sys.stdout)
285
+ print(f"\n{color.bright_yellow('=' * 60)}")
286
+ print(color.bold_yellow("🎉 A new version of mdify-cli is available!"))
287
+ print(f"{color.dim_white(' Current:')} {__version__} → {color.bright_green(remote_version)}")
288
+ print(f"{color.bright_yellow('=' * 60)}")
289
+ print(f"\n{color.cyan('To upgrade, run:')}")
290
+ print(f" {color.bold('pipx upgrade mdify-cli')}")
291
+ print(f" {color.dim_white('# or: pip install --upgrade mdify-cli')}\n")
289
292
 
290
293
 
291
294
  # =============================================================================
@@ -1113,57 +1116,57 @@ def main_async_remote(args) -> int:
1113
1116
 
1114
1117
  # Connect to remote server
1115
1118
  if not args.quiet:
1116
- print(color.cyan(f"Connecting to {ssh_config.host}:{ssh_config.port}..."), file=sys.stderr)
1119
+ print(color.bright_cyan(f"🔗 Connecting to {color.bold(ssh_config.host)}:{ssh_config.port}..."), file=sys.stderr)
1117
1120
 
1118
1121
  await ssh_client.connect()
1119
1122
 
1120
1123
  if not args.quiet:
1121
- print(color.green(f"✓ Connected to {ssh_config.host}"), file=sys.stderr)
1124
+ print(color.success(f"✓ Connected to {ssh_config.host}"), file=sys.stderr)
1122
1125
 
1123
1126
  # Validate remote resources if not skipped
1124
1127
  if not args.remote_skip_validation:
1125
1128
  if not args.quiet:
1126
- print(color.cyan("Validating remote resources..."), file=sys.stderr)
1129
+ print(color.cyan("🔍 Validating remote resources..."), file=sys.stderr)
1127
1130
 
1128
1131
  validation_result = await ssh_client.validate_remote_resources()
1129
1132
 
1130
1133
  if not validation_result.get("can_connect"):
1131
1134
  await ssh_client.disconnect()
1132
- print("Error: Cannot connect to remote server", file=sys.stderr)
1135
+ print(color.error(" Cannot connect to remote server"), file=sys.stderr)
1133
1136
  return 1
1134
1137
 
1135
1138
  if not validation_result.get("work_dir_writable"):
1136
1139
  await ssh_client.disconnect()
1137
- print(f"Error: Work directory not writable: {ssh_config.work_dir}", file=sys.stderr)
1140
+ print(color.error(f" Work directory not writable: {ssh_config.work_dir}"), file=sys.stderr)
1138
1141
  return 1
1139
1142
 
1140
1143
  if not validation_result.get("container_runtime_available"):
1141
1144
  await ssh_client.disconnect()
1142
1145
  runtime_str = ssh_config.container_runtime or "docker/podman"
1143
- print(f"Error: Container runtime not available: {runtime_str}", file=sys.stderr)
1146
+ print(color.error(f" Container runtime not available: {runtime_str}"), file=sys.stderr)
1144
1147
  return 1
1145
1148
 
1146
1149
  if not validation_result.get("disk_space_min_5gb"):
1147
- print(f"Warning: Less than 5GB available on remote", file=sys.stderr)
1150
+ print(color.warning(f" Less than 5GB available on remote"), file=sys.stderr)
1148
1151
  if not args.yes and sys.stdin.isatty():
1149
1152
  if not confirm_proceed("Continue anyway?"):
1150
1153
  await ssh_client.disconnect()
1151
1154
  return 130
1152
1155
 
1153
1156
  if not validation_result.get("memory_min_2gb"):
1154
- print(f"Warning: Less than 2GB available memory on remote", file=sys.stderr)
1157
+ print(color.warning(f" Less than 2GB available memory on remote"), file=sys.stderr)
1155
1158
  if not args.yes and sys.stdin.isatty():
1156
1159
  if not confirm_proceed("Continue anyway?"):
1157
1160
  await ssh_client.disconnect()
1158
1161
  return 130
1159
1162
 
1160
1163
  if not args.quiet:
1161
- print(color.green("✓ All remote resources validated"), file=sys.stderr)
1164
+ print(color.success("✓ All remote resources validated"), file=sys.stderr)
1162
1165
 
1163
1166
  # If --remote-validate-only, exit here
1164
1167
  if args.remote_validate_only:
1165
1168
  await ssh_client.disconnect()
1166
- print("Remote validation successful", file=sys.stderr)
1169
+ print(color.success("Remote validation successful"), file=sys.stderr)
1167
1170
  return 0
1168
1171
 
1169
1172
  # Phase 2.4.2: File upload, remote conversion, and download
@@ -1172,7 +1175,8 @@ def main_async_remote(args) -> int:
1172
1175
  input_path = Path(args.input)
1173
1176
  if not input_path.exists():
1174
1177
  await ssh_client.disconnect()
1175
- print(f"Error: Input file or directory not found: {args.input}", file=sys.stderr)
1178
+ color = Colorizer(sys.stderr)
1179
+ print(f"{color.error('✗ Error:')} Input file or directory not found: {args.input}", file=sys.stderr)
1176
1180
  return 1
1177
1181
 
1178
1182
  # Store resolved path as base for relative path calculations
@@ -1181,8 +1185,9 @@ def main_async_remote(args) -> int:
1181
1185
 
1182
1186
  if not files_to_convert:
1183
1187
  await ssh_client.disconnect()
1184
- print(f"Error: No supported files found in {args.input}", file=sys.stderr)
1185
- print(f" Supported formats: {', '.join(sorted(SUPPORTED_EXTENSIONS))}", file=sys.stderr)
1188
+ color = Colorizer(sys.stderr)
1189
+ print(f"{color.error(' Error:')} No supported files found in {args.input}", file=sys.stderr)
1190
+ print(f" {color.dim_white(f'Supported formats: {', '.join(sorted(SUPPORTED_EXTENSIONS))}')} ", file=sys.stderr)
1186
1191
  return 1
1187
1192
 
1188
1193
  if not args.quiet:
@@ -1378,7 +1383,8 @@ def main_async_remote(args) -> int:
1378
1383
  break
1379
1384
 
1380
1385
  if not conversion_success:
1381
- print(f" ✗ Failed: Conversion failed after {conversion_attempt} attempt(s)", file=sys.stderr)
1386
+ color_error = Colorizer(sys.stderr)
1387
+ print(f" {color_error.error('✗ Failed:')} Conversion failed after {conversion_attempt} attempt(s)", file=sys.stderr)
1382
1388
  failed += 1
1383
1389
  break
1384
1390
 
@@ -1490,7 +1496,8 @@ def main_async_remote(args) -> int:
1490
1496
  await ssh_client.connect()
1491
1497
  continue
1492
1498
 
1493
- print(f" ✗ Failed: {e}", file=sys.stderr)
1499
+ color_err = Colorizer(sys.stderr)
1500
+ print(f" {color_err.error('✗ Failed:')} {str(e)}", file=sys.stderr)
1494
1501
  if DEBUG:
1495
1502
  import traceback
1496
1503
  traceback.print_exc(file=sys.stderr)
@@ -1524,13 +1531,21 @@ def main_async_remote(args) -> int:
1524
1531
 
1525
1532
  # Print summary
1526
1533
  print(color.cyan(f"\n{'='*60}"), file=sys.stderr)
1527
- print(color.cyan(f"Remote conversion complete:"), file=sys.stderr)
1528
- print(color.green(f" Successful: {successful}"), file=sys.stderr)
1534
+ print(color.bold_cyan("📊 Remote Conversion Summary"), file=sys.stderr)
1535
+ print(color.cyan(f"{'='*60}"), file=sys.stderr)
1536
+
1537
+ # Format summary with emojis and colors
1538
+ total = len(files_to_convert)
1539
+ success_pct = f" ({successful}/{total})" if total > 0 else ""
1540
+ failed_pct = f" ({failed}/{total})" if total > 0 else ""
1541
+ skipped_pct = f" ({total - successful - failed}/{total})" if total > 0 else ""
1542
+
1543
+ print(f" {color.green('✓ Successful:')} {color.bold_green(str(successful))}{success_pct}", file=sys.stderr)
1529
1544
  if failed > 0:
1530
- print(color.yellow(f" Failed: {failed}"), file=sys.stderr)
1531
- else:
1532
- print(f" Failed: {failed}", file=sys.stderr)
1533
- print(f" Total: {len(files_to_convert)}", file=sys.stderr)
1545
+ print(f" {color.red('✗ Failed:')} {color.bold_red(str(failed))}{failed_pct}", file=sys.stderr)
1546
+ if (total - successful - failed) > 0:
1547
+ print(f" {color.yellow('⊘ Skipped:')} {color.bold_yellow(str(total - successful - failed))}{skipped_pct}", file=sys.stderr)
1548
+ print(f" {color.cyan('Total:')} {color.bold(str(total))}", file=sys.stderr)
1534
1549
  print(color.cyan(f"{'='*60}"), file=sys.stderr)
1535
1550
 
1536
1551
  return 0 if failed == 0 else 1
@@ -1578,7 +1593,9 @@ def main_async_remote(args) -> int:
1578
1593
 
1579
1594
  def main() -> int:
1580
1595
  """Main entry point for the CLI."""
1581
- print(f"mdify v{__version__}", file=sys.stderr)
1596
+ from mdify.formatting import Colorizer
1597
+ color = Colorizer(sys.stderr)
1598
+ print(color.bold_cyan(f"📄 mdify v{__version__}"), file=sys.stderr)
1582
1599
  args = parse_args()
1583
1600
 
1584
1601
  # Handle --check-update flag
@@ -1726,34 +1743,41 @@ def main() -> int:
1726
1743
 
1727
1744
  # Validate input
1728
1745
  if not input_path.exists():
1729
- print(f"Error: Input path does not exist: {input_path}", file=sys.stderr)
1746
+ color = Colorizer(sys.stderr)
1747
+ print(f"{color.error('✗ Error:')} Input path does not exist: {input_path}", file=sys.stderr)
1730
1748
  return 1
1731
1749
 
1732
1750
  # Get files to convert
1733
1751
  try:
1734
1752
  files_to_convert = get_files_to_convert(input_path, args.glob, args.recursive)
1735
1753
  except Exception as e:
1736
- print(f"Error: {e}", file=sys.stderr)
1754
+ color = Colorizer(sys.stderr)
1755
+ print(f"{color.error('✗ Error:')} {e}", file=sys.stderr)
1737
1756
  return 1
1738
1757
 
1739
1758
  if not files_to_convert:
1740
- print(f"No files found to convert in: {input_path}", file=sys.stderr)
1759
+ color = Colorizer(sys.stderr)
1760
+ print(f"{color.warning('⚠ Warning:')} No files found to convert in: {input_path}", file=sys.stderr)
1741
1761
  return 1
1742
1762
 
1743
1763
  total_files = len(files_to_convert)
1744
1764
  total_size = sum(f.stat().st_size for f in files_to_convert)
1745
1765
 
1746
1766
  if not args.quiet:
1747
- print(f"Found {total_files} file(s) to convert ({format_size(total_size)})")
1748
- print(f"Source: {input_path.resolve()}")
1749
- print(f"Output: {output_dir.resolve()}")
1750
- print(f"Using runtime: {runtime}")
1751
- print(f"Using image: {image}")
1767
+ from mdify.formatting import Colorizer
1768
+ color_info = Colorizer(sys.stdout)
1769
+ print(f"{color_info.bright_cyan('📦 Found')} {color_info.bold(str(total_files))} {color_info.bright_cyan('file(s)')} {color_info.dim_white(f'({format_size(total_size)})')}")
1770
+ print(f"{color_info.cyan('📁 Source:')} {color_info.bright_white(str(input_path.resolve()))}")
1771
+ print(f"{color_info.cyan('💾 Output:')} {color_info.bright_white(str(output_dir.resolve()))}")
1772
+ print(f"{color_info.cyan('🐳 Runtime:')} {color_info.bright_white(runtime)}")
1773
+ print(f"{color_info.cyan('🖼️ Image:')} {color_info.dim_white(image)}")
1752
1774
  print()
1753
1775
 
1754
1776
  if args.mask:
1777
+ from mdify.formatting import Colorizer
1778
+ color_warn = Colorizer(sys.stderr)
1755
1779
  print(
1756
- "Warning: --mask is not supported with docling-serve and will be ignored",
1780
+ color_warn.warning(" --mask is not supported with docling-serve and will be ignored"),
1757
1781
  file=sys.stderr,
1758
1782
  )
1759
1783
 
@@ -1770,7 +1794,9 @@ def main() -> int:
1770
1794
 
1771
1795
  try:
1772
1796
  if not args.quiet:
1773
- print(f"Starting docling-serve container...\n")
1797
+ from mdify.formatting import Colorizer
1798
+ color_start = Colorizer(sys.stdout)
1799
+ print(f"{color_start.bright_cyan('▶️ Starting')} {color_start.bright_white('docling-serve')} {color_start.bright_cyan('container')}...\n")
1774
1800
 
1775
1801
  # Apply resource profile
1776
1802
  profile = RESOURCE_PROFILES[args.profile]
@@ -2009,22 +2035,30 @@ def main() -> int:
2009
2035
 
2010
2036
  # Print summary
2011
2037
  if not args.quiet:
2038
+ from mdify.formatting import Colorizer
2039
+ color_out = Colorizer(sys.stdout)
2012
2040
  print()
2013
- print("=" * 50)
2014
- print("Conversion Summary:")
2015
- print(f" Total files: {total_files}")
2016
- print(f" Successful: {success_count}")
2017
- print(f" Skipped: {skipped_count}")
2018
- print(f" Failed: {failed_count}")
2019
- print(f" Total time: {format_duration(total_elapsed)}")
2020
- print("=" * 50)
2041
+ print(color_out.cyan("=" * 60))
2042
+ print(color_out.bold_cyan("📊 Local Conversion Summary"))
2043
+ print(color_out.cyan("=" * 60))
2044
+ print(f" {color_out.cyan('Total files:')} {color_out.bold(str(total_files))}")
2045
+ if success_count > 0:
2046
+ print(f" {color_out.green('✓ Successful:')} {color_out.bold_green(str(success_count))}")
2047
+ if skipped_count > 0:
2048
+ print(f" {color_out.yellow('⊘ Skipped:')} {color_out.bold_yellow(str(skipped_count))}")
2049
+ if failed_count > 0:
2050
+ print(f" {color_out.red('✗ Failed:')} {color_out.bold_red(str(failed_count))}")
2051
+ print(f" {color_out.cyan('Total time:')} {color_out.bright_cyan(format_duration(total_elapsed))}")
2052
+ print(color_out.cyan("=" * 60))
2021
2053
 
2022
2054
  except KeyboardInterrupt:
2023
2055
  if not args.quiet:
2024
- print("\n\nInterrupted by user. Container stopped.")
2056
+ from mdify.formatting import Colorizer
2057
+ color_out = Colorizer(sys.stdout)
2058
+ print(f"\n\n{color_out.warning('⚠ Interrupted by user. Container stopped.')}")
2025
2059
  if success_count > 0 or skipped_count > 0 or failed_count > 0:
2026
2060
  print(
2027
- f"Partial progress: {success_count} successful, {failed_count} failed, {skipped_count} skipped"
2061
+ f"{color_out.dim_white('Partial progress:')} {color_out.green(str(success_count))} successful, {color_out.red(str(failed_count))} failed, {color_out.yellow(str(skipped_count))} skipped"
2028
2062
  )
2029
2063
  return 130
2030
2064
 
@@ -0,0 +1,138 @@
1
+ """Formatting helpers for CLI output."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TextIO
7
+
8
+
9
+ class Colorizer:
10
+ """ANSI color and style helper for terminal output."""
11
+
12
+ # ANSI color codes
13
+ RESET = "0"
14
+ BOLD = "1"
15
+ DIM = "2"
16
+ ITALIC = "3"
17
+ UNDERLINE = "4"
18
+
19
+ # Colors
20
+ BLACK = "30"
21
+ RED = "31"
22
+ GREEN = "32"
23
+ YELLOW = "33"
24
+ BLUE = "34"
25
+ MAGENTA = "35"
26
+ CYAN = "36"
27
+ WHITE = "37"
28
+ BRIGHT_BLACK = "90"
29
+ BRIGHT_RED = "91"
30
+ BRIGHT_GREEN = "92"
31
+ BRIGHT_YELLOW = "93"
32
+ BRIGHT_BLUE = "94"
33
+ BRIGHT_MAGENTA = "95"
34
+ BRIGHT_CYAN = "96"
35
+ BRIGHT_WHITE = "97"
36
+
37
+ def __init__(self, stream: TextIO) -> None:
38
+ force_color = os.environ.get("FORCE_COLOR")
39
+ no_color = os.environ.get("NO_COLOR")
40
+ self.enabled = bool(force_color) or (stream.isatty() and not no_color)
41
+
42
+ def _apply(self, text: str, *codes: str) -> str:
43
+ """Apply ANSI codes to text. Supports multiple codes."""
44
+ if not self.enabled:
45
+ return text
46
+ code_str = ";".join(codes)
47
+ return f"\033[{code_str}m{text}\033[0m"
48
+
49
+ def color(self, text: str, code: str) -> str:
50
+ """Apply a single color code."""
51
+ return self._apply(text, code)
52
+
53
+ # Basic colors
54
+ def green(self, text: str) -> str:
55
+ return self._apply(text, self.GREEN)
56
+
57
+ def yellow(self, text: str) -> str:
58
+ return self._apply(text, self.YELLOW)
59
+
60
+ def cyan(self, text: str) -> str:
61
+ return self._apply(text, self.CYAN)
62
+
63
+ def red(self, text: str) -> str:
64
+ return self._apply(text, self.RED)
65
+
66
+ def blue(self, text: str) -> str:
67
+ return self._apply(text, self.BLUE)
68
+
69
+ def magenta(self, text: str) -> str:
70
+ return self._apply(text, self.MAGENTA)
71
+
72
+ def white(self, text: str) -> str:
73
+ return self._apply(text, self.WHITE)
74
+
75
+ # Bright colors
76
+ def bright_green(self, text: str) -> str:
77
+ return self._apply(text, self.BRIGHT_GREEN)
78
+
79
+ def bright_yellow(self, text: str) -> str:
80
+ return self._apply(text, self.BRIGHT_YELLOW)
81
+
82
+ def bright_cyan(self, text: str) -> str:
83
+ return self._apply(text, self.BRIGHT_CYAN)
84
+
85
+ def bright_red(self, text: str) -> str:
86
+ return self._apply(text, self.BRIGHT_RED)
87
+
88
+ def bright_white(self, text: str) -> str:
89
+ return self._apply(text, self.BRIGHT_WHITE)
90
+
91
+ # Styles
92
+ def bold(self, text: str) -> str:
93
+ return self._apply(text, self.BOLD)
94
+
95
+ def dim(self, text: str) -> str:
96
+ return self._apply(text, self.DIM)
97
+
98
+ def underline(self, text: str) -> str:
99
+ return self._apply(text, self.UNDERLINE)
100
+
101
+ # Combined styles
102
+ def bold_green(self, text: str) -> str:
103
+ return self._apply(text, self.BOLD, self.GREEN)
104
+
105
+ def bold_cyan(self, text: str) -> str:
106
+ return self._apply(text, self.BOLD, self.CYAN)
107
+
108
+ def bold_yellow(self, text: str) -> str:
109
+ return self._apply(text, self.BOLD, self.YELLOW)
110
+
111
+ def bold_red(self, text: str) -> str:
112
+ return self._apply(text, self.BOLD, self.RED)
113
+
114
+ def dim_cyan(self, text: str) -> str:
115
+ return self._apply(text, self.DIM, self.CYAN)
116
+
117
+ def dim_white(self, text: str) -> str:
118
+ return self._apply(text, self.DIM, self.WHITE)
119
+
120
+ def success(self, text: str) -> str:
121
+ """Styled success message (bold green)."""
122
+ return self._apply(text, self.BOLD, self.GREEN)
123
+
124
+ def error(self, text: str) -> str:
125
+ """Styled error message (bold red)."""
126
+ return self._apply(text, self.BOLD, self.RED)
127
+
128
+ def warning(self, text: str) -> str:
129
+ """Styled warning message (bold yellow)."""
130
+ return self._apply(text, self.BOLD, self.YELLOW)
131
+
132
+ def info(self, text: str) -> str:
133
+ """Styled info message (cyan)."""
134
+ return self._apply(text, self.CYAN)
135
+
136
+ def muted(self, text: str) -> str:
137
+ """Muted text (dim white)."""
138
+ return self._apply(text, self.DIM, self.WHITE)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdify-cli
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Convert PDFs and document images into structured Markdown for LLM workflows
5
5
  Author: tiroq
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mdify-cli"
3
- version = "3.1.1"
3
+ version = "3.2.1"
4
4
  description = "Convert PDFs and document images into structured Markdown for LLM workflows"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,29 +0,0 @@
1
- """Formatting helpers for CLI output."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- from typing import TextIO
7
-
8
-
9
- class Colorizer:
10
- """ANSI color helper for terminal output."""
11
-
12
- def __init__(self, stream: TextIO) -> None:
13
- force_color = os.environ.get("FORCE_COLOR")
14
- no_color = os.environ.get("NO_COLOR")
15
- self.enabled = bool(force_color) or (stream.isatty() and not no_color)
16
-
17
- def color(self, text: str, code: str) -> str:
18
- if not self.enabled:
19
- return text
20
- return f"\033[{code}m{text}\033[0m"
21
-
22
- def green(self, text: str) -> str:
23
- return self.color(text, "32")
24
-
25
- def yellow(self, text: str) -> str:
26
- return self.color(text, "33")
27
-
28
- def cyan(self, text: str) -> str:
29
- return self.color(text, "36")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes