sipecom-soporte 0.5.4__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 (25) hide show
  1. sipecom_soporte-0.5.4/LICENSE +21 -0
  2. sipecom_soporte-0.5.4/PKG-INFO +116 -0
  3. sipecom_soporte-0.5.4/README.md +81 -0
  4. sipecom_soporte-0.5.4/pyproject.toml +36 -0
  5. sipecom_soporte-0.5.4/pyproject.toml.orig +33 -0
  6. sipecom_soporte-0.5.4/src/soporte_sipecom/__init__.py +5 -0
  7. sipecom_soporte-0.5.4/src/soporte_sipecom/banner.py +20 -0
  8. sipecom_soporte-0.5.4/src/soporte_sipecom/cli.py +229 -0
  9. sipecom_soporte-0.5.4/src/soporte_sipecom/config.py +38 -0
  10. sipecom_soporte-0.5.4/src/soporte_sipecom/constants.py +6 -0
  11. sipecom_soporte-0.5.4/src/soporte_sipecom/conversations.py +83 -0
  12. sipecom_soporte-0.5.4/src/soporte_sipecom/detect.py +370 -0
  13. sipecom_soporte-0.5.4/src/soporte_sipecom/engine.py +211 -0
  14. sipecom_soporte-0.5.4/src/soporte_sipecom/ingest.py +153 -0
  15. sipecom_soporte-0.5.4/src/soporte_sipecom/install_tools.py +136 -0
  16. sipecom_soporte-0.5.4/src/soporte_sipecom/maps.py +239 -0
  17. sipecom_soporte-0.5.4/src/soporte_sipecom/models.py +229 -0
  18. sipecom_soporte-0.5.4/src/soporte_sipecom/onboard.py +150 -0
  19. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/.streamlit/config.toml +18 -0
  20. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/__init__.py +1 -0
  21. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/app.py +504 -0
  22. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipecom-logo.jpg +0 -0
  23. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipecom-logo.png +0 -0
  24. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipi-colibri.png +0 -0
  25. sipecom_soporte-0.5.4/src/soporte_sipecom/ui/styles.css +137 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SIPECOM / kevin21sipecom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.3
