augint-shell 0.76.0__tar.gz → 0.77.0__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.
- {augint_shell-0.76.0 → augint_shell-0.77.0}/PKG-INFO +2 -2
- {augint_shell-0.76.0 → augint_shell-0.77.0}/README.md +1 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/pyproject.toml +1 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/__init__.py +1 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/llm.py +61 -23
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/config.py +5 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/container.py +41 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/defaults.py +4 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/ai-shell.yaml +1 -1
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/__main__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/manage.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/tools.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/exceptions.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/gpu.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/interactive.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/local_chrome.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/scaffold.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/selector.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/ai-shell.toml +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/aider/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/claude/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/claude/settings.json +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/codex/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/opencode/__init__.py +0 -0
- {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/tmux.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: augint-shell
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.77.0
|
|
4
4
|
Summary: Launch AI coding tools and local LLMs in Docker containers
|
|
5
5
|
Author: svange
|
|
6
6
|
Requires-Dist: docker>=7.0.0
|
|
@@ -106,7 +106,7 @@ extra_env = { MY_VAR = "value" }
|
|
|
106
106
|
|
|
107
107
|
[llm]
|
|
108
108
|
primary_model = "qwen3-coder:32b-a3b-q4_K_M"
|
|
109
|
-
fallback_model = "
|
|
109
|
+
fallback_model = "huihui_ai/llama3.3-abliterated"
|
|
110
110
|
context_size = 32768
|
|
111
111
|
ollama_port = 11434
|
|
112
112
|
webui_port = 3000
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""LLM stack management commands: up, down, pull, setup, status, logs, shell."""
|
|
2
2
|
|
|
3
|
+
import socket
|
|
3
4
|
import time
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
@@ -9,7 +10,7 @@ from rich.console import Console
|
|
|
9
10
|
from ai_shell.cli import CONTEXT_SETTINGS
|
|
10
11
|
from ai_shell.config import load_config
|
|
11
12
|
from ai_shell.container import ContainerManager
|
|
12
|
-
from ai_shell.defaults import OLLAMA_CONTAINER, WEBUI_CONTAINER
|
|
13
|
+
from ai_shell.defaults import LOBECHAT_CONTAINER, OLLAMA_CONTAINER, WEBUI_CONTAINER
|
|
13
14
|
from ai_shell.gpu import get_vram_info, get_vram_processes
|
|
14
15
|
|
|
15
16
|
console = Console(stderr=True)
|
|
@@ -17,6 +18,26 @@ console = Console(stderr=True)
|
|
|
17
18
|
_LOW_MEMORY_THRESHOLD_GIB = 30 # 27B+ models need ~30 GiB
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def _lan_ip() -> str | None:
|
|
22
|
+
"""Return the host's primary LAN IPv4 address, or None if undetectable.
|
|
23
|
+
|
|
24
|
+
Uses a UDP socket's routing-table selection without actually sending
|
|
25
|
+
traffic. Works on Linux, Mac, and WSL2. On WSL2 this returns the
|
|
26
|
+
WSL VM's eth0 address (typically 172.x.x.x), which is reachable from
|
|
27
|
+
the Windows host but not the broader LAN unless WSL mirrored mode or
|
|
28
|
+
a Windows portproxy is configured.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
32
|
+
s.connect(("8.8.8.8", 80))
|
|
33
|
+
ip = str(s.getsockname()[0])
|
|
34
|
+
except OSError:
|
|
35
|
+
return None
|
|
36
|
+
if ip.startswith("127."):
|
|
37
|
+
return None
|
|
38
|
+
return ip
|
|
39
|
+
|
|
40
|
+
|
|
20
41
|
def _warn_if_low_memory() -> None:
|
|
21
42
|
"""Check system memory and warn if it may be insufficient for large models."""
|
|
22
43
|
try:
|
|
@@ -59,13 +80,13 @@ def _get_manager(ctx) -> ContainerManager:
|
|
|
59
80
|
@click.group("llm", context_settings=CONTEXT_SETTINGS)
|
|
60
81
|
@click.pass_context
|
|
61
82
|
def llm_group(ctx):
|
|
62
|
-
"""Manage the local LLM stack (Ollama + Open WebUI)."""
|
|
83
|
+
"""Manage the local LLM stack (Ollama + Open WebUI + LobeChat)."""
|
|
63
84
|
|
|
64
85
|
|
|
65
86
|
@llm_group.command("up")
|
|
66
87
|
@click.pass_context
|
|
67
88
|
def llm_up(ctx):
|
|
68
|
-
"""Start the LLM stack (Ollama + Open WebUI)."""
|
|
89
|
+
"""Start the LLM stack (Ollama + Open WebUI + LobeChat)."""
|
|
69
90
|
manager = _get_manager(ctx)
|
|
70
91
|
console.print("[bold]Starting LLM stack...[/bold]")
|
|
71
92
|
_warn_if_low_memory()
|
|
@@ -76,6 +97,18 @@ def llm_up(ctx):
|
|
|
76
97
|
manager.ensure_webui()
|
|
77
98
|
console.print(f" Open WebUI: http://localhost:{manager.config.webui_port}")
|
|
78
99
|
|
|
100
|
+
manager.ensure_lobechat()
|
|
101
|
+
console.print(
|
|
102
|
+
f" LobeChat: http://localhost:{manager.config.lobechat_port} [dim](recommended)[/dim]"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
lan = _lan_ip()
|
|
106
|
+
if lan:
|
|
107
|
+
console.print("\n[bold]LAN access[/bold] (bound to 0.0.0.0):")
|
|
108
|
+
console.print(f" Ollama API: http://{lan}:{manager.config.ollama_port}")
|
|
109
|
+
console.print(f" Open WebUI: http://{lan}:{manager.config.webui_port}")
|
|
110
|
+
console.print(f" LobeChat: http://{lan}:{manager.config.lobechat_port}")
|
|
111
|
+
|
|
79
112
|
console.print("\n[bold green]LLM stack is running.[/bold green]")
|
|
80
113
|
console.print("If this is your first time, run: [bold]ai-shell llm setup[/bold]")
|
|
81
114
|
|
|
@@ -87,7 +120,7 @@ def llm_down(ctx):
|
|
|
87
120
|
manager = _get_manager(ctx)
|
|
88
121
|
console.print("[bold]Stopping LLM stack...[/bold]")
|
|
89
122
|
|
|
90
|
-
for name in [WEBUI_CONTAINER, OLLAMA_CONTAINER]:
|
|
123
|
+
for name in [LOBECHAT_CONTAINER, WEBUI_CONTAINER, OLLAMA_CONTAINER]:
|
|
91
124
|
status = manager.container_status(name)
|
|
92
125
|
if status == "running":
|
|
93
126
|
manager.stop_container(name)
|
|
@@ -132,6 +165,7 @@ def llm_setup(ctx):
|
|
|
132
165
|
_warn_if_low_memory()
|
|
133
166
|
manager.ensure_ollama()
|
|
134
167
|
manager.ensure_webui()
|
|
168
|
+
manager.ensure_lobechat()
|
|
135
169
|
|
|
136
170
|
# Wait for Ollama to be ready
|
|
137
171
|
console.print("[bold]Waiting for Ollama to be ready...[/bold]")
|
|
@@ -157,23 +191,10 @@ def llm_setup(ctx):
|
|
|
157
191
|
output = manager.exec_in_ollama(["ollama", "pull", config.fallback_model])
|
|
158
192
|
console.print(output)
|
|
159
193
|
|
|
160
|
-
# Configure context window
|
|
161
|
-
console.print(f"\n[bold]Configuring context window ({config.context_size} tokens)...[/bold]")
|
|
162
|
-
for model in [config.primary_model, config.fallback_model]:
|
|
163
|
-
modelfile = f"FROM {model}\nPARAMETER num_ctx {config.context_size}\n"
|
|
164
|
-
# Write modelfile and create model
|
|
165
|
-
manager.exec_in_ollama(
|
|
166
|
-
[
|
|
167
|
-
"sh",
|
|
168
|
-
"-c",
|
|
169
|
-
f'printf "{modelfile}" > /tmp/Modelfile && '
|
|
170
|
-
f"ollama create {model} -f /tmp/Modelfile && rm -f /tmp/Modelfile",
|
|
171
|
-
]
|
|
172
|
-
)
|
|
173
|
-
|
|
174
194
|
console.print("\n[bold green]============================================[/bold green]")
|
|
175
195
|
console.print("[bold green] Setup complete![/bold green]")
|
|
176
|
-
console.print(f"\n
|
|
196
|
+
console.print(f"\n LobeChat: http://localhost:{config.lobechat_port} (recommended)")
|
|
197
|
+
console.print(f" Open WebUI: http://localhost:{config.webui_port}")
|
|
177
198
|
console.print(f" Ollama API: http://localhost:{config.ollama_port}")
|
|
178
199
|
console.print(f"\n Primary model: {config.primary_model}")
|
|
179
200
|
console.print(f" Fallback model: {config.fallback_model}")
|
|
@@ -189,9 +210,14 @@ def llm_status(ctx):
|
|
|
189
210
|
config = manager.config
|
|
190
211
|
ollama_running = manager.container_status(OLLAMA_CONTAINER) == "running"
|
|
191
212
|
webui_running = manager.container_status(WEBUI_CONTAINER) == "running"
|
|
213
|
+
lobechat_running = manager.container_status(LOBECHAT_CONTAINER) == "running"
|
|
192
214
|
|
|
193
215
|
console.print("[bold]Container status:[/bold]")
|
|
194
|
-
for name, running in [
|
|
216
|
+
for name, running in [
|
|
217
|
+
(OLLAMA_CONTAINER, ollama_running),
|
|
218
|
+
(WEBUI_CONTAINER, webui_running),
|
|
219
|
+
(LOBECHAT_CONTAINER, lobechat_running),
|
|
220
|
+
]:
|
|
195
221
|
status = manager.container_status(name)
|
|
196
222
|
if running:
|
|
197
223
|
console.print(f" {name}: [green]{status}[/green]")
|
|
@@ -203,6 +229,13 @@ def llm_status(ctx):
|
|
|
203
229
|
console.print("\n[bold]Access URLs:[/bold]")
|
|
204
230
|
ollama_url = f"http://localhost:{config.ollama_port}"
|
|
205
231
|
webui_url = f"http://localhost:{config.webui_port}"
|
|
232
|
+
lobechat_url = f"http://localhost:{config.lobechat_port}"
|
|
233
|
+
if lobechat_running:
|
|
234
|
+
console.print(
|
|
235
|
+
f" LobeChat: [cyan]{lobechat_url}[/cyan] [bold](recommended)[/bold]"
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
console.print(f" LobeChat: [dim]{lobechat_url}[/dim] (not running)")
|
|
206
239
|
if ollama_running:
|
|
207
240
|
console.print(f" Ollama API: [cyan]{ollama_url}[/cyan]")
|
|
208
241
|
console.print(f" OpenAI-compatible: [cyan]{ollama_url}/v1[/cyan]")
|
|
@@ -211,10 +244,15 @@ def llm_status(ctx):
|
|
|
211
244
|
console.print(f" OpenAI-compatible: [dim]{ollama_url}/v1[/dim] (not running)")
|
|
212
245
|
if webui_running:
|
|
213
246
|
console.print(f" Open WebUI: [cyan]{webui_url}[/cyan]")
|
|
214
|
-
console.print(f" Chat interface: [cyan]{webui_url}[/cyan]")
|
|
215
247
|
else:
|
|
216
248
|
console.print(f" Open WebUI: [dim]{webui_url}[/dim] (not running)")
|
|
217
|
-
|
|
249
|
+
|
|
250
|
+
lan = _lan_ip()
|
|
251
|
+
if lan:
|
|
252
|
+
console.print("\n[bold]LAN access[/bold] (bound to 0.0.0.0):")
|
|
253
|
+
console.print(f" Ollama API: [cyan]http://{lan}:{config.ollama_port}[/cyan]")
|
|
254
|
+
console.print(f" Open WebUI: [cyan]http://{lan}:{config.webui_port}[/cyan]")
|
|
255
|
+
console.print(f" LobeChat: [cyan]http://{lan}:{config.lobechat_port}[/cyan]")
|
|
218
256
|
|
|
219
257
|
console.print("\n[bold]Configuration:[/bold]")
|
|
220
258
|
console.print(f" Primary model: {config.primary_model}")
|
|
@@ -252,7 +290,7 @@ def llm_logs(ctx, follow):
|
|
|
252
290
|
if follow:
|
|
253
291
|
manager.container_logs(OLLAMA_CONTAINER, follow=True)
|
|
254
292
|
else:
|
|
255
|
-
for name in [OLLAMA_CONTAINER, WEBUI_CONTAINER]:
|
|
293
|
+
for name in [OLLAMA_CONTAINER, WEBUI_CONTAINER, LOBECHAT_CONTAINER]:
|
|
256
294
|
status = manager.container_status(name)
|
|
257
295
|
if status is not None:
|
|
258
296
|
console.print(f"\n[bold]--- {name} ---[/bold]")
|
|
@@ -26,6 +26,7 @@ from ai_shell.defaults import (
|
|
|
26
26
|
DEFAULT_DEV_PORTS,
|
|
27
27
|
DEFAULT_FALLBACK_MODEL,
|
|
28
28
|
DEFAULT_IMAGE,
|
|
29
|
+
DEFAULT_LOBECHAT_PORT,
|
|
29
30
|
DEFAULT_OLLAMA_PORT,
|
|
30
31
|
DEFAULT_PRIMARY_MODEL,
|
|
31
32
|
DEFAULT_WEBUI_PORT,
|
|
@@ -50,6 +51,7 @@ class AiShellConfig:
|
|
|
50
51
|
context_size: int = DEFAULT_CONTEXT_SIZE
|
|
51
52
|
ollama_port: int = DEFAULT_OLLAMA_PORT
|
|
52
53
|
webui_port: int = DEFAULT_WEBUI_PORT
|
|
54
|
+
lobechat_port: int = DEFAULT_LOBECHAT_PORT
|
|
53
55
|
|
|
54
56
|
# Extra configuration
|
|
55
57
|
extra_env: dict[str, str] = field(default_factory=dict)
|
|
@@ -180,6 +182,8 @@ def _apply_config(config: AiShellConfig, path: Path) -> None:
|
|
|
180
182
|
config.ollama_port = int(llm["ollama_port"])
|
|
181
183
|
if "webui_port" in llm:
|
|
182
184
|
config.webui_port = int(llm["webui_port"])
|
|
185
|
+
if "lobechat_port" in llm:
|
|
186
|
+
config.lobechat_port = int(llm["lobechat_port"])
|
|
183
187
|
|
|
184
188
|
# [aws] section
|
|
185
189
|
aws = data.get("aws", {})
|
|
@@ -213,6 +217,7 @@ def _apply_env_vars(config: AiShellConfig) -> None:
|
|
|
213
217
|
"AI_SHELL_CONTEXT_SIZE": ("context_size", int),
|
|
214
218
|
"AI_SHELL_OLLAMA_PORT": ("ollama_port", int),
|
|
215
219
|
"AI_SHELL_WEBUI_PORT": ("webui_port", int),
|
|
220
|
+
"AI_SHELL_LOBECHAT_PORT": ("lobechat_port", int),
|
|
216
221
|
"AI_SHELL_AI_PROFILE": ("ai_profile", str),
|
|
217
222
|
"AI_SHELL_AWS_REGION": ("aws_region", str),
|
|
218
223
|
"AI_SHELL_BEDROCK_PROFILE": ("bedrock_profile", str),
|
|
@@ -19,6 +19,8 @@ from docker.types import DeviceRequest, Mount
|
|
|
19
19
|
import docker
|
|
20
20
|
from ai_shell.defaults import (
|
|
21
21
|
LLM_NETWORK,
|
|
22
|
+
LOBECHAT_CONTAINER,
|
|
23
|
+
LOBECHAT_IMAGE,
|
|
22
24
|
OLLAMA_CONTAINER,
|
|
23
25
|
OLLAMA_CPU_SHARES,
|
|
24
26
|
OLLAMA_DATA_VOLUME,
|
|
@@ -274,7 +276,9 @@ class ContainerManager:
|
|
|
274
276
|
# GPU auto-detection
|
|
275
277
|
gpu_available = detect_gpu()
|
|
276
278
|
device_requests = None
|
|
277
|
-
env: dict[str, str] = {
|
|
279
|
+
env: dict[str, str] = {
|
|
280
|
+
"OLLAMA_CONTEXT_LENGTH": str(self.config.context_size),
|
|
281
|
+
}
|
|
278
282
|
if gpu_available:
|
|
279
283
|
device_requests = [DeviceRequest(count=1, capabilities=[["gpu"]])]
|
|
280
284
|
vram = get_vram_info()
|
|
@@ -358,6 +362,42 @@ class ContainerManager:
|
|
|
358
362
|
logger.info("Open WebUI container created on port %d", self.config.webui_port)
|
|
359
363
|
return WEBUI_CONTAINER
|
|
360
364
|
|
|
365
|
+
def ensure_lobechat(self) -> str:
|
|
366
|
+
"""Get or create the LobeChat container.
|
|
367
|
+
|
|
368
|
+
Client-DB mode: state lives in the browser's IndexedDB, so no
|
|
369
|
+
persistent server-side volume is needed.
|
|
370
|
+
|
|
371
|
+
Returns the container name.
|
|
372
|
+
"""
|
|
373
|
+
container = self._get_container(LOBECHAT_CONTAINER)
|
|
374
|
+
|
|
375
|
+
if container is not None:
|
|
376
|
+
if container.status != "running":
|
|
377
|
+
logger.info("Starting existing LobeChat container")
|
|
378
|
+
container.start()
|
|
379
|
+
return LOBECHAT_CONTAINER
|
|
380
|
+
|
|
381
|
+
logger.info("Creating LobeChat container")
|
|
382
|
+
self._pull_image_if_needed(LOBECHAT_IMAGE)
|
|
383
|
+
network_name = self._ensure_llm_network()
|
|
384
|
+
|
|
385
|
+
self.client.containers.run(
|
|
386
|
+
image=LOBECHAT_IMAGE,
|
|
387
|
+
name=LOBECHAT_CONTAINER,
|
|
388
|
+
ports={"3210/tcp": ("0.0.0.0", self.config.lobechat_port)}, # nosec B104
|
|
389
|
+
environment={
|
|
390
|
+
"OLLAMA_PROXY_URL": f"http://{OLLAMA_CONTAINER}:11434/v1",
|
|
391
|
+
"ACCESS_CODE": "",
|
|
392
|
+
},
|
|
393
|
+
restart_policy={"Name": "unless-stopped"},
|
|
394
|
+
detach=True,
|
|
395
|
+
network=network_name,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
logger.info("LobeChat container created on port %d", self.config.lobechat_port)
|
|
399
|
+
return LOBECHAT_CONTAINER
|
|
400
|
+
|
|
361
401
|
def exec_in_ollama(self, command: list[str]) -> str:
|
|
362
402
|
"""Run a command in the Ollama container and return stdout.
|
|
363
403
|
|
|
@@ -53,11 +53,13 @@ WEBUI_DATA_VOLUME = "augint-shell-webui-data"
|
|
|
53
53
|
# =============================================================================
|
|
54
54
|
OLLAMA_IMAGE = "ollama/ollama"
|
|
55
55
|
WEBUI_IMAGE = "ghcr.io/open-webui/open-webui:main"
|
|
56
|
+
LOBECHAT_IMAGE = "lobehub/lobe-chat:latest"
|
|
56
57
|
DEFAULT_PRIMARY_MODEL = "qwen3-coder:32b-a3b-q4_K_M"
|
|
57
|
-
DEFAULT_FALLBACK_MODEL = "
|
|
58
|
+
DEFAULT_FALLBACK_MODEL = "huihui_ai/llama3.3-abliterated"
|
|
58
59
|
DEFAULT_CONTEXT_SIZE = 32768
|
|
59
60
|
DEFAULT_OLLAMA_PORT = 11434
|
|
60
61
|
DEFAULT_WEBUI_PORT = 3000
|
|
62
|
+
DEFAULT_LOBECHAT_PORT = 3210
|
|
61
63
|
DEFAULT_DEV_PORTS = [3000, 4200, 5000, 5173, 5678, 8000, 8080, 8888]
|
|
62
64
|
|
|
63
65
|
# =============================================================================
|
|
@@ -76,6 +78,7 @@ OLLAMA_CPU_SHARES = 1024 # Docker CPU scheduling priority (default 0 = fair-sha
|
|
|
76
78
|
# =============================================================================
|
|
77
79
|
OLLAMA_CONTAINER = "augint-shell-ollama"
|
|
78
80
|
WEBUI_CONTAINER = "augint-shell-webui"
|
|
81
|
+
LOBECHAT_CONTAINER = "augint-shell-lobechat"
|
|
79
82
|
|
|
80
83
|
# =============================================================================
|
|
81
84
|
# Docker network
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|