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.
- sipecom_soporte-0.5.4/LICENSE +21 -0
- sipecom_soporte-0.5.4/PKG-INFO +116 -0
- sipecom_soporte-0.5.4/README.md +81 -0
- sipecom_soporte-0.5.4/pyproject.toml +36 -0
- sipecom_soporte-0.5.4/pyproject.toml.orig +33 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/__init__.py +5 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/banner.py +20 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/cli.py +229 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/config.py +38 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/constants.py +6 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/conversations.py +83 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/detect.py +370 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/engine.py +211 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ingest.py +153 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/install_tools.py +136 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/maps.py +239 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/models.py +229 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/onboard.py +150 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/.streamlit/config.toml +18 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/__init__.py +1 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/app.py +504 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipecom-logo.jpg +0 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipecom-logo.png +0 -0
- sipecom_soporte-0.5.4/src/soporte_sipecom/ui/assets/sipi-colibri.png +0 -0
- 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
|
+

|
|
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
|
+

|
|
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,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,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))
|