synapseForge 0.1.24.dev2__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,447 @@
1
+ """tkinter GUI for synapseforge init — replaces terminal get_user_input()."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import re
7
+ import threading
8
+ import tkinter as tk
9
+ from tkinter import colorchooser, filedialog, messagebox, ttk
10
+ from typing import Any, Dict, Optional
11
+ from pathlib import Path
12
+ import ctypes
13
+ import sys
14
+
15
+ _HERE = Path(__file__).resolve().parent
16
+ _ICO_PATH = _HERE / "logo.ico"
17
+ _LOGO_PNG_PATH = _HERE / "logo.png"
18
+
19
+ try:
20
+ from PIL import Image, ImageTk
21
+ except ImportError: # pragma: no cover
22
+ Image = None # type: ignore[assignment]
23
+ ImageTk = None
24
+
25
+ _HEX_RE = re.compile(r"^#[0-9a-fA-F]{6}$")
26
+
27
+ COLOR_TAB_FIELDS = [
28
+ ("primary", "Color principal: botón de enviar, burbuja y avatar del asistente, barra de actividad, opción seleccionada del menú, enlaces de las respuestas y puntos de “escribiendo…”"),
29
+ ("secondary", "Color de los detalles suaves: borde que se ilumina al hacer clic en un campo, anillo de la conversación seleccionada, bordes de las tarjetas y cursor de escritura"),
30
+ ("primary_text", "Color del texto e íconos que van sobre el color principal: flecha de enviar, texto de botones, ícono del avatar y texto de la burbuja del asistente"),
31
+ ("gradient_secondary", "Color final del degradé de los botones y del avatar del asistente (el inicio es el color principal)"),
32
+ ]
33
+
34
+
35
+ # ──────────────────────────────────────────────────────────────
36
+ # Fijar el AppUserModelID ANTES de crear cualquier ventana
37
+ # ──────────────────────────────────────────────────────────────
38
+ def _set_app_user_model_id() -> None:
39
+ if sys.platform != "win32":
40
+ return
41
+ try:
42
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
43
+ "synapseforge.init.1"
44
+ )
45
+ except Exception:
46
+ pass
47
+
48
+ _set_app_user_model_id()
49
+
50
+
51
+ class InitApp:
52
+ """GUI for collecting project configuration for synapseforge init."""
53
+
54
+ def __init__(self, target_dir: str) -> None:
55
+ self.target_dir = target_dir
56
+ self.result: Optional[Dict[str, Any]] = None
57
+
58
+ # Crear la ventana
59
+ self.root = tk.Tk()
60
+ self.root.title("synapseForge — Init")
61
+ self.root.resizable(False, False)
62
+
63
+ # ── Configurar el ícono (con retraso para asegurar que la ventana esté lista) ──
64
+ self.root.after(100, self._set_icon)
65
+
66
+ # ── Logo header (desde archivo local) ────────────────────────
67
+ self._load_logo_local()
68
+
69
+ # ── Notebook (tabs) ──────────────────────────────────────────
70
+ self.notebook = ttk.Notebook(self.root)
71
+ self.notebook.pack(fill="both", expand=True, padx=10, pady=(0, 10))
72
+
73
+ self._build_project_tab()
74
+ self._build_logos_tab()
75
+ self._build_colors_tab()
76
+
77
+ # ── Bottom frame (progress + buttons) ────────────────────────
78
+ bottom = ttk.Frame(self.root)
79
+ bottom.pack(fill="x", padx=10, pady=(0, 10))
80
+
81
+ self.progress = ttk.Progressbar(bottom, mode="indeterminate", length=400)
82
+ self.progress.pack(side="left", fill="x", expand=True, padx=(0, 10))
83
+
84
+ self.btn_cancel = ttk.Button(bottom, text="Cancelar", command=self._on_cancel)
85
+ self.btn_cancel.pack(side="right", padx=(5, 0))
86
+
87
+ self.btn_submit = ttk.Button(bottom, text="Crear Proyecto", command=self._on_submit)
88
+ self.btn_submit.pack(side="right")
89
+
90
+ # ── Center window ────────────────────────────────────────────
91
+ self._center(640, 800)
92
+
93
+ # ──────────────────────────────────────────────────────────────────
94
+ # Configuración robusta del ícono usando ctypes
95
+ # ──────────────────────────────────────────────────────────────────
96
+ def _set_icon(self) -> None:
97
+ """Asigna el ícono de la ventana y de la barra de tareas usando ctypes."""
98
+ if not _ICO_PATH.is_file():
99
+ return
100
+
101
+ try:
102
+ # 1. iconbitmap (funciona para la ventana)
103
+ self.root.iconbitmap(str(_ICO_PATH.resolve()))
104
+
105
+ # 2. Forzar actualización de la barra de tareas con ctypes
106
+ hwnd = self.root.winfo_id()
107
+ user32 = ctypes.windll.user32
108
+ # Cargar el ícono desde el archivo
109
+ hicon = user32.LoadImageW(
110
+ 0,
111
+ str(_ICO_PATH.resolve()),
112
+ 1, # IMAGE_ICON
113
+ 0, 0,
114
+ 0x00000010 # LR_LOADFROMFILE
115
+ )
116
+ if hicon:
117
+ # GCL_HICON = -14, GCL_HICONSM = -34
118
+ user32.SetClassLongW(hwnd, -14, hicon)
119
+ user32.SetClassLongW(hwnd, -34, hicon)
120
+ # WM_SETICON = 0x0080, ICON_BIG = 0, ICON_SMALL = 1
121
+ user32.SendMessageW(hwnd, 0x0080, 0, hicon)
122
+ user32.SendMessageW(hwnd, 0x0080, 1, hicon)
123
+
124
+ except Exception:
125
+ pass # Silencioso si falla
126
+
127
+ # ------------------------------------------------------------------
128
+ # Logo (cargado desde archivo local)
129
+ # ------------------------------------------------------------------
130
+ def _load_logo_local(self) -> None:
131
+ """Carga el logo desde logo_transparente.png (local) y lo muestra en la ventana."""
132
+ if Image is None or ImageTk is None:
133
+ return
134
+ if not _LOGO_PNG_PATH.is_file():
135
+ return
136
+ try:
137
+ pil = Image.open(_LOGO_PNG_PATH)
138
+ pil.thumbnail((150, 150), Image.LANCZOS)
139
+ self._logo_img = ImageTk.PhotoImage(pil)
140
+ lbl = tk.Label(self.root, image=self._logo_img)
141
+ lbl.pack(pady=(10, 5))
142
+ except Exception:
143
+ pass # silencioso si falla
144
+
145
+ # ------------------------------------------------------------------
146
+ # Tab 1: Project info
147
+ # ------------------------------------------------------------------
148
+ def _build_project_tab(self) -> None:
149
+ tab = ttk.Frame(self.notebook, padding=15)
150
+ self.notebook.add(tab, text="Proyecto")
151
+
152
+ fields: list[tuple[str, str, bool]] = [
153
+ ("empresa", "Nombre de la empresa desarrolladora", True),
154
+ ("owner", "Owner del repo (usuario de GitHub)", True),
155
+ ("legal", "Nombre legal / razón social", True),
156
+ ("repo", "Nombre del repo", True),
157
+ ("cliente", "Nombre del cliente", True),
158
+ ("descripcion", "Descripción del proyecto", True),
159
+ ("tarea", "Nombre de la tarea / rubro", True),
160
+ ]
161
+
162
+ self._entries: Dict[str, tk.Entry] = {}
163
+ for i, (key, label, required) in enumerate(fields):
164
+ lbl_text = label + " *" if required else label
165
+ lbl = ttk.Label(tab, text=lbl_text)
166
+ lbl.grid(row=i, column=0, sticky="w", pady=3, padx=(0, 10))
167
+ ent = ttk.Entry(tab, width=55)
168
+ ent.grid(row=i, column=1, pady=3)
169
+ self._entries[key] = ent
170
+
171
+ # ------------------------------------------------------------------
172
+ # Tab 2: Logos (file pickers)
173
+ # ------------------------------------------------------------------
174
+ def _build_logos_tab(self) -> None:
175
+ tab = ttk.Frame(self.notebook, padding=15)
176
+ self.notebook.add(tab, text="Logos")
177
+
178
+ # ── Logo empresa (required) ──────────────────────────────────
179
+ ttk.Label(tab, text="Logo de la empresa (para README) *").grid(
180
+ row=0, column=0, sticky="w", pady=3
181
+ )
182
+ self._logo_path_var = tk.StringVar()
183
+ ttk.Entry(tab, textvariable=self._logo_path_var, width=50).grid(
184
+ row=0, column=1, padx=(0, 5), pady=3
185
+ )
186
+ ttk.Button(tab, text="Examinar…", command=self._browse_logo).grid(
187
+ row=0, column=2, pady=3
188
+ )
189
+
190
+ # ── Logo cliente (optional) ──────────────────────────────────
191
+ ttk.Label(tab, text="Logo del cliente (para la app)").grid(
192
+ row=1, column=0, sticky="w", pady=(15, 3)
193
+ )
194
+ self._logo_cliente_var = tk.StringVar()
195
+ ttk.Entry(tab, textvariable=self._logo_cliente_var, width=50).grid(
196
+ row=1, column=1, padx=(0, 5), pady=(15, 3)
197
+ )
198
+ ttk.Button(tab, text="Examinar…", command=self._browse_logo_cliente).grid(
199
+ row=1, column=2, pady=(15, 3)
200
+ )
201
+
202
+ def _browse_logo(self) -> None:
203
+ path = filedialog.askopenfilename(
204
+ title="Seleccionar logo de la empresa",
205
+ filetypes=[("Imágenes", "*.png *.jpg *.jpeg *.gif *.bmp"), ("Todos", "*.*")],
206
+ )
207
+ if path:
208
+ self._logo_path_var.set(path)
209
+
210
+ def _browse_logo_cliente(self) -> None:
211
+ path = filedialog.askopenfilename(
212
+ title="Seleccionar logo del cliente",
213
+ filetypes=[("Imágenes", "*.png *.jpg *.jpeg *.gif *.bmp"), ("Todos", "*.*")],
214
+ )
215
+ if path:
216
+ self._logo_cliente_var.set(path)
217
+
218
+ # ------------------------------------------------------------------
219
+ # Tab 3: Colors (optional hex fields with color picker)
220
+ # ------------------------------------------------------------------
221
+ def _build_colors_tab(self) -> None:
222
+ tab = ttk.Frame(self.notebook, padding=15)
223
+ self.notebook.add(tab, text="Colores")
224
+
225
+ ttk.Label(
226
+ tab,
227
+ text="Colores del proyecto (obligatorios):",
228
+ font=("", 10, "bold"),
229
+ ).grid(row=0, column=0, columnspan=4, sticky="w", pady=(0, 10))
230
+
231
+ self._color_entries: Dict[str, tk.Entry] = {}
232
+ self._color_previews: Dict[str, tk.Canvas] = {}
233
+
234
+ for i, (key, desc) in enumerate(COLOR_TAB_FIELDS, start=1):
235
+ row = i * 2
236
+
237
+ # Explanation (wraps so it doesn't push the selector out of view)
238
+ ttk.Label(tab, text=desc, wraplength=420, justify="left").grid(
239
+ row=row, column=0, columnspan=4, sticky="w", pady=(6, 0), padx=(0, 8)
240
+ )
241
+
242
+ # Preview square (16×16)
243
+ cv = tk.Canvas(tab, width=18, height=18, highlightthickness=1,
244
+ highlightbackground="#ccc")
245
+ cv.grid(row=row + 1, column=0, pady=(4, 6), padx=(0, 4))
246
+ self._color_previews[key] = cv
247
+
248
+ ent = ttk.Entry(tab, width=12)
249
+ ent.grid(row=row + 1, column=1, pady=(4, 6), padx=(0, 4))
250
+ self._color_entries[key] = ent
251
+
252
+ # Bind entry change → update preview
253
+ ent.bind("<KeyRelease>", lambda _e, k=key: self._update_preview(k))
254
+
255
+ ttk.Button(tab, text="Seleccionar", command=lambda k=key: self._pick_color(k)).grid(
256
+ row=row + 1, column=2, pady=(4, 6), sticky="w"
257
+ )
258
+
259
+ # ── Gradient toggle ──────────────────────────────────────────
260
+ self._usar_gradiente_var = tk.BooleanVar(value=True)
261
+ cb = ttk.Checkbutton(
262
+ tab,
263
+ text="Usar degradé en botones y avatar",
264
+ variable=self._usar_gradiente_var,
265
+ command=self._toggle_gradient_fields,
266
+ )
267
+ cb.grid(row=len(COLOR_TAB_FIELDS) * 2 + 2, column=0, columnspan=4, sticky="w", pady=(12, 0))
268
+
269
+ ttk.Label(
270
+ tab,
271
+ text="Si lo desactivás, botones y avatar usan el color principal liso.",
272
+ font=("", 8, "italic"),
273
+ foreground="#888",
274
+ ).grid(row=len(COLOR_TAB_FIELDS) * 2 + 3, column=0, columnspan=4, sticky="w")
275
+
276
+ def _pick_color(self, key: str) -> None:
277
+ """Open OS color chooser, fill entry and update preview."""
278
+ result = colorchooser.askcolor(
279
+ title=key,
280
+ parent=self.root,
281
+ )
282
+ if result and result[1]: # result is ((R,G,B), "#RRGGBB")
283
+ hex_val = result[1]
284
+ self._color_entries[key].delete(0, tk.END)
285
+ self._color_entries[key].insert(0, hex_val)
286
+ self._update_preview(key)
287
+
288
+ def _update_preview(self, key: str) -> None:
289
+ """Update the preview square for the given color key."""
290
+ cv = self._color_previews[key]
291
+ raw = self._color_entries[key].get().strip()
292
+ cv.delete("all")
293
+ if raw and _HEX_RE.match(raw):
294
+ cv.config(bg=raw)
295
+ else:
296
+ cv.config(bg="#ffffff")
297
+
298
+ def _toggle_gradient_fields(self) -> None:
299
+ """Enable/disable gradient_secondary picker based on checkbox."""
300
+ state = "normal" if self._usar_gradiente_var.get() else "disabled"
301
+ key = "gradient_secondary"
302
+ if key in self._color_entries:
303
+ self._color_entries[key].config(state=state)
304
+ if key in self._color_previews:
305
+ self._color_previews[key].config(highlightbackground="#ccc" if state == "normal" else "#eee")
306
+
307
+ # ------------------------------------------------------------------
308
+ # Collect + validate
309
+ # ------------------------------------------------------------------
310
+ def _collect_config(self) -> Optional[Dict[str, Any]]:
311
+ """Read all fields and return config dict, or None if validation fails."""
312
+
313
+ # ── Required text fields ──────────────────────────────────────
314
+ required = ["empresa", "owner", "legal", "repo", "cliente", "descripcion", "tarea"]
315
+ config: Dict[str, Any] = {}
316
+ for key in required:
317
+ val = self._entries[key].get().strip()
318
+ if not val:
319
+ messagebox.showwarning("Campo requerido",
320
+ f"'{key}' es obligatorio.", parent=self.root)
321
+ return None
322
+ config[key] = val
323
+
324
+ # ── Logo empresa ──────────────────────────────────────────────
325
+ logo_path = self._logo_path_var.get().strip()
326
+ if not logo_path:
327
+ messagebox.showwarning("Campo requerido",
328
+ "El logo de la empresa es obligatorio.", parent=self.root)
329
+ return None
330
+ from pathlib import Path
331
+ logo_resolved = Path(logo_path).resolve()
332
+ if not logo_resolved.is_file():
333
+ messagebox.showwarning("Archivo no encontrado",
334
+ f"No se encontró: {logo_resolved}", parent=self.root)
335
+ return None
336
+
337
+ config["logo"] = {
338
+ "path": str(logo_resolved),
339
+ }
340
+
341
+ # ── Logo cliente ──────────────────────────────────────────────
342
+ logo_cliente = self._logo_cliente_var.get().strip()
343
+ config["logo_cliente"] = logo_cliente or None
344
+
345
+ # ── Colors (required) ─────────────────────────────────────────
346
+ colors: Dict[str, str] = {}
347
+ usar_gradiente = self._usar_gradiente_var.get()
348
+ colors["usar_gradiente"] = usar_gradiente
349
+ for key, _desc in COLOR_TAB_FIELDS:
350
+ # Skip gradient_secondary if gradient toggle is off
351
+ if key == "gradient_secondary" and not usar_gradiente:
352
+ colors[key] = colors.get("primary", "#000000")
353
+ continue
354
+ val = self._color_entries[key].get().strip()
355
+ if not val:
356
+ messagebox.showwarning(
357
+ "Color requerido",
358
+ f"'{_desc}' es obligatorio.",
359
+ parent=self.root,
360
+ )
361
+ return None
362
+ if not _HEX_RE.match(val):
363
+ messagebox.showwarning(
364
+ "Color inválido",
365
+ f"{key}: '{val}' no es un color hex válido (#RRGGBB).",
366
+ parent=self.root,
367
+ )
368
+ return None
369
+ colors[key] = val
370
+ config["colors"] = colors
371
+
372
+ return config
373
+
374
+ def _on_submit(self) -> None:
375
+ config = self._collect_config()
376
+ if config is None:
377
+ return # validation failed
378
+
379
+ # Disable UI and start progress
380
+ self._set_ui_enabled(False)
381
+ self.progress.start(15)
382
+
383
+ # Run pipeline in daemon thread
384
+ t = threading.Thread(target=self._run_pipeline, args=(config,), daemon=True)
385
+ t.start()
386
+
387
+ def _run_pipeline(self, config: Dict[str, Any]) -> None:
388
+ """Execute init pipeline in background thread."""
389
+ try:
390
+ from pipeline.init.main import run
391
+ run(self.target_dir, config=config)
392
+ self.result = config
393
+ self.root.after(0, self._on_success)
394
+ except Exception as exc:
395
+ # Bind the message now: ``exc`` is deleted when the except block
396
+ # exits, before the deferred callback runs.
397
+ msg = str(exc)
398
+ self.root.after(0, lambda: self._on_error(msg))
399
+
400
+ def _on_success(self) -> None:
401
+ self.progress.stop()
402
+ self.root.destroy()
403
+
404
+ def _on_error(self, msg: str) -> None:
405
+ self.progress.stop()
406
+ messagebox.showerror("Error", msg, parent=self.root)
407
+ self._set_ui_enabled(True)
408
+
409
+ def _on_cancel(self) -> None:
410
+ self.result = None
411
+ self.root.destroy()
412
+
413
+ def _set_ui_enabled(self, enabled: bool) -> None:
414
+ state = "normal" if enabled else "disabled"
415
+ self.btn_submit.config(state=state)
416
+ self.btn_cancel.config(state=state)
417
+ for ent in self._entries.values():
418
+ ent.config(state=state)
419
+ # notebooks aren't easily disabled, skip for UX
420
+
421
+ # ------------------------------------------------------------------
422
+ # Window utils
423
+ # ------------------------------------------------------------------
424
+ def _center(self, w: int, h: int) -> None:
425
+ sw = self.root.winfo_screenwidth()
426
+ sh = self.root.winfo_screenheight()
427
+ x = (sw - w) // 2
428
+ y = (sh - h) // 2
429
+ self.root.geometry(f"{w}x{h}+{x}+{y}")
430
+
431
+ # ------------------------------------------------------------------
432
+ # Public entry point
433
+ # ------------------------------------------------------------------
434
+ @staticmethod
435
+ def launch(target_dir: str) -> Optional[Dict[str, Any]]:
436
+ """Open the init GUI and return the config dict (or None if cancelled).
437
+
438
+ Args:
439
+ target_dir: Absolute path to the target project directory.
440
+
441
+ Returns:
442
+ Config dictionary matching ``input_handler.get_user_input()``
443
+ format, or ``None`` if the user cancelled.
444
+ """
445
+ app = InitApp(target_dir)
446
+ app.root.mainloop()
447
+ return app.result
Binary file
Binary file
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: synapseForge
3
+ Version: 0.1.24.dev2
4
+ Summary: AI agent project scaffolding & distribution CLI
5
+ Author-email: "synapse.ai" <developer@synapseaihub.com.ar>
6
+ Maintainer-email: "synapse.ai" <developer@synapseaihub.com.ar>
7
+ License: Apache-2.0
8
+ Project-URL: Homepage, https://www.synapseaihub.com.ar
9
+ Project-URL: Repository, https://github.com/synapse-ai-hub/synapseForge
10
+ Project-URL: Issues, https://github.com/synapse-ai-hub/synapseForge/issues
11
+ Keywords: ai-agents,llm,framework,cli,fastapi,react,mcp,tool-calling
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: Pillow>=10.0.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: ruff>=0.10.0; extra == "dev"
25
+ Requires-Dist: mypy>=1.15.0; extra == "dev"
26
+ Requires-Dist: black>=24.0.0; extra == "dev"
27
+ Requires-Dist: pre-commit>=4.0.0; extra == "dev"
28
+ Provides-Extra: build
29
+ Requires-Dist: pyinstaller>=6.0.0; extra == "build"
30
+ Dynamic: license-file
31
+
32
+ <p align="center">
33
+ <img src="https://github.com/synapse-ai-hub/sources/raw/main/logo_transparente.png" alt="Logo" width="150">
34
+ </p>
35
+
36
+ ---
37
+
38
+ <h1 align="center">
39
+ <img src="https://github.com/synapse-ai-hub/sources/raw/main/forge.png" alt="synapseForge" width="420">
40
+ </h1>
41
+
42
+ ---
43
+
44
+ <p align="center">
45
+ <a href="./LICENSE">
46
+ <img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License: Apache 2.0" />
47
+ </a>
48
+ &nbsp;
49
+ <a href="https://link.mercadopago.com.ar/synapseforge">
50
+ <img src="https://github.com/synapse-ai-hub/sources/raw/main/badges/mercadopago-support.svg" alt="Support this project" />
51
+ </a>
52
+ </p>
53
+
54
+ ---
55
+
56
+ <h3 align="center">CLI to scaffold and ship full-stack AI agent projects (FastAPI + React/Vite/TS)</h3>
57
+
58
+ ---
59
+
60
+ ## Description
61
+
62
+ **synapseForge** is a PyPI package that provides a CLI to scaffold full-stack AI agent projects from scratch — backend (FastAPI), frontend (React/Vite/TypeScript), branding (logo, .ico, color palette), dependencies, and a self-contained distribution build.
63
+
64
+ The generated project includes:
65
+
66
+ - **Agent Framework**: AgentLoop with native tool calling, tools registry (native + external + MCP), sessions (SQLite WAL), per-agent permissions, skills and sub-agent delegation
67
+ - **Multi-provider LLM**: LOCAL (Ollama), Groq, Google Gemini and OpenRouter — cloud API keys managed from the config panel, validated against each provider's API and stored encrypted in SQLite
68
+ - **RAG knowledge base**: ChromaDB vector collections with cloud embeddings via OpenRouter; upload files and web pages, cosine-similarity search
69
+ - **LLM-assisted creation**: standalone interfaces to generate skills, tools and agents through an iterative interview (with real tools enabled), with ephemeral cloud model selection per task
70
+ - **Scheduled tasks**: user-defined tasks (description + time + weekdays) managed from the header Agenda or via Telegram; the backend runs them with the selected model and notifies the result in the UI bell and on Telegram
71
+ - **Telegram bot**: remote control that bridges messages to the agent through the web UI (commands, voice transcription, attachments)
72
+ - **Frontend**: chat with SSE streaming, config panel, sessions sidebar, context-window gauge, metrics dashboard
73
+ - **Docker** support and **desktop app mode** (heartbeat watchdog + shutdown endpoint)
74
+
75
+ ---
76
+
77
+ ## Installation
78
+
79
+ ```bash
80
+ pip install synapseForge
81
+ ```
82
+
83
+ Package dependencies: `Pillow` (.ico generation) — everything else is project-level.
84
+
85
+ ## Requirements
86
+
87
+ | Tool | Version | Needed for |
88
+ |------|---------|------------|
89
+ | Python | 3.12+ | `init`, `launch`, `run`, `colors` |
90
+ | Node.js | 20+ | `launch` (frontend build), `run` (dev server) |
91
+ | Docker | 20+ | Optional: containerized deployment |
92
+
93
+ **LLM provider (required):** at least one cloud API key is needed to use the app — [OpenRouter](https://openrouter.ai/settings/keys), [Google Gemini](https://aistudio.google.com/apikey) or [Groq](https://console.groq.com/keys) all offer free tiers. Keys are loaded from the in-app config panel (**Providers**) on first launch; nothing else has to be installed.
94
+
95
+ > The **knowledge base** feature specifically requires an **OpenRouter** key (free tier works). Without it, that section stays disabled — everything else runs normally.
96
+
97
+ **Ollama (optional):** local models are supported but not required. Install Ollama only if you want to run models locally.
98
+
99
+ ---
100
+
101
+ ## Quick Start
102
+
103
+ ### Create a new project
104
+
105
+ ```bash
106
+ synapseforge init my-project
107
+ cd my-project
108
+ ```
109
+
110
+ Interactive GUI pipeline (project data, logos, colors).
111
+
112
+ ### Run in development
113
+
114
+ ```bash
115
+ synapseforge run ./my-project
116
+ ```
117
+
118
+ Requires the project venv to be activated (`VIRTUAL_ENV`). Starts backend + frontend dev servers and opens the browser. Ctrl+C stops both.
119
+
120
+ ### Build a distributable
121
+
122
+ ```bash
123
+ synapseforge launch -p ./my-project -n "MyApp"
124
+ ```
125
+
126
+ Builds the frontend, bundles embedded Python and packages everything into a self-contained zip ready to deliver. By default the backend ships as `.py` sources; pass `-c` / `--compile` to compile it to `.pyc`. Other options: `--skip-frontend`, `--no-embed`.
127
+
128
+ ### Edit colors at runtime (no rebuild)
129
+
130
+ ```bash
131
+ synapseforge colors ./my-project
132
+ ```
133
+
134
+ GUI editor for `frontend/public/colors.json`. Refresh the browser (F5) to see changes instantly.
135
+
136
+ ---
137
+
138
+ ## CLI Reference
139
+
140
+ | Command | Description |
141
+ |---------|-------------|
142
+ | `synapseforge init [dir]` | Scaffold a project from bundled template (GUI) |
143
+ | `synapseforge launch -p <path> -n <exe> [--skip-frontend] [--no-embed] [-c]` | Build self-contained distribution zip (`-c` compiles backend to `.pyc`, default ships `.py`) |
144
+ | `synapseforge colors [dir]` | Edit `frontend/public/colors.json` via GUI (live reload) |
145
+ | `synapseforge run [dir]` | Start uvicorn + npm dev servers, open browser (venv must be active) |
146
+ | `synapseforge --help` | Show global help |
147
+
148
+ ---
149
+
150
+ ## Tech Stack
151
+
152
+ [![Python](https://github.com/synapse-ai-hub/sources/raw/main/badges/python.svg)](https://www.python.org/)
153
+ [![FastAPI](https://github.com/synapse-ai-hub/sources/raw/main/badges/fastapi.svg)](https://fastapi.tiangolo.com/)
154
+ [![SQLite](https://github.com/synapse-ai-hub/sources/raw/main/badges/sqlite.svg)](https://www.sqlite.org/)
155
+ [![ChromaDB](https://github.com/synapse-ai-hub/sources/raw/main/badges/chromadb.svg)](https://www.trychroma.com/)
156
+ [![React](https://github.com/synapse-ai-hub/sources/raw/main/badges/react.svg)](https://react.dev/)
157
+ [![TypeScript](https://github.com/synapse-ai-hub/sources/raw/main/badges/typescript.svg)](https://www.typescriptlang.org/)
158
+ [![Vite](https://github.com/synapse-ai-hub/sources/raw/main/badges/vite.svg)](https://vite.dev/)
159
+ [![Tailwind](https://github.com/synapse-ai-hub/sources/raw/main/badges/tailwind.svg)](https://tailwindcss.com/)
160
+ [![shadcn/ui](https://img.shields.io/badge/shadcn%2Fui-UI-555?labelColor=d0d4dc&logo=shadcnui&logoColor=111)](https://ui.shadcn.com/)
161
+ [![Node.js](https://github.com/synapse-ai-hub/sources/raw/main/badges/nodejs.svg)](https://nodejs.org/)
162
+ [![PyInstaller](https://github.com/synapse-ai-hub/sources/raw/main/badges/pyinstaller.svg)](https://pyinstaller.org/)
163
+ [![Docker](https://github.com/synapse-ai-hub/sources/raw/main/badges/docker.svg)](https://www.docker.com/)
164
+ [![Telegram](https://github.com/synapse-ai-hub/sources/raw/main/badges/telegram.svg)](https://telegram.org/)
165
+ [![PyPI](https://github.com/synapse-ai-hub/sources/raw/main/badges/pypi.svg)](https://pypi.org/)
166
+ [![Groq](https://github.com/synapse-ai-hub/sources/raw/main/badges/groq.svg)](https://groq.com/)
167
+ [![Google Gemini](https://github.com/synapse-ai-hub/sources/raw/main/badges/gemini.svg)](https://ai.google.dev/)
168
+ [![OpenRouter](https://github.com/synapse-ai-hub/sources/raw/main/badges/openrouter.svg)](https://openrouter.ai/)
169
+ [![Ollama](https://img.shields.io/badge/Ollama-Local-555?labelColor=d0d4dc&logo=ollama&logoColor=111)](https://ollama.com/)
170
+
171
+ ---
172
+
173
+ ## Support this project
174
+
175
+ synapseForge will always be free and open source. If you find it useful, consider supporting its development with a [donation via Mercado Pago](https://link.mercadopago.com.ar/synapseforge) — your donation goes directly into new features, fixes and better docs for everyone.
176
+
177
+ ---
178
+
179
+ ## License
180
+
181
+ Apache 2.0
182
+
183
+ ---
184
+
185
+ Copyright (c) 2026 SYNASPE AI SAS
186
+
187
+ ---
@@ -0,0 +1,29 @@
1
+ pipeline/__init__.py,sha256=LTNoAzYsOBPjlMBN535yd4Wgqeip82REd351GiRc_d0,70
2
+ pipeline/template.zip,sha256=ZP6s3j7plTbKOjT-4ACx5dGptf4N6FPefhQDgjgyKcU,443942
3
+ pipeline/init/__init__.py,sha256=whbnhhDnssVvRCEaqRzx84T1tTQxku0oEQAcrfY9NEg,204
4
+ pipeline/init/__main__.py,sha256=DmAGPXdmSm97TBIjFXmVkU1MEhibSF5V6JGchdV0n5c,217
5
+ pipeline/init/config_handler.py,sha256=xk5aUh0bpK5ohnTDjPTNYA0PYrOe0Z-lkLEY_UxFFbk,1341
6
+ pipeline/init/input_handler.py,sha256=Ue22816T_76prfUEsOO4ifjViGujvvc4pTkVduAT-3k,4934
7
+ pipeline/init/logo_handler.py,sha256=1Kd05nhz3jufAKgfemNvrYLQ1nvPpHDhWU0QCXOBYxM,1121
8
+ pipeline/init/main.py,sha256=Eiqqh9m4IidjEOqbviRc9PenUUxx6Unu096vdkpy3-E,5427
9
+ pipeline/init/placeholder_handler.py,sha256=xbvHiwfOxYn4366LznX0CiddiXs5qWJoNnQ76IJajwQ,5176
10
+ pipeline/init/template_handler.py,sha256=XH4ka6ilf0d_az9Nya3YuoMCqNI_nWk4E1idHLAjWQ8,3018
11
+ pipeline/init/venv_handler.py,sha256=rvugBdUcWU-xajIITfmxQGD2mKNPlXY4_2pKYy16fHU,2054
12
+ pipeline/launch/__init__.py,sha256=kTjcQgfeK-2CjUXJFZtFIVRTc1_ibT2pWBEpJK2QxfI,58
13
+ pipeline/launch/forge.py,sha256=xfDiFovkM_g7ipYFO4qhnL9wJKMLx3Np4aZ9YLEU8ho,24930
14
+ pipeline/launch/templates/launcher.py,sha256=pSrhLmyXdnCv0Tdae5GOWYqbOOf7CtVBD7jwute8J_Y,2077
15
+ synapseforge/__init__.py,sha256=WC0q7w92WA_KK0PU4ngoM0m6wG5-dYyXCLwPsz1ym48,99
16
+ synapseforge/__main__.py,sha256=c8L2gqOZGeF8pYZ0D3_SbfT_jzCoTRC6TE2aT70q_Mk,134
17
+ synapseforge/cli/__init__.py,sha256=tu3FwtlQJ_oU8TIXdFE5OLfANQMa0u7Yqtr--Dh3Zbc,51
18
+ synapseforge/cli/main.py,sha256=8DwozYeXU5sdp375AfychQIednKcrA3JjXctujVid8A,15808
19
+ synapseforge/tk/__init__.py,sha256=U8qSNZIc_98Q7ZJqTnrINgHADFfPq-RUjupaz2xDQ14,56
20
+ synapseforge/tk/colors_app.py,sha256=ROZanpzLQKw5y9867mKK_6S8MSXKBMMrPkYNbt6RfXs,13338
21
+ synapseforge/tk/init_app.py,sha256=d9qZhFQs8lJy5akeVxmq97uzUu44FY7gPSNdL_knYMg,19860
22
+ synapseforge/tk/logo.ico,sha256=Q46zWEsYbqfmcB_1HaRqZM9kyFORGGQaFQPn-r_D16w,43415
23
+ synapseforge/tk/logo.png,sha256=bLPNBk-kAhTBVlGraFr9o-abJJ98KKiolj33WcG2zUY,81279
24
+ synapseforge-0.1.24.dev2.dist-info/licenses/LICENSE,sha256=MutYgYox1x17FbtHn5lIxcBSgE3BsVqkfj0-JJMQhdQ,11550
25
+ synapseforge-0.1.24.dev2.dist-info/METADATA,sha256=cBgfvxqM-42LXSnr3TMh1cKwoOxfCSRYh-TirNYyqdE,8792
26
+ synapseforge-0.1.24.dev2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
27
+ synapseforge-0.1.24.dev2.dist-info/entry_points.txt,sha256=zNxPVdol1eNIZxZlXtq5oshdPiaWMtoL2kmdIKfASBs,60
28
+ synapseforge-0.1.24.dev2.dist-info/top_level.txt,sha256=U8Du3csIq81izghzQDWFFT1Q9nhUNEAGiPA-8BBL-r0,22
29
+ synapseforge-0.1.24.dev2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ synapseForge = synapseforge.cli.main:main