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.
Files changed (28) hide show
  1. {augint_shell-0.76.0 → augint_shell-0.77.0}/PKG-INFO +2 -2
  2. {augint_shell-0.76.0 → augint_shell-0.77.0}/README.md +1 -1
  3. {augint_shell-0.76.0 → augint_shell-0.77.0}/pyproject.toml +1 -1
  4. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/__init__.py +1 -1
  5. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/llm.py +61 -23
  6. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/config.py +5 -0
  7. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/container.py +41 -1
  8. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/defaults.py +4 -1
  9. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/ai-shell.yaml +1 -1
  10. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/__init__.py +0 -0
  11. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/__main__.py +0 -0
  12. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/__init__.py +0 -0
  13. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/manage.py +0 -0
  14. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/cli/commands/tools.py +0 -0
  15. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/exceptions.py +0 -0
  16. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/gpu.py +0 -0
  17. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/interactive.py +0 -0
  18. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/local_chrome.py +0 -0
  19. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/scaffold.py +0 -0
  20. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/selector.py +0 -0
  21. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/__init__.py +0 -0
  22. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/ai-shell.toml +0 -0
  23. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/aider/__init__.py +0 -0
  24. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/claude/__init__.py +0 -0
  25. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/claude/settings.json +0 -0
  26. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/codex/__init__.py +0 -0
  27. {augint_shell-0.76.0 → augint_shell-0.77.0}/src/ai_shell/templates/opencode/__init__.py +0 -0
  28. {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.76.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 = "qwen3.5:27b"
109
+ fallback_model = "huihui_ai/llama3.3-abliterated"
110
110
  context_size = 32768
111
111
  ollama_port = 11434
112
112
  webui_port = 3000
@@ -93,7 +93,7 @@ extra_env = { MY_VAR = "value" }
93
93
 
94
94
  [llm]
95
95
  primary_model = "qwen3-coder:32b-a3b-q4_K_M"
96
- fallback_model = "qwen3.5:27b"
96
+ fallback_model = "huihui_ai/llama3.3-abliterated"
97
97
  context_size = 32768
98
98
  ollama_port = 11434
99
99
  webui_port = 3000
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "augint-shell"
3
- version = "0.76.0"
3
+ version = "0.77.0"
4
4
  description = "Launch AI coding tools and local LLMs in Docker containers"
5
5
  authors = [{name = "svange"}]
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  """augint-shell (ai-shell) - Launch AI coding tools and local LLMs in Docker containers."""
2
2
 
3
- __version__ = "0.76.0"
3
+ __version__ = "0.77.0"
4
4
 
5
5
  __all__ = [
6
6
  "__version__",
@@ -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 Open WebUI: http://localhost:{config.webui_port}")
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 [(OLLAMA_CONTAINER, ollama_running), (WEBUI_CONTAINER, webui_running)]:
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
- console.print(f" Chat interface: [dim]{webui_url}[/dim] (not running)")
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 = "qwen3.5:27b"
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
@@ -157,7 +157,7 @@
157
157
  #
158
158
  # llm:
159
159
  # primary_model: qwen3-coder:32b-a3b-q4_K_M
160
- # fallback_model: qwen3.5:27b
160
+ # fallback_model: huihui_ai/llama3.3-abliterated
161
161
  # context_size: 32768
162
162
  # ollama_port: 11434
163
163
  # webui_port: 3000