modforge-cli 0.2.3__py3-none-any.whl

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.
@@ -0,0 +1,150 @@
1
+ """
2
+ Modpack operations - Add, resolve, build
3
+ """
4
+
5
+ import asyncio
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from modforge_cli.api import ModrinthAPIConfig
11
+ from modforge_cli.cli.shared import MODRINTH_API, POLICY_PATH, REGISTRY_PATH, console
12
+ from modforge_cli.core import (
13
+ ModPolicy,
14
+ ModResolver,
15
+ get_api_session,
16
+ get_manifest,
17
+ load_registry,
18
+ perform_add,
19
+ run,
20
+ )
21
+
22
+ app = typer.Typer()
23
+
24
+ # Initialize API
25
+ api = ModrinthAPIConfig(MODRINTH_API)
26
+
27
+
28
+ @app.command()
29
+ def add(name: str, project_type: str = "mod", pack_name: str | None = None) -> None:
30
+ """Add a project to the manifest"""
31
+
32
+ if project_type not in ["mod", "resourcepack", "shaderpack"]:
33
+ console.print(f"[red]Invalid type:[/red] {project_type}")
34
+ console.print("[yellow]Valid types:[/yellow] mod, resourcepack, shaderpack")
35
+ raise typer.Exit(1)
36
+
37
+ # Auto-detect pack if not specified
38
+ if not pack_name:
39
+ manifest = get_manifest(console, Path.cwd())
40
+ if manifest:
41
+ pack_name = manifest.name
42
+ else:
43
+ console.print("[red]No manifest found in current directory[/red]")
44
+ console.print("[yellow]Specify --pack-name or run from project directory[/yellow]")
45
+ raise typer.Exit(1)
46
+
47
+ registry = load_registry(REGISTRY_PATH)
48
+ if pack_name not in registry:
49
+ console.print(f"[red]Pack '{pack_name}' not found in registry[/red]")
50
+ console.print("[yellow]Available packs:[/yellow]")
51
+ for p in registry:
52
+ console.print(f" - {p}")
53
+ raise typer.Exit(1)
54
+
55
+ pack_path = Path(registry[pack_name])
56
+ manifest_file = pack_path / "ModForge-CLI.json"
57
+
58
+ manifest = get_manifest(console, pack_path)
59
+ if not manifest:
60
+ console.print(f"[red]Could not load manifest at {manifest_file}[/red]")
61
+ raise typer.Exit(1)
62
+
63
+ asyncio.run(perform_add(api, name, manifest, project_type, console, manifest_file))
64
+
65
+
66
+ @app.command()
67
+ def resolve(pack_name: str | None = None) -> None:
68
+ """Resolve all mod dependencies"""
69
+
70
+ # Auto-detect pack
71
+ if not pack_name:
72
+ manifest = get_manifest(console, Path.cwd())
73
+ if manifest:
74
+ pack_name = manifest.name
75
+ else:
76
+ console.print("[red]No manifest found[/red]")
77
+ raise typer.Exit(1)
78
+
79
+ registry = load_registry(REGISTRY_PATH)
80
+ if pack_name not in registry:
81
+ console.print(f"[red]Pack '{pack_name}' not found[/red]")
82
+ raise typer.Exit(1)
83
+
84
+ pack_path = Path(registry[pack_name])
85
+ manifest_file = pack_path / "ModForge-CLI.json"
86
+
87
+ manifest = get_manifest(console, pack_path)
88
+ if not manifest:
89
+ console.print("[red]Could not load manifest[/red]")
90
+ raise typer.Exit(1)
91
+
92
+ console.print(f"[cyan]Resolving dependencies for {pack_name}...[/cyan]")
93
+
94
+ policy = ModPolicy(POLICY_PATH)
95
+ resolver = ModResolver(
96
+ policy=policy, api=api, mc_version=manifest.minecraft, loader=manifest.loader
97
+ )
98
+
99
+ async def do_resolve():
100
+ async with await get_api_session() as session:
101
+ return await resolver.resolve(manifest.mods, session)
102
+
103
+ try:
104
+ resolved_mods = asyncio.run(do_resolve())
105
+ except Exception as e:
106
+ console.print(f"[red]Resolution failed:[/red] {e}")
107
+ raise typer.Exit(1) from e
108
+
109
+ manifest.mods = sorted(list(resolved_mods))
110
+ manifest_file.write_text(manifest.model_dump_json(indent=4))
111
+
112
+ console.print(f"[green]✓ Resolved {len(manifest.mods)} mods[/green]")
113
+
114
+
115
+ @app.command()
116
+ def build(pack_name: str | None = None) -> None:
117
+ """Download all mods and dependencies"""
118
+
119
+ if not pack_name:
120
+ manifest = get_manifest(console, Path.cwd())
121
+ if manifest:
122
+ pack_name = manifest.name
123
+ else:
124
+ console.print("[red]No manifest found[/red]")
125
+ raise typer.Exit(1)
126
+
127
+ registry = load_registry(REGISTRY_PATH)
128
+ if pack_name not in registry:
129
+ console.print(f"[red]Pack '{pack_name}' not found[/red]")
130
+ raise typer.Exit(1)
131
+
132
+ pack_path = Path(registry[pack_name])
133
+ manifest = get_manifest(console, pack_path)
134
+ if not manifest:
135
+ raise typer.Exit(1)
136
+
137
+ pack_root = pack_path
138
+ mods_dir = pack_root / "mods"
139
+ index_file = pack_root / "modrinth.index.json"
140
+
141
+ mods_dir.mkdir(exist_ok=True)
142
+
143
+ console.print(f"[cyan]Building {manifest.name}...[/cyan]")
144
+
145
+ try:
146
+ asyncio.run(run(api, manifest, mods_dir, index_file))
147
+ console.print("[green]✓ Build complete[/green]")
148
+ except Exception as e:
149
+ console.print(f"[red]Build failed:[/red] {e}")
150
+ raise typer.Exit(1) from e
@@ -0,0 +1,72 @@
1
+ """
2
+ Project management commands - List and remove projects
3
+ """
4
+
5
+ from pathlib import Path
6
+ import shutil
7
+
8
+ from rich.panel import Panel
9
+ from rich.prompt import Confirm
10
+ from rich.table import Table
11
+ import typer
12
+
13
+ from modforge_cli.cli.shared import REGISTRY_PATH, console
14
+ from modforge_cli.core import load_registry, save_registry_atomic
15
+
16
+ app = typer.Typer()
17
+
18
+
19
+ @app.command(name="ls")
20
+ def list_projects() -> None:
21
+ """List all registered modpacks"""
22
+ registry = load_registry(REGISTRY_PATH)
23
+
24
+ if not registry:
25
+ console.print("[yellow]No projects registered yet[/yellow]")
26
+ console.print("[dim]Run 'ModForge-CLI setup <name>' to create one[/dim]")
27
+ return
28
+
29
+ table = Table(title="ModForge-CLI Projects", header_style="bold magenta")
30
+ table.add_column("Name", style="cyan")
31
+ table.add_column("Location", style="dim")
32
+
33
+ for name, path in registry.items():
34
+ table.add_row(name, path)
35
+
36
+ console.print(table)
37
+
38
+
39
+ @app.command()
40
+ def remove(pack_name: str) -> None:
41
+ """Remove a modpack and unregister it"""
42
+ registry = load_registry(REGISTRY_PATH)
43
+
44
+ if pack_name not in registry:
45
+ console.print(f"[red]Pack '{pack_name}' not found[/red]")
46
+ raise typer.Exit(1)
47
+
48
+ pack_path = Path(registry[pack_name])
49
+
50
+ console.print(
51
+ Panel.fit(
52
+ f"[bold red]This will permanently delete:[/bold red]\n\n"
53
+ f"[white]{pack_name}[/white]\n"
54
+ f"[dim]{pack_path}[/dim]",
55
+ title="⚠️ Destructive Action",
56
+ border_style="red",
57
+ )
58
+ )
59
+
60
+ if not Confirm.ask("Are you sure?", default=False):
61
+ console.print("Aborted.")
62
+ raise typer.Exit()
63
+
64
+ # Remove directory
65
+ if pack_path.exists():
66
+ shutil.rmtree(pack_path)
67
+
68
+ # Update registry
69
+ del registry[pack_name]
70
+ save_registry_atomic(registry, REGISTRY_PATH)
71
+
72
+ console.print(f"[green]✓ Removed {pack_name}[/green]")
@@ -0,0 +1,72 @@
1
+ """
2
+ Setup command - Initialize a new modpack project
3
+ """
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from modforge_cli.cli.shared import FABRIC_LOADER_VERSION, REGISTRY_PATH, console
11
+ from modforge_cli.core import Manifest, load_registry, save_registry_atomic
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ @app.command()
17
+ def setup(
18
+ name: str,
19
+ mc: str = "1.21.1",
20
+ loader: str = "fabric",
21
+ loader_version: str = FABRIC_LOADER_VERSION,
22
+ ) -> None:
23
+ """Initialize a new modpack project"""
24
+ pack_dir = Path.cwd() / name
25
+
26
+ if pack_dir.exists():
27
+ console.print(f"[red]Error:[/red] Directory '{name}' already exists")
28
+ raise typer.Exit(1)
29
+
30
+ pack_dir.mkdir(parents=True, exist_ok=True)
31
+
32
+ # Create standard structure
33
+ for folder in [
34
+ "mods",
35
+ "overrides/resourcepacks",
36
+ "overrides/shaderpacks",
37
+ "overrides/config",
38
+ "overrides/config/openloader/data",
39
+ "versions",
40
+ ]:
41
+ (pack_dir / folder).mkdir(parents=True, exist_ok=True)
42
+
43
+ # Create manifest
44
+ manifest = Manifest(name=name, minecraft=mc, loader=loader, loader_version=loader_version)
45
+ (pack_dir / "ModForge-CLI.json").write_text(manifest.model_dump_json(indent=4))
46
+
47
+ # Create Modrinth index
48
+ loader_key_map = {
49
+ "fabric": "fabric-loader",
50
+ "quilt": "quilt-loader",
51
+ "forge": "forge",
52
+ "neoforge": "neoforge",
53
+ }
54
+ loader_key = loader_key_map.get(loader.lower(), loader.lower())
55
+
56
+ index_data = {
57
+ "formatVersion": 1,
58
+ "game": "minecraft",
59
+ "versionId": "1.0.0",
60
+ "name": name,
61
+ "files": [],
62
+ "dependencies": {loader_key: loader_version, "minecraft": mc},
63
+ }
64
+ (pack_dir / "modrinth.index.json").write_text(json.dumps(index_data, indent=2))
65
+
66
+ # Register project
67
+ registry = load_registry(REGISTRY_PATH)
68
+ registry[name] = str(pack_dir.absolute())
69
+ save_registry_atomic(registry, REGISTRY_PATH)
70
+
71
+ console.print(f"[green]✓ Project '{name}' created at {pack_dir}[/green]")
72
+ console.print(f"[dim]Run 'cd {name}' to enter the project[/dim]")
@@ -0,0 +1,41 @@
1
+ """
2
+ Shared utilities and constants for CLI commands.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ from rich.console import Console
8
+
9
+ # Shared console instance
10
+ console = Console()
11
+
12
+ # Configuration paths
13
+ CONFIG_PATH = Path.home() / ".config" / "ModForge-CLI"
14
+ REGISTRY_PATH = CONFIG_PATH / "registry.json"
15
+ MODRINTH_API = CONFIG_PATH / "modrinth_api.json"
16
+ POLICY_PATH = CONFIG_PATH / "policy.json"
17
+
18
+ # Constants
19
+ FABRIC_LOADER_VERSION = "0.16.9"
20
+
21
+ # URLs
22
+ GITHUB_RAW = "https://raw.githubusercontent.com/Frank1o3/ModForge-CLI"
23
+ VERSION_TAG = "v0.1.8"
24
+
25
+ FABRIC_INSTALLER_URL = (
26
+ "https://maven.fabricmc.net/net/fabricmc/fabric-installer/1.1.1/fabric-installer-1.1.1.jar"
27
+ )
28
+ FABRIC_INSTALLER_SHA256 = "8fa465768bd7fc452e08c3a1e5c8a6b4b5f6a4e64bc7def47f89d8d3a6f4e7b8"
29
+
30
+ DEFAULT_MODRINTH_API_URL = f"{GITHUB_RAW}/{VERSION_TAG}/configs/modrinth_api.json"
31
+ DEFAULT_POLICY_URL = f"{GITHUB_RAW}/{VERSION_TAG}/configs/policy.json"
32
+
33
+
34
+ def get_version_info() -> tuple[str, str]:
35
+ """Get version and author info"""
36
+ try:
37
+ from modforge_cli.__version__ import __author__, __version__
38
+
39
+ return __version__, __author__
40
+ except ImportError:
41
+ return "unknown", "Frank1o3"
@@ -0,0 +1,125 @@
1
+ """
2
+ SKLauncher integration command
3
+ """
4
+
5
+ from datetime import datetime
6
+ import json
7
+ from pathlib import Path
8
+ import platform
9
+ import shutil
10
+
11
+ import typer
12
+
13
+ from modforge_cli.cli.shared import FABRIC_LOADER_VERSION, REGISTRY_PATH, console
14
+ from modforge_cli.core import get_manifest, load_registry
15
+
16
+ app = typer.Typer()
17
+
18
+
19
+ @app.command()
20
+ def sklauncher(pack_name: str | None = None, profile_name: str | None = None) -> None:
21
+ """Create SKLauncher-compatible profile (alternative to export)"""
22
+
23
+ if not pack_name:
24
+ manifest = get_manifest(console, Path.cwd())
25
+ if manifest:
26
+ pack_name = manifest.name
27
+ else:
28
+ console.print("[red]No manifest found[/red]")
29
+ raise typer.Exit(1)
30
+
31
+ registry = load_registry(REGISTRY_PATH)
32
+ if pack_name not in registry:
33
+ console.print(f"[red]Pack '{pack_name}' not found[/red]")
34
+ raise typer.Exit(1)
35
+
36
+ pack_path = Path(registry[pack_name])
37
+ manifest = get_manifest(console, pack_path)
38
+ if not manifest:
39
+ raise typer.Exit(1)
40
+
41
+ # Check if mods are built
42
+ mods_dir = pack_path / "mods"
43
+ if not mods_dir.exists() or not any(mods_dir.iterdir()):
44
+ console.print("[red]No mods found. Run 'ModForge-CLI build' first[/red]")
45
+ raise typer.Exit(1)
46
+
47
+ # Get Minecraft directory
48
+ if platform.system() == "Windows":
49
+ minecraft_dir = Path.home() / "AppData" / "Roaming" / ".minecraft"
50
+ elif platform.system() == "Darwin":
51
+ minecraft_dir = Path.home() / "Library" / "Application Support" / "minecraft"
52
+ else:
53
+ minecraft_dir = Path.home() / ".minecraft"
54
+
55
+ if not minecraft_dir.exists():
56
+ console.print(f"[red]Minecraft directory not found: {minecraft_dir}[/red]")
57
+ raise typer.Exit(1)
58
+
59
+ # Use pack name if profile name not specified
60
+ if not profile_name:
61
+ profile_name = pack_name
62
+
63
+ console.print(f"[cyan]Creating SKLauncher profile '{profile_name}'...[/cyan]")
64
+
65
+ # Create instance directory
66
+ instance_dir = minecraft_dir / "instances" / profile_name
67
+ instance_dir.mkdir(parents=True, exist_ok=True)
68
+
69
+ # Copy mods
70
+ dst_mods = instance_dir / "mods"
71
+ if dst_mods.exists():
72
+ shutil.rmtree(dst_mods)
73
+ shutil.copytree(mods_dir, dst_mods)
74
+ mod_count = len(list(dst_mods.glob("*.jar")))
75
+ console.print(f"[green]✓ Copied {mod_count} mods[/green]")
76
+
77
+ # Copy overrides
78
+ overrides_src = pack_path / "overrides"
79
+ if overrides_src.exists():
80
+ for item in overrides_src.iterdir():
81
+ dst = instance_dir / item.name
82
+ if item.is_dir():
83
+ if dst.exists():
84
+ shutil.rmtree(dst)
85
+ shutil.copytree(item, dst)
86
+ else:
87
+ shutil.copy2(item, dst)
88
+ console.print("[green]✓ Copied overrides[/green]")
89
+
90
+ # Update launcher_profiles.json
91
+ profiles_file = minecraft_dir / "launcher_profiles.json"
92
+
93
+ if profiles_file.exists():
94
+ profiles_data = json.loads(profiles_file.read_text())
95
+ else:
96
+ profiles_data = {"profiles": {}, "settings": {}, "version": 3}
97
+
98
+ # Create profile entry
99
+ profile_id = profile_name.lower().replace(" ", "_").replace("-", "_")
100
+ loader_version = manifest.loader_version or FABRIC_LOADER_VERSION
101
+
102
+ profiles_data["profiles"][profile_id] = {
103
+ "name": profile_name,
104
+ "type": "custom",
105
+ "created": datetime.now().isoformat() + "Z",
106
+ "lastUsed": datetime.now().isoformat() + "Z",
107
+ "icon": "Furnace_On",
108
+ "lastVersionId": f"fabric-loader-{loader_version}-{manifest.minecraft}",
109
+ "gameDir": str(instance_dir),
110
+ }
111
+
112
+ # Save profiles
113
+ profiles_file.write_text(json.dumps(profiles_data, indent=2))
114
+
115
+ console.print("\n[green bold]✓ SKLauncher profile created![/green bold]")
116
+ console.print(f"\n[cyan]Profile:[/cyan] {profile_name}")
117
+ console.print(f"[cyan]Location:[/cyan] {instance_dir}")
118
+ console.print(f"[cyan]Version:[/cyan] fabric-loader-{loader_version}-{manifest.minecraft}")
119
+ console.print("\n[yellow]Next steps:[/yellow]")
120
+ console.print(" 1. Close SKLauncher if it's open")
121
+ console.print(" 2. Restart SKLauncher")
122
+ console.print(f" 3. Select profile '{profile_name}'")
123
+ console.print(" 4. If Fabric isn't installed, install it from SKLauncher:")
124
+ console.print(f" - MC: {manifest.minecraft}")
125
+ console.print(f" - Fabric: {loader_version}")
@@ -0,0 +1,64 @@
1
+ """
2
+ Utility commands - Doctor and self-update
3
+ """
4
+
5
+ import subprocess
6
+ import sys
7
+
8
+ import typer
9
+
10
+ from modforge_cli.cli.shared import MODRINTH_API, POLICY_PATH, REGISTRY_PATH, console
11
+ from modforge_cli.core import load_registry, self_update as core_self_update
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ @app.command()
17
+ def doctor() -> None:
18
+ """Validate ModForge-CLI installation"""
19
+ console.print("[bold cyan]Running diagnostics...[/bold cyan]\n")
20
+
21
+ issues = []
22
+
23
+ # Check Python version
24
+ py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
25
+ console.print(f"[green]✓[/green] Python {py_version}")
26
+
27
+ # Check config files
28
+ for name, path in [("API Config", MODRINTH_API), ("Policy", POLICY_PATH)]:
29
+ if path.exists():
30
+ console.print(f"[green]✓[/green] {name}: {path}")
31
+ else:
32
+ console.print(f"[red]✗[/red] {name} missing")
33
+ issues.append(f"Reinstall {name}")
34
+
35
+ # Check registry
36
+ registry = load_registry(REGISTRY_PATH)
37
+ console.print(f"[green]✓[/green] Registry: {len(registry)} projects")
38
+
39
+ # Check Java
40
+ try:
41
+ subprocess.run(["java", "-version"], capture_output=True, text=True, check=True)
42
+ console.print("[green]✓[/green] Java installed")
43
+ except (FileNotFoundError, subprocess.CalledProcessError):
44
+ console.print("[yellow]![/yellow] Java not found (needed for Fabric)")
45
+ issues.append("Install Java 17+")
46
+
47
+ # Summary
48
+ console.print()
49
+ if issues:
50
+ console.print("[yellow]Issues found:[/yellow]")
51
+ for issue in issues:
52
+ console.print(f" - {issue}")
53
+ else:
54
+ console.print("[green bold]✓ All checks passed![/green bold]")
55
+
56
+
57
+ @app.command(name="self-update")
58
+ def self_update_cmd() -> None:
59
+ """Update ModForge-CLI to latest version"""
60
+ try:
61
+ core_self_update(console)
62
+ except Exception as e:
63
+ console.print(f"[red]Update failed:[/red] {e}")
64
+ raise typer.Exit(1) from e
@@ -0,0 +1,39 @@
1
+ from .downloader import ModDownloader
2
+ from .models import Hit, Manifest, ProjectVersion, ProjectVersionList, SearchResult
3
+ from .policy import ModPolicy
4
+ from .resolver import ModResolver
5
+ from .utils import (
6
+ detect_install_method,
7
+ ensure_config_file,
8
+ get_api_session,
9
+ get_manifest,
10
+ install_fabric,
11
+ load_registry,
12
+ perform_add,
13
+ run,
14
+ save_registry_atomic,
15
+ self_update,
16
+ setup_crash_logging,
17
+ )
18
+
19
+ __all__ = [
20
+ "ModPolicy",
21
+ "ModResolver",
22
+ "Manifest",
23
+ "Hit",
24
+ "SearchResult",
25
+ "ProjectVersion",
26
+ "ProjectVersionList",
27
+ "ModDownloader",
28
+ "ensure_config_file",
29
+ "install_fabric",
30
+ "run",
31
+ "get_api_session",
32
+ "get_manifest",
33
+ "self_update",
34
+ "perform_add",
35
+ "detect_install_method",
36
+ "load_registry",
37
+ "save_registry_atomic",
38
+ "setup_crash_logging",
39
+ ]