webxos commited on
Commit
c2c2423
·
verified ·
1 Parent(s): a0049ab

Upload stack.py

Browse files
Files changed (1) hide show
  1. stack.py +593 -0
stack.py ADDED
@@ -0,0 +1,593 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ STACK – Local Repository Agent with nomic-embed-text
4
+ Auto‑bootstraps venv + dependencies on first run.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import math
11
+ import time
12
+ import threading
13
+ import hashlib
14
+ import subprocess
15
+ import venv
16
+ from pathlib import Path
17
+ from typing import Dict, List, Optional, Tuple, Any
18
+
19
+ # ============================================================================
20
+ # 0. VENV BOOTSTRAP – prevent “externally-managed” errors
21
+ # ============================================================================
22
+ STACK_DIR = Path(__file__).resolve().parent
23
+ VENV_DIR = STACK_DIR / ".stack_venv"
24
+
25
+ def is_venv() -> bool:
26
+ """Check if we are already running inside a virtual environment."""
27
+ return (
28
+ hasattr(sys, 'real_prefix')
29
+ or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
30
+ )
31
+
32
+ def create_venv():
33
+ """Create the virtual environment and return the path to its Python binary."""
34
+ print("[*] Creating virtual environment...")
35
+ venv.create(str(VENV_DIR), with_pip=True)
36
+ if os.name == 'nt':
37
+ python = VENV_DIR / 'Scripts' / 'python.exe'
38
+ else:
39
+ python = VENV_DIR / 'bin' / 'python'
40
+ return str(python)
41
+
42
+ def install_dependencies(python_exe: str):
43
+ """Install required packages inside the virtual environment."""
44
+ packages = ["ollama"]
45
+ print(f"[*] Installing dependencies inside venv: {packages}")
46
+ subprocess.check_call([python_exe, "-m", "pip", "install", "--upgrade", "pip"])
47
+ subprocess.check_call([python_exe, "-m", "pip", "install"] + packages)
48
+
49
+ def relaunch_in_venv(python_exe: str):
50
+ """Replace the current process with one running inside the venv."""
51
+ print("[*] Relaunching inside virtual environment...")
52
+ os.execv(python_exe, [python_exe, __file__] + sys.argv[1:])
53
+
54
+ # Bootstrap logic
55
+ if not is_venv():
56
+ print("[!] Not running in a virtual environment.")
57
+ if VENV_DIR.exists():
58
+ # Venv exists – use it
59
+ if os.name == 'nt':
60
+ python_exe = str(VENV_DIR / 'Scripts' / 'python.exe')
61
+ else:
62
+ python_exe = str(VENV_DIR / 'bin' / 'python')
63
+ if not Path(python_exe).exists():
64
+ print("[!] Venv appears corrupt, recreating...")
65
+ python_exe = create_venv()
66
+ install_dependencies(python_exe)
67
+ relaunch_in_venv(python_exe)
68
+ else:
69
+ # First run – create venv, install deps, relaunch
70
+ python_exe = create_venv()
71
+ install_dependencies(python_exe)
72
+ relaunch_in_venv(python_exe)
73
+
74
+ # ============================================================================
75
+ # Now safely inside the venv – import ollama
76
+ # ============================================================================
77
+ import ollama
78
+
79
+ # ============================================================================
80
+ # 2. CONFIGURATION & PATHS
81
+ # ============================================================================
82
+ STACK_ROOT = os.path.abspath("./stack_system")
83
+ WORKSPACE = os.path.join(STACK_ROOT, "current_workspace")
84
+ TEMPLATES_DIR = os.path.join(STACK_ROOT, "templates")
85
+ MANIFEST_PATH = os.path.join(TEMPLATES_DIR, "manifest.json")
86
+ MEMORY_FILE = os.path.join(STACK_ROOT, ".stack_memory.json")
87
+ MAX_TOOL_DEPTH = 10
88
+
89
+ os.makedirs(WORKSPACE, exist_ok=True)
90
+ os.makedirs(TEMPLATES_DIR, exist_ok=True)
91
+
92
+ _manifest_lock = threading.RLock()
93
+
94
+ # ============================================================================
95
+ # 3. OLLAMA CONNECTION & MODEL MANAGEMENT
96
+ # ============================================================================
97
+ def check_ollama_running() -> bool:
98
+ try:
99
+ ollama.list()
100
+ return True
101
+ except Exception:
102
+ return False
103
+
104
+ def start_ollama() -> bool:
105
+ try:
106
+ subprocess.Popen(["ollama", "serve"],
107
+ stdout=subprocess.DEVNULL,
108
+ stderr=subprocess.DEVNULL)
109
+ time.sleep(3)
110
+ return check_ollama_running()
111
+ except Exception:
112
+ return False
113
+
114
+ def ensure_models() -> None:
115
+ print("[*] Verifying Ollama models...")
116
+ if not check_ollama_running():
117
+ print("[!] Ollama not running. Attempting to start...")
118
+ if not start_ollama():
119
+ print("[!] Could not start Ollama. Please run 'ollama serve' manually.")
120
+ sys.exit(1)
121
+
122
+ required_models = ["nomic-embed-text"]
123
+ try:
124
+ installed = ollama.list()
125
+ installed_names = [m.model for m in installed.models] if hasattr(installed, 'models') else []
126
+ except Exception:
127
+ installed_names = []
128
+
129
+ for model in required_models:
130
+ if model not in installed_names:
131
+ print(f"[*] Pulling {model}...")
132
+ try:
133
+ ollama.pull(model)
134
+ print(f"[✓] {model} ready")
135
+ except Exception as e:
136
+ print(f"[!] Failed to pull {model}: {e}")
137
+ sys.exit(1)
138
+
139
+ def get_embedding(text: str) -> Optional[List[float]]:
140
+ try:
141
+ response = ollama.embed(model="nomic-embed-text", input=text)
142
+ if hasattr(response, 'embeddings'):
143
+ embeddings = response.embeddings
144
+ elif isinstance(response, dict):
145
+ embeddings = response.get('embeddings', [])
146
+ else:
147
+ return None
148
+ if embeddings and len(embeddings) > 0:
149
+ return embeddings[0] if isinstance(embeddings[0], list) else embeddings[0]
150
+ return None
151
+ except Exception as e:
152
+ print(f"⚠️ Embedding error: {e}")
153
+ return None
154
+
155
+ # ============================================================================
156
+ # 4. VECTOR SIMILARITY & MEMORY SYSTEM
157
+ # ============================================================================
158
+ def cosine_similarity(v1: List[float], v2: List[float]) -> float:
159
+ if not v1 or not v2 or len(v1) != len(v2):
160
+ return 0.0
161
+ dot = sum(a * b for a, b in zip(v1, v2))
162
+ mag1 = math.sqrt(sum(a * a for a in v1))
163
+ mag2 = math.sqrt(sum(b * b for b in v2))
164
+ if mag1 == 0 or mag2 == 0:
165
+ return 0.0
166
+ return dot / (mag1 * mag2)
167
+
168
+ def remember_action(action_summary: str, metadata: Dict = None) -> None:
169
+ memory = []
170
+ if os.path.exists(MEMORY_FILE):
171
+ try:
172
+ with open(MEMORY_FILE, 'r') as f:
173
+ memory = json.load(f)
174
+ except json.JSONDecodeError:
175
+ memory = []
176
+ vector = get_embedding(action_summary)
177
+ if vector:
178
+ entry = {
179
+ "text": action_summary,
180
+ "vector": vector,
181
+ "timestamp": time.time(),
182
+ "metadata": metadata or {}
183
+ }
184
+ memory.append(entry)
185
+ if len(memory) > 1000:
186
+ memory = memory[-1000:]
187
+ with open(MEMORY_FILE, 'w') as f:
188
+ json.dump(memory, f)
189
+
190
+ def query_memory(query: str, top_k: int = 3) -> List[str]:
191
+ if not os.path.exists(MEMORY_FILE):
192
+ return []
193
+ try:
194
+ with open(MEMORY_FILE, 'r') as f:
195
+ memory = json.load(f)
196
+ except (json.JSONDecodeError, IOError):
197
+ return []
198
+ query_vector = get_embedding(query)
199
+ if not query_vector:
200
+ return []
201
+ scored = []
202
+ for item in memory:
203
+ if "vector" in item and item["vector"]:
204
+ score = cosine_similarity(query_vector, item["vector"])
205
+ scored.append((score, item["text"]))
206
+ scored.sort(key=lambda x: x[0], reverse=True)
207
+ return [text for score, text in scored[:top_k] if score > 0.3]
208
+
209
+ # ============================================================================
210
+ # 5. TEMPLATE MANAGEMENT & HOT RELOAD
211
+ # ============================================================================
212
+ DEFAULT_TEMPLATES = {
213
+ "python_web_server": {
214
+ "description": "minimal fastapi python web server with api routes",
215
+ "files": {
216
+ "app.py": "from fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get('/')\ndef root():\n return {'status': 'STACK running'}\n",
217
+ "requirements.txt": "fastapi\nuvicorn"
218
+ }
219
+ },
220
+ "html_login_component": {
221
+ "description": "modern tailwind css login form ui component",
222
+ "files": {
223
+ "login.html": """<!DOCTYPE html>
224
+ <html>
225
+ <head>
226
+ <script src="https://cdn.tailwindcss.com"></script>
227
+ </head>
228
+ <body class="bg-gray-100 flex items-center justify-center h-screen">
229
+ <div class="bg-white p-8 rounded-lg shadow-md w-96">
230
+ <h2 class="text-2xl font-bold mb-6">Login</h2>
231
+ <input type="email" placeholder="Email" class="w-full p-2 border rounded mb-4">
232
+ <input type="password" placeholder="Password" class="w-full p-2 border rounded mb-4">
233
+ <button class="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600">Sign In</button>
234
+ </div>
235
+ </body>
236
+ </html>"""
237
+ }
238
+ }
239
+ }
240
+
241
+ def seed_initial_templates() -> None:
242
+ if os.path.exists(MANIFEST_PATH):
243
+ return
244
+ print("[*] Seeding initial templates...")
245
+ manifest = {}
246
+ for name, data in DEFAULT_TEMPLATES.items():
247
+ vector = get_embedding(data["description"])
248
+ if vector:
249
+ manifest[name] = {
250
+ "description": data["description"],
251
+ "vector": vector,
252
+ "files": data["files"]
253
+ }
254
+ print(f" ✓ {name}")
255
+ with _manifest_lock:
256
+ with open(MANIFEST_PATH, 'w') as f:
257
+ json.dump(manifest, f, indent=2)
258
+
259
+ def scan_templates_folder() -> Dict:
260
+ discovered = {}
261
+ if not os.path.exists(TEMPLATES_DIR):
262
+ return discovered
263
+ for item in os.listdir(TEMPLATES_DIR):
264
+ item_path = os.path.join(TEMPLATES_DIR, item)
265
+ if not os.path.isdir(item_path):
266
+ continue
267
+ files_dict = {}
268
+ description = f"code template for {item.replace('_', ' ')}"
269
+ for root, _, files in os.walk(item_path):
270
+ for filename in files:
271
+ if filename == "description.txt":
272
+ try:
273
+ with open(os.path.join(root, filename), 'r') as f:
274
+ description = f.read().strip()
275
+ except Exception:
276
+ pass
277
+ continue
278
+ file_path = os.path.join(root, filename)
279
+ rel_path = os.path.relpath(file_path, item_path)
280
+ try:
281
+ with open(file_path, 'r', encoding='utf-8') as f:
282
+ files_dict[rel_path] = f.read()
283
+ except Exception:
284
+ pass
285
+ if files_dict:
286
+ discovered[item] = {
287
+ "description": description,
288
+ "files": files_dict
289
+ }
290
+ return discovered
291
+
292
+ def reload_manifest() -> None:
293
+ raw_templates = scan_templates_folder()
294
+ with _manifest_lock:
295
+ manifest = {}
296
+ if os.path.exists(MANIFEST_PATH):
297
+ try:
298
+ with open(MANIFEST_PATH, 'r') as f:
299
+ manifest = json.load(f)
300
+ except json.JSONDecodeError:
301
+ manifest = {}
302
+ updated = False
303
+ for name, data in raw_templates.items():
304
+ if name not in manifest or manifest[name]["description"] != data["description"]:
305
+ vector = get_embedding(data["description"])
306
+ if vector:
307
+ manifest[name] = {
308
+ "description": data["description"],
309
+ "vector": vector,
310
+ "files": data["files"]
311
+ }
312
+ updated = True
313
+ print(f" ✓ Updated template: {name}")
314
+ if updated:
315
+ with open(MANIFEST_PATH, 'w') as f:
316
+ json.dump(manifest, f, indent=2)
317
+
318
+ def start_template_watcher() -> threading.Thread:
319
+ def watcher():
320
+ last_mtime = {}
321
+ while True:
322
+ time.sleep(2)
323
+ try:
324
+ current = {}
325
+ if os.path.exists(TEMPLATES_DIR):
326
+ for root, _, files in os.walk(TEMPLATES_DIR):
327
+ for f in files:
328
+ path = os.path.join(root, f)
329
+ current[path] = os.path.getmtime(path)
330
+ if current != last_mtime:
331
+ reload_manifest()
332
+ last_mtime = current
333
+ except Exception:
334
+ pass
335
+ thread = threading.Thread(target=watcher, daemon=True)
336
+ thread.start()
337
+ return thread
338
+
339
+ # ============================================================================
340
+ # 6. SANDBOXED GIT & FILE OPERATIONS
341
+ # ============================================================================
342
+ def validate_workspace_path(target_path: str) -> bool:
343
+ abs_target = os.path.abspath(os.path.join(WORKSPACE, target_path))
344
+ abs_workspace = os.path.abspath(WORKSPACE)
345
+ return abs_target.startswith(abs_workspace)
346
+
347
+ def run_git_safe(args: List[str], error_ok: bool = False) -> Tuple[bool, str]:
348
+ try:
349
+ result = subprocess.run(
350
+ ["git"] + args,
351
+ cwd=WORKSPACE,
352
+ capture_output=True,
353
+ text=True,
354
+ timeout=30
355
+ )
356
+ if result.returncode == 0:
357
+ return True, result.stdout or "OK"
358
+ elif error_ok:
359
+ return False, result.stderr
360
+ else:
361
+ return False, result.stderr
362
+ except subprocess.TimeoutExpired:
363
+ return False, "Git operation timed out"
364
+ except Exception as e:
365
+ return False, str(e)
366
+
367
+ def cmd_build(repo_name: str) -> str:
368
+ global WORKSPACE
369
+ new_workspace = os.path.join(STACK_ROOT, repo_name)
370
+ try:
371
+ os.makedirs(new_workspace, exist_ok=True)
372
+ WORKSPACE = new_workspace
373
+ success, msg = run_git_safe(["init"])
374
+ if not success:
375
+ return f"Git init failed: {msg}"
376
+ success, msg = run_git_safe(["checkout", "-b", "main"])
377
+ config_path = os.path.join(WORKSPACE, "STACK.json")
378
+ with open(config_path, 'w') as f:
379
+ json.dump({
380
+ "name": repo_name,
381
+ "created": time.time(),
382
+ "managed_by": "STACK"
383
+ }, f)
384
+ run_git_safe(["add", "."])
385
+ run_git_safe(["commit", "-m", "chore: initial STACK bootstrap"])
386
+ remember_action(f"Created repository '{repo_name}'", {"action": "build", "repo": repo_name})
387
+ return f"✓ Repository '{repo_name}' created at {WORKSPACE}"
388
+ except Exception as e:
389
+ return f"✗ Build failed: {e}"
390
+
391
+ def cmd_add(search_query: str) -> str:
392
+ workspace_git = os.path.join(WORKSPACE, ".git")
393
+ if not os.path.exists(workspace_git):
394
+ return "✗ No active repository. Use '/build <name>' first"
395
+ with _manifest_lock:
396
+ if not os.path.exists(MANIFEST_PATH):
397
+ return "✗ No templates available. Run '/import' or add to templates folder"
398
+ try:
399
+ with open(MANIFEST_PATH, 'r') as f:
400
+ manifest = json.load(f)
401
+ except Exception:
402
+ return "✗ Corrupted manifest file"
403
+ if not manifest:
404
+ return "✗ Manifest is empty. Seed templates first"
405
+ query_vector = get_embedding(search_query)
406
+ if not query_vector:
407
+ return "✗ Could not generate embedding for query"
408
+ best_match = None
409
+ best_score = -1.0
410
+ for name, data in manifest.items():
411
+ if "vector" in data:
412
+ score = cosine_similarity(query_vector, data["vector"])
413
+ if score > best_score:
414
+ best_score = score
415
+ best_match = (name, data["files"], data["description"])
416
+ if best_score < 0.35:
417
+ return f"✗ No good match (confidence: {best_score:.2f}). Try different phrasing"
418
+ template_name, files, description = best_match
419
+ branch_name = f"stack/{template_name}"
420
+ success, msg = run_git_safe(["checkout", "-b", branch_name], error_ok=True)
421
+ files_written = []
422
+ for filename, content in files.items():
423
+ target = os.path.join(WORKSPACE, filename)
424
+ if not validate_workspace_path(filename):
425
+ continue
426
+ os.makedirs(os.path.dirname(target), exist_ok=True)
427
+ with open(target, 'w', encoding='utf-8') as f:
428
+ f.write(content)
429
+ files_written.append(filename)
430
+ run_git_safe(["add", "."])
431
+ commit_msg = f"feat: add {template_name} via STACK\n\n{description}"
432
+ run_git_safe(["commit", "-m", commit_msg])
433
+ run_git_safe(["checkout", "main"])
434
+ run_git_safe(["merge", branch_name])
435
+ remember_action(f"Added template '{template_name}' to repository",
436
+ {"action": "add", "template": template_name, "files": files_written})
437
+ return f"✓ Added '{template_name}' (confidence: {best_score:.2f})\n Files: {', '.join(files_written)}"
438
+
439
+ def cmd_import(json_path: str) -> str:
440
+ if not os.path.exists(json_path):
441
+ return f"✗ File not found: {json_path}"
442
+ try:
443
+ with open(json_path, 'r', encoding='utf-8') as f:
444
+ new_templates = json.load(f)
445
+ except Exception as e:
446
+ return f"✗ Invalid JSON: {e}"
447
+ with _manifest_lock:
448
+ manifest = {}
449
+ if os.path.exists(MANIFEST_PATH):
450
+ try:
451
+ with open(MANIFEST_PATH, 'r') as f:
452
+ manifest = json.load(f)
453
+ except Exception:
454
+ pass
455
+ imported = 0
456
+ for name, data in new_templates.items():
457
+ if "description" not in data or "files" not in data:
458
+ continue
459
+ vector = get_embedding(data["description"])
460
+ if vector:
461
+ manifest[name] = {
462
+ "description": data["description"],
463
+ "vector": vector,
464
+ "files": data["files"]
465
+ }
466
+ imported += 1
467
+ with open(MANIFEST_PATH, 'w') as f:
468
+ json.dump(manifest, f, indent=2)
469
+ return f"✓ Imported {imported} templates from {json_path}"
470
+
471
+ def cmd_status() -> str:
472
+ with _manifest_lock:
473
+ manifest_count = 0
474
+ if os.path.exists(MANIFEST_PATH):
475
+ try:
476
+ with open(MANIFEST_PATH, 'r') as f:
477
+ manifest_count = len(json.load(f))
478
+ except Exception:
479
+ pass
480
+ has_git = os.path.exists(os.path.join(WORKSPACE, ".git"))
481
+ lines = [
482
+ f"STACK Status",
483
+ f" Workspace: {WORKSPACE}",
484
+ f" Git initialized: {'✓' if has_git else '✗'}",
485
+ f" Templates in manifest: {manifest_count}",
486
+ f" Ollama: {'✓' if check_ollama_running() else '✗'}"
487
+ ]
488
+ return "\n".join(lines)
489
+
490
+ def cmd_list_templates() -> str:
491
+ with _manifest_lock:
492
+ if not os.path.exists(MANIFEST_PATH):
493
+ return "No templates found"
494
+ try:
495
+ with open(MANIFEST_PATH, 'r') as f:
496
+ manifest = json.load(f)
497
+ except Exception:
498
+ return "Error reading manifest"
499
+ if not manifest:
500
+ return "No templates in manifest"
501
+ lines = ["Available templates:"]
502
+ for name, data in manifest.items():
503
+ desc = data.get("description", "No description")[:60]
504
+ lines.append(f" • {name}: {desc}")
505
+ return "\n".join(lines)
506
+
507
+ # ============================================================================
508
+ # 7. CLI INTERFACE
509
+ # ============================================================================
510
+ def print_banner() -> None:
511
+ banner = """
512
+ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
513
+ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
514
+ ███████╗ ██║ ███████║██║ █████╔╝
515
+ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
516
+ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
517
+ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
518
+ Embed-Driven Repository Architect
519
+ """
520
+ print(banner)
521
+
522
+ def print_commands() -> None:
523
+ print("""
524
+ Commands:
525
+ /build <name> - Create new git repository workspace
526
+ /add <description> - Find matching template, inject into workspace
527
+ /import <file.json> - Import external template bundle
528
+ /list - List available templates
529
+ /status - Show system status
530
+ /help - Show this help
531
+ /quit - Exit STACK
532
+
533
+ Templates folder: ./stack_system/templates/
534
+ - Add folders with code + description.txt for hot-reload
535
+ - JSON imports also supported
536
+ """)
537
+
538
+ def main() -> None:
539
+ print("[*] Initializing STACK...")
540
+ ensure_models()
541
+ seed_initial_templates()
542
+ start_template_watcher()
543
+ print_banner()
544
+ print_commands()
545
+ while True:
546
+ try:
547
+ user_input = input("\nSTACK > ").strip()
548
+ if not user_input:
549
+ continue
550
+ if user_input.lower() in ["/quit", "/exit", "quit", "exit"]:
551
+ print("[*] Goodbye!")
552
+ break
553
+ if user_input == "/help":
554
+ print_commands()
555
+ continue
556
+ if user_input == "/status":
557
+ print(cmd_status())
558
+ continue
559
+ if user_input == "/list":
560
+ print(cmd_list_templates())
561
+ continue
562
+ if user_input.startswith("/build "):
563
+ repo_name = user_input[7:].strip()
564
+ if repo_name:
565
+ print(cmd_build(repo_name))
566
+ else:
567
+ print("Usage: /build <repo_name>")
568
+ continue
569
+ if user_input.startswith("/add "):
570
+ query = user_input[5:].strip()
571
+ if query:
572
+ print(cmd_add(query))
573
+ else:
574
+ print("Usage: /add <description>")
575
+ continue
576
+ if user_input.startswith("/import "):
577
+ path = user_input[8:].strip()
578
+ if path:
579
+ print(cmd_import(path))
580
+ else:
581
+ print("Usage: /import <path/to/template.json>")
582
+ continue
583
+ print(f"Unknown command. Type /help for available commands.")
584
+ except KeyboardInterrupt:
585
+ print("\n[*] Interrupted. Goodbye!")
586
+ break
587
+ except EOFError:
588
+ break
589
+ except Exception as e:
590
+ print(f"Error: {e}")
591
+
592
+ if __name__ == "__main__":
593
+ main()