2
+ Name: sipecom-soporte
3
+ Version: 0.5.4
4
+ Summary: SIPECOM-SOPORTE: CLI global y dashboard local (127.0.0.1:2121).
5
+ Author: kevin21sipecom
6
+ Author-email: kevin21sipecom <kfernandez@sipecom.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 SIPECOM / kevin21sipecom
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ Requires-Dist: pyfiglet>=1.0
29
+ Requires-Dist: streamlit>=1.36
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Python: >=3.11
32
+ Project-URL: Homepage, https://github.com/kevin21sipecom/soporte-sipecom
33
+ Project-URL: Documentation, https://github.com/kevin21sipecom/soporte-sipecom/blob/main/docs/README.md
34
+ Description-Content-Type: text/markdown
35
+
36
+ # SIPECOM-SOPORTE
37
+
38
+ CLI global y consola local para consultar proyectos (CodeGraph + Repomix) con **grok**, **antigravity** o **codex**.
39
+
40
+ La UI es **localhost** (`127.0.0.1:2121`). Sin túnel.
41
+
42
+ **Plataforma:** de momento solo **Windows**. No está validado en Linux ni macOS.
43
+
44
+ ![Consola SIPECOM-SOPORTE](docs/consola-ui.png)
45
+
46
+ ## Instalar uv
47
+
48
+ Windows (PowerShell):
49
+
50
+ ```text
51
+ powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
52
+ ```
53
+
54
+ Cierra y abre la terminal, luego `uv --version`.
55
+
56
+ ## Instalar en toda la PC
57
+
58
+ No hace falta `cd` al repo. Desde PyPI:
59
+
60
+ ```text
61
+ uv tool install sipecom-soporte
62
+ uv tool update-shell
63
+ sipecom-soporte
64
+ sipecom-soporte dashboard
65
+ ```
66
+
67
+ Desde git (misma versión, sin PyPI):
68
+
69
+ ```text
70
+ uv tool install git+https://github.com/kevin21sipecom/soporte-sipecom.git
71
+ ```
72
+
73
+ Actualizar:
74
+
75
+ ```text
76
+ uv tool upgrade sipecom-soporte
77
+ ```
78
+
79
+ Desarrollo local (opcional): `git clone` + `uv sync` + `uv run sipecom-soporte`.
80
+
81
+ ## Onboarding
82
+
83
+ `sipecom-soporte` (sin argumentos) comprueba:
84
+
85
+ 1. **node** y **npm** (Repomix y Archify los necesitan)
86
+ 2. **CodeGraph**, **Repomix**, **Archify**
87
+ 3. Agentes: grok / antigravity (`agy`) / codex
88
+
89
+ Si falta CodeGraph, Repomix o Archify, pregunta s/n y los instala en el mismo onboarding. Cuando todo está OK:
90
+
91
+ ```text
92
+ sipecom-soporte dashboard
93
+ ```
94
+
95
+ Las CLIs las elige el **dashboard**. No hace falta `select --use`.
96
+
97
+ ## Dashboard
98
+
99
+ - Motor = detector de CLIs de esta PC.
100
+ - Modelos = listado vivo de esa CLI.
101
+ - Proyecto: CodeGraph + pack Repomix (el pack no se vuelca al prompt).
102
+ - Imágenes: viven en **esta conversación**; puedes subir otra y se recuerdan las anteriores. «Nueva conversación» limpia el hilo.
103
+
104
+ ## Comandos
105
+
106
+ | Comando | Descripción |
107
+ | --- | --- |
108
+ | `sipecom-soporte` | Onboarding |
109
+ | `sipecom-soporte doctor` | Mismas 4 familias, formato corto |
110
+ | `sipecom-soporte models` | Modelos de cada CLI |
111
+ | `sipecom-soporte dashboard` | Consola en localhost:2121 |
112
+ | `sipecom-soporte ui` | Alias de dashboard |
113
+
114
+ ## Documentación
115
+
116
+ [docs/README.md](docs/README.md)
@@ -0,0 +1,81 @@
1
+ # SIPECOM-SOPORTE
2
+
3
+ CLI global y consola local para consultar proyectos (CodeGraph + Repomix) con **grok**, **antigravity** o **codex**.
4
+
5
+ La UI es **localhost** (`127.0.0.1:2121`). Sin túnel.
6
+
7
+ **Plataforma:** de momento solo **Windows**. No está validado en Linux ni macOS.
8
+
9
+ ![Consola SIPECOM-SOPORTE](docs/consola-ui.png)
10
+
11
+ ## Instalar uv
12
+
13
+ Windows (PowerShell):
14
+
15
+ ```text
16
+ powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
17
+ ```
18
+
19
+ Cierra y abre la terminal, luego `uv --version`.
20
+
21
+ ## Instalar en toda la PC
22
+
23
+ No hace falta `cd` al repo. Desde PyPI:
24
+
25
+ ```text
26
+ uv tool install sipecom-soporte
27
+ uv tool update-shell
28
+ sipecom-soporte
29
+ sipecom-soporte dashboard
30
+ ```
31
+
32
+ Desde git (misma versión, sin PyPI):
33
+
34
+ ```text
35
+ uv tool install git+https://github.com/kevin21sipecom/soporte-sipecom.git
36
+ ```
37
+
38
+ Actualizar:
39
+
40
+ ```text
41
+ uv tool upgrade sipecom-soporte
42
+ ```
43
+
44
+ Desarrollo local (opcional): `git clone` + `uv sync` + `uv run sipecom-soporte`.
45
+
46
+ ## Onboarding
47
+
48
+ `sipecom-soporte` (sin argumentos) comprueba:
49
+
50
+ 1. **node** y **npm** (Repomix y Archify los necesitan)
51
+ 2. **CodeGraph**, **Repomix**, **Archify**
52
+ 3. Agentes: grok / antigravity (`agy`) / codex
53
+
54
+ Si falta CodeGraph, Repomix o Archify, pregunta s/n y los instala en el mismo onboarding. Cuando todo está OK:
55
+
56
+ ```text
57
+ sipecom-soporte dashboard
58
+ ```
59
+
60
+ Las CLIs las elige el **dashboard**. No hace falta `select --use`.
61
+
62
+ ## Dashboard
63
+
64
+ - Motor = detector de CLIs de esta PC.
65
+ - Modelos = listado vivo de esa CLI.
66
+ - Proyecto: CodeGraph + pack Repomix (el pack no se vuelca al prompt).
67
+ - Imágenes: viven en **esta conversación**; puedes subir otra y se recuerdan las anteriores. «Nueva conversación» limpia el hilo.
68
+
69
+ ## Comandos
70
+
71
+ | Comando | Descripción |
72
+ | --- | --- |
73
+ | `sipecom-soporte` | Onboarding |
74
+ | `sipecom-soporte doctor` | Mismas 4 familias, formato corto |
75
+ | `sipecom-soporte models` | Modelos de cada CLI |
76
+ | `sipecom-soporte dashboard` | Consola en localhost:2121 |
77
+ | `sipecom-soporte ui` | Alias de dashboard |
78
+
79
+ ## Documentación
80
+
81
+ [docs/README.md](docs/README.md)
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "sipecom-soporte"
3
+ version = "0.5.4"
4
+ description = "SIPECOM-SOPORTE: CLI global y dashboard local (127.0.0.1:2121)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "pyfiglet>=1.0",
9
+ "streamlit>=1.36",
10
+ "pyyaml>=6.0",
11
+ ]
12
+
13
+ [[project.authors]]
14
+ name = "kevin21sipecom"
15
+ email = "kfernandez@sipecom.com"
16
+
17
+ [project.license]
18
+ file = "LICENSE"
19
+
20
+ [project.scripts]
21
+ sipecom-soporte = "soporte_sipecom.cli:main"
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/kevin21sipecom/soporte-sipecom"
25
+ Documentation = "https://github.com/kevin21sipecom/soporte-sipecom/blob/main/docs/README.md"
26
+
27
+ [tool.uv.build-backend]
28
+ module-name = "soporte_sipecom"
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+ pythonpath = ["src"]
33
+
34
+ [build-system]
35
+ requires = ["uv_build>=0.12.9,<0.13.0"]
36
+ build-backend = "uv_build"
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "sipecom-soporte"
3
+ version = "0.5.4"
4
+ description = "SIPECOM-SOPORTE: CLI global y dashboard local (127.0.0.1:2121)."
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "kevin21sipecom", email = "kfernandez@sipecom.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ license = { file = "LICENSE" }
11
+ dependencies = [
12
+ "pyfiglet>=1.0",
13
+ "streamlit>=1.36",
14
+ "pyyaml>=6.0",
15
+ ]
16
+
17
+ [project.scripts]
18
+ sipecom-soporte = "soporte_sipecom.cli:main"
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/kevin21sipecom/soporte-sipecom"
22
+ Documentation = "https://github.com/kevin21sipecom/soporte-sipecom/blob/main/docs/README.md"
23
+
24
+ [tool.uv.build-backend]
25
+ module-name = "soporte_sipecom"
26
+
27
+ [build-system]
28
+ requires = ["uv_build>=0.12.9,<0.13.0"]
29
+ build-backend = "uv_build"
30
+
31
+ [tool.pytest.ini_options]
32
+ testpaths = ["tests"]
33
+ pythonpath = ["src"]
@@ -0,0 +1,5 @@
1
+ """SIPECOM-SOPORTE."""
2
+
3
+ from soporte_sipecom.cli import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,20 @@
1
+ """Banner típico de CLI."""
2
+ from __future__ import annotations
3
+
4
+ import pyfiglet
5
+
6
+ TITLE = "SIPECOM"
7
+ SUB = "SOPORTE"
8
+ PORT = 2121
9
+
10
+
11
+ def banner(port: int = PORT) -> str:
12
+ top = pyfiglet.figlet_format(TITLE, font="slant", width=80).rstrip()
13
+ bottom = pyfiglet.figlet_format(SUB, font="slant", width=80).rstrip()
14
+ line = "-" * 64
15
+ return (
16
+ f"{top}\n{bottom}\n"
17
+ f"{line}\n"
18
+ f" SIPECOM-SOPORTE · puerto {port} · uv run soporte\n"
19
+ f"{line}"
20
+ )
@@ -0,0 +1,229 @@
1
+ """CLI principal SIPECOM-SOPORTE."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from soporte_sipecom.banner import banner
11
+ from soporte_sipecom.config import DEFAULT_PORT, config_path, load, save
12
+ from soporte_sipecom.constants import VALID_AGENTS
13
+ from soporte_sipecom.detect import doctor, probe_agent
14
+ from soporte_sipecom.models import list_models
15
+ from soporte_sipecom.onboard import detected_agents, print_onboard
16
+
17
+
18
+ def _print_doctor(probes, *, as_json: bool) -> int:
19
+ if as_json:
20
+ print(json.dumps([p.as_dict() for p in probes], indent=2, ensure_ascii=False))
21
+ return 0 if all(p.ok for p in probes) else 1
22
+ width = max(len(p.name) for p in probes)
23
+ print()
24
+ for probe in probes:
25
+ mark = "OK " if probe.ok else "NO "
26
+ loc = probe.path or "—"
27
+ ver = probe.version or ""
28
+ print(f" {mark} {probe.name:<{width}} {ver:<18} {loc}")
29
+ if probe.detail and not probe.ok:
30
+ print(f" {probe.detail}")
31
+ if probe.name == "agentes":
32
+ for member in probe.extra.get("members") or []:
33
+ mm = "ok" if member.get("ok") else "no"
34
+ print(
35
+ f" [{mm}] {member.get('name')}: "
36
+ f"{member.get('version') or ''} {member.get('path') or '—'}"
37
+ )
38
+ if member.get("detail") and not member.get("ok"):
39
+ print(f" {member['detail']}")
40
+ print()
41
+ ok = all(p.ok for p in probes)
42
+ print("4/4 listas" if ok else "faltan herramientas (4 familias: codegraph, repomix, archify, agentes)")
43
+ return 0 if ok else 1
44
+
45
+
46
+ def _usable_agents() -> list[dict]:
47
+ out = []
48
+ for name in VALID_AGENTS:
49
+ probe = probe_agent(name)
50
+ if probe.found:
51
+ out.append(probe.as_dict())
52
+ return out
53
+
54
+
55
+ def cmd_select(chosen: list[str] | None) -> int:
56
+ found = _usable_agents()
57
+ ok_names = [a["name"] for a in found if a.get("ok")]
58
+ if not found:
59
+ print("No hay agentes CLI detectados (grok, antigravity, codex).")
60
+ return 1
61
+ print("Agentes detectados:")
62
+ for i, item in enumerate(found, 1):
63
+ flag = "OK" if item.get("ok") else "NO"
64
+ print(f" {i}. [{flag}] {item['name']:8} {item.get('version') or ''} {item.get('path') or ''}")
65
+ selected: list[str]
66
+ if chosen:
67
+ selected = []
68
+ for name in chosen:
69
+ name = name.strip().lower()
70
+ if name == "agy":
71
+ name = "antigravity"
72
+ if name not in VALID_AGENTS:
73
+ print(f"CLI no válida: {name}. Usa: grok, antigravity, codex")
74
+ return 1
75
+ if name not in {a["name"] for a in found}:
76
+ print(f"no detectado: {name}")
77
+ return 1
78
+ selected.append(name)
79
+ elif sys.stdin.isatty() and sys.stdout.isatty():
80
+ raw = input("\nNúmeros o nombres (ej. 1,2 o grok,codex). Vacío = todos los OK: ").strip()
81
+ if not raw:
82
+ selected = ok_names
83
+ else:
84
+ selected = []
85
+ tokens = [t.strip().lower() for t in raw.replace(";", ",").split(",") if t.strip()]
86
+ for tok in tokens:
87
+ if tok.isdigit():
88
+ idx = int(tok) - 1
89
+ if idx < 0 or idx >= len(found):
90
+ print(f"índice inválido: {tok}")
91
+ return 1
92
+ selected.append(found[idx]["name"])
93
+ else:
94
+ if tok == "agy":
95
+ tok = "antigravity"
96
+ if tok not in VALID_AGENTS:
97
+ print(f"CLI no válida: {tok}. Usa: grok, antigravity, codex")
98
+ return 1
99
+ if tok not in {a["name"] for a in found}:
100
+ print(f"no detectado: {tok}")
101
+ return 1
102
+ selected.append(tok)
103
+ else:
104
+ selected = ok_names
105
+ print(f"(sin TTY) usando todos los OK: {', '.join(selected) or '—'}")
106
+ cfg = load()
107
+ cfg["agents"] = selected
108
+ cfg["port"] = cfg.get("port") or DEFAULT_PORT
109
+ path = save(cfg)
110
+ print(f"guardado: {path}")
111
+ print(f"agentes: {', '.join(selected) or '—'}")
112
+ print(f"puerto: {cfg['port']}")
113
+ return 0
114
+
115
+
116
+ def launch_dashboard(port: int) -> int:
117
+ cfg = load()
118
+ found = detected_agents()
119
+ if found:
120
+ cfg["agents"] = found
121
+ save(cfg)
122
+ app = Path(__file__).resolve().parent / "ui" / "app.py"
123
+ cmd = [
124
+ sys.executable,
125
+ "-m",
126
+ "streamlit",
127
+ "run",
128
+ str(app),
129
+ "--server.address",
130
+ "127.0.0.1",
131
+ "--server.port",
132
+ str(port),
133
+ "--server.headless",
134
+ "true",
135
+ "--browser.gatherUsageStats",
136
+ "false",
137
+ ]
138
+ print(banner(port))
139
+ print(f"Dashboard (solo localhost) → http://127.0.0.1:{port}")
140
+ return subprocess.call(cmd)
141
+
142
+
143
+ def main(argv: list[str] | None = None) -> int:
144
+ raw = list(argv) if argv is not None else sys.argv[1:]
145
+ raw = ["dashboard" if a == "dasboard" else a for a in raw]
146
+ parser = argparse.ArgumentParser(
147
+ prog="sipecom-soporte",
148
+ description="SIPECOM-SOPORTE — CLI de soporte y dashboard Streamlit (puerto 2121).",
149
+ )
150
+ parser.add_argument("--port", type=int, default=None, help="puerto (default 2121)")
151
+ sub = parser.add_subparsers(dest="cmd")
152
+
153
+ doc = sub.add_parser("doctor", help="Validar codegraph, repomix, archify y agentes")
154
+ doc.add_argument("--json", action="store_true")
155
+
156
+ sel = sub.add_parser("select", help="Opcional. El dashboard elige las CLIs.")
157
+ sel.add_argument(
158
+ "--use",
159
+ default="",
160
+ help="lista: grok,antigravity,codex (si se omite, pregunta o usa los OK)",
161
+ )
162
+
163
+ models_p = sub.add_parser("models", help="Listar modelos de grok, antigravity y codex")
164
+ models_p.add_argument("--refresh", action="store_true")
165
+ models_p.add_argument("--json", action="store_true")
166
+
167
+ sub.add_parser("onboard", help="Comprobar node/npm, CodeGraph, Repomix, Archify y agentes")
168
+ sub.add_parser("config", help="Mostrar config (puerto + agentes)")
169
+ sub.add_parser("dashboard", help="Abrir la consola Streamlit (esta UI)")
170
+ sub.add_parser("ui", help="Alias de dashboard")
171
+
172
+ args = parser.parse_args(raw)
173
+ port = args.port if args.port else load().get("port") or DEFAULT_PORT
174
+
175
+ if args.cmd is None or args.cmd == "onboard":
176
+ print(banner(port))
177
+ code = print_onboard()
178
+ print(f"\nDashboard: sipecom-soporte dashboard")
179
+ print(f"Puerto: {port} (solo localhost)")
180
+ return code
181
+
182
+ if args.cmd == "doctor":
183
+ print(banner(port))
184
+ return _print_doctor(doctor(), as_json=args.json)
185
+
186
+ if args.cmd == "select":
187
+ print(banner(port))
188
+ chosen = [x for x in args.use.split(",") if x.strip()] if args.use else None
189
+ return cmd_select(chosen)
190
+
191
+ if args.cmd == "models":
192
+ print(banner(port))
193
+ payload = {}
194
+ for name in VALID_AGENTS:
195
+ infos = list_models(name, refresh=args.refresh)
196
+ payload[name] = [m.as_dict() for m in infos]
197
+ if not args.json:
198
+ print(f"\n{name}")
199
+ if not infos:
200
+ print(" (sin modelos; CLI ausente o listado vacío)")
201
+ continue
202
+ for item in infos:
203
+ mark = "*" if item.default else "-"
204
+ extra = f" [{', '.join(item.efforts)}]" if item.efforts else ""
205
+ label = f" {item.label}" if item.label and item.label != item.id else ""
206
+ print(f" {mark} {item.id}{label}{extra}")
207
+ if args.json:
208
+ print(json.dumps(payload, indent=2, ensure_ascii=False))
209
+ return 0
210
+
211
+ if args.cmd == "config":
212
+ print(banner(port))
213
+ cfg = load()
214
+ if args.port:
215
+ cfg["port"] = args.port
216
+ save(cfg)
217
+ print(json.dumps(cfg, indent=2, ensure_ascii=False))
218
+ print(f"archivo: {config_path()}")
219
+ return 0
220
+
221
+ if args.cmd in {"dashboard", "ui"}:
222
+ return launch_dashboard(port)
223
+
224
+ parser.print_help()
225
+ return 2
226
+
227
+
228
+ if __name__ == "__main__":
229
+ raise SystemExit(main())
@@ -0,0 +1,38 @@
1
+ """Config local: agentes elegidos y puerto 2121."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+ DEFAULT_PORT = 2121
8
+
9
+
10
+ def config_path() -> Path:
11
+ return Path.home() / ".soporte-sipecom.json"
12
+
13
+
14
+ def load() -> dict:
15
+ path = config_path()
16
+ if not path.is_file():
17
+ return {"port": DEFAULT_PORT, "agents": []}
18
+ try:
19
+ data = json.loads(path.read_text(encoding="utf-8"))
20
+ except (OSError, json.JSONDecodeError):
21
+ return {"port": DEFAULT_PORT, "agents": []}
22
+ data.setdefault("port", DEFAULT_PORT)
23
+ data.setdefault("agents", [])
24
+ data.setdefault("last_agent", "")
25
+ data.setdefault("archify_home", "")
26
+ return data
27
+
28
+
29
+ def save(data: dict) -> Path:
30
+ path = config_path()
31
+ payload = {
32
+ "port": int(data.get("port") or DEFAULT_PORT),
33
+ "agents": list(data.get("agents") or []),
34
+ "last_agent": data.get("last_agent") or "",
35
+ "archify_home": data.get("archify_home") or "",
36
+ }
37
+ path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
38
+ return path
@@ -0,0 +1,6 @@
1
+ """Constantes de agentes. Sin I/O ni imports pesados (Streamlit recarga módulos)."""
2
+
3
+ VALID_AGENTS = ("grok", "antigravity", "codex")
4
+ AGENT_ALIASES = {
5
+ "antigravity": ("antigravity", "agy"),
6
+ }
@@ -0,0 +1,83 @@
1
+ """Hilos de chat persistentes (estilo lista Hermes)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import time
6
+ from pathlib import Path
7
+
8
+
9
+ def chats_root() -> Path:
10
+ path = Path.home() / ".soporte-sipecom" / "chats"
11
+ path.mkdir(parents=True, exist_ok=True)
12
+ return path
13
+
14
+
15
+ def index_path() -> Path:
16
+ return chats_root() / "index.json"
17
+
18
+
19
+ def load_index() -> list[dict]:
20
+ path = index_path()
21
+ if not path.is_file():
22
+ return []
23
+ try:
24
+ data = json.loads(path.read_text(encoding="utf-8"))
25
+ except (OSError, json.JSONDecodeError):
26
+ return []
27
+ items = data if isinstance(data, list) else []
28
+ seen: set[str] = set()
29
+ unique: list[dict] = []
30
+ for item in items:
31
+ if not isinstance(item, dict):
32
+ continue
33
+ cid = str(item.get("id") or "")
34
+ if not cid or cid in seen:
35
+ continue
36
+ seen.add(cid)
37
+ unique.append(item)
38
+ return unique
39
+
40
+
41
+ def save_index(items: list[dict]) -> None:
42
+ index_path().write_text(json.dumps(items, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
43
+
44
+
45
+ def thread_dir(cid: str) -> Path:
46
+ path = chats_root() / cid
47
+ path.mkdir(parents=True, exist_ok=True)
48
+ return path
49
+
50
+
51
+ def title_from_messages(messages: list[dict]) -> str:
52
+ for item in messages:
53
+ if item.get("role") == "user" and (item.get("content") or "").strip():
54
+ return " ".join(item["content"].split())[:72]
55
+ return "Nueva conversación"
56
+
57
+
58
+ def load_thread(cid: str) -> tuple[list[dict], list[str]]:
59
+ path = thread_dir(cid) / "thread.json"
60
+ if not path.is_file():
61
+ return [], []
62
+ try:
63
+ data = json.loads(path.read_text(encoding="utf-8"))
64
+ except (OSError, json.JSONDecodeError):
65
+ return [], []
66
+ return list(data.get("messages") or []), list(data.get("images") or [])
67
+
68
+
69
+ def save_thread(cid: str, messages: list[dict], images: list[str], project: str = "") -> None:
70
+ path = thread_dir(cid) / "thread.json"
71
+ payload = {"messages": messages, "images": images, "project": project}
72
+ path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
73
+ title = title_from_messages(messages)
74
+ items = [x for x in load_index() if x.get("id") != cid]
75
+ items.insert(
76
+ 0,
77
+ {"id": cid, "title": title, "project": project, "updated": time.time()},
78
+ )
79
+ save_index(items[:40])
80
+
81
+
82
+ def new_id() -> str:
83
+ return str(int(time.time() * 1000))