From 110458c133507620500afdd242bdefb4d5b71d72 Mon Sep 17 00:00:00 2001 From: nathaninline Date: Tue, 4 Aug 2026 13:59:06 +0200 Subject: [PATCH] Renommage AJEAN (1/3) : module, chemins de donnees et auto-MAJ Socle du renommage jean -> AJEAN, sans aucun impact visible pour le parc installe. Trois briques : - module github.com/nathaninline/ajean, cmd/ajean, internal/ajean, package ajean. Purement interne. - AjeanHome() remplace JeanHome(). Les defauts deviennent %ProgramData%\ajean et /etc/ajean, avec migration du dossier existant au premier lancement. La migration est un os.Rename dans le dossier parent de l'ancien chemin : intra-volume, donc atomique et instantane meme avec des .gguf de plusieurs dizaines de Go. Si le rename echoue (handle ouvert sous Windows, droits, volume en lecture seule) on CONTINUE sur l'ancien chemin et on retentera : jamais de copie, jamais de suppression, le pire cas est "rien n'a bouge". $AJEAN_HOME et $JEAN_HOME restent tous deux honores, ainsi que /etc/default/ajean et /etc/default/jean : un chemin impose a la main est un ordre, on ne le migre pas. - l'auto-MAJ accepte desormais ajean-- ET jean--, dans cet ordre. Indispensable dans les deux sens : les binaires deja installes ne connaissent que jean-*, et ce binaire-ci doit pouvoir s'installer depuis une release anterieure au renommage. Couvert par des tests, dont le repli quand un handle ouvert bloque le rename sous Windows. --- README.md | 4 +- cmd/{jean => ajean}/icon.ico | Bin cmd/{jean => ajean}/icon.png | Bin cmd/{jean => ajean}/main.go | 8 +- .../resource_windows_amd64.syso | Bin .../resource_windows_arm64.syso | Bin cmd/{jean => ajean}/versioninfo.json | 2 +- go.mod | 2 +- internal/{jean => ajean}/backend_build.go | 2 +- .../backend_buildfilter_test.go | 2 +- internal/{jean => ajean}/backend_catalog.go | 2 +- internal/{jean => ajean}/backend_config.go | 2 +- internal/{jean => ajean}/backend_gpu.go | 2 +- internal/{jean => ajean}/backend_llamacpp.go | 6 +- .../{jean => ajean}/backend_model_dirs.go | 16 +-- .../backend_model_dirs_test.go | 2 +- internal/{jean => ajean}/backend_models.go | 6 +- .../{jean => ajean}/backend_models_dl_test.go | 2 +- internal/{jean => ajean}/backend_prebuilt.go | 4 +- .../{jean => ajean}/backend_prebuilt_test.go | 2 +- internal/{jean => ajean}/backend_presets.go | 2 +- .../{jean => ajean}/backend_presets_test.go | 2 +- internal/{jean => ajean}/backend_serve.go | 6 +- internal/{jean => ajean}/chat_agent.go | 4 +- internal/{jean => ajean}/chat_cmd.go | 2 +- internal/{jean => ajean}/chat_compact.go | 2 +- internal/{jean => ajean}/chat_compact_test.go | 2 +- internal/{jean => ajean}/chat_conversation.go | 8 +- .../{jean => ajean}/chat_conversation_test.go | 2 +- internal/{jean => ajean}/chat_diff.go | 2 +- internal/{jean => ajean}/chat_internet.go | 6 +- .../{jean => ajean}/chat_internet_tools.go | 2 +- internal/{jean => ajean}/chat_memory.go | 2 +- internal/{jean => ajean}/chat_tools.go | 2 +- .../{jean => ajean}/chat_tools_write_test.go | 2 +- internal/{jean => ajean}/chat_workspace.go | 6 +- .../{jean => ajean}/chat_workspace_test.go | 2 +- internal/{jean => ajean}/cli_app.go | 2 +- internal/{jean => ajean}/doc.go | 2 +- internal/{jean => ajean}/llm_bench.go | 6 +- internal/{jean => ajean}/llm_client.go | 2 +- internal/{jean => ajean}/llm_oai.go | 6 +- internal/{jean => ajean}/llm_testcmd.go | 2 +- internal/{jean => ajean}/mcp_client.go | 2 +- internal/{jean => ajean}/mcp_client_test.go | 2 +- internal/{jean => ajean}/mcp_config.go | 4 +- internal/{jean => ajean}/relay_e2e.go | 6 +- internal/{jean => ajean}/relay_e2eauth.go | 12 +-- .../{jean => ajean}/relay_e2eauth_test.go | 2 +- internal/{jean => ajean}/relay_link.go | 20 ++-- internal/{jean => ajean}/run.go | 70 ++++++------ internal/{jean => ajean}/sys_brand_icon.go | 2 +- .../{jean => ajean}/sys_console_darwin.go | 2 +- internal/{jean => ajean}/sys_console_other.go | 2 +- .../{jean => ajean}/sys_console_windows.go | 2 +- .../{jean => ajean}/sys_firstrun_other.go | 2 +- .../{jean => ajean}/sys_firstrun_windows.go | 6 +- .../sys_firstrun_windows_test.go | 2 +- .../{jean => ajean}/sys_install_darwin.go | 2 +- internal/{jean => ajean}/sys_install_linux.go | 2 +- .../{jean => ajean}/sys_install_windows.go | 8 +- internal/ajean/sys_migrate.go | 80 ++++++++++++++ internal/ajean/sys_migrate_test.go | 102 ++++++++++++++++++ internal/{jean => ajean}/sys_paths.go | 12 +-- internal/{jean => ajean}/sys_platform_unix.go | 31 +++--- .../{jean => ajean}/sys_platform_windows.go | 23 ++-- internal/{jean => ajean}/sys_service.go | 2 +- .../{jean => ajean}/sys_service_darwin.go | 12 +-- internal/{jean => ajean}/sys_service_linux.go | 2 +- .../{jean => ajean}/sys_service_windows.go | 10 +- internal/{jean => ajean}/sys_splash_other.go | 2 +- .../{jean => ajean}/sys_splash_windows.go | 2 +- internal/{jean => ajean}/sys_tray_darwin.go | 2 +- internal/{jean => ajean}/sys_tray_other.go | 2 +- internal/{jean => ajean}/sys_tray_windows.go | 2 +- internal/{jean => ajean}/sys_tty.go | 2 +- internal/{jean => ajean}/sys_update.go | 69 +++++++----- internal/ajean/sys_update_asset_test.go | 63 +++++++++++ internal/{jean => ajean}/ui/index.html | 0 internal/{jean => ajean}/ui/marked.min.js | 0 .../{jean => ajean}/ui/src/index.tmpl.html | 0 internal/{jean => ajean}/ui/src/js/00-md.js | 0 .../{jean => ajean}/ui/src/js/01-theme.js | 0 .../{jean => ajean}/ui/src/js/02-prefs.js | 0 .../{jean => ajean}/ui/src/js/03-ui-kit.js | 0 internal/{jean => ajean}/ui/src/js/04-api.js | 0 .../{jean => ajean}/ui/src/js/05-status.js | 0 .../{jean => ajean}/ui/src/js/06-settings.js | 0 .../{jean => ajean}/ui/src/js/07-models.js | 0 .../ui/src/js/08-chat-render.js | 0 .../{jean => ajean}/ui/src/js/09-stream.js | 0 .../{jean => ajean}/ui/src/js/10-llamacpp.js | 0 internal/{jean => ajean}/ui/src/js/12-mcp.js | 0 .../{jean => ajean}/ui/src/js/13-remote.js | 0 internal/{jean => ajean}/ui/src/styles.css | 0 internal/{jean => ajean}/web_api.go | 6 +- internal/{jean => ajean}/web_auth.go | 6 +- internal/{jean => ajean}/web_chat.go | 2 +- internal/{jean => ajean}/web_link_api.go | 2 +- internal/{jean => ajean}/web_llamacpp.go | 4 +- internal/{jean => ajean}/web_prefs.go | 4 +- internal/{jean => ajean}/web_server.go | 2 +- internal/{jean => ajean}/web_sysprompt.go | 4 +- tools/gen-icon/main.go | 6 +- 104 files changed, 514 insertions(+), 226 deletions(-) rename cmd/{jean => ajean}/icon.ico (100%) rename cmd/{jean => ajean}/icon.png (100%) rename cmd/{jean => ajean}/main.go (74%) rename cmd/{jean => ajean}/resource_windows_amd64.syso (100%) rename cmd/{jean => ajean}/resource_windows_arm64.syso (100%) rename cmd/{jean => ajean}/versioninfo.json (90%) rename internal/{jean => ajean}/backend_build.go (99%) rename internal/{jean => ajean}/backend_buildfilter_test.go (99%) rename internal/{jean => ajean}/backend_catalog.go (99%) rename internal/{jean => ajean}/backend_config.go (99%) rename internal/{jean => ajean}/backend_gpu.go (99%) rename internal/{jean => ajean}/backend_llamacpp.go (99%) rename internal/{jean => ajean}/backend_model_dirs.go (92%) rename internal/{jean => ajean}/backend_model_dirs_test.go (99%) rename internal/{jean => ajean}/backend_models.go (99%) rename internal/{jean => ajean}/backend_models_dl_test.go (99%) rename internal/{jean => ajean}/backend_prebuilt.go (99%) rename internal/{jean => ajean}/backend_prebuilt_test.go (99%) rename internal/{jean => ajean}/backend_presets.go (99%) rename internal/{jean => ajean}/backend_presets_test.go (98%) rename internal/{jean => ajean}/backend_serve.go (98%) rename internal/{jean => ajean}/chat_agent.go (97%) rename internal/{jean => ajean}/chat_cmd.go (99%) rename internal/{jean => ajean}/chat_compact.go (99%) rename internal/{jean => ajean}/chat_compact_test.go (99%) rename internal/{jean => ajean}/chat_conversation.go (99%) rename internal/{jean => ajean}/chat_conversation_test.go (99%) rename internal/{jean => ajean}/chat_diff.go (99%) rename internal/{jean => ajean}/chat_internet.go (99%) rename internal/{jean => ajean}/chat_internet_tools.go (99%) rename internal/{jean => ajean}/chat_memory.go (99%) rename internal/{jean => ajean}/chat_tools.go (99%) rename internal/{jean => ajean}/chat_tools_write_test.go (98%) rename internal/{jean => ajean}/chat_workspace.go (94%) rename internal/{jean => ajean}/chat_workspace_test.go (98%) rename internal/{jean => ajean}/cli_app.go (99%) rename internal/{jean => ajean}/doc.go (98%) rename internal/{jean => ajean}/llm_bench.go (98%) rename internal/{jean => ajean}/llm_client.go (99%) rename internal/{jean => ajean}/llm_oai.go (98%) rename internal/{jean => ajean}/llm_testcmd.go (99%) rename internal/{jean => ajean}/mcp_client.go (99%) rename internal/{jean => ajean}/mcp_client_test.go (99%) rename internal/{jean => ajean}/mcp_config.go (99%) rename internal/{jean => ajean}/relay_e2e.go (98%) rename internal/{jean => ajean}/relay_e2eauth.go (96%) rename internal/{jean => ajean}/relay_e2eauth_test.go (99%) rename internal/{jean => ajean}/relay_link.go (97%) rename internal/{jean => ajean}/run.go (79%) rename internal/{jean => ajean}/sys_brand_icon.go (99%) rename internal/{jean => ajean}/sys_console_darwin.go (99%) rename internal/{jean => ajean}/sys_console_other.go (97%) rename internal/{jean => ajean}/sys_console_windows.go (99%) rename internal/{jean => ajean}/sys_firstrun_other.go (97%) rename internal/{jean => ajean}/sys_firstrun_windows.go (99%) rename internal/{jean => ajean}/sys_firstrun_windows_test.go (99%) rename internal/{jean => ajean}/sys_install_darwin.go (99%) rename internal/{jean => ajean}/sys_install_linux.go (99%) rename internal/{jean => ajean}/sys_install_windows.go (98%) create mode 100644 internal/ajean/sys_migrate.go create mode 100644 internal/ajean/sys_migrate_test.go rename internal/{jean => ajean}/sys_paths.go (93%) rename internal/{jean => ajean}/sys_platform_unix.go (92%) rename internal/{jean => ajean}/sys_platform_windows.go (95%) rename internal/{jean => ajean}/sys_service.go (99%) rename internal/{jean => ajean}/sys_service_darwin.go (96%) rename internal/{jean => ajean}/sys_service_linux.go (99%) rename internal/{jean => ajean}/sys_service_windows.go (95%) rename internal/{jean => ajean}/sys_splash_other.go (94%) rename internal/{jean => ajean}/sys_splash_windows.go (99%) rename internal/{jean => ajean}/sys_tray_darwin.go (99%) rename internal/{jean => ajean}/sys_tray_other.go (97%) rename internal/{jean => ajean}/sys_tray_windows.go (99%) rename internal/{jean => ajean}/sys_tty.go (94%) rename internal/{jean => ajean}/sys_update.go (84%) create mode 100644 internal/ajean/sys_update_asset_test.go rename internal/{jean => ajean}/ui/index.html (100%) rename internal/{jean => ajean}/ui/marked.min.js (100%) rename internal/{jean => ajean}/ui/src/index.tmpl.html (100%) rename internal/{jean => ajean}/ui/src/js/00-md.js (100%) rename internal/{jean => ajean}/ui/src/js/01-theme.js (100%) rename internal/{jean => ajean}/ui/src/js/02-prefs.js (100%) rename internal/{jean => ajean}/ui/src/js/03-ui-kit.js (100%) rename internal/{jean => ajean}/ui/src/js/04-api.js (100%) rename internal/{jean => ajean}/ui/src/js/05-status.js (100%) rename internal/{jean => ajean}/ui/src/js/06-settings.js (100%) rename internal/{jean => ajean}/ui/src/js/07-models.js (100%) rename internal/{jean => ajean}/ui/src/js/08-chat-render.js (100%) rename internal/{jean => ajean}/ui/src/js/09-stream.js (100%) rename internal/{jean => ajean}/ui/src/js/10-llamacpp.js (100%) rename internal/{jean => ajean}/ui/src/js/12-mcp.js (100%) rename internal/{jean => ajean}/ui/src/js/13-remote.js (100%) rename internal/{jean => ajean}/ui/src/styles.css (100%) rename internal/{jean => ajean}/web_api.go (99%) rename internal/{jean => ajean}/web_auth.go (96%) rename internal/{jean => ajean}/web_chat.go (99%) rename internal/{jean => ajean}/web_link_api.go (99%) rename internal/{jean => ajean}/web_llamacpp.go (99%) rename internal/{jean => ajean}/web_prefs.go (96%) rename internal/{jean => ajean}/web_server.go (99%) rename internal/{jean => ajean}/web_sysprompt.go (95%) diff --git a/README.md b/README.md index 4b184c6..ed52c0b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Aucune dépendance à l'exécution, aucun flag CMake à retenir, aucun conteneur ### 1. Le binaire ```bash -curl -L -o jean https://github.com/nathaninline/jean/releases/latest/download/jean-linux-amd64 +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 ``` @@ -264,7 +264,7 @@ Chaque appel `/api/*` présente alors `Authorization: Bearer ` : Go 1.25+. AJEAN est écrit à 100 % en Go, l'interface est embarquée via `go:embed` : ```bash -git clone https://github.com/nathaninline/jean.git +git clone https://github.com/nathaninline/ajean.git cd jean CGO_ENABLED=0 go build -o jean ./cmd/jean ``` diff --git a/cmd/jean/icon.ico b/cmd/ajean/icon.ico similarity index 100% rename from cmd/jean/icon.ico rename to cmd/ajean/icon.ico diff --git a/cmd/jean/icon.png b/cmd/ajean/icon.png similarity index 100% rename from cmd/jean/icon.png rename to cmd/ajean/icon.png diff --git a/cmd/jean/main.go b/cmd/ajean/main.go similarity index 74% rename from cmd/jean/main.go rename to cmd/ajean/main.go index b7559cd..51b0427 100644 --- a/cmd/jean/main.go +++ b/cmd/ajean/main.go @@ -1,4 +1,4 @@ -// Point d'entrée du binaire jean. Tout le code vit dans internal/jean ; ce +// Point d'entrée du binaire ajean. Tout le code vit dans internal/jean ; ce // dossier ne porte que le main() et les ressources Windows (.syso, icône, // versioninfo) qui doivent résider dans le dossier du package main. // @@ -7,11 +7,11 @@ // les .syso après avoir bumpé la version : `go generate ./...` // (nécessite : go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest) // -//go:generate go run github.com/nathaninline/jean/tools/gen-icon icon.ico +//go:generate go run github.com/nathaninline/ajean/tools/gen-icon icon.ico //go:generate goversioninfo -64 -icon=icon.ico -o resource_windows_amd64.syso versioninfo.json //go:generate goversioninfo -64 -arm -icon=icon.ico -o resource_windows_arm64.syso versioninfo.json package main -import "github.com/nathaninline/jean/internal/jean" +import "github.com/nathaninline/ajean/internal/ajean" -func main() { jean.Main() } +func main() { ajean.Main() } diff --git a/cmd/jean/resource_windows_amd64.syso b/cmd/ajean/resource_windows_amd64.syso similarity index 100% rename from cmd/jean/resource_windows_amd64.syso rename to cmd/ajean/resource_windows_amd64.syso diff --git a/cmd/jean/resource_windows_arm64.syso b/cmd/ajean/resource_windows_arm64.syso similarity index 100% rename from cmd/jean/resource_windows_arm64.syso rename to cmd/ajean/resource_windows_arm64.syso diff --git a/cmd/jean/versioninfo.json b/cmd/ajean/versioninfo.json similarity index 90% rename from cmd/jean/versioninfo.json rename to cmd/ajean/versioninfo.json index 507563d..3b3f5dd 100644 --- a/cmd/jean/versioninfo.json +++ b/cmd/ajean/versioninfo.json @@ -26,7 +26,7 @@ "OriginalFilename": "jean.exe", "ProductName": "AJEAN", "ProductVersion": "0.6.15", - "Comments": "https://github.com/nathaninline/jean — projet open source (MIT)" + "Comments": "https://github.com/nathaninline/ajean — projet open source (MIT)" }, "VarFileInfo": { "Translation": { diff --git a/go.mod b/go.mod index 5e588df..4d7ff1d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/nathaninline/jean +module github.com/nathaninline/ajean go 1.25.0 diff --git a/internal/jean/backend_build.go b/internal/ajean/backend_build.go similarity index 99% rename from internal/jean/backend_build.go rename to internal/ajean/backend_build.go index 42c7639..f796c83 100644 --- a/internal/jean/backend_build.go +++ b/internal/ajean/backend_build.go @@ -1,6 +1,6 @@ // backend_build.go — machinerie de compilation de llama.cpp : détection du // plan de build (CUDA/ROCm/Metal/Vulkan/CPU), cmake, suivi de progression, logs. -package jean +package ajean import ( "bufio" diff --git a/internal/jean/backend_buildfilter_test.go b/internal/ajean/backend_buildfilter_test.go similarity index 99% rename from internal/jean/backend_buildfilter_test.go rename to internal/ajean/backend_buildfilter_test.go index f1d93b1..a1dd2ab 100644 --- a/internal/jean/backend_buildfilter_test.go +++ b/internal/ajean/backend_buildfilter_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import "testing" diff --git a/internal/jean/backend_catalog.go b/internal/ajean/backend_catalog.go similarity index 99% rename from internal/jean/backend_catalog.go rename to internal/ajean/backend_catalog.go index eb7b13b..d63feb3 100644 --- a/internal/jean/backend_catalog.go +++ b/internal/ajean/backend_catalog.go @@ -1,4 +1,4 @@ -package jean +package ajean // backend_catalog.go — récupère la liste de modèles curatée servie par ajean.link et la // combine avec les infos matérielles locales, pour que l'écran d'accueil (à diff --git a/internal/jean/backend_config.go b/internal/ajean/backend_config.go similarity index 99% rename from internal/jean/backend_config.go rename to internal/ajean/backend_config.go index 75b6f7a..c8df85c 100644 --- a/internal/jean/backend_config.go +++ b/internal/ajean/backend_config.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/backend_gpu.go b/internal/ajean/backend_gpu.go similarity index 99% rename from internal/jean/backend_gpu.go rename to internal/ajean/backend_gpu.go index 9a16692..c70b6c6 100644 --- a/internal/jean/backend_gpu.go +++ b/internal/ajean/backend_gpu.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/backend_llamacpp.go b/internal/ajean/backend_llamacpp.go similarity index 99% rename from internal/jean/backend_llamacpp.go rename to internal/ajean/backend_llamacpp.go index 4ea1805..3748b93 100644 --- a/internal/jean/backend_llamacpp.go +++ b/internal/ajean/backend_llamacpp.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "fmt" @@ -82,7 +82,7 @@ func llamacppRepoDir() string { } func defaultRepoDir() string { - return filepath.Join(JeanHome(), "backends", "llama.cpp") + return filepath.Join(AjeanHome(), "backends", "llama.cpp") } // findRepoRoot walks up from a binary path (…/build/bin/llama-server) looking @@ -510,7 +510,7 @@ func sanitizeBackendName(name string) string { // dossier backends (../, chemin absolu…). func backendDir(name string) (string, error) { name = sanitizeBackendName(name) - root, err := filepath.Abs(filepath.Join(JeanHome(), "backends")) + root, err := filepath.Abs(filepath.Join(AjeanHome(), "backends")) if err != nil { return "", err } diff --git a/internal/jean/backend_model_dirs.go b/internal/ajean/backend_model_dirs.go similarity index 92% rename from internal/jean/backend_model_dirs.go rename to internal/ajean/backend_model_dirs.go index 3bef6f8..c9e843c 100644 --- a/internal/jean/backend_model_dirs.go +++ b/internal/ajean/backend_model_dirs.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "encoding/json" @@ -15,7 +15,7 @@ import ( // (disque dur externe, second SSD, montage réseau…). La liste est persistée // dans $JEAN_HOME/model_dirs.json — PAS dans config.env, que le changement de // preset réécrit — et peut aussi venir de $JEAN_MODEL_DIRS. -func modelDirsPath() string { return filepath.Join(JeanHome(), "model_dirs.json") } +func modelDirsPath() string { return filepath.Join(AjeanHome(), "model_dirs.json") } // modelDirsListSep sépare les dossiers dans $JEAN_MODEL_DIRS (':' sous Unix, // ';' sous Windows, comme le PATH). @@ -39,7 +39,7 @@ func extraModelDirs() []string { if v := strings.TrimSpace(os.Getenv("JEAN_MODEL_DIRS")); v != "" { raw = append(raw, strings.Split(v, modelDirsListSep())...) } - return dedupDirs(raw, JeanHome()) + return dedupDirs(raw, AjeanHome()) } // dedupDirs nettoie une liste de chemins : vides retirés, chemins absolus @@ -82,17 +82,17 @@ func normDir(p string) string { // modelDirs renvoie tous les dossiers où chercher un .gguf : JEAN_HOME d'abord // (il reste la destination des téléchargements), puis les dossiers ajoutés. func modelDirs() []string { - return append([]string{JeanHome()}, extraModelDirs()...) + return append([]string{AjeanHome()}, extraModelDirs()...) } // saveExtraModelDirs écrit la liste des dossiers supplémentaires sur disque. func saveExtraModelDirs(dirs []string) error { - clean := dedupDirs(dirs, JeanHome()) + clean := dedupDirs(dirs, AjeanHome()) b, err := json.MarshalIndent(clean, "", " ") if err != nil { return err } - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } return os.WriteFile(modelDirsPath(), append(b, '\n'), 0o644) @@ -147,7 +147,7 @@ func resolveModelPath(name string) (string, error) { return p, nil } } - return filepath.Join(JeanHome(), base), nil // introuvable : l'appelant décidera + return filepath.Join(AjeanHome(), base), nil // introuvable : l'appelant décidera } // modelDirCount compte les .gguf lisibles d'un dossier (-1 si illisible). @@ -211,7 +211,7 @@ func handleModelDirs(w http.ResponseWriter, r *http.Request) { sendJSON(w, 200, map[string]any{"ok": true}) return } - out := []map[string]any{{"path": JeanHome(), "home": true, "count": modelDirCount(JeanHome()), "exists": true}} + out := []map[string]any{{"path": AjeanHome(), "home": true, "count": modelDirCount(AjeanHome()), "exists": true}} for _, d := range extraModelDirs() { n := modelDirCount(d) out = append(out, map[string]any{"path": d, "home": false, "count": n, "exists": n >= 0}) diff --git a/internal/jean/backend_model_dirs_test.go b/internal/ajean/backend_model_dirs_test.go similarity index 99% rename from internal/jean/backend_model_dirs_test.go rename to internal/ajean/backend_model_dirs_test.go index f2dc780..f7700a8 100644 --- a/internal/jean/backend_model_dirs_test.go +++ b/internal/ajean/backend_model_dirs_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "os" diff --git a/internal/jean/backend_models.go b/internal/ajean/backend_models.go similarity index 99% rename from internal/jean/backend_models.go rename to internal/ajean/backend_models.go index 379b34d..4d27b18 100644 --- a/internal/jean/backend_models.go +++ b/internal/ajean/backend_models.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" @@ -97,7 +97,7 @@ func downloadDestPath(name string) (string, error) { if !strings.HasSuffix(strings.ToLower(base), ".gguf") { return "", fmt.Errorf("seuls les fichiers .gguf sont acceptés") } - return filepath.Join(JeanHome(), base), nil + return filepath.Join(AjeanHome(), base), nil } // modelFromPresetContent extracts the MODEL= value from a preset's config.env @@ -600,7 +600,7 @@ func handleModelDownloadCancel(w http.ResponseWriter, r *http.Request) { // (its state lived in memory), so the partial file would otherwise sit there // forever eating disk. func cleanStalePartFiles() { - matches, err := filepath.Glob(filepath.Join(JeanHome(), "*.gguf.part")) + matches, err := filepath.Glob(filepath.Join(AjeanHome(), "*.gguf.part")) if err != nil { return } diff --git a/internal/jean/backend_models_dl_test.go b/internal/ajean/backend_models_dl_test.go similarity index 99% rename from internal/jean/backend_models_dl_test.go rename to internal/ajean/backend_models_dl_test.go index b295072..ec8a09f 100644 --- a/internal/jean/backend_models_dl_test.go +++ b/internal/ajean/backend_models_dl_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bytes" diff --git a/internal/jean/backend_prebuilt.go b/internal/ajean/backend_prebuilt.go similarity index 99% rename from internal/jean/backend_prebuilt.go rename to internal/ajean/backend_prebuilt.go index 97aefe1..13cea55 100644 --- a/internal/jean/backend_prebuilt.go +++ b/internal/ajean/backend_prebuilt.go @@ -8,7 +8,7 @@ // CUDA officiel pour Linux (on retombe sur Vulkan — la compilation locale // reste la voie CUDA sous Linux). La compilation locale reste indispensable // pour les forks (ex. PrismML). -package jean +package ajean import ( "archive/tar" @@ -37,7 +37,7 @@ type ghAsset struct { } func prebuiltDir() string { - return filepath.Join(JeanHome(), "backends", "llama.cpp-prebuilt") + return filepath.Join(AjeanHome(), "backends", "llama.cpp-prebuilt") } // prebuiltVersion lit le marqueur VERSION du dossier prebuilt : "tag cudaVer" diff --git a/internal/jean/backend_prebuilt_test.go b/internal/ajean/backend_prebuilt_test.go similarity index 99% rename from internal/jean/backend_prebuilt_test.go rename to internal/ajean/backend_prebuilt_test.go index e513cd6..98719b0 100644 --- a/internal/jean/backend_prebuilt_test.go +++ b/internal/ajean/backend_prebuilt_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "archive/tar" diff --git a/internal/jean/backend_presets.go b/internal/ajean/backend_presets.go similarity index 99% rename from internal/jean/backend_presets.go rename to internal/ajean/backend_presets.go index 4a9794e..cddec24 100644 --- a/internal/jean/backend_presets.go +++ b/internal/ajean/backend_presets.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/backend_presets_test.go b/internal/ajean/backend_presets_test.go similarity index 98% rename from internal/jean/backend_presets_test.go rename to internal/ajean/backend_presets_test.go index d6d3dc0..1d896e9 100644 --- a/internal/jean/backend_presets_test.go +++ b/internal/ajean/backend_presets_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import "testing" diff --git a/internal/jean/backend_serve.go b/internal/ajean/backend_serve.go similarity index 98% rename from internal/jean/backend_serve.go rename to internal/ajean/backend_serve.go index d706298..d6bf09c 100644 --- a/internal/jean/backend_serve.go +++ b/internal/ajean/backend_serve.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "fmt" @@ -34,7 +34,7 @@ func cmdServe(args []string) error { return fmt.Errorf("modèle introuvable : %s", model) } if !filepath.IsAbs(bin) { - bin = filepath.Join(JeanHome(), bin) + bin = filepath.Join(AjeanHome(), bin) } // Make sure llama-server can find its bundled shared libraries (the .so/.dll @@ -102,7 +102,7 @@ func cmdServe(args []string) error { // Working dir = JEAN_HOME so relative paths in EXTRA_ARGS (e.g. --mmproj // mmproj-F16.gguf) still resolve. - _ = os.Chdir(JeanHome()) + _ = os.Chdir(AjeanHome()) fmt.Fprintf(os.Stderr, "[jean serve] %s model=%s port=%s\n", bin, filepath.Base(model), get("PORT", "8080")) diff --git a/internal/jean/chat_agent.go b/internal/ajean/chat_agent.go similarity index 97% rename from internal/jean/chat_agent.go rename to internal/ajean/chat_agent.go index fdbaccd..7ed8921 100644 --- a/internal/jean/chat_agent.go +++ b/internal/ajean/chat_agent.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "fmt" @@ -27,7 +27,7 @@ func agentEnabled() bool { } func setAgentEnabled(on bool) error { - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) // On nettoie systématiquement les anciens drapeaux pour ne pas garder un // état fantôme « à moitié activé » hérité de l'ancien modèle à deux toggles. _ = os.Remove(legacyToolsFlag()) diff --git a/internal/jean/chat_cmd.go b/internal/ajean/chat_cmd.go similarity index 99% rename from internal/jean/chat_cmd.go rename to internal/ajean/chat_cmd.go index a6cb6dd..f58ab3a 100644 --- a/internal/jean/chat_cmd.go +++ b/internal/ajean/chat_cmd.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/chat_compact.go b/internal/ajean/chat_compact.go similarity index 99% rename from internal/jean/chat_compact.go rename to internal/ajean/chat_compact.go index 23be98e..b9509ee 100644 --- a/internal/jean/chat_compact.go +++ b/internal/ajean/chat_compact.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bytes" diff --git a/internal/jean/chat_compact_test.go b/internal/ajean/chat_compact_test.go similarity index 99% rename from internal/jean/chat_compact_test.go rename to internal/ajean/chat_compact_test.go index 91f6dfa..1bf05ae 100644 --- a/internal/jean/chat_compact_test.go +++ b/internal/ajean/chat_compact_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "strings" diff --git a/internal/jean/chat_conversation.go b/internal/ajean/chat_conversation.go similarity index 99% rename from internal/jean/chat_conversation.go rename to internal/ajean/chat_conversation.go index 9a2420b..1656121 100644 --- a/internal/jean/chat_conversation.go +++ b/internal/ajean/chat_conversation.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" @@ -65,10 +65,10 @@ var conv = func() *Conversation { return c }() -// convPath = fichier de persistance sur la box jean (JeanHome = /etc/jean en +// convPath = fichier de persistance sur la box jean (AjeanHome = /etc/jean en // prod). En clair : la box déchiffre déjà pour lancer le modèle, le relais reste // aveugle — le persister ici ne change rien à la posture E2E. -func convPath() string { return filepath.Join(JeanHome(), "conversation.json") } +func convPath() string { return filepath.Join(AjeanHome(), "conversation.json") } // LoadConversation recharge l'état persisté au démarrage du process. Sans fichier // (première fois) on part d'une conversation vide. @@ -94,7 +94,7 @@ func (c *Conversation) persist() { if err != nil { return } - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) tmp := convPath() + ".tmp" if err := os.WriteFile(tmp, b, 0o600); err != nil { return diff --git a/internal/jean/chat_conversation_test.go b/internal/ajean/chat_conversation_test.go similarity index 99% rename from internal/jean/chat_conversation_test.go rename to internal/ajean/chat_conversation_test.go index 585b3b8..a46969d 100644 --- a/internal/jean/chat_conversation_test.go +++ b/internal/ajean/chat_conversation_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" diff --git a/internal/jean/chat_diff.go b/internal/ajean/chat_diff.go similarity index 99% rename from internal/jean/chat_diff.go rename to internal/ajean/chat_diff.go index 11e852c..bd355a0 100644 --- a/internal/jean/chat_diff.go +++ b/internal/ajean/chat_diff.go @@ -1,4 +1,4 @@ -package jean +package ajean // chat_diff.go — petit diff ligne à ligne pour l'interface : quand l'IA modifie // un fichier (outil `edit`) ou une page de mémoire (`mem_edit`, `mem_add`), on diff --git a/internal/jean/chat_internet.go b/internal/ajean/chat_internet.go similarity index 99% rename from internal/jean/chat_internet.go rename to internal/ajean/chat_internet.go index 3547cb9..0d9c947 100644 --- a/internal/jean/chat_internet.go +++ b/internal/ajean/chat_internet.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bytes" @@ -74,7 +74,7 @@ func writeCrawlKey(key string) error { } return nil } - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) return os.WriteFile(crawlKeyPath(), []byte(key+"\n"), 0o600) } @@ -97,7 +97,7 @@ func internetEnabled() bool { } func setInternetEnabled(on bool) error { - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) if on { f, err := os.Create(internetFlag()) if err != nil { diff --git a/internal/jean/chat_internet_tools.go b/internal/ajean/chat_internet_tools.go similarity index 99% rename from internal/jean/chat_internet_tools.go rename to internal/ajean/chat_internet_tools.go index 9a6c632..a0f6378 100644 --- a/internal/jean/chat_internet_tools.go +++ b/internal/ajean/chat_internet_tools.go @@ -1,6 +1,6 @@ // 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`. -package jean +package ajean import ( "fmt" diff --git a/internal/jean/chat_memory.go b/internal/ajean/chat_memory.go similarity index 99% rename from internal/jean/chat_memory.go rename to internal/ajean/chat_memory.go index c85824e..03be346 100644 --- a/internal/jean/chat_memory.go +++ b/internal/ajean/chat_memory.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "errors" diff --git a/internal/jean/chat_tools.go b/internal/ajean/chat_tools.go similarity index 99% rename from internal/jean/chat_tools.go rename to internal/ajean/chat_tools.go index a3b6d24..4cb81a5 100644 --- a/internal/jean/chat_tools.go +++ b/internal/ajean/chat_tools.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" diff --git a/internal/jean/chat_tools_write_test.go b/internal/ajean/chat_tools_write_test.go similarity index 98% rename from internal/jean/chat_tools_write_test.go rename to internal/ajean/chat_tools_write_test.go index cab2195..ab07b4d 100644 --- a/internal/jean/chat_tools_write_test.go +++ b/internal/ajean/chat_tools_write_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "os" diff --git a/internal/jean/chat_workspace.go b/internal/ajean/chat_workspace.go similarity index 94% rename from internal/jean/chat_workspace.go rename to internal/ajean/chat_workspace.go index c845175..3b5f652 100644 --- a/internal/jean/chat_workspace.go +++ b/internal/ajean/chat_workspace.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "os" @@ -26,7 +26,7 @@ var ( ) // agentWorkspace renvoie le dossier de travail de l'agent, créé au besoin. Il -// essaie plusieurs emplacements car JeanHome() vaut %ProgramData%\jean sous +// essaie plusieurs emplacements car AjeanHome() vaut %ProgramData%\jean sous // Windows, qui n'est pas inscriptible par un utilisateur non administrateur. func agentWorkspace() string { workspaceOnce.Do(func() { @@ -60,7 +60,7 @@ func workspaceCandidates() []string { if v := strings.TrimSpace(os.Getenv(workspaceEnv)); v != "" { c = append(c, v) } - c = append(c, filepath.Join(JeanHome(), "workspace")) + c = append(c, filepath.Join(AjeanHome(), "workspace")) if home, err := os.UserHomeDir(); err == nil && home != "" { c = append(c, filepath.Join(home, "jean", "workspace")) } diff --git a/internal/jean/chat_workspace_test.go b/internal/ajean/chat_workspace_test.go similarity index 98% rename from internal/jean/chat_workspace_test.go rename to internal/ajean/chat_workspace_test.go index 9ce7481..7ee8955 100644 --- a/internal/jean/chat_workspace_test.go +++ b/internal/ajean/chat_workspace_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "path/filepath" diff --git a/internal/jean/cli_app.go b/internal/ajean/cli_app.go similarity index 99% rename from internal/jean/cli_app.go rename to internal/ajean/cli_app.go index 7900a7a..32e1314 100644 --- a/internal/jean/cli_app.go +++ b/internal/ajean/cli_app.go @@ -1,4 +1,4 @@ -package jean +package ajean // cli_app.go — expérience « application » de Jean. // diff --git a/internal/jean/doc.go b/internal/ajean/doc.go similarity index 98% rename from internal/jean/doc.go rename to internal/ajean/doc.go index 87090ec..2ca1c5b 100644 --- a/internal/jean/doc.go +++ b/internal/ajean/doc.go @@ -15,4 +15,4 @@ // // Les suffixes _windows/_linux/_darwin/_unix/_other portent les contraintes de // compilation par OS. ui/index.html est GÉNÉRÉ depuis ui/src/ (voir ui/assemble.ps1). -package jean +package ajean diff --git a/internal/jean/llm_bench.go b/internal/ajean/llm_bench.go similarity index 98% rename from internal/jean/llm_bench.go rename to internal/ajean/llm_bench.go index dfe647e..ad9365c 100644 --- a/internal/jean/llm_bench.go +++ b/internal/ajean/llm_bench.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bytes" @@ -138,7 +138,7 @@ func runBench(nPrompt, nPredict int) (*benchResult, error) { // lastBenchPath stores the most recent benchmark result so the web UI can show // it without re-running. Lives in JEAN_HOME so it survives restarts. -func lastBenchPath() string { return filepath.Join(JeanHome(), ".last_bench.json") } +func lastBenchPath() string { return filepath.Join(AjeanHome(), ".last_bench.json") } // savedBench is a benchResult plus the model it was run against and a timestamp. type savedBench struct { @@ -170,7 +170,7 @@ func loadLastBench() *savedBench { // benchStorePath holds per-preset benchmark results so the UI can show each // preset's measured performance. Keyed by preset name. -func benchStorePath() string { return filepath.Join(JeanHome(), ".bench_presets.json") } +func benchStorePath() string { return filepath.Join(AjeanHome(), ".bench_presets.json") } // loadBenchStore returns the per-preset bench map (empty if none/unreadable). func loadBenchStore() map[string]savedBench { diff --git a/internal/jean/llm_client.go b/internal/ajean/llm_client.go similarity index 99% rename from internal/jean/llm_client.go rename to internal/ajean/llm_client.go index c4880cb..ad800a9 100644 --- a/internal/jean/llm_client.go +++ b/internal/ajean/llm_client.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/llm_oai.go b/internal/ajean/llm_oai.go similarity index 98% rename from internal/jean/llm_oai.go rename to internal/ajean/llm_oai.go index bc57a7c..ff31642 100644 --- a/internal/jean/llm_oai.go +++ b/internal/ajean/llm_oai.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" @@ -74,7 +74,7 @@ func runOAIFront(rawLn net.Listener, tlsCfg *tls.Config) error { // oaiPublicPath est le drapeau qui active l'accès OpenAI public (piloté par l'UI, // lu en direct → activable/coupable sans redémarrer le service de lien). -func oaiPublicPath() string { return filepath.Join(JeanHome(), ".oai_public") } +func oaiPublicPath() string { return filepath.Join(AjeanHome(), ".oai_public") } // oaiPublicEnabled indique si l'accès OpenAI public est activé pour cette machine. func oaiPublicEnabled() bool { @@ -101,7 +101,7 @@ func setOAIPublic(on bool) error { // $JEAN_HOME/certs et ne quitte jamais la machine. Toujours construite ; c'est le // démux (oaiPublicEnabled, lu en direct) qui décide de router ou non le trafic. func oaiTLSConfig() *tls.Config { - certmagic.Default.Storage = &certmagic.FileStorage{Path: filepath.Join(JeanHome(), "certs")} + 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.DisableHTTPChallenge = true // pas de :80 accessible (CGNAT) → TLS-ALPN uniquement diff --git a/internal/jean/llm_testcmd.go b/internal/ajean/llm_testcmd.go similarity index 99% rename from internal/jean/llm_testcmd.go rename to internal/ajean/llm_testcmd.go index 17eb092..5a4dae2 100644 --- a/internal/jean/llm_testcmd.go +++ b/internal/ajean/llm_testcmd.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" diff --git a/internal/jean/mcp_client.go b/internal/ajean/mcp_client.go similarity index 99% rename from internal/jean/mcp_client.go rename to internal/ajean/mcp_client.go index c1e271d..a82d63f 100644 --- a/internal/jean/mcp_client.go +++ b/internal/ajean/mcp_client.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "context" diff --git a/internal/jean/mcp_client_test.go b/internal/ajean/mcp_client_test.go similarity index 99% rename from internal/jean/mcp_client_test.go rename to internal/ajean/mcp_client_test.go index 152c968..10bd944 100644 --- a/internal/jean/mcp_client_test.go +++ b/internal/ajean/mcp_client_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "os/exec" diff --git a/internal/jean/mcp_config.go b/internal/ajean/mcp_config.go similarity index 99% rename from internal/jean/mcp_config.go rename to internal/ajean/mcp_config.go index 0dca871..308d3ce 100644 --- a/internal/jean/mcp_config.go +++ b/internal/ajean/mcp_config.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "encoding/json" @@ -149,7 +149,7 @@ func loadMCPConfigLocked() (map[string]MCPServerConfig, error) { // saveMCPConfigLocked écrit atomiquement mcp.json (write temp + rename). func saveMCPConfigLocked(servers map[string]MCPServerConfig) error { - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) f := mcpConfigFile{MCPServers: servers} b, err := json.MarshalIndent(f, "", " ") if err != nil { diff --git a/internal/jean/relay_e2e.go b/internal/ajean/relay_e2e.go similarity index 98% rename from internal/jean/relay_e2e.go rename to internal/ajean/relay_e2e.go index 8e79017..9e52578 100644 --- a/internal/jean/relay_e2e.go +++ b/internal/ajean/relay_e2e.go @@ -1,4 +1,4 @@ -package jean +package ajean // relay_e2e.go — chiffrement bout-en-bout du chat entre le NAVIGATEUR et CET agent, de // sorte que le relais (ajean.link) ne voie que de l'opaque : « boîte noire ». @@ -32,7 +32,7 @@ import ( "sync" ) -func e2eKeyPath() string { return filepath.Join(JeanHome(), ".e2e_key") } +func e2eKeyPath() string { return filepath.Join(AjeanHome(), ".e2e_key") } var ( e2eOnce sync.Once @@ -55,7 +55,7 @@ func e2ePrivateKey() (*ecdh.PrivateKey, error) { e2eErr = err return } - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) if err := os.WriteFile(e2eKeyPath(), []byte(hex.EncodeToString(k.Bytes())+"\n"), 0o600); err != nil { e2eErr = err return diff --git a/internal/jean/relay_e2eauth.go b/internal/ajean/relay_e2eauth.go similarity index 96% rename from internal/jean/relay_e2eauth.go rename to internal/ajean/relay_e2eauth.go index b55b341..41d007a 100644 --- a/internal/jean/relay_e2eauth.go +++ b/internal/ajean/relay_e2eauth.go @@ -1,4 +1,4 @@ -package jean +package ajean // relay_e2eauth.go — authentification MUTUELLE + anti-rejeu du canal chiffré. // @@ -44,7 +44,7 @@ var ( authSet = map[string]bool{} ) -func authUsersPath() string { return filepath.Join(JeanHome(), ".authorized_users") } +func authUsersPath() string { return filepath.Join(AjeanHome(), ".authorized_users") } func loadAuthUsers() { authOnce.Do(func() { @@ -88,7 +88,7 @@ func authorizeUser(uPubHex string) error { return nil } authSet[uPubHex] = true - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) f, err := os.OpenFile(authUsersPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) if err != nil { return err @@ -102,7 +102,7 @@ func authorizeUser(uPubHex string) error { // // 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 -// un fichier JeanHome/.pair_codes — sinon les deux process ne s'accorderaient pas. +// 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. @@ -113,7 +113,7 @@ type pairEntry struct { Exp int64 `json:"e"` // expiration (unix ms) } -func pairCodesPath() string { return filepath.Join(JeanHome(), ".pair_codes") } +func pairCodesPath() string { return filepath.Join(AjeanHome(), ".pair_codes") } func hashPairCode(code string) string { h := sha256.Sum256([]byte(strings.ToUpper(strings.TrimSpace(code)))) @@ -141,7 +141,7 @@ func loadPairEntries() []pairEntry { } func savePairEntries(es []pairEntry) error { - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } b, _ := json.Marshal(es) diff --git a/internal/jean/relay_e2eauth_test.go b/internal/ajean/relay_e2eauth_test.go similarity index 99% rename from internal/jean/relay_e2eauth_test.go rename to internal/ajean/relay_e2eauth_test.go index 8489860..a75accf 100644 --- a/internal/jean/relay_e2eauth_test.go +++ b/internal/ajean/relay_e2eauth_test.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "strings" diff --git a/internal/jean/relay_link.go b/internal/ajean/relay_link.go similarity index 97% rename from internal/jean/relay_link.go rename to internal/ajean/relay_link.go index a62ab6e..2b02f0e 100644 --- a/internal/jean/relay_link.go +++ b/internal/ajean/relay_link.go @@ -1,4 +1,4 @@ -package jean +package ajean // relay_link.go — `jean link ` : connecte ce serveur Jean au relais public // (ajean.link) par une connexion SORTANTE persistante, pour qu'un utilisateur @@ -42,8 +42,8 @@ import ( // $JEAN_LINK_URL (utile pour tester contre un relais local). const defaultRelayURL = "wss://ajean.link/agent" -func linkTokenPath() string { return filepath.Join(JeanHome(), ".link_token") } -func linkMachinePath() string { return filepath.Join(JeanHome(), ".link_machine") } +func linkTokenPath() string { return filepath.Join(AjeanHome(), ".link_token") } +func linkMachinePath() string { return filepath.Join(AjeanHome(), ".link_machine") } // machineID returns a stable per-machine identifier, creating one on first use. // Permet au relais de regrouper les connexions d'une même machine sous le compte. @@ -56,7 +56,7 @@ func machineID() string { buf := make([]byte, 8) _, _ = rand.Read(buf) id := hex.EncodeToString(buf) - _ = os.MkdirAll(JeanHome(), 0o755) + _ = os.MkdirAll(AjeanHome(), 0o755) _ = os.WriteFile(linkMachinePath(), []byte(id+"\n"), 0o600) return id } @@ -71,7 +71,7 @@ func readLinkToken() string { } func saveLinkToken(tok string) error { - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } return os.WriteFile(linkTokenPath(), []byte(tok+"\n"), 0o600) @@ -140,7 +140,7 @@ func cmdLink(args []string) error { case "code": code, err := newPairCode() if err != nil { - return fmt.Errorf("génération du code (droits sur %s ?): %w", JeanHome(), err) + return fmt.Errorf("génération du code (droits sur %s ?): %w", AjeanHome(), err) } fmt.Printf("%s code d'appairage (valable 10 min, à usage unique) :\n %s\n", green("[link]"), bold(code)) return nil @@ -209,7 +209,7 @@ func linkPrintIdentity() error { } code, err := newPairCode() if err != nil { - fmt.Printf("%s code d'appairage indisponible (droits sur %s ?): %v\n", yellow("[link]"), JeanHome(), err) + 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") return nil } @@ -375,8 +375,8 @@ func serveLocalWebMux(mux *http.ServeMux) { // linkPIDPath / linkLogPath : suivi du worker de lien hors systemd (macOS, // Windows), où Jean est une app de bureau lancée sans droits root. -func linkPIDPath() string { return filepath.Join(JeanHome(), linkServiceName+".pid") } -func linkLogPath() string { return filepath.Join(JeanHome(), linkServiceName+".log") } +func linkPIDPath() string { return filepath.Join(AjeanHome(), linkServiceName+".pid") } +func linkLogPath() string { return filepath.Join(AjeanHome(), linkServiceName+".log") } // 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) ; @@ -486,7 +486,7 @@ func linkUserSvcCtl(action string) error { if p, err := filepath.EvalSymlinks(self); err == nil { self = p } - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(linkLogPath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) diff --git a/internal/jean/run.go b/internal/ajean/run.go similarity index 79% rename from internal/jean/run.go rename to internal/ajean/run.go index a00ec6b..b526c94 100644 --- a/internal/jean/run.go +++ b/internal/ajean/run.go @@ -1,7 +1,7 @@ // 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 // .syso/go:generate vivent là-bas, dans le dossier du package main). -package jean +package ajean import ( "fmt" @@ -175,55 +175,65 @@ func mustExit(err error) { } } -// JeanHome resolves the JEAN data directory. -// Precedence: $JEAN_HOME → /etc/default/jean (unix only) → defaultJeanHome(). -// defaultJeanHome() is platform-specific (see sys_platform_unix.go / sys_platform_windows.go). -func JeanHome() string { +// AjeanHome resolves the AJEAN data directory. +// Precedence: $AJEAN_HOME → $JEAN_HOME (héritage) → /etc/default/ajean puis +// /etc/default/jean (unix only) → migratedDefaultHome(). +// +// Les deux noms de variable sont acceptés indéfiniment : ils sont posés À LA MAIN +// dans les config des utilisateurs et dans des scripts qu'on ne voit pas. Une +// variable explicite est un ordre — on ne migre RIEN quand il y en a une, on obéit. +func AjeanHome() string { + if h := os.Getenv("AJEAN_HOME"); h != "" { + return h + } if h := os.Getenv("JEAN_HOME"); h != "" { return h } if h := readEtcDefault(); h != "" { return h } - return defaultJeanHome() + return migratedDefaultHome() } -// readEtcDefault parses /etc/default/jean for JEAN_HOME=. Quiet on errors. +// readEtcDefault parses /etc/default/ajean (puis /etc/default/jean, hérité) for +// AJEAN_HOME= ou JEAN_HOME=. Quiet on errors. func readEtcDefault() string { - b, err := os.ReadFile("/etc/default/jean") - if err != nil { - return "" - } - for _, line := range strings.Split(string(b), "\n") { - s := strings.TrimSpace(line) - if s == "" || strings.HasPrefix(s, "#") { + for _, path := range []string{"/etc/default/ajean", "/etc/default/jean"} { + b, err := os.ReadFile(path) + if err != nil { continue } - s = strings.TrimPrefix(s, "export ") - if eq := strings.IndexByte(s, '='); eq > 0 { - k := strings.TrimSpace(s[:eq]) - v := strings.Trim(strings.TrimSpace(s[eq+1:]), "\"'") - if k == "JEAN_HOME" { - return v + for _, line := range strings.Split(string(b), "\n") { + s := strings.TrimSpace(line) + if s == "" || strings.HasPrefix(s, "#") { + continue + } + s = strings.TrimPrefix(s, "export ") + if eq := strings.IndexByte(s, '='); eq > 0 { + k := strings.TrimSpace(s[:eq]) + v := strings.Trim(strings.TrimSpace(s[eq+1:]), "\"'") + if k == "AJEAN_HOME" || k == "JEAN_HOME" { + return v + } } } } return "" } -func confPath() string { return filepath.Join(JeanHome(), "config.env") } -func presetsDir() string { return filepath.Join(JeanHome(), "configs") } -func skillsDir() string { return filepath.Join(JeanHome(), "SKILLS") } -func memoryDir() string { return filepath.Join(JeanHome(), "MEMORY") } -func agentFlag() string { return filepath.Join(JeanHome(), ".agent_enabled") } -func internetFlag() string { return filepath.Join(JeanHome(), ".internet_enabled") } -func mcpConfigPath() string { return filepath.Join(JeanHome(), "mcp.json") } +func confPath() string { return filepath.Join(AjeanHome(), "config.env") } +func presetsDir() string { return filepath.Join(AjeanHome(), "configs") } +func skillsDir() string { return filepath.Join(AjeanHome(), "SKILLS") } +func memoryDir() string { return filepath.Join(AjeanHome(), "MEMORY") } +func agentFlag() string { return filepath.Join(AjeanHome(), ".agent_enabled") } +func internetFlag() string { return filepath.Join(AjeanHome(), ".internet_enabled") } +func mcpConfigPath() string { return filepath.Join(AjeanHome(), "mcp.json") } // Anciens drapeaux séparés, conservés pour la migration vers le mode agent unifié. func legacySkillsFlag() string { return filepath.Join(skillsDir(), ".enabled") } -func legacyToolsFlag() string { return filepath.Join(JeanHome(), ".tools_enabled") } -func apiKeyPath() string { return filepath.Join(JeanHome(), ".api_key") } -func crawlKeyPath() string { return filepath.Join(JeanHome(), ".crawl4ai_key") } +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") } func serviceName() string { if n := os.Getenv("JEAN_SERVICE"); n != "" { return n diff --git a/internal/jean/sys_brand_icon.go b/internal/ajean/sys_brand_icon.go similarity index 99% rename from internal/jean/sys_brand_icon.go rename to internal/ajean/sys_brand_icon.go index 134de41..04b70bb 100644 --- a/internal/jean/sys_brand_icon.go +++ b/internal/ajean/sys_brand_icon.go @@ -1,4 +1,4 @@ -package jean +package ajean // sys_brand_icon.go — l'icône de la marque AJEAN, rendue à la volée. // diff --git a/internal/jean/sys_console_darwin.go b/internal/ajean/sys_console_darwin.go similarity index 99% rename from internal/jean/sys_console_darwin.go rename to internal/ajean/sys_console_darwin.go index 40b4409..2d02240 100644 --- a/internal/jean/sys_console_darwin.go +++ b/internal/ajean/sys_console_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package jean +package ajean // sys_console_darwin.go — détection du lancement « par clic » sous macOS. // diff --git a/internal/jean/sys_console_other.go b/internal/ajean/sys_console_other.go similarity index 97% rename from internal/jean/sys_console_other.go rename to internal/ajean/sys_console_other.go index 1dee73a..63f7a35 100644 --- a/internal/jean/sys_console_other.go +++ b/internal/ajean/sys_console_other.go @@ -1,6 +1,6 @@ //go:build !windows && !darwin -package jean +package ajean // setupConsole : sous Linux, le process a toujours une vraie console/tty // standard (stdout hérité). Rien à faire → on signale simplement « on a une diff --git a/internal/jean/sys_console_windows.go b/internal/ajean/sys_console_windows.go similarity index 99% rename from internal/jean/sys_console_windows.go rename to internal/ajean/sys_console_windows.go index d8e8b56..821c4e6 100644 --- a/internal/jean/sys_console_windows.go +++ b/internal/ajean/sys_console_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean // sys_console_windows.go — gestion de la console sous Windows. // diff --git a/internal/jean/sys_firstrun_other.go b/internal/ajean/sys_firstrun_other.go similarity index 97% rename from internal/jean/sys_firstrun_other.go rename to internal/ajean/sys_firstrun_other.go index 5ca594a..b6eec3c 100644 --- a/internal/jean/sys_firstrun_other.go +++ b/internal/ajean/sys_firstrun_other.go @@ -1,6 +1,6 @@ //go:build !windows -package jean +package ajean import "os" diff --git a/internal/jean/sys_firstrun_windows.go b/internal/ajean/sys_firstrun_windows.go similarity index 99% rename from internal/jean/sys_firstrun_windows.go rename to internal/ajean/sys_firstrun_windows.go index 6d42128..f9ad683 100644 --- a/internal/jean/sys_firstrun_windows.go +++ b/internal/ajean/sys_firstrun_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean import ( "fmt" @@ -113,7 +113,7 @@ func appFirstRun() bool { "Installer AJEAN sur cet ordinateur ?\n\n"+ "• le programme sera copié dans :\n "+target+"\n"+ "• un raccourci sera ajouté au menu Démarrer et au Bureau\n"+ - "• vos réglages et modèles seront rangés dans :\n "+JeanHome()+"\n\n"+ + "• vos réglages et modèles seront rangés dans :\n "+AjeanHome()+"\n\n"+ "Vous pourrez ensuite supprimer le fichier que vous venez de télécharger.\n\n"+ "Répondre Non démarre AJEAN sans rien installer. La question reviendra\n"+ "au prochain lancement de ce fichier.", @@ -376,7 +376,7 @@ type vsFixedFileInfo struct { // Extrait de cmdInstall pour être réutilisable au premier lancement, sans // embarquer l'installation du binaire ni les messages de console. func provisionDataDir() error { - jeanHome := JeanHome() + jeanHome := AjeanHome() for _, d := range []string{jeanHome, filepath.Join(jeanHome, "configs"), filepath.Join(jeanHome, "SKILLS")} { if err := os.MkdirAll(d, 0o755); err != nil { return err diff --git a/internal/jean/sys_firstrun_windows_test.go b/internal/ajean/sys_firstrun_windows_test.go similarity index 99% rename from internal/jean/sys_firstrun_windows_test.go rename to internal/ajean/sys_firstrun_windows_test.go index 43449ec..51a651a 100644 --- a/internal/jean/sys_firstrun_windows_test.go +++ b/internal/ajean/sys_firstrun_windows_test.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean import ( "os" diff --git a/internal/jean/sys_install_darwin.go b/internal/ajean/sys_install_darwin.go similarity index 99% rename from internal/jean/sys_install_darwin.go rename to internal/ajean/sys_install_darwin.go index f74001d..56f55d8 100644 --- a/internal/jean/sys_install_darwin.go +++ b/internal/ajean/sys_install_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package jean +package ajean import ( "fmt" diff --git a/internal/jean/sys_install_linux.go b/internal/ajean/sys_install_linux.go similarity index 99% rename from internal/jean/sys_install_linux.go rename to internal/ajean/sys_install_linux.go index 154bdce..d53fedc 100644 --- a/internal/jean/sys_install_linux.go +++ b/internal/ajean/sys_install_linux.go @@ -1,6 +1,6 @@ //go:build linux -package jean +package ajean import ( "fmt" diff --git a/internal/jean/sys_install_windows.go b/internal/ajean/sys_install_windows.go similarity index 98% rename from internal/jean/sys_install_windows.go rename to internal/ajean/sys_install_windows.go index ccce2b6..9aae2dc 100644 --- a/internal/jean/sys_install_windows.go +++ b/internal/ajean/sys_install_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean import ( "fmt" @@ -38,7 +38,7 @@ EXTRA_ARGS="" ` func cmdInstall(args []string) error { - jeanHome := JeanHome() + jeanHome := AjeanHome() fmt.Printf("Installation (Windows)\n") fmt.Printf(" JEAN_HOME = %s\n", jeanHome) @@ -203,7 +203,7 @@ func cmdUninstall(args []string) error { _ = svcStop(false) // Pull JEAN_HOME\bin off the user PATH (best-effort). - binDir := filepath.Join(JeanHome(), "bin") + binDir := filepath.Join(AjeanHome(), "bin") if removed, err := removeFromUserPath(binDir); err == nil && removed { fmt.Printf(" %s %s retiré du PATH utilisateur\n", green("✓"), binDir) } @@ -214,7 +214,7 @@ func cmdUninstall(args []string) error { } if !keepData { - jeanHome := JeanHome() + jeanHome := AjeanHome() if err := os.RemoveAll(jeanHome); err != nil { return fmt.Errorf("suppression de %s: %w", jeanHome, err) } diff --git a/internal/ajean/sys_migrate.go b/internal/ajean/sys_migrate.go new file mode 100644 index 0000000..9aa5141 --- /dev/null +++ b/internal/ajean/sys_migrate.go @@ -0,0 +1,80 @@ +package ajean + +import ( + "fmt" + "os" + "path/filepath" + "sync" +) + +// Migration du dossier de données jean → ajean. +// +// C'est le seul endroit du renommage qui peut faire PERDRE des données : le +// dossier contient la config, les clés, la mémoire, les conversations, et +// surtout des .gguf qui pèsent des dizaines de gigaoctets. Trois règles en +// découlent, et elles expliquent tout le code ci-dessous : +// +// 1. On déplace par os.Rename DANS LE DOSSIER PARENT de l'ancien. Un rename +// intra-volume est atomique et instantané — il ne recopie pas les 40 Go, et +// il n'existe aucun instant où les données seraient à moitié quelque part. +// Choisir le parent de l'ancien chemin (et pas le défaut de la plateforme) +// garantit qu'on reste sur le même volume, donc que le rename est bien un +// rename et non un copy+delete déguisé. +// +// 2. Si le rename échoue — service en cours qui tient des handles sous Windows, +// droits insuffisants, volume monté en lecture seule — on CONTINUE SUR +// L'ANCIEN CHEMIN. Une machine qui n'a pas migré marche exactement comme +// avant ; une machine qui a perdu ses modèles, non. La migration est donc +// retentée à chaque démarrage jusqu'à ce qu'elle passe. +// +// 3. Aucune suppression, jamais. Le pire cas est « rien n'a bougé ». + +var ( + homeOnce sync.Once + homePath string +) + +// migratedDefaultHome renvoie le dossier de données par défaut, en migrant +// l'ancien dossier « jean » vers « ajean » à la première résolution du process. +// Le résultat est mis en cache : deux appels ne doivent jamais désigner deux +// dossiers différents, sinon une moitié du programme écrirait à côté de l'autre. +// +// N'est PAS appelé quand $AJEAN_HOME/$JEAN_HOME ou /etc/default/* imposent un +// chemin : un choix explicite de l'utilisateur ne se migre pas. +func migratedDefaultHome() string { + homeOnce.Do(func() { homePath = resolveDefaultHome() }) + return homePath +} + +func resolveDefaultHome() string { + return migrateHome(defaultAjeanHome(), legacyDefaultHome()) +} + +// 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, +// l'ancien si elle a échoué. +func migrateHome(target, legacy string) string { + if isDir(target) { + return target // déjà migré (ou installation neuve déjà faite) + } + if !isDir(legacy) { + return target // installation neuve : rien à migrer + } + + // Même parent que l'ancien dossier ⇒ même volume ⇒ rename atomique. + sibling := filepath.Join(filepath.Dir(legacy), filepath.Base(target)) + if isDir(sibling) { + return sibling + } + if err := os.Rename(legacy, sibling); err != nil { + // Cas le plus courant sous Windows : un service AJEAN tourne encore et + // tient un handle dans le dossier. On reste sur l'ancien chemin — tout + // fonctionne — et on retentera au prochain démarrage. + fmt.Fprintf(os.Stderr, "[info] dossier de données pas encore migré vers %s (%v) — on continue sur %s\n", + sibling, err, legacy) + return legacy + } + fmt.Fprintf(os.Stderr, "[ok] dossier de données migré : %s → %s\n", legacy, sibling) + return sibling +} diff --git a/internal/ajean/sys_migrate_test.go b/internal/ajean/sys_migrate_test.go new file mode 100644 index 0000000..87f24f4 --- /dev/null +++ b/internal/ajean/sys_migrate_test.go @@ -0,0 +1,102 @@ +package ajean + +import ( + "os" + "path/filepath" + "runtime" + "testing" +) + +// mkHome crée un dossier de données factice contenant un fichier témoin, pour +// vérifier que la migration déplace bien le CONTENU et pas seulement le dossier. +func mkHome(t *testing.T, dir string) { + t.Helper() + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "config.env"), []byte("MODEL=x.gguf\n"), 0o644); err != nil { + t.Fatal(err) + } +} + +// Cas nominal : seul l'ancien dossier existe → il est migré, contenu compris. +func TestMigrateHomeMovesLegacy(t *testing.T) { + root := t.TempDir() + legacy := filepath.Join(root, "jean") + target := filepath.Join(root, "ajean") + mkHome(t, legacy) + + if got := migrateHome(target, legacy); got != target { + t.Fatalf("got %q, attendu le nouveau chemin %q", got, target) + } + if b, err := os.ReadFile(filepath.Join(target, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" { + t.Fatalf("contenu non migré: %q (%v)", b, err) + } + if isDir(legacy) { + t.Fatalf("l'ancien dossier existe encore après un rename réussi") + } +} + +// Installation neuve : rien à migrer, on renvoie le nouveau chemin sans rien créer. +func TestMigrateHomeFreshInstall(t *testing.T) { + root := t.TempDir() + target := filepath.Join(root, "ajean") + if got := migrateHome(target, filepath.Join(root, "jean")); got != target { + t.Fatalf("got %q, attendu %q", got, target) + } +} + +// Déjà migré : le nouveau dossier existe → on ne retouche à rien, même si un +// vieux dossier « jean » traîne encore (utilisateur qui l'a recréé, restauration +// de sauvegarde…). Écraser serait perdre les données courantes. +func TestMigrateHomeKeepsExistingTarget(t *testing.T) { + root := t.TempDir() + legacy := filepath.Join(root, "jean") + target := filepath.Join(root, "ajean") + mkHome(t, legacy) + mkHome(t, target) + if err := os.WriteFile(filepath.Join(target, "config.env"), []byte("MODEL=courant.gguf\n"), 0o644); err != nil { + t.Fatal(err) + } + + if got := migrateHome(target, legacy); got != target { + t.Fatalf("got %q, attendu %q", got, target) + } + b, _ := os.ReadFile(filepath.Join(target, "config.env")) + if string(b) != "MODEL=courant.gguf\n" { + t.Fatalf("le dossier courant a été écrasé par l'ancien: %q", b) + } + if !isDir(legacy) { + t.Fatalf("l'ancien dossier a été supprimé — la migration ne doit JAMAIS supprimer") + } +} + +// Rename impossible → on doit retomber sur l'ancien chemin, intact. C'est la +// garantie « au pire, rien n'a bougé », celle qui protège les .gguf. +// +// On reproduit le scénario réel : un service AJEAN tourne encore et tient un +// handle ouvert dans le dossier de données, ce qui fait échouer le rename du +// dossier sous Windows. Unix, lui, renomme sans broncher un dossier dont des +// fichiers sont ouverts — il n'y a donc rien à simuler là-bas. +func TestMigrateHomeFallsBackWhenRenameFails(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("un rename de dossier réussit avec des fichiers ouverts hors Windows") + } + root := t.TempDir() + legacy := filepath.Join(root, "jean") + target := filepath.Join(root, "ajean") + mkHome(t, legacy) + + held, err := os.Open(filepath.Join(legacy, "config.env")) + if err != nil { + t.Fatal(err) + } + defer held.Close() + + if got := migrateHome(target, legacy); got != legacy { + t.Fatalf("got %q, attendu un repli sur l'ancien chemin %q", got, legacy) + } + if b, err := os.ReadFile(filepath.Join(legacy, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" { + t.Fatalf("l'ancien dossier a été abîmé par une migration ratée: %q (%v)", b, err) + } +} diff --git a/internal/jean/sys_paths.go b/internal/ajean/sys_paths.go similarity index 93% rename from internal/jean/sys_paths.go rename to internal/ajean/sys_paths.go index 5a05a00..a7ec1e8 100644 --- a/internal/jean/sys_paths.go +++ b/internal/ajean/sys_paths.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "fmt" @@ -15,7 +15,7 @@ import ( // 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. -type jeanPaths struct { +type ajeanPaths struct { Home string `json:"home"` // JEAN_HOME : racine des données Config string `json:"config"` // config.env Exe string `json:"exe"` // binaire en cours d'exécution @@ -31,18 +31,18 @@ type jeanPaths struct { // faute d'équivalent, on utilise JEAN_HOME\bin ajouté au PATH utilisateur. func installedExePath() string { if runtime.GOOS == "windows" { - return filepath.Join(JeanHome(), "bin", "jean.exe") + return filepath.Join(AjeanHome(), "bin", "jean.exe") } return "/usr/local/bin/jean" } -func currentPaths() jeanPaths { +func currentPaths() ajeanPaths { exe, _ := os.Executable() if resolved, err := filepath.EvalSymlinks(exe); err == nil { exe = resolved } - return jeanPaths{ - Home: JeanHome(), + return ajeanPaths{ + Home: AjeanHome(), Config: confPath(), Exe: exe, Installed: installedExePath(), diff --git a/internal/jean/sys_platform_unix.go b/internal/ajean/sys_platform_unix.go similarity index 92% rename from internal/jean/sys_platform_unix.go rename to internal/ajean/sys_platform_unix.go index 8a1aa99..b36f497 100644 --- a/internal/jean/sys_platform_unix.go +++ b/internal/ajean/sys_platform_unix.go @@ -1,6 +1,6 @@ //go:build unix -package jean +package ajean import ( "context" @@ -16,25 +16,30 @@ import ( "time" ) -// defaultJeanHome is the data root when neither $JEAN_HOME nor /etc/default/jean -// override it. +// defaultAjeanHome is the data root when neither $AJEAN_HOME/$JEAN_HOME nor +// /etc/default/* override it. legacyDefaultHome est son équivalent d'avant le +// renommage, migré au premier lancement (voir sys_migrate.go). // -// Linux : /etc/jean (machine de prod, service systemd lancé par root). -// macOS : /etc/jean n'est PAS écrivable par une app lancée depuis le Finder -// (« mkdir /etc/jean: permission denied »), et un Mac est une machine de bureau, +// Linux : /etc/ajean (machine de prod, service systemd lancé par root). +// macOS : /etc/ajean n'est PAS écrivable par une app lancée depuis le Finder +// (« mkdir /etc/ajean: permission denied »), et un Mac est une machine de bureau, // pas un serveur — on retombe donc sur le dossier utilisateur standard, sauf si -// un `sudo jean install` a déjà créé /etc/jean à notre nom. -func defaultJeanHome() string { +// un `sudo ajean install` a déjà créé /etc/ajean à notre nom. +func defaultAjeanHome() string { return unixHome("ajean") } + +func legacyDefaultHome() string { return unixHome("jean") } + +func unixHome(name string) string { if runtime.GOOS != "darwin" || os.Geteuid() == 0 { - return "/etc/jean" + return "/etc/" + name } - if isWritableDir("/etc/jean") { - return "/etc/jean" // installé par `sudo jean install` puis chown à l'utilisateur + if isWritableDir("/etc/" + name) { + return "/etc/" + name // installé par `sudo ajean install` puis chown à l'utilisateur } if home, err := os.UserHomeDir(); err == nil { - return filepath.Join(home, "Library", "Application Support", "jean") + return filepath.Join(home, "Library", "Application Support", name) } - return "/etc/jean" + return "/etc/" + name } // isWritableDir teste qu'un dossier existe ET qu'on peut y écrire (le seul test diff --git a/internal/jean/sys_platform_windows.go b/internal/ajean/sys_platform_windows.go similarity index 95% rename from internal/jean/sys_platform_windows.go rename to internal/ajean/sys_platform_windows.go index 2e6bbb3..c7d77ec 100644 --- a/internal/jean/sys_platform_windows.go +++ b/internal/ajean/sys_platform_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean import ( "context" @@ -40,19 +40,26 @@ func init() { } } -// defaultJeanHome is the data root when $JEAN_HOME is unset. We use -// %ProgramData%\jean (machine-wide, the closest analogue to /etc/jean), falling -// back to %LOCALAPPDATA%\jean for unprivileged setups. -func defaultJeanHome() string { +// homeRoot is the parent directory of the data root: %ProgramData% (machine-wide, +// the closest analogue to /etc), falling back to %LOCALAPPDATA% for unprivileged +// setups. Ancien et nouveau dossier partagent ce parent, ce qui garantit que la +// migration jean → ajean est un rename intra-volume (voir sys_migrate.go). +func homeRoot() string { if pd := os.Getenv("ProgramData"); pd != "" { - return filepath.Join(pd, "jean") + return pd } if la := os.Getenv("LOCALAPPDATA"); la != "" { - return filepath.Join(la, "jean") + return la } - return filepath.Join(os.TempDir(), "jean") + return os.TempDir() } +// defaultAjeanHome is the data root when no AJEAN_HOME/JEAN_HOME is set. +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. func defaultEditor() string { return "notepad" } diff --git a/internal/jean/sys_service.go b/internal/ajean/sys_service.go similarity index 99% rename from internal/jean/sys_service.go rename to internal/ajean/sys_service.go index a5b6fb3..fa3d46f 100644 --- a/internal/jean/sys_service.go +++ b/internal/ajean/sys_service.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "fmt" diff --git a/internal/jean/sys_service_darwin.go b/internal/ajean/sys_service_darwin.go similarity index 96% rename from internal/jean/sys_service_darwin.go rename to internal/ajean/sys_service_darwin.go index ca9e609..4a167f5 100644 --- a/internal/jean/sys_service_darwin.go +++ b/internal/ajean/sys_service_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package jean +package ajean import ( "fmt" @@ -29,7 +29,7 @@ func launchdPlistPath(svc string) string { // launchdLogPath : sortie standard/erreur du service, sous JEAN_HOME (accessible // en écriture par l'utilisateur du service après le chown de l'installation). -func launchdLogPath() string { return filepath.Join(JeanHome(), serviceName()+".log") } +func launchdLogPath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } // serviceAction mappe start/stop/restart/enable/disable sur launchctl. `load -w` // (re)active le service ET le rend persistant au boot ; `unload -w` le désactive. @@ -133,8 +133,8 @@ func serviceIsActive() bool { // la fermeture de Jean.app, et permet de tuer tout le groupe (llama-server // enfant compris) d'un seul signal. -func pidFilePath() string { return filepath.Join(JeanHome(), serviceName()+".pid") } -func logFilePath() string { return filepath.Join(JeanHome(), serviceName()+".log") } +func pidFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".pid") } +func logFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } func userSvcAction(action string) error { switch action { @@ -173,7 +173,7 @@ func userSvcStart() error { if err != nil { return err } - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(logFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -187,7 +187,7 @@ func userSvcStart() error { // Même répertoire de travail que sous systemd/launchd : les chemins relatifs // de config.env (MODEL=…gguf) se résolvent depuis JEAN_HOME, pas depuis « / » // que nous hérite le Finder. - cmd.Dir = JeanHome() + 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) diff --git a/internal/jean/sys_service_linux.go b/internal/ajean/sys_service_linux.go similarity index 99% rename from internal/jean/sys_service_linux.go rename to internal/ajean/sys_service_linux.go index ede3a77..0558e22 100644 --- a/internal/jean/sys_service_linux.go +++ b/internal/ajean/sys_service_linux.go @@ -1,6 +1,6 @@ //go:build linux -package jean +package ajean import ( "fmt" diff --git a/internal/jean/sys_service_windows.go b/internal/ajean/sys_service_windows.go similarity index 95% rename from internal/jean/sys_service_windows.go rename to internal/ajean/sys_service_windows.go index ccf50a2..da9f807 100644 --- a/internal/jean/sys_service_windows.go +++ b/internal/ajean/sys_service_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean import ( "fmt" @@ -26,8 +26,8 @@ const ( createNoWindow = 0x08000000 // CREATE_NO_WINDOW (aucune console pour l'enfant) ) -func pidFilePath() string { return filepath.Join(JeanHome(), serviceName()+".pid") } -func logFilePath() string { return filepath.Join(JeanHome(), serviceName()+".log") } +func pidFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".pid") } +func logFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } func serviceAction(action string) error { switch action { @@ -59,7 +59,7 @@ func svcStart() error { if err != nil { return err } - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(logFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -71,7 +71,7 @@ func svcStart() error { cmd := exec.Command(self, "serve") cmd.Stdout = logf cmd.Stderr = logf - cmd.Dir = JeanHome() // les chemins relatifs de config.env se résolvent depuis JEAN_HOME + 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 // faire clignoter de console noire quand Jean est lancé en mode app (double-clic). cmd.SysProcAttr = &syscall.SysProcAttr{ diff --git a/internal/jean/sys_splash_other.go b/internal/ajean/sys_splash_other.go similarity index 94% rename from internal/jean/sys_splash_other.go rename to internal/ajean/sys_splash_other.go index 8dc5b26..f91f5bc 100644 --- a/internal/jean/sys_splash_other.go +++ b/internal/ajean/sys_splash_other.go @@ -1,6 +1,6 @@ //go:build !windows -package jean +package ajean // sys_splash_other.go — pas de fenêtre de démarrage hors Windows (no-op). diff --git a/internal/jean/sys_splash_windows.go b/internal/ajean/sys_splash_windows.go similarity index 99% rename from internal/jean/sys_splash_windows.go rename to internal/ajean/sys_splash_windows.go index d1ff561..4a96703 100644 --- a/internal/jean/sys_splash_windows.go +++ b/internal/ajean/sys_splash_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +package ajean // sys_splash_windows.go — écran de démarrage « Lancement de Jean… ». // diff --git a/internal/jean/sys_tray_darwin.go b/internal/ajean/sys_tray_darwin.go similarity index 99% rename from internal/jean/sys_tray_darwin.go rename to internal/ajean/sys_tray_darwin.go index 038788f..0a2e56b 100644 --- a/internal/jean/sys_tray_darwin.go +++ b/internal/ajean/sys_tray_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package jean +package ajean // sys_tray_darwin.go — icône de Jean dans la barre de menus macOS (en haut à // droite, à côté de l'horloge), pendant exact du system tray Windows : quand on diff --git a/internal/jean/sys_tray_other.go b/internal/ajean/sys_tray_other.go similarity index 97% rename from internal/jean/sys_tray_other.go rename to internal/ajean/sys_tray_other.go index 9824225..d1992c8 100644 --- a/internal/jean/sys_tray_other.go +++ b/internal/ajean/sys_tray_other.go @@ -1,6 +1,6 @@ //go:build !windows && !darwin -package jean +package ajean // sys_tray_other.go — pas d'icône de zone de notification sous Linux (serveur // sans session graphique la plupart du temps). On se contente d'ouvrir l'UI dans diff --git a/internal/jean/sys_tray_windows.go b/internal/ajean/sys_tray_windows.go similarity index 99% rename from internal/jean/sys_tray_windows.go rename to internal/ajean/sys_tray_windows.go index 655e4e3..6fb8ed2 100644 --- a/internal/jean/sys_tray_windows.go +++ b/internal/ajean/sys_tray_windows.go @@ -1,6 +1,6 @@ //go:build windows -package jean +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 diff --git a/internal/jean/sys_tty.go b/internal/ajean/sys_tty.go similarity index 94% rename from internal/jean/sys_tty.go rename to internal/ajean/sys_tty.go index b5b24d7..d3d6329 100644 --- a/internal/jean/sys_tty.go +++ b/internal/ajean/sys_tty.go @@ -1,4 +1,4 @@ -package jean +package ajean import "os" diff --git a/internal/jean/sys_update.go b/internal/ajean/sys_update.go similarity index 84% rename from internal/jean/sys_update.go rename to internal/ajean/sys_update.go index e6abadc..f91e5c0 100644 --- a/internal/jean/sys_update.go +++ b/internal/ajean/sys_update.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "crypto/sha256" @@ -22,7 +22,10 @@ import ( // 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). -const updateRepo = "nathaninline/jean" +// Le dépôt a été renommé jean → ajean ; GitHub redirige les anciennes URL et +// l'API REST, donc les binaires déjà installés qui pointent encore sur +// « nathaninline/jean » continuent de trouver les releases. +const updateRepo = "nathaninline/ajean" type ghRelease struct { TagName string `json:"tag_name"` @@ -34,14 +37,39 @@ type ghRelease struct { } `json:"assets"` } -// updateAssetName reconstruit le nom d'asset attendu pour la plateforme courante, -// suivant la convention des releases : jean--[.exe]. -func updateAssetName() string { - n := "jean-" + runtime.GOOS + "-" + runtime.GOARCH +// updateAssetNames reconstruit les noms d'asset acceptables pour la plateforme +// courante, par ordre de préférence : ajean--[.exe] puis, en repli, +// l'ancien nom jean--[.exe]. +// +// Le repli n'est pas de la coquetterie : pendant la transition, les releases +// publient les DEUX noms (mêmes binaires) pour que le parc déjà installé — qui +// ne connaît que « jean-* » — continue de se mettre à jour. Ce binaire-ci sait +// lire les deux, il fonctionne donc aussi bien avec une release de transition +// qu'avec une release future qui n'aura plus que « ajean-* ». +func updateAssetNames() []string { + suffix := runtime.GOOS + "-" + runtime.GOARCH if runtime.GOOS == "windows" { - n += ".exe" + suffix += ".exe" } - return n + return []string{"ajean-" + suffix, "jean-" + suffix} +} + +// updateAssetName renvoie le nom d'asset préféré (usage : messages à l'écran). +func updateAssetName() string { return updateAssetNames()[0] } + +// pickAsset choisit dans la release le premier asset correspondant à la +// plateforme, en respectant l'ordre de préférence. Renvoie son nom, son URL et +// sa taille — le nom retourné est celui qui servira à vérifier le SHA-256, qui +// doit donc être celui réellement téléchargé, pas celui qu'on préférait. +func pickAsset(rel *ghRelease) (name, url string, size int64) { + for _, want := range updateAssetNames() { + for _, a := range rel.Assets { + if a.Name == want { + return a.Name, a.BrowserDownloadURL, a.Size + } + } + } + return "", "", 0 } // ensureV préfixe "v" si absent, pour comparer via golang.org/x/mod/semver. @@ -56,7 +84,7 @@ func ensureV(s string) string { func fetchLatestRelease() (*ghRelease, error) { req, _ := http.NewRequest("GET", "https://api.github.com/repos/"+updateRepo+"/releases/latest", nil) req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "jean-update") + req.Header.Set("User-Agent", "ajean-update") resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req) if err != nil { return nil, err @@ -114,17 +142,10 @@ func applyUpdate() (string, error) { return Version, fmt.Errorf("déjà à jour (%s)", Version) } - want := updateAssetName() - var url string - var size int64 - for _, a := range rel.Assets { - if a.Name == want { - url, size = a.BrowserDownloadURL, a.Size - break - } - } + want, url, size := pickAsset(rel) if url == "" { - return "", fmt.Errorf("aucun binaire %q dans la release %s (os/arch %s/%s)", want, latest, runtime.GOOS, runtime.GOARCH) + return "", fmt.Errorf("aucun binaire %s dans la release %s (os/arch %s/%s)", + strings.Join(updateAssetNames(), " ni "), latest, runtime.GOOS, runtime.GOARCH) } exe, err := os.Executable() @@ -136,12 +157,12 @@ 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/.jean-update.tmp: permission denied » incompréhensible, + // /usr/local/bin/.ajean-update.tmp: permission denied » incompréhensible, // notamment depuis le bouton de l'UI web (jean web lancé sans sudo). if err := checkUpdateWritable(exe); err != nil { return "", err } - tmp := filepath.Join(filepath.Dir(exe), ".jean-update.tmp") + tmp := filepath.Join(filepath.Dir(exe), ".ajean-update.tmp") if err := downloadTo(url, tmp); err != nil { os.Remove(tmp) @@ -190,7 +211,7 @@ func updatePermissionError(exe string) error { // pas : root, groupes, ACL, montage en lecture seule…). func checkUpdateWritable(exe string) error { dir := filepath.Dir(exe) - probe, err := os.CreateTemp(dir, ".jean-update-probe-*") + probe, err := os.CreateTemp(dir, ".ajean-update-probe-*") if err != nil { if os.IsPermission(err) { return updatePermissionError(exe) @@ -237,7 +258,7 @@ func cmdUpdate(args []string) error { func downloadTo(url, dst string) error { req, _ := http.NewRequest("GET", url, nil) - req.Header.Set("User-Agent", "jean-update") + req.Header.Set("User-Agent", "ajean-update") req.Header.Set("Accept", "application/octet-stream") resp, err := (&http.Client{Timeout: 5 * time.Minute}).Do(req) if err != nil { @@ -274,7 +295,7 @@ func verifyChecksum(rel *ghRelease, assetName, path string) error { return nil } req, _ := http.NewRequest("GET", sumsURL, nil) - req.Header.Set("User-Agent", "jean-update") + req.Header.Set("User-Agent", "ajean-update") resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req) if err != nil { return fmt.Errorf("téléchargement des sommes de contrôle : %w", err) diff --git a/internal/ajean/sys_update_asset_test.go b/internal/ajean/sys_update_asset_test.go new file mode 100644 index 0000000..db8eb02 --- /dev/null +++ b/internal/ajean/sys_update_asset_test.go @@ -0,0 +1,63 @@ +package ajean + +import "testing" + +// relWith fabrique une release ne contenant que les assets nommés. +func relWith(names ...string) *ghRelease { + rel := &ghRelease{TagName: "v9.9.9"} + for _, n := range names { + rel.Assets = append(rel.Assets, struct { + Name string `json:"name"` + BrowserDownloadURL string `json:"browser_download_url"` + Size int64 `json:"size"` + }{Name: n, BrowserDownloadURL: "https://example.invalid/" + n, Size: 42}) + } + return rel +} + +// Une release de transition publie les deux noms : on doit préférer ajean-*. +func TestPickAssetPrefersAjean(t *testing.T) { + names := updateAssetNames() + got, url, _ := pickAsset(relWith(names[1], names[0])) + if got != names[0] { + t.Fatalf("got %q, attendu la préférence %q", got, names[0]) + } + if url == "" { + t.Fatal("URL vide") + } +} + +// Release future, purgée des anciens noms : ajean-* seul suffit. +func TestPickAssetAjeanOnly(t *testing.T) { + names := updateAssetNames() + if got, _, _ := pickAsset(relWith(names[0])); got != names[0] { + t.Fatalf("got %q, attendu %q", got, names[0]) + } +} + +// Ancienne release, antérieure au renommage : le repli sur jean-* doit marcher, +// sinon un client neuf ne pourrait pas s'installer depuis une release existante. +func TestPickAssetFallsBackToLegacy(t *testing.T) { + names := updateAssetNames() + got, _, _ := pickAsset(relWith(names[1])) + if got != names[1] { + t.Fatalf("got %q, attendu le repli %q", got, names[1]) + } +} + +// Le nom renvoyé sert à vérifier le SHA-256 : il doit être celui réellement +// choisi, pas le nom préféré, sinon la vérification échoue sur un repli. +func TestPickAssetReturnsCheckedName(t *testing.T) { + names := updateAssetNames() + got, url, size := pickAsset(relWith(names[1])) + if got != names[1] || url != "https://example.invalid/"+names[1] || size != 42 { + t.Fatalf("incohérence nom/URL/taille: %q %q %d", got, url, size) + } +} + +// Aucun asset pour cette plateforme → pas d'URL, l'appelant doit pouvoir le voir. +func TestPickAssetNoMatch(t *testing.T) { + if _, url, _ := pickAsset(relWith("ajean-plan9-386", "SHA256SUMS")); url != "" { + t.Fatalf("URL inattendue: %q", url) + } +} diff --git a/internal/jean/ui/index.html b/internal/ajean/ui/index.html similarity index 100% rename from internal/jean/ui/index.html rename to internal/ajean/ui/index.html diff --git a/internal/jean/ui/marked.min.js b/internal/ajean/ui/marked.min.js similarity index 100% rename from internal/jean/ui/marked.min.js rename to internal/ajean/ui/marked.min.js diff --git a/internal/jean/ui/src/index.tmpl.html b/internal/ajean/ui/src/index.tmpl.html similarity index 100% rename from internal/jean/ui/src/index.tmpl.html rename to internal/ajean/ui/src/index.tmpl.html diff --git a/internal/jean/ui/src/js/00-md.js b/internal/ajean/ui/src/js/00-md.js similarity index 100% rename from internal/jean/ui/src/js/00-md.js rename to internal/ajean/ui/src/js/00-md.js diff --git a/internal/jean/ui/src/js/01-theme.js b/internal/ajean/ui/src/js/01-theme.js similarity index 100% rename from internal/jean/ui/src/js/01-theme.js rename to internal/ajean/ui/src/js/01-theme.js diff --git a/internal/jean/ui/src/js/02-prefs.js b/internal/ajean/ui/src/js/02-prefs.js similarity index 100% rename from internal/jean/ui/src/js/02-prefs.js rename to internal/ajean/ui/src/js/02-prefs.js diff --git a/internal/jean/ui/src/js/03-ui-kit.js b/internal/ajean/ui/src/js/03-ui-kit.js similarity index 100% rename from internal/jean/ui/src/js/03-ui-kit.js rename to internal/ajean/ui/src/js/03-ui-kit.js diff --git a/internal/jean/ui/src/js/04-api.js b/internal/ajean/ui/src/js/04-api.js similarity index 100% rename from internal/jean/ui/src/js/04-api.js rename to internal/ajean/ui/src/js/04-api.js diff --git a/internal/jean/ui/src/js/05-status.js b/internal/ajean/ui/src/js/05-status.js similarity index 100% rename from internal/jean/ui/src/js/05-status.js rename to internal/ajean/ui/src/js/05-status.js diff --git a/internal/jean/ui/src/js/06-settings.js b/internal/ajean/ui/src/js/06-settings.js similarity index 100% rename from internal/jean/ui/src/js/06-settings.js rename to internal/ajean/ui/src/js/06-settings.js diff --git a/internal/jean/ui/src/js/07-models.js b/internal/ajean/ui/src/js/07-models.js similarity index 100% rename from internal/jean/ui/src/js/07-models.js rename to internal/ajean/ui/src/js/07-models.js diff --git a/internal/jean/ui/src/js/08-chat-render.js b/internal/ajean/ui/src/js/08-chat-render.js similarity index 100% rename from internal/jean/ui/src/js/08-chat-render.js rename to internal/ajean/ui/src/js/08-chat-render.js diff --git a/internal/jean/ui/src/js/09-stream.js b/internal/ajean/ui/src/js/09-stream.js similarity index 100% rename from internal/jean/ui/src/js/09-stream.js rename to internal/ajean/ui/src/js/09-stream.js diff --git a/internal/jean/ui/src/js/10-llamacpp.js b/internal/ajean/ui/src/js/10-llamacpp.js similarity index 100% rename from internal/jean/ui/src/js/10-llamacpp.js rename to internal/ajean/ui/src/js/10-llamacpp.js diff --git a/internal/jean/ui/src/js/12-mcp.js b/internal/ajean/ui/src/js/12-mcp.js similarity index 100% rename from internal/jean/ui/src/js/12-mcp.js rename to internal/ajean/ui/src/js/12-mcp.js diff --git a/internal/jean/ui/src/js/13-remote.js b/internal/ajean/ui/src/js/13-remote.js similarity index 100% rename from internal/jean/ui/src/js/13-remote.js rename to internal/ajean/ui/src/js/13-remote.js diff --git a/internal/jean/ui/src/styles.css b/internal/ajean/ui/src/styles.css similarity index 100% rename from internal/jean/ui/src/styles.css rename to internal/ajean/ui/src/styles.css diff --git a/internal/jean/web_api.go b/internal/ajean/web_api.go similarity index 99% rename from internal/jean/web_api.go rename to internal/ajean/web_api.go index 70d35f8..fd0b98c 100644 --- a/internal/jean/web_api.go +++ b/internal/ajean/web_api.go @@ -1,6 +1,6 @@ // web_api.go — handlers REST /api/* (statut, config, presets, modèles, // mémoire, agent, clés, bench…) du serveur web local. -package jean +package ajean import ( "encoding/json" @@ -169,7 +169,7 @@ func handleBackends(w http.ResponseWriter, r *http.Request) { // (essaie les sous-chemins des différents générateurs CMake). Renvoie // [{name, path}] — un par dossier de backend contenant un binaire. func listBackendBins() []map[string]any { - root := JeanHome() + "/backends" + root := AjeanHome() + "/backends" entries, err := os.ReadDir(root) if err != nil { return []map[string]any{} @@ -269,7 +269,7 @@ func handleLlamacppUninstallCustom(w http.ResponseWriter, r *http.Request) { func handleModels(w http.ResponseWriter, r *http.Request) { out := []map[string]any{} seen := map[string]bool{} - home := JeanHome() + home := AjeanHome() for _, dir := range modelDirs() { entries, err := os.ReadDir(dir) if err != nil { diff --git a/internal/jean/web_auth.go b/internal/ajean/web_auth.go similarity index 96% rename from internal/jean/web_auth.go rename to internal/ajean/web_auth.go index 66013e8..ae0e7bd 100644 --- a/internal/jean/web_auth.go +++ b/internal/ajean/web_auth.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "crypto/rand" @@ -22,7 +22,7 @@ import ( // requête (pas de cache) pour qu'un changement de clé prenne effet sans // redémarrer le serveur web. -func webKeyPath() string { return filepath.Join(JeanHome(), ".web_key") } +func webKeyPath() string { return filepath.Join(AjeanHome(), ".web_key") } // readWebKey returns the trimmed contents of $JEAN_HOME/.web_key, or "" if the // file is absent/empty. @@ -98,7 +98,7 @@ func cmdSetWebKey(args []string) error { fmt.Printf("%s clé de pilotage supprimée — l'API web n'est plus protégée\n", yellow("[info]")) return nil } - if err := os.MkdirAll(JeanHome(), 0o755); err != nil { + if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { return err } if err := os.WriteFile(webKeyPath(), []byte(key+"\n"), 0o600); err != nil { diff --git a/internal/jean/web_chat.go b/internal/ajean/web_chat.go similarity index 99% rename from internal/jean/web_chat.go rename to internal/ajean/web_chat.go index ec7bb87..aa211c0 100644 --- a/internal/jean/web_chat.go +++ b/internal/ajean/web_chat.go @@ -1,6 +1,6 @@ // web_chat.go — endpoints de chat du serveur web local : envoi/stop/reset, // flux SSE d'abonnement à la conversation serveur (voir chat_conversation.go). -package jean +package ajean import ( "context" diff --git a/internal/jean/web_link_api.go b/internal/ajean/web_link_api.go similarity index 99% rename from internal/jean/web_link_api.go rename to internal/ajean/web_link_api.go index 9b137ea..ef71886 100644 --- a/internal/jean/web_link_api.go +++ b/internal/ajean/web_link_api.go @@ -1,4 +1,4 @@ -package jean +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 diff --git a/internal/jean/web_llamacpp.go b/internal/ajean/web_llamacpp.go similarity index 99% rename from internal/jean/web_llamacpp.go rename to internal/ajean/web_llamacpp.go index 0232ac1..c983a6b 100644 --- a/internal/jean/web_llamacpp.go +++ b/internal/ajean/web_llamacpp.go @@ -8,7 +8,7 @@ // POST /api/llamacpp/install → job d'installation {force?} // POST /api/llamacpp/update → job de mise à jour {clean?} // GET /api/llamacpp/job?from= → progression du job (phase, lignes, erreur) -package jean +package ajean import ( "encoding/json" @@ -166,7 +166,7 @@ func handleLlamacpp(w http.ResponseWriter, r *http.Request) { "bin": pbBin, "in_use": pbBin != "" && samePath(pbBin, cfgBin), } - out["backends_dir"] = filepath.Join(JeanHome(), "backends") + out["backends_dir"] = filepath.Join(AjeanHome(), "backends") out["job"] = lcJobSnapshot(0, false) sendJSON(w, 200, out) } diff --git a/internal/jean/web_prefs.go b/internal/ajean/web_prefs.go similarity index 96% rename from internal/jean/web_prefs.go rename to internal/ajean/web_prefs.go index 424e0be..9c10988 100644 --- a/internal/jean/web_prefs.go +++ b/internal/ajean/web_prefs.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "encoding/json" @@ -15,7 +15,7 @@ import ( // Avant ça, elles ne vivaient qu'en localStorage, donc par navigateur/appareil. // webPrefsPath est le fichier JSON des préférences d'apparence. -func webPrefsPath() string { return filepath.Join(JeanHome(), "webprefs.json") } +func webPrefsPath() string { return filepath.Join(AjeanHome(), "webprefs.json") } var webPrefsMu sync.Mutex diff --git a/internal/jean/web_server.go b/internal/ajean/web_server.go similarity index 99% rename from internal/jean/web_server.go rename to internal/ajean/web_server.go index b13939a..0b8d514 100644 --- a/internal/jean/web_server.go +++ b/internal/ajean/web_server.go @@ -1,4 +1,4 @@ -package jean +package ajean import ( "bufio" diff --git a/internal/jean/web_sysprompt.go b/internal/ajean/web_sysprompt.go similarity index 95% rename from internal/jean/web_sysprompt.go rename to internal/ajean/web_sysprompt.go index 1902821..5722740 100644 --- a/internal/jean/web_sysprompt.go +++ b/internal/ajean/web_sysprompt.go @@ -5,7 +5,7 @@ // /api/chat/send ne porte que le message → le champ de l'UI n'avait plus aucun // effet. Il est maintenant lu ici par la génération (chat_conversation.go), et // InjectSkills (llm_client.go) le fusionne avec le préambule agent. -package jean +package ajean import ( "encoding/json" @@ -15,7 +15,7 @@ import ( "strings" ) -func sysPromptPath() string { return filepath.Join(JeanHome(), "sysprompt.txt") } +func sysPromptPath() string { return filepath.Join(AjeanHome(), "sysprompt.txt") } // readSysPrompt renvoie le prompt système personnalisé ("" si absent). func readSysPrompt() string { diff --git a/tools/gen-icon/main.go b/tools/gen-icon/main.go index d90b24f..469a2c3 100644 --- a/tools/gen-icon/main.go +++ b/tools/gen-icon/main.go @@ -11,7 +11,7 @@ import ( "os" "strings" - "github.com/nathaninline/jean/internal/jean" + "github.com/nathaninline/ajean/internal/ajean" ) func main() { @@ -21,13 +21,13 @@ func main() { } // Les tailles attendues par l'explorateur Windows, de la barre des tâches à // l'aperçu en grandes icônes. - write(out, jean.BrandICO(16, 32, 48, 64, 128, 256)) + write(out, ajean.BrandICO(16, 32, 48, 64, 128, 256)) // PNG 1024 pour le bundle macOS : la CI en tire le .icns via sips/iconutil. // Elle partait auparavant du .ico, ce qui suppose que sips sache lire un ICO — // et l'échec y est masqué par « || true », donc une app Mac sans icône passait // inaperçue. Un PNG ne laisse aucune place au doute. - write(strings.TrimSuffix(out, ".ico")+".png", jean.BrandIconPNG(1024)) + write(strings.TrimSuffix(out, ".ico")+".png", ajean.BrandIconPNG(1024)) } func write(path string, data []byte) {