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.
- {max_cli-0.2.0/src/max_cli.egg-info → max_cli-0.3.3}/PKG-INFO +157 -35
- {max_cli-0.2.0 → max_cli-0.3.3}/README.md +155 -34
- {max_cli-0.2.0 → max_cli-0.3.3}/pyproject.toml +4 -3
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/logger.py +2 -2
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/config.py +12 -1
- max_cli-0.3.3/src/max_cli/core/cli/commands/__init__.py +22 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/ai.py +12 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/audio.py +14 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/config.py +11 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/files.py +14 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/media.py +19 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/network.py +16 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/plugins.py +113 -0
- max_cli-0.3.3/src/max_cli/core/cli/commands/tools.py +11 -0
- max_cli-0.3.3/src/max_cli/core/cli/plugins.py +25 -0
- max_cli-0.3.3/src/max_cli/core/cli/registry.py +22 -0
- max_cli-0.3.3/src/max_cli/core/engines/__init__.py +21 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/ai_engine.py +28 -7
- max_cli-0.3.3/src/max_cli/core/engines/audio_metadata_engine.py +317 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/network_engine.py +40 -7
- max_cli-0.3.3/src/max_cli/core/engines/queue_manager.py +458 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_ai.py +6 -1
- max_cli-0.3.3/src/max_cli/interface/cli_audio.py +249 -0
- max_cli-0.3.3/src/max_cli/interface/cli_config.py +14 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_files.py +4 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_images.py +5 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_media.py +4 -1
- max_cli-0.3.3/src/max_cli/interface/cli_network.py +564 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_pdf.py +8 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/interface/cli_tools.py +2 -1
- max_cli-0.3.3/src/max_cli/interface/config/__init__.py +6 -0
- max_cli-0.3.3/src/max_cli/interface/config/grab.py +84 -0
- 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
- max_cli-0.3.3/src/max_cli/interface/config/setup.py +83 -0
- max_cli-0.3.3/src/max_cli/main.py +37 -0
- max_cli-0.3.3/src/max_cli/plugins/__init__.py +20 -0
- max_cli-0.3.3/src/max_cli/plugins/base.py +178 -0
- max_cli-0.3.3/src/max_cli/plugins/manager.py +260 -0
- {max_cli-0.2.0 → max_cli-0.3.3/src/max_cli.egg-info}/PKG-INFO +157 -35
- max_cli-0.3.3/src/max_cli.egg-info/SOURCES.txt +63 -0
- max_cli-0.3.3/src/max_cli.egg-info/entry_points.txt +2 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/requires.txt +1 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_ai.py +20 -20
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_file_organizer.py +1 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_images.py +1 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_media.py +1 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_network.py +1 -1
- {max_cli-0.2.0 → max_cli-0.3.3}/tests/test_core_pdf.py +1 -1
- max_cli-0.2.0/src/max_cli/interface/cli_network.py +0 -174
- max_cli-0.2.0/src/max_cli/main.py +0 -91
- max_cli-0.2.0/src/max_cli/plugins/__init__.py +0 -4
- max_cli-0.2.0/src/max_cli/plugins/base.py +0 -39
- max_cli-0.2.0/src/max_cli/plugins/manager.py +0 -81
- max_cli-0.2.0/src/max_cli.egg-info/SOURCES.txt +0 -44
- max_cli-0.2.0/src/max_cli.egg-info/entry_points.txt +0 -2
- {max_cli-0.2.0 → max_cli-0.3.3}/LICENSE +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/setup.cfg +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/__init__.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/cache.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/concurrent.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/exceptions.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/logging.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/retry.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli/common/utils.py +0 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/file_organizer.py +0 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/image_processor.py +0 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/media_engine.py +0 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/pdf_engine.py +0 -0
- {max_cli-0.2.0/src/max_cli/core → max_cli-0.3.3/src/max_cli/core/engines}/system_engine.py +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/dependency_links.txt +0 -0
- {max_cli-0.2.0 → max_cli-0.3.3}/src/max_cli.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-
| **
|
|
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
|
-
**
|
|
128
|
+
**AI Provider Options:**
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
#
|
|
249
|
-
max grab
|
|
304
|
+
# Show download history
|
|
305
|
+
max grab history
|
|
250
306
|
|
|
251
|
-
#
|
|
252
|
-
max grab
|
|
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
|
-
#
|
|
271
|
-
|
|
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
|
-
|
|
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
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
def
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def
|
|
586
|
-
|
|
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
|
-
|
|
591
|
-
|
|
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/
|
|
599
|
-
├──
|
|
600
|
-
├──
|
|
601
|
-
|
|
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
|
-
| **
|
|
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
|
-
**
|
|
98
|
+
**AI Provider Options:**
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
#
|
|
220
|
-
max grab
|
|
274
|
+
# Show download history
|
|
275
|
+
max grab history
|
|
221
276
|
|
|
222
|
-
#
|
|
223
|
-
max grab
|
|
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
|
-
#
|
|
242
|
-
|
|
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
|
-
|
|
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
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
def
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
def
|
|
557
|
-
|
|
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
|
-
|
|
562
|
-
|
|
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/
|
|
570
|
-
├──
|
|
571
|
-
├──
|
|
572
|
-
|
|
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.
|
|
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:
|
|
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]
|
|
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]
|
|
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 = "
|
|
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.")
|