@innvisor/conny-ai 9.8.0 → 9.8.4
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.
- package/CHANGELOG.md +24 -0
- package/conny_app.py +5 -3
- package/conny_cli.py +3 -0
- package/conny_i18n.py +81 -2
- package/conny_init.py +20 -0
- package/conny_studio.py +53 -11
- package/conny_tui.py +7 -0
- package/npm/conny.js +3 -5
- package/package.json +2 -2
- package/src/conny/channels/cli.py +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.8.4 - 2026-06-02
|
|
4
|
+
|
|
5
|
+
- made bare `conny` route to the modern onboarding/chat surface instead of the legacy help screen
|
|
6
|
+
- kept `conny init` untouched while preserving the same banner and branding in the post-onboarding chat UI
|
|
7
|
+
- aligned the legacy Python CLI fallback so direct `conny_cli.py` launches follow the same start behavior
|
|
8
|
+
|
|
9
|
+
## 9.8.3 - 2026-06-02
|
|
10
|
+
|
|
11
|
+
- made `conny` open the real chat interface after onboarding instead of the setup flow
|
|
12
|
+
- kept `conny init` unchanged and preserved its banner/design exactly
|
|
13
|
+
- added slash-command chat shortcuts with a Codex-style launcher header
|
|
14
|
+
|
|
15
|
+
## 9.8.2 - 2026-06-02
|
|
16
|
+
|
|
17
|
+
- persisted the language selected in `conny init` so the rest of the CLI loads it automatically
|
|
18
|
+
- made `conny_i18n` read the saved workspace language on startup
|
|
19
|
+
- kept the `conny init` banner unchanged while aligning other launch paths to the same brand
|
|
20
|
+
|
|
21
|
+
## 9.8.1 - 2026-06-02
|
|
22
|
+
|
|
23
|
+
- made `conny` open the guided setup flow by default instead of the old help banner
|
|
24
|
+
- removed the default launcher banner from the primary `conny` path
|
|
25
|
+
- aligned the TUI version display with `package.json`
|
|
26
|
+
|
|
3
27
|
## 9.8.0 - 2026-06-02
|
|
4
28
|
|
|
5
29
|
- blocked first-contact setup for unknown chats until a valid activation token is provided
|
package/conny_app.py
CHANGED
|
@@ -282,10 +282,12 @@ def _sh(*a):
|
|
|
282
282
|
|
|
283
283
|
def main():
|
|
284
284
|
signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
|
|
285
|
-
if first_run() and not (len(sys.argv)>1 and sys.argv[1] in ("help","--help","-h","-v","--version")):
|
|
286
|
-
onboard()
|
|
287
285
|
if len(sys.argv) <= 1:
|
|
288
|
-
|
|
286
|
+
if first_run():
|
|
287
|
+
onboard()
|
|
288
|
+
return
|
|
289
|
+
cmd_chat()
|
|
290
|
+
return
|
|
289
291
|
else:
|
|
290
292
|
route(sys.argv[1], " ".join(sys.argv[2:]))
|
|
291
293
|
|
package/conny_cli.py
CHANGED
|
@@ -11845,6 +11845,9 @@ def main():
|
|
|
11845
11845
|
if not workspace_is_configured() or not get_instances():
|
|
11846
11846
|
cmd_init(args)
|
|
11847
11847
|
return
|
|
11848
|
+
modern_entrypoint = Path(os.environ.get("CONNY_DIR", os.path.dirname(os.path.abspath(__file__)))) / "conny_app.py"
|
|
11849
|
+
subprocess.call([sys.executable, str(modern_entrypoint)])
|
|
11850
|
+
return
|
|
11848
11851
|
|
|
11849
11852
|
# Help
|
|
11850
11853
|
if args.help or cmd in ("help", "--help", "-h", ""):
|
package/conny_i18n.py
CHANGED
|
@@ -5,6 +5,9 @@ Supported languages: es, en, pt, fr, de
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
8
11
|
from typing import Dict, Optional
|
|
9
12
|
from dataclasses import dataclass
|
|
10
13
|
|
|
@@ -20,6 +23,79 @@ SUPPORTED_LANGUAGES = {
|
|
|
20
23
|
DEFAULT_LANGUAGE = "es"
|
|
21
24
|
|
|
22
25
|
|
|
26
|
+
def _workspace_config_path() -> Path:
|
|
27
|
+
explicit = os.environ.get("CONNY_WORKSPACE_CONFIG")
|
|
28
|
+
if explicit:
|
|
29
|
+
return Path(explicit)
|
|
30
|
+
conny_home = os.environ.get("CONNY_HOME")
|
|
31
|
+
if conny_home:
|
|
32
|
+
return Path(conny_home) / "config.json"
|
|
33
|
+
return Path.home() / ".conny" / "config.json"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _extract_language(payload: object) -> str:
|
|
37
|
+
if not isinstance(payload, dict):
|
|
38
|
+
return ""
|
|
39
|
+
candidates = [
|
|
40
|
+
payload.get("language"),
|
|
41
|
+
payload.get("ui_language"),
|
|
42
|
+
payload.get("locale"),
|
|
43
|
+
]
|
|
44
|
+
for nested_key in ("agent", "meta"):
|
|
45
|
+
nested = payload.get(nested_key)
|
|
46
|
+
if isinstance(nested, dict):
|
|
47
|
+
candidates.append(nested.get("language"))
|
|
48
|
+
candidates.append(nested.get("ui_language"))
|
|
49
|
+
candidates.append(nested.get("locale"))
|
|
50
|
+
for value in candidates:
|
|
51
|
+
if isinstance(value, str) and value in SUPPORTED_LANGUAGES:
|
|
52
|
+
return value
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _load_persisted_language() -> str:
|
|
57
|
+
for candidate in (
|
|
58
|
+
os.environ.get("CONNY_LANG"),
|
|
59
|
+
os.environ.get("CONNY_UI_LANG"),
|
|
60
|
+
os.environ.get("CONNY_INIT_LANG"),
|
|
61
|
+
):
|
|
62
|
+
if candidate in SUPPORTED_LANGUAGES:
|
|
63
|
+
return candidate
|
|
64
|
+
path = _workspace_config_path()
|
|
65
|
+
try:
|
|
66
|
+
if path.exists():
|
|
67
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
68
|
+
lang = _extract_language(payload)
|
|
69
|
+
if lang:
|
|
70
|
+
return lang
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
return DEFAULT_LANGUAGE
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _persist_language(lang: str) -> None:
|
|
77
|
+
if lang not in SUPPORTED_LANGUAGES:
|
|
78
|
+
return
|
|
79
|
+
os.environ["CONNY_LANG"] = lang
|
|
80
|
+
os.environ["CONNY_UI_LANG"] = lang
|
|
81
|
+
try:
|
|
82
|
+
path = _workspace_config_path()
|
|
83
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
payload = {}
|
|
85
|
+
if path.exists():
|
|
86
|
+
try:
|
|
87
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
88
|
+
if not isinstance(payload, dict):
|
|
89
|
+
payload = {}
|
|
90
|
+
except Exception:
|
|
91
|
+
payload = {}
|
|
92
|
+
payload["language"] = lang
|
|
93
|
+
payload["ui_language"] = lang
|
|
94
|
+
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
23
99
|
@dataclass
|
|
24
100
|
class TranslationSet:
|
|
25
101
|
ui: Dict[str, str]
|
|
@@ -523,7 +599,9 @@ _TRANSLATIONS: Dict[str, TranslationSet] = {
|
|
|
523
599
|
|
|
524
600
|
|
|
525
601
|
class I18n:
|
|
526
|
-
def __init__(self, lang: str =
|
|
602
|
+
def __init__(self, lang: str = None):
|
|
603
|
+
if lang is None:
|
|
604
|
+
lang = _load_persisted_language()
|
|
527
605
|
self.lang = lang if lang in _TRANSLATIONS else DEFAULT_LANGUAGE
|
|
528
606
|
self._cache: Dict[str, str] = {}
|
|
529
607
|
|
|
@@ -576,6 +654,7 @@ def get_i18n() -> I18n:
|
|
|
576
654
|
|
|
577
655
|
def set_language(lang: str) -> None:
|
|
578
656
|
_global_i18n.set_lang(lang)
|
|
657
|
+
_persist_language(lang)
|
|
579
658
|
|
|
580
659
|
|
|
581
660
|
def t(key: str, category: str = "ui") -> str:
|
|
@@ -616,4 +695,4 @@ LANGUAGE_MENU = {
|
|
|
616
695
|
"pt": "🇧🇷 Português",
|
|
617
696
|
"fr": "🇫🇷 Français",
|
|
618
697
|
"de": "🇩🇪 Deutsch",
|
|
619
|
-
}
|
|
698
|
+
}
|
package/conny_init.py
CHANGED
|
@@ -214,6 +214,25 @@ def validate_api_key(provider, key):
|
|
|
214
214
|
return True
|
|
215
215
|
|
|
216
216
|
|
|
217
|
+
def _persist_language(lang: str) -> None:
|
|
218
|
+
try:
|
|
219
|
+
workspace_config_path = Path(os.environ.get("CONNY_WORKSPACE_CONFIG", str(CONNY_HOME / "config.json")))
|
|
220
|
+
workspace_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
221
|
+
payload = {}
|
|
222
|
+
if workspace_config_path.exists():
|
|
223
|
+
try:
|
|
224
|
+
payload = json.loads(workspace_config_path.read_text(encoding="utf-8"))
|
|
225
|
+
if not isinstance(payload, dict):
|
|
226
|
+
payload = {}
|
|
227
|
+
except Exception:
|
|
228
|
+
payload = {}
|
|
229
|
+
payload["language"] = lang
|
|
230
|
+
payload["ui_language"] = lang
|
|
231
|
+
workspace_config_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
|
|
217
236
|
def _next_available_port() -> int:
|
|
218
237
|
existing_ports = []
|
|
219
238
|
if INSTANCES_DIR.exists():
|
|
@@ -379,6 +398,7 @@ def run_wizard():
|
|
|
379
398
|
lang = LANGUAGES[i][0]
|
|
380
399
|
global CURRENT_LANG
|
|
381
400
|
CURRENT_LANG = lang
|
|
401
|
+
_persist_language(lang)
|
|
382
402
|
|
|
383
403
|
# 1. Identity
|
|
384
404
|
clear()
|
package/conny_studio.py
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""conny_studio.py — Interactive CLI
|
|
2
|
+
"""conny_studio.py — Interactive CLI chat with live monitoring."""
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
import time
|
|
8
8
|
import uuid
|
|
9
|
+
import subprocess
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
14
|
+
from rich.console import Console
|
|
13
15
|
|
|
14
16
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
17
|
+
from conny_design import LOGO_FULL, SEP, ICON_BRAND
|
|
15
18
|
from conny_uncertainty import UncertaintyDetector
|
|
16
19
|
from conny_voice import ConnyVoice
|
|
17
20
|
|
|
21
|
+
CONSOLE = Console()
|
|
22
|
+
VERSION = "9.8.2"
|
|
23
|
+
try:
|
|
24
|
+
package_path = Path(__file__).resolve().parent / "package.json"
|
|
25
|
+
if package_path.exists():
|
|
26
|
+
VERSION = json.loads(package_path.read_text(encoding="utf-8")).get("version", VERSION)
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
18
30
|
STUDIO_DIR = Path.home() / ".conny" / "studio" / "memory"
|
|
19
31
|
API_URL = "http://localhost:8001/test"
|
|
20
32
|
|
|
@@ -69,11 +81,16 @@ class ConnyStudio:
|
|
|
69
81
|
}, ensure_ascii=False) + "\n")
|
|
70
82
|
|
|
71
83
|
async def handle_command(self, cmd: str) -> str:
|
|
84
|
+
if cmd in ("/help", "/menu", "/start"):
|
|
85
|
+
return (
|
|
86
|
+
"Comandos: /help /menu /clear /history /models /config /language "
|
|
87
|
+
"/export /reload-persona /fix-last"
|
|
88
|
+
)
|
|
72
89
|
if cmd == "/clear":
|
|
73
90
|
self.history = []
|
|
74
91
|
self.chat_id = f"studio_{uuid.uuid4().hex[:8]}"
|
|
75
92
|
return "Session cleared. New conversation started."
|
|
76
|
-
elif cmd
|
|
93
|
+
elif cmd in ("/history", "/show-memory"):
|
|
77
94
|
if not self.history:
|
|
78
95
|
return "No turns in memory yet."
|
|
79
96
|
lines = []
|
|
@@ -81,6 +98,14 @@ class ConnyStudio:
|
|
|
81
98
|
role = "YOU" if h["role"] == "user" else "MEL"
|
|
82
99
|
lines.append(f" [{role}] {h['content'][:80]}")
|
|
83
100
|
return "\n".join(lines)
|
|
101
|
+
elif cmd == "/models":
|
|
102
|
+
return self._run_cli_command("modelo")
|
|
103
|
+
elif cmd == "/config":
|
|
104
|
+
return self._run_cli_command("config")
|
|
105
|
+
elif cmd == "/language":
|
|
106
|
+
return self._run_cli_command("language")
|
|
107
|
+
elif cmd in ("/new", "/init"):
|
|
108
|
+
return self._run_cli_command("init")
|
|
84
109
|
elif cmd == "/show-failures":
|
|
85
110
|
if not self.failures_file.exists():
|
|
86
111
|
return "No failures detected this session."
|
|
@@ -101,13 +126,28 @@ class ConnyStudio:
|
|
|
101
126
|
return "No previous turn to fix."
|
|
102
127
|
return f"Unknown command: {cmd}"
|
|
103
128
|
|
|
129
|
+
def _run_cli_command(self, command: str) -> str:
|
|
130
|
+
cli = Path(__file__).resolve().parent / "conny_cli.py"
|
|
131
|
+
if not cli.exists():
|
|
132
|
+
return f"CLI no disponible para /{command}"
|
|
133
|
+
try:
|
|
134
|
+
print(f"\033[90m[system] launching: conny {command}\033[0m")
|
|
135
|
+
subprocess.run([sys.executable, str(cli), command], check=False)
|
|
136
|
+
return f"/{command} closed. Back in chat."
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
return f"No pude abrir /{command}: {exc}"
|
|
139
|
+
|
|
104
140
|
def print_header(self):
|
|
105
|
-
print(
|
|
106
|
-
print(
|
|
107
|
-
print(f"
|
|
108
|
-
print(
|
|
109
|
-
print("
|
|
110
|
-
print("
|
|
141
|
+
print()
|
|
142
|
+
CONSOLE.print(LOGO_FULL)
|
|
143
|
+
CONSOLE.print(f" {ICON_BRAND} v{VERSION} · chat real")
|
|
144
|
+
CONSOLE.print(SEP)
|
|
145
|
+
print(f" Instance: {self.instance_id}")
|
|
146
|
+
print(f" Session: {self.session_id}")
|
|
147
|
+
print(" Comandos: /help /menu /clear /history /models /config /language /export")
|
|
148
|
+
print(" Atajos: 1=models 2=config 3=language 4=help")
|
|
149
|
+
CONSOLE.print(SEP)
|
|
150
|
+
print()
|
|
111
151
|
|
|
112
152
|
def print_scores(self, scores):
|
|
113
153
|
conf = scores["confidence"]
|
|
@@ -122,14 +162,16 @@ class ConnyStudio:
|
|
|
122
162
|
self.print_header()
|
|
123
163
|
while True:
|
|
124
164
|
try:
|
|
125
|
-
user_input = input("\033[1;32m[YOU]\033[0m ")
|
|
165
|
+
user_input = input("\033[1;32m[YOU]\033[0m ").strip()
|
|
126
166
|
except (EOFError, KeyboardInterrupt):
|
|
127
167
|
print("\n\033[90mSession ended.\033[0m")
|
|
128
168
|
break
|
|
129
|
-
if not user_input
|
|
169
|
+
if not user_input:
|
|
130
170
|
continue
|
|
171
|
+
if user_input in ("1", "2", "3", "4"):
|
|
172
|
+
user_input = { "1": "/models", "2": "/config", "3": "/language", "4": "/help" }[user_input]
|
|
131
173
|
if user_input.startswith("/"):
|
|
132
|
-
result = await self.handle_command(user_input
|
|
174
|
+
result = await self.handle_command(user_input)
|
|
133
175
|
print(f"\033[1;33m[SYSTEM]\033[0m {result}")
|
|
134
176
|
continue
|
|
135
177
|
try:
|
package/conny_tui.py
CHANGED
|
@@ -30,6 +30,13 @@ CONNY_DIR = os.getenv("CONNY_DIR", str(Path(__file__).resolve().parent))
|
|
|
30
30
|
INSTANCES_DIR = os.getenv("INSTANCES_DIR", str(Path.home() / "conny-instances"))
|
|
31
31
|
CLI_SCRIPT = os.getenv("CONNY_CLI", str(Path(__file__).resolve().parent / "conny_cli.py"))
|
|
32
32
|
|
|
33
|
+
try:
|
|
34
|
+
_package_path = Path(CONNY_DIR) / "package.json"
|
|
35
|
+
if _package_path.exists():
|
|
36
|
+
VERSION = json.loads(_package_path.read_text(encoding="utf-8")).get("version", VERSION)
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
33
40
|
# ── Colores curses (índices de par) ─────────────────────────────────────────
|
|
34
41
|
CP_LOGO1 = 1 # Rosa — letras CONNY (gradiente línea 1)
|
|
35
42
|
CP_LOGO2 = 10 # Rosa claro (gradiente línea 2)
|
package/npm/conny.js
CHANGED
|
@@ -485,7 +485,7 @@ function execConny(argv) {
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
const args = process.argv.slice(2);
|
|
488
|
-
const isHelp = args.
|
|
488
|
+
const isHelp = args.includes("-h") || args.includes("--help") || args.includes("help");
|
|
489
489
|
const isVersion = args.includes("-v") || args.includes("--version") || args.includes("version");
|
|
490
490
|
const isBootstrapCheck = args.includes("--bootstrap-check");
|
|
491
491
|
const isJson = args.includes("--json");
|
|
@@ -518,10 +518,8 @@ if (isHelp) {
|
|
|
518
518
|
process.exit(0);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
|
|
522
|
-
printBanner();
|
|
523
|
-
}
|
|
521
|
+
const launchArgs = args;
|
|
524
522
|
|
|
525
|
-
if (!execConny(
|
|
523
|
+
if (!execConny(launchArgs)) {
|
|
526
524
|
fail(`No pude iniciar Conny desde ${connyHome}`);
|
|
527
525
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@innvisor/conny-ai",
|
|
3
|
-
"version": "9.8.
|
|
3
|
+
"version": "9.8.4",
|
|
4
4
|
"description": "Open-source CLI and runtime for building Conny AI receptionist agents on WhatsApp and Telegram.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Santiago Rubio",
|
|
@@ -109,4 +109,4 @@
|
|
|
109
109
|
"figlet": "^1.11.0",
|
|
110
110
|
"ora": "^5.4.1"
|
|
111
111
|
}
|
|
112
|
-
}
|
|
112
|
+
}
|
|
@@ -11785,6 +11785,9 @@ def main():
|
|
|
11785
11785
|
if not workspace_is_configured() or not get_instances():
|
|
11786
11786
|
cmd_init(args)
|
|
11787
11787
|
return
|
|
11788
|
+
modern_entrypoint = Path(os.environ.get("CONNY_DIR", os.path.dirname(os.path.abspath(__file__)))) / "conny_app.py"
|
|
11789
|
+
subprocess.call([sys.executable, str(modern_entrypoint)])
|
|
11790
|
+
return
|
|
11788
11791
|
|
|
11789
11792
|
# Help
|
|
11790
11793
|
if args.help or cmd in ("help", "--help", "-h", ""):
|