g360-cli 1.15.1 → 1.16.0

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.
@@ -1,105 +1,171 @@
1
1
  import json
2
2
  from pathlib import Path
3
3
 
4
- ACCENT = "#10B981"
5
- ACCENT_DARK = "#047857"
6
-
7
- DARK_COLORS = {
8
- "accent": "#10B981",
9
- "accent_dark": "#047857",
10
- "success": "#34D399",
11
- "warning": "#F59E0B",
12
- "error": "#EF4444",
13
- "info": "#3B82F6",
14
- "violet": "#8B5CF6",
15
- "pink": "#EC4899",
16
- "cyan": "#06B6D4",
17
- "orange": "#F97316",
18
- "kpis": {
19
- "primary": "#06B6D4",
20
- "secondary": "#8B5CF6",
21
- "tertiary": "#0EA5E9",
22
- "quaternary": "#6366F1",
23
- "quinary": "#EC4899",
24
- "senary": "#F59E0B",
25
- "septenary": "#EF4444",
26
- "octonary": "#F97316",
4
+ # Colores G360 estandar como referencia
5
+ G360_DEFAULTS = {
6
+ "dark": {
7
+ "accent": "#10B981",
8
+ "accent_dark": "#047857",
9
+ "success": "#34D399",
10
+ "warning": "#F59E0B",
11
+ "error": "#EF4444",
12
+ "info": "#3B82F6",
13
+ "violet": "#8B5CF6",
14
+ "pink": "#EC4899",
15
+ "cyan": "#06B6D4",
16
+ "orange": "#F97316",
17
+ "surface": "#141D33",
18
+ "surface_variant": "#1B2740",
19
+ "surface_sunken": "#0E1627",
20
+ "background": "#0A0F1E",
21
+ "border": "#FFFFFF17",
22
+ "text_muted": "#8FA0BA",
23
+ "text_primary": "#F1F5FB",
24
+ "text_secondary": "#C9D4E6",
25
+ "kpis": {
26
+ "primary": "#06B6D4",
27
+ "secondary": "#8B5CF6",
28
+ "tertiary": "#0EA5E9",
29
+ "quaternary": "#6366F1",
30
+ "quinary": "#EC4899",
31
+ "senary": "#F59E0B",
32
+ "septenary": "#EF4444",
33
+ "octonary": "#F97316",
34
+ },
27
35
  },
28
- "surface": "#141D33",
29
- "surface_variant": "#1B2740",
30
- "surface_sunken": "#0E1627",
31
- "background": "#0A0F1E",
32
- "border": "#FFFFFF17",
33
- "text_muted": "#8FA0BA",
34
- "text_primary": "#F1F5FB",
35
- "text_secondary": "#C9D4E6",
36
- }
37
-
38
- LIGHT_COLORS = {
39
- "accent": "#047857",
40
- "accent_dark": "#065F46",
41
- "success": "#15803D",
42
- "warning": "#B45309",
43
- "error": "#DC2626",
44
- "info": "#2563EB",
45
- "violet": "#7C3AED",
46
- "pink": "#DB2777",
47
- "cyan": "#0891B2",
48
- "orange": "#EA580C",
49
- "kpis": {
50
- "primary": "#0891B2",
51
- "secondary": "#7C3AED",
52
- "tertiary": "#0284C7",
53
- "quaternary": "#4F46E5",
54
- "quinary": "#DB2777",
55
- "senary": "#B45309",
56
- "septenary": "#DC2626",
57
- "octonary": "#EA580C",
36
+ "light": {
37
+ "accent": "#047857",
38
+ "accent_dark": "#065F46",
39
+ "success": "#15803D",
40
+ "warning": "#B45309",
41
+ "error": "#DC2626",
42
+ "info": "#2563EB",
43
+ "violet": "#7C3AED",
44
+ "pink": "#DB2777",
45
+ "cyan": "#0891B2",
46
+ "orange": "#EA580C",
47
+ "surface": "#FFFFFF",
48
+ "surface_variant": "#F7F9FC",
49
+ "surface_sunken": "#EEF1F6",
50
+ "background": "#F3F5F9",
51
+ "border": "#E3E8F0",
52
+ "text_muted": "#64748B",
53
+ "text_primary": "#0F172A",
54
+ "text_secondary": "#334155",
55
+ "kpis": {
56
+ "primary": "#0891B2",
57
+ "secondary": "#7C3AED",
58
+ "tertiary": "#0284C7",
59
+ "quaternary": "#4F46E5",
60
+ "quinary": "#DB2777",
61
+ "senary": "#B45309",
62
+ "septenary": "#DC2626",
63
+ "octonary": "#EA580C",
64
+ },
58
65
  },
59
- "surface": "#FFFFFF",
60
- "surface_variant": "#F7F9FC",
61
- "surface_sunken": "#EEF1F6",
62
- "background": "#F3F5F9",
63
- "border": "#E3E8F0",
64
- "text_muted": "#64748B",
65
- "text_primary": "#0F172A",
66
- "text_secondary": "#334155",
67
66
  }
68
67
 
69
68
 
70
- def get_colors(mode: str) -> dict:
71
- return dict(DARK_COLORS if mode == "dark" else LIGHT_COLORS)
69
+ def get_colors(mode: str = None, skill_path: Path = None) -> dict:
70
+ """
71
+ Carga la paleta de colores para el modo especificado.
72
+
73
+ Prioridad:
74
+ 1. skill.json del proyecto (si existe)
75
+ 2. Configuracion guardada en ~/.g360/
76
+ 3. Valores por defecto G360
77
+
78
+ Args:
79
+ mode: 'dark' o 'light'. Si es None, lee de la preferencia guardada.
80
+ skill_path: Ruta al skill.json. Si es None, busca automaticamente.
81
+
82
+ Returns:
83
+ dict con los colores aplicados
84
+ """
85
+ # Determinar modo
86
+ if mode is None:
87
+ mode = load_theme_preference()
88
+
89
+ # Cargar desde skill.json si existe
90
+ skill_colors = _load_skill_colors(skill_path)
91
+
92
+ # Combinar: defaults -> skill overrides
93
+ defaults = G360_DEFAULTS.get(mode, G360_DEFAULTS["dark"])
94
+
95
+ if skill_colors:
96
+ # Skill tiene colores propios, usarlos
97
+ colors = {**defaults, **skill_colors}
98
+ # Asegurar que kpis se fusionen correctamente
99
+ if "kpis" in skill_colors:
100
+ colors["kpis"] = {**defaults.get("kpis", {}), **skill_colors["kpis"]}
101
+ else:
102
+ colors = defaults.copy()
103
+ if "kpis" not in colors:
104
+ colors["kpis"] = defaults.get("kpis", {}).copy()
105
+
106
+ return colors
107
+
108
+
109
+ def _load_skill_colors(skill_path: Path = None) -> dict | None:
110
+ """Carga colores desde skill.json del proyecto."""
111
+ try:
112
+ if skill_path is None:
113
+ skill_path = _find_skill_json()
114
+
115
+ if skill_path and skill_path.exists():
116
+ with open(skill_path, encoding="utf-8") as f:
117
+ data = json.load(f)
118
+ return data.get("colors", {})
119
+ except Exception:
120
+ pass
121
+ return None
122
+
123
+
124
+ def _find_skill_json() -> Path | None:
125
+ """Busca skill.json en las ubicaciones posibles."""
126
+ candidates = [
127
+ Path("skill.json"),
128
+ Path("src/core/skill.json"),
129
+ Path(__file__).resolve().parent.parent.parent / "skill.json",
130
+ ]
131
+ for candidate in candidates:
132
+ if candidate.exists():
133
+ return candidate
134
+ return None
72
135
 
73
136
 
74
137
  def rgba(color: str, opacity: float) -> str:
138
+ """Convierte un color hex a rgba con la opacidad especificada."""
75
139
  if color.startswith("#") and len(color) == 9:
76
- return color
140
+ return color # Ya tiene alpha
77
141
  if color.startswith("#") and len(color) == 7:
78
142
  hex_color = color[1:]
79
143
  alpha = int(max(0.0, min(1.0, opacity)) * 255)
80
144
  return f"#{alpha:02x}{hex_color}"
81
- return color
145
+ return color # Retornar como esta si no es hex valido
82
146
 
83
147
 
84
148
  def get_theme_file() -> Path:
85
- return Path.home() / ".g360" / f"{_get_app_name()}_config.json"
149
+ """Retorna la ruta del archivo de preferencia de tema."""
150
+ app_name = _get_app_name()
151
+ return Path.home() / ".g360" / f"{app_name}_config.json"
86
152
 
87
153
 
88
154
  def _get_app_name() -> str:
155
+ """Obtiene el nombre de la app desde skill.json o el nombre del directorio."""
89
156
  try:
90
- from pathlib import Path as _P
91
- _root = _P(__file__).resolve().parent.parent.parent.parent
92
- _skill = _root / "skill.json"
93
- if _skill.exists():
94
- with open(_skill, encoding="utf-8") as _f:
95
- _data = json.load(_f)
96
- return _data.get("name", "g360-app").replace("-", "_")
157
+ skill_path = _find_skill_json()
158
+ if skill_path and skill_path.exists():
159
+ with open(skill_path, encoding="utf-8") as f:
160
+ data = json.load(f)
161
+ return data.get("name", Path(__file__).resolve().parent.parent.parent.name)
97
162
  except Exception:
98
163
  pass
99
- return "g360_app"
164
+ return Path(__file__).resolve().parent.parent.parent.name
100
165
 
101
166
 
102
167
  def load_theme_preference() -> str:
168
+ """Carga la preferencia de tema guardada (dark/light)."""
103
169
  try:
104
170
  config_file = get_theme_file()
105
171
  if config_file.exists():
@@ -112,6 +178,7 @@ def load_theme_preference() -> str:
112
178
 
113
179
 
114
180
  def save_theme_preference(mode: str):
181
+ """Guarda la preferencia de tema."""
115
182
  try:
116
183
  config_file = get_theme_file()
117
184
  config_file.parent.mkdir(parents=True, exist_ok=True)
@@ -120,3 +187,15 @@ def save_theme_preference(mode: str):
120
187
  json.dump(config, f)
121
188
  except Exception:
122
189
  pass
190
+
191
+
192
+ def set_brand_colors(colors: dict):
193
+ """
194
+ Permite sobrescribir los colores de la marca en runtime.
195
+ Useful para apps que necesitan colores dinamicos.
196
+ """
197
+ global G360_DEFAULTS
198
+ # Actualizar solo los modos que existan
199
+ for mode in ["dark", "light"]:
200
+ if mode in G360_DEFAULTS and mode in colors:
201
+ G360_DEFAULTS[mode].update(colors[mode])