max-cli 0.2.0__tar.gz → 0.3.3__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 (72) hide show
  1. {max_cli-0.2.0/src/max_cli.egg-info → max_cli-0.3.3}/PKG-INFO +157 -35
  2. {max_cli-0.2.0 → max_cli-0.3.3}/README.md +155 -34
  3. {max_cli-0.2.0 → max_cli-0.3.3}/pyproject.toml +4 -3
  4. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/logger.py +2 -2
  5. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/config.py +12 -1
  6. max_cli-0.3.3/src/max_cli/core/cli/commands/__init__.py +22 -0
  7. max_cli-0.3.3/src/max_cli/core/cli/commands/ai.py +12 -0
  8. max_cli-0.3.3/src/max_cli/core/cli/commands/audio.py +14 -0
  9. max_cli-0.3.3/src/max_cli/core/cli/commands/config.py +11 -0
  10. max_cli-0.3.3/src/max_cli/core/cli/commands/files.py +14 -0
  11. max_cli-0.3.3/src/max_cli/core/cli/commands/media.py +19 -0
  12. max_cli-0.3.3/src/max_cli/core/cli/commands/network.py +16 -0
  13. max_cli-0.3.3/src/max_cli/core/cli/commands/plugins.py +113 -0
  14. max_cli-0.3.3/src/max_cli/core/cli/commands/tools.py +11 -0
  15. max_cli-0.3.3/src/max_cli/core/cli/plugins.py +25 -0
  16. max_cli-0.3.3/src/max_cli/core/cli/registry.py +22 -0
  17. max_cli-0.3.3/src/max_cli/core/engines/__init__.py +21 -0
  18. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/ai_engine.py +28 -7
  19. max_cli-0.3.3/src/max_cli/core/engines/audio_metadata_engine.py +317 -0
  20. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/network_engine.py +40 -7
  21. max_cli-0.3.3/src/max_cli/core/engines/queue_manager.py +458 -0
  22. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_ai.py +6 -1
  23. max_cli-0.3.3/src/max_cli/interface/cli_audio.py +249 -0
  24. max_cli-0.3.3/src/max_cli/interface/cli_config.py +14 -0
  25. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_files.py +4 -1
  26. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_images.py +5 -1
  27. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_media.py +4 -1
  28. max_cli-0.3.3/src/max_cli/interface/cli_network.py +564 -0
  29. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_pdf.py +8 -1
  30. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_tools.py +2 -1
  31. max_cli-0.3.3/src/max_cli/interface/config/__init__.py +6 -0
  32. max_cli-0.3.3/src/max_cli/interface/config/grab.py +84 -0
  33. max_cli-0.2.0/src/max_cli/interface/cli_config.py → max_cli-0.3.3/src/max_cli/interface/config/manage.py +41 -158
  34. max_cli-0.3.3/src/max_cli/interface/config/setup.py +83 -0
  35. max_cli-0.3.3/src/max_cli/main.py +37 -0
  36. max_cli-0.3.3/src/max_cli/plugins/__init__.py +20 -0
  37. max_cli-0.3.3/src/max_cli/plugins/base.py +178 -0
  38. max_cli-0.3.3/src/max_cli/plugins/manager.py +260 -0
  39. {max_cli-0.2.0 → max_cli-0.3.3/src/max_cli.egg-info}/PKG-INFO +157 -35
  40. max_cli-0.3.3/src/max_cli.egg-info/SOURCES.txt +63 -0
  41. max_cli-0.3.3/src/max_cli.egg-info/entry_points.txt +2 -0
  42. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/requires.txt +1 -0
  43. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_ai.py +20 -20
  44. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_file_organizer.py +1 -1
  45. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_images.py +1 -1
  46. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_media.py +1 -1
  47. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_network.py +1 -1
  48. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_pdf.py +1 -1
  49. max_cli-0.2.0/src/max_cli/interface/cli_network.py +0 -174
  50. max_cli-0.2.0/src/max_cli/main.py +0 -91
  51. max_cli-0.2.0/src/max_cli/plugins/__init__.py +0 -4
  52. max_cli-0.2.0/src/max_cli/plugins/base.py +0 -39
  53. max_cli-0.2.0/src/max_cli/plugins/manager.py +0 -81
  54. max_cli-0.2.0/src/max_cli.egg-info/SOURCES.txt +0 -44
  55. max_cli-0.2.0/src/max_cli.egg-info/entry_points.txt +0 -2
  56. {max_cli-0.2.0 → max_cli-0.3.3}/LICENSE +0 -0
  57. {max_cli-0.2.0 → max_cli-0.3.3}/setup.cfg +0 -0
  58. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/__init__.py +0 -0
  59. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/cache.py +0 -0
  60. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/concurrent.py +0 -0
  61. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/exceptions.py +0 -0
  62. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/logging.py +0 -0
  63. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/retry.py +0 -0
  64. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/utils.py +0 -0
  65. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/file_organizer.py +0 -0
  66. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/image_processor.py +0 -0
  67. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/media_engine.py +0 -0
  68. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/pdf_engine.py +0 -0
  69. {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/system_engine.py +0 -0
  70. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/dependency_links.txt +0 -0
  71. {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/top_level.txt +0 -0
  72. {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_cli_images.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: max-cli
3
- Version: 0.2.0
3
+ Version: 0.3.3
4
4
  Summary: The Local, Fast, & Lazy Terminal Assistant for high-performance tasks.
5
5
  Author-email: Abubakr Alsheikh <abubakralsheikh@outlook.com>
6
6
  Requires-Python: >=3.9
@@ -17,6 +17,7 @@ Requires-Dist: requests>=2.31.0
17
17
  Requires-Dist: yt-dlp>=2023.0.0
18
18
  Requires-Dist: segno>=1.5.0
19
19
  Requires-Dist: pyperclip>=1.8.0
20
+ Requires-Dist: mutagen>=1.47.0
20
21
  Provides-Extra: dev
21
22
  Requires-Dist: pytest>=7.0.0; extra == "dev"
22
23
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -60,7 +61,13 @@ Max transforms complex tasks—like compressing videos, merging PDFs, or downloa
60
61
  |------|-------------|---------|
61
62
  | **Compress videos** | `max video compress` | `max video compress movie.mp4` |
62
63
  | **Convert video to audio** | `max video to-audio` | `max video to-audio podcast.mp4` |
63
- | **Download videos/music** | `max grab` | `max grab "youtube.com/..."` |
64
+ | **Get audio metadata** | `max audio get` | `max audio get song.mp3` |
65
+ | **Set audio metadata** | `max audio set` | `max audio set song.mp3 --artist "Artist" --album "Album"` |
66
+ | **Batch organize audio** | `max audio batch` | `max audio batch "folder/*.mp3" --album "My Album" --artist "Band"` |
67
+ | **Organize to folders** | `max audio organize` | `max audio organize "*.mp3" --pattern artist` |
68
+ | **Download videos/music** | `max grab download` | `max grab download youtube.com/...` |
69
+ | **Check download queue** | `max grab queue` | `max grab queue` |
70
+ | **Download history** | `max grab history` | `max grab history` |
64
71
  | **Merge PDFs** | `max pdf bundle` | `max pdf bundle contracts/` |
65
72
  | **Compress images** | `max img compress` | `max img compress photos/` |
66
73
  | **Resize images** | `max img resize` | `max img resize logo.png --width 800` |
@@ -114,14 +121,33 @@ max config setup
114
121
 
115
122
  This wizard will guide you through:
116
123
 
117
- - Choosing your AI provider (Google Gemini, OpenAI, or custom)
118
- - Entering your API key
124
+ - Choosing your AI provider (Google Gemini, OpenAI, Ollama, or custom)
125
+ - Entering your API key (not needed for Ollama)
119
126
  - Setting preferences
120
127
 
121
- **Get a free API key:**
128
+ **AI Provider Options:**
122
129
 
123
- - [Google AI Studio](https://aistudio.google.com/app/apikey) - Free tier available
124
- - [OpenAI](https://platform.openai.com/api-keys) - Pay-as-you-go
130
+ | Provider | API Key Required | Best For |
131
+ |----------|------------------|----------|
132
+ | [Google Gemini](https://aistudio.google.com/app/apikey) | Yes | Free tier, vision support |
133
+ | [OpenAI](https://platform.openai.com/api-keys) | Yes | GPT models, image generation |
134
+ | **Ollama** | No | Local/private AI, no internet needed |
135
+
136
+ **Using Ollama (Local AI):**
137
+
138
+ If you want to run AI locally without an internet connection:
139
+
140
+ 1. Install [Ollama](https://ollama.com)
141
+ 2. Run `max config setup` and select "ollama"
142
+ 3. Choose your model (e.g., llama3, mistral, codellama)
143
+
144
+ ```bash
145
+ # Example Ollama .env settings
146
+ OLLAMA_ENABLED=true
147
+ OLLAMA_MODEL=llama3
148
+ OPENAI_BASE_URL=http://localhost:11434/v1
149
+ OPENAI_API_KEY=ollama
150
+ ```
125
151
 
126
152
  ### Step 5: Start Using Max
127
153
 
@@ -237,19 +263,49 @@ Download from almost any website:
237
263
 
238
264
  ```bash
239
265
  # Download video (best quality)
240
- max grab "https://youtube.com/watch?v=..."
266
+ max grab download "https://youtube.com/watch?v=..."
241
267
 
242
268
  # Download audio only (MP3)
243
- max grab "https://youtube.com/watch?v=..." -a
269
+ max grab download "https://youtube.com/watch?v=..." -a
270
+
271
+ # Force video download (override default)
272
+ max grab download "https://youtube.com/watch?v=..." -v
244
273
 
245
274
  # Choose quality: s=480p, m=720p, h=1080p, x=4K
246
- max grab "..." -q h
275
+ max grab download "..." -q h
276
+
277
+ # Interactive mode - add URLs and download in background
278
+ max grab download
279
+
280
+ # Download to specific folder
281
+ max grab download "..." -o ./my-videos
282
+
283
+ # Add to queue without processing (batch mode)
284
+ max grab download "..." --no-process
285
+ ```
286
+
287
+ #### Interactive Mode
288
+
289
+ Run `max grab download` without a URL to enter interactive mode:
290
+
291
+ ```bash
292
+ max grab download
293
+ # Enter URL and press Enter - download starts in background
294
+ # Enter another URL while the first is downloading
295
+ # Press Enter with empty input to exit
296
+ ```
297
+
298
+ #### Queue System
299
+
300
+ ```bash
301
+ # Check download queue
302
+ max grab queue
247
303
 
248
- # Download a playlist
249
- max grab "https://youtube.com/playlist?list=..."
304
+ # Show download history
305
+ max grab history
250
306
 
251
- # Download specific video from playlist
252
- max grab "..." -i 3
307
+ # Clear completed/failed downloads
308
+ max grab clear
253
309
  ```
254
310
 
255
311
  #### Quality Presets Explained
@@ -267,8 +323,13 @@ max grab "..." -i 3
267
323
  # Set your defaults once
268
324
  max config grab
269
325
 
270
- # Now just run:
271
- max grab "url" # Uses your saved preferences
326
+ # Configure:
327
+ # - Default quality (s/m/h/x)
328
+ # - Auto-strip playlist info
329
+ # - Embed metadata
330
+ # - Default type (video/audio)
331
+ # - Default download folder
332
+ # - Enable/disable queue system
272
333
  ```
273
334
 
274
335
  ---
@@ -433,6 +494,9 @@ AI_MODEL=gpt-4o-mini
433
494
  DEFAULT_QUALITY=80
434
495
  GRAB_QUALITY=h
435
496
  GRAB_AUDIO_FORMAT=mp3
497
+ GRAB_DEFAULT_PATH=~/Max Downloads
498
+ GRAB_DEFAULT_TYPE=video
499
+ GRAB_QUEUE_ENABLED=true
436
500
  ```
437
501
 
438
502
  ### Configuration Locations
@@ -566,39 +630,97 @@ Max CLI supports plugins for extensibility. Plugins are stored in:
566
630
  - `~/.max_cli/plugins/` (user-level)
567
631
  - `./plugins/` (project-level)
568
632
 
569
- Example plugin structure:
633
+ #### Plugin Commands
634
+
635
+ ```bash
636
+ # List all installed plugins
637
+ max plugins list
638
+ max plugins list --all # Include disabled plugins
639
+
640
+ # Get detailed info about a plugin
641
+ max plugins info <plugin-name>
642
+
643
+ # Enable or disable a plugin
644
+ max plugins enable <plugin-name>
645
+ max plugins disable <plugin-name>
646
+ ```
647
+
648
+ #### Creating a Plugin
570
649
 
571
650
  ```python
572
- from max_cli.plugins.base import CLIPlugin
573
651
  import typer
652
+ from max_cli.plugins.base import CLIPlugin
653
+
574
654
 
575
655
  class MyPlugin(CLIPlugin):
656
+ """My custom plugin."""
657
+
658
+ def __init__(self):
659
+ super().__init__(
660
+ name="my-plugin",
661
+ version="1.0.0",
662
+ description="My custom plugin description",
663
+ author="Your Name",
664
+ author_email="you@example.com",
665
+ url="https://github.com/you/plugin",
666
+ license="MIT",
667
+ tags=["custom", "example"],
668
+ )
669
+
576
670
  @property
577
- def name(self) -> str:
578
- return "my-plugin"
579
-
580
- @property
581
- def version(self) -> str:
582
- return "1.0.0"
583
-
584
- @property
585
- def description(self) -> str:
586
- return "My custom plugin"
587
-
671
+ def priority(self) -> int:
672
+ """Lower = registered first. Default is 100."""
673
+ return 100
674
+
675
+ def validate(self) -> tuple[bool, str | None]:
676
+ """Validate plugin requirements."""
677
+ return True, None
678
+
679
+ def on_load(self, context) -> None:
680
+ """Called when plugin loads."""
681
+ pass
682
+
683
+ def on_unload(self) -> None:
684
+ """Called when plugin unloads."""
685
+ pass
686
+
588
687
  def register(self, app: typer.Typer) -> None:
589
688
  @app.command("my-command")
590
- def my_command():
591
- typer.echo("Hello from my plugin!")
689
+ @app.command("mc") # Alias
690
+ def my_command(
691
+ name: str = typer.Option("World", "--name", "-n", help="Name to greet"),
692
+ ) -> None:
693
+ """My custom command."""
694
+ typer.echo(f"Hello, {name}!")
695
+
696
+
697
+ # IMPORTANT: Instantiate at module level
698
+ plugin = MyPlugin()
592
699
  ```
593
700
 
701
+ For detailed documentation, see `PLANS/docs/plugins.md`.
702
+
594
703
  ### Architecture
595
704
 
596
705
  ```
597
706
  src/max_cli/
598
- ├── core/ # Business logic (engines)
599
- ├── interface/ # CLI commands (Typer)
600
- ├── common/ # Shared utilities
601
- └── __init__.py # Package exports
707
+ ├── core/
708
+ ├── engines/ # Business logic (AI, media, PDF, etc.)
709
+ │ │ ├── ai_engine.py
710
+ │ │ ├── file_organizer.py
711
+ │ │ ├── image_processor.py
712
+ │ │ ├── media_engine.py
713
+ │ │ ├── network_engine.py
714
+ │ │ ├── pdf_engine.py
715
+ │ │ ├── queue_manager.py
716
+ │ │ └── system_engine.py
717
+ │ └── cli/ # CLI command registration
718
+ │ ├── commands/ # Command modules
719
+ │ ├── plugins.py # Plugin lifecycle
720
+ │ └── registry.py # Command registry
721
+ ├── interface/ # Typer CLI command interfaces
722
+ ├── common/ # Shared utilities and exceptions
723
+ └── __init__.py # Package exports
602
724
  ```
603
725
 
604
726
  ---
@@ -31,7 +31,13 @@ Max transforms complex tasks—like compressing videos, merging PDFs, or downloa
31
31
  |------|-------------|---------|
32
32
  | **Compress videos** | `max video compress` | `max video compress movie.mp4` |
33
33
  | **Convert video to audio** | `max video to-audio` | `max video to-audio podcast.mp4` |
34
- | **Download videos/music** | `max grab` | `max grab "youtube.com/..."` |
34
+ | **Get audio metadata** | `max audio get` | `max audio get song.mp3` |
35
+ | **Set audio metadata** | `max audio set` | `max audio set song.mp3 --artist "Artist" --album "Album"` |
36
+ | **Batch organize audio** | `max audio batch` | `max audio batch "folder/*.mp3" --album "My Album" --artist "Band"` |
37
+ | **Organize to folders** | `max audio organize` | `max audio organize "*.mp3" --pattern artist` |
38
+ | **Download videos/music** | `max grab download` | `max grab download youtube.com/...` |
39
+ | **Check download queue** | `max grab queue` | `max grab queue` |
40
+ | **Download history** | `max grab history` | `max grab history` |
35
41
  | **Merge PDFs** | `max pdf bundle` | `max pdf bundle contracts/` |
36
42
  | **Compress images** | `max img compress` | `max img compress photos/` |
37
43
  | **Resize images** | `max img resize` | `max img resize logo.png --width 800` |
@@ -85,14 +91,33 @@ max config setup
85
91
 
86
92
  This wizard will guide you through:
87
93
 
88
- - Choosing your AI provider (Google Gemini, OpenAI, or custom)
89
- - Entering your API key
94
+ - Choosing your AI provider (Google Gemini, OpenAI, Ollama, or custom)
95
+ - Entering your API key (not needed for Ollama)
90
96
  - Setting preferences
91
97
 
92
- **Get a free API key:**
98
+ **AI Provider Options:**
93
99
 
94
- - [Google AI Studio](https://aistudio.google.com/app/apikey) - Free tier available
95
- - [OpenAI](https://platform.openai.com/api-keys) - Pay-as-you-go
100
+ | Provider | API Key Required | Best For |
101
+ |----------|------------------|----------|
102
+ | [Google Gemini](https://aistudio.google.com/app/apikey) | Yes | Free tier, vision support |
103
+ | [OpenAI](https://platform.openai.com/api-keys) | Yes | GPT models, image generation |
104
+ | **Ollama** | No | Local/private AI, no internet needed |
105
+
106
+ **Using Ollama (Local AI):**
107
+
108
+ If you want to run AI locally without an internet connection:
109
+
110
+ 1. Install [Ollama](https://ollama.com)
111
+ 2. Run `max config setup` and select "ollama"
112
+ 3. Choose your model (e.g., llama3, mistral, codellama)
113
+
114
+ ```bash
115
+ # Example Ollama .env settings
116
+ OLLAMA_ENABLED=true
117
+ OLLAMA_MODEL=llama3
118
+ OPENAI_BASE_URL=http://localhost:11434/v1
119
+ OPENAI_API_KEY=ollama
120
+ ```
96
121
 
97
122
  ### Step 5: Start Using Max
98
123
 
@@ -208,19 +233,49 @@ Download from almost any website:
208
233
 
209
234
  ```bash
210
235
  # Download video (best quality)
211
- max grab "https://youtube.com/watch?v=..."
236
+ max grab download "https://youtube.com/watch?v=..."
212
237
 
213
238
  # Download audio only (MP3)
214
- max grab "https://youtube.com/watch?v=..." -a
239
+ max grab download "https://youtube.com/watch?v=..." -a
240
+
241
+ # Force video download (override default)
242
+ max grab download "https://youtube.com/watch?v=..." -v
215
243
 
216
244
  # Choose quality: s=480p, m=720p, h=1080p, x=4K
217
- max grab "..." -q h
245
+ max grab download "..." -q h
246
+
247
+ # Interactive mode - add URLs and download in background
248
+ max grab download
249
+
250
+ # Download to specific folder
251
+ max grab download "..." -o ./my-videos
252
+
253
+ # Add to queue without processing (batch mode)
254
+ max grab download "..." --no-process
255
+ ```
256
+
257
+ #### Interactive Mode
258
+
259
+ Run `max grab download` without a URL to enter interactive mode:
260
+
261
+ ```bash
262
+ max grab download
263
+ # Enter URL and press Enter - download starts in background
264
+ # Enter another URL while the first is downloading
265
+ # Press Enter with empty input to exit
266
+ ```
267
+
268
+ #### Queue System
269
+
270
+ ```bash
271
+ # Check download queue
272
+ max grab queue
218
273
 
219
- # Download a playlist
220
- max grab "https://youtube.com/playlist?list=..."
274
+ # Show download history
275
+ max grab history
221
276
 
222
- # Download specific video from playlist
223
- max grab "..." -i 3
277
+ # Clear completed/failed downloads
278
+ max grab clear
224
279
  ```
225
280
 
226
281
  #### Quality Presets Explained
@@ -238,8 +293,13 @@ max grab "..." -i 3
238
293
  # Set your defaults once
239
294
  max config grab
240
295
 
241
- # Now just run:
242
- max grab "url" # Uses your saved preferences
296
+ # Configure:
297
+ # - Default quality (s/m/h/x)
298
+ # - Auto-strip playlist info
299
+ # - Embed metadata
300
+ # - Default type (video/audio)
301
+ # - Default download folder
302
+ # - Enable/disable queue system
243
303
  ```
244
304
 
245
305
  ---
@@ -404,6 +464,9 @@ AI_MODEL=gpt-4o-mini
404
464
  DEFAULT_QUALITY=80
405
465
  GRAB_QUALITY=h
406
466
  GRAB_AUDIO_FORMAT=mp3
467
+ GRAB_DEFAULT_PATH=~/Max Downloads
468
+ GRAB_DEFAULT_TYPE=video
469
+ GRAB_QUEUE_ENABLED=true
407
470
  ```
408
471
 
409
472
  ### Configuration Locations
@@ -537,39 +600,97 @@ Max CLI supports plugins for extensibility. Plugins are stored in:
537
600
  - `~/.max_cli/plugins/` (user-level)
538
601
  - `./plugins/` (project-level)
539
602
 
540
- Example plugin structure:
603
+ #### Plugin Commands
604
+
605
+ ```bash
606
+ # List all installed plugins
607
+ max plugins list
608
+ max plugins list --all # Include disabled plugins
609
+
610
+ # Get detailed info about a plugin
611
+ max plugins info <plugin-name>
612
+
613
+ # Enable or disable a plugin
614
+ max plugins enable <plugin-name>
615
+ max plugins disable <plugin-name>
616
+ ```
617
+
618
+ #### Creating a Plugin
541
619
 
542
620
  ```python
543
- from max_cli.plugins.base import CLIPlugin
544
621
  import typer
622
+ from max_cli.plugins.base import CLIPlugin
623
+
545
624
 
546
625
  class MyPlugin(CLIPlugin):
626
+ """My custom plugin."""
627
+
628
+ def __init__(self):
629
+ super().__init__(
630
+ name="my-plugin",
631
+ version="1.0.0",
632
+ description="My custom plugin description",
633
+ author="Your Name",
634
+ author_email="you@example.com",
635
+ url="https://github.com/you/plugin",
636
+ license="MIT",
637
+ tags=["custom", "example"],
638
+ )
639
+
547
640
  @property
548
- def name(self) -> str:
549
- return "my-plugin"
550
-
551
- @property
552
- def version(self) -> str:
553
- return "1.0.0"
554
-
555
- @property
556
- def description(self) -> str:
557
- return "My custom plugin"
558
-
641
+ def priority(self) -> int:
642
+ """Lower = registered first. Default is 100."""
643
+ return 100
644
+
645
+ def validate(self) -> tuple[bool, str | None]:
646
+ """Validate plugin requirements."""
647
+ return True, None
648
+
649
+ def on_load(self, context) -> None:
650
+ """Called when plugin loads."""
651
+ pass
652
+
653
+ def on_unload(self) -> None:
654
+ """Called when plugin unloads."""
655
+ pass
656
+
559
657
  def register(self, app: typer.Typer) -> None:
560
658
  @app.command("my-command")
561
- def my_command():
562
- typer.echo("Hello from my plugin!")
659
+ @app.command("mc") # Alias
660
+ def my_command(
661
+ name: str = typer.Option("World", "--name", "-n", help="Name to greet"),
662
+ ) -> None:
663
+ """My custom command."""
664
+ typer.echo(f"Hello, {name}!")
665
+
666
+
667
+ # IMPORTANT: Instantiate at module level
668
+ plugin = MyPlugin()
563
669
  ```
564
670
 
671
+ For detailed documentation, see `PLANS/docs/plugins.md`.
672
+
565
673
  ### Architecture
566
674
 
567
675
  ```
568
676
  src/max_cli/
569
- ├── core/ # Business logic (engines)
570
- ├── interface/ # CLI commands (Typer)
571
- ├── common/ # Shared utilities
572
- └── __init__.py # Package exports
677
+ ├── core/
678
+ ├── engines/ # Business logic (AI, media, PDF, etc.)
679
+ │ │ ├── ai_engine.py
680
+ │ │ ├── file_organizer.py
681
+ │ │ ├── image_processor.py
682
+ │ │ ├── media_engine.py
683
+ │ │ ├── network_engine.py
684
+ │ │ ├── pdf_engine.py
685
+ │ │ ├── queue_manager.py
686
+ │ │ └── system_engine.py
687
+ │ └── cli/ # CLI command registration
688
+ │ ├── commands/ # Command modules
689
+ │ ├── plugins.py # Plugin lifecycle
690
+ │ └── registry.py # Command registry
691
+ ├── interface/ # Typer CLI command interfaces
692
+ ├── common/ # Shared utilities and exceptions
693
+ └── __init__.py # Package exports
573
694
  ```
574
695
 
575
696
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "max-cli"
7
- version = "0.2.0"
7
+ version = "0.3.3"
8
8
  description = "The Local, Fast, & Lazy Terminal Assistant for high-performance tasks."
9
9
  authors = [{name = "Abubakr Alsheikh", email = "abubakralsheikh@outlook.com"}]
10
10
  readme = "README.md"
@@ -20,7 +20,8 @@ dependencies = [
20
20
  "requests>=2.31.0",
21
21
  "yt-dlp>=2023.0.0",
22
22
  "segno>=1.5.0",
23
- "pyperclip>=1.8.0"
23
+ "pyperclip>=1.8.0",
24
+ "mutagen>=1.47.0"
24
25
  ]
25
26
 
26
27
  [project.optional-dependencies]
@@ -36,7 +37,7 @@ ocr = [
36
37
  ]
37
38
 
38
39
  [project.scripts]
39
- max = "max_cli.main:app"
40
+ max = "max_cli.main:main"
40
41
 
41
42
  [tool.ruff]
42
43
  line-length = 88
@@ -15,8 +15,8 @@ console = Console(theme=custom_theme)
15
15
 
16
16
 
17
17
  def log_error(message: str):
18
- console.print(f"[error] Error:[/error] {message}")
18
+ console.print(f"[error]X Error:[/error] {message}")
19
19
 
20
20
 
21
21
  def log_success(message: str):
22
- console.print(f"[success] Success:[/success] {message}")
22
+ console.print(f"[success]V Success:[/success] {message}")
@@ -21,11 +21,17 @@ class Settings(BaseSettings):
21
21
  # AI Configuration
22
22
  # If using OpenAI, leave BASE_URL as None.
23
23
  # If using Gemini, set to: https://generativelanguage.googleapis.com/v1beta/openai/
24
+ # If using Ollama, set to: http://localhost:11434/v1
24
25
  OPENAI_API_KEY: Optional[str] = None
25
26
  OPENAI_BASE_URL: Optional[str] = None
26
27
  # Models
27
28
  AI_MODEL: str = "gpt-5-nano" # For 'ask', 'chat', 'analyze'
28
- AI_IMAGE_MODEL: str = "gemini-2.5-flash-image" # For 'create', 'edit'
29
+ AI_IMAGE_MODEL: str = "gpt-image-1" # For 'create', 'edit'
30
+
31
+ # Ollama Configuration
32
+ OLLAMA_ENABLED: bool = False
33
+ OLLAMA_BASE_URL: str = "http://localhost:11434"
34
+ OLLAMA_MODEL: str = "llama3"
29
35
 
30
36
  # --- GRAB (DOWNLOADER) DEFAULTS ---
31
37
  # These save your preferences
@@ -34,6 +40,11 @@ class Settings(BaseSettings):
34
40
  GRAB_STRIP_PLAYLIST: bool = True # If True, removes '&list=...' from video URLs
35
41
  GRAB_INCLUDE_METADATA: bool = True # If True, embeds tags/thumbnails
36
42
 
43
+ # New: Default path and type for downloads
44
+ GRAB_DEFAULT_PATH: Path = Path.home() / "Max Downloads"
45
+ GRAB_DEFAULT_TYPE: str = "video" # "video" or "audio"
46
+ GRAB_QUEUE_ENABLED: bool = False
47
+
37
48
  class Config:
38
49
  env_file = [str(Path.home() / ".max_config.env"), ".env"]
39
50
  env_file_encoding = "utf-8"
@@ -0,0 +1,22 @@
1
+ # Command registration modules
2
+ from max_cli.core.cli.commands import (
3
+ ai,
4
+ audio,
5
+ config,
6
+ files,
7
+ media,
8
+ network,
9
+ plugins as plugin_commands,
10
+ tools,
11
+ )
12
+
13
+ __all__ = [
14
+ "ai",
15
+ "audio",
16
+ "config",
17
+ "files",
18
+ "media",
19
+ "network",
20
+ "plugin_commands",
21
+ "tools",
22
+ ]
@@ -0,0 +1,12 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typer import Typer
5
+
6
+ from max_cli.interface import cli_ai
7
+
8
+
9
+ def register(app: "Typer") -> None:
10
+ """Register AI assistant commands."""
11
+ app.add_typer(cli_ai.app, name="ai", help="Ask AI to run commands.")
12
+ cli_ai.MAIN_APP_REF = app
@@ -0,0 +1,14 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typer import Typer
5
+
6
+ from max_cli.interface import cli_audio
7
+
8
+
9
+ def register(app: "Typer") -> None:
10
+ """Register audio metadata commands."""
11
+ app.add_typer(
12
+ cli_audio.app, name="audio", help="Read, write, and manage audio metadata."
13
+ )
14
+ app.add_typer(cli_audio.app, name="a", hidden=True)
@@ -0,0 +1,11 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typer import Typer
5
+
6
+ from max_cli.interface import cli_config
7
+
8
+
9
+ def register(app: "Typer") -> None:
10
+ """Register configuration commands."""
11
+ app.add_typer(cli_config.app, name="config", help="Manage API keys and settings.")
@@ -0,0 +1,14 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typer import Typer
5
+
6
+ from max_cli.interface import cli_files, cli_pdf
7
+
8
+
9
+ def register(app: "Typer") -> None:
10
+ """Register file management commands."""
11
+ app.add_typer(cli_files.app, name="files", help="Organize and bulk-rename files.")
12
+ app.add_typer(cli_files.app, name="file", hidden=True)
13
+
14
+ app.add_typer(cli_pdf.app, name="pdf", help="Merge, split, and compress PDFs.")