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.
- modforge_cli/__init__.py +7 -0
- modforge_cli/__main__.py +132 -0
- modforge_cli/__version__.py +5 -0
- modforge_cli/api/__init__.py +7 -0
- modforge_cli/api/modrinth.py +210 -0
- modforge_cli/cli/__init__.py +7 -0
- modforge_cli/cli/export.py +246 -0
- modforge_cli/cli/modpack.py +150 -0
- modforge_cli/cli/project.py +72 -0
- modforge_cli/cli/setup.py +72 -0
- modforge_cli/cli/shared.py +41 -0
- modforge_cli/cli/sklauncher.py +125 -0
- modforge_cli/cli/utils.py +64 -0
- modforge_cli/core/__init__.py +39 -0
- modforge_cli/core/downloader.py +208 -0
- modforge_cli/core/models.py +66 -0
- modforge_cli/core/policy.py +161 -0
- modforge_cli/core/resolver.py +184 -0
- modforge_cli/core/utils.py +416 -0
- modforge_cli-0.2.3.dist-info/METADATA +70 -0
- modforge_cli-0.2.3.dist-info/RECORD +24 -0
- modforge_cli-0.2.3.dist-info/WHEEL +4 -0
- modforge_cli-0.2.3.dist-info/entry_points.txt +3 -0
- modforge_cli-0.2.3.dist-info/licenses/LICENSE +21 -0
|
@@ -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
|
+
]
|