opsiom-cli 1.1.2__tar.gz → 1.1.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.
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/PKG-INFO +1 -1
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/pyproject.toml +1 -1
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli/cli.py +61 -61
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/PKG-INFO +1 -1
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/README.md +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/setup.cfg +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli/__init__.py +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/SOURCES.txt +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/dependency_links.txt +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/entry_points.txt +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/requires.txt +0 -0
- {opsiom_cli-1.1.2 → opsiom_cli-1.1.4}/src/opsiom_cli.egg-info/top_level.txt +0 -0
|
@@ -39,7 +39,7 @@ try:
|
|
|
39
39
|
except ImportError:
|
|
40
40
|
readline = None # absent par défaut sur certains Windows (pyreadline3 comble le manque)
|
|
41
41
|
|
|
42
|
-
VERSION = "1.1.
|
|
42
|
+
VERSION = "1.1.4"
|
|
43
43
|
CONFIG_PATH = Path.home() / ".config" / "opsiom" / "config.json"
|
|
44
44
|
HISTORY_PATH = Path.home() / ".local" / "share" / "opsiom" / "history"
|
|
45
45
|
TRANSCRIPTS_DIR = Path.home() / "opsiom-conversations"
|
|
@@ -88,36 +88,36 @@ def ok_text(text):
|
|
|
88
88
|
# ----------------------------------------------------------------------------
|
|
89
89
|
|
|
90
90
|
OCTOPUS_MARK = [
|
|
91
|
-
" . +**=::::::::=**+.
|
|
92
|
-
" *#....::::::::::::::.:#*
|
|
93
|
-
" .=*:..::.::::::::::::::::::::*=.
|
|
94
|
-
" .=*:......::::::::::::::::::::::-*=.
|
|
95
|
-
" .+-.:...:::::::::::::::::::::::::::=*.
|
|
96
|
-
" -*.::::::::::::::::::::::::::::::::::-*=.
|
|
97
|
-
" #= =*.:...::::::::::::::::::::::::::::::::-*+
|
|
98
|
-
" .. -= .*.:::::::::::::::::::::::::::::::::::::--*.
|
|
99
|
-
" +: - *.:::::::::::::::::::::::::::::::::::::::--*
|
|
100
|
-
" :==-. :=.:::::::::::::::::::::::::::::::::::::::--+:
|
|
101
|
-
" .-:: +:::::::::::::::::::::::::::::::::::::::::---*
|
|
102
|
-
" * -- +:::::::::::::::::::::::::::::::::::::::::---+
|
|
103
|
-
" *::::::::::*## :+::::::::::::+: ##*::::::----*
|
|
104
|
-
" :::. :+:::::::::**###*-::::::::::-*###**::::::---+:
|
|
105
|
-
" ==...:++. *.::::::::===++=::::::::::::=++===:::::----*
|
|
106
|
-
" +::++::::=..*::::::-----:::::-*+::+*-:::::-----::----*
|
|
107
|
-
" .: :*=::+: :+::::::::::::::::::--:::::::::::::::---*
|
|
108
|
-
" .#+::+: .*:::::::::::::::::::::::::::::::::---*.
|
|
109
|
-
" -#**: *:+::+- .+::::::::::::::::::::::::::::::----+.
|
|
110
|
-
" -*-::.*-*=*:::=*::::=+-:::::::::::::::::::::::::::--+=:::-*+---*=*-*:::-*-
|
|
111
|
-
" :+=::::+#---::::-----+=::::::::::::::::::::::::::::++------------#+::::=+:
|
|
112
|
-
" -+=-::..=**=--+*--*+-::::::::::::::::::::::::::::::-+*--*+--=**=..::--+-
|
|
113
|
-
" ==*-::::..:-*#:::-+:::::-*::::::::::::::::::*-:::::+::::#*-:..::::-*==
|
|
114
|
-
" +-:+-:::::*-.=::-+:::--#:.:::::::-=:::::::::#--:::+:::=.-*:::::-+:-+
|
|
115
|
-
" +-:*---:+-.-::-*+==**:.:::::::-##-:::::::.:**==+*:::-.-+:---*:-+
|
|
116
|
-
" =*=:--+=:#::::::::.::::::::-===+-::::::::.::.:::::#:=+-=:-*=
|
|
117
|
-
" .--=*-..+::::::::::::::--+- -+--::::::::::::::+..-*=--.
|
|
118
|
-
" +:::+-:::::::::---=*- -#=---:::::::::-*:::+
|
|
119
|
-
" .#-::-+-------+**- -+*+-------+-::-#:
|
|
120
|
-
" **-::--::-*+ =*=::--::-**
|
|
91
|
+
" . +**=::::::::=**+. ",
|
|
92
|
+
" *#....::::::::::::::.:#* ",
|
|
93
|
+
" .=*:..::.::::::::::::::::::::*=. ",
|
|
94
|
+
" .=*:......::::::::::::::::::::::-*=. ",
|
|
95
|
+
" .+-.:...:::::::::::::::::::::::::::=*. ",
|
|
96
|
+
" -*.::::::::::::::::::::::::::::::::::-*=. ",
|
|
97
|
+
" #= =*.:...::::::::::::::::::::::::::::::::-*+ ",
|
|
98
|
+
" .. -= .*.:::::::::::::::::::::::::::::::::::::--*. ",
|
|
99
|
+
" +: - *.:::::::::::::::::::::::::::::::::::::::--* ",
|
|
100
|
+
" :==-. :=.:::::::::::::::::::::::::::::::::::::::--+: ",
|
|
101
|
+
" .-:: +:::::::::::::::::::::::::::::::::::::::::---* ",
|
|
102
|
+
" * -- +:::::::::::::::::::::::::::::::::::::::::---+ ",
|
|
103
|
+
" *::::::::::*## :+::::::::::::+: ##*::::::----* ",
|
|
104
|
+
" :::. :+:::::::::**###*-::::::::::-*###**::::::---+: ",
|
|
105
|
+
" ==...:++. *.::::::::===++=::::::::::::=++===:::::----* ",
|
|
106
|
+
" +::++::::=..*::::::-----:::::-*+::+*-:::::-----::----* ",
|
|
107
|
+
" .: :*=::+: :+::::::::::::::::::--:::::::::::::::---* ",
|
|
108
|
+
" .#+::+: .*:::::::::::::::::::::::::::::::::---*. ",
|
|
109
|
+
" -#**: *:+::+- .+::::::::::::::::::::::::::::::----+. ",
|
|
110
|
+
" -*-::.*-*=*:::=*::::=+-:::::::::::::::::::::::::::--+=:::-*+---*=*-*:::-*- ",
|
|
111
|
+
" :+=::::+#---::::-----+=::::::::::::::::::::::::::::++------------#+::::=+: ",
|
|
112
|
+
" -+=-::..=**=--+*--*+-::::::::::::::::::::::::::::::-+*--*+--=**=..::--+- ",
|
|
113
|
+
" ==*-::::..:-*#:::-+:::::-*::::::::::::::::::*-:::::+::::#*-:..::::-*== ",
|
|
114
|
+
" +-:+-:::::*-.=::-+:::--#:.:::::::-=:::::::::#--:::+:::=.-*:::::-+:-+ ",
|
|
115
|
+
" +-:*---:+-.-::-*+==**:.:::::::-##-:::::::.:**==+*:::-.-+:---*:-+ ",
|
|
116
|
+
" =*=:--+=:#::::::::.::::::::-===+-::::::::.::.:::::#:=+-=:-*= ",
|
|
117
|
+
" .--=*-..+::::::::::::::--+- -+--::::::::::::::+..-*=--. ",
|
|
118
|
+
" +:::+-:::::::::---=*- -#=---:::::::::-*:::+ ",
|
|
119
|
+
" .#-::-+-------+**- -+*+-------+-::-#: ",
|
|
120
|
+
" **-::--::-*+ =*=::--::-** ",
|
|
121
121
|
]
|
|
122
122
|
|
|
123
123
|
|
|
@@ -220,14 +220,14 @@ def save_transcript(transcript, filename=None):
|
|
|
220
220
|
if not filename.endswith(".md"):
|
|
221
221
|
filename += ".md"
|
|
222
222
|
path = TRANSCRIPTS_DIR / filename
|
|
223
|
-
lines = ["# Conversation Opsiom
|
|
223
|
+
lines = ["# Conversation Opsiom ", ""]
|
|
224
224
|
for entry in transcript:
|
|
225
225
|
if entry["role"] == "user":
|
|
226
226
|
lines.append(f"**Vous :** {entry['text']}")
|
|
227
227
|
else:
|
|
228
228
|
lines.append(f"**Opsiom ({entry['model']}) :** {entry['text']} _({entry['elapsed_ms']} ms)_")
|
|
229
229
|
lines.append("")
|
|
230
|
-
path.write_text("
|
|
230
|
+
path.write_text(" ".join(lines), encoding="utf-8")
|
|
231
231
|
return path
|
|
232
232
|
|
|
233
233
|
|
|
@@ -251,14 +251,14 @@ def save_config(config):
|
|
|
251
251
|
|
|
252
252
|
def prompt_for_config():
|
|
253
253
|
print(f" {accent('Configuration initiale', bold=True)}")
|
|
254
|
-
print(f" {muted('ces informations te sont transmises par la personne qui héberge Opsiom')}
|
|
254
|
+
print(f" {muted('ces informations te sont transmises par la personne qui héberge Opsiom')} ")
|
|
255
255
|
url = input(" URL du serveur : ").strip().rstrip("/")
|
|
256
256
|
api_key = input(" Clé API (vide si aucune) : ").strip()
|
|
257
257
|
save = input(muted(" Sauvegarder localement pour la prochaine fois ? [O/n] ")).strip().lower()
|
|
258
258
|
config = {"url": url, "api_key": api_key}
|
|
259
|
-
if save in ("
|
|
259
|
+
if save in (" ", "o ", "oui ", "y ", "yes"):
|
|
260
260
|
save_config(config)
|
|
261
|
-
print(muted(f" → enregistré dans {CONFIG_PATH}
|
|
261
|
+
print(muted(f" → enregistré dans {CONFIG_PATH} "))
|
|
262
262
|
return config
|
|
263
263
|
|
|
264
264
|
|
|
@@ -288,12 +288,12 @@ class OpsiomClient:
|
|
|
288
288
|
self.session.headers.update(headers)
|
|
289
289
|
|
|
290
290
|
def health(self):
|
|
291
|
-
r = self.session.get(f"{self.base_url}/api/health
|
|
291
|
+
r = self.session.get(f"{self.base_url}/api/health ", timeout=10)
|
|
292
292
|
r.raise_for_status()
|
|
293
293
|
return r.json()
|
|
294
294
|
|
|
295
295
|
def list_models(self):
|
|
296
|
-
r = self.session.get(f"{self.base_url}/api/models
|
|
296
|
+
r = self.session.get(f"{self.base_url}/api/models ", timeout=10)
|
|
297
297
|
r.raise_for_status()
|
|
298
298
|
return r.json()
|
|
299
299
|
|
|
@@ -302,7 +302,7 @@ class OpsiomClient:
|
|
|
302
302
|
if model:
|
|
303
303
|
payload["model"] = model
|
|
304
304
|
payload.update(params)
|
|
305
|
-
r = self.session.post(f"{self.base_url}/api/chat
|
|
305
|
+
r = self.session.post(f"{self.base_url}/api/chat ", json=payload, timeout=self.timeout)
|
|
306
306
|
r.raise_for_status()
|
|
307
307
|
return r.json()
|
|
308
308
|
|
|
@@ -339,12 +339,12 @@ def print_error(text, detail=None):
|
|
|
339
339
|
|
|
340
340
|
def choose_default_model(models_payload):
|
|
341
341
|
default = models_payload.get("default")
|
|
342
|
-
models = models_payload.get("models
|
|
342
|
+
models = models_payload.get("models ", [])
|
|
343
343
|
return default or (models[0]["id"] if models else None)
|
|
344
344
|
|
|
345
345
|
|
|
346
346
|
def print_models(models_payload, current_model):
|
|
347
|
-
for i, m in enumerate(models_payload.get("models
|
|
347
|
+
for i, m in enumerate(models_payload.get("models ", []), start=1):
|
|
348
348
|
is_current = m["id"] == current_model
|
|
349
349
|
marker = accent("›") if is_current else " "
|
|
350
350
|
label = accent(m["label"], bold=True) if is_current else m["label"]
|
|
@@ -366,12 +366,12 @@ def resolve_model_choice(choice, models):
|
|
|
366
366
|
|
|
367
367
|
|
|
368
368
|
def select_model_interactively(models_payload, current_model):
|
|
369
|
-
models = models_payload.get("models
|
|
369
|
+
models = models_payload.get("models ", [])
|
|
370
370
|
if not models:
|
|
371
371
|
print_error("aucun modèle disponible côté serveur.")
|
|
372
372
|
return current_model
|
|
373
373
|
print_models(models_payload, current_model)
|
|
374
|
-
choice = input(f"
|
|
374
|
+
choice = input(f" {muted('Numéro (Entrée pour annuler) :')} ").strip()
|
|
375
375
|
if not choice:
|
|
376
376
|
return current_model
|
|
377
377
|
resolved = resolve_model_choice(choice, models)
|
|
@@ -386,7 +386,7 @@ def print_assistant_message(text, elapsed_ms):
|
|
|
386
386
|
width = max(term_width() - 4, 30)
|
|
387
387
|
wrapped = textwrap.fill(text, width=width)
|
|
388
388
|
print()
|
|
389
|
-
for line in wrapped.split("
|
|
389
|
+
for line in wrapped.split(" "):
|
|
390
390
|
print(f" {line}")
|
|
391
391
|
print(f" {muted(format_elapsed(elapsed_ms))}")
|
|
392
392
|
|
|
@@ -402,7 +402,7 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
402
402
|
try:
|
|
403
403
|
health = client.health()
|
|
404
404
|
connected = health.get("status") == "ok"
|
|
405
|
-
models_loaded = health.get("models_loaded
|
|
405
|
+
models_loaded = health.get("models_loaded ", [])
|
|
406
406
|
except requests.RequestException:
|
|
407
407
|
connected = False
|
|
408
408
|
|
|
@@ -413,7 +413,7 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
413
413
|
pass
|
|
414
414
|
|
|
415
415
|
print_header(url, connected, api_key, models_loaded, current_model)
|
|
416
|
-
print(f" {muted('Tapez votre message, ou /help pour la liste des commandes.')}
|
|
416
|
+
print(f" {muted('Tapez votre message, ou /help pour la liste des commandes.')} ")
|
|
417
417
|
|
|
418
418
|
while True:
|
|
419
419
|
try:
|
|
@@ -430,7 +430,7 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
430
430
|
arg = rest[0].strip() if rest else ""
|
|
431
431
|
cmd = cmd.lower()
|
|
432
432
|
|
|
433
|
-
if cmd in ("quit
|
|
433
|
+
if cmd in ("quit ", "exit"):
|
|
434
434
|
break
|
|
435
435
|
elif cmd == "help":
|
|
436
436
|
print(muted(HELP_TEXT))
|
|
@@ -450,12 +450,12 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
450
450
|
models_payload = client.list_models()
|
|
451
451
|
print_models(models_payload, current_model)
|
|
452
452
|
except requests.RequestException as e:
|
|
453
|
-
print_error("impossible de récupérer les modèles
|
|
453
|
+
print_error("impossible de récupérer les modèles ", str(e))
|
|
454
454
|
elif cmd == "model":
|
|
455
455
|
if not arg:
|
|
456
456
|
current_model = select_model_interactively(models_payload, current_model)
|
|
457
457
|
else:
|
|
458
|
-
resolved = resolve_model_choice(arg, models_payload.get("models
|
|
458
|
+
resolved = resolve_model_choice(arg, models_payload.get("models ", [])) or arg
|
|
459
459
|
current_model = resolved
|
|
460
460
|
print(f" {accent('→')} modèle actif : {accent(resolved, bold=True)}")
|
|
461
461
|
else:
|
|
@@ -463,7 +463,7 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
463
463
|
print()
|
|
464
464
|
continue
|
|
465
465
|
|
|
466
|
-
transcript.append({"role": "user
|
|
466
|
+
transcript.append({"role": "user ", "text": user_input})
|
|
467
467
|
|
|
468
468
|
spinner = Spinner(label="Réflexion")
|
|
469
469
|
spinner.start()
|
|
@@ -472,21 +472,21 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
472
472
|
result = client.chat(user_input, model=current_model)
|
|
473
473
|
except KeyboardInterrupt:
|
|
474
474
|
spinner.stop()
|
|
475
|
-
print(muted(" (requête annulée)
|
|
475
|
+
print(muted(" (requête annulée) "))
|
|
476
476
|
continue
|
|
477
477
|
except requests.RequestException as e:
|
|
478
478
|
spinner.stop()
|
|
479
|
-
print_error("la requête a
|
|
479
|
+
print_error("la requête a échoué ", str(e))
|
|
480
480
|
print()
|
|
481
481
|
continue
|
|
482
482
|
spinner.stop()
|
|
483
483
|
elapsed_ms = int((time.perf_counter() - start_time) * 1000)
|
|
484
484
|
|
|
485
|
-
response_text = result.get("response
|
|
486
|
-
used_model = result.get("model
|
|
485
|
+
response_text = result.get("response ", "(réponse vide)")
|
|
486
|
+
used_model = result.get("model ", current_model or "?")
|
|
487
487
|
print_assistant_message(response_text, elapsed_ms)
|
|
488
488
|
print()
|
|
489
|
-
transcript.append({"role": "assistant
|
|
489
|
+
transcript.append({"role": "assistant ", "text": response_text, "model": used_model, "elapsed_ms": elapsed_ms})
|
|
490
490
|
|
|
491
491
|
save_history()
|
|
492
492
|
|
|
@@ -496,12 +496,12 @@ def run_chat(client: OpsiomClient, url, api_key):
|
|
|
496
496
|
# ----------------------------------------------------------------------------
|
|
497
497
|
|
|
498
498
|
def main():
|
|
499
|
-
parser = argparse.ArgumentParser(prog="opsiom
|
|
500
|
-
parser.add_argument("--url
|
|
501
|
-
parser.add_argument("--api-key
|
|
502
|
-
parser.add_argument("--configure
|
|
503
|
-
parser.add_argument("--no-logo
|
|
504
|
-
parser.add_argument("--version
|
|
499
|
+
parser = argparse.ArgumentParser(prog="opsiom ", description="CLI de chat pour Opsiom")
|
|
500
|
+
parser.add_argument("--url ", default=None, help="URL du serveur Opsiom (sinon config/prompt)")
|
|
501
|
+
parser.add_argument("--api-key ", default=None, help="clé API si le serveur en exige une")
|
|
502
|
+
parser.add_argument("--configure ", action="store_true ", help="reconfigure l'URL/clé et les réenregistre")
|
|
503
|
+
parser.add_argument("--no-logo ", action="store_true ", help="masque le repère ASCII au démarrage")
|
|
504
|
+
parser.add_argument("--version ", action="version ", version=f"opsiom-cli {VERSION}")
|
|
505
505
|
args = parser.parse_args()
|
|
506
506
|
|
|
507
507
|
url, api_key = resolve_settings(args)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|