diff --git a/README.md b/README.md index ed52c0b..51d92e7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ AJEAN fournit tout ce qui entoure le modèle : l'interface de chat, les outils de l'assistant, la gestion du service et celle du matériel. Le moteur d'inférence est [llama.cpp](https://github.com/ggml-org/llama.cpp), qu'AJEAN compile lui-même pour la machine sur laquelle il tourne. ``` -télécharger le binaire → jean llamacpp install → jean edit → jean start → c'est parti +télécharger le binaire → ajean llamacpp install → ajean edit → ajean start → c'est parti ``` Aucune dépendance à l'exécution, aucun flag CMake à retenir, aucun conteneur. Vous obtenez une interface de chat complète et un endpoint compatible OpenAI pour vos outils tiers. @@ -18,7 +18,7 @@ Aucune dépendance à l'exécution, aucun flag CMake à retenir, aucun conteneur **Un assistant, pas seulement un modèle.** L'interface web offre un chat avec raisonnement affiché, mémoire persistante, compactage automatique du contexte quand la conversation s'allonge, prompt système éditable, et des réglages d'apparence synchronisés entre appareils. -**Des outils réels.** `jean agent on` active d'un seul coup toutes les capacités du modèle sur la machine : +**Des outils réels.** `ajean agent on` active d'un seul coup toutes les capacités du modèle sur la machine : | Outil | Rôle | |---|---| @@ -28,13 +28,13 @@ Aucune dépendance à l'exécution, aucun flag CMake à retenir, aucun conteneur | web_* | recherche et lecture de pages, via Crawl4AI | | mcp__* | les outils des serveurs MCP configurés | -**Le matériel et le moteur, gérés pour vous.** `jean llamacpp install` clone et compile llama.cpp avec les bons flags pour *cette* machine : CUDA (capacité de calcul détectée par GPU, donc le multi-GPU fonctionne), ROCm, Metal, Vulkan, ou repli CPU. `jean llamacpp update` récupère le dernier commit, arrête le service le temps de recompiler, puis le redémarre. +**Le matériel et le moteur, gérés pour vous.** `ajean llamacpp install` clone et compile llama.cpp avec les bons flags pour *cette* machine : CUDA (capacité de calcul détectée par GPU, donc le multi-GPU fonctionne), ROCm, Metal, Vulkan, ou repli CPU. `ajean llamacpp update` récupère le dernier commit, arrête le service le temps de recompiler, puis le redémarre. -**Un service, pas un script.** `jean install` écrit l'unité systemd, la règle sudoers et les dossiers. Ensuite `start` / `stop` / `status` / `logs`. Windows et macOS ont leurs équivalents natifs (voir plus bas). +**Un service, pas un script.** `ajean install` écrit l'unité systemd, la règle sudoers et les dossiers. Ensuite `start` / `stop` / `status` / `logs`. Windows et macOS ont leurs équivalents natifs (voir plus bas). **Plusieurs modèles, un clic.** Les presets gardent chacun leur configuration complète ; basculer de l'un à l'autre recharge le modèle sans toucher à un fichier. Les `.gguf` peuvent vivre sur n'importe quel disque. -**Accessible de partout.** `jean link` ouvre une connexion sortante vers [ajean.link](https://ajean.link), donc aucun port à ouvrir, et cela fonctionne même en CGNAT. Le chat est chiffré de bout en bout : le relais ne voit jamais les conversations. +**Accessible de partout.** `ajean link` ouvre une connexion sortante vers [ajean.link](https://ajean.link), donc aucun port à ouvrir, et cela fonctionne même en CGNAT. Le chat est chiffré de bout en bout : le relais ne voit jamais les conversations. ## Démarrage @@ -42,8 +42,8 @@ Aucune dépendance à l'exécution, aucun flag CMake à retenir, aucun conteneur ```bash curl -L -o jean https://github.com/nathaninline/ajean/releases/latest/download/jean-linux-amd64 -chmod +x jean -sudo mv jean /usr/local/bin/jean +chmod +x ajean +sudo mv ajean /usr/local/bin/ajean ``` (Autres cibles sur la page [Releases](../../releases) : Linux, macOS et Windows, en amd64 et arm64.) @@ -51,8 +51,8 @@ sudo mv jean /usr/local/bin/jean ### 2. Installation et compilation du moteur ```bash -sudo jean install # unité systemd, sudoers, dossiers -jean llamacpp install # compile llama.cpp pour le GPU présent +sudo ajean install # unité systemd, sudoers, dossiers +ajean llamacpp install # compile llama.cpp pour le GPU présent ``` Nécessite `git` et `cmake`, plus le toolkit de l'accélérateur (CUDA, ROCm…) pour l'accélération GPU. @@ -60,10 +60,10 @@ Nécessite `git` et `cmake`, plus le toolkit de l'accélérateur (CUDA, ROCm…) ### 3. Démarrage ```bash -jean edit # régler MODEL=/chemin/vers/le-modele.gguf -jean start -jean test # vérifier que le modèle répond -jean web # http://:8090 +ajean edit # régler MODEL=/chemin/vers/le-modele.gguf +ajean start +ajean test # vérifier que le modèle répond +ajean web # http://:8090 ``` ## Commandes @@ -78,7 +78,7 @@ Service : start | stop | restart gérer le service status | logs état / logs en direct enable | disable démarrage au boot - edit éditer $JEAN_HOME/config.env + edit éditer $AJEAN_HOME/config.env test | bench [N] vérifier que le modèle répond / mesurer les tok/s vram | gpu [index…] VRAM / choix des GPU (gpu all = tous) set-api-key [clé] protéger le moteur d'inférence (Bearer) @@ -107,7 +107,7 @@ Installation : ## Configuration -Tout vit sous **`$JEAN_HOME`** (`/etc/jean` sous Linux/macOS, `%ProgramData%\jean` sous Windows). Le service lit `config.env` : +Tout vit sous **`$AJEAN_HOME`** (`/etc/ajean` sous Linux/macOS, `%ProgramData%\ajean` sous Windows). Le service lit `config.env` : | Clé | Signification | Défaut | |-----|---------------|--------| @@ -119,7 +119,7 @@ Tout vit sous **`$JEAN_HOME`** (`/etc/jean` sous Linux/macOS, `%ProgramData%\jea | `BATCH` / `UBATCH` | batch / micro-batch | `2048` / `512` | | `THREADS` / `THREADS_BATCH` | threads CPU | `0` (auto) | | `KV_TYPE` (`_K` / `_V`) | quantization du cache KV | — | -| `CUDA_VISIBLE_DEVICES` | GPU utilisés (réglé par `jean gpu`) | tous | +| `CUDA_VISIBLE_DEVICES` | GPU utilisés (réglé par `ajean gpu`) | tous | | `REASONING` | passthrough du mode raisonnement | — | | `REASONING_BUDGET` | plafond de tokens de réflexion ; `-1` = illimité | `-1` | | `COMPACT` | compactage automatique du contexte (`off` pour couper) | activé | @@ -127,31 +127,31 @@ Tout vit sous **`$JEAN_HOME`** (`/etc/jean` sous Linux/macOS, `%ProgramData%\jea | `CRAWL4AI_URL` / `CRAWL4AI_KEY` | serveur d'accès internet | — | | `EXTRA_ARGS` | ajouté tel quel à la ligne de commande du moteur | — | -La clé API (`jean set-api-key`) vit dans `$JEAN_HOME/.api_key`, à part, afin de survivre aux changements de preset. +La clé API (`ajean set-api-key`) vit dans `$AJEAN_HOME/.api_key`, à part, afin de survivre aux changements de preset. -**Modèles sur un autre disque.** Les `.gguf` n'ont pas à résider dans `$JEAN_HOME` : dans l'éditeur de preset de l'interface, section *Modèle → Dossiers de modèles*, ajoutez le dossier voulu. Ses modèles apparaissent dans la liste, groupés par dossier. La liste est enregistrée dans `model_dirs.json`, donc conservée d'un preset à l'autre. +**Modèles sur un autre disque.** Les `.gguf` n'ont pas à résider dans `$AJEAN_HOME` : dans l'éditeur de preset de l'interface, section *Modèle → Dossiers de modèles*, ajoutez le dossier voulu. Ses modèles apparaissent dans la liste, groupés par dossier. La liste est enregistrée dans `model_dirs.json`, donc conservée d'un preset à l'autre. ### Variables d'environnement | Variable | Rôle | Défaut | |----------|------|--------| -| `JEAN_HOME` | racine des données | `/etc/jean`, `%ProgramData%\jean` | -| `JEAN_MODEL_DIRS` | dossiers de modèles (séparés par `:`, `;` sous Windows) | — | -| `JEAN_SERVICE` | nom de l'unité systemd | `jean` | +| `AJEAN_HOME` | racine des données | `/etc/ajean`, `%ProgramData%\ajean` | +| `AJEAN_MODEL_DIRS` | dossiers de modèles (séparés par `:`, `;` sous Windows) | — | +| `JEAN_SERVICE` | nom de l'unité systemd | `ajean` | | `HF_TOKEN` | token Hugging Face pour les modèles privés | — | -| `JEAN_DL_CONNS` | connexions parallèles au téléchargement | — | -| `EDITOR` | éditeur pour `jean edit` | `nano` / `notepad` | +| `AJEAN_DL_CONNS` | connexions parallèles au téléchargement | — | +| `EDITOR` | éditeur pour `ajean edit` | `nano` / `notepad` | ## Les capacités de l'IA ### Mémoire -L'IA tient des pages Markdown sous `$JEAN_HOME/MEMORY/`, relues et mises à jour entre les sessions. Trois modes, indépendants du mode agent : +L'IA tient des pages Markdown sous `$AJEAN_HOME/MEMORY/`, relues et mises à jour entre les sessions. Trois modes, indépendants du mode agent : ```bash -jean memory always # (défaut) elle cherche avant de répondre et enregistre d'elle-même -jean memory ondemand # outils disponibles, mais utilisés seulement sur demande -jean memory off # mémoire coupée +ajean memory always # (défaut) elle cherche avant de répondre et enregistre d'elle-même +ajean memory ondemand # outils disponibles, mais utilisés seulement sur demande +ajean memory off # mémoire coupée ``` ### Accès internet @@ -160,9 +160,9 @@ Par défaut, l'IA n'a pas accès au web. En branchant un serveur [Crawl4AI](http ```bash docker run -d -p 11235:11235 --shm-size=1g unclecode/crawl4ai:latest -jean internet url http://localhost:11235 -jean internet on -jean internet status +ajean internet url http://localhost:11235 +ajean internet on +ajean internet status ``` Les outils web ne sont proposés au modèle que si le mode agent est actif, l'accès internet activé **et** le serveur joignable. Sinon ils n'existent pas, et le modèle ne peut donc pas les inventer. @@ -171,7 +171,7 @@ Les outils web ne sont proposés au modèle que si le mode agent est actif, l'ac AJEAN parle le [Model Context Protocol](https://modelcontextprotocol.io) : on y branche des serveurs tiers (fichiers, bases de données, API…) et leurs outils s'ajoutent à ceux de l'IA, nommés `mcp____`. -La configuration se fait depuis l'interface web (section *Serveurs MCP*) ou en éditant `$JEAN_HOME/mcp.json`, au **même format que Claude Desktop**, si bien qu'un fichier existant se réutilise tel quel : +La configuration se fait depuis l'interface web (section *Serveurs MCP*) ou en éditant `$AJEAN_HOME/mcp.json`, au **même format que Claude Desktop**, si bien qu'un fichier existant se réutilise tel quel : ```json { @@ -186,38 +186,38 @@ Les transports **stdio** et **HTTP** sont pris en charge. Comme le terminal, un ## Windows -- **Pas de systemd** : `jean start` lance le service en arrière-plan (suivi par fichier PID) ; `stop`, `restart`, `status` et `logs` agissent dessus, sans droits administrateur. `enable` / `disable` ne sont pas gérés, il faut passer par une tâche planifiée. -- `JEAN_HOME` vaut `%ProgramData%\jean` (repli `%LOCALAPPDATA%\jean`). -- `jean install` crée seulement le dossier de données et un `config.env` de départ. +- **Pas de systemd** : `ajean start` lance le service en arrière-plan (suivi par fichier PID) ; `stop`, `restart`, `status` et `logs` agissent dessus, sans droits administrateur. `enable` / `disable` ne sont pas gérés, il faut passer par une tâche planifiée. +- `AJEAN_HOME` vaut `%ProgramData%\ajean` (repli `%LOCALAPPDATA%\ajean`). +- `ajean install` crée seulement le dossier de données et un `config.env` de départ. - Le terminal de l'IA passe par `cmd.exe`. Elle le sait, et écrit ses fichiers par l'outil dédié plutôt que par le shell, ce qui lui permet de produire des scripts contenant des guillemets. ```powershell -jean install -jean edit # BIN=...\llama-server.exe et MODEL=...\modele.gguf -jean start -jean status +ajean install +ajean edit # BIN=...\llama-server.exe et MODEL=...\modele.gguf +ajean start +ajean status ``` -`jean llamacpp install` compile également sous Windows si `git` et `cmake` sont présents ; sinon, récupérer un `llama-server.exe` pré-compilé et pointer `BIN` dessus. +`ajean llamacpp install` compile également sous Windows si `git` et `cmake` sont présents ; sinon, récupérer un `llama-server.exe` pré-compilé et pointer `BIN` dessus. ## macOS -La page [Releases](../../releases) publie `Jean-macos-arm64.zip` / `Jean-macos-amd64.zip`, un bundle **`Jean.app`**. Dézipper, glisser dans *Applications*, ouvrir : l'interface démarre sur `http://localhost:8090`, s'ouvre dans le navigateur, et l'icône se pose dans la **barre de menus**. Pas de fenêtre de Terminal, pas d'icône dans le Dock. +La page [Releases](../../releases) publie `AJEAN-macos-arm64.zip` / `AJEAN-macos-amd64.zip`, un bundle **`AJEAN.app`**. Dézipper, glisser dans *Applications*, ouvrir : l'interface démarre sur `http://localhost:8090`, s'ouvre dans le navigateur, et l'icône se pose dans la **barre de menus**. Pas de fenêtre de Terminal, pas d'icône dans le Dock. L'application n'est signée qu'en ad-hoc : au premier lancement, faire **clic droit → Ouvrir**. -Pour un usage en ligne de commande, prendre le binaire nu `jean-darwin-arm64` : hors bundle, il conserve son comportement CLI. Les services passent par **launchd**. +Pour un usage en ligne de commande, prendre le binaire nu `ajean-darwin-arm64` : hors bundle, il conserve son comportement CLI. Les services passent par **launchd**. ## Accès distant via ajean.link -`jean link` ouvre une connexion **sortante** vers le relais : le serveur reste injoignable depuis l'extérieur, mais reste accessible de partout. +`ajean link` ouvre une connexion **sortante** vers le relais : le serveur reste injoignable depuis l'extérieur, mais reste accessible de partout. ```bash -jean link # token fourni sur ajean.link -jean link code # code d'appairage à saisir dans le portail +ajean link # token fourni sur ajean.link +ajean link code # code d'appairage à saisir dans le portail ``` -Le lien tourne comme un service : la commande rend la main, la connexion continue en tâche de fond. Inutile de lancer `jean web`, l'interface est servie dans le tunnel. Le portail donne accès à l'interface du serveur avec un chat chiffré, à la gestion de plusieurs machines, et en option à un endpoint compatible OpenAI. +Le lien tourne comme un service : la commande rend la main, la connexion continue en tâche de fond. Inutile de lancer `ajean web`, l'interface est servie dans le tunnel. Le portail donne accès à l'interface du serveur avec un chat chiffré, à la gestion de plusieurs machines, et en option à un endpoint compatible OpenAI. Il s'agit d'un service optionnel et payant ; tout le reste d'AJEAN est et restera open source et gratuit. @@ -226,8 +226,8 @@ Il s'agit d'un service optionnel et payant ; tout le reste d'AJEAN est et rester Le relais est conçu comme un **tube aveugle** : il transporte les données sans pouvoir les lire. - **Chat chiffré de bout en bout** (X25519 + AES-GCM). La clé est dérivée du mot de passe via **OPAQUE** et ne quitte jamais le navigateur. -- **Empreinte vérifiée.** `jean link` affiche l'empreinte de la clé de la machine, à confirmer une fois dans le portail, ce qui défait toute tentative d'interception par le relais. -- **Appairage authentifié.** Un code à usage unique (`jean link code`) garantit qu'un seul navigateur autorisé pilote le serveur ; même compromis, le relais ne peut pas forger de commande. +- **Empreinte vérifiée.** `ajean link` affiche l'empreinte de la clé de la machine, à confirmer une fois dans le portail, ce qui défait toute tentative d'interception par le relais. +- **Appairage authentifié.** Un code à usage unique (`ajean link code`) garantit qu'un seul navigateur autorisé pilote le serveur ; même compromis, le relais ne peut pas forger de commande. - **Code servi hors du relais.** Le portail provient d'une origine indépendante (GitHub Pages) : le relais ne peut pas injecter de code pour dérober la clé. Reste visible du relais : des métadonnées techniques (machine en ligne, modèle chargé, VRAM), jamais le contenu des conversations. @@ -240,10 +240,10 @@ Le VPS effectue un simple **passthrough SNI** : le TLS est terminé sur la machi ## API de pilotage -`jean web` expose une API HTTP pour piloter AJEAN à distance. À protéger avant toute exposition : +`ajean web` expose une API HTTP pour piloter AJEAN à distance. À protéger avant toute exposition : ```bash -jean set-web-key # génère une clé +ajean set-web-key # génère une clé ``` Chaque appel `/api/*` présente alors `Authorization: Bearer ` : @@ -257,7 +257,7 @@ Chaque appel `/api/*` présente alors `Authorization: Bearer ` : | POST | `/api/start` · `/api/stop` · `/api/restart` | piloter le service | | POST | `/api/chat` `{"messages":[…]}` | chat (flux SSE) | -> ⚠️ La clé voyage en clair en HTTP. Pour une exposition publique, placer un reverse-proxy HTTPS devant, ou utiliser `jean link`. +> ⚠️ La clé voyage en clair en HTTP. Pour une exposition publique, placer un reverse-proxy HTTPS devant, ou utiliser `ajean link`. ## Compiler depuis les sources @@ -265,17 +265,17 @@ Go 1.25+. AJEAN est écrit à 100 % en Go, l'interface est embarquée via `go:em ```bash git clone https://github.com/nathaninline/ajean.git -cd jean -CGO_ENABLED=0 go build -o jean ./cmd/jean +cd ajean +CGO_ENABLED=0 go build -o ajean ./cmd/ajean ``` > Compiler **AJEAN** ne demande que Go. Compiler le **moteur llama.cpp** demande `git`, `cmake` et le toolkit de l'accélérateur. ## Arborescence -- `cmd/jean/` : point d'entrée + ressources Windows (icône, versioninfo). -- `internal/jean/` : tout le code, fichiers préfixés par domaine (`web_*`, `chat_*`, `llm_*`, `backend_*`, `relay_*`, `sys_*`, `mcp_*`) ; carte dans `doc.go`. -- `internal/jean/ui/` : interface web embarquée. **`index.html` est généré** : les sources vivent dans `ui/src/`. Pour modifier l'interface, éditer `ui/src/` puis lancer `go generate ./internal/jean`. +- `cmd/ajean/` : point d'entrée + ressources Windows (icône, versioninfo). +- `internal/ajean/` : tout le code, fichiers préfixés par domaine (`web_*`, `chat_*`, `llm_*`, `backend_*`, `relay_*`, `sys_*`, `mcp_*`) ; carte dans `doc.go`. +- `internal/ajean/ui/` : interface web embarquée. **`index.html` est généré** : les sources vivent dans `ui/src/`. Pour modifier l'interface, éditer `ui/src/` puis lancer `go generate ./internal/ajean`. ## Licence diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6de4650..857fec7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,7 +9,7 @@ Si vous étiez bloqué dans cet état, il n'y a rien à réparer : téléchargez ## Mise à jour ``` -jean update +ajean update ``` Sous Windows, télécharger le fichier depuis la page des releases et le lancer fait le même travail. diff --git a/cmd/ajean/versioninfo.json b/cmd/ajean/versioninfo.json index 3b3f5dd..9ab5a4c 100644 --- a/cmd/ajean/versioninfo.json +++ b/cmd/ajean/versioninfo.json @@ -21,9 +21,9 @@ "StringFileInfo": { "CompanyName": "AJEAN contributors", "FileDescription": "AJEAN — gestionnaire mono-binaire de serveurs llama.cpp auto-hébergés", - "InternalName": "jean", + "InternalName": "ajean", "LegalCopyright": "Copyright (c) 2026 AJEAN contributors. MIT License.", - "OriginalFilename": "jean.exe", + "OriginalFilename": "ajean.exe", "ProductName": "AJEAN", "ProductVersion": "0.6.15", "Comments": "https://github.com/nathaninline/ajean — projet open source (MIT)" diff --git a/internal/ajean/backend_build.go b/internal/ajean/backend_build.go index f796c83..6d5fb9f 100644 --- a/internal/ajean/backend_build.go +++ b/internal/ajean/backend_build.go @@ -228,15 +228,15 @@ func hintMissingBuildDep(p buildPlan, cfgLog string) { fmt.Printf(" Relance l'installeur du CUDA Toolkit (installation personnalisée) en cochant « CUDA → Visual Studio Integration »\n") fmt.Printf(" (Visual Studio avec le workload C++ doit déjà être installé), ou copie les fichiers de\n") fmt.Printf(" \\extras\\visual_studio_integration\\MSBuildExtensions vers\n") - fmt.Printf(" \\MSBuild\\Microsoft\\VC\\\\BuildCustomizations, puis relance %s.\n", bold("jean llamacpp install")) + fmt.Printf(" \\MSBuild\\Microsoft\\VC\\\\BuildCustomizations, puis relance %s.\n", bold("ajean llamacpp install")) } if p.backend == "vulkan" && strings.Contains(log, "SPIRV-Headers") { fmt.Printf("\n%s dépendance manquante pour le backend %s : les en-têtes SPIR-V (paquet « SPIRV-Headers ») sont introuvables.\n", yellow("[dépendance]"), green("Vulkan")) if cmd := pkgInstallHint("spirv-headers"); cmd != "" { - fmt.Printf(" installe-les puis relance %s : %s\n", bold("jean llamacpp install"), bold(cmd)) + fmt.Printf(" installe-les puis relance %s : %s\n", bold("ajean llamacpp install"), bold(cmd)) } else { - fmt.Printf(" installe le paquet de développement « SPIRV-Headers » de ta distribution, puis relance %s.\n", bold("jean llamacpp install")) + fmt.Printf(" installe le paquet de développement « SPIRV-Headers » de ta distribution, puis relance %s.\n", bold("ajean llamacpp install")) } } } diff --git a/internal/ajean/backend_config.go b/internal/ajean/backend_config.go index c8df85c..5cb1e5f 100644 --- a/internal/ajean/backend_config.go +++ b/internal/ajean/backend_config.go @@ -69,9 +69,9 @@ func maskAPIKey(k string) string { // exposed on the internet, llama-server requires "Authorization: Bearer " // for every call. // -// jean set-api-key définit la clé -// jean set-api-key génère une clé aléatoire -// jean set-api-key "" supprime la protection +// ajean set-api-key définit la clé +// ajean set-api-key génère une clé aléatoire +// ajean set-api-key "" supprime la protection func cmdSetAPIKey(args []string) error { var key string switch { @@ -97,7 +97,7 @@ func cmdSetAPIKey(args []string) error { fmt.Print(dim("[info] redémarrer le service pour appliquer ? [Y/n] ")) sc := bufio.NewScanner(os.Stdin) if sc.Scan() && strings.HasPrefix(strings.ToLower(strings.TrimSpace(sc.Text())), "n") { - fmt.Println(dim("[info] pense à lancer 'jean restart'")) + fmt.Println(dim("[info] pense à lancer 'ajean restart'")) return nil } return serviceAction("restart") diff --git a/internal/ajean/backend_gpu.go b/internal/ajean/backend_gpu.go index c70b6c6..b041582 100644 --- a/internal/ajean/backend_gpu.go +++ b/internal/ajean/backend_gpu.go @@ -11,10 +11,10 @@ import ( // backend_gpu.go — sélection du/des GPU utilisés par llama-server. // -// jean gpu liste les GPU et montre la sélection courante -// jean gpu 1 n'utilise que le GPU d'index 1 -// jean gpu 0 1 utilise les GPU 0 et 1 -// jean gpu all réinitialise (tous les GPU visibles) +// ajean gpu liste les GPU et montre la sélection courante +// ajean gpu 1 n'utilise que le GPU d'index 1 +// ajean gpu 0 1 utilise les GPU 0 et 1 +// ajean gpu all réinitialise (tous les GPU visibles) // // La sélection est stockée dans config.env sous CUDA_VISIBLE_DEVICES ; backend_serve.go // l'exporte (avec CUDA_DEVICE_ORDER=PCI_BUS_ID pour que les index correspondent @@ -53,7 +53,7 @@ func cmdGPU(args []string) error { return fmt.Errorf("index GPU invalide: %q (attendu un nombre)", tok) } if n < 0 || n >= len(gpus) { - return fmt.Errorf("index GPU %d hors limites (0..%d) — voir « jean gpu »", n, len(gpus)-1) + return fmt.Errorf("index GPU %d hors limites (0..%d) — voir « ajean gpu »", n, len(gpus)-1) } idx = append(idx, strconv.Itoa(n)) } @@ -102,7 +102,7 @@ func gpuList() error { } else { fmt.Printf(" Sélection : %s (CUDA_VISIBLE_DEVICES=%s)\n", bold(sel), sel) } - fmt.Printf(" %s jean gpu pour choisir, jean gpu all pour réinitialiser\n", dim("→")) + fmt.Printf(" %s ajean gpu pour choisir, ajean gpu all pour réinitialiser\n", dim("→")) return nil } @@ -120,7 +120,7 @@ func gpuSet(value string) error { fmt.Print(dim("[info] redémarrer le service pour appliquer ? [Y/n] ")) sc := bufio.NewScanner(os.Stdin) if sc.Scan() && strings.HasPrefix(strings.ToLower(strings.TrimSpace(sc.Text())), "n") { - fmt.Println(dim("[info] pense à lancer 'jean restart'")) + fmt.Println(dim("[info] pense à lancer 'ajean restart'")) return nil } return serviceAction("restart") diff --git a/internal/ajean/backend_llamacpp.go b/internal/ajean/backend_llamacpp.go index 3748b93..4208ccf 100644 --- a/internal/ajean/backend_llamacpp.go +++ b/internal/ajean/backend_llamacpp.go @@ -9,12 +9,12 @@ import ( // backend_llamacpp.go — gestion du backend llama.cpp (clone, build, mise à jour). // -// `jean llamacpp install` installe un build neuf, détecte automatiquement +// `ajean llamacpp install` installe un build neuf, détecte automatiquement // l'accélérateur (CUDA / ROCm / Metal / CPU) et la // compute capability du GPU, puis pointe BIN dessus. -// `jean llamacpp update` met à jour le dépôt existant (git pull) et recompile +// `ajean llamacpp update` met à jour le dépôt existant (git pull) et recompile // avec la bonne config, sans intervention. -// `jean llamacpp status` montre le commit courant, le backend détecté et le +// `ajean llamacpp status` montre le commit courant, le backend détecté et le // retard éventuel sur origin. const llamacppRepoURL = "https://github.com/ggml-org/llama.cpp.git" @@ -55,7 +55,7 @@ func cmdLlamacpp(args []string) error { if err := SetConfigKey("BIN", bin); err != nil { return fmt.Errorf("binaires installés mais échec écriture BIN dans config.env: %w", err) } - fmt.Printf("%s BIN mis à jour dans %s — %s pour appliquer\n", green("✓"), confPath(), bold("jean restart")) + fmt.Printf("%s BIN mis à jour dans %s — %s pour appliquer\n", green("✓"), confPath(), bold("ajean restart")) return nil default: return fmt.Errorf("sous-commande inconnue: %s (install | update | prebuilt | status)", sub) @@ -161,7 +161,7 @@ func llamacppInstall(args []string) error { if isDir(filepath.Join(repo, ".git")) { if !force { fmt.Printf("%s dépôt déjà présent dans %s\n", yellow("[info]"), repo) - fmt.Printf(" → %s pour le mettre à jour, ou --force pour repartir de zéro\n", bold("jean llamacpp update")) + fmt.Printf(" → %s pour le mettre à jour, ou --force pour repartir de zéro\n", bold("ajean llamacpp update")) return nil } fmt.Printf("%s --force : suppression de %s\n", yellow("[info]"), repo) @@ -208,7 +208,7 @@ func llamacppInstall(args []string) error { } fmt.Printf("%s BIN mis à jour dans %s\n", green("✓"), confPath()) fmt.Printf("\nProchaines étapes :\n 1. renseigne MODEL : %s\n 2. démarre : %s\n", - bold("jean edit"), bold("jean restart")) + bold("ajean edit"), bold("ajean restart")) return nil } @@ -246,7 +246,7 @@ func llamacppUpdate(args []string) error { repo := llamacppRepoDir() if !isDir(filepath.Join(repo, ".git")) { - return fmt.Errorf("aucun dépôt llama.cpp trouvé (%s).\n → lance d'abord %s", repo, bold("jean llamacpp install")) + return fmt.Errorf("aucun dépôt llama.cpp trouvé (%s).\n → lance d'abord %s", repo, bold("ajean llamacpp install")) } fmt.Printf("%s dépôt : %s\n", cyan("▶"), repo) @@ -317,14 +317,14 @@ func llamacppUpdate(args []string) error { fmt.Printf("\n%s mis à jour : %s → %s\n", green("✓"), oldCommit, newCommit) if noRestart { - fmt.Printf("%s --no-restart : pense à lancer %s\n", dim("[info]"), bold("jean restart")) + fmt.Printf("%s --no-restart : pense à lancer %s\n", dim("[info]"), bold("ajean restart")) return nil } if svcWasUp { fmt.Printf("%s redémarrage du service…\n", cyan("▶")) return serviceAction("start") } - fmt.Printf("%s service non démarré auparavant — lance %s quand tu veux\n", dim("[info]"), bold("jean start")) + fmt.Printf("%s service non démarré auparavant — lance %s quand tu veux\n", dim("[info]"), bold("ajean start")) return nil } @@ -337,7 +337,7 @@ func llamacppStatus(args []string) error { fmt.Printf("%s\n", bold("llama.cpp")) fmt.Printf(" dépôt : %s\n", repo) if !isDir(filepath.Join(repo, ".git")) { - fmt.Printf(" %s pas encore installé — %s\n", yellow("état"), bold("jean llamacpp install")) + fmt.Printf(" %s pas encore installé — %s\n", yellow("état"), bold("ajean llamacpp install")) return nil } commit := gitOutput(repo, "log", "-1", "--format=%h %ci %s") @@ -354,7 +354,7 @@ func llamacppStatus(args []string) error { // Retard sur origin (best-effort, sans fetch réseau). if branch != "" && branch != "HEAD" { if behind := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+branch); behind != "" && behind != "0" { - fmt.Printf(" maj : %s commit(s) de retard sur origin/%s — %s\n", yellow(behind), branch, bold("jean llamacpp update")) + fmt.Printf(" maj : %s commit(s) de retard sur origin/%s — %s\n", yellow(behind), branch, bold("ajean llamacpp update")) } } diff --git a/internal/ajean/backend_model_dirs.go b/internal/ajean/backend_model_dirs.go index c9e843c..e485763 100644 --- a/internal/ajean/backend_model_dirs.go +++ b/internal/ajean/backend_model_dirs.go @@ -36,7 +36,7 @@ func extraModelDirs() []string { raw = append(raw, dirs...) } } - if v := strings.TrimSpace(os.Getenv("JEAN_MODEL_DIRS")); v != "" { + if v := envAny("AJEAN_MODEL_DIRS", "JEAN_MODEL_DIRS"); v != "" { raw = append(raw, strings.Split(v, modelDirsListSep())...) } return dedupDirs(raw, AjeanHome()) @@ -216,5 +216,5 @@ func handleModelDirs(w http.ResponseWriter, r *http.Request) { n := modelDirCount(d) out = append(out, map[string]any{"path": d, "home": false, "count": n, "exists": n >= 0}) } - sendJSON(w, 200, map[string]any{"ok": true, "dirs": out, "env": os.Getenv("JEAN_MODEL_DIRS") != ""}) + sendJSON(w, 200, map[string]any{"ok": true, "dirs": out, "env": envAny("AJEAN_MODEL_DIRS", "JEAN_MODEL_DIRS") != ""}) } diff --git a/internal/ajean/backend_models.go b/internal/ajean/backend_models.go index 4d27b18..45e9117 100644 --- a/internal/ajean/backend_models.go +++ b/internal/ajean/backend_models.go @@ -197,7 +197,7 @@ var dlClient = &http.Client{ // Overridable with JEAN_DL_CONNS (1 disables parallelism). func dlConns() int { n := 8 - if v := strings.TrimSpace(os.Getenv("JEAN_DL_CONNS")); v != "" { + if v := envAny("AJEAN_DL_CONNS", "JEAN_DL_CONNS"); v != "" { if p, err := strconv.Atoi(v); err == nil && p > 0 { n = p } diff --git a/internal/ajean/backend_serve.go b/internal/ajean/backend_serve.go index d6bf09c..096b3c5 100644 --- a/internal/ajean/backend_serve.go +++ b/internal/ajean/backend_serve.go @@ -42,7 +42,7 @@ func cmdServe(args []string) error { // Linux, PATH on Windows — handled inside execServer. setLibraryPath(filepath.Dir(bin)) - // Sélection GPU (jean gpu) : on filtre les devices visibles par llama-server. + // Sélection GPU (ajean gpu) : on filtre les devices visibles par llama-server. // CUDA_DEVICE_ORDER=PCI_BUS_ID garantit que les index correspondent à ceux // affichés par nvidia-smi (sinon CUDA réordonne par "device le plus rapide"). if v := cfg["CUDA_VISIBLE_DEVICES"]; v != "" { @@ -104,7 +104,7 @@ func cmdServe(args []string) error { // mmproj-F16.gguf) still resolve. _ = os.Chdir(AjeanHome()) - fmt.Fprintf(os.Stderr, "[jean serve] %s model=%s port=%s\n", + fmt.Fprintf(os.Stderr, "[ajean serve] %s model=%s port=%s\n", bin, filepath.Base(model), get("PORT", "8080")) // Hand off to the llama-server process. On Unix this replaces the current diff --git a/internal/ajean/chat_agent.go b/internal/ajean/chat_agent.go index 7ed8921..48b5b62 100644 --- a/internal/ajean/chat_agent.go +++ b/internal/ajean/chat_agent.go @@ -78,7 +78,7 @@ func cmdAgent(args []string) error { fmt.Printf(" %s %s\n", bold(p.Name), p.Title) } default: - return fmt.Errorf("usage: jean agent [on|off|status]") + return fmt.Errorf("usage: ajean agent [on|off|status]") } return nil } diff --git a/internal/ajean/chat_cmd.go b/internal/ajean/chat_cmd.go index f58ab3a..653a852 100644 --- a/internal/ajean/chat_cmd.go +++ b/internal/ajean/chat_cmd.go @@ -12,14 +12,14 @@ import ( // First positional arg (if any) becomes the system prompt. func cmdChat(args []string) error { if !healthCheck() { - return fmt.Errorf("serveur injoignable sur :%d — jean start d'abord", LLMPort()) + return fmt.Errorf("serveur injoignable sur :%d — ajean start d'abord", LLMPort()) } sysPrompt := strings.Join(args, " ") msgs := []Message{} if sysPrompt != "" { msgs = append(msgs, Message{Role: "system", Content: sysPrompt}) } - fmt.Printf("\n%s — /reset pour vider, /sys pour changer le system, /quit ou Ctrl-D pour sortir\n", cyan("jean chat")) + fmt.Printf("\n%s — /reset pour vider, /sys pour changer le system, /quit ou Ctrl-D pour sortir\n", cyan("ajean chat")) if sysPrompt != "" { fmt.Println(dim("system: " + sysPrompt)) } @@ -65,7 +65,7 @@ func cmdChat(args []string) error { inReason := false var stats *StatsEvent // Print the assistant prefix once; reasoning is shown inline with a tag. - fmt.Print(cyan("jean") + " > ") + fmt.Print(cyan("ajean") + " > ") caps := globalCaps() // Compaction proactive (façon Hermes) : on résume les vieux tours quand // l'historique dépasse le seuil, au lieu d'imposer un /reset. @@ -120,7 +120,7 @@ func cmdChat(args []string) error { fmt.Print("\n") inReason = false } - fmt.Printf("\n%s %s : %s\n%s ", dim(icon+" "+verb), "", magenta(ev.ToolUsed.Label), cyan("jean")+" >") + fmt.Printf("\n%s %s : %s\n%s ", dim(icon+" "+verb), "", magenta(ev.ToolUsed.Label), cyan("ajean")+" >") case ev.Reasoning != "": if !inReason { fmt.Print(magenta("[reasoning] ") + dim("")) @@ -129,7 +129,7 @@ func cmdChat(args []string) error { fmt.Print(dim(ev.Reasoning)) case ev.Content != "": if inReason { - fmt.Print("\n" + cyan("jean") + " > ") + fmt.Print("\n" + cyan("ajean") + " > ") inReason = false } full.WriteString(ev.Content) diff --git a/internal/ajean/chat_internet_tools.go b/internal/ajean/chat_internet_tools.go index a0f6378..bb74c70 100644 --- a/internal/ajean/chat_internet_tools.go +++ b/internal/ajean/chat_internet_tools.go @@ -1,5 +1,5 @@ // chat_internet_tools.go — les 4 outils web exposés au modèle (web_search, -// web_open, web_read, web_grep) + la sous-commande `jean internet`. +// web_open, web_read, web_grep) + la sous-commande `ajean internet`. package ajean import ( @@ -251,7 +251,7 @@ func toolWebGrep(args map[string]any) string { entry.url, len(matchIdx), pattern, capped, strings.Join(blocks, "\n\n---\n\n")) } -// ─── CLI : jean internet [on|off|status|url ] ────────────────────────── +// ─── CLI : ajean internet [on|off|status|url ] ────────────────────────── func cmdInternet(args []string) error { sub := "" @@ -261,7 +261,7 @@ func cmdInternet(args []string) error { switch sub { case "on": if crawl4aiURL() == "" { - return fmt.Errorf("configure d'abord l'URL : jean internet url ") + return fmt.Errorf("configure d'abord l'URL : ajean internet url ") } if err := setInternetEnabled(true); err != nil { return err @@ -274,7 +274,7 @@ func cmdInternet(args []string) error { fmt.Println(green("[ok]") + " accès internet désactivé") case "url": if len(args) < 2 { - return fmt.Errorf("usage: jean internet url (ex: http://localhost:11235)") + return fmt.Errorf("usage: ajean internet url (ex: http://localhost:11235)") } u := strings.TrimRight(strings.TrimSpace(args[1]), "/") if err := SetConfigKey("CRAWL4AI_URL", u); err != nil { @@ -286,7 +286,7 @@ func cmdInternet(args []string) error { fmt.Printf("%s serveur Crawl4AI : %s\n", green("[ok]"), bold(u)) case "key": if len(args) < 2 { - return fmt.Errorf("usage: jean internet key (vide pour l'enlever : jean internet key \"\")") + return fmt.Errorf("usage: ajean internet key (vide pour l'enlever : ajean internet key \"\")") } k := strings.TrimSpace(args[1]) if err := writeCrawlKey(k); err != nil { @@ -308,7 +308,7 @@ func cmdInternet(args []string) error { fmt.Printf("%s état: %s\n", cyan("Accès internet"), state) u := crawl4aiURL() if u == "" { - fmt.Printf(" serveur : %s — configure : jean internet url \n", dim("(non configuré)")) + fmt.Printf(" serveur : %s — configure : ajean internet url \n", dim("(non configuré)")) return nil } reach := red("injoignable") @@ -318,7 +318,7 @@ func cmdInternet(args []string) error { fmt.Printf(" serveur : %s (%s)\n", bold(u), reach) fmt.Printf(" outils : web_search, web_open, web_read, web_grep\n") default: - return fmt.Errorf("usage: jean internet [on|off|status|url ]") + return fmt.Errorf("usage: ajean internet [on|off|status|url ]") } return nil } diff --git a/internal/ajean/chat_memory.go b/internal/ajean/chat_memory.go index 03be346..8eb9ff4 100644 --- a/internal/ajean/chat_memory.go +++ b/internal/ajean/chat_memory.go @@ -372,7 +372,7 @@ func setMemMode(m MemMode) error { return SetConfigKey("MEM_MODE", string(m)) } -// cmdMemory : jean memory [off|ondemand|always|status] +// cmdMemory : ajean memory [off|ondemand|always|status] func cmdMemory(args []string) error { sub := "" if len(args) > 0 { @@ -403,7 +403,7 @@ func cmdMemory(args []string) error { fmt.Printf(" %d page(s) sous %s\n", len(pages), memoryDir()) return nil default: - return fmt.Errorf("usage: jean memory [off|ondemand|always|status]") + return fmt.Errorf("usage: ajean memory [off|ondemand|always|status]") } m := memMode() fmt.Printf("%s mémoire : %s — %s\n", green("[ok]"), bold(string(m)), label[m]) diff --git a/internal/ajean/chat_workspace.go b/internal/ajean/chat_workspace.go index 3b5f652..dc9f2ad 100644 --- a/internal/ajean/chat_workspace.go +++ b/internal/ajean/chat_workspace.go @@ -62,9 +62,12 @@ func workspaceCandidates() []string { } c = append(c, filepath.Join(AjeanHome(), "workspace")) if home, err := os.UserHomeDir(); err == nil && home != "" { + // Le nouveau nom d'abord, l'ancien ensuite : un dossier ~/jean/workspace + // déjà rempli par l'agent reste utilisable au lieu de repartir de zéro. + c = append(c, filepath.Join(home, "ajean", "workspace")) c = append(c, filepath.Join(home, "jean", "workspace")) } - c = append(c, filepath.Join(os.TempDir(), "jean-workspace")) + c = append(c, filepath.Join(os.TempDir(), "ajean-workspace")) return c } diff --git a/internal/ajean/llm_oai.go b/internal/ajean/llm_oai.go index ff31642..cf5d3d3 100644 --- a/internal/ajean/llm_oai.go +++ b/internal/ajean/llm_oai.go @@ -103,7 +103,7 @@ func setOAIPublic(on bool) error { func oaiTLSConfig() *tls.Config { certmagic.Default.Storage = &certmagic.FileStorage{Path: filepath.Join(AjeanHome(), "certs")} certmagic.DefaultACME.Agreed = true - certmagic.DefaultACME.Email = strings.TrimSpace(os.Getenv("JEAN_ACME_EMAIL")) + certmagic.DefaultACME.Email = envAny("AJEAN_ACME_EMAIL", "JEAN_ACME_EMAIL") certmagic.DefaultACME.DisableHTTPChallenge = true // pas de :80 accessible (CGNAT) → TLS-ALPN uniquement magic := certmagic.NewDefault() magic.OnDemand = &certmagic.OnDemandConfig{ @@ -221,7 +221,7 @@ func selfSignedTLSConfig(host string) (*tls.Config, error) { // cmdOAI pilote l'accès OpenAI public côté agent. // -// jean oai serve [port] [host] (test local) termine le TLS sur :port avec un +// ajean oai serve [port] [host] (test local) termine le TLS sur :port avec un // cert auto-signé et proxifie vers llama /v1. func cmdOAI(args []string) error { sub := "" @@ -251,11 +251,11 @@ func cmdOAI(args []string) error { if err != nil { return err } - fmt.Printf("[jean oai] front TLS (test, auto-signé) https://%s:%d/v1 → llama :%d\n", host, port, LLMPort()) + fmt.Printf("[ajean oai] front TLS (test, auto-signé) https://%s:%d/v1 → llama :%d\n", host, port, LLMPort()) return runOAIFront(ln, tlsCfg) default: - fmt.Println("usage: jean oai serve [port] [host] (front TLS de test → llama /v1)") - fmt.Println(" en prod, le front TLS est servi automatiquement dans le tunnel (jean link)") + fmt.Println("usage: ajean oai serve [port] [host] (front TLS de test → llama /v1)") + fmt.Println(" en prod, le front TLS est servi automatiquement dans le tunnel (ajean link)") fmt.Println(" quand JEAN_LINK_ALLOW_OAI=1 ; cert Let's Encrypt via TLS-ALPN-01.") return nil } diff --git a/internal/ajean/llm_testcmd.go b/internal/ajean/llm_testcmd.go index 5a4dae2..081d2bf 100644 --- a/internal/ajean/llm_testcmd.go +++ b/internal/ajean/llm_testcmd.go @@ -14,7 +14,7 @@ func cmdTest(args []string) error { fmt.Printf("→ GET http://localhost:%d/health … ", port) if !healthCheck() { fmt.Println(red("ko")) - return fmt.Errorf("/health ne répond pas — jean start d'abord") + return fmt.Errorf("/health ne répond pas — ajean start d'abord") } fmt.Println(green("ok")) diff --git a/internal/ajean/relay_e2e.go b/internal/ajean/relay_e2e.go index 9e52578..735ba25 100644 --- a/internal/ajean/relay_e2e.go +++ b/internal/ajean/relay_e2e.go @@ -5,7 +5,7 @@ package ajean // // Modèle : cet agent a une paire X25519 long-terme (clé privée locale, jamais // transmise). Sa clé publique est publiée au relais et son EMPREINTE est affichée -// au `jean link` — l'utilisateur la confirme dans le portail, ce qui défait tout +// au `ajean link` — l'utilisateur la confirme dans le portail, ce qui défait tout // MITM du relais. Le navigateur dérive une racine R de son mot de passe (exportKey // OPAQUE), la SCELLE vers la clé publique de l'agent (le relais ne peut pas // l'ouvrir), puis chiffre/déchiffre le chat avec une clé dérivée de R. diff --git a/internal/ajean/relay_e2eauth.go b/internal/ajean/relay_e2eauth.go index 41d007a..8b372b8 100644 --- a/internal/ajean/relay_e2eauth.go +++ b/internal/ajean/relay_e2eauth.go @@ -9,7 +9,7 @@ package ajean // Modèle : l'utilisateur a une identité X25519 dérivée de façon déterministe de son // mot de passe (exportKey OPAQUE → racine R → uPriv) — le relais ne connaît pas R, // donc ne peut PAS reproduire cette identité. La clé publique uPub est APPAIRÉE une -// fois à l'agent via un code affiché par « jean link » (canal hors-bande : le log du +// fois à l'agent via un code affiché par « ajean link » (canal hors-bande : le log du // serveur, que le relais ne voit pas ; le code voyage scellé vers l'agent). Ensuite, // chaque requête est chiffrée avec K = SHA256(ECDH(uPriv, agentPriv) || "authchan"), // liée à uPub+horodatage (AAD) et protégée contre le rejeu. Seul le vrai utilisateur @@ -100,8 +100,8 @@ func authorizeUser(uPubHex string) error { // ---- Codes d'appairage : à la demande, usage unique, TTL 10 min -------------- // -// Générés par « jean link code » (ou affichés par « jean link »), ils sont -// partagés avec le worker « jean link --foreground » (systemd, autre process) via +// Générés par « ajean link code » (ou affichés par « ajean link »), ils sont +// partagés avec le worker « ajean link --foreground » (systemd, autre process) via // un fichier AjeanHome/.pair_codes — sinon les deux process ne s'accorderaient pas. // Stockés HACHÉS (SHA-256) : le fichier ne révèle aucun code. Chaque code expire // au bout de 10 min et est consommé (retiré) au premier appairage réussi. diff --git a/internal/ajean/relay_link.go b/internal/ajean/relay_link.go index 1b2580b..161fb2a 100644 --- a/internal/ajean/relay_link.go +++ b/internal/ajean/relay_link.go @@ -1,16 +1,16 @@ package ajean -// relay_link.go — `jean link ` : connecte ce serveur Jean au relais public +// relay_link.go — `ajean link ` : connecte ce serveur Jean au relais public // (ajean.link) par une connexion SORTANTE persistante, pour qu'un utilisateur // y accède depuis n'importe où sans ouvrir de port (CGNAT, box, etc.). // // Principe : l'agent ouvre un WebSocket vers le relais, l'authentifie avec le // token d'abonnement, puis multiplexe (yamux) ce lien unique en un stream par -// requête navigateur. Chaque stream est reverse-proxyfié vers le `jean web` +// requête navigateur. Chaque stream est reverse-proxyfié vers le `ajean web` // local. Keepalive + reconnexion automatique avec backoff. // // Le token est fourni par la boutique à l'achat. Il est mémorisé dans -// $JEAN_HOME/.link_token pour que `jean link` (sans argument) reprenne la +// $JEAN_HOME/.link_token pour que `ajean link` (sans argument) reprenne la // connexion. import ( @@ -87,7 +87,7 @@ func removeLinkToken() error { // relayURL resolves the relay WebSocket endpoint (env override → default). func relayURL() string { - if u := strings.TrimSpace(os.Getenv("JEAN_LINK_URL")); u != "" { + if u := envAny("AJEAN_LINK_URL", "JEAN_LINK_URL"); u != "" { return u } return defaultRelayURL @@ -111,12 +111,12 @@ func cmdLink(args []string) error { } switch sub { case "": - // `jean link` seul : afficher l'aide des sous-commandes (NE démarre pas — + // `ajean link` seul : afficher l'aide des sous-commandes (NE démarre pas — // éviter de prendre un mot pour un token et d'écraser le vrai). printLinkHelp() return nil case "serve", "--foreground", "fg": - // Le worker réel (boucle de connexion au relais), pendant de `jean serve`. + // Le worker réel (boucle de connexion au relais), pendant de `ajean serve`. // C'est ce que lance l'unité systemd ; il NE doit PAS rendre la main. return runLinkForeground() case "start": @@ -124,14 +124,14 @@ func cmdLink(args []string) error { case "status": tok := readLinkToken() if tok == "" { - fmt.Println(yellow("[info]") + " aucun token enregistré — lance: jean link ") + fmt.Println(yellow("[info]") + " aucun token enregistré — lance: ajean link ") return nil } fmt.Printf("%s token enregistré (%s…), relais: %s\n", green("[ok]"), tok[:min(8, len(tok))], relayURL()) if linkServiceActive() { fmt.Printf("%s service %s: actif\n", green("[ok]"), linkServiceName()) } else { - fmt.Printf("%s service %s: arrêté (jean link pour démarrer)\n", yellow("[info]"), linkServiceName()) + fmt.Printf("%s service %s: arrêté (ajean link pour démarrer)\n", yellow("[info]"), linkServiceName()) } return nil case "logout": @@ -174,7 +174,7 @@ func cmdLink(args []string) error { // on le signale au lieu d'un faux « démarré ». func startLink(force bool) error { if readLinkToken() == "" { - return fmt.Errorf("aucun token. Usage: jean link (token fourni à l'achat sur la boutique)") + return fmt.Errorf("aucun token. Usage: ajean link (token fourni à l'achat sur la boutique)") } switch { case force: @@ -182,7 +182,7 @@ func startLink(force bool) error { return err } case linkServiceActive(): - fmt.Printf("%s service %s déjà en cours — « jean link restart » pour le relancer\n", yellow("[info]"), linkServiceName()) + fmt.Printf("%s service %s déjà en cours — « ajean link restart » pour le relancer\n", yellow("[info]"), linkServiceName()) default: if err := linkServiceCtl("start"); err != nil { return err @@ -191,20 +191,20 @@ func startLink(force bool) error { return linkPrintIdentity() } -// printLinkHelp liste les sous-commandes de `jean link`. +// printLinkHelp liste les sous-commandes de `ajean link`. func printLinkHelp() { - fmt.Print(`jean link — accès distant via le relais ajean.link + fmt.Print(`ajean link — accès distant via le relais ajean.link Usage : - jean link enregistre le token (1re fois / pour le changer) et démarre le lien - jean link start démarre le lien en arrière-plan (service) - jean link restart redémarre le service de lien - jean link stop arrête le service de lien - jean link status état du token et du service - jean link code génère un code d'appairage (valable 10 min, à usage unique) - jean link logout oublie le token enregistré + ajean link enregistre le token (1re fois / pour le changer) et démarre le lien + ajean link start démarre le lien en arrière-plan (service) + ajean link restart redémarre le service de lien + ajean link stop arrête le service de lien + ajean link status état du token et du service + ajean link code génère un code d'appairage (valable 10 min, à usage unique) + ajean link logout oublie le token enregistré -Le token est fourni sur ajean.link. « jean link » seul affiche cette aide. +Le token est fourni sur ajean.link. « ajean link » seul affiche cette aide. `) } @@ -218,7 +218,7 @@ func linkPrintIdentity() error { code, err := newPairCode() if err != nil { fmt.Printf("%s code d'appairage indisponible (droits sur %s ?): %v\n", yellow("[link]"), AjeanHome(), err) - fmt.Printf(" Réessaie : sudo jean link code\n") + fmt.Printf(" Réessaie : sudo ajean link code\n") return nil } fmt.Printf(" Code d'appairage (valable 10 min, usage unique) : %s\n\n", bold(code)) @@ -230,17 +230,17 @@ func linkPrintIdentity() error { func runLinkForeground() error { token := readLinkToken() if token == "" { - return fmt.Errorf("aucun token. Usage: jean link ") + return fmt.Errorf("aucun token. Usage: ajean link ") } fmt.Printf("%s connexion au relais %s …\n", cyan("[link]"), relayURL()) - fmt.Printf(" (UI AJEAN + endpoint OpenAI servis dans le tunnel — pas besoin de 'jean web')\n") + fmt.Printf(" (UI AJEAN + endpoint OpenAI servis dans le tunnel — pas besoin de 'ajean web')\n") if fp := e2eFingerprint(); fp != "" { - fmt.Printf("%s empreinte E2E : %s (code d'appairage : jean link code)\n", green("[e2e]"), bold(fp)) + fmt.Printf("%s empreinte E2E : %s (code d'appairage : ajean link code)\n", green("[e2e]"), bold(fp)) } // UNE seule instance du mux web → UNE seule conversation en mémoire, PARTAGÉE // entre l'UI locale (:8090, jean.n27.fr) et le tunnel (app.ajean.link). Avant, - // `jean web` et `jean link` étaient deux process distincts avec chacun leur + // `ajean web` et `ajean link` étaient deux process distincts avec chacun leur // historique → confusion. Désormais le process jean-link est l'unique // propriétaire de la conversation et sert les DEUX surfaces. webMux := newWebMux() @@ -369,12 +369,12 @@ func killForeignLinkWorker() { // serveLocalWebMux sert l'UI web locale sur :8090 avec le mux fourni — le MÊME // que celui exposé dans le tunnel. Le process jean-link devient ainsi l'unique // propriétaire de la conversation, partagée entre l'accès local et l'accès -// distant. Remplace le `jean web` autonome (à ne plus lancer séparément). +// distant. Remplace le `ajean web` autonome (à ne plus lancer séparément). func serveLocalWebMux(mux *http.ServeMux) { addr := "0.0.0.0:8090" ln, err := net.Listen("tcp", addr) if err != nil { - fmt.Printf("%s UI locale non démarrée (%s occupé ? tue le vieux « jean web ») : %v\n", yellow("[web]"), addr, err) + fmt.Printf("%s UI locale non démarrée (%s occupé ? tue le vieux « ajean web ») : %v\n", yellow("[web]"), addr, err) return } fmt.Printf("%s UI locale sur http://%s (même conversation que le tunnel)\n", green("[web]"), addr) @@ -410,7 +410,7 @@ func adoptLegacyLinkFile(ext string) string { // linkServiceCtl pilote le worker de lien (start/stop/restart). Sous Linux c'est // l'unité systemd jean-link (avec sudo non interactif si on n'est pas root) ; // ailleurs — macOS et Windows, où il n'y a ni systemd ni droits root — on lance -// « jean link serve » en processus détaché suivi par un fichier PID, comme le +// « ajean link serve » en processus détaché suivi par un fichier PID, comme le // fait déjà le service principal. Sans ça, l'accès distant restait définitivement // « arrêté » sur un Mac ou un PC : le token était enregistré mais rien ne // composait jamais le tunnel. @@ -527,7 +527,7 @@ func linkUserSvcCtl(action string) error { cmd := spawnDetached(self, "link", "serve") cmd.Stdout, cmd.Stderr = logf, logf if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de « jean link serve »: %w", err) + return fmt.Errorf("démarrage de « ajean link serve »: %w", err) } pid := cmd.Process.Pid // PID + binaire d'origine : la 2e ligne dit QUELLE copie de l'app a lancé ce diff --git a/internal/ajean/run.go b/internal/ajean/run.go index cfc0f97..d97c4ec 100644 --- a/internal/ajean/run.go +++ b/internal/ajean/run.go @@ -1,5 +1,5 @@ -// jean — single-binary LLM server manager + web UI for llama.cpp deployments. -// Point d'entrée réel : Main(), appelé par cmd/jean (les métadonnées Windows +// ajean — single-binary LLM server manager + web UI for llama.cpp deployments. +// Point d'entrée réel : Main(), appelé par cmd/ajean (les métadonnées Windows // .syso/go:generate vivent là-bas, dans le dossier du package main). package ajean @@ -12,7 +12,7 @@ import ( const Version = "0.6.15" -// Main est le vrai main() du binaire (cmd/jean ne fait que l'appeler). +// Main est le vrai main() du binaire (cmd/ajean ne fait que l'appeler). func Main() { // Rattache la console du terminal parent si on est lancé depuis un shell // (Windows : binaire GUI). Retourne false au double-clic (aucune console) → @@ -37,7 +37,7 @@ func Main() { // lance l'expérience « application » (UI web + navigateur + icône tray) plutôt // que d'afficher l'aide. Le binaire étant compilé en sous-système GUI, il n'y a // AUCUNE console à ce stade (donc plus de fenêtre noire). Lancé depuis un shell, - // `jean` sans argument garde son comportement d'aide. + // `ajean` sans argument garde son comportement d'aide. if noArgs && !haveConsole { mustExit(cmdApp(args)) return @@ -96,7 +96,7 @@ func Main() { case "uninstall": mustExit(cmdUninstall(args)) case "version", "-v", "--version": - fmt.Println("jean", Version) + fmt.Println("ajean", Version) case "help", "-h", "--help", "": printHelp() default: @@ -107,9 +107,9 @@ func Main() { } func printHelp() { - fmt.Printf(`jean %s — manager llama.cpp + UI web (single binary) + fmt.Printf(`ajean %s — manager llama.cpp + UI web (single binary) -Usage: jean [args] +Usage: ajean [args] Application: app lance l'UI web + ouvre le navigateur (auto au double-clic du binaire) @@ -118,9 +118,9 @@ Service: start | stop | restart gérer le service (systemd sous Linux, processus en arrière-plan sous Windows) status | logs état / logs en direct enable | disable auto-démarrage au boot - edit éditer $JEAN_HOME/config.env + edit éditer $AJEAN_HOME/config.env set-api-key [clé] protéger llama-server (clé Bearer); vide = générer, "" = retirer - set-web-key [clé] protéger l'API de pilotage 'jean web'; vide = générer, "" = retirer + set-web-key [clé] protéger l'API de pilotage 'ajean web'; vide = générer, "" = retirer vram utilisation GPU/VRAM (nvidia-smi) gpu [index…] liste les GPU / choisit le(s)quel(s) utiliser (gpu all = tous) test vérifie que l'IA répond (health + completion) @@ -141,7 +141,7 @@ Accès distant (ajean.link) : link code génère un code d'appairage (valable 10 min, à usage unique) pour le portail link status | logout état du lien / oublier le token link (sans argument) affiche l'aide des sous-commandes link - link serve exécute le worker au premier plan (utilisé par jean-link.service ; pendant de 'jean serve') + link serve exécute le worker au premier plan (utilisé par jean-link.service ; pendant de 'ajean serve') Mode agent: agent [on|off|status] active TOUS les outils de l'IA (shell complet + mémoire) — un seul interrupteur @@ -151,20 +151,21 @@ Backend llama.cpp : llamacpp update git pull + recompile le backend existant (arrête/redémarre le service) llamacpp status commit courant, backend détecté, retard sur origin -Entrypoint (utilisé par jean.service) : +Entrypoint (utilisé par ajean.service) : serve lit config.env et exec le binaire llama-server Installation: where affiche où sont le binaire, la config, la mémoire et le dossier de travail de l'agent install installer (Linux: unité systemd, sudoers, dossiers ; Windows: dossiers + config) uninstall désinstaller - update [--check] mettre à jour jean depuis les releases GitHub (--check = signale sans installer) + update [--check] mettre à jour AJEAN depuis les releases GitHub (--check = signale sans installer) Env: - JEAN_HOME racine (défaut: /etc/jean sur Linux/macOS, %%ProgramData%%\jean sur Windows) - EDITOR éditeur pour 'jean edit' (défaut: nano sur Unix, notepad sur Windows) + AJEAN_HOME racine (défaut: /etc/ajean sur Linux/macOS, %%ProgramData%%\ajean sur Windows) + JEAN_HOME reste accepté (héritage) + EDITOR éditeur pour 'ajean edit' (défaut: nano sur Unix, notepad sur Windows) -Config: $JEAN_HOME/config.env +Config: $AJEAN_HOME/config.env `, Version) } @@ -234,6 +235,7 @@ func legacySkillsFlag() string { return filepath.Join(skillsDir(), ".enabled") } func legacyToolsFlag() string { return filepath.Join(AjeanHome(), ".tools_enabled") } func apiKeyPath() string { return filepath.Join(AjeanHome(), ".api_key") } func crawlKeyPath() string { return filepath.Join(AjeanHome(), ".crawl4ai_key") } + // serviceName est le nom de l'unité qui exécute llama-server. AJEAN_SERVICE et // JEAN_SERVICE sont tous deux honorés : le second est posé à la main sur des // machines qu'on ne voit pas, le renommage ne doit pas les casser. diff --git a/internal/ajean/sys_console_windows.go b/internal/ajean/sys_console_windows.go index 821c4e6..5d0eacf 100644 --- a/internal/ajean/sys_console_windows.go +++ b/internal/ajean/sys_console_windows.go @@ -9,7 +9,7 @@ package ajean // qui s'ouvre et se ferme. En contrepartie, un binaire GUI lancé depuis un // terminal (cmd/PowerShell) n'écrit nulle part par défaut : on se rattache alors // explicitement à la console du parent et on réouvre stdout/stderr/stdin dessus, -// pour que l'usage CLI (`jean web`, `jean status`, …) reste lisible. +// pour que l'usage CLI (`ajean web`, `ajean status`, …) reste lisible. import ( "os" diff --git a/internal/ajean/sys_env.go b/internal/ajean/sys_env.go new file mode 100644 index 0000000..f0aa377 --- /dev/null +++ b/internal/ajean/sys_env.go @@ -0,0 +1,23 @@ +package ajean + +import ( + "os" + "strings" +) + +// envAny lit la première variable d'environnement renseignée parmi names. +// +// Sert au renommage jean → AJEAN : chaque réglage s'appelle désormais AJEAN_*, +// mais l'ancien nom JEAN_* reste lu en second. Ces variables sont posées à la +// main — dans un shell, un fichier d'unité, un docker-compose, un script de +// déploiement — et rien ne nous permet de les retrouver pour les réécrire. Les +// cesser de les lire, c'est casser silencieusement des installations qui +// marchaient, avec un réglage qui redevient sa valeur par défaut sans le dire. +func envAny(names ...string) string { + for _, n := range names { + if v := strings.TrimSpace(os.Getenv(n)); v != "" { + return v + } + } + return "" +} diff --git a/internal/ajean/sys_firstrun_other.go b/internal/ajean/sys_firstrun_other.go index b6eec3c..b99bf1a 100644 --- a/internal/ajean/sys_firstrun_other.go +++ b/internal/ajean/sys_firstrun_other.go @@ -4,7 +4,7 @@ package ajean import "os" -// appFirstRun : hors Windows, `jean app` est lancé depuis un terminal par +// appFirstRun : hors Windows, `ajean app` est lancé depuis un terminal par // quelqu'un qui a déjà suivi l'installation — pas de double-clic à désambiguïser, // donc pas de boîte de dialogue. On se contente de provisionner les données au // premier lancement, comme avant. Ne relance jamais (renvoie toujours false). diff --git a/internal/ajean/sys_firstrun_windows.go b/internal/ajean/sys_firstrun_windows.go index f9ad683..8c4562c 100644 --- a/internal/ajean/sys_firstrun_windows.go +++ b/internal/ajean/sys_firstrun_windows.go @@ -195,6 +195,7 @@ func runAsInstaller(target string) bool { return launch(target) // l'instance en cours reprend la main (port occupé) } stopProcesses(running) + target = migrateThenResolveTarget(target) if err := replaceInstalled(target); err != nil { messageBox("La mise à jour a échoué :\n\n"+err.Error()+"\n\nAJEAN va redémarrer dans sa version actuelle.", "AJEAN", mbIconInfo) @@ -205,6 +206,7 @@ func runAsInstaller(target string) bool { case cmp > 0: // Mise à jour, application arrêtée : rien à décider, on remplace et on // démarre. C'est le cas courant et il doit rester muet. + target = migrateThenResolveTarget(target) _ = replaceInstalled(target) ensureShortcuts(target) return launch(target) @@ -225,7 +227,22 @@ func verLabel(v string) string { // replaceInstalled écrase le binaire installé par celui qu'on exécute. Le // renommage préalable en .old permet de remplacer un exécutable encore ouvert -// (même mécanique que replaceBinary pour `jean update`). +// (même mécanique que replaceBinary pour `ajean update`). +// migrateThenResolveTarget tente la migration du dossier de données maintenant +// qu'aucune instance ne tient plus de handle, puis renvoie le chemin du binaire +// installé DANS le dossier résultant. +// +// Recalculer le chemin est indispensable et pas cosmétique : `target` a été +// calculé avant la migration, il désigne donc encore …\jean\bin\ajean.exe. Sans +// ce recalcul, on réinstallerait le binaire dans l'ancien dossier qu'on vient +// tout juste de déplacer, en recréant au passage une arborescence fantôme. +func migrateThenResolveTarget(target string) string { + if !retryHomeMigration() { + return target + } + return installedExePath() +} + func replaceInstalled(target string) error { old := target + ".old" _ = os.Remove(old) @@ -321,7 +338,7 @@ func portBusy(port int) bool { // posées par goversioninfo, cf cmd/jean/versioninfo.json), comme le fait // l'onglet « Détails » des propriétés Windows. // -// ⚠️ NE PAS remplacer par un `jean version` exécuté : le binaire est compilé en +// ⚠️ NE PAS remplacer par un `ajean version` exécuté : le binaire est compilé en // sous-système GUI, il s'attache à la console du parent et n'écrit RIEN dans un // tuyau. La sortie capturée est vide, donc la comparaison de versions échouerait // toujours en silence et la mise à jour ne se ferait jamais (vérifié). diff --git a/internal/ajean/sys_firstrun_windows_test.go b/internal/ajean/sys_firstrun_windows_test.go index 51a651a..b5cde1e 100644 --- a/internal/ajean/sys_firstrun_windows_test.go +++ b/internal/ajean/sys_firstrun_windows_test.go @@ -12,7 +12,7 @@ import ( // binaryVersion pilote la mise à jour automatique du binaire installé quand on // lance le fichier téléchargé : si elle renvoie du vide, plus aucune mise à jour // n'a lieu, et en silence. C'est exactement ce qui arrivait avec la première -// implémentation (exécuter `jean version`, dont la sortie est vide pour un +// implémentation (exécuter `ajean version`, dont la sortie est vide pour un // binaire en sous-système GUI). D'où ces tests sur la plomberie syscall. func TestBinaryVersionReadsResource(t *testing.T) { // kernel32.dll porte toujours une ressource de version. diff --git a/internal/ajean/sys_install_darwin.go b/internal/ajean/sys_install_darwin.go index 404e83d..03c5609 100644 --- a/internal/ajean/sys_install_darwin.go +++ b/internal/ajean/sys_install_darwin.go @@ -17,7 +17,7 @@ import ( // sur « systemctl: command not found » / « /etc/default/jean », cf. issue #4). // Implémentation prudente ; à valider sur une machine Apple. -const configTemplate = `# Configuration JEAN — édite-moi puis: jean restart +const configTemplate = `# Configuration JEAN — édite-moi puis: ajean restart # Le service launchd lit ce fichier et lance ton binaire llama.cpp. # Chemins @@ -38,9 +38,15 @@ NGL="999" EXTRA_ARGS="" ` -// launchdPlistTemplate — champs formatés : Label, UserName, WorkingDirectory, -// JEAN_HOME, StandardOutPath, StandardErrorPath. KeepAlive/SuccessfulExit=false -// ≈ Restart=on-failure ; RunAtLoad relance au boot une fois chargé avec `-w`. +// launchdPlistTemplate — champs formatés : Label, chemin du binaire, UserName, +// WorkingDirectory, AJEAN_HOME, JEAN_HOME, StandardOutPath, StandardErrorPath. +// KeepAlive/SuccessfulExit=false ≈ Restart=on-failure ; RunAtLoad relance au +// boot une fois chargé avec `-w`. +// +// Le chemin du binaire est injecté et non codé en dur : il a changé avec le +// renommage, et un plist figé sur l'ancien chemin donnerait un daemon qui refuse +// de démarrer. Les deux variables d'environnement sont posées car le code lit +// encore JEAN_HOME en héritage. const launchdPlistTemplate = ` @@ -48,13 +54,13 @@ const launchdPlistTemplate = ` Label%s ProgramArguments - /usr/local/bin/jean + %s serve UserName%s WorkingDirectory%s EnvironmentVariables - JEAN_HOME%s + AJEAN_HOME%sJEAN_HOME%s RunAtLoad KeepAlive SuccessfulExit @@ -66,7 +72,7 @@ const launchdPlistTemplate = ` func cmdInstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("jean install doit être exécuté en root (sudo jean install)") + return fmt.Errorf("ajean install doit être exécuté en root (sudo ajean install)") } targetUser := os.Getenv("SUDO_USER") if targetUser == "" { @@ -108,7 +114,7 @@ func cmdInstall(args []string) error { fmt.Printf(" %s écrit %s\n", green("✓"), conf) } - // 3. Symlink idempotent vers /usr/local/bin/jean — même garde-fou que Linux + // 3. Symlink idempotent vers /usr/local/bin/ajean — même garde-fou que Linux // (issue #5 : ne pas se lier sur soi-même si on tourne déjà depuis la cible). self, err := os.Executable() if err != nil { @@ -117,7 +123,7 @@ func cmdInstall(args []string) error { if rp, e := filepath.EvalSymlinks(self); e == nil { self = rp } - target := "/usr/local/bin/jean" + target := installedExePath() if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return err } @@ -137,6 +143,18 @@ func cmdInstall(args []string) error { fmt.Printf(" %s %s -> %s\n", green("✓"), target, self) } + // 3 bis. Alias hérité : `jean` doit rester tapable (voir sys_paths.go). + if alias := legacyExePath(); alias != target { + if rp, e := filepath.EvalSymlinks(alias); e != nil || rp != self { + _ = os.Remove(alias) + if err := os.Symlink(target, alias); err != nil { + fmt.Printf(" %s alias %s non créé (%v) — « ajean » reste disponible\n", yellow("[info]"), alias, err) + } else { + fmt.Printf(" %s %s -> %s (alias hérité)\n", green("✓"), alias, target) + } + } + } + // 4. /etc/default/ajean pour que les invocations CLI résolvent AJEAN_HOME. // Les deux clés sont écrites : des scripts d'utilisateurs sourcent ce fichier // et lisent encore $JEAN_HOME. @@ -149,7 +167,7 @@ func cmdInstall(args []string) error { // 5. LaunchDaemon plist (le log va sous JEAN_HOME, accessible au user cible) logPath := filepath.Join(ajeanHome, svc+".log") - plist := fmt.Sprintf(launchdPlistTemplate, launchdLabel(svc), targetUser, ajeanHome, ajeanHome, logPath, logPath) + plist := fmt.Sprintf(launchdPlistTemplate, launchdLabel(svc), target, targetUser, ajeanHome, ajeanHome, ajeanHome, logPath, logPath) plistPath := launchdPlistPath(svc) if err := os.WriteFile(plistPath, []byte(plist), 0o644); err != nil { return err @@ -162,16 +180,16 @@ func cmdInstall(args []string) error { fmt.Println() fmt.Printf("%s installation terminée.\n", green("[ok]")) fmt.Printf("\nProchaines étapes :\n") - fmt.Printf(" 1. édite la config : %s\n", bold("sudo -u "+targetUser+" jean edit")) + fmt.Printf(" 1. édite la config : %s\n", bold("sudo -u "+targetUser+" ajean edit")) fmt.Printf(" (renseigne BIN, MODEL, etc.)\n") - fmt.Printf(" 2. démarre le service: %s\n", bold("sudo jean start")) - fmt.Printf(" 3. UI web : %s\n", bold("sudo -u "+targetUser+" jean web")) + fmt.Printf(" 2. démarre le service: %s\n", bold("sudo ajean start")) + fmt.Printf(" 3. UI web : %s\n", bold("sudo -u "+targetUser+" ajean web")) return nil } func cmdUninstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("jean uninstall doit être exécuté en root") + return fmt.Errorf("ajean uninstall doit être exécuté en root") } svc := serviceName() _ = exec.Command("launchctl", "unload", "-w", launchdPlistPath(svc)).Run() diff --git a/internal/ajean/sys_install_linux.go b/internal/ajean/sys_install_linux.go index 2d3f0b0..6441c82 100644 --- a/internal/ajean/sys_install_linux.go +++ b/internal/ajean/sys_install_linux.go @@ -11,7 +11,7 @@ import ( "strings" ) -const configTemplate = `# Configuration JEAN — édite-moi puis: jean restart +const configTemplate = `# Configuration JEAN — édite-moi puis: ajean restart # Le service systemd lit ce fichier et lance ton binaire llama.cpp. # Chemins @@ -61,7 +61,7 @@ WantedBy=multi-user.target func cmdInstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("jean install doit être exécuté en root (sudo jean install)") + return fmt.Errorf("ajean install doit être exécuté en root (sudo ajean install)") } targetUser := os.Getenv("SUDO_USER") if targetUser == "" { @@ -113,7 +113,7 @@ func cmdInstall(args []string) error { } target := installedExePath() // Garde-fou (issue #5) : si on tourne DÉJÀ depuis la cible (l'utilisateur a - // posé le binaire dans /usr/local/bin/jean puis lancé `sudo jean install`), + // posé le binaire dans /usr/local/bin/jean puis lancé `sudo ajean install`), // ne surtout pas Remove+Symlink sur soi-même — ça effacerait le vrai binaire // et créerait /usr/local/bin/jean -> /usr/local/bin/jean (« Too many levels // of symbolic links »). On compare les chemins réels (résolus). @@ -134,7 +134,7 @@ func cmdInstall(args []string) error { fmt.Printf(" %s %s -> %s\n", green("✓"), target, self) } - // 3 bis. Alias hérité : `jean` doit rester tapable. Des utilisateurs ont des + // 3 bis. Alias hérité : `ajean` doit rester tapable. Des utilisateurs ont des // alias shell, des cron, des scripts de déploiement et des tutos qui appellent // « jean » — on ne contrôle rien de tout ça, et un binaire renommé qui casse // la commande d'hier est une régression pour eux. Le lien pointe sur le @@ -185,16 +185,16 @@ func cmdInstall(args []string) error { fmt.Println() fmt.Printf("%s installation terminée.\n", green("[ok]")) fmt.Printf("\nProchaines étapes :\n") - fmt.Printf(" 1. édite la config : %s\n", bold("sudo -u "+targetUser+" jean edit")) + fmt.Printf(" 1. édite la config : %s\n", bold("sudo -u "+targetUser+" ajean edit")) fmt.Printf(" (renseigne BIN, MODEL, etc.)\n") - fmt.Printf(" 2. démarre le service: %s\n", bold("sudo -u "+targetUser+" jean start")) - fmt.Printf(" 3. UI web : %s\n", bold("sudo -u "+targetUser+" jean web")) + fmt.Printf(" 2. démarre le service: %s\n", bold("sudo -u "+targetUser+" ajean start")) + fmt.Printf(" 3. UI web : %s\n", bold("sudo -u "+targetUser+" ajean web")) return nil } func cmdUninstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("jean uninstall doit être exécuté en root") + return fmt.Errorf("ajean uninstall doit être exécuté en root") } svc := serviceName() keepData := false diff --git a/internal/ajean/sys_install_windows.go b/internal/ajean/sys_install_windows.go index 4cfd47e..6d90438 100644 --- a/internal/ajean/sys_install_windows.go +++ b/internal/ajean/sys_install_windows.go @@ -12,12 +12,12 @@ import ( ) // On Windows there's no systemd unit, sudoers, or /usr/local/bin to populate. -// `jean install` simply provisions the data directory and a starter config; the +// `ajean install` simply provisions the data directory and a starter config; the // service itself is managed by the PID-file supervisor in sys_service_windows.go -// (jean start / stop / status), which needs no admin rights. +// (ajean start / stop / status), which needs no admin rights. -const configTemplate = `# Configuration JEAN — édite-moi puis: jean restart -# jean serve lit ce fichier et lance ton binaire llama.cpp. +const configTemplate = `# Configuration JEAN — édite-moi puis: ajean restart +# ajean serve lit ce fichier et lance ton binaire llama.cpp. # Chemins (utilise des chemins Windows ; les antislashs ou slashs marchent) BIN="C:/llama.cpp/build/bin/Release/llama-server.exe" @@ -59,7 +59,7 @@ func cmdInstall(args []string) error { } // 3. Install the binary into JEAN_HOME\bin and put that dir on the user PATH, - // so `jean` is callable from any shell (this is the Windows analogue of the + // so `ajean` is callable from any shell (this is the Windows analogue of the // /usr/local/bin symlink the Unix installer creates). binDir := filepath.Join(jeanHome, "bin") if err := os.MkdirAll(binDir, 0o755); err != nil { @@ -86,9 +86,9 @@ func cmdInstall(args []string) error { fmt.Println() fmt.Printf("%s installation terminée.\n", green("[ok]")) fmt.Printf("\nProchaines étapes :\n") - fmt.Printf(" 1. édite la config : %s (renseigne BIN, MODEL)\n", bold("jean edit")) - fmt.Printf(" 2. démarre le service: %s\n", bold("jean start")) - fmt.Printf(" 3. UI web : %s\n", bold("jean web")) + fmt.Printf(" 1. édite la config : %s (renseigne BIN, MODEL)\n", bold("ajean edit")) + fmt.Printf(" 2. démarre le service: %s\n", bold("ajean start")) + fmt.Printf(" 3. UI web : %s\n", bold("ajean web")) if onPath { fmt.Printf("\n%s ouvre un NOUVEAU terminal pour que 'jean' soit reconnu (le PATH n'est lu qu'au démarrage du shell).\n", dim("[info]")) } else { diff --git a/internal/ajean/sys_migrate.go b/internal/ajean/sys_migrate.go index 9aa5141..d56389d 100644 --- a/internal/ajean/sys_migrate.go +++ b/internal/ajean/sys_migrate.go @@ -50,6 +50,26 @@ func resolveDefaultHome() string { return migrateHome(defaultAjeanHome(), legacyDefaultHome()) } +// retryHomeMigration relance la résolution — donc la migration — après avoir +// arrêté ce qui tournait. Renvoie true si le dossier a effectivement changé. +// +// Raison d'être : sous Windows, un rename de dossier échoue tant qu'un process +// y tient un handle. Or le service AJEAN détaché en tient en permanence, si bien +// que la migration tentée au démarrage échouerait indéfiniment et que la machine +// resterait sur l'ancien chemin. L'installateur, lui, a une fenêtre où tout est +// arrêté : c'est là qu'on retente. +// +// À N'APPELER QUE dans cette fenêtre, et avant que quoi que ce soit d'autre +// n'ait ouvert de fichier de données : réinitialiser le cache n'est ni atomique +// ni sûr vis-à-vis des goroutines, et surtout les chemins déjà calculés par +// l'appelant deviennent obsolètes (voir migrateThenResolveTarget). +func retryHomeMigration() bool { + before := migratedDefaultHome() + homeOnce = sync.Once{} + homePath = "" + return migratedDefaultHome() != before +} + // migrateHome contient toute la logique de migration, isolée des chemins réels // de la plateforme pour être testable telle quelle. Renvoie le dossier à // utiliser — le nouveau si la migration a réussi ou n'était pas nécessaire, diff --git a/internal/ajean/sys_paths.go b/internal/ajean/sys_paths.go index 7ef148d..1a1264f 100644 --- a/internal/ajean/sys_paths.go +++ b/internal/ajean/sys_paths.go @@ -13,7 +13,7 @@ import ( // emplacements invisibles : on ne sait plus si le .exe qu'on a double-cliqué EST // l'application ou seulement un installateur, ni où atterrissent la config, les // modèles et les fichiers que l'agent écrit. On centralise donc la réponse ici, -// et on l'expose à la fois en ligne de commande (`jean where`) et dans l'UI. +// et on l'expose à la fois en ligne de commande (`ajean where`) et dans l'UI. type ajeanPaths struct { Home string `json:"home"` // JEAN_HOME : racine des données diff --git a/internal/ajean/sys_platform_unix.go b/internal/ajean/sys_platform_unix.go index b36f497..a2d4ab2 100644 --- a/internal/ajean/sys_platform_unix.go +++ b/internal/ajean/sys_platform_unix.go @@ -59,7 +59,7 @@ func isWritableDir(dir string) bool { return true } -// defaultEditor is used by `jean edit` when $EDITOR is unset. +// defaultEditor is used by `ajean edit` when $EDITOR is unset. func defaultEditor() string { return "nano" } // hideCmd : no-op sur Unix (pas de fenêtre de console à masquer). diff --git a/internal/ajean/sys_platform_windows.go b/internal/ajean/sys_platform_windows.go index c7d77ec..bb3fff3 100644 --- a/internal/ajean/sys_platform_windows.go +++ b/internal/ajean/sys_platform_windows.go @@ -60,7 +60,7 @@ func defaultAjeanHome() string { return filepath.Join(homeRoot(), "ajean") } // legacyDefaultHome is the pre-rename data root, migrated on first run. func legacyDefaultHome() string { return filepath.Join(homeRoot(), "jean") } -// defaultEditor is used by `jean edit` when $EDITOR is unset. +// defaultEditor is used by `ajean edit` when $EDITOR is unset. func defaultEditor() string { return "notepad" } // openBrowser ouvre l'URL dans le navigateur par défaut. rundll32 évite les @@ -130,7 +130,7 @@ func setLibraryPath(dir string) { } // execServer runs llama-server as a child process and waits for it. Windows has -// no exec() that replaces the current image, so `jean serve` stays alive as the +// no exec() that replaces the current image, so `ajean serve` stays alive as the // parent (this is the detached process the service supervisor tracks). // args[0] is the binary path; the rest are its arguments. func execServer(bin string, args []string) error { @@ -232,7 +232,7 @@ func msvcGenerator() string { // ensureCompiler makes sure an MSVC C++ toolchain is available, installing the // Visual Studio 2022 Build Tools (VCTools workload) via winget if not. This is a -// large download but keeps `jean llamacpp install` fully unattended on a bare +// large download but keeps `ajean llamacpp install` fully unattended on a bare // Windows box. func ensureCompiler() error { if msvcInstallVersion() != "" { @@ -354,7 +354,7 @@ Sans elle, CMake échoue sur « No CUDA toolset found ». Pour corriger, au choi 1. relance l'installeur du CUDA Toolkit (installation personnalisée) et coche « CUDA → Visual Studio Integration » — Visual Studio doit déjà être installé à ce moment-là ; 2. ou copie (en admin) les fichiers de %s - vers le dossier BuildCustomizations ci-dessus, puis relance jean llamacpp install`, installPath, src) + vers le dossier BuildCustomizations ci-dessus, puis relance ajean llamacpp install`, installPath, src) } // cudaPathEnv returns the CUDA toolkit env vars the MSBuild CUDA integration diff --git a/internal/ajean/sys_service.go b/internal/ajean/sys_service.go index fa3d46f..3a688c4 100644 --- a/internal/ajean/sys_service.go +++ b/internal/ajean/sys_service.go @@ -28,7 +28,7 @@ func editConfig() error { if err := cmd.Run(); err != nil { return err } - fmt.Println(dim("[info] jean restart pour appliquer")) + fmt.Println(dim("[info] ajean restart pour appliquer")) return nil } diff --git a/internal/ajean/sys_service_darwin.go b/internal/ajean/sys_service_darwin.go index c277e46..d5e947a 100644 --- a/internal/ajean/sys_service_darwin.go +++ b/internal/ajean/sys_service_darwin.go @@ -54,8 +54,8 @@ func serviceAction(action string) error { svc := serviceName() plist := launchdPlistPath(svc) // Pas de LaunchDaemon installé — cas normal d'un Mac de bureau, où Jean tourne - // via Jean.app sans jamais passer par `sudo jean install` : on gère alors le - // service comme sous Windows, avec un `jean serve` détaché suivi par un fichier + // via Jean.app sans jamais passer par `sudo ajean install` : on gère alors le + // service comme sous Windows, avec un `ajean serve` détaché suivi par un fichier // PID. Aucun droit root requis. Sans ça, `start` échouait sur un plist absent // et l'UI affichait éternellement « service arrêté ». if _, err := os.Stat(plist); err != nil { @@ -117,7 +117,7 @@ func checkStarted(svc string) error { fmt.Println(strings.Join(lines, "\n")) } fmt.Println("------------------------------------------------") - fmt.Printf("→ jean logs pour plus de détails\n→ jean edit pour corriger config.env\n") + fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") return fmt.Errorf("service %s non démarré", svc) } @@ -144,7 +144,7 @@ func serviceIsActive() bool { } // --------------------------------------------------------------------------- -// Mode utilisateur (sans launchd) — même approche que Windows : `jean serve` est +// Mode utilisateur (sans launchd) — même approche que Windows : `ajean serve` est // lancé détaché, son PID va dans JEAN_HOME/.pid et sa sortie dans // JEAN_HOME/.log. Setsid le détache de notre session pour qu'il survive à // la fermeture de Jean.app, et permet de tuer tout le groupe (llama-server @@ -175,7 +175,7 @@ func userSvcAction(action string) error { return nil case "enable", "disable": fmt.Printf("%s '%s' réclame un service système : lance %s pour installer le LaunchDaemon.\n", - yellow("[info]"), action, bold("sudo jean install")) + yellow("[info]"), action, bold("sudo ajean install")) return nil } return fmt.Errorf("action inconnue: %s", action) @@ -207,7 +207,7 @@ func userSvcStart() error { cmd.Dir = AjeanHome() cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de 'jean serve': %w", err) + return fmt.Errorf("démarrage de 'ajean serve': %w", err) } pid := cmd.Process.Pid if err := os.WriteFile(pidFilePath(), []byte(strconv.Itoa(pid)), 0o644); err != nil { @@ -221,14 +221,14 @@ func userCheckStarted(pid int) error { time.Sleep(2 * time.Second) if processAlive(pid) { fmt.Printf("%s %s: démarré (PID %d)\n", green("[ok]"), serviceName(), pid) - fmt.Printf(" logs: %s (jean logs pour suivre)\n", dim(logFilePath())) + fmt.Printf(" logs: %s (ajean logs pour suivre)\n", dim(logFilePath())) return nil } fmt.Printf("%s %s: le processus s'est arrêté — derniers logs :\n", red("[ERREUR]"), serviceName()) fmt.Println("------------------------------------------------") fmt.Print(tailFile(logFilePath(), 20)) fmt.Println("------------------------------------------------") - fmt.Printf("→ jean logs pour plus de détails\n→ jean edit pour corriger config.env\n") + fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") _ = os.Remove(pidFilePath()) return fmt.Errorf("service %s non démarré", serviceName()) } diff --git a/internal/ajean/sys_service_linux.go b/internal/ajean/sys_service_linux.go index 0558e22..00c16b4 100644 --- a/internal/ajean/sys_service_linux.go +++ b/internal/ajean/sys_service_linux.go @@ -55,7 +55,7 @@ func checkStarted(svc string) error { logs, _ := exec.Command("journalctl", "-u", svc, "-n", "20", "--no-pager").Output() fmt.Print(string(logs)) fmt.Println("------------------------------------------------") - fmt.Printf("→ jean logs pour plus de détails\n→ jean edit pour corriger config.env\n") + fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") return fmt.Errorf("service %s non démarré", svc) } diff --git a/internal/ajean/sys_service_windows.go b/internal/ajean/sys_service_windows.go index da9f807..fe05806 100644 --- a/internal/ajean/sys_service_windows.go +++ b/internal/ajean/sys_service_windows.go @@ -16,7 +16,7 @@ import ( ) // On Windows there is no systemd, so "the service" is a background copy of -// `jean serve` that this process launches detached. We track it with a PID file +// `ajean serve` that this process launches detached. We track it with a PID file // and stream its output to a log file under JEAN_HOME. This needs no admin // rights and no external tools beyond the always-present tasklist/taskkill. @@ -72,14 +72,14 @@ func svcStart() error { cmd.Stdout = logf cmd.Stderr = logf cmd.Dir = AjeanHome() // les chemins relatifs de config.env se résolvent depuis JEAN_HOME - // createNoWindow + HideWindow : le service enfant (`jean serve`) ne doit JAMAIS + // createNoWindow + HideWindow : le service enfant (`ajean serve`) ne doit JAMAIS // faire clignoter de console noire quand Jean est lancé en mode app (double-clic). cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: createNewProcessGroup | detachedProcess | createNoWindow, } if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de 'jean serve': %w", err) + return fmt.Errorf("démarrage de 'ajean serve': %w", err) } pid := cmd.Process.Pid if err := os.WriteFile(pidFilePath(), []byte(strconv.Itoa(pid)), 0o644); err != nil { @@ -94,14 +94,14 @@ func checkStarted(pid int) error { time.Sleep(2 * time.Second) if processAlive(pid) { fmt.Printf("%s %s: démarré (PID %d)\n", green("[ok]"), serviceName(), pid) - fmt.Printf(" logs: %s (jean logs pour suivre)\n", dim(logFilePath())) + fmt.Printf(" logs: %s (ajean logs pour suivre)\n", dim(logFilePath())) return nil } fmt.Printf("%s %s: le processus s'est arrêté — derniers logs :\n", red("[ERREUR]"), serviceName()) fmt.Println("------------------------------------------------") fmt.Print(tailFile(logFilePath(), 20)) fmt.Println("------------------------------------------------") - fmt.Printf("→ jean logs pour plus de détails\n→ jean edit pour corriger config.env\n") + fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") _ = os.Remove(pidFilePath()) return fmt.Errorf("service %s non démarré", serviceName()) } diff --git a/internal/ajean/sys_tray_windows.go b/internal/ajean/sys_tray_windows.go index 6fb8ed2..9ce58f1 100644 --- a/internal/ajean/sys_tray_windows.go +++ b/internal/ajean/sys_tray_windows.go @@ -3,7 +3,7 @@ package ajean // sys_tray_windows.go — icône de Jean dans la zone de notification Windows (system -// tray). Quand on lance l'app (double-clic ou `jean app`), l'UI web s'ouvre dans +// tray). Quand on lance l'app (double-clic ou `ajean app`), l'UI web s'ouvre dans // le navigateur ET une petite icône apparaît près de l'horloge : elle montre que // Jean tourne et offre un menu « Ouvrir Jean » / « Quitter ». // diff --git a/internal/ajean/sys_update.go b/internal/ajean/sys_update.go index 9717199..f1a8bd8 100644 --- a/internal/ajean/sys_update.go +++ b/internal/ajean/sys_update.go @@ -17,7 +17,7 @@ import ( "golang.org/x/mod/semver" ) -// jean update — met à jour le binaire depuis les releases GitHub du projet. +// ajean update — met à jour le binaire depuis les releases GitHub du projet. // Périmètre minimal : compare la version, télécharge l'asset correspondant à // l'OS/arch courant, remplace le binaire en place, puis affiche quoi redémarrer. // AUCUN redémarrage de service automatique (choix volontaire, plus sûr). @@ -109,7 +109,7 @@ type updateInfo struct { } // checkForUpdate interroge GitHub et compare à la version courante. Réutilisé -// par `jean update` (CLI) et par l'endpoint web /api/update. +// par `ajean update` (CLI) et par l'endpoint web /api/update. func checkForUpdate() (updateInfo, error) { info := updateInfo{Current: Version} rel, err := fetchLatestRelease() @@ -158,7 +158,7 @@ func applyUpdate() (string, error) { // Contrôle des droits AVANT de télécharger : sans ça, l'échec survient sur le // os.Create du fichier temporaire et l'utilisateur reçoit un « open // /usr/local/bin/.ajean-update.tmp: permission denied » incompréhensible, - // notamment depuis le bouton de l'UI web (jean web lancé sans sudo). + // notamment depuis le bouton de l'UI web (ajean web lancé sans sudo). if err := checkUpdateWritable(exe); err != nil { return "", err } @@ -203,7 +203,7 @@ func updatePermissionError(exe string) error { if runtime.GOOS == "windows" { return fmt.Errorf("droits insuffisants pour remplacer %s — relance AJEAN en administrateur puis réessaie", exe) } - return fmt.Errorf("droits insuffisants pour remplacer %s (le binaire appartient à root) — lance la mise à jour en ligne de commande : sudo jean update", exe) + return fmt.Errorf("droits insuffisants pour remplacer %s (le binaire appartient à root) — lance la mise à jour en ligne de commande : sudo ajean update", exe) } // checkUpdateWritable vérifie qu'on peut écrire dans le dossier du binaire, en @@ -243,7 +243,7 @@ func cmdUpdate(args []string) error { fmt.Printf("nouvelle version disponible : %s (actuelle : %s)\n", info.Latest, Version) fmt.Printf(" %s\n", info.URL) if checkOnly { - fmt.Println("lance 'jean update' pour l'installer.") + fmt.Println("lance 'ajean update' pour l'installer.") return nil } fmt.Printf("téléchargement de %s…\n", updateAssetName()) diff --git a/internal/ajean/ui/index.html b/internal/ajean/ui/index.html index 4574494..abfe9c7 100644 --- a/internal/ajean/ui/index.html +++ b/internal/ajean/ui/index.html @@ -1256,7 +1256,7 @@ button:hover{border-color:var(--dim);color:var(--text)} -
Les téléchargements, eux, vont toujours dans le dossier jean.
+
Les téléchargements, eux, vont toujours dans le dossier AJEAN.
diff --git a/internal/ajean/ui/src/index.tmpl.html b/internal/ajean/ui/src/index.tmpl.html index e2a1589..df3a247 100644 --- a/internal/ajean/ui/src/index.tmpl.html +++ b/internal/ajean/ui/src/index.tmpl.html @@ -428,7 +428,7 @@ -
Les téléchargements, eux, vont toujours dans le dossier jean.
+
Les téléchargements, eux, vont toujours dans le dossier AJEAN.
diff --git a/internal/ajean/web_auth.go b/internal/ajean/web_auth.go index ae0e7bd..a7f6e3f 100644 --- a/internal/ajean/web_auth.go +++ b/internal/ajean/web_auth.go @@ -11,7 +11,7 @@ import ( "strings" ) -// web_auth.go protège l'API de pilotage (jean web) quand elle est exposée sur +// web_auth.go protège l'API de pilotage (ajean web) quand elle est exposée sur // internet — c.-à-d. l'API que tout client (navigateur, app mobile, script…) // utilise pour switcher de preset, redémarrer le service, lire le status, etc. // @@ -70,9 +70,9 @@ func checkBearer(r *http.Request, key string) bool { // cmdSetWebKey sets (or clears) the control-API key in $JEAN_HOME/.web_key. // -// jean set-web-key définit la clé -// jean set-web-key génère une clé aléatoire -// jean set-web-key "" supprime la protection (API ouverte) +// ajean set-web-key définit la clé +// ajean set-web-key génère une clé aléatoire +// ajean set-web-key "" supprime la protection (API ouverte) // // Contrairement à set-api-key, aucun redémarrage n'est nécessaire : le serveur // web relit la clé à chaque requête. @@ -106,6 +106,6 @@ func cmdSetWebKey(args []string) error { } fmt.Printf("%s clé de pilotage enregistrée dans %s\n", green("[ok]"), webKeyPath()) fmt.Printf(" les clients doivent envoyer : %s\n", dim("Authorization: Bearer "+key)) - fmt.Printf(" (relance 'jean web' si le serveur web tourne déjà — non requis, lu à chaud)\n") + fmt.Printf(" (relance 'ajean web' si le serveur web tourne déjà — non requis, lu à chaud)\n") return nil } diff --git a/internal/ajean/web_link_api.go b/internal/ajean/web_link_api.go index ef71886..0c6f420 100644 --- a/internal/ajean/web_link_api.go +++ b/internal/ajean/web_link_api.go @@ -3,7 +3,7 @@ package ajean // web_link_api.go — API locale pilotant l'accès distant (ajean.link) depuis l'UI // web de Jean, pour éviter le terminal. Le panneau « Accès distant » ouvre une // popup app.ajean.link/connect.html qui gère compte + paiement puis renvoie une -// clé de liaison (jl_…) ; l'UI la POSTe ici, et Jean fait le `jean link` tout +// clé de liaison (jl_…) ; l'UI la POSTe ici, et Jean fait le `ajean link` tout // seul (écrit le token + (re)démarre le service). // // Ces routes ne sont accessibles qu'en LOCAL : à travers le tunnel du relais, @@ -79,7 +79,7 @@ func handleLinkStart(w http.ResponseWriter, r *http.Request) { } // handleLinkDisconnect (POST /api/link/disconnect) : arrête le service et oublie -// la clé (équivalent `jean link stop` + `jean link logout`). +// la clé (équivalent `ajean link stop` + `ajean link logout`). func handleLinkDisconnect(w http.ResponseWriter, r *http.Request) { _ = linkServiceCtl("stop") _ = removeLinkToken() @@ -89,7 +89,7 @@ func handleLinkDisconnect(w http.ResponseWriter, r *http.Request) { // handleLinkPairCode (POST /api/link/paircode) : génère un code d'appairage frais // (usage unique, TTL 10 min) + l'empreinte E2E, à saisir UNE fois dans le portail // distant lors de la première connexion (confirmation de la boîte noire). Évite le -// détour par `jean link code` en terminal. +// détour par `ajean link code` en terminal. func handleLinkPairCode(w http.ResponseWriter, r *http.Request) { code, err := newPairCode() if err != nil { diff --git a/internal/ajean/web_server.go b/internal/ajean/web_server.go index 0b8d514..fe04fa0 100644 --- a/internal/ajean/web_server.go +++ b/internal/ajean/web_server.go @@ -45,10 +45,10 @@ func cmdWeb(args []string) error { return err } } - fmt.Printf("[jean web] http://%s (Ctrl-C pour arrêter)\n", addr) + fmt.Printf("[ajean web] http://%s (Ctrl-C pour arrêter)\n", addr) if readWebKey() == "" { fmt.Printf("%s API de pilotage NON protégée (aucune clé). Avant de l'exposer sur internet :\n", yellow("[!]")) - fmt.Printf(" %s\n", bold("jean set-web-key")) + fmt.Printf(" %s\n", bold("ajean set-web-key")) } else { fmt.Printf("%s API protégée par clé (Authorization: Bearer …)\n", green("[ok]")) } @@ -56,13 +56,13 @@ func cmdWeb(args []string) error { } // newWebMux construit le routeur HTTP de l'UI web. Extrait de cmdWeb pour être -// réutilisé par `jean link`, qui sert ce même mux à travers le tunnel sans +// réutilisé par `ajean link`, qui sert ce même mux à travers le tunnel sans // repasser par un écouteur TCP local. var convLoadOnce sync.Once func newWebMux() *http.ServeMux { - // Charge l'état de conversation persisté (une fois par process : jean web ET - // jean link serve appellent newWebMux). + // Charge l'état de conversation persisté (une fois par process : ajean web ET + // ajean link serve appellent newWebMux). convLoadOnce.Do(LoadConversation) // Pré-chauffe les serveurs MCP en tâche de fond : sinon le handshake (plusieurs // secondes pour un serveur lancé via npx) est payé par le premier message. @@ -120,7 +120,7 @@ func newWebMux() *http.ServeMux { api("/api/apikey", handleAPIKey) api("/api/oai/public", handleOAIPublic) api("/api/link/status", handleLinkStatus) // état de l'accès distant (ajean.link) - api("/api/link/connect", handleLinkConnect) // clé de liaison remise par connect.html → jean link + api("/api/link/connect", handleLinkConnect) // clé de liaison remise par connect.html → ajean link api("/api/link/start", handleLinkStart) // (re)démarre le tunnel avec la clé déjà enregistrée api("/api/link/disconnect", handleLinkDisconnect) // arrête le lien + oublie la clé api("/api/link/paircode", handleLinkPairCode) // code d'appairage + empreinte pour la 1re connexion