From de6551a153a5dcd36cc6e659b313c4e1ebe10809 Mon Sep 17 00:00:00 2001 From: Loki Date: Fri, 14 Aug 2026 21:33:15 +0000 Subject: [PATCH] =?UTF-8?q?Rebaptise=20AJEAN=20en=20Loki=20(fork,=20lign?= =?UTF-8?q?=C3=A9e=20conserv=C3=A9e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts. --- .github/workflows/ci.yml | 6 +- .github/workflows/mac-build.yml | 38 +++--- .github/workflows/release.yml | 68 +++++----- .gitignore | 4 +- cmd/{ajean => loki}/icon.ico | Bin cmd/{ajean => loki}/icon.png | Bin cmd/{ajean => loki}/main.go | 8 +- .../resource_windows_amd64.syso | Bin .../resource_windows_arm64.syso | Bin cmd/{ajean => loki}/versioninfo.json | 14 +-- go.mod | 2 +- internal/{ajean => loki}/backend_build.go | 12 +- .../backend_build_cuda_test.go | 2 +- .../backend_buildfilter_test.go | 4 +- internal/{ajean => loki}/backend_catalog.go | 4 +- internal/{ajean => loki}/backend_config.go | 18 +-- .../backend_config_quote_test.go | 6 +- internal/{ajean => loki}/backend_gpu.go | 16 +-- internal/{ajean => loki}/backend_llamacpp.go | 26 ++-- .../{ajean => loki}/backend_model_dirs.go | 18 +-- .../backend_model_dirs_test.go | 6 +- internal/{ajean => loki}/backend_models.go | 14 +-- .../backend_models_dest_test.go | 6 +- .../{ajean => loki}/backend_models_dl_test.go | 6 +- .../{ajean => loki}/backend_models_shards.go | 4 +- .../backend_models_shards_test.go | 2 +- internal/{ajean => loki}/backend_prebuilt.go | 4 +- .../backend_prebuilt_reco_test.go | 2 +- .../{ajean => loki}/backend_prebuilt_test.go | 2 +- internal/{ajean => loki}/backend_presets.go | 4 +- .../{ajean => loki}/backend_presets_test.go | 6 +- internal/{ajean => loki}/backend_serve.go | 24 ++-- internal/{ajean => loki}/chat_agent.go | 4 +- internal/{ajean => loki}/chat_cmd.go | 12 +- internal/{ajean => loki}/chat_compact.go | 8 +- internal/{ajean => loki}/chat_compact_test.go | 2 +- internal/{ajean => loki}/chat_conversation.go | 2 +- .../{ajean => loki}/chat_conversation_test.go | 2 +- internal/{ajean => loki}/chat_diff.go | 2 +- internal/{ajean => loki}/chat_export.go | 28 ++--- internal/{ajean => loki}/chat_export_test.go | 8 +- internal/{ajean => loki}/chat_internet.go | 4 +- .../{ajean => loki}/chat_internet_tools.go | 20 +-- internal/{ajean => loki}/chat_memory.go | 8 +- .../{ajean => loki}/chat_prompt_size_test.go | 2 +- internal/{ajean => loki}/chat_stuck_test.go | 2 +- internal/{ajean => loki}/chat_tools.go | 14 +-- .../{ajean => loki}/chat_tools_write_test.go | 2 +- internal/{ajean => loki}/chat_workspace.go | 12 +- .../{ajean => loki}/chat_workspace_test.go | 4 +- internal/{ajean => loki}/cli_app.go | 18 +-- internal/{ajean => loki}/doc.go | 12 +- internal/{ajean => loki}/llm_bench.go | 4 +- internal/{ajean => loki}/llm_client.go | 4 +- internal/{ajean => loki}/llm_oai.go | 18 +-- internal/{ajean => loki}/llm_repeat_test.go | 2 +- .../{ajean => loki}/llm_stream_cut_test.go | 2 +- internal/{ajean => loki}/llm_testcmd.go | 4 +- internal/{ajean => loki}/mcp_client.go | 4 +- internal/{ajean => loki}/mcp_client_test.go | 8 +- internal/{ajean => loki}/mcp_config.go | 10 +- internal/{ajean => loki}/migrate_07.go | 4 +- internal/{ajean => loki}/migrate_07_test.go | 6 +- internal/{ajean => loki}/node_api.go | 2 +- internal/{ajean => loki}/node_proto.go | 10 +- internal/{ajean => loki}/node_proto_test.go | 2 +- internal/{ajean => loki}/node_server.go | 4 +- internal/{ajean => loki}/node_ws_test.go | 4 +- internal/{ajean => loki}/relay_e2e.go | 8 +- internal/{ajean => loki}/relay_e2eauth.go | 10 +- .../{ajean => loki}/relay_e2eauth_test.go | 2 +- internal/{ajean => loki}/relay_link.go | 76 +++++------ .../{ajean => loki}/relay_localauth_test.go | 4 +- internal/{ajean => loki}/remote.go | 38 +++--- internal/{ajean => loki}/run.go | 66 +++++----- internal/{ajean => loki}/store.go | 8 +- internal/{ajean => loki}/store_cache_test.go | 4 +- internal/{ajean => loki}/store_conc_test.go | 2 +- internal/{ajean => loki}/store_test.go | 6 +- internal/{ajean => loki}/sys_brand_icon.go | 6 +- .../{ajean => loki}/sys_console_darwin.go | 20 +-- internal/{ajean => loki}/sys_console_other.go | 6 +- .../{ajean => loki}/sys_console_windows.go | 12 +- internal/{ajean => loki}/sys_datadir.go | 10 +- internal/{ajean => loki}/sys_disk_other.go | 2 +- internal/{ajean => loki}/sys_disk_windows.go | 2 +- .../{ajean => loki}/sys_firewall_other.go | 2 +- .../{ajean => loki}/sys_firewall_windows.go | 10 +- .../{ajean => loki}/sys_firstrun_other.go | 4 +- .../{ajean => loki}/sys_firstrun_windows.go | 66 +++++----- .../sys_firstrun_windows_test.go | 18 +-- .../{ajean => loki}/sys_install_darwin.go | 12 +- internal/{ajean => loki}/sys_install_linux.go | 14 +-- internal/{ajean => loki}/sys_install_unix.go | 50 ++++---- .../{ajean => loki}/sys_install_windows.go | 50 ++++---- internal/{ajean => loki}/sys_network.go | 20 +-- internal/{ajean => loki}/sys_network_test.go | 2 +- internal/{ajean => loki}/sys_paths.go | 28 ++--- internal/{ajean => loki}/sys_platform_unix.go | 24 ++-- .../{ajean => loki}/sys_platform_windows.go | 22 ++-- internal/{ajean => loki}/sys_restart_other.go | 2 +- .../{ajean => loki}/sys_restart_windows.go | 6 +- internal/{ajean => loki}/sys_service.go | 28 ++--- .../{ajean => loki}/sys_service_darwin.go | 48 +++---- internal/{ajean => loki}/sys_service_linux.go | 12 +- .../{ajean => loki}/sys_service_windows.go | 24 ++-- internal/{ajean => loki}/sys_splash_other.go | 2 +- .../{ajean => loki}/sys_splash_windows.go | 10 +- internal/{ajean => loki}/sys_tray_darwin.go | 14 +-- internal/{ajean => loki}/sys_tray_other.go | 2 +- internal/{ajean => loki}/sys_tray_windows.go | 16 +-- internal/{ajean => loki}/sys_tty.go | 2 +- internal/{ajean => loki}/sys_update.go | 56 ++++----- .../{ajean => loki}/sys_update_aside_test.go | 10 +- .../{ajean => loki}/sys_update_asset_test.go | 22 ++-- internal/{ajean => loki}/ui/index.html | 118 +++++++++--------- internal/{ajean => loki}/ui/marked.min.js | 0 .../{ajean => loki}/ui/src/index.tmpl.html | 22 ++-- internal/{ajean => loki}/ui/src/js/00-md.js | 4 +- .../{ajean => loki}/ui/src/js/01-theme.js | 10 +- .../{ajean => loki}/ui/src/js/02-prefs.js | 8 +- .../{ajean => loki}/ui/src/js/03-ui-kit.js | 2 +- internal/{ajean => loki}/ui/src/js/04-api.js | 10 +- .../{ajean => loki}/ui/src/js/05-status.js | 2 +- .../{ajean => loki}/ui/src/js/06-settings.js | 8 +- .../{ajean => loki}/ui/src/js/07-models.js | 18 +-- .../ui/src/js/08-chat-render.js | 4 +- .../{ajean => loki}/ui/src/js/09-stream.js | 4 +- .../{ajean => loki}/ui/src/js/10-llamacpp.js | 0 internal/{ajean => loki}/ui/src/js/12-mcp.js | 0 .../{ajean => loki}/ui/src/js/13-remote.js | 12 +- .../{ajean => loki}/ui/src/js/14-attach.js | 0 internal/{ajean => loki}/ui/src/js/15-node.js | 4 +- internal/{ajean => loki}/ui/src/styles.css | 10 +- internal/{ajean => loki}/version_test.go | 8 +- internal/{ajean => loki}/web_api.go | 20 +-- internal/{ajean => loki}/web_auth.go | 18 +-- internal/{ajean => loki}/web_chat.go | 2 +- .../{ajean => loki}/web_chat_caps_test.go | 2 +- internal/{ajean => loki}/web_devices.go | 2 +- internal/{ajean => loki}/web_devices_test.go | 2 +- internal/{ajean => loki}/web_fetch_go.go | 2 +- internal/{ajean => loki}/web_link_api.go | 10 +- internal/{ajean => loki}/web_llamacpp.go | 4 +- internal/{ajean => loki}/web_llamacpp_job.go | 4 +- internal/{ajean => loki}/web_prefs.go | 4 +- internal/{ajean => loki}/web_server.go | 20 +-- internal/{ajean => loki}/web_sysprompt.go | 2 +- internal/{ajean => loki}/web_upload.go | 4 +- internal/{ajean => loki}/web_upload_test.go | 4 +- internal/nodeclient/client.go | 14 +-- internal/nodeclient/install_other.go | 20 +-- internal/nodeclient/install_windows.go | 10 +- internal/nodewire/crypto.go | 4 +- internal/nodewire/wire.go | 4 +- tools/assemble-ui/main.go | 10 +- tools/gen-icon/main.go | 12 +- 157 files changed, 928 insertions(+), 928 deletions(-) rename cmd/{ajean => loki}/icon.ico (100%) rename cmd/{ajean => loki}/icon.png (100%) rename cmd/{ajean => loki}/main.go (74%) rename cmd/{ajean => loki}/resource_windows_amd64.syso (100%) rename cmd/{ajean => loki}/resource_windows_arm64.syso (100%) rename cmd/{ajean => loki}/versioninfo.json (56%) rename internal/{ajean => loki}/backend_build.go (98%) rename internal/{ajean => loki}/backend_build_cuda_test.go (99%) rename internal/{ajean => loki}/backend_buildfilter_test.go (94%) rename internal/{ajean => loki}/backend_catalog.go (96%) rename internal/{ajean => loki}/backend_config.go (92%) rename internal/{ajean => loki}/backend_config_quote_test.go (91%) rename internal/{ajean => loki}/backend_gpu.go (89%) rename internal/{ajean => loki}/backend_llamacpp.go (96%) rename internal/{ajean => loki}/backend_model_dirs.go (94%) rename internal/{ajean => loki}/backend_model_dirs_test.go (96%) rename internal/{ajean => loki}/backend_models.go (98%) rename internal/{ajean => loki}/backend_models_dest_test.go (96%) rename internal/{ajean => loki}/backend_models_dl_test.go (98%) rename internal/{ajean => loki}/backend_models_shards.go (97%) rename internal/{ajean => loki}/backend_models_shards_test.go (99%) rename internal/{ajean => loki}/backend_prebuilt.go (99%) rename internal/{ajean => loki}/backend_prebuilt_reco_test.go (98%) rename internal/{ajean => loki}/backend_prebuilt_test.go (99%) rename internal/{ajean => loki}/backend_presets.go (99%) rename internal/{ajean => loki}/backend_presets_test.go (96%) rename internal/{ajean => loki}/backend_serve.go (91%) rename internal/{ajean => loki}/chat_agent.go (95%) rename internal/{ajean => loki}/chat_cmd.go (93%) rename internal/{ajean => loki}/chat_compact.go (99%) rename internal/{ajean => loki}/chat_compact_test.go (99%) rename internal/{ajean => loki}/chat_conversation.go (99%) rename internal/{ajean => loki}/chat_conversation_test.go (99%) rename internal/{ajean => loki}/chat_diff.go (99%) rename internal/{ajean => loki}/chat_export.go (92%) rename internal/{ajean => loki}/chat_export_test.go (98%) rename internal/{ajean => loki}/chat_internet.go (99%) rename internal/{ajean => loki}/chat_internet_tools.go (93%) rename internal/{ajean => loki}/chat_memory.go (97%) rename internal/{ajean => loki}/chat_prompt_size_test.go (99%) rename internal/{ajean => loki}/chat_stuck_test.go (99%) rename internal/{ajean => loki}/chat_tools.go (97%) rename internal/{ajean => loki}/chat_tools_write_test.go (98%) rename internal/{ajean => loki}/chat_workspace.go (86%) rename internal/{ajean => loki}/chat_workspace_test.go (91%) rename internal/{ajean => loki}/cli_app.go (83%) rename internal/{ajean => loki}/doc.go (86%) rename internal/{ajean => loki}/llm_bench.go (99%) rename internal/{ajean => loki}/llm_client.go (99%) rename internal/{ajean => loki}/llm_oai.go (93%) rename internal/{ajean => loki}/llm_repeat_test.go (98%) rename internal/{ajean => loki}/llm_stream_cut_test.go (99%) rename internal/{ajean => loki}/llm_testcmd.go (95%) rename internal/{ajean => loki}/mcp_client.go (99%) rename internal/{ajean => loki}/mcp_client_test.go (91%) rename internal/{ajean => loki}/mcp_config.go (95%) rename internal/{ajean => loki}/migrate_07.go (99%) rename internal/{ajean => loki}/migrate_07_test.go (98%) rename internal/{ajean => loki}/node_api.go (99%) rename internal/{ajean => loki}/node_proto.go (88%) rename internal/{ajean => loki}/node_proto_test.go (99%) rename internal/{ajean => loki}/node_server.go (99%) rename internal/{ajean => loki}/node_ws_test.go (98%) rename internal/{ajean => loki}/relay_e2e.go (97%) rename internal/{ajean => loki}/relay_e2eauth.go (96%) rename internal/{ajean => loki}/relay_e2eauth_test.go (99%) rename internal/{ajean => loki}/relay_link.go (89%) rename internal/{ajean => loki}/relay_localauth_test.go (95%) rename internal/{ajean => loki}/remote.go (77%) rename internal/{ajean => loki}/run.go (80%) rename internal/{ajean => loki}/store.go (97%) rename internal/{ajean => loki}/store_cache_test.go (95%) rename internal/{ajean => loki}/store_conc_test.go (98%) rename internal/{ajean => loki}/store_test.go (93%) rename internal/{ajean => loki}/sys_brand_icon.go (96%) rename internal/{ajean => loki}/sys_console_darwin.go (78%) rename internal/{ajean => loki}/sys_console_other.go (76%) rename internal/{ajean => loki}/sys_console_windows.go (92%) rename internal/{ajean => loki}/sys_datadir.go (79%) rename internal/{ajean => loki}/sys_disk_other.go (98%) rename internal/{ajean => loki}/sys_disk_windows.go (98%) rename internal/{ajean => loki}/sys_firewall_other.go (97%) rename internal/{ajean => loki}/sys_firewall_windows.go (91%) rename internal/{ajean => loki}/sys_firstrun_other.go (79%) rename internal/{ajean => loki}/sys_firstrun_windows.go (89%) rename internal/{ajean => loki}/sys_firstrun_windows_test.go (89%) rename internal/{ajean => loki}/sys_install_darwin.go (87%) rename internal/{ajean => loki}/sys_install_linux.go (89%) rename internal/{ajean => loki}/sys_install_unix.go (73%) rename internal/{ajean => loki}/sys_install_windows.go (81%) rename internal/{ajean => loki}/sys_network.go (91%) rename internal/{ajean => loki}/sys_network_test.go (99%) rename internal/{ajean => loki}/sys_paths.go (78%) rename internal/{ajean => loki}/sys_platform_unix.go (94%) rename internal/{ajean => loki}/sys_platform_windows.go (95%) rename internal/{ajean => loki}/sys_restart_other.go (96%) rename internal/{ajean => loki}/sys_restart_windows.go (95%) rename internal/{ajean => loki}/sys_service.go (88%) rename internal/{ajean => loki}/sys_service_darwin.go (83%) rename internal/{ajean => loki}/sys_service_linux.go (91%) rename internal/{ajean => loki}/sys_service_windows.go (86%) rename internal/{ajean => loki}/sys_splash_other.go (94%) rename internal/{ajean => loki}/sys_splash_windows.go (97%) rename internal/{ajean => loki}/sys_tray_darwin.go (79%) rename internal/{ajean => loki}/sys_tray_other.go (97%) rename internal/{ajean => loki}/sys_tray_windows.go (78%) rename internal/{ajean => loki}/sys_tty.go (94%) rename internal/{ajean => loki}/sys_update.go (89%) rename internal/{ajean => loki}/sys_update_aside_test.go (89%) rename internal/{ajean => loki}/sys_update_asset_test.go (75%) rename internal/{ajean => loki}/ui/index.html (98%) rename internal/{ajean => loki}/ui/marked.min.js (100%) rename internal/{ajean => loki}/ui/src/index.tmpl.html (95%) rename internal/{ajean => loki}/ui/src/js/00-md.js (96%) rename internal/{ajean => loki}/ui/src/js/01-theme.js (85%) rename internal/{ajean => loki}/ui/src/js/02-prefs.js (78%) rename internal/{ajean => loki}/ui/src/js/03-ui-kit.js (98%) rename internal/{ajean => loki}/ui/src/js/04-api.js (86%) rename internal/{ajean => loki}/ui/src/js/05-status.js (99%) rename internal/{ajean => loki}/ui/src/js/06-settings.js (98%) rename internal/{ajean => loki}/ui/src/js/07-models.js (98%) rename internal/{ajean => loki}/ui/src/js/08-chat-render.js (99%) rename internal/{ajean => loki}/ui/src/js/09-stream.js (99%) rename internal/{ajean => loki}/ui/src/js/10-llamacpp.js (100%) rename internal/{ajean => loki}/ui/src/js/12-mcp.js (100%) rename internal/{ajean => loki}/ui/src/js/13-remote.js (91%) rename internal/{ajean => loki}/ui/src/js/14-attach.js (100%) rename internal/{ajean => loki}/ui/src/js/15-node.js (98%) rename internal/{ajean => loki}/ui/src/styles.css (99%) rename internal/{ajean => loki}/version_test.go (86%) rename internal/{ajean => loki}/web_api.go (98%) rename internal/{ajean => loki}/web_auth.go (86%) rename internal/{ajean => loki}/web_chat.go (99%) rename internal/{ajean => loki}/web_chat_caps_test.go (99%) rename internal/{ajean => loki}/web_devices.go (99%) rename internal/{ajean => loki}/web_devices_test.go (99%) rename internal/{ajean => loki}/web_fetch_go.go (99%) rename internal/{ajean => loki}/web_link_api.go (92%) rename internal/{ajean => loki}/web_llamacpp.go (99%) rename internal/{ajean => loki}/web_llamacpp_job.go (97%) rename internal/{ajean => loki}/web_prefs.go (95%) rename internal/{ajean => loki}/web_server.go (96%) rename internal/{ajean => loki}/web_sysprompt.go (99%) rename internal/{ajean => loki}/web_upload.go (99%) rename internal/{ajean => loki}/web_upload_test.go (99%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdd8895..2320f31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: # par Cocoa (CGO) → les cibles macOS se compilent sur un runner Apple # (job « macos » ci-dessous, comme dans release.yml). for t in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do - GOOS=${t%/*} GOARCH=${t#*/} CGO_ENABLED=0 go build -o /dev/null ./cmd/ajean + GOOS=${t%/*} GOARCH=${t#*/} CGO_ENABLED=0 go build -o /dev/null ./cmd/loki done macos: @@ -45,7 +45,7 @@ jobs: - run: go test ./... - name: build macOS (CGO, arm64 + amd64) run: | - CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o /dev/null ./cmd/ajean + CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -o /dev/null ./cmd/loki CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \ CGO_CFLAGS="-arch x86_64" CGO_LDFLAGS="-arch x86_64" \ - go build -o /dev/null ./cmd/ajean + go build -o /dev/null ./cmd/loki diff --git a/.github/workflows/mac-build.yml b/.github/workflows/mac-build.yml index 424bdf4..2e99451 100644 --- a/.github/workflows/mac-build.yml +++ b/.github/workflows/mac-build.yml @@ -1,4 +1,4 @@ -# Build macOS à la demande, SANS rien publier : produit AJEAN.app en artifact +# Build macOS à la demande, SANS rien publier : produit Loki.app en artifact # téléchargeable depuis l'onglet Actions (visible seulement par le dépôt). # Sert à tester le bundle avant de décider d'une vraie release (release.yml). name: mac-build @@ -13,40 +13,40 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: go.mod - - name: build + bundle AJEAN.app (arm64) + - name: build + bundle Loki.app (arm64) run: | mkdir -p dist CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \ - go build -trimpath -ldflags="-s -w" -o dist/ajean-macos-arm ./cmd/ajean - mkdir -p /tmp/ajean.iconset - sips -s format png 'cmd/ajean/icon.ico' --out /tmp/icon.png >/dev/null 2>&1 || true + go build -trimpath -ldflags="-s -w" -o dist/loki-macos-arm ./cmd/loki + mkdir -p /tmp/loki.iconset + sips -s format png 'cmd/loki/icon.ico' --out /tmp/icon.png >/dev/null 2>&1 || true for s in 16 32 128 256 512; do - sips -z $s $s /tmp/icon.png --out "/tmp/ajean.iconset/icon_${s}x${s}.png" >/dev/null 2>&1 || true + sips -z $s $s /tmp/icon.png --out "/tmp/loki.iconset/icon_${s}x${s}.png" >/dev/null 2>&1 || true done - iconutil -c icns /tmp/ajean.iconset -o /tmp/ajean.icns >/dev/null 2>&1 || true - app="dist/AJEAN.app" + iconutil -c icns /tmp/loki.iconset -o /tmp/loki.icns >/dev/null 2>&1 || true + app="dist/Loki.app" mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" - cp dist/ajean-macos-arm "$app/Contents/MacOS/ajean" - chmod +x "$app/Contents/MacOS/ajean" - cp /tmp/ajean.icns "$app/Contents/Resources/ajean.icns" 2>/dev/null || true + cp dist/loki-macos-arm "$app/Contents/MacOS/loki" + chmod +x "$app/Contents/MacOS/loki" + cp /tmp/loki.icns "$app/Contents/Resources/loki.icns" 2>/dev/null || true printf '%s\n' \ '' \ '' \ '' \ - 'CFBundleNameAJEAN' \ - 'CFBundleDisplayNameAJEAN' \ - 'CFBundleExecutableajean' \ - 'CFBundleIdentifierlink.ajean.app' \ - 'CFBundleIconFileajean' \ + 'CFBundleNameLoki' \ + 'CFBundleDisplayNameLoki' \ + 'CFBundleExecutableloki' \ + 'CFBundleIdentifierlink.loki.app' \ + 'CFBundleIconFileloki' \ 'CFBundlePackageTypeAPPL' \ 'LSMinimumSystemVersion11.0' \ 'LSUIElement' \ 'NSHighResolutionCapable' \ '' > "$app/Contents/Info.plist" codesign --force --deep -s - "$app" || true - (cd dist && zip -qry ajean-macos-arm.zip AJEAN.app && rm -rf AJEAN.app) + (cd dist && zip -qry loki-macos-arm.zip Loki.app && rm -rf Loki.app) ls -l dist - uses: actions/upload-artifact@v4 with: - name: ajean-macos-arm - path: dist/ajean-macos-arm.zip + name: loki-macos-arm + path: dist/loki-macos-arm.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed84f34..9cd15e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,11 @@ # Release : sur tag vX.Y.Z, compile les 6 cibles et publie la release GitHub -# avec les binaires (ajean-linux, ajean-macos, ajean-windows.exe et leurs -# variantes -arm) ET le fichier SHA256SUMS que `ajean update` vérifie avant +# avec les binaires (loki-linux, loki-macos, loki-windows.exe et leurs +# variantes -arm) ET le fichier SHA256SUMS que `loki update` vérifie avant # d'installer. Ces noms doivent rester en phase avec assetNameFor (sys_update.go), # sinon plus aucune machine ne se met à jour. # ⚠️ NE PAS uploader de binaires à la main en plus : ce workflow écrase les # assets du même nom (softprops remplace), et des sommes qui ne correspondent -# plus aux binaires font échouer `ajean update` (vécu sur v0.4.7). +# plus aux binaires font échouer `loki update` (vécu sur v0.4.7). # # Deux jobs de build : macOS a besoin d'un vrai runner Apple (l'icône de barre de # menus passe par Cocoa → CGO, pas cross-compilable depuis Linux) ; Linux et @@ -31,43 +31,43 @@ jobs: mkdir -p dist # arm64 = natif ; amd64 = cross avec le SDK universel (-arch x86_64). CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \ - go build -trimpath -ldflags="-s -w" -o dist/ajean-macos-arm ./cmd/ajean + go build -trimpath -ldflags="-s -w" -o dist/loki-macos-arm ./cmd/loki CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \ CGO_CFLAGS="-arch x86_64" CGO_LDFLAGS="-arch x86_64" \ - go build -trimpath -ldflags="-s -w" -o dist/ajean-macos ./cmd/ajean - file dist/ajean-macos* - - name: bundles AJEAN.app + go build -trimpath -ldflags="-s -w" -o dist/loki-macos ./cmd/loki + file dist/loki-macos* + - name: bundles Loki.app run: | # Un binaire nu double-cliqué ouvrirait le Terminal. On publie donc - # aussi un vrai bundle AJEAN.app (zippé pour garder le bit exécutable) : - # au clic, le Finder lance Contents/MacOS/ajean, qui détecte le bundle + # aussi un vrai bundle Loki.app (zippé pour garder le bit exécutable) : + # au clic, le Finder lance Contents/MacOS/loki, qui détecte le bundle # et bascule en mode « application » (UI web + navigateur + icône dans - # la barre de menus), comme le double-clic sur ajean.exe sous Windows. + # la barre de menus), comme le double-clic sur loki.exe sous Windows. # LSUIElement : app de barre de menus, aucune icône dans le Dock. # L'icône part du PNG 1024 généré avec le reste de la marque - # (cmd/ajean/icon.png, voir tools/gen-icon) : plus de conversion depuis + # (cmd/loki/icon.png, voir tools/gen-icon) : plus de conversion depuis # le .ico, dont l'échec silencieux donnait une app sans icône. - mkdir -p /tmp/ajean.iconset + mkdir -p /tmp/loki.iconset for s in 16 32 128 256 512; do - sips -z $s $s 'cmd/ajean/icon.png' --out "/tmp/ajean.iconset/icon_${s}x${s}.png" >/dev/null + sips -z $s $s 'cmd/loki/icon.png' --out "/tmp/loki.iconset/icon_${s}x${s}.png" >/dev/null done - iconutil -c icns /tmp/ajean.iconset -o /tmp/ajean.icns + iconutil -c icns /tmp/loki.iconset -o /tmp/loki.icns v="${GITHUB_REF_NAME#v}" - for bin in ajean-macos ajean-macos-arm; do - app="dist/bundle-$bin/AJEAN.app" + for bin in loki-macos loki-macos-arm; do + app="dist/bundle-$bin/Loki.app" mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" - cp "dist/$bin" "$app/Contents/MacOS/ajean" - chmod +x "$app/Contents/MacOS/ajean" - cp /tmp/ajean.icns "$app/Contents/Resources/ajean.icns" 2>/dev/null || true + cp "dist/$bin" "$app/Contents/MacOS/loki" + chmod +x "$app/Contents/MacOS/loki" + cp /tmp/loki.icns "$app/Contents/Resources/loki.icns" 2>/dev/null || true printf '%s\n' \ '' \ '' \ '' \ - 'CFBundleNameAJEAN' \ - 'CFBundleDisplayNameAJEAN' \ - 'CFBundleExecutableajean' \ - 'CFBundleIdentifierlink.ajean.app' \ - 'CFBundleIconFileajean' \ + 'CFBundleNameLoki' \ + 'CFBundleDisplayNameLoki' \ + 'CFBundleExecutableloki' \ + 'CFBundleIdentifierlink.loki.app' \ + 'CFBundleIconFileloki' \ 'CFBundlePackageTypeAPPL' \ "CFBundleShortVersionString$v" \ "CFBundleVersion$v" \ @@ -78,7 +78,7 @@ jobs: # Signature ad-hoc : sans elle, macOS refuse purement et simplement # de lancer un binaire arm64 non signé (pas juste un avertissement). codesign --force --deep -s - "$app" || true - (cd "dist/bundle-$bin" && zip -qry "../$bin.zip" AJEAN.app) + (cd "dist/bundle-$bin" && zip -qry "../$bin.zip" Loki.app) rm -rf "dist/bundle-$bin" done ls -l dist @@ -98,13 +98,13 @@ jobs: - name: build Linux + Windows run: | mkdir -p dist - # Noms d'asset : ajean-[-arm][.exe]. Volontairement lisibles, et - # volontairement DIFFÉRENTS du schéma ajean-- des 0.7 : + # Noms d'asset : loki-[-arm][.exe]. Volontairement lisibles, et + # volontairement DIFFÉRENTS du schéma loki-- des 0.7 : # leur mise à jour automatique ne trouve alors aucun asset et échoue # sans rien remplacer, au lieu d'installer un binaire 0.8 sur une # machine encore agencée en 0.7 (voir updateAssetName, sys_update.go). - for t in linux/amd64:ajean-linux linux/arm64:ajean-linux-arm \ - windows/amd64:ajean-windows.exe windows/arm64:ajean-windows-arm.exe; do + for t in linux/amd64:loki-linux linux/arm64:loki-linux-arm \ + windows/amd64:loki-windows.exe windows/arm64:loki-windows-arm.exe; do target=${t%%:*}; name=${t#*:} os=${target%/*}; arch=${target#*/}; ldflags="-s -w" # Windows : sous-système CONSOLE, pour que `cmd` attende la fin du @@ -112,7 +112,7 @@ jobs: # fonctionnent. La console créée au double-clic est refermée par le # programme lui-même (voir setupConsole, sys_console_windows.go). GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ - go build -trimpath -ldflags="$ldflags" -o "dist/$name" ./cmd/ajean + go build -trimpath -ldflags="$ldflags" -o "dist/$name" ./cmd/loki done - uses: actions/download-artifact@v4 with: @@ -120,11 +120,11 @@ jobs: path: dist - name: SHA256SUMS run: | - chmod +x dist/ajean-macos dist/ajean-macos-arm - # Tous les assets commencent par « ajean- », zips compris : un seul + chmod +x dist/loki-macos dist/loki-macos-arm + # Tous les assets commencent par « loki- », zips compris : un seul # motif suffit. Ajouter « *.zip » les compterait DEUX fois, et un - # SHA256SUMS à doublons est exactement ce que `ajean update` vérifie. - (cd dist && sha256sum ajean-* > SHA256SUMS) + # SHA256SUMS à doublons est exactement ce que `loki update` vérifie. + (cd dist && sha256sum loki-* > SHA256SUMS) ls -l dist && cat dist/SHA256SUMS - name: publier la release uses: softprops/action-gh-release@v2 diff --git a/.gitignore b/.gitignore index 3710cdb..4244b68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Binaires compilés -/ajean -/ajean.exe +/loki +/loki.exe /dist/ # Artefacts de build Go diff --git a/cmd/ajean/icon.ico b/cmd/loki/icon.ico similarity index 100% rename from cmd/ajean/icon.ico rename to cmd/loki/icon.ico diff --git a/cmd/ajean/icon.png b/cmd/loki/icon.png similarity index 100% rename from cmd/ajean/icon.png rename to cmd/loki/icon.png diff --git a/cmd/ajean/main.go b/cmd/loki/main.go similarity index 74% rename from cmd/ajean/main.go rename to cmd/loki/main.go index 2846d3b..985e385 100644 --- a/cmd/ajean/main.go +++ b/cmd/loki/main.go @@ -1,4 +1,4 @@ -// Point d'entrée du binaire ajean. Tout le code vit dans internal/ajean ; ce +// Point d'entrée du binaire loki. Tout le code vit dans internal/loki ; 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/ajean/tools/gen-icon icon.ico +//go:generate go run github.com/R0m1k3/Loki/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/ajean/internal/ajean" +import "github.com/R0m1k3/Loki/internal/loki" -func main() { ajean.Main() } +func main() { loki.Main() } diff --git a/cmd/ajean/resource_windows_amd64.syso b/cmd/loki/resource_windows_amd64.syso similarity index 100% rename from cmd/ajean/resource_windows_amd64.syso rename to cmd/loki/resource_windows_amd64.syso diff --git a/cmd/ajean/resource_windows_arm64.syso b/cmd/loki/resource_windows_arm64.syso similarity index 100% rename from cmd/ajean/resource_windows_arm64.syso rename to cmd/loki/resource_windows_arm64.syso diff --git a/cmd/ajean/versioninfo.json b/cmd/loki/versioninfo.json similarity index 56% rename from cmd/ajean/versioninfo.json rename to cmd/loki/versioninfo.json index 63296b7..9129eed 100644 --- a/cmd/ajean/versioninfo.json +++ b/cmd/loki/versioninfo.json @@ -19,14 +19,14 @@ "FileSubType": "00" }, "StringFileInfo": { - "CompanyName": "AJEAN contributors", - "FileDescription": "AJEAN — gestionnaire mono-binaire de serveurs llama.cpp auto-hébergés", - "InternalName": "ajean", - "LegalCopyright": "Copyright (c) 2026 AJEAN contributors. MIT License.", - "OriginalFilename": "ajean.exe", - "ProductName": "AJEAN", + "CompanyName": "Loki contributors", + "FileDescription": "Loki — gestionnaire mono-binaire de serveurs llama.cpp auto-hébergés", + "InternalName": "loki", + "LegalCopyright": "Copyright (c) 2026 Loki contributors. MIT License.", + "OriginalFilename": "loki.exe", + "ProductName": "Loki", "ProductVersion": "0.9.4", - "Comments": "https://github.com/nathaninline/ajean — projet open source (MIT)" + "Comments": "https://github.com/R0m1k3/Loki — projet open source (MIT)" }, "VarFileInfo": { "Translation": { diff --git a/go.mod b/go.mod index 39cfd71..86ba813 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/nathaninline/ajean +module github.com/R0m1k3/Loki go 1.25.0 diff --git a/internal/ajean/backend_build.go b/internal/loki/backend_build.go similarity index 98% rename from internal/ajean/backend_build.go rename to internal/loki/backend_build.go index 53e4918..7dc999e 100644 --- a/internal/ajean/backend_build.go +++ b/internal/loki/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 ajean +package loki import ( "bufio" @@ -73,7 +73,7 @@ func detectBuildPlan() buildPlan { // L'UI web embarquée de llama-server exige npm (ou un téléchargement // d'assets pré-compilés depuis HuggingFace) pour générer un service-worker // PWA — une dépendance lourde qui casse le build sur une machine sans node. - // ajean fournit sa propre UI, donc on la désactive : build plus rapide et + // loki fournit sa propre UI, donc on la désactive : build plus rapide et // sans dépendance réseau/npm. BUILD_UI=OFF coupe npm ; USE_PREBUILT_UI=OFF // coupe le téléchargement d'assets pré-compilés depuis HuggingFace (qui // échoue sur un réseau restreint et fait planter l'embed). Sur un checkout @@ -237,7 +237,7 @@ func hintMissingBuildDep(p buildPlan, cfgLog string) { fmt.Printf(" Relance l'installeur du CUDA Toolkit (installation personnalisée) en cochant « CUDA → Visual Studio Integration »\n") fmt.Printf(" (Visual Studio avec le workload C++ doit déjà être installé), ou copie les fichiers de\n") fmt.Printf(" \\extras\\visual_studio_integration\\MSBuildExtensions vers\n") - fmt.Printf(" \\MSBuild\\Microsoft\\VC\\\\BuildCustomizations, puis relance %s.\n", bold("ajean llamacpp install")) + fmt.Printf(" \\MSBuild\\Microsoft\\VC\\\\BuildCustomizations, puis relance %s.\n", bold("loki llamacpp install")) } // Linux/CUDA : nvcc trouvé mais en-têtes/cudart introuvables = le toolkit // complet n'est pas installé (seul le paquet nvcc l'est). On passe déjà @@ -245,15 +245,15 @@ func hintMissingBuildDep(p buildPlan, cfgLog string) { // c'est qu'il manque pour de bon. if p.backend == "cuda" && strings.Contains(log, "CUDA Toolkit not found") { fmt.Printf("\n%s nvcc est présent mais le CUDA Toolkit complet (en-têtes + cudart) est introuvable.\n", yellow("[dépendance]")) - fmt.Printf(" Installe le toolkit NVIDIA officiel (il se pose dans /usr/local/cuda), puis relance %s.\n", bold("ajean llamacpp install")) + fmt.Printf(" Installe le toolkit NVIDIA officiel (il se pose dans /usr/local/cuda), puis relance %s.\n", bold("loki llamacpp install")) } if p.backend == "vulkan" && strings.Contains(log, "SPIRV-Headers") { fmt.Printf("\n%s dépendance manquante pour le backend %s : les en-têtes SPIR-V (paquet « SPIRV-Headers ») sont introuvables.\n", yellow("[dépendance]"), green("Vulkan")) if cmd := pkgInstallHint("spirv-headers"); cmd != "" { - fmt.Printf(" installe-les puis relance %s : %s\n", bold("ajean llamacpp install"), bold(cmd)) + fmt.Printf(" installe-les puis relance %s : %s\n", bold("loki llamacpp install"), bold(cmd)) } else { - fmt.Printf(" installe le paquet de développement « SPIRV-Headers » de ta distribution, puis relance %s.\n", bold("ajean llamacpp install")) + fmt.Printf(" installe le paquet de développement « SPIRV-Headers » de ta distribution, puis relance %s.\n", bold("loki llamacpp install")) } } } diff --git a/internal/ajean/backend_build_cuda_test.go b/internal/loki/backend_build_cuda_test.go similarity index 99% rename from internal/ajean/backend_build_cuda_test.go rename to internal/loki/backend_build_cuda_test.go index fc200be..4222b47 100644 --- a/internal/ajean/backend_build_cuda_test.go +++ b/internal/loki/backend_build_cuda_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "errors" diff --git a/internal/ajean/backend_buildfilter_test.go b/internal/loki/backend_buildfilter_test.go similarity index 94% rename from internal/ajean/backend_buildfilter_test.go rename to internal/loki/backend_buildfilter_test.go index 491f147..e1ac352 100644 --- a/internal/ajean/backend_buildfilter_test.go +++ b/internal/loki/backend_buildfilter_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import "testing" @@ -12,7 +12,7 @@ func TestCompiledFile(t *testing.T) { `[123/456] Building CUDA object ggml/src/ggml-cuda/CMakeFiles/ggml-cuda.dir/acc.cu.o`: "acc.cu", // La ligne de commande nvcc géante ne doit PAS être prise pour un fichier. ` C:\...\nvcc.exe -x cu ... -o ggml-cuda.dir\Release\acc.obj "C:\...\acc.cu"`: "", - `Building Custom Rule C:/ProgramData/ajean/...`: "", + `Building Custom Rule C:/ProgramData/loki/...`: "", `-- UI: running npm install`: "", } for in, want := range cases { diff --git a/internal/ajean/backend_catalog.go b/internal/loki/backend_catalog.go similarity index 96% rename from internal/ajean/backend_catalog.go rename to internal/loki/backend_catalog.go index 380bae4..1d123a1 100644 --- a/internal/ajean/backend_catalog.go +++ b/internal/loki/backend_catalog.go @@ -1,11 +1,11 @@ -package ajean +package loki // 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 (à // venir) propose « en un clic » un modèle adapté à la machine. // // La liste vit sur https://ajean.link/models.json (éditée par l'opérateur, voir -// ajean-relay/models_catalog.go). Si le réseau échoue, on retombe sur un +// loki-relay/models_catalog.go). Si le réseau échoue, on retombe sur un // catalogue minimal embarqué (fallbackCatalogJSON) pour ne jamais bloquer. import ( diff --git a/internal/ajean/backend_config.go b/internal/loki/backend_config.go similarity index 92% rename from internal/ajean/backend_config.go rename to internal/loki/backend_config.go index a08f83f..ab22b72 100644 --- a/internal/ajean/backend_config.go +++ b/internal/loki/backend_config.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -18,7 +18,7 @@ import ( func readAPIKey() string { return getStr(bkState, "api_key") } // authHeader sets the Authorization: Bearer header on req when an API key is -// configured, so AJEAN's own internal calls (chat/web/bench/test) authenticate +// configured, so Loki's own internal calls (chat/web/bench/test) authenticate // against a protected llama-server. No-op when no key is set. func authHeader(req *http.Request) { if k := readAPIKey(); k != "" { @@ -30,7 +30,7 @@ func authHeader(req *http.Request) { func genAPIKey() string { buf := make([]byte, 24) _, _ = rand.Read(buf) - return "sk-ajean-" + hex.EncodeToString(buf) + return "sk-loki-" + hex.EncodeToString(buf) } // writeAPIKey enregistre (key != "") ou supprime (key == "") la clé Bearer. @@ -41,7 +41,7 @@ func writeAPIKey(key string) error { return putStr(bkState, "api_key", key) } -// maskAPIKey renders a key for display: keep the "sk-ajean-" prefix and last 4 +// maskAPIKey renders a key for display: keep the "sk-loki-" prefix and last 4 // chars, elide the middle. Empty in → empty out. func maskAPIKey(k string) string { if k == "" { @@ -57,9 +57,9 @@ func maskAPIKey(k string) string { // internet, le serveur exige alors « Authorization: Bearer » à chaque // appel. // -// ajean set-api-key définit la clé -// ajean set-api-key génère une clé aléatoire -// ajean set-api-key "" supprime la protection +// loki set-api-key définit la clé +// loki set-api-key génère une clé aléatoire +// loki set-api-key "" supprime la protection func cmdSetAPIKey(args []string) error { var key string switch { @@ -83,7 +83,7 @@ func cmdSetAPIKey(args []string) error { fmt.Print(dim("[info] redémarrer le service pour appliquer ? [Y/n] ")) sc := bufio.NewScanner(os.Stdin) if sc.Scan() && strings.HasPrefix(strings.ToLower(strings.TrimSpace(sc.Text())), "n") { - fmt.Println(dim("[info] pense à lancer 'ajean restart'")) + fmt.Println(dim("[info] pense à lancer 'loki restart'")) return nil } return serviceAction("restart") @@ -105,7 +105,7 @@ func WriteConfig(m map[string]string) error { return replaceKV(bkConfig, m) } // eux : une paire ouvrante/fermante du même caractère. // // Un simple Trim(v, `"`) mangeait le guillemet d'un argument interne — par -// exemple EXTRA_ARGS=--chat-template-file "/etc/ajean/tpl.jinja" perdait son +// exemple EXTRA_ARGS=--chat-template-file "/etc/loki/tpl.jinja" perdait son // guillemet final et repartait déséquilibré dans le preset (issue #17 : contenu // tronqué / guillemets non appariés à la création d'un preset). func unquoteValue(v string) string { diff --git a/internal/ajean/backend_config_quote_test.go b/internal/loki/backend_config_quote_test.go similarity index 91% rename from internal/ajean/backend_config_quote_test.go rename to internal/loki/backend_config_quote_test.go index 6f23227..00840ab 100644 --- a/internal/ajean/backend_config_quote_test.go +++ b/internal/loki/backend_config_quote_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "reflect" @@ -10,7 +10,7 @@ import ( // dans le preset (issue #17). func TestParseEnvKeepsInnerQuotes(t *testing.T) { cases := map[string]string{ - `EXTRA_ARGS=--jinja --chat-template-file "/etc/ajean/tpl.jinja"`: `--jinja --chat-template-file "/etc/ajean/tpl.jinja"`, + `EXTRA_ARGS=--jinja --chat-template-file "/etc/loki/tpl.jinja"`: `--jinja --chat-template-file "/etc/loki/tpl.jinja"`, `EXTRA_ARGS="--jinja --flash-attn"`: `--jinja --flash-attn`, `MODEL='/mnt/d/x.gguf'`: `/mnt/d/x.gguf`, `MODEL=/mnt/d/x.gguf`: `/mnt/d/x.gguf`, @@ -31,7 +31,7 @@ func TestParseEnvKeepsInnerQuotes(t *testing.T) { func TestFormatEnvRoundTrip(t *testing.T) { in := map[string]string{ "MODEL": "/mnt/mes modèles/x.gguf", - "EXTRA_ARGS": `--jinja --chat-template-file "/etc/ajean/tpl.jinja"`, + "EXTRA_ARGS": `--jinja --chat-template-file "/etc/loki/tpl.jinja"`, "CTX": "32768", } out := parseEnv(formatEnv(in)) diff --git a/internal/ajean/backend_gpu.go b/internal/loki/backend_gpu.go similarity index 89% rename from internal/ajean/backend_gpu.go rename to internal/loki/backend_gpu.go index b041582..8c0d3d7 100644 --- a/internal/ajean/backend_gpu.go +++ b/internal/loki/backend_gpu.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -11,10 +11,10 @@ import ( // backend_gpu.go — sélection du/des GPU utilisés par llama-server. // -// ajean gpu liste les GPU et montre la sélection courante -// ajean gpu 1 n'utilise que le GPU d'index 1 -// ajean gpu 0 1 utilise les GPU 0 et 1 -// ajean gpu all réinitialise (tous les GPU visibles) +// loki gpu liste les GPU et montre la sélection courante +// loki gpu 1 n'utilise que le GPU d'index 1 +// loki gpu 0 1 utilise les GPU 0 et 1 +// loki gpu all réinitialise (tous les GPU visibles) // // La sélection est stockée dans config.env sous CUDA_VISIBLE_DEVICES ; backend_serve.go // l'exporte (avec CUDA_DEVICE_ORDER=PCI_BUS_ID pour que les index correspondent @@ -53,7 +53,7 @@ func cmdGPU(args []string) error { return fmt.Errorf("index GPU invalide: %q (attendu un nombre)", tok) } if n < 0 || n >= len(gpus) { - return fmt.Errorf("index GPU %d hors limites (0..%d) — voir « ajean gpu »", n, len(gpus)-1) + return fmt.Errorf("index GPU %d hors limites (0..%d) — voir « loki gpu »", n, len(gpus)-1) } idx = append(idx, strconv.Itoa(n)) } @@ -102,7 +102,7 @@ func gpuList() error { } else { fmt.Printf(" Sélection : %s (CUDA_VISIBLE_DEVICES=%s)\n", bold(sel), sel) } - fmt.Printf(" %s ajean gpu pour choisir, ajean gpu all pour réinitialiser\n", dim("→")) + fmt.Printf(" %s loki gpu pour choisir, loki gpu all pour réinitialiser\n", dim("→")) return nil } @@ -120,7 +120,7 @@ func gpuSet(value string) error { fmt.Print(dim("[info] redémarrer le service pour appliquer ? [Y/n] ")) sc := bufio.NewScanner(os.Stdin) if sc.Scan() && strings.HasPrefix(strings.ToLower(strings.TrimSpace(sc.Text())), "n") { - fmt.Println(dim("[info] pense à lancer 'ajean restart'")) + fmt.Println(dim("[info] pense à lancer 'loki restart'")) return nil } return serviceAction("restart") diff --git a/internal/ajean/backend_llamacpp.go b/internal/loki/backend_llamacpp.go similarity index 96% rename from internal/ajean/backend_llamacpp.go rename to internal/loki/backend_llamacpp.go index 25834fc..fd62cab 100644 --- a/internal/ajean/backend_llamacpp.go +++ b/internal/loki/backend_llamacpp.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "fmt" @@ -9,12 +9,12 @@ import ( // backend_llamacpp.go — gestion du backend llama.cpp (clone, build, mise à jour). // -// `ajean llamacpp install` installe un build neuf, détecte automatiquement +// `loki llamacpp install` installe un build neuf, détecte automatiquement // l'accélérateur (CUDA / ROCm / Metal / CPU) et la // compute capability du GPU, puis pointe BIN dessus. -// `ajean llamacpp update` met à jour le dépôt existant (git pull) et recompile +// `loki llamacpp update` met à jour le dépôt existant (git pull) et recompile // avec la bonne config, sans intervention. -// `ajean llamacpp status` montre le commit courant, le backend détecté et le +// `loki llamacpp status` montre le commit courant, le backend détecté et le // retard éventuel sur origin. const llamacppRepoURL = "https://github.com/ggml-org/llama.cpp.git" @@ -55,7 +55,7 @@ func cmdLlamacpp(args []string) error { if err := SetConfigKey("BIN", bin); err != nil { return fmt.Errorf("binaires installés mais échec d'écriture de BIN : %w", err) } - fmt.Printf("%s BIN mis à jour — %s pour appliquer\n", green("✓"), bold("ajean restart")) + fmt.Printf("%s BIN mis à jour — %s pour appliquer\n", green("✓"), bold("loki restart")) return nil default: return fmt.Errorf("sous-commande inconnue: %s (install | update | prebuilt | status)", sub) @@ -68,7 +68,7 @@ func cmdLlamacpp(args []string) error { // llamacppRepoDir resolves the llama.cpp checkout: derived from config BIN when // possible (so `update` targets whatever build the service actually runs), -// otherwise the default under $AJEAN_HOME/backends/llama.cpp. +// otherwise the default under $LOKI_HOME/backends/llama.cpp. func llamacppRepoDir() string { if bin := ReadConfig()["BIN"]; bin != "" { if real, err := filepath.EvalSymlinks(bin); err == nil { @@ -161,7 +161,7 @@ func llamacppInstall(args []string) error { if isDir(filepath.Join(repo, ".git")) { if !force { fmt.Printf("%s dépôt déjà présent dans %s\n", yellow("[info]"), repo) - fmt.Printf(" → %s pour le mettre à jour, ou --force pour repartir de zéro\n", bold("ajean llamacpp update")) + fmt.Printf(" → %s pour le mettre à jour, ou --force pour repartir de zéro\n", bold("loki llamacpp update")) return nil } fmt.Printf("%s --force : suppression de %s\n", yellow("[info]"), repo) @@ -208,7 +208,7 @@ func llamacppInstall(args []string) error { } fmt.Printf("%s BIN mis à jour\n", green("✓")) fmt.Printf("\nProchaines étapes :\n 1. renseigne MODEL : %s\n 2. démarre : %s\n", - bold("ajean edit"), bold("ajean restart")) + bold("loki edit"), bold("loki restart")) return nil } @@ -246,7 +246,7 @@ func llamacppUpdate(args []string) error { repo := llamacppRepoDir() if !isDir(filepath.Join(repo, ".git")) { - return fmt.Errorf("aucun dépôt llama.cpp trouvé (%s).\n → lance d'abord %s", repo, bold("ajean llamacpp install")) + return fmt.Errorf("aucun dépôt llama.cpp trouvé (%s).\n → lance d'abord %s", repo, bold("loki llamacpp install")) } fmt.Printf("%s dépôt : %s\n", cyan("▶"), repo) @@ -317,14 +317,14 @@ func llamacppUpdate(args []string) error { fmt.Printf("\n%s mis à jour : %s → %s\n", green("✓"), oldCommit, newCommit) if noRestart { - fmt.Printf("%s --no-restart : pense à lancer %s\n", dim("[info]"), bold("ajean restart")) + fmt.Printf("%s --no-restart : pense à lancer %s\n", dim("[info]"), bold("loki restart")) return nil } if svcWasUp { fmt.Printf("%s redémarrage du service…\n", cyan("▶")) return serviceAction("start") } - fmt.Printf("%s service non démarré auparavant — lance %s quand tu veux\n", dim("[info]"), bold("ajean start")) + fmt.Printf("%s service non démarré auparavant — lance %s quand tu veux\n", dim("[info]"), bold("loki start")) return nil } @@ -337,7 +337,7 @@ func llamacppStatus(args []string) error { fmt.Printf("%s\n", bold("llama.cpp")) fmt.Printf(" dépôt : %s\n", repo) if !isDir(filepath.Join(repo, ".git")) { - fmt.Printf(" %s pas encore installé — %s\n", yellow("état"), bold("ajean llamacpp install")) + fmt.Printf(" %s pas encore installé — %s\n", yellow("état"), bold("loki llamacpp install")) return nil } commit := gitOutput(repo, "log", "-1", "--format=%h %ci %s") @@ -354,7 +354,7 @@ func llamacppStatus(args []string) error { // Retard sur origin (best-effort, sans fetch réseau). if branch != "" && branch != "HEAD" { if behind := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+branch); behind != "" && behind != "0" { - fmt.Printf(" maj : %s commit(s) de retard sur origin/%s — %s\n", yellow(behind), branch, bold("ajean llamacpp update")) + fmt.Printf(" maj : %s commit(s) de retard sur origin/%s — %s\n", yellow(behind), branch, bold("loki llamacpp update")) } } diff --git a/internal/ajean/backend_model_dirs.go b/internal/loki/backend_model_dirs.go similarity index 94% rename from internal/ajean/backend_model_dirs.go rename to internal/loki/backend_model_dirs.go index b77611d..976ea2b 100644 --- a/internal/ajean/backend_model_dirs.go +++ b/internal/loki/backend_model_dirs.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -10,13 +10,13 @@ import ( "strings" ) -// Les .gguf vivent dans $AJEAN_HOME/models, mais un modèle de 40 Go tient +// Les .gguf vivent dans $LOKI_HOME/models, mais un modèle de 40 Go tient // rarement sur le disque système : on autorise des dossiers supplémentaires // (disque dur externe, second SSD, montage réseau…). La liste est persistée en // base — PAS dans la configuration, que le changement de preset remplace — et -// peut aussi venir de $AJEAN_MODEL_DIRS. +// peut aussi venir de $LOKI_MODEL_DIRS. -// modelDirsListSep sépare les dossiers dans $AJEAN_MODEL_DIRS (':' sous Unix, +// modelDirsListSep sépare les dossiers dans $LOKI_MODEL_DIRS (':' sous Unix, // ';' sous Windows, comme le PATH). func modelDirsListSep() string { if runtime.GOOS == "windows" { @@ -30,7 +30,7 @@ func modelDirsListSep() string { func extraModelDirs() []string { var raw []string getJSON(bkState, "model_dirs", &raw) - if v := os.Getenv("AJEAN_MODEL_DIRS"); v != "" { + if v := os.Getenv("LOKI_MODEL_DIRS"); v != "" { raw = append(raw, strings.Split(v, modelDirsListSep())...) } return dedupDirs(raw, modelsDir()) @@ -92,7 +92,7 @@ func pathWithin(dir, p string) bool { // baseName renvoie le nom de fichier d'un chemin en acceptant les DEUX // séparateurs : un config.env écrit sous Windows (C:\models\x.gguf) peut être -// relu par un ajean Linux, où filepath.Base ne coupe pas sur '\'. +// relu par un loki Linux, où filepath.Base ne coupe pas sur '\'. func baseName(p string) string { p = strings.TrimRight(strings.TrimSpace(p), `/\`) if i := strings.LastIndexAny(p, `/\`); i >= 0 { @@ -128,7 +128,7 @@ func resolveServeModelPath(name string) (string, error) { // absolu) en chemin absolu vers un .gguf. Un chemin absolu n'est accepté que // s'il est dans un des dossiers déclarés — sinon l'UI web deviendrait un moyen // de lire/supprimer n'importe quel fichier de la machine. Un simple nom de -// fichier est cherché dans chaque dossier, AJEAN_HOME en premier. +// fichier est cherché dans chaque dossier, LOKI_HOME en premier. func resolveModelPath(name string) (string, error) { s := strings.TrimSpace(strings.Trim(strings.TrimSpace(name), `"`)) if s == "" { @@ -218,7 +218,7 @@ func resolveDownloadDir(dir string) (string, error) { return "", fmt.Errorf("dossier non autorisé : %s — ajoute-le dans « Dossiers de modèles »", abs) } -// handleModelDirs : GET liste les dossiers de modèles (AJEAN_HOME + ajoutés), +// handleModelDirs : GET liste les dossiers de modèles (LOKI_HOME + ajoutés), // POST {path, action:"add"|"remove"} en ajoute ou en retire un. func handleModelDirs(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { @@ -269,5 +269,5 @@ func handleModelDirs(w http.ResponseWriter, r *http.Request) { n := modelDirCount(d) out = append(out, map[string]any{"path": d, "home": false, "count": n, "exists": n >= 0, "free": diskFree(d)}) } - sendJSON(w, 200, map[string]any{"ok": true, "dirs": out, "env": os.Getenv("AJEAN_MODEL_DIRS") != ""}) + sendJSON(w, 200, map[string]any{"ok": true, "dirs": out, "env": os.Getenv("LOKI_MODEL_DIRS") != ""}) } diff --git a/internal/ajean/backend_model_dirs_test.go b/internal/loki/backend_model_dirs_test.go similarity index 96% rename from internal/ajean/backend_model_dirs_test.go rename to internal/loki/backend_model_dirs_test.go index fbad3f1..4ca6def 100644 --- a/internal/ajean/backend_model_dirs_test.go +++ b/internal/loki/backend_model_dirs_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -11,7 +11,7 @@ import ( func TestResolveModelPathExtraDir(t *testing.T) { testHome(t) ext := t.TempDir() - t.Setenv("AJEAN_MODEL_DIRS", "") + t.Setenv("LOKI_MODEL_DIRS", "") extModel := filepath.Join(ext, "gros.gguf") if err := os.WriteFile(extModel, []byte("x"), 0o644); err != nil { @@ -42,7 +42,7 @@ func TestResolveModelPathExtraDir(t *testing.T) { func TestResolveModelPathHomeFirst(t *testing.T) { testHome(t) ext := t.TempDir() - t.Setenv("AJEAN_MODEL_DIRS", ext) + t.Setenv("LOKI_MODEL_DIRS", ext) if err := os.MkdirAll(modelsDir(), 0o755); err != nil { t.Fatal(err) } diff --git a/internal/ajean/backend_models.go b/internal/loki/backend_models.go similarity index 98% rename from internal/ajean/backend_models.go rename to internal/loki/backend_models.go index 39fae47..d59dd7d 100644 --- a/internal/ajean/backend_models.go +++ b/internal/loki/backend_models.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" @@ -87,7 +87,7 @@ func detectQuant(content string) string { } // downloadDestPath resolves the destination of a downloaded model : le dossier -// demandé (vide = AJEAN_HOME) parmi les dossiers de modèles déclarés, en +// demandé (vide = LOKI_HOME) parmi les dossiers de modèles déclarés, en // refusant tout ce qui en sortirait (path traversal). func downloadDestPath(name, dir string) (string, error) { base := filepath.Base(strings.TrimSpace(name)) @@ -146,7 +146,7 @@ func deleteModelFile(name string) error { return nil } -// handleModelDelete deletes a single .gguf from AJEAN_HOME. +// handleModelDelete deletes a single .gguf from LOKI_HOME. func handleModelDelete(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` @@ -208,10 +208,10 @@ var dlClient = &http.Client{ } // dlConns is the number of parallel range requests used per download. -// Overridable with AJEAN_DL_CONNS (1 disables parallelism). +// Overridable with LOKI_DL_CONNS (1 disables parallelism). func dlConns() int { n := 8 - if v := os.Getenv("AJEAN_DL_CONNS"); v != "" { + if v := os.Getenv("LOKI_DL_CONNS"); v != "" { if p, err := strconv.Atoi(v); err == nil && p > 0 { n = p } @@ -256,7 +256,7 @@ func normalizeHFURL(raw string) (string, string, error) { } // handleModelDownload kicks off a background download of a .gguf from a URL -// (typically Hugging Face) into AJEAN_HOME. Progress is polled via +// (typically Hugging Face) into LOKI_HOME. Progress is polled via // /api/models/download/status. func handleModelDownload(w http.ResponseWriter, r *http.Request) { var req struct { @@ -412,7 +412,7 @@ func dlRequest(ctx context.Context, dlURL, rng string) (*http.Request, error) { if k := os.Getenv("HF_TOKEN"); k != "" { req.Header.Set("Authorization", "Bearer "+k) } - req.Header.Set("User-Agent", "ajean/"+Version) + req.Header.Set("User-Agent", "loki/"+Version) req.Header.Set("Accept-Encoding", "identity") // never gzip a .gguf: it breaks ranges if rng != "" { req.Header.Set("Range", rng) diff --git a/internal/ajean/backend_models_dest_test.go b/internal/loki/backend_models_dest_test.go similarity index 96% rename from internal/ajean/backend_models_dest_test.go rename to internal/loki/backend_models_dest_test.go index ff45b90..f25d7f5 100644 --- a/internal/ajean/backend_models_dest_test.go +++ b/internal/loki/backend_models_dest_test.go @@ -1,4 +1,4 @@ -package ajean +package loki // Destination des téléchargements : le dossier doit être choisi parmi les // dossiers de modèles déclarés (sinon l'UI web écrirait n'importe où), et @@ -14,7 +14,7 @@ import ( func TestDownloadDestPathDir(t *testing.T) { testHome(t) extra := t.TempDir() - t.Setenv("AJEAN_MODEL_DIRS", extra) + t.Setenv("LOKI_MODEL_DIRS", extra) // Dossier vide → models/. got, err := downloadDestPath("m.gguf", "") @@ -77,7 +77,7 @@ func TestDiskFreeAndCheck(t *testing.T) { func TestCleanStalePartFilesExtraDirs(t *testing.T) { testHome(t) extra := t.TempDir() - t.Setenv("AJEAN_MODEL_DIRS", extra) + t.Setenv("LOKI_MODEL_DIRS", extra) stale := filepath.Join(extra, "coupe.gguf.part") if err := os.WriteFile(stale, []byte("x"), 0o644); err != nil { t.Fatal(err) diff --git a/internal/ajean/backend_models_dl_test.go b/internal/loki/backend_models_dl_test.go similarity index 98% rename from internal/ajean/backend_models_dl_test.go rename to internal/loki/backend_models_dl_test.go index b92e808..11fc8e3 100644 --- a/internal/ajean/backend_models_dl_test.go +++ b/internal/loki/backend_models_dl_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bytes" @@ -27,7 +27,7 @@ func serveBlob(data []byte, ranges bool) *httptest.Server { // Un téléchargement annulé ne doit laisser NI le .gguf final NI le .part. func TestRunDownloadCancelLeavesNothing(t *testing.T) { - t.Setenv("AJEAN_DL_CONNS", "4") + t.Setenv("LOKI_DL_CONNS", "4") release := make(chan struct{}) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Range") == "bytes=0-0" { // sonde : réponse immédiate @@ -99,7 +99,7 @@ func TestCleanStalePartFiles(t *testing.T) { func TestRunDownloadParallelAndFallback(t *testing.T) { data := make([]byte, 48<<20) // > 3×dlMinChunk so the split actually kicks in rand.New(rand.NewSource(1)).Read(data) - t.Setenv("AJEAN_DL_CONNS", "4") + t.Setenv("LOKI_DL_CONNS", "4") for _, ranges := range []bool{true, false} { srv := serveBlob(data, ranges) diff --git a/internal/ajean/backend_models_shards.go b/internal/loki/backend_models_shards.go similarity index 97% rename from internal/ajean/backend_models_shards.go rename to internal/loki/backend_models_shards.go index b9fce69..d33a8e4 100644 --- a/internal/ajean/backend_models_shards.go +++ b/internal/loki/backend_models_shards.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "fmt" @@ -15,7 +15,7 @@ import ( // llama-server n'a besoin QUE de la première : il ouvre les suivantes tout seul, // à condition qu'elles soient dans le même dossier. // -// Tout le reste d'AJEAN traitait chaque tranche comme un modèle indépendant : +// Tout le reste d'Loki traitait chaque tranche comme un modèle indépendant : // le lien collé ne rapatriait qu'un fichier sur trois (le moteur mourait sur // « tensor not found »), la liste du sélecteur montrait trois entrées dont deux // ne démarrent pas, et supprimer « le modèle » laissait les deux autres tranches diff --git a/internal/ajean/backend_models_shards_test.go b/internal/loki/backend_models_shards_test.go similarity index 99% rename from internal/ajean/backend_models_shards_test.go rename to internal/loki/backend_models_shards_test.go index c7e6477..f9f10bc 100644 --- a/internal/ajean/backend_models_shards_test.go +++ b/internal/loki/backend_models_shards_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" diff --git a/internal/ajean/backend_prebuilt.go b/internal/loki/backend_prebuilt.go similarity index 99% rename from internal/ajean/backend_prebuilt.go rename to internal/loki/backend_prebuilt.go index 3a4e415..94654d6 100644 --- a/internal/ajean/backend_prebuilt.go +++ b/internal/loki/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 ajean +package loki import ( "archive/tar" @@ -375,7 +375,7 @@ func prebuiltInstall(logf, phasef func(string)) (string, error) { logf(fmt.Sprintf("release %s — variant retenu : %s", tag, label)) // Réinstaller à l'identique est inutile SAUF si l'extraction date d'une - // version de AJEAN qui ignorait les liens des archives (backend installé mais + // version de Loki qui ignorait les liens des archives (backend installé mais // bibliothèques introuvables au lancement) : le marqueur de format force alors // une ré-extraction propre au lieu d'un « déjà à jour » trompeur. if cur := prebuiltServerBin(); curTag == tag && prebuiltVersionFormat() == prebuiltFormat && cur != "" { diff --git a/internal/ajean/backend_prebuilt_reco_test.go b/internal/loki/backend_prebuilt_reco_test.go similarity index 98% rename from internal/ajean/backend_prebuilt_reco_test.go rename to internal/loki/backend_prebuilt_reco_test.go index 497b132..6f20c6d 100644 --- a/internal/ajean/backend_prebuilt_reco_test.go +++ b/internal/loki/backend_prebuilt_reco_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "runtime" diff --git a/internal/ajean/backend_prebuilt_test.go b/internal/loki/backend_prebuilt_test.go similarity index 99% rename from internal/ajean/backend_prebuilt_test.go rename to internal/loki/backend_prebuilt_test.go index fb4a50e..620512f 100644 --- a/internal/ajean/backend_prebuilt_test.go +++ b/internal/loki/backend_prebuilt_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "archive/tar" diff --git a/internal/ajean/backend_presets.go b/internal/loki/backend_presets.go similarity index 99% rename from internal/ajean/backend_presets.go rename to internal/loki/backend_presets.go index 55d6f41..797b5f0 100644 --- a/internal/ajean/backend_presets.go +++ b/internal/loki/backend_presets.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -193,7 +193,7 @@ var preservedKeys = []string{"MEM_MODE", "CRAWL4AI_URL", "WEB_ENGINE", "CUDA_VIS // softPreservedKeys : préservées SEULEMENT si le preset d'arrivée ne les définit // pas lui-même. CUDA_VISIBLE_DEVICES est dans ce cas : c'est d'ordinaire un -// choix de machine (`ajean gpu`) qui doit survivre aux bascules, MAIS un preset +// choix de machine (`loki gpu`) qui doit survivre aux bascules, MAIS un preset // a le droit de l'imposer — « QWEN 3.6 27B FABLE 2 GPU » réclame `1,0` pour que // son `--tensor-split 0.965,0.035` ait bien deux cartes en face. Le préserver // inconditionnellement écrasait cette valeur par l'ancienne (mono-GPU) : le diff --git a/internal/ajean/backend_presets_test.go b/internal/loki/backend_presets_test.go similarity index 96% rename from internal/ajean/backend_presets_test.go rename to internal/loki/backend_presets_test.go index 385bd3a..f05a5f0 100644 --- a/internal/ajean/backend_presets_test.go +++ b/internal/loki/backend_presets_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -36,9 +36,9 @@ func TestPresetFingerprintIgnoresCosmetics(t *testing.T) { // TestSwitchToPresetGardeLesReglagesMachine : basculer de preset ne doit pas // effacer les réglages qui décrivent la MACHINE et non le modèle. Le cas vécu : -// `ajean gpu 1` écrit CUDA_VISIBLE_DEVICES dans config.env, la bascule de preset +// `loki gpu 1` écrit CUDA_VISIBLE_DEVICES dans config.env, la bascule de preset // suivante l'écrasait, llama.cpp revoyait les deux cartes et réétalait le modèle -// sur la petite — pendant que `ajean gpu` réaffichait « auto ». +// sur la petite — pendant que `loki gpu` réaffichait « auto ». func TestSwitchToPresetGardeLesReglagesMachine(t *testing.T) { home := testHome(t) write := func(p, body string) { diff --git a/internal/ajean/backend_serve.go b/internal/loki/backend_serve.go similarity index 91% rename from internal/ajean/backend_serve.go rename to internal/loki/backend_serve.go index ebab289..b65f2ff 100644 --- a/internal/ajean/backend_serve.go +++ b/internal/loki/backend_serve.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bytes" @@ -41,15 +41,15 @@ func cmdServe(args []string) error { cfg := ReadConfig() bin := cfg["BIN"] if bin == "" { - return fmt.Errorf("BIN non défini — lance « ajean edit »") + return fmt.Errorf("BIN non défini — lance « loki edit »") } model := cfg["MODEL"] if model == "" { - return fmt.Errorf("MODEL non défini — lance « ajean edit »") + return fmt.Errorf("MODEL non défini — lance « loki edit »") } - // MODEL vaut soit un simple nom de fichier (le .gguf vit dans AJEAN_HOME ou + // MODEL vaut soit un simple nom de fichier (le .gguf vit dans LOKI_HOME ou // dans un dossier déclaré — disque externe…), soit un chemin absolu. Sous - // systemd/launchd le WorkingDirectory vaut AJEAN_HOME, donc le relatif tombait + // systemd/launchd le WorkingDirectory vaut LOKI_HOME, donc le relatif tombait // juste ; lancé depuis une app de bureau, le répertoire courant est « / » et // llama-server ne trouvait rien. On résout donc explicitement, quel que soit // le contexte de lancement. @@ -69,7 +69,7 @@ func cmdServe(args []string) error { strings.Join(missing, ", "), filepath.Dir(model), len(shardFamily(filepath.Base(model)))) } if !filepath.IsAbs(bin) { - bin = filepath.Join(AjeanHome(), bin) + bin = filepath.Join(LokiHome(), bin) } // Le moteur précompilé s'installe dans un dossier versionné : un preset écrit // avant une mise à jour pointe sur une release qui n'existe plus. On le fait @@ -81,7 +81,7 @@ func cmdServe(args []string) error { // Linux, PATH on Windows — handled inside execServer. setLibraryPath(filepath.Dir(bin)) - // Sélection GPU (ajean gpu) : on filtre les devices visibles par llama-server. + // Sélection GPU (loki gpu) : on filtre les devices visibles par llama-server. // CUDA_DEVICE_ORDER=PCI_BUS_ID garantit que les index correspondent à ceux // affichés par nvidia-smi (sinon CUDA réordonne par "device le plus rapide"). if v := cfg["CUDA_VISIBLE_DEVICES"]; v != "" { @@ -160,12 +160,12 @@ func cmdServe(args []string) error { // Vieux moteur (ou fork) qui ne connaît pas le drapeau : le lui passer // le ferait sortir en erreur au démarrage, donc boucler. On le dit et on // continue sans — mieux vaut un modèle qui réfléchit qu'un moteur mort. - fmt.Fprintf(os.Stderr, "[ajean serve] ce moteur ne connaît pas --reasoning : impossible de désactiver le raisonnement\n") + fmt.Fprintf(os.Stderr, "[loki serve] ce moteur ne connaît pas --reasoning : impossible de désactiver le raisonnement\n") } } // API_KEY protège le serveur quand il est exposé sur internet : llama-server // exige alors l'en-tête "Authorization: Bearer ". La clé est lue depuis - // $AJEAN_HOME/.api_key en priorité (elle survit ainsi aux changements de preset + // $LOKI_HOME/.api_key en priorité (elle survit ainsi aux changements de preset // qui réécrivent config.env), avec config.env comme repli rétro-compatible. if k := readAPIKey(); k != "" { llmArgs = append(llmArgs, "--api-key", k) @@ -176,11 +176,11 @@ func cmdServe(args []string) error { // together so a path with spaces stays one argument. llmArgs = append(llmArgs, splitArgs(cfg["EXTRA_ARGS"])...) - // Working dir = AJEAN_HOME so relative paths in EXTRA_ARGS (e.g. --mmproj + // Working dir = LOKI_HOME so relative paths in EXTRA_ARGS (e.g. --mmproj // mmproj-F16.gguf) still resolve. - _ = os.Chdir(AjeanHome()) + _ = os.Chdir(LokiHome()) - fmt.Fprintf(os.Stderr, "[ajean serve] %s model=%s port=%s\n", + fmt.Fprintf(os.Stderr, "[loki serve] %s model=%s port=%s\n", bin, filepath.Base(model), get("PORT", "8080")) // Hand off to the llama-server process. On Unix this replaces the current diff --git a/internal/ajean/chat_agent.go b/internal/loki/chat_agent.go similarity index 95% rename from internal/ajean/chat_agent.go rename to internal/loki/chat_agent.go index 4eb6aba..f67a003 100644 --- a/internal/ajean/chat_agent.go +++ b/internal/loki/chat_agent.go @@ -1,4 +1,4 @@ -package ajean +package loki import "fmt" @@ -43,7 +43,7 @@ func cmdAgent(args []string) error { fmt.Printf(" %s %s\n", bold(p.Name), p.Title) } default: - return fmt.Errorf("usage: ajean agent [on|off|status]") + return fmt.Errorf("usage: loki agent [on|off|status]") } return nil } diff --git a/internal/ajean/chat_cmd.go b/internal/loki/chat_cmd.go similarity index 93% rename from internal/ajean/chat_cmd.go rename to internal/loki/chat_cmd.go index 653a852..f9417da 100644 --- a/internal/ajean/chat_cmd.go +++ b/internal/loki/chat_cmd.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -12,14 +12,14 @@ import ( // First positional arg (if any) becomes the system prompt. func cmdChat(args []string) error { if !healthCheck() { - return fmt.Errorf("serveur injoignable sur :%d — ajean start d'abord", LLMPort()) + return fmt.Errorf("serveur injoignable sur :%d — loki start d'abord", LLMPort()) } sysPrompt := strings.Join(args, " ") msgs := []Message{} if sysPrompt != "" { msgs = append(msgs, Message{Role: "system", Content: sysPrompt}) } - fmt.Printf("\n%s — /reset pour vider, /sys pour changer le system, /quit ou Ctrl-D pour sortir\n", cyan("ajean chat")) + fmt.Printf("\n%s — /reset pour vider, /sys pour changer le system, /quit ou Ctrl-D pour sortir\n", cyan("loki chat")) if sysPrompt != "" { fmt.Println(dim("system: " + sysPrompt)) } @@ -65,7 +65,7 @@ func cmdChat(args []string) error { inReason := false var stats *StatsEvent // Print the assistant prefix once; reasoning is shown inline with a tag. - fmt.Print(cyan("ajean") + " > ") + fmt.Print(cyan("loki") + " > ") caps := globalCaps() // Compaction proactive (façon Hermes) : on résume les vieux tours quand // l'historique dépasse le seuil, au lieu d'imposer un /reset. @@ -120,7 +120,7 @@ func cmdChat(args []string) error { fmt.Print("\n") inReason = false } - fmt.Printf("\n%s %s : %s\n%s ", dim(icon+" "+verb), "", magenta(ev.ToolUsed.Label), cyan("ajean")+" >") + fmt.Printf("\n%s %s : %s\n%s ", dim(icon+" "+verb), "", magenta(ev.ToolUsed.Label), cyan("loki")+" >") case ev.Reasoning != "": if !inReason { fmt.Print(magenta("[reasoning] ") + dim("")) @@ -129,7 +129,7 @@ func cmdChat(args []string) error { fmt.Print(dim(ev.Reasoning)) case ev.Content != "": if inReason { - fmt.Print("\n" + cyan("ajean") + " > ") + fmt.Print("\n" + cyan("loki") + " > ") inReason = false } full.WriteString(ev.Content) diff --git a/internal/ajean/chat_compact.go b/internal/loki/chat_compact.go similarity index 99% rename from internal/ajean/chat_compact.go rename to internal/loki/chat_compact.go index f5903a8..cd41c37 100644 --- a/internal/ajean/chat_compact.go +++ b/internal/loki/chat_compact.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bytes" @@ -159,7 +159,7 @@ func compactWouldTrigger(msgs []Message, knownTokens int) bool { } // logCompact trace UNE ligne par décision de compaction sur la sortie d'erreur -// (donc dans `journalctl -u ajean-ui`). Sans ça, une compaction qui ne se +// (donc dans `journalctl -u loki-ui`). Sans ça, une compaction qui ne se // déclenche pas — ou qui se déclenche et n'enlève rien — est invisible : côté // UI on ne voit qu'une jauge qui reste haute, sans savoir si le seuil n'a pas // été atteint ou si la réduction a été refusée. @@ -318,7 +318,7 @@ func compactMessages(ctx context.Context, msgs []Message, caps Caps) ([]Message, // Garantie de réduction : on n'accepte la compaction que si elle enlève au // moins ~20% du contexte estimé. Sinon (torse déjà maigre, résumé peu rentable) - // on la refuse — sans ça, ajean « compactait » à presque chaque message sans + // on la refuse — sans ça, loki « compactait » à presque chaque message sans // vraiment réduire, puis re-déclenchait aussitôt. before, after := estimateTokens(msgs), estimateTokens(out) if after > before*4/5 { @@ -387,7 +387,7 @@ Strict rules: no preamble or conclusion, no verbatim or long quotes, no throwawa Write the summary in the SAME language as the conversation.` payload := map[string]any{ - "model": "ajean", + "model": "loki", "messages": []Message{ {Role: "system", Content: sys}, {Role: "user", Content: transcript}, diff --git a/internal/ajean/chat_compact_test.go b/internal/loki/chat_compact_test.go similarity index 99% rename from internal/ajean/chat_compact_test.go rename to internal/loki/chat_compact_test.go index cd0ae37..2f263b6 100644 --- a/internal/ajean/chat_compact_test.go +++ b/internal/loki/chat_compact_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "strings" diff --git a/internal/ajean/chat_conversation.go b/internal/loki/chat_conversation.go similarity index 99% rename from internal/ajean/chat_conversation.go rename to internal/loki/chat_conversation.go index e5edd14..780c9fe 100644 --- a/internal/ajean/chat_conversation.go +++ b/internal/loki/chat_conversation.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" diff --git a/internal/ajean/chat_conversation_test.go b/internal/loki/chat_conversation_test.go similarity index 99% rename from internal/ajean/chat_conversation_test.go rename to internal/loki/chat_conversation_test.go index 214f416..5173852 100644 --- a/internal/ajean/chat_conversation_test.go +++ b/internal/loki/chat_conversation_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" diff --git a/internal/ajean/chat_diff.go b/internal/loki/chat_diff.go similarity index 99% rename from internal/ajean/chat_diff.go rename to internal/loki/chat_diff.go index bd355a0..c8b216d 100644 --- a/internal/ajean/chat_diff.go +++ b/internal/loki/chat_diff.go @@ -1,4 +1,4 @@ -package ajean +package loki // 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/ajean/chat_export.go b/internal/loki/chat_export.go similarity index 92% rename from internal/ajean/chat_export.go rename to internal/loki/chat_export.go index 3c24789..af117a6 100644 --- a/internal/ajean/chat_export.go +++ b/internal/loki/chat_export.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -13,10 +13,10 @@ import ( // chat_export.go — sortir la conversation de la base. // // Jusqu'à la 0.7, l'historique vivait dans un `conversation.json` qu'on pouvait -// ouvrir, lire et copier pour archive. La 0.8 l'a rangé dans ajean.db (bbolt) : +// ouvrir, lire et copier pour archive. La 0.8 l'a rangé dans loki.db (bbolt) : // un fichier binaire, et qui porte de surcroît un verrou EXCLUSIF tant que le // service tourne — donc impossible ne serait-ce que de le copier sans arrêter -// AJEAN. Le fil de discussion et les raisonnements sont toujours là, mais plus +// Loki. Le fil de discussion et les raisonnements sont toujours là, mais plus // personne ne peut les récupérer. C'est une régression pour qui relisait ses // échanges, et ce fichier la répare. // @@ -80,7 +80,7 @@ func exportOptsFromQuery(q map[string][]string) exportOpts { // envoyée au modèle, `log` le journal d'affichage (celui qui porte les // raisonnements, les outils et les vitesses). type exportPayload struct { - Version string `json:"ajean_version"` + Version string `json:"loki_version"` ExportedAt string `json:"exported_at"` CtxUsed int `json:"ctx_used"` Messages []Message `json:"messages"` @@ -199,7 +199,7 @@ func (c *Conversation) ExportMarkdown(o exportOpts) string { } var b strings.Builder - fmt.Fprintf(&b, "# Conversation AJEAN\n\nExportée le %s par AJEAN %s.\n", + fmt.Fprintf(&b, "# Conversation Loki\n\nExportée le %s par Loki %s.\n", time.Now().Format("02/01/2006 à 15:04"), Version) // Un export tronqué ou allégé doit le DIRE : relu six mois plus tard, un fil // sans ses raisonnements ne doit pas passer pour le fil complet. @@ -208,12 +208,12 @@ func (c *Conversation) ExportMarkdown(o exportOpts) string { } // openBubble : une bulle assistant est ouverte (au moins un bout de réponse a - // été écrit). Sert à ne poser l'en-tête « AJEAN » qu'une fois par tour, même + // été écrit). Sert à ne poser l'en-tête « Loki » qu'une fois par tour, même // quand la réponse est entrecoupée d'appels d'outils. openBubble := false head := func() { if !openBubble { - b.WriteString("\n## AJEAN\n") + b.WriteString("\n## Loki\n") openBubble = true } } @@ -303,7 +303,7 @@ func mdInline(v any) string { // exportFilename : nom proposé au téléchargement, horodaté pour que deux exports // ne s'écrasent pas dans le dossier de téléchargements. func exportFilename(ext string) string { - return "ajean-conversation-" + time.Now().Format("2006-01-02-1504") + "." + ext + return "loki-conversation-" + time.Now().Format("2006-01-02-1504") + "." + ext } // exportBody rend la conversation selon les options. Renvoie le corps, son type @@ -318,10 +318,10 @@ func exportBody(o exportOpts) ([]byte, string, string, error) { // cmdExport écrit la conversation dans un fichier (ou sur la sortie standard). // -// ajean export → ajean-conversation-.md dans le dossier courant -// ajean export --json → idem en JSON -// ajean export mon-fil.md → nom de fichier imposé (l'extension choisit le format) -// ajean export - → sur la sortie standard, pour enchaîner un tube +// loki export → loki-conversation-.md dans le dossier courant +// loki export --json → idem en JSON +// loki export mon-fil.md → nom de fichier imposé (l'extension choisit le format) +// loki export - → sur la sortie standard, pour enchaîner un tube // // Options de contenu, les mêmes que la fenêtre d'export de l'interface : // @@ -357,12 +357,12 @@ func cmdExport(args []string) error { o.Turns = n default: if strings.HasPrefix(a, "-") && a != "-" { - return fmt.Errorf("option inconnue : %s (voir « ajean help »)", a) + return fmt.Errorf("option inconnue : %s (voir « loki help »)", a) } out = a } } - // Une extension explicite l'emporte sur le drapeau : `ajean export fil.json` + // Une extension explicite l'emporte sur le drapeau : `loki export fil.json` // qui produirait du Markdown serait un piège. if strings.HasSuffix(strings.ToLower(out), ".json") { o.Format = "json" diff --git a/internal/ajean/chat_export_test.go b/internal/loki/chat_export_test.go similarity index 98% rename from internal/ajean/chat_export_test.go rename to internal/loki/chat_export_test.go index 5a446ad..6b4699f 100644 --- a/internal/ajean/chat_export_test.go +++ b/internal/loki/chat_export_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -37,7 +37,7 @@ func TestExportMarkdownPorteToutLeFil(t *testing.T) { md := conv.ExportMarkdown(defaultExportOpts()) for _, want := range []string{ "## Vous", "combien font 2+2 ?", - "## AJEAN", + "## Loki", "Raisonnement", "addition simple", "bash", "echo 4", } { @@ -45,9 +45,9 @@ func TestExportMarkdownPorteToutLeFil(t *testing.T) { t.Errorf("Markdown exporté sans %q :\n%s", want, md) } } - // L'en-tête AJEAN ne doit apparaître qu'UNE fois pour un tour, même entrecoupé + // L'en-tête Loki ne doit apparaître qu'UNE fois pour un tour, même entrecoupé // d'un appel d'outil. - if n := strings.Count(md, "\n## AJEAN\n"); n != 1 { + if n := strings.Count(md, "\n## Loki\n"); n != 1 { t.Errorf("en-tête assistant répété %d fois", n) } } diff --git a/internal/ajean/chat_internet.go b/internal/loki/chat_internet.go similarity index 99% rename from internal/ajean/chat_internet.go rename to internal/loki/chat_internet.go index 061d155..0f4b642 100644 --- a/internal/ajean/chat_internet.go +++ b/internal/loki/chat_internet.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bytes" @@ -18,7 +18,7 @@ import ( // Accès internet de l'IA — port Go de l'extension pi ~/.pi/agent/extensions/web.ts. // -// ajean parle à un serveur Crawl4AI (Chrome headless, endpoint /crawl) dont l'URL +// loki parle à un serveur Crawl4AI (Chrome headless, endpoint /crawl) dont l'URL // est configurée dans config.env (CRAWL4AI_URL). Quand le mode agent ET l'accès // internet sont actifs ET que le serveur répond, l'IA dispose de 4 outils : // diff --git a/internal/ajean/chat_internet_tools.go b/internal/loki/chat_internet_tools.go similarity index 93% rename from internal/ajean/chat_internet_tools.go rename to internal/loki/chat_internet_tools.go index 438fd45..80719cb 100644 --- a/internal/ajean/chat_internet_tools.go +++ b/internal/loki/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 `ajean internet`. -package ajean +// web_open, web_read, web_grep) + la sous-commande `loki internet`. +package loki import ( "fmt" @@ -281,7 +281,7 @@ func toolWebGrep(args map[string]any) string { entry.url, len(matchIdx), pattern, capped, strings.Join(blocks, "\n\n---\n\n")) } -// ─── CLI : ajean internet [on|off|status|engine|url|key] ──────────────────── +// ─── CLI : loki internet [on|off|status|engine|url|key] ──────────────────── func cmdInternet(args []string) error { sub := "" @@ -292,7 +292,7 @@ func cmdInternet(args []string) error { case "on": // Le moteur intégré ne demande aucun réglage ; Crawl4AI exige un serveur. if webEngine() == engineCrawl && crawl4aiURL() == "" { - return fmt.Errorf("configure d'abord l'URL : ajean internet url (ou bascule sur le moteur intégré : ajean internet engine go)") + return fmt.Errorf("configure d'abord l'URL : loki internet url (ou bascule sur le moteur intégré : loki internet engine go)") } if err := setInternetEnabled(true); err != nil { return err @@ -305,7 +305,7 @@ func cmdInternet(args []string) error { fmt.Println(green("[ok]") + " accès internet désactivé") case "url": if len(args) < 2 { - return fmt.Errorf("usage: ajean internet url (ex: http://localhost:11235)") + return fmt.Errorf("usage: loki internet url (ex: http://localhost:11235)") } u := strings.TrimRight(strings.TrimSpace(args[1]), "/") if err := SetConfigKey("CRAWL4AI_URL", u); err != nil { @@ -317,7 +317,7 @@ func cmdInternet(args []string) error { fmt.Printf("%s serveur Crawl4AI : %s\n", green("[ok]"), bold(u)) case "key": if len(args) < 2 { - return fmt.Errorf("usage: ajean internet key (vide pour l'enlever : ajean internet key \"\")") + return fmt.Errorf("usage: loki internet key (vide pour l'enlever : loki internet key \"\")") } k := strings.TrimSpace(args[1]) if err := writeCrawlKey(k); err != nil { @@ -333,7 +333,7 @@ func cmdInternet(args []string) error { } case "engine": if len(args) < 2 { - return fmt.Errorf("usage: ajean internet engine ") + return fmt.Errorf("usage: loki internet engine ") } e := strings.ToLower(strings.TrimSpace(args[1])) if err := setWebEngine(e); err != nil { @@ -342,7 +342,7 @@ func cmdInternet(args []string) error { if e == engineGo { fmt.Println(green("[ok]") + " moteur intégré — aucune installation requise (pas de rendu JavaScript)") } else { - fmt.Println(green("[ok]") + " moteur Crawl4AI — configure le serveur : ajean internet url ") + fmt.Println(green("[ok]") + " moteur Crawl4AI — configure le serveur : loki internet url ") } case "", "status", "list": state := dim("off") @@ -358,7 +358,7 @@ func cmdInternet(args []string) error { fmt.Printf(" moteur : %s\n", bold("crawl4ai")) u := crawl4aiURL() if u == "" { - fmt.Printf(" serveur : %s — configure : ajean internet url \n", dim("(non configuré)")) + fmt.Printf(" serveur : %s — configure : loki internet url \n", dim("(non configuré)")) return nil } reach := red("injoignable") @@ -368,7 +368,7 @@ func cmdInternet(args []string) error { fmt.Printf(" serveur : %s (%s)\n", bold(u), reach) fmt.Printf(" outils : web_search, web_open, web_read, web_grep\n") default: - return fmt.Errorf("usage: ajean internet [on|off|status|engine |url |key ]") + return fmt.Errorf("usage: loki internet [on|off|status|engine |url |key ]") } return nil } diff --git a/internal/ajean/chat_memory.go b/internal/loki/chat_memory.go similarity index 97% rename from internal/ajean/chat_memory.go rename to internal/loki/chat_memory.go index c3e4cfe..a53d8ca 100644 --- a/internal/ajean/chat_memory.go +++ b/internal/loki/chat_memory.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "errors" @@ -10,7 +10,7 @@ import ( "strings" ) -// La mémoire de ajean = des fichiers Markdown plats sous memory/.md. +// La mémoire de loki = des fichiers Markdown plats sous memory/.md. // L'IA y range ce qu'elle veut retenir entre les sessions (préférences, // décisions, procédures, infos projet). Quatre outils : mem_search, mem_read, // mem_add, mem_edit. @@ -331,7 +331,7 @@ func setMemMode(m MemMode) error { return SetConfigKey("MEM_MODE", string(m)) } -// cmdMemory : ajean memory [off|ondemand|always|status] +// cmdMemory : loki memory [off|ondemand|always|status] func cmdMemory(args []string) error { sub := "" if len(args) > 0 { @@ -362,7 +362,7 @@ func cmdMemory(args []string) error { fmt.Printf(" %d page(s) sous %s\n", len(pages), memoryDir()) return nil default: - return fmt.Errorf("usage: ajean memory [off|ondemand|always|status]") + return fmt.Errorf("usage: loki memory [off|ondemand|always|status]") } m := memMode() fmt.Printf("%s mémoire : %s — %s\n", green("[ok]"), bold(string(m)), label[m]) diff --git a/internal/ajean/chat_prompt_size_test.go b/internal/loki/chat_prompt_size_test.go similarity index 99% rename from internal/ajean/chat_prompt_size_test.go rename to internal/loki/chat_prompt_size_test.go index fde8d5c..24e5720 100644 --- a/internal/ajean/chat_prompt_size_test.go +++ b/internal/loki/chat_prompt_size_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" diff --git a/internal/ajean/chat_stuck_test.go b/internal/loki/chat_stuck_test.go similarity index 99% rename from internal/ajean/chat_stuck_test.go rename to internal/loki/chat_stuck_test.go index 60fa5a0..47f6ef1 100644 --- a/internal/ajean/chat_stuck_test.go +++ b/internal/loki/chat_stuck_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" diff --git a/internal/ajean/chat_tools.go b/internal/loki/chat_tools.go similarity index 97% rename from internal/ajean/chat_tools.go rename to internal/loki/chat_tools.go index a2c9d42..c300d22 100644 --- a/internal/ajean/chat_tools.go +++ b/internal/loki/chat_tools.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" @@ -41,9 +41,9 @@ func baseSystemPrompt(caps Caps) string { // sur-raisonner les modèles à reasoning (Qwen3) : ils émettent leur // puis le token de fin SANS appeler d'outil (~25-45 % de tours « morts » // mesurés). Une version courte et directe ramène ça à 0 %. NE PAS regonfler. - // « AJEAN » avec une majuscule : c'est un nom propre, et le modèle recopie - // littéralement la casse d'ici quand il se présente (« je suis ajean »). - b.WriteString("You are AJEAN, an expert assistant operating directly on this machine with real tools.") + // « Loki » avec une majuscule : c'est un nom propre, et le modèle recopie + // littéralement la casse d'ici quand il se présente (« je suis loki »). + b.WriteString("You are Loki, an expert assistant operating directly on this machine with real tools.") if caps.Mem == MemAlways { b.WriteString(" You evolve with every conversation: you actively maintain a persistent memory so nothing useful is lost between sessions.") } @@ -161,7 +161,7 @@ func machineSystemPrompt(caps Caps) string { } b.WriteString(".") if cwd != "" { - b.WriteString(" Relative paths in write/edit/bash resolve inside this working folder — put scratch files there. Write outside it ONLY with an absolute path the user explicitly asked for; never scatter files into the folder ajean was launched from.") + b.WriteString(" Relative paths in write/edit/bash resolve inside this working folder — put scratch files there. Write outside it ONLY with an absolute path the user explicitly asked for; never scatter files into the folder loki was launched from.") } return b.String() } @@ -185,7 +185,7 @@ func runShell(parent context.Context, command string, timeoutSec int) string { ctx, cancel := context.WithTimeout(parent, time.Duration(timeoutSec)*time.Second) defer cancel() cmd := newShellCmd(ctx, command) - // Le shell démarre dans le workspace, pas dans le dossier d'où ajean a été + // Le shell démarre dans le workspace, pas dans le dossier d'où loki a été // lancé : un `> notes.txt` du modèle ne doit pas atterrir sur le Bureau. // // Le dossier est résolu UNE fois par process (agentWorkspace), donc s'il @@ -208,7 +208,7 @@ func runShell(parent context.Context, command string, timeoutSec int) string { // commande du genre « ./serveur & » rend la main tout de suite mais laisse // un process en arrière-plan accroché aux tubes : runShell ne revenait alors // JAMAIS, ni au délai, ni au stop. Le tour restait bloqué à vie, et la seule - // issue connue était de redémarrer ajean-ui. + // issue connue était de redémarrer loki-ui. cmd.WaitDelay = 2 * time.Second err := cmd.Run() switch { diff --git a/internal/ajean/chat_tools_write_test.go b/internal/loki/chat_tools_write_test.go similarity index 98% rename from internal/ajean/chat_tools_write_test.go rename to internal/loki/chat_tools_write_test.go index ab07b4d..bb0aeb6 100644 --- a/internal/ajean/chat_tools_write_test.go +++ b/internal/loki/chat_tools_write_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" diff --git a/internal/ajean/chat_workspace.go b/internal/loki/chat_workspace.go similarity index 86% rename from internal/ajean/chat_workspace.go rename to internal/loki/chat_workspace.go index 20016fb..faae313 100644 --- a/internal/ajean/chat_workspace.go +++ b/internal/loki/chat_workspace.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -11,14 +11,14 @@ import ( // // Sans ça, les outils write/edit/bash héritent du répertoire courant du PROCESSUS : // le modèle écrit "meteo.json", et le fichier atterrit là d'où l'utilisateur a -// lancé ajean — le Bureau quand on double-clique l'installateur, C:\ProgramData\ajean\bin +// lancé loki — le Bureau quand on double-clique l'installateur, C:\ProgramData\loki\bin // quand on lance le binaire installé. Personne ne s'attend à voir un chat déposer // des fichiers sur son Bureau. On donne donc à l'agent un dossier à lui : les // chemins relatifs y sont résolus, et le shell y démarre. Les chemins ABSOLUS // restent honorés tels quels — quand l'utilisateur demande d'écrire dans un // dossier précis, ça doit marcher. -const workspaceEnv = "AJEAN_WORKSPACE" +const workspaceEnv = "LOKI_WORKSPACE" var ( workspaceOnce sync.Once @@ -26,7 +26,7 @@ var ( ) // agentWorkspace renvoie le dossier de travail de l'agent, créé au besoin. Il -// essaie plusieurs emplacements car AjeanHome() vaut %ProgramData%\ajean sous +// essaie plusieurs emplacements car LokiHome() vaut %ProgramData%\loki sous // Windows, qui n'est pas inscriptible par un utilisateur non administrateur. func agentWorkspace() string { workspaceOnce.Do(func() { @@ -62,9 +62,9 @@ func workspaceCandidates() []string { } c = append(c, workspaceDir()) if home, err := os.UserHomeDir(); err == nil && home != "" { - c = append(c, filepath.Join(home, "ajean", "workspace")) + c = append(c, filepath.Join(home, "loki", "workspace")) } - c = append(c, filepath.Join(os.TempDir(), "ajean-workspace")) + c = append(c, filepath.Join(os.TempDir(), "loki-workspace")) return c } diff --git a/internal/ajean/chat_workspace_test.go b/internal/loki/chat_workspace_test.go similarity index 91% rename from internal/ajean/chat_workspace_test.go rename to internal/loki/chat_workspace_test.go index ae8c295..acb1a52 100644 --- a/internal/ajean/chat_workspace_test.go +++ b/internal/loki/chat_workspace_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "path/filepath" @@ -7,7 +7,7 @@ import ( ) // Un chemin relatif du modèle doit atterrir dans le workspace, jamais dans le -// répertoire courant du processus (Bureau, C:\ProgramData\ajean\bin…). +// répertoire courant du processus (Bureau, C:\ProgramData\loki\bin…). func TestResolveAgentPathRelative(t *testing.T) { got := resolveAgentPath("meteo.json") want := filepath.Join(agentWorkspace(), "meteo.json") diff --git a/internal/ajean/cli_app.go b/internal/loki/cli_app.go similarity index 83% rename from internal/ajean/cli_app.go rename to internal/loki/cli_app.go index cf53a78..1aa7385 100644 --- a/internal/ajean/cli_app.go +++ b/internal/loki/cli_app.go @@ -1,12 +1,12 @@ -package ajean +package loki -// cli_app.go — expérience « application » de AJEAN. +// cli_app.go — expérience « application » de Loki. // // Quand on double-clique sur le binaire (aucun argument, console fraîche), au -// lieu d'afficher l'aide dans une console qui se ferme, AJEAN démarre son UI web, +// lieu d'afficher l'aide dans une console qui se ferme, Loki démarre son UI web, // l'ouvre dans le navigateur ET pose une icône dans la zone de notification -// Windows (voir sys_tray_windows.go) : on voit que AJEAN tourne et on le pilote -// (« Ouvrir AJEAN » / « Quitter »). +// Windows (voir sys_tray_windows.go) : on voit que Loki tourne et on le pilote +// (« Ouvrir Loki » / « Quitter »). import ( "fmt" @@ -34,13 +34,13 @@ func cmdApp(args []string) error { addr := fmt.Sprintf("0.0.0.0:%d", appPort) ln, err := net.Listen("tcp", addr) if err != nil { - // AJEAN tourne déjà sur ce port : on ouvre juste l'UI sur l'instance + // Loki tourne déjà sur ce port : on ouvre juste l'UI sur l'instance // existante plutôt que d'échouer. - fmt.Printf("AJEAN est déjà lancé — ouverture de %s\n", url) + fmt.Printf("Loki est déjà lancé — ouverture de %s\n", url) return openBrowser(url) } - // UN SEUL process propriétaire de la conversation, comme le service ajean-ui + // UN SEUL process propriétaire de la conversation, comme le service loki-ui // sous Linux : l'app sert l'UI locale ET le tunnel avec le MÊME mux. Deux // process qui servent la conversation (objet en mémoire persisté dans // conversation.json) donnent deux fils divergents entre le local et @@ -60,7 +60,7 @@ func cmdApp(args []string) error { startAppLink(appWebMux) }() - sp := showSplash("Lancement d'AJEAN en cours…") + sp := showSplash("Lancement d'Loki en cours…") waitServerReady(url) _ = openBrowser(url) time.Sleep(900 * time.Millisecond) // laisse le navigateur s'afficher par-dessus le splash diff --git a/internal/ajean/doc.go b/internal/loki/doc.go similarity index 86% rename from internal/ajean/doc.go rename to internal/loki/doc.go index 1b31409..e7f58d1 100644 --- a/internal/ajean/doc.go +++ b/internal/loki/doc.go @@ -1,8 +1,8 @@ -// AJEAN — cœur du binaire (package ajean, appelé par cmd/ajean). Go n'autorise +// Loki — cœur du binaire (package loki, appelé par cmd/loki). Go n'autorise // pas de sous-dossiers dans un même package : les fichiers sont donc préfixés // par domaine. // -// run.go point d'entrée : dispatch des sous-commandes, AjeanHome() et +// run.go point d'entrée : dispatch des sous-commandes, LokiHome() et // l'arborescence de données // store.go LA base (bbolt) : configuration, préférences, conversation, // clés, jetons, interrupteurs — tout l'état non éditable à la main @@ -28,11 +28,11 @@ // supprimer un symbole jugé inutilisé, vérifier `grep` sur les fichiers _darwin. // // DEUX SERVICES à l'exécution, un seul binaire : -// - ajean-engine (« ajean serve ») exec llama-server ; -// - ajean-ui (« ajean web ») sert l'UI locale, le tunnel du relais et +// - loki-engine (« loki serve ») exec llama-server ; +// - loki-ui (« loki web ») sert l'UI locale, le tunnel du relais et // l'endpoint OpenAI depuis un SEUL process — condition d'une conversation // unique, celle-ci vivant en mémoire (voir chat_conversation.go). // // ui/index.html est GÉNÉRÉ depuis ui/src/ : éditer les sources puis -// `go generate ./internal/ajean` (voir tools/assemble-ui). -package ajean +// `go generate ./internal/loki` (voir tools/assemble-ui). +package loki diff --git a/internal/ajean/llm_bench.go b/internal/loki/llm_bench.go similarity index 99% rename from internal/ajean/llm_bench.go rename to internal/loki/llm_bench.go index 1a29714..53b84dc 100644 --- a/internal/ajean/llm_bench.go +++ b/internal/loki/llm_bench.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bytes" @@ -70,7 +70,7 @@ func runBench(nPrompt, nPredict int) (*benchResult, error) { // Use the same endpoint your real chat hits, so the comparison is honest // (chat template, reasoning, OpenAI-compat layer all included). payload := map[string]any{ - "model": "ajean", + "model": "loki", "messages": []Message{{Role: "user", Content: prompt + "\n\nContinue this passage with another 1000+ words of original varied prose, mixing French and English narrative paragraphs on different topics."}}, "max_tokens": nPredict, "stream": false, diff --git a/internal/ajean/llm_client.go b/internal/loki/llm_client.go similarity index 99% rename from internal/ajean/llm_client.go rename to internal/loki/llm_client.go index f6ced71..02e0fcd 100644 --- a/internal/ajean/llm_client.go +++ b/internal/loki/llm_client.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -575,7 +575,7 @@ func runChat(ctx context.Context, messages []Message, temperature float64, caps // le contexte — c'est un choix assumé. for iter := 0; ; iter++ { payload := map[string]any{ - "model": "ajean", + "model": "loki", "messages": messages, "stream": true, "temperature": temperature, diff --git a/internal/ajean/llm_oai.go b/internal/loki/llm_oai.go similarity index 93% rename from internal/ajean/llm_oai.go rename to internal/loki/llm_oai.go index a018cce..762ac4b 100644 --- a/internal/ajean/llm_oai.go +++ b/internal/loki/llm_oai.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "bufio" @@ -83,12 +83,12 @@ func setOAIPublic(on bool) error { return putBool(bkState, "oai_public", on) } // oaiTLSConfig renvoie une config TLS qui, à la demande, obtient/renouvelle via // Let's Encrypt (TLS-ALPN-01) le certificat de tout nom en *.oai.ajean.link, et // répond elle-même aux challenges ACME. La clé privée est stockée dans -// $AJEAN_HOME/certs et ne quitte jamais la machine. Toujours construite ; c'est le +// $LOKI_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(AjeanHome(), "certs")} + certmagic.Default.Storage = &certmagic.FileStorage{Path: filepath.Join(LokiHome(), "certs")} certmagic.DefaultACME.Agreed = true - certmagic.DefaultACME.Email = os.Getenv("AJEAN_ACME_EMAIL") + certmagic.DefaultACME.Email = os.Getenv("LOKI_ACME_EMAIL") certmagic.DefaultACME.DisableHTTPChallenge = true // pas de :80 accessible (CGNAT) → TLS-ALPN uniquement magic := certmagic.NewDefault() magic.OnDemand = &certmagic.OnDemandConfig{ @@ -206,7 +206,7 @@ func selfSignedTLSConfig(host string) (*tls.Config, error) { // cmdOAI pilote l'accès OpenAI public côté agent. // -// ajean oai serve [port] [host] (test local) termine le TLS sur :port avec un +// loki oai serve [port] [host] (test local) termine le TLS sur :port avec un // cert auto-signé et proxifie vers llama /v1. func cmdOAI(args []string) error { sub := "" @@ -236,12 +236,12 @@ func cmdOAI(args []string) error { if err != nil { return err } - fmt.Printf("[ajean oai] front TLS (test, auto-signé) https://%s:%d/v1 → llama :%d\n", host, port, LLMPort()) + fmt.Printf("[loki oai] front TLS (test, auto-signé) https://%s:%d/v1 → llama :%d\n", host, port, LLMPort()) return runOAIFront(ln, tlsCfg) default: - fmt.Println("usage: ajean oai serve [port] [host] (front TLS de test → llama /v1)") - fmt.Println(" en prod, le front TLS est servi automatiquement dans le tunnel (ajean link)") - fmt.Println(" quand AJEAN_LINK_ALLOW_OAI=1 ; cert Let's Encrypt via TLS-ALPN-01.") + fmt.Println("usage: loki oai serve [port] [host] (front TLS de test → llama /v1)") + fmt.Println(" en prod, le front TLS est servi automatiquement dans le tunnel (loki link)") + fmt.Println(" quand LOKI_LINK_ALLOW_OAI=1 ; cert Let's Encrypt via TLS-ALPN-01.") return nil } } diff --git a/internal/ajean/llm_repeat_test.go b/internal/loki/llm_repeat_test.go similarity index 98% rename from internal/ajean/llm_repeat_test.go rename to internal/loki/llm_repeat_test.go index b9b731a..0cff050 100644 --- a/internal/ajean/llm_repeat_test.go +++ b/internal/loki/llm_repeat_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "strings" diff --git a/internal/ajean/llm_stream_cut_test.go b/internal/loki/llm_stream_cut_test.go similarity index 99% rename from internal/ajean/llm_stream_cut_test.go rename to internal/loki/llm_stream_cut_test.go index 71436b7..dbd8a2b 100644 --- a/internal/ajean/llm_stream_cut_test.go +++ b/internal/loki/llm_stream_cut_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" diff --git a/internal/ajean/llm_testcmd.go b/internal/loki/llm_testcmd.go similarity index 95% rename from internal/ajean/llm_testcmd.go rename to internal/loki/llm_testcmd.go index 081d2bf..26bbbfb 100644 --- a/internal/ajean/llm_testcmd.go +++ b/internal/loki/llm_testcmd.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" @@ -14,7 +14,7 @@ func cmdTest(args []string) error { fmt.Printf("→ GET http://localhost:%d/health … ", port) if !healthCheck() { fmt.Println(red("ko")) - return fmt.Errorf("/health ne répond pas — ajean start d'abord") + return fmt.Errorf("/health ne répond pas — loki start d'abord") } fmt.Println(green("ok")) diff --git a/internal/ajean/mcp_client.go b/internal/loki/mcp_client.go similarity index 99% rename from internal/ajean/mcp_client.go rename to internal/loki/mcp_client.go index 7fcdf02..fd932b1 100644 --- a/internal/ajean/mcp_client.go +++ b/internal/loki/mcp_client.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" @@ -127,7 +127,7 @@ func (h headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) // connect établit une session pour la config donnée. func mcpConnect(ctx context.Context, name string, cfg MCPServerConfig) (*mcpsdk.ClientSession, error) { - client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "ajean", Version: Version}, nil) + client := mcpsdk.NewClient(&mcpsdk.Implementation{Name: "loki", Version: Version}, nil) var transport mcpsdk.Transport switch cfg.Transport() { diff --git a/internal/ajean/mcp_client_test.go b/internal/loki/mcp_client_test.go similarity index 91% rename from internal/ajean/mcp_client_test.go rename to internal/loki/mcp_client_test.go index c65207e..a6008c5 100644 --- a/internal/ajean/mcp_client_test.go +++ b/internal/loki/mcp_client_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os/exec" @@ -17,7 +17,7 @@ func TestMCPEnsureDeduplicatesConcurrentConnects(t *testing.T) { // Commande inexistante : la connexion échoue, mais l'entrée du pool est bien // partagée — c'est ce qu'on teste. - cfg := MCPServerConfig{Command: "ajean-binaire-inexistant-pour-test", Enabled: true} + cfg := MCPServerConfig{Command: "loki-binaire-inexistant-pour-test", Enabled: true} const callers = 8 var wg sync.WaitGroup @@ -83,8 +83,8 @@ func TestMCPEndToEnd(t *testing.T) { t.Fatalf("outil %s introuvable dans %d outils", echoName, len(tools)) } - out := mcpCall(echoName, map[string]any{"message": "bonjour-ajean"}) - if !strings.Contains(out, "bonjour-ajean") { + out := mcpCall(echoName, map[string]any{"message": "bonjour-loki"}) + if !strings.Contains(out, "bonjour-loki") { t.Fatalf("echo n'a pas renvoyé le message ; got: %q", out) } } diff --git a/internal/ajean/mcp_config.go b/internal/loki/mcp_config.go similarity index 95% rename from internal/ajean/mcp_config.go rename to internal/loki/mcp_config.go index 678de75..9ab71fc 100644 --- a/internal/ajean/mcp_config.go +++ b/internal/loki/mcp_config.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -10,14 +10,14 @@ import ( // Configuration des serveurs MCP (Model Context Protocol). // -// ajean peut se connecter à des serveurs MCP tiers pour enrichir la palette +// loki peut se connecter à des serveurs MCP tiers pour enrichir la palette // d'outils de l'IA (accès fichiers rapide type desktop-commander, bases de // données, APIs métier…). Deux transports : // -// - stdio : ajean lance un process local (command + args) et parle en +// - stdio : loki lance un process local (command + args) et parle en // JSON-RPC sur stdin/stdout. C'est le cas de desktop-commander, du serveur // filesystem officiel, etc. — lancés via npx/uvx/un binaire. -// - http : ajean parle à un serveur MCP distant en Streamable HTTP (url + +// - http : loki parle à un serveur MCP distant en Streamable HTTP (url + // éventuels en-têtes d'auth). // // Le format du fichier mcp.json reprend celui de Claude Desktop (clé @@ -25,7 +25,7 @@ import ( // leurs configs existantes. On ajoute un champ "enabled" par serveur. // // IMPORTANT (sécurité) : un serveur MCP stdio exécute un process arbitraire sur -// la machine où tourne ajean — même niveau de confiance que l'outil `bash` du +// la machine où tourne loki — même niveau de confiance que l'outil `bash` du // mode agent. La configuration MCP est donc réservée au propriétaire local de la // machine et ne doit JAMAIS être pilotable depuis le relais/accès distant. diff --git a/internal/ajean/migrate_07.go b/internal/loki/migrate_07.go similarity index 99% rename from internal/ajean/migrate_07.go rename to internal/loki/migrate_07.go index b4d28ab..48c6dc7 100644 --- a/internal/ajean/migrate_07.go +++ b/internal/loki/migrate_07.go @@ -1,8 +1,8 @@ -package ajean +package loki // migrate_07.go — CODE TEMPORAIRE, à supprimer d'ici une version ou deux. // -// Reprend une installation 0.7.x au moment de `ajean install`. Tout est ici, et +// Reprend une installation 0.7.x au moment de `loki install`. Tout est ici, et // rien d'autre n'en dépend : le jour où le parc aura basculé, supprimer ce // fichier et les deux appels à migrateFrom07() (sys_install_unix.go et // sys_install_windows.go) suffit. C'est la seule raison d'être de ce fichier — diff --git a/internal/ajean/migrate_07_test.go b/internal/loki/migrate_07_test.go similarity index 98% rename from internal/ajean/migrate_07_test.go rename to internal/loki/migrate_07_test.go index c05d13f..1b6626a 100644 --- a/internal/ajean/migrate_07_test.go +++ b/internal/loki/migrate_07_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -38,7 +38,7 @@ func fake07Home(t *testing.T) string { write(".agent_enabled", "") write("webprefs.json", `{"theme":"dark"}`) write("mcp.json", `{"mcpServers":{"fs":{"command":"npx","enabled":true}}}`) - write("sysprompt.txt", "Tu es AJEAN.\n") + write("sysprompt.txt", "Tu es Loki.\n") write("conversation.json", `{"seq":42,"messages":[{"role":"user","content":"salut"}]}`) write("SKILLS/vieux-skill/SKILL.md", "# un skill d'avant\n") write(".authorized_users", "AABBCCDDEEFF00112233445566778899AABBCCDDEEFF001122334455667788AA\n") @@ -90,7 +90,7 @@ func TestMigration07(t *testing.T) { if !agentEnabled() { t.Error("mode agent perdu") } - if readSysPrompt() != "Tu es AJEAN." { + if readSysPrompt() != "Tu es Loki." { t.Errorf("prompt système = %q", readSysPrompt()) } if loadWebPrefs()["theme"] != "dark" { diff --git a/internal/ajean/node_api.go b/internal/loki/node_api.go similarity index 99% rename from internal/ajean/node_api.go rename to internal/loki/node_api.go index 82c929b..80e7230 100644 --- a/internal/ajean/node_api.go +++ b/internal/loki/node_api.go @@ -6,7 +6,7 @@ // - ENRÔLEMENT (public) : le poste échange un code d'appairage à usage unique // contre sa clé d'appareil. Authentifié par le code, pas par la clé de // pilotage — que le poste ne possède pas. -package ajean +package loki import ( "encoding/base64" diff --git a/internal/ajean/node_proto.go b/internal/loki/node_proto.go similarity index 88% rename from internal/ajean/node_proto.go rename to internal/loki/node_proto.go index dc4a4f9..b3dcc79 100644 --- a/internal/ajean/node_proto.go +++ b/internal/loki/node_proto.go @@ -1,16 +1,16 @@ // node_proto.go — côté serveur, alias vers le protocole partagé (internal/nodewire) -// + les helpers de nommage d'outils propres à ajean. +// + les helpers de nommage d'outils propres à loki. // // Le format du fil (Msg, capacités, confinement de chemin) vit dans nodewire, -// paquet SANS dépendance, pour que le client léger ajean-node le partage sans -// tirer tout ajean. Ici on ne fait qu'exposer ces symboles sous les noms +// paquet SANS dépendance, pour que le client léger loki-node le partage sans +// tirer tout loki. Ici on ne fait qu'exposer ces symboles sous les noms // historiques du paquet, pour ne pas toucher le reste du code serveur. -package ajean +package loki import ( "strings" - "github.com/nathaninline/ajean/internal/nodewire" + "github.com/R0m1k3/Loki/internal/nodewire" ) // Capacités (alias des constantes partagées). diff --git a/internal/ajean/node_proto_test.go b/internal/loki/node_proto_test.go similarity index 99% rename from internal/ajean/node_proto_test.go rename to internal/loki/node_proto_test.go index a5b861d..1e34dfe 100644 --- a/internal/ajean/node_proto_test.go +++ b/internal/loki/node_proto_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "path/filepath" diff --git a/internal/ajean/node_server.go b/internal/loki/node_server.go similarity index 99% rename from internal/ajean/node_server.go rename to internal/loki/node_server.go index 1d3d157..d75f56f 100644 --- a/internal/ajean/node_server.go +++ b/internal/loki/node_server.go @@ -2,7 +2,7 @@ // WebSocket, registre des postes connectés, et exposition de leurs outils à // l'agent. Le serveur n'exécute JAMAIS rien lui-même ici : il route une demande // d'outil vers le poste concerné et attend sa réponse. -package ajean +package loki import ( "context" @@ -18,7 +18,7 @@ import ( "github.com/coder/websocket" "github.com/coder/websocket/wsjson" - "github.com/nathaninline/ajean/internal/nodewire" + "github.com/R0m1k3/Loki/internal/nodewire" ) const ( diff --git a/internal/ajean/node_ws_test.go b/internal/loki/node_ws_test.go similarity index 98% rename from internal/ajean/node_ws_test.go rename to internal/loki/node_ws_test.go index b9521f5..53311dd 100644 --- a/internal/ajean/node_ws_test.go +++ b/internal/loki/node_ws_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "context" @@ -12,7 +12,7 @@ import ( "github.com/coder/websocket" "github.com/coder/websocket/wsjson" - "github.com/nathaninline/ajean/internal/nodewire" + "github.com/R0m1k3/Loki/internal/nodewire" ) // TestNodeE2E exerce tout le chemin : enrôlement SCELLÉ (le sceau du client est diff --git a/internal/ajean/relay_e2e.go b/internal/loki/relay_e2e.go similarity index 97% rename from internal/ajean/relay_e2e.go rename to internal/loki/relay_e2e.go index 33aecdb..96ae0de 100644 --- a/internal/ajean/relay_e2e.go +++ b/internal/loki/relay_e2e.go @@ -1,11 +1,11 @@ -package ajean +package loki // 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 ». // // Modèle : cet agent a une paire X25519 long-terme (clé privée locale, jamais // transmise). Sa clé publique est publiée au relais et son EMPREINTE est affichée -// au `ajean link` — l'utilisateur la confirme dans le portail, ce qui défait tout +// au `loki link` — l'utilisateur la confirme dans le portail, ce qui défait tout // MITM du relais. Le navigateur dérive une racine R de son mot de passe (exportKey // OPAQUE), la SCELLE vers la clé publique de l'agent (le relais ne peut pas // l'ouvrir), puis chiffre/déchiffre le chat avec une clé dérivée de R. @@ -32,7 +32,7 @@ import ( "sync" ) -func e2eKeyPath() string { return filepath.Join(AjeanHome(), ".e2e_key") } +func e2eKeyPath() string { return filepath.Join(LokiHome(), ".e2e_key") } var ( e2eOnce sync.Once @@ -55,7 +55,7 @@ func e2ePrivateKey() (*ecdh.PrivateKey, error) { e2eErr = err return } - _ = os.MkdirAll(AjeanHome(), 0o755) + _ = os.MkdirAll(LokiHome(), 0o755) if err := os.WriteFile(e2eKeyPath(), []byte(hex.EncodeToString(k.Bytes())+"\n"), 0o600); err != nil { e2eErr = err return diff --git a/internal/ajean/relay_e2eauth.go b/internal/loki/relay_e2eauth.go similarity index 96% rename from internal/ajean/relay_e2eauth.go rename to internal/loki/relay_e2eauth.go index d56a460..7bf2aae 100644 --- a/internal/ajean/relay_e2eauth.go +++ b/internal/loki/relay_e2eauth.go @@ -1,4 +1,4 @@ -package ajean +package loki // relay_e2eauth.go — authentification MUTUELLE + anti-rejeu du canal chiffré. // @@ -9,7 +9,7 @@ package ajean // Modèle : l'utilisateur a une identité X25519 dérivée de façon déterministe de son // mot de passe (exportKey OPAQUE → racine R → uPriv) — le relais ne connaît pas R, // donc ne peut PAS reproduire cette identité. La clé publique uPub est APPAIRÉE une -// fois à l'agent via un code affiché par « ajean link » (canal hors-bande : le log du +// fois à l'agent via un code affiché par « loki link » (canal hors-bande : le log du // serveur, que le relais ne voit pas ; le code voyage scellé vers l'agent). Ensuite, // chaque requête est chiffrée avec K = SHA256(ECDH(uPriv, agentPriv) || "authchan"), // liée à uPub+horodatage (AAD) et protégée contre le rejeu. Seul le vrai utilisateur @@ -92,7 +92,7 @@ func authorizeUser(uPubHex string) error { // ---- Codes d'appairage : à la demande, usage unique, TTL 10 min -------------- // -// Générés par « ajean link code » (ou affichés par « ajean link »), ils sont +// Générés par « loki link code » (ou affichés par « loki link »), ils sont // partagés avec le service d'interface (autre process) via la base — sinon les // deux ne s'accorderaient pas. Stockés HACHÉS (SHA-256) : la base ne révèle // aucun code. Chaque code expire au bout de 10 min et est consommé (retiré) au @@ -232,7 +232,7 @@ func replayCheck(uPub string, ts int64, iv string) bool { // ---- Dérivation de clé + ouverture authentifiée ---------------------------- // e2eAuthKey dérive la clé du canal authentifié pour l'utilisateur uPub : -// SHA256( ECDH(agentPriv, uPub) || "ajean-authchan-v1" ). Symétrique de la version +// SHA256( ECDH(agentPriv, uPub) || "loki-authchan-v1" ). Symétrique de la version // navigateur (WASM) qui calcule ECDH(uPriv, agentPub) = même secret partagé. func e2eAuthKey(uPubHex string) ([]byte, error) { priv, err := e2ePrivateKey() @@ -253,7 +253,7 @@ func e2eAuthKey(uPubHex string) ([]byte, error) { } h := sha256.New() h.Write(ss) - h.Write([]byte("ajean-authchan-v1")) + h.Write([]byte("loki-authchan-v1")) return h.Sum(nil), nil } diff --git a/internal/ajean/relay_e2eauth_test.go b/internal/loki/relay_e2eauth_test.go similarity index 99% rename from internal/ajean/relay_e2eauth_test.go rename to internal/loki/relay_e2eauth_test.go index a75accf..16c1d34 100644 --- a/internal/ajean/relay_e2eauth_test.go +++ b/internal/loki/relay_e2eauth_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "strings" diff --git a/internal/ajean/relay_link.go b/internal/loki/relay_link.go similarity index 89% rename from internal/ajean/relay_link.go rename to internal/loki/relay_link.go index 0fb8783..d336cd7 100644 --- a/internal/ajean/relay_link.go +++ b/internal/loki/relay_link.go @@ -1,16 +1,16 @@ -package ajean +package loki -// relay_link.go — `ajean link ` : connecte ce serveur AJEAN au relais public +// relay_link.go — `loki link ` : connecte ce serveur Loki au relais public // (ajean.link) par une connexion SORTANTE persistante, pour qu'un utilisateur // y accède depuis n'importe où sans ouvrir de port (CGNAT, box, etc.). // // Principe : l'agent ouvre un WebSocket vers le relais, l'authentifie avec le // token d'abonnement, puis multiplexe (yamux) ce lien unique en un stream par -// requête navigateur. Chaque stream est reverse-proxyfié vers le `ajean web` +// requête navigateur. Chaque stream est reverse-proxyfié vers le `loki web` // local. Keepalive + reconnexion automatique avec backoff. // // Le token est fourni par la boutique à l'achat. Il est mémorisé dans -// $AJEAN_HOME/.link_token pour que `ajean link` (sans argument) reprenne la +// $LOKI_HOME/.link_token pour que `loki link` (sans argument) reprenne la // connexion. import ( @@ -39,7 +39,7 @@ import ( ) // defaultRelayURL est l'endpoint WebSocket du relais. Surchageable via -// $AJEAN_LINK_URL (utile pour tester contre un relais local). +// $LOKI_LINK_URL (utile pour tester contre un relais local). const defaultRelayURL = "wss://ajean.link/agent" // machineID renvoie un identifiant stable de la machine, créé au premier appel. @@ -65,20 +65,20 @@ func removeLinkToken() error { return putStr(bkState, "link_token", "") } // relayURL resolves the relay WebSocket endpoint (env override → default). func relayURL() string { - if u := os.Getenv("AJEAN_LINK_URL"); u != "" { + if u := os.Getenv("LOKI_LINK_URL"); u != "" { return u } return defaultRelayURL } -// uiUnitName est l'unité qui exécute « ajean web » : l'UI locale, le tunnel du +// uiUnitName est l'unité qui exécute « loki web » : l'UI locale, le tunnel du // relais et l'endpoint OpenAI, dans un seul et même process. -const uiUnitName = "ajean-ui" +const uiUnitName = "loki-ui" func uiServiceName() string { return uiUnitName } // cmdLink ne gère QUE le compte d'accès distant : le jeton, l'appairage, l'état. -// Le tunnel n'est plus un service à part — il est ouvert par « ajean web » dès +// Le tunnel n'est plus un service à part — il est ouvert par « loki web » dès // qu'un jeton est enregistré. Piloter le tunnel, c'est donc piloter le service // d'interface (voir cmdUI). func cmdLink(args []string) error { @@ -88,33 +88,33 @@ func cmdLink(args []string) error { } switch sub { case "": - // `ajean link` seul : afficher l'aide (NE démarre rien — éviter de prendre + // `loki link` seul : afficher l'aide (NE démarre rien — éviter de prendre // un mot pour un jeton et d'écraser le vrai). printLinkHelp() return nil case "status": tok := readLinkToken() if tok == "" { - fmt.Println(yellow("[info]") + " aucun jeton enregistré — lance: ajean link ") + fmt.Println(yellow("[info]") + " aucun jeton enregistré — lance: loki link ") return nil } fmt.Printf("%s jeton enregistré (%s…), relais: %s\n", green("[ok]"), tok[:min(8, len(tok))], relayURL()) if uiServiceActive() { fmt.Printf("%s service %s actif — le tunnel est ouvert\n", green("[ok]"), uiServiceName()) } else { - fmt.Printf("%s service %s arrêté — pas de tunnel (ajean ui start)\n", yellow("[info]"), uiServiceName()) + fmt.Printf("%s service %s arrêté — pas de tunnel (loki ui start)\n", yellow("[info]"), uiServiceName()) } return nil case "logout": if err := removeLinkToken(); err != nil { return err } - fmt.Println(green("[ok]") + " jeton supprimé — « ajean ui restart » pour fermer le tunnel") + fmt.Println(green("[ok]") + " jeton supprimé — « loki ui restart » pour fermer le tunnel") return nil case "code": code, err := newPairCode() if err != nil { - return fmt.Errorf("génération du code (droits sur %s ?): %w", AjeanHome(), err) + return fmt.Errorf("génération du code (droits sur %s ?): %w", LokiHome(), err) } fmt.Printf("%s code d'appairage (valable 10 min, à usage unique) :\n %s\n", green("[link]"), bold(code)) return nil @@ -138,7 +138,7 @@ func cmdLink(args []string) error { return linkPrintIdentity() } -// cmdUI pilote le service d'interface — « ajean web » en arrière-plan : l'UI +// cmdUI pilote le service d'interface — « loki web » en arrière-plan : l'UI // locale, le tunnel du relais et l'endpoint OpenAI, servis par le MÊME process. // C'est ce qui garantit une conversation unique, identique en local et à distance. func cmdUI(args []string) error { @@ -160,22 +160,22 @@ func cmdUI(args []string) error { } return nil default: - return fmt.Errorf("usage: ajean ui [start|stop|restart|status]") + return fmt.Errorf("usage: loki ui [start|stop|restart|status]") } } -// printLinkHelp liste les sous-commandes de `ajean link`. +// printLinkHelp liste les sous-commandes de `loki link`. func printLinkHelp() { - fmt.Print(`ajean link — accès distant via le relais ajean.link + fmt.Print(`loki link — accès distant via le relais ajean.link Usage : - ajean link enregistre le jeton (1re fois / pour le changer) et ouvre le tunnel - ajean link status état du jeton et du tunnel - ajean link code génère un code d'appairage (valable 10 min, à usage unique) - ajean link logout oublie le jeton enregistré + loki link enregistre le jeton (1re fois / pour le changer) et ouvre le tunnel + loki link status état du jeton et du tunnel + loki link code génère un code d'appairage (valable 10 min, à usage unique) + loki link logout oublie le jeton enregistré Le jeton est fourni sur ajean.link. Le tunnel est ouvert par le service -d'interface (« ajean ui ») dès qu'un jeton est enregistré : il n'y a pas de +d'interface (« loki ui ») dès qu'un jeton est enregistré : il n'y a pas de service séparé pour l'accès distant. `) } @@ -189,8 +189,8 @@ func linkPrintIdentity() error { } code, err := newPairCode() if err != nil { - fmt.Printf("%s code d'appairage indisponible (droits sur %s ?): %v\n", yellow("[link]"), AjeanHome(), err) - fmt.Printf(" Réessaie : sudo ajean link code\n") + fmt.Printf("%s code d'appairage indisponible (droits sur %s ?): %v\n", yellow("[link]"), LokiHome(), err) + fmt.Printf(" Réessaie : sudo loki link code\n") return nil } fmt.Printf(" Code d'appairage (valable 10 min, usage unique) : %s\n\n", bold(code)) @@ -208,7 +208,7 @@ func linkPrintIdentity() error { // // Sur un poste de bureau, l'app EST ce process propriétaire : elle sert déjà // :8090, donc elle fait aussi tourner le tunnel elle-même au lieu de déléguer à -// un worker détaché. L'accès distant vit donc aussi longtemps que AJEAN.app est +// un worker détaché. L'accès distant vit donc aussi longtemps que Loki.app est // ouverte — sur un portable qui s'endort, c'est de toute façon la réalité. var appLink struct { @@ -310,14 +310,14 @@ func killForeignUIWorker() { } // uiPIDPath / uiLogPath : suivi du worker de lien hors systemd (macOS, -// Windows), où AJEAN est une app de bureau lancée sans droits root. -func uiPIDPath() string { return filepath.Join(AjeanHome(), uiUnitName+".pid") } -func uiLogPath() string { return filepath.Join(AjeanHome(), uiUnitName+".log") } +// Windows), où Loki est une app de bureau lancée sans droits root. +func uiPIDPath() string { return filepath.Join(LokiHome(), uiUnitName+".pid") } +func uiLogPath() string { return filepath.Join(LokiHome(), uiUnitName+".log") } // uiServiceCtl pilote le worker de lien (start/stop/restart). Sous Linux c'est -// l'unité systemd ajean-ui (avec sudo non interactif si on n'est pas root) ; +// l'unité systemd loki-ui (avec sudo non interactif si on n'est pas root) ; // ailleurs — macOS et Windows, où il n'y a ni systemd ni droits root — on lance -// « ajean link serve » en processus détaché suivi par un fichier PID, comme le +// « loki link serve » en processus détaché suivi par un fichier PID, comme le // fait déjà le service principal. Sans ça, l'accès distant restait définitivement // « arrêté » sur un Mac ou un PC : le token était enregistré mais rien ne // composait jamais le tunnel. @@ -422,7 +422,7 @@ func uiUserSvcCtl(action string) error { if p, err := filepath.EvalSymlinks(self); err == nil { self = p } - if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { + if err := os.MkdirAll(LokiHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(uiLogPath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -434,7 +434,7 @@ func uiUserSvcCtl(action string) error { cmd := spawnDetached(self, "web") cmd.Stdout, cmd.Stderr = logf, logf if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de « ajean web »: %w", err) + return fmt.Errorf("démarrage de « loki web »: %w", err) } pid := cmd.Process.Pid // PID + binaire d'origine : la 2e ligne dit QUELLE copie de l'app a lancé ce @@ -460,7 +460,7 @@ func uiUserSvcCtl(action string) error { // - /v1/*, /health, /props, /metrics, /slots → llama-server local (endpoint // compatible OpenAI, avec injection de la clé API locale) → permet de // brancher OpenCode, Hermes, etc. sur ajean.link/oai//v1 -// - tout le reste → l'UI web de AJEAN (avec injection de la clé de pilotage) +// - tout le reste → l'UI web de Loki (avec injection de la clé de pilotage) func newLinkHandler(mux *http.ServeMux) http.Handler { web := withLocalAuth(mux) llama := &url.URL{Scheme: "http", Host: fmt.Sprintf("127.0.0.1:%d", LLMPort())} @@ -484,7 +484,7 @@ func newLinkHandler(mux *http.ServeMux) http.Handler { // - /api/e2e/chat : chat chiffré (streaming SSE chiffré). // - /api/e2e/req : proxy de contrôle chiffré (presets, VRAM, skills, service…). // Tout autre /api/* en clair est REFUSÉ via le tunnel. - oaiAllowed := os.Getenv("AJEAN_LINK_ALLOW_OAI") == "1" + oaiAllowed := os.Getenv("LOKI_LINK_ALLOW_OAI") == "1" return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := r.URL.Path if strings.HasPrefix(p, "/api/e2e/") { @@ -514,7 +514,7 @@ func newLinkHandler(mux *http.ServeMux) http.Handler { // L'endpoint OpenAI (OpenCode/Hermes) ne peut pas être chiffré navigateur : // il transiterait en clair par le relais. Désactivé par défaut. if !oaiAllowed { - http.Error(w, "endpoint OpenAI désactivé via le relais (transiterait en clair) ; AJEAN_LINK_ALLOW_OAI=1 pour l'autoriser", http.StatusForbidden) + http.Error(w, "endpoint OpenAI désactivé via le relais (transiterait en clair) ; LOKI_LINK_ALLOW_OAI=1 pour l'autoriser", http.StatusForbidden) return } lp.ServeHTTP(w, r) @@ -531,7 +531,7 @@ func newLinkHandler(mux *http.ServeMux) http.Handler { // // ⚠️ La clé est relue À CHAQUE REQUÊTE, et surtout pas capturée une fois pour // toutes à l'ouverture du tunnel. Avec une capture, changer la clé (depuis -// l'interface ou `ajean set-web-key`) faisait injecter l'ANCIENNE jusqu'au +// l'interface ou `loki set-web-key`) faisait injecter l'ANCIENNE jusqu'au // prochain redémarrage du service : tout l'accès distant tombait en 401, et // comme le chat n'affiche pas le code HTTP d'un flux qui n'arrive jamais, le // symptôme était un « chargement de la conversation » infini, sans la moindre @@ -599,7 +599,7 @@ func runLinkSession(ctx context.Context, token string, handler http.Handler, oai fmt.Printf("%s lien établi ✓\n", green("[link]")) // Chaque Accept() = un stream. Deux natures possibles : - // - requête HTTP normale (UI AJEAN / E2E) → servie par `handler` ; + // - requête HTTP normale (UI Loki / E2E) → servie par `handler` ; // - session TLS brute (accès OpenAI public) → terminée par le front TLS. // On les distingue au 1er octet (0x16 = handshake TLS). Sans OAI activé, tout // va au HTTP (comportement historique inchangé). diff --git a/internal/ajean/relay_localauth_test.go b/internal/loki/relay_localauth_test.go similarity index 95% rename from internal/ajean/relay_localauth_test.go rename to internal/loki/relay_localauth_test.go index 7cdad30..1c06dd3 100644 --- a/internal/ajean/relay_localauth_test.go +++ b/internal/loki/relay_localauth_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "net/http" @@ -8,7 +8,7 @@ import ( // Changer la clé de pilotage ne doit PAS couper l'accès distant jusqu'au // prochain redémarrage. La clé était capturée à l'ouverture du tunnel : après un -// `ajean set-web-key`, le tunnel injectait l'ancienne, tout passait en 401, et +// `loki set-web-key`, le tunnel injectait l'ancienne, tout passait en 401, et // le portail restait bloqué sur « chargement de la conversation » sans jamais // dire pourquoi. func TestWithLocalAuthSuitLaCleCourante(t *testing.T) { diff --git a/internal/ajean/remote.go b/internal/loki/remote.go similarity index 77% rename from internal/ajean/remote.go rename to internal/loki/remote.go index 9326162..d482e46 100644 --- a/internal/ajean/remote.go +++ b/internal/loki/remote.go @@ -1,17 +1,17 @@ -// Sous-commande « ajean remote » — client « poste distant » intégré au binaire -// principal. On l'installe sur un PC pour que l'IA d'un serveur AJEAN puisse agir +// Sous-commande « loki remote » — client « poste distant » intégré au binaire +// principal. On l'installe sur un PC pour que l'IA d'un serveur Loki puisse agir // dessus (shell/fichiers), chiffré de bout en bout via ajean.link (relais aveugle) // ou en direct sur le réseau local. // -// ajean remote install --machine ID --key CLÉ --code CODE [--allow shell,read,write,list] [--root DIR] +// loki remote install --machine ID --key CLÉ --code CODE [--allow shell,read,write,list] [--root DIR] // appaire ce PC et l'installe en service (démarrage auto) -// ajean remote connect ... idem mais en avant-plan (test) -// ajean remote run boucle client (utilisé par le service) -// ajean remote status | uninstall | logout +// loki remote connect ... idem mais en avant-plan (test) +// loki remote run boucle client (utilisé par le service) +// loki remote status | uninstall | logout // -// Historiquement un binaire séparé « ajean-remote » ; fusionné dans ce binaire +// Historiquement un binaire séparé « loki-remote » ; fusionné dans ce binaire // pour n'avoir qu'une seule application. -package ajean +package loki import ( "context" @@ -20,7 +20,7 @@ import ( "runtime" "strings" - "github.com/nathaninline/ajean/internal/nodeclient" + "github.com/R0m1k3/Loki/internal/nodeclient" ) func cmdRemote(args []string) error { @@ -59,18 +59,18 @@ func cmdRemote(args []string) error { } func remoteUsage() { - fmt.Print(`ajean remote — piloter CE PC depuis l'IA d'un serveur AJEAN + fmt.Print(`loki remote — piloter CE PC depuis l'IA d'un serveur Loki - ajean remote install --code CODE [options] appaire + installe en service (démarrage auto, invisible) - ajean remote connect --code CODE [options] idem en avant-plan (pour tester) - ajean remote status état de la configuration - ajean remote uninstall retire le service (garde la config) - ajean remote logout retire le service ET oublie la clé + loki remote install --code CODE [options] appaire + installe en service (démarrage auto, invisible) + loki remote connect --code CODE [options] idem en avant-plan (pour tester) + loki remote status état de la configuration + loki remote uninstall retire le service (garde la config) + loki remote logout retire le service ET oublie la clé Options : --machine ID machine à joindre via ajean.link (fournie par l'UI → Postes distants) --key CLÉ clé publique de l'agent (le poste scelle vers elle) - --code CODE code d'appairage (10 min, généré dans l'UI AJEAN → Postes distants) + --code CODE code d'appairage (10 min, généré dans l'UI Loki → Postes distants) --allow LISTE capacités acceptées : shell,read,write,list (défaut : ce que le serveur autorise) --root DIR dossier autorisé (confine lecture/écriture) --yes auto-approuve les actions (obligatoire pour le service, sans terminal) @@ -84,8 +84,8 @@ func remoteConnect(args []string, install bool) error { cfg, lerr := nodeclient.LoadConfig() // Un --code est un jeton d'appairage à usage unique : le fournir veut TOUJOURS // dire « (ré)appaire-moi maintenant ». Sans ce « || f.code != "" », une config - // résiduelle (test précédent, ancien binaire ajean-remote au même emplacement - // %ProgramData%\ajean-remote\config.json) faisait sauter l'appairage en silence : + // résiduelle (test précédent, ancien binaire loki-remote au même emplacement + // %ProgramData%\loki-remote\config.json) faisait sauter l'appairage en silence : // le service démarrait avec de vieilles clés que le serveur ignore et le poste // ne remontait jamais, alors que tout affichait « [ok] ». needEnroll := lerr != nil || cfg.Priv == "" || f.code != "" @@ -148,7 +148,7 @@ func remoteConnect(args []string, install bool) error { func remoteRun() error { cfg, err := nodeclient.LoadConfig() if err != nil { - return fmt.Errorf("aucune configuration — lance d'abord « ajean remote install … »") + return fmt.Errorf("aucune configuration — lance d'abord « loki remote install … »") } return nodeclient.RunService(cfg) } diff --git a/internal/ajean/run.go b/internal/loki/run.go similarity index 80% rename from internal/ajean/run.go rename to internal/loki/run.go index 95058ff..e576889 100644 --- a/internal/ajean/run.go +++ b/internal/loki/run.go @@ -1,7 +1,7 @@ -// ajean — single-binary LLM server manager + web UI for llama.cpp deployments. -// Point d'entrée réel : Main(), appelé par cmd/ajean (les métadonnées Windows +// loki — single-binary LLM server manager + web UI for llama.cpp deployments. +// Point d'entrée réel : Main(), appelé par cmd/loki (les métadonnées Windows // .syso/go:generate vivent là-bas, dans le dossier du package main). -package ajean +package loki import ( "fmt" @@ -12,7 +12,7 @@ import ( const Version = "0.9.4" -// Main est le vrai main() du binaire (cmd/ajean ne fait que l'appeler). +// Main est le vrai main() du binaire (cmd/loki ne fait que l'appeler). func Main() { // Rattache la console du terminal parent si on est lancé depuis un shell // (Windows : binaire GUI). Retourne false au double-clic (aucune console) → @@ -34,7 +34,7 @@ func Main() { // lance l'expérience « application » (UI web + navigateur + icône tray) plutôt // que d'afficher l'aide. Le binaire étant compilé en sous-système GUI, il n'y a // AUCUNE console à ce stade (donc plus de fenêtre noire). Lancé depuis un shell, - // `ajean` sans argument garde son comportement d'aide. + // `loki` sans argument garde son comportement d'aide. if noArgs && !haveConsole { mustExit(cmdApp(args)) return @@ -107,7 +107,7 @@ func Main() { case "uninstall": mustExit(cmdUninstall(args)) case "version", "-v", "--version": - fmt.Println("ajean", Version) + fmt.Println("loki", Version) case "help", "-h", "--help", "": printHelp() default: @@ -118,14 +118,14 @@ func Main() { } func printHelp() { - fmt.Printf(`ajean %s — manager llama.cpp + UI web (single binary) + fmt.Printf(`loki %s — manager llama.cpp + UI web (single binary) -Usage: ajean [args] +Usage: loki [args] -AJEAN tourne en deux services : ajean-engine (le modèle) et ajean-ui +Loki tourne en deux services : loki-engine (le modèle) et loki-ui (l'interface web, le tunnel d'accès distant et l'endpoint OpenAI). -Moteur (ajean-engine) : +Moteur (loki-engine) : start | stop | restart gérer le service status | logs état / logs en direct enable | disable auto-démarrage au boot @@ -138,7 +138,7 @@ Moteur (ajean-engine) : network [on|off|status] rendre l'endpoint OpenAI joignable depuis le réseau local (HOST + règle de pare-feu sous Windows) -Interface (ajean-ui) : +Interface (loki-ui) : ui [start|stop|restart|status] pilote le service d'interface web [PORT] sert l'interface au premier plan (défaut :8090) — ouvre aussi le tunnel si un jeton est enregistré @@ -160,7 +160,7 @@ Accès distant (ajean.link) : link code code d'appairage (10 min, usage unique) pour le portail link status | logout état du jeton / l'oublier -Poste distant (piloter CE PC depuis l'IA d'un serveur AJEAN) : +Poste distant (piloter CE PC depuis l'IA d'un serveur Loki) : remote install --machine ID --key CLÉ --code CODE [--allow shell,read,write,list] appaire ce PC + installe le service (démarrage auto) remote connect … idem en avant-plan (pour tester) @@ -179,8 +179,8 @@ Installation: serve entrypoint du service : exec llama-server (usage interne) Env: - AJEAN_HOME racine des données (défaut : /etc/ajean, %%ProgramData%%\ajean sous Windows) - EDITOR éditeur pour 'ajean edit' (défaut : nano, notepad sous Windows) + LOKI_HOME racine des données (défaut : /etc/loki, %%ProgramData%%\loki sous Windows) + EDITOR éditeur pour 'loki edit' (défaut : nano, notepad sous Windows) `, Version) } @@ -191,21 +191,21 @@ func mustExit(err error) { } } -// AjeanHome resolves the AJEAN data directory. -// Précédence : $AJEAN_HOME → /etc/default/ajean (unix) → défaut plateforme. -func AjeanHome() string { - if h := os.Getenv("AJEAN_HOME"); h != "" { +// LokiHome resolves the Loki data directory. +// Précédence : $LOKI_HOME → /etc/default/loki (unix) → défaut plateforme. +func LokiHome() string { + if h := os.Getenv("LOKI_HOME"); h != "" { return h } if h := readEtcDefault(); h != "" { return h } - return defaultAjeanHome() + return defaultLokiHome() } -// readEtcDefault parses /etc/default/ajean for AJEAN_HOME=. Quiet on errors. +// readEtcDefault parses /etc/default/loki for LOKI_HOME=. Quiet on errors. func readEtcDefault() string { - b, err := os.ReadFile("/etc/default/ajean") + b, err := os.ReadFile("/etc/default/loki") if err != nil { return "" } @@ -215,30 +215,30 @@ func readEtcDefault() string { continue } s = strings.TrimPrefix(s, "export ") - if k, v, ok := strings.Cut(s, "="); ok && strings.TrimSpace(k) == "AJEAN_HOME" { + if k, v, ok := strings.Cut(s, "="); ok && strings.TrimSpace(k) == "LOKI_HOME" { return strings.Trim(strings.TrimSpace(v), "\"'") } } return "" } -// Arborescence de $AJEAN_HOME. Elle tient en six dossiers, et rien d'autre : +// Arborescence de $LOKI_HOME. Elle tient en six dossiers, et rien d'autre : // tout le reste (config, préférences, conversation, clés, drapeaux) vit dans la -// base ajean.db — voir store.go. -func backendsDir() string { return filepath.Join(AjeanHome(), "backends") } -func binDir() string { return filepath.Join(AjeanHome(), "bin") } -func presetsDir() string { return filepath.Join(AjeanHome(), "presets") } -func memoryDir() string { return filepath.Join(AjeanHome(), "memory") } -func modelsDir() string { return filepath.Join(AjeanHome(), "models") } -func workspaceDir() string { return filepath.Join(AjeanHome(), "workspace") } +// base loki.db — voir store.go. +func backendsDir() string { return filepath.Join(LokiHome(), "backends") } +func binDir() string { return filepath.Join(LokiHome(), "bin") } +func presetsDir() string { return filepath.Join(LokiHome(), "presets") } +func memoryDir() string { return filepath.Join(LokiHome(), "memory") } +func modelsDir() string { return filepath.Join(LokiHome(), "models") } +func workspaceDir() string { return filepath.Join(LokiHome(), "workspace") } // serviceName est le nom de l'unité qui exécute llama-server. Son pendant est -// uiUnitName (« ajean-ui »), qui sert l'interface et le tunnel. +// uiUnitName (« loki-ui »), qui sert l'interface et le tunnel. func serviceName() string { - if n := os.Getenv("AJEAN_SERVICE"); n != "" { + if n := os.Getenv("LOKI_SERVICE"); n != "" { return n } - return "ajean-engine" + return "loki-engine" } // Color helpers (ANSI). Disabled when stdout is not a TTY. diff --git a/internal/ajean/store.go b/internal/loki/store.go similarity index 97% rename from internal/ajean/store.go rename to internal/loki/store.go index 9676291..4c6a91a 100644 --- a/internal/ajean/store.go +++ b/internal/loki/store.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -13,7 +13,7 @@ import ( bolterrors "go.etcd.io/bbolt/errors" ) -// store.go — l'unique endroit où AJEAN écrit son état. +// store.go — l'unique endroit où Loki écrit son état. // // Avant, chaque réglage avait son fichier : config.env, webprefs.json, // conversation.json, .api_key, .link_token, .agent_enabled, model_dirs.json… @@ -40,7 +40,7 @@ const ( // bbolt pose un verrou EXCLUSIF sur son fichier tant qu'il est ouvert. Or une // machine installée fait tourner en permanence le service de lien, qui sert le // tunnel et l'UI : s'il gardait la base ouverte, plus une seule commande ne -// fonctionnerait à côté — « ajean status », « ajean switch », « ajean edit » +// fonctionnerait à côté — « loki status », « loki switch », « loki edit » // échoueraient toutes sur un délai d'attente, sur la machine même où tout est // censé marcher. On ouvre donc pour la durée d'une opération, puis on referme. // @@ -49,7 +49,7 @@ const ( // contention entre process ; dbMu la sérialise à l'intérieur du process. var dbMu sync.Mutex -func dbPath() string { return filepath.Join(AjeanHome(), "ajean.db") } +func dbPath() string { return filepath.Join(LokiHome(), "loki.db") } // withDB ouvre la base, exécute fn, puis referme — toujours, même en erreur. func withDB(fn func(*bolt.DB) error) error { diff --git a/internal/ajean/store_cache_test.go b/internal/loki/store_cache_test.go similarity index 95% rename from internal/ajean/store_cache_test.go rename to internal/loki/store_cache_test.go index 0d2ee52..1bb80a0 100644 --- a/internal/ajean/store_cache_test.go +++ b/internal/loki/store_cache_test.go @@ -1,9 +1,9 @@ -package ajean +package loki import "testing" // Le cache de lecture ne doit jamais servir une valeur périmée après une -// écriture de ce process : un « ajean edit » suivi d'un « ajean restart » doit +// écriture de ce process : un « loki edit » suivi d'un « loki restart » doit // démarrer sur la NOUVELLE configuration. func TestCacheConfigInvalideParEcriture(t *testing.T) { testHome(t) diff --git a/internal/ajean/store_conc_test.go b/internal/loki/store_conc_test.go similarity index 98% rename from internal/ajean/store_conc_test.go rename to internal/loki/store_conc_test.go index 50eda6e..795bcf5 100644 --- a/internal/ajean/store_conc_test.go +++ b/internal/loki/store_conc_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "sync" diff --git a/internal/ajean/store_test.go b/internal/loki/store_test.go similarity index 93% rename from internal/ajean/store_test.go rename to internal/loki/store_test.go index 1ecd5d2..205b120 100644 --- a/internal/ajean/store_test.go +++ b/internal/loki/store_test.go @@ -1,14 +1,14 @@ -package ajean +package loki import "testing" -// testHome donne au test un $AJEAN_HOME à lui, et neutralise le pare-feu : un +// testHome donne au test un $LOKI_HOME à lui, et neutralise le pare-feu : un // test ne doit jamais poser (ni retirer) une règle entrante sur la machine qui // le fait tourner. func testHome(t *testing.T) string { t.Helper() home := t.TempDir() - t.Setenv("AJEAN_HOME", home) + t.Setenv("LOKI_HOME", home) firewallInert = true t.Cleanup(func() { firewallInert = false }) return home diff --git a/internal/ajean/sys_brand_icon.go b/internal/loki/sys_brand_icon.go similarity index 96% rename from internal/ajean/sys_brand_icon.go rename to internal/loki/sys_brand_icon.go index f38e81d..0f75171 100644 --- a/internal/ajean/sys_brand_icon.go +++ b/internal/loki/sys_brand_icon.go @@ -1,6 +1,6 @@ -package ajean +package loki -// sys_brand_icon.go — l'icône de la marque AJEAN, rendue à la volée. +// sys_brand_icon.go — l'icône de la marque Loki, rendue à la volée. // // RÉPLIQUE EXACTE du favicon de l'UI web : carré à coins arrondis NOIR + « j » // blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12). Une seule source pour @@ -9,7 +9,7 @@ package ajean // au noir en laissant les icônes système en bleu. // // Aucun asset binaire à committer : tout est dessiné ici, et l'icône du .exe est -// produite par `go generate ./cmd/ajean` (voir tools/gen-icon). +// produite par `go generate ./cmd/loki` (voir tools/gen-icon). import ( "bytes" diff --git a/internal/ajean/sys_console_darwin.go b/internal/loki/sys_console_darwin.go similarity index 78% rename from internal/ajean/sys_console_darwin.go rename to internal/loki/sys_console_darwin.go index f551ff5..45ea050 100644 --- a/internal/ajean/sys_console_darwin.go +++ b/internal/loki/sys_console_darwin.go @@ -1,16 +1,16 @@ //go:build darwin -package ajean +package loki // sys_console_darwin.go — détection du lancement « par clic » sous macOS. // // Sur macOS il n'y a pas d'équivalent d'AttachConsole : un binaire lancé depuis // le Finder hérite quand même de descripteurs stdout/stderr (vers les logs // système). Le signal fiable, c'est l'emplacement de l'exécutable : la release -// publie un bundle AJEAN.app dont le binaire vit dans -// AJEAN.app/Contents/MacOS/ajean. Si on tourne depuis là, c'est un double-clic +// publie un bundle Loki.app dont le binaire vit dans +// Loki.app/Contents/MacOS/loki. Si on tourne depuis là, c'est un double-clic // dans le Finder → expérience « application » (UI web + navigateur), exactement -// comme le double-clic sur ajean.exe sous Windows. +// comme le double-clic sur loki.exe sous Windows. // // Le même binaire copié dans /usr/local/bin garde évidemment le comportement CLI. @@ -28,7 +28,7 @@ func setupConsole() bool { if p, err := filepath.EvalSymlinks(exe); err == nil { exe = p } - // .../AJEAN.app/Contents/MacOS/ajean + // .../Loki.app/Contents/MacOS/loki if strings.HasSuffix(filepath.Dir(exe), "/Contents/MacOS") { fixFinderPath() return false @@ -36,7 +36,7 @@ func setupConsole() bool { return true } -// appWarning signale une situation de lancement qui casse AJEAN de façon peu +// appWarning signale une situation de lancement qui casse Loki de façon peu // évidente. Aujourd'hui : l'App Translocation de Gatekeeper. Une app non signée // ouverte depuis Téléchargements est exécutée depuis une copie en LECTURE SEULE // sous /private/var/folders/…/AppTranslocation//, à un chemin différent à @@ -44,7 +44,7 @@ func setupConsole() bool { // dessus (la seconde trouve le port 8090 occupé et se contente d'ouvrir l'UI de // la première, souvent une version périmée), des process fantômes issus de // copies précédentes, et une mise à jour en place impossible. -// Le remède est côté utilisateur : déplacer AJEAN.app dans /Applications. +// Le remède est côté utilisateur : déplacer Loki.app dans /Applications. func appWarning() string { exe, err := os.Executable() if err != nil { @@ -53,14 +53,14 @@ func appWarning() string { if !strings.Contains(exe, "/AppTranslocation/") { return "" } - return "AJEAN tourne depuis une copie temporaire en lecture seule (App Translocation de macOS). " + - "Fermez l'app, déplacez AJEAN.app dans le dossier Applications, puis rouvrez-la — " + + return "Loki tourne depuis une copie temporaire en lecture seule (App Translocation de macOS). " + + "Fermez l'app, déplacez Loki.app dans le dossier Applications, puis rouvrez-la — " + "sinon chaque lancement crée une instance séparée et les mises à jour du moteur échouent." } // fixFinderPath complète le PATH famélique hérité du Finder. Une app lancée par // LaunchServices reçoit /usr/bin:/bin:/usr/sbin:/sbin — pas les dossiers de -// Homebrew : sans ça, AJEAN ne voit ni `brew`, ni `cmake`, ni `git`, et la +// Homebrew : sans ça, Loki ne voit ni `brew`, ni `cmake`, ni `git`, et la // compilation d'un backend échoue alors que les outils sont bien installés. func fixFinderPath() { extra := []string{ diff --git a/internal/ajean/sys_console_other.go b/internal/loki/sys_console_other.go similarity index 76% rename from internal/ajean/sys_console_other.go rename to internal/loki/sys_console_other.go index 1cb9988..fcd9b80 100644 --- a/internal/ajean/sys_console_other.go +++ b/internal/loki/sys_console_other.go @@ -1,12 +1,12 @@ //go:build !windows && !darwin -package ajean +package loki // setupConsole : sous Linux, le process a toujours une vraie console/tty // standard (stdout hérité). Rien à faire → on signale simplement « on a une // console ». Les cas « lancé par un clic » sont traités par -// sys_console_windows.go (double-clic sur ajean.exe) et sys_console_darwin.go -// (ouverture de AJEAN.app depuis le Finder). +// sys_console_windows.go (double-clic sur loki.exe) et sys_console_darwin.go +// (ouverture de Loki.app depuis le Finder). func setupConsole() bool { return true } // appWarning : rien à signaler hors macOS (l'App Translocation est spécifique à diff --git a/internal/ajean/sys_console_windows.go b/internal/loki/sys_console_windows.go similarity index 92% rename from internal/ajean/sys_console_windows.go rename to internal/loki/sys_console_windows.go index 8921f7f..2339c44 100644 --- a/internal/ajean/sys_console_windows.go +++ b/internal/loki/sys_console_windows.go @@ -1,19 +1,19 @@ //go:build windows -package ajean +package loki // sys_console_windows.go — gestion de la console sous Windows. // // Le binaire est compilé en sous-système CONSOLE. C'est ce qui fait que `cmd` // ATTEND la fin du programme : avec un binaire graphique, il rendait la main -// aussitôt et réaffichait son invite AVANT la sortie d'AJEAN, ce qui donnait +// aussitôt et réaffichait son invite AVANT la sortie d'Loki, ce qui donnait // -// C:\Users\Nathan>ajean +// C:\Users\Nathan>loki // -// C:\Users\Nathan>ajean 0.8.0 — manager llama.cpp + UI web +// C:\Users\Nathan>loki 0.8.0 — manager llama.cpp + UI web // -// La même cause cassait la redirection et les tubes, et rendait `ajean chat` -// inutilisable depuis un terminal (le shell et AJEAN se disputaient l'entrée). +// La même cause cassait la redirection et les tubes, et rendait `loki chat` +// inutilisable depuis un terminal (le shell et Loki se disputaient l'entrée). // // EN CONTREPARTIE, Windows alloue une console à tout lancement sans terminal — // un double-clic. On la referme alors immédiatement : voir setupConsole. Les diff --git a/internal/ajean/sys_datadir.go b/internal/loki/sys_datadir.go similarity index 79% rename from internal/ajean/sys_datadir.go rename to internal/loki/sys_datadir.go index 0d3f0ff..c5e51a6 100644 --- a/internal/ajean/sys_datadir.go +++ b/internal/loki/sys_datadir.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -11,15 +11,15 @@ import ( // modèle de config.env : quatre copies qui divergeaient à la première // modification. Il n'y en a plus qu'une. -// dataDirs est l'arborescence complète de $AJEAN_HOME. Rien d'autre n'y est -// créé : tout le reste vit dans ajean.db. +// dataDirs est l'arborescence complète de $LOKI_HOME. Rien d'autre n'y est +// créé : tout le reste vit dans loki.db. func dataDirs() []string { - return []string{AjeanHome(), backendsDir(), binDir(), presetsDir(), memoryDir(), modelsDir(), workspaceDir()} + return []string{LokiHome(), backendsDir(), binDir(), presetsDir(), memoryDir(), modelsDir(), workspaceDir()} } // defaultConfig est la configuration de départ d'une installation neuve. Les // valeurs sont volontairement incomplètes (BIN et MODEL sont vides) : c'est -// l'écran d'accueil, ou « ajean llamacpp install », qui les renseigne. +// l'écran d'accueil, ou « loki llamacpp install », qui les renseigne. func defaultConfig() map[string]string { host := "0.0.0.0" if runtime.GOOS == "windows" { diff --git a/internal/ajean/sys_disk_other.go b/internal/loki/sys_disk_other.go similarity index 98% rename from internal/ajean/sys_disk_other.go rename to internal/loki/sys_disk_other.go index 604dd84..2bd914b 100644 --- a/internal/ajean/sys_disk_other.go +++ b/internal/loki/sys_disk_other.go @@ -1,6 +1,6 @@ //go:build !windows -package ajean +package loki // sys_disk_other.go — espace libre d'un système de fichiers via statfs(2). // Bavail (et pas Bfree) : les blocs réservés à root ne sont pas utilisables pour diff --git a/internal/ajean/sys_disk_windows.go b/internal/loki/sys_disk_windows.go similarity index 98% rename from internal/ajean/sys_disk_windows.go rename to internal/loki/sys_disk_windows.go index 166bc32..872ab39 100644 --- a/internal/ajean/sys_disk_windows.go +++ b/internal/loki/sys_disk_windows.go @@ -1,4 +1,4 @@ -package ajean +package loki // sys_disk_windows.go — espace libre d'un volume via GetDiskFreeSpaceExW. On // veut l'espace réellement disponible pour l'utilisateur courant (quotas diff --git a/internal/ajean/sys_firewall_other.go b/internal/loki/sys_firewall_other.go similarity index 97% rename from internal/ajean/sys_firewall_other.go rename to internal/loki/sys_firewall_other.go index 59def08..ec02068 100644 --- a/internal/ajean/sys_firewall_other.go +++ b/internal/loki/sys_firewall_other.go @@ -1,6 +1,6 @@ //go:build !windows -package ajean +package loki // Linux et macOS n'ont pas de pare-feu entrant activé par défaut, et quand il y // en a un (ufw, firewalld, pf) c'est une affaire d'administration système qu'un diff --git a/internal/ajean/sys_firewall_windows.go b/internal/loki/sys_firewall_windows.go similarity index 91% rename from internal/ajean/sys_firewall_windows.go rename to internal/loki/sys_firewall_windows.go index 8857a30..7252826 100644 --- a/internal/ajean/sys_firewall_windows.go +++ b/internal/loki/sys_firewall_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "fmt" @@ -14,9 +14,9 @@ import ( // Sur une installation Windows fraîche, même avec HOST=0.0.0.0, le pare-feu // bloque tout ce qui arrive du réseau : l'utilisateur voyait « ça écoute // partout » et se prenait quand même un délai d'attente depuis son autre -// machine. AJEAN pose donc la règle lui-même, quand il en a le droit. +// machine. Loki pose donc la règle lui-même, quand il en a le droit. // -// `ajean install` ne réclame PAS les droits administrateur (voir +// `loki install` ne réclame PAS les droits administrateur (voir // sys_install_windows.go) : netsh échouera donc souvent. On ne fait pas semblant // que ça a marché — firewallState relit la règle, et firewallManualHint donne la // commande exacte à coller dans un terminal administrateur. @@ -24,7 +24,7 @@ import ( // firewallRuleName identifie nos règles. Une par port : changer PORT dans la // configuration ne doit pas laisser une règle ouverte sur l'ancien. func firewallRuleName(port int) string { - return "AJEAN moteur (port " + strconv.Itoa(port) + ")" + return "Loki moteur (port " + strconv.Itoa(port) + ")" } // netsh exécute netsh sans faire clignoter de console (hideCmd) et renvoie sa @@ -71,7 +71,7 @@ func firewallState(port int) string { } // firewallManualHint : la commande à coller dans un terminal ADMINISTRATEUR -// quand AJEAN n'a pas pu poser la règle lui-même. +// quand Loki n'a pas pu poser la règle lui-même. func firewallManualHint(port int) string { return fmt.Sprintf("le pare-feu Windows bloque encore le port %d. Ouvre un terminal ADMINISTRATEUR et lance :\n"+ ` netsh advfirewall firewall add rule name="%s" dir=in action=allow protocol=TCP localport=%d profile=private,domain`, diff --git a/internal/ajean/sys_firstrun_other.go b/internal/loki/sys_firstrun_other.go similarity index 79% rename from internal/ajean/sys_firstrun_other.go rename to internal/loki/sys_firstrun_other.go index 51df785..490c5e0 100644 --- a/internal/ajean/sys_firstrun_other.go +++ b/internal/loki/sys_firstrun_other.go @@ -1,8 +1,8 @@ //go:build !windows -package ajean +package loki -// appFirstRun : hors Windows, `ajean app` est lancé depuis un terminal par +// appFirstRun : hors Windows, `loki app` est lancé depuis un terminal par // quelqu'un qui a déjà suivi l'installation — pas de double-clic à désambiguïser, // donc pas de boîte de dialogue. On se contente de provisionner les données au // premier lancement. Ne relance jamais (renvoie toujours false). diff --git a/internal/ajean/sys_firstrun_windows.go b/internal/loki/sys_firstrun_windows.go similarity index 89% rename from internal/ajean/sys_firstrun_windows.go rename to internal/loki/sys_firstrun_windows.go index dbf32f0..95f6cf2 100644 --- a/internal/ajean/sys_firstrun_windows.go +++ b/internal/loki/sys_firstrun_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "crypto/sha256" @@ -22,8 +22,8 @@ import ( // Premier lancement sous Windows. // -// Avant : double-cliquer ajean-windows-amd64.exe déclenchait une installation -// SILENCIEUSE (copie du binaire dans %ProgramData%\ajean\bin, ajout au PATH) dont +// Avant : double-cliquer loki-windows-amd64.exe déclenchait une installation +// SILENCIEUSE (copie du binaire dans %ProgramData%\loki\bin, ajout au PATH) dont // l'utilisateur ne voyait rien — cmdApp appelait cmdInstall sans console pour en // afficher la sortie. D'où la confusion légitime : est-ce que ça installe ou est-ce // que ça lance ? Et surtout, deux copies du binaire coexistaient (celle du Bureau, @@ -32,13 +32,13 @@ import ( // // Maintenant, deux cas seulement : // -// 1. AJEAN n'est pas installé. On installe, sans demander : rester à +// 1. Loki n'est pas installé. On installe, sans demander : rester à // l'emplacement du fichier téléchargé ne donne pas une installation // utilisable, la question n'avait donc qu'une réponse. Copie + PATH + // raccourcis, un message qui dit ce qui a été fait, et on démarre depuis la // copie installée. // -// 2. AJEAN est déjà installé. Le fichier téléchargé se comporte en installeur de +// 2. Loki est déjà installé. Le fichier téléchargé se comporte en installeur de // mise à jour, avec trois situations bien distinctes (voir runAsInstaller) : // plus récent et application arrêtée, on remplace et on démarre, sans un mot ; // plus récent mais application EN COURS, on demande s'il faut la fermer pour @@ -93,7 +93,7 @@ func appFirstRun() bool { if canonPath(exe) == canonPath(target) { // On EST l'application installée. On en profite pour garantir que les // raccourcis existent : sans ça, quelqu'un qui perd son raccourci ne - // retrouve plus AJEAN, et n'a aucune raison de relancer le fichier + // retrouve plus Loki, et n'a aucune raison de relancer le fichier // téléchargé (qu'il a souvent supprimé) pour le récupérer. En tâche de // fond, l'appel PowerShell ne doit pas retarder le démarrage. go ensureShortcuts(target) @@ -114,18 +114,18 @@ func appFirstRun() bool { // un fichier, autant dire où le programme s'est installé et qu'il peut // supprimer ce qu'il a téléchargé. if _, err := installSelf(filepath.Dir(target)); err != nil { - // Échec le plus courant : pas les droits sur %ProgramData%. AJEAN reste + // Échec le plus courant : pas les droits sur %ProgramData%. Loki reste // parfaitement utilisable depuis son emplacement actuel, le dossier de // données étant ailleurs — on démarre donc au lieu d'abandonner. - messageBox("Installation impossible :\n\n"+err.Error()+"\n\nAJEAN va démarrer depuis l'emplacement actuel.", "AJEAN", mbIconInfo) + messageBox("Installation impossible :\n\n"+err.Error()+"\n\nLoki va démarrer depuis l'emplacement actuel.", "Loki", mbIconInfo) return false } _, _ = addToUserPath(filepath.Dir(target)) shortcuts := ensureShortcuts(target) - messageBox("AJEAN est installé.\n\n"+target+"\n\n"+shortcuts+ + messageBox("Loki est installé.\n\n"+target+"\n\n"+shortcuts+ "\n\nL'application va démarrer. Vous pouvez supprimer le fichier téléchargé.", - "AJEAN", mbIconInfo) + "Loki", mbIconInfo) return launch(target) } @@ -193,7 +193,7 @@ func launch(target string) bool { return cmd.Start() == nil } -// runAsInstaller : AJEAN est déjà installé et on exécute une AUTRE copie (le +// runAsInstaller : Loki est déjà installé et on exécute une AUTRE copie (le // fichier fraîchement téléchargé). Le fichier joue alors le rôle d'installeur de // mise à jour. Trois situations, trois comportements distincts, parce qu'elles // n'appellent pas la même décision de l'utilisateur. @@ -224,37 +224,37 @@ func runAsInstaller(target string) bool { // serait une régression silencieuse, et démarrer sans rien dire laisserait // croire qu'on utilise la version qu'on vient de télécharger. if messageBox( - "Une version plus récente d'AJEAN est déjà installée sur cet ordinateur.\n\n"+ + "Une version plus récente d'Loki est déjà installée sur cet ordinateur.\n\n"+ " installée : "+installed+"\n"+ " ce fichier : "+Version+"\n\n"+ "Rien ne sera remplacé.\n\n"+ - "Voulez-vous démarrer AJEAN (version "+installed+") ?\n"+ + "Voulez-vous démarrer Loki (version "+installed+") ?\n"+ "Répondre Non ferme simplement cette fenêtre.", - "AJEAN", mbYesNo|mbIconQuestion) != idYes { + "Loki", mbYesNo|mbIconQuestion) != idYes { return true // rien à faire, on quitte sans démarrer } ensureShortcuts(target) return launch(target) case cmp > 0 && len(running) > 0: - // Mise à jour disponible, mais AJEAN tourne. Écrire le nouveau binaire + // Mise à jour disponible, mais Loki tourne. Écrire le nouveau binaire // sans redémarrer ne changerait RIEN à ce que l'utilisateur a sous les // yeux : l'onglet qui s'ouvrirait serait servi par l'ancienne version. On // pose donc la seule question qui compte. if messageBox( - "AJEAN est déjà en cours d'exécution.\n\n"+ + "Loki est déjà en cours d'exécution.\n\n"+ " version en cours : "+verLabel(installed)+"\n"+ " ce fichier : "+Version+" (plus récente)\n\n"+ - "Fermer AJEAN et le redémarrer pour appliquer la mise à jour ?\n\n"+ - "Répondre Non ouvre AJEAN dans sa version actuelle, sans rien mettre à jour.", - "Mise à jour d'AJEAN", mbYesNo|mbIconQuestion) != idYes { + "Fermer Loki et le redémarrer pour appliquer la mise à jour ?\n\n"+ + "Répondre Non ouvre Loki dans sa version actuelle, sans rien mettre à jour.", + "Mise à jour d'Loki", mbYesNo|mbIconQuestion) != idYes { ensureShortcuts(target) return launch(target) // l'instance en cours reprend la main (port occupé) } stopProcesses(running) if err := replaceInstalled(target); err != nil { - messageBox("La mise à jour a échoué :\n\n"+err.Error()+"\n\nAJEAN va redémarrer dans sa version actuelle.", - "AJEAN", mbIconInfo) + messageBox("La mise à jour a échoué :\n\n"+err.Error()+"\n\nLoki va redémarrer dans sa version actuelle.", + "Loki", mbIconInfo) } ensureShortcuts(target) return launch(target) @@ -282,7 +282,7 @@ func verLabel(v string) string { // replaceInstalled écrase le binaire installé par celui qu'on exécute. Le // renommage préalable en .old permet de remplacer un exécutable encore ouvert -// (même mécanique que replaceBinary pour `ajean update`). +// (même mécanique que replaceBinary pour `loki update`). func replaceInstalled(target string) error { removeOldBinaries(target) old, err := renameAside(target) // nom unique, cf. renameAside @@ -298,7 +298,7 @@ func replaceInstalled(target string) error { } // runningPIDs liste les processus qui exécutent exactement ce fichier. On compare -// le CHEMIN, pas le nom : tuer par nom d'image (« ajean.exe ») emporterait aussi +// le CHEMIN, pas le nom : tuer par nom d'image (« loki.exe ») emporterait aussi // le processus courant et toute autre copie sans rapport. // // La comparaison se fait ICI, sur des chemins canonisés, et non dans le script @@ -375,10 +375,10 @@ func portBusy(port int) bool { } // binaryVersion lit la version dans les ressources du fichier (VS_VERSIONINFO, -// posées par goversioninfo, cf cmd/ajean/versioninfo.json), comme le fait +// posées par goversioninfo, cf cmd/loki/versioninfo.json), comme le fait // l'onglet « Détails » des propriétés Windows. // -// ⚠️ NE PAS remplacer par un `ajean version` exécuté : le binaire est compilé en +// ⚠️ NE PAS remplacer par un `loki version` exécuté : le binaire est compilé en // sous-système GUI, il s'attache à la console du parent et n'écrit RIEN dans un // tuyau. La sortie capturée est vide, donc la comparaison de versions échouerait // toujours en silence et la mise à jour ne se ferait jamais (vérifié). @@ -429,7 +429,7 @@ type vsFixedFileInfo struct { FileDateLS uint32 } -// ensureShortcuts garantit qu'un raccourci « AJEAN » existe dans le menu +// ensureShortcuts garantit qu'un raccourci « Loki » existe dans le menu // Démarrer et sur le Bureau, et qu'il pointe sur le bon exécutable. Appelée à // CHAQUE lancement du fichier téléchargé, pas seulement à l'installation : // un raccourci perdu (nettoyage, migration de profil, dossier Démarrer @@ -448,9 +448,9 @@ $progs=Join-Path ([Environment]::GetFolderPath('StartMenu')) 'Programs' foreach ($d in @($progs, [Environment]::GetFolderPath('Desktop'))) { if (-not $d) { continue } if (-not (Test-Path $d)) { continue } - $lnk=Join-Path $d 'AJEAN.lnk' + $lnk=Join-Path $d 'Loki.lnk' # Un raccourci existant est REPOINTÉ s'il vise autre chose que le binaire - # canonique — typiquement l'ancien « ajean.exe », qui n'est plus qu'un alias. + # canonique — typiquement l'ancien « loki.exe », qui n'est plus qu'un alias. # Le laisser en l'état condamnait l'utilisateur à relancer indéfiniment une # version périmée par son propre raccourci. if (Test-Path $lnk) { @@ -476,9 +476,9 @@ foreach ($d in @($progs, [Environment]::GetFolderPath('Desktop'))) { $s=$w.CreateShortcut($lnk) $s.TargetPath=$t $s.WorkingDirectory=(Split-Path $t) - $s.Description='AJEAN, votre IA locale' + $s.Description='Loki, votre IA locale' # Minimisé (7) : le binaire étant en sous-système console, Windows lui alloue - # une console au lancement. AJEAN la referme aussitôt, mais demander un + # une console au lancement. Loki la referme aussitôt, mais demander un # démarrage minimisé garantit qu'elle n'est jamais peinte à l'écran. $s.WindowStyle=7 $s.IconLocation="$t,0" @@ -490,9 +490,9 @@ Write-Output ($done -join ';')`, psQuote(target)) cmd := hideCmd(exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", ps)) out, err := cmd.CombinedOutput() if err != nil || strings.TrimSpace(string(out)) == "" { - return "(raccourcis non créés — lancez AJEAN depuis " + target + ")" + return "(raccourcis non créés — lancez Loki depuis " + target + ")" } - return "Raccourci « AJEAN » ajouté au menu Démarrer et au Bureau." + return "Raccourci « Loki » ajouté au menu Démarrer et au Bureau." } // removeShortcuts efface les raccourcis posés par ensureShortcuts. Renvoie true @@ -503,7 +503,7 @@ func removeShortcuts() bool { $sm=[Environment]::GetFolderPath('StartMenu') foreach ($d in @($sm, (Join-Path $sm 'Programs'), [Environment]::GetFolderPath('Desktop'))) { if (-not $d) { continue } - $p=Join-Path $d 'AJEAN.lnk' + $p=Join-Path $d 'Loki.lnk' if (Test-Path $p) { try { Remove-Item $p -Force; $n++ } catch {} } } Write-Output $n` diff --git a/internal/ajean/sys_firstrun_windows_test.go b/internal/loki/sys_firstrun_windows_test.go similarity index 89% rename from internal/ajean/sys_firstrun_windows_test.go rename to internal/loki/sys_firstrun_windows_test.go index d4bb084..3150a7d 100644 --- a/internal/ajean/sys_firstrun_windows_test.go +++ b/internal/loki/sys_firstrun_windows_test.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "os" @@ -12,7 +12,7 @@ import ( // binaryVersion pilote la mise à jour automatique du binaire installé quand on // lance le fichier téléchargé : si elle renvoie du vide, plus aucune mise à jour // n'a lieu, et en silence. C'est exactement ce qui arrivait avec la première -// implémentation (exécuter `ajean version`, dont la sortie est vide pour un +// implémentation (exécuter `loki version`, dont la sortie est vide pour un // binaire en sous-système GUI). D'où ces tests sur la plomberie syscall. func TestBinaryVersionReadsResource(t *testing.T) { // kernel32.dll porte toujours une ressource de version. @@ -28,10 +28,10 @@ func TestBinaryVersionReadsResource(t *testing.T) { // Sur une machine vierge, le dossier bin n'existe pas encore : installSelf doit // le créer. Sans ça, le premier lancement échouait sur « open -// C:\ProgramData\ajean\bin\ajean.exe: The system cannot find the path specified » -// et AJEAN ne s'installait jamais. +// C:\ProgramData\loki\bin\loki.exe: The system cannot find the path specified » +// et Loki ne s'installait jamais. func TestInstallSelfCreatesBinDir(t *testing.T) { - binDir := filepath.Join(t.TempDir(), "ajean", "bin") // deux niveaux absents + binDir := filepath.Join(t.TempDir(), "loki", "bin") // deux niveaux absents dst, err := installSelf(binDir) if err != nil { t.Fatalf("installSelf sur un dossier absent : %v", err) @@ -45,7 +45,7 @@ func TestInstallSelfCreatesBinDir(t *testing.T) { // cible existe déjà, et laisser l'ancien de côté sans le confondre avec la cible. func TestReplaceInstalled(t *testing.T) { dir := t.TempDir() - target := filepath.Join(dir, "ajean.exe") + target := filepath.Join(dir, "loki.exe") if err := os.WriteFile(target, []byte("ancienne version"), 0o755); err != nil { t.Fatal(err) } @@ -81,7 +81,7 @@ func TestBinaryVersionWithoutResource(t *testing.T) { } } -// Reproduit le cas signale en usage : l'alias « ajean.exe » est EN COURS +// Reproduit le cas signale en usage : l'alias « loki.exe » est EN COURS // d'execution au moment de la mise a jour. copyExe seul echouait, l'alias // restait fige sur une version perimee, et tout raccourci le visant relancait // indefiniment l'ancienne version — qui affichait « une version plus recente @@ -89,7 +89,7 @@ func TestBinaryVersionWithoutResource(t *testing.T) { func TestReplaceExeSurchargeUnFichierVerrouille(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "neuf.exe") - dst := filepath.Join(dir, "ajean.exe") + dst := filepath.Join(dir, "loki.exe") if err := os.WriteFile(src, []byte("VERSION-NEUVE"), 0o755); err != nil { t.Fatal(err) } @@ -120,7 +120,7 @@ func TestReplaceExeSurchargeUnFichierVerrouille(t *testing.T) { // echoue apres le renommage, l'ancien fichier revient a sa place. func TestReplaceExeRestaureSiLaCopieEchoue(t *testing.T) { dir := t.TempDir() - dst := filepath.Join(dir, "ajean.exe") + dst := filepath.Join(dir, "loki.exe") if err := os.WriteFile(dst, []byte("a-preserver"), 0o755); err != nil { t.Fatal(err) } diff --git a/internal/ajean/sys_install_darwin.go b/internal/loki/sys_install_darwin.go similarity index 87% rename from internal/ajean/sys_install_darwin.go rename to internal/loki/sys_install_darwin.go index a555121..74093dc 100644 --- a/internal/ajean/sys_install_darwin.go +++ b/internal/loki/sys_install_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package ajean +package loki import ( "fmt" @@ -17,7 +17,7 @@ import ( // Implémentation prudente, à valider sur une machine Apple. // launchdPlistTemplate — champs : Label, binaire, argument, UserName, -// WorkingDirectory, AJEAN_HOME, StandardOutPath, StandardErrorPath. +// WorkingDirectory, LOKI_HOME, StandardOutPath, StandardErrorPath. // KeepAlive/SuccessfulExit=false ≈ Restart=on-failure ; RunAtLoad relance au // boot une fois chargé avec `-w`. const launchdPlistTemplate = ` @@ -33,7 +33,7 @@ const launchdPlistTemplate = ` UserName%s WorkingDirectory%s EnvironmentVariables - AJEAN_HOME%s + LOKI_HOME%s RunAtLoad KeepAlive SuccessfulExit @@ -48,12 +48,12 @@ func unitArgs() map[string]string { return map[string]string{serviceName(): "serve", uiUnitName: "web"} } -func installServices(targetUser, ajeanHome string) error { +func installServices(targetUser, lokiHome string) error { exe := installedExePath() for name, arg := range unitArgs() { - logPath := filepath.Join(ajeanHome, name+".log") + logPath := filepath.Join(lokiHome, name+".log") plist := fmt.Sprintf(launchdPlistTemplate, - launchdLabel(name), exe, arg, targetUser, ajeanHome, ajeanHome, logPath, logPath) + launchdLabel(name), exe, arg, targetUser, lokiHome, lokiHome, logPath, logPath) path := launchdPlistPath(name) if err := os.WriteFile(path, []byte(plist), 0o644); err != nil { return err diff --git a/internal/ajean/sys_install_linux.go b/internal/loki/sys_install_linux.go similarity index 89% rename from internal/ajean/sys_install_linux.go rename to internal/loki/sys_install_linux.go index 769f004..fb2a3d1 100644 --- a/internal/ajean/sys_install_linux.go +++ b/internal/loki/sys_install_linux.go @@ -1,6 +1,6 @@ //go:build linux -package ajean +package loki import ( "fmt" @@ -14,14 +14,14 @@ import ( // sudoersTemplate autorise l'utilisateur à piloter UNE unité sans mot de passe. // Posé pour les deux : l'interface, qui tourne sans privilèges, doit pouvoir // redémarrer le moteur (bascule de preset) comme elle-même (nouveau jeton). -const sudoersTemplate = `# Permet à %[1]s de piloter l'unité %[2]s sans mot de passe (posé par ajean install). +const sudoersTemplate = `# Permet à %[1]s de piloter l'unité %[2]s sans mot de passe (posé par loki install). %[1]s ALL=(root) NOPASSWD: /bin/systemctl start %[2]s, /bin/systemctl stop %[2]s, /bin/systemctl restart %[2]s, /bin/systemctl enable %[2]s, /bin/systemctl disable %[2]s ` // engineUnitTemplate — le moteur : exec llama-server, supervisé directement par // systemd. Champs : User, WorkingDirectory, ExecStart. const engineUnitTemplate = `[Unit] -Description=AJEAN — moteur llama.cpp +Description=Loki — moteur llama.cpp After=network.target [Service] @@ -47,7 +47,7 @@ WantedBy=multi-user.target // OpenAI, servis par un SEUL process (donc une seule conversation). // Champs : unité du moteur (dépendance), User, WorkingDirectory, ExecStart. const uiUnitTemplate = `[Unit] -Description=AJEAN — interface web + accès distant +Description=Loki — interface web + accès distant After=network-online.target %s.service Wants=network-online.target @@ -63,11 +63,11 @@ RestartSec=5 WantedBy=multi-user.target ` -func installServices(targetUser, ajeanHome string) error { +func installServices(targetUser, lokiHome string) error { svc, exe := serviceName(), installedExePath() units := map[string]string{ - svc: fmt.Sprintf(engineUnitTemplate, targetUser, ajeanHome, exe+" serve"), - uiUnitName: fmt.Sprintf(uiUnitTemplate, svc, targetUser, ajeanHome, exe+" web"), + svc: fmt.Sprintf(engineUnitTemplate, targetUser, lokiHome, exe+" serve"), + uiUnitName: fmt.Sprintf(uiUnitTemplate, svc, targetUser, lokiHome, exe+" web"), } for name, body := range units { path := "/etc/systemd/system/" + name + ".service" diff --git a/internal/ajean/sys_install_unix.go b/internal/loki/sys_install_unix.go similarity index 73% rename from internal/ajean/sys_install_unix.go rename to internal/loki/sys_install_unix.go index c65ed50..bc8ccac 100644 --- a/internal/ajean/sys_install_unix.go +++ b/internal/loki/sys_install_unix.go @@ -1,6 +1,6 @@ //go:build !windows -package ajean +package loki import ( "fmt" @@ -23,7 +23,7 @@ import ( func cmdInstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("ajean install doit être exécuté en root (sudo ajean install)") + return fmt.Errorf("loki install doit être exécuté en root (sudo loki install)") } targetUser := os.Getenv("SUDO_USER") if targetUser == "" { @@ -38,19 +38,19 @@ func cmdInstall(args []string) error { if err != nil { return fmt.Errorf("utilisateur '%s' introuvable: %w", targetUser, err) } - ajeanHome := defaultAjeanHome() - if v := os.Getenv("AJEAN_HOME"); v != "" { - ajeanHome = v + lokiHome := defaultLokiHome() + if v := os.Getenv("LOKI_HOME"); v != "" { + lokiHome = v } fmt.Printf("Installation pour utilisateur %s\n", cyan(targetUser)) - fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome) + fmt.Printf(" LOKI_HOME = %s\n", lokiHome) fmt.Printf(" services = %s + %s\n", serviceName(), uiUnitName) // 0. Reprise d'une installation 0.7, si c'en est une. AVANT provisionDataDir : // elle déplace des dossiers entiers, donc la destination ne doit pas // encore exister. Code temporaire, voir migrate_07.go. - if err := migrateFrom07(ajeanHome); err != nil { + if err := migrateFrom07(lokiHome); err != nil { return fmt.Errorf("reprise de l'installation 0.7 : %w", err) } @@ -60,45 +60,45 @@ func cmdInstall(args []string) error { } fmt.Printf(" %s arborescence prête\n", green("✓")) - // 2. Lien vers /usr/local/bin/ajean. + // 2. Lien vers /usr/local/bin/loki. if err := linkInstalledExe(); err != nil { return err } - // 3. /etc/default/ajean, pour que les invocations root résolvent AJEAN_HOME. + // 3. /etc/default/loki, pour que les invocations root résolvent LOKI_HOME. if err := os.MkdirAll("/etc/default", 0o755); err != nil { return err } - defaults := fmt.Sprintf("# Généré par ajean install — racine des données\nAJEAN_HOME=%s\n", ajeanHome) - if err := os.WriteFile("/etc/default/ajean", []byte(defaults), 0o644); err != nil { + defaults := fmt.Sprintf("# Généré par loki install — racine des données\nLOKI_HOME=%s\n", lokiHome) + if err := os.WriteFile("/etc/default/loki", []byte(defaults), 0o644); err != nil { return err } - fmt.Printf(" %s /etc/default/ajean\n", green("✓")) + fmt.Printf(" %s /etc/default/loki\n", green("✓")) // 4. Déclaration des services (systemd ou launchd selon la plateforme). - if err := installServices(targetUser, ajeanHome); err != nil { + if err := installServices(targetUser, lokiHome); err != nil { return err } - // 5. AJEAN_HOME appartient à l'utilisateur cible : les deux services et les + // 5. LOKI_HOME appartient à l'utilisateur cible : les deux services et les // commandes qu'il tape doivent pouvoir écrire dans la base. - chown(ajeanHome, u) + chown(lokiHome, u) fmt.Println() fmt.Printf("%s installation terminée.\n", green("[ok]")) fmt.Printf("\nProchaines étapes :\n") - fmt.Printf(" 1. installe le moteur : %s (compile llama.cpp, renseigne BIN)\n", bold("sudo -u "+targetUser+" ajean llamacpp install")) - fmt.Printf(" 2. choisis le modèle : %s (renseigne MODEL, un fichier .gguf)\n", bold("sudo -u "+targetUser+" ajean edit")) - fmt.Printf(" 3. démarre le moteur : %s\n", bold("sudo ajean start")) - fmt.Printf(" 4. vérifie : %s\n", bold("ajean test")) - fmt.Printf(" 5. démarre l'interface : %s (http://localhost:8090)\n", bold("sudo ajean ui start")) + fmt.Printf(" 1. installe le moteur : %s (compile llama.cpp, renseigne BIN)\n", bold("sudo -u "+targetUser+" loki llamacpp install")) + fmt.Printf(" 2. choisis le modèle : %s (renseigne MODEL, un fichier .gguf)\n", bold("sudo -u "+targetUser+" loki edit")) + fmt.Printf(" 3. démarre le moteur : %s\n", bold("sudo loki start")) + fmt.Printf(" 4. vérifie : %s\n", bold("loki test")) + fmt.Printf(" 5. démarre l'interface : %s (http://localhost:8090)\n", bold("sudo loki ui start")) return nil } -// linkInstalledExe pose /usr/local/bin/ajean sur le binaire courant. +// linkInstalledExe pose /usr/local/bin/loki sur le binaire courant. // // Garde-fou (issue #5) : si on tourne DÉJÀ depuis la cible — l'utilisateur a -// posé le binaire là puis lancé `sudo ajean install` — il ne faut surtout pas +// posé le binaire là puis lancé `sudo loki install` — il ne faut surtout pas // Remove+Symlink sur soi-même : ça effacerait le vrai binaire et créerait un // lien vers lui-même (« Too many levels of symbolic links »). On compare les // chemins réels, résolus. @@ -135,15 +135,15 @@ func linkInstalledExe() error { func cmdUninstall(args []string) error { if os.Geteuid() != 0 { - return fmt.Errorf("ajean uninstall doit être exécuté en root") + return fmt.Errorf("loki uninstall doit être exécuté en root") } uninstallServices() - for _, p := range []string{"/etc/default/ajean", installedExePath()} { + for _, p := range []string{"/etc/default/loki", installedExePath()} { if err := os.Remove(p); err == nil { fmt.Printf(" %s %s\n", green("✓"), p) } } - fmt.Println(dim("(données utilisateur conservées — supprime $AJEAN_HOME manuellement si tu veux purger)")) + fmt.Println(dim("(données utilisateur conservées — supprime $LOKI_HOME manuellement si tu veux purger)")) fmt.Println(green("[ok]") + " désinstallé") return nil } diff --git a/internal/ajean/sys_install_windows.go b/internal/loki/sys_install_windows.go similarity index 81% rename from internal/ajean/sys_install_windows.go rename to internal/loki/sys_install_windows.go index 1ccd722..278e754 100644 --- a/internal/ajean/sys_install_windows.go +++ b/internal/loki/sys_install_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "fmt" @@ -12,21 +12,21 @@ import ( ) // On Windows there's no systemd unit, sudoers, or /usr/local/bin to populate. -// `ajean install` simply provisions the data directory and a starter config; the +// `loki install` simply provisions the data directory and a starter config; the // service itself is managed by the PID-file supervisor in sys_service_windows.go -// (ajean start / stop / status), which needs no admin rights. +// (loki start / stop / status), which needs no admin rights. func cmdInstall(args []string) error { - ajeanHome := AjeanHome() + lokiHome := LokiHome() fmt.Printf("Installation (Windows)\n") - fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome) + fmt.Printf(" LOKI_HOME = %s\n", lokiHome) fmt.Printf(" service = %s\n", serviceName()) // 0. Reprise d'une installation 0.7, si c'en est une. AVANT provisionDataDir : // elle déplace des dossiers entiers, donc la destination ne doit pas // encore exister. Code temporaire, voir migrate_07.go. - if err := migrateFrom07(ajeanHome); err != nil { + if err := migrateFrom07(lokiHome); err != nil { return fmt.Errorf("reprise de l'installation 0.7 : %w", err) } @@ -37,8 +37,8 @@ func cmdInstall(args []string) error { } fmt.Printf(" %s arborescence prête\n", green("✓")) - // 2. Le binaire va dans AJEAN_HOME\bin, ajouté au PATH utilisateur, pour que - // `ajean` soit appelable depuis n'importe quel shell (pendant Windows du + // 2. Le binaire va dans LOKI_HOME\bin, ajouté au PATH utilisateur, pour que + // `loki` soit appelable depuis n'importe quel shell (pendant Windows du // lien /usr/local/bin créé par l'installateur Unix). dir := binDir() onPath := false @@ -62,40 +62,40 @@ func cmdInstall(args []string) error { fmt.Println() fmt.Printf("%s installation terminée.\n", green("[ok]")) fmt.Printf("\nProchaines étapes :\n") - fmt.Printf(" 1. édite la config : %s (renseigne BIN, MODEL)\n", bold("ajean edit")) - fmt.Printf(" 2. démarre le service: %s\n", bold("ajean start")) - fmt.Printf(" 3. UI web : %s\n", bold("ajean web")) + fmt.Printf(" 1. édite la config : %s (renseigne BIN, MODEL)\n", bold("loki edit")) + fmt.Printf(" 2. démarre le service: %s\n", bold("loki start")) + fmt.Printf(" 3. UI web : %s\n", bold("loki web")) if onPath { - fmt.Printf("\n%s ouvre un NOUVEAU terminal pour que 'ajean' soit reconnu (le PATH n'est lu qu'au démarrage du shell).\n", dim("[info]")) + fmt.Printf("\n%s ouvre un NOUVEAU terminal pour que 'loki' soit reconnu (le PATH n'est lu qu'au démarrage du shell).\n", dim("[info]")) } else { - fmt.Printf("\n%s pour exécuter 'ajean' depuis n'importe où, ajoute son dossier au PATH.\n", dim("[info]")) + fmt.Printf("\n%s pour exécuter 'loki' depuis n'importe où, ajoute son dossier au PATH.\n", dim("[info]")) } return nil } -// installSelf copie l'exécutable en cours dans binDir sous le nom ajean.exe et +// installSelf copie l'exécutable en cours dans binDir sous le nom loki.exe et // renvoie le chemin de destination. Sans effet si l'exe tourne déjà depuis là // (ré-installation). // // binDir est créé ici, et pas seulement par l'appelant : sur une machine vierge, // le premier lancement passait par appFirstRun, qui ne créait que le dossier de // données et pas son sous-dossier bin. L'utilisateur recevait alors « open -// C:\ProgramData\ajean\bin\ajean.exe: The system cannot find the path specified » -// et AJEAN démarrait depuis le fichier téléchargé, sans jamais s'installer. +// C:\ProgramData\loki\bin\loki.exe: The system cannot find the path specified » +// et Loki démarrait depuis le fichier téléchargé, sans jamais s'installer. func installSelf(binDir string) (string, error) { src, err := os.Executable() if err != nil { return "", err } src, _ = filepath.EvalSymlinks(src) - dst := filepath.Join(binDir, "ajean.exe") + dst := filepath.Join(binDir, "loki.exe") if strings.EqualFold(src, dst) { return dst, nil } if err := os.MkdirAll(binDir, 0o755); err != nil { return "", err } - // replaceExe et non copyExe : ajean.exe peut etre en cours d'execution + // replaceExe et non copyExe : loki.exe peut etre en cours d'execution // (service en tache de fond), auquel cas Windows refuse de l'ecraser. if err := replaceExe(src, dst); err != nil { return "", err @@ -107,7 +107,7 @@ func installSelf(binDir string) (string, error) { // // Windows refuse d'écraser un .exe en cours d'exécution, mais accepte de le // RENOMMER : on décale l'ancien puis on écrit le nouveau à sa place. Sans ça, -// l'alias « ajean.exe » restait figé sur une version périmée dès qu'il tournait +// l'alias « loki.exe » restait figé sur une version périmée dès qu'il tournait // au moment de la mise à jour — et comme les raccourcis existants le visent // encore, chaque lancement relançait l'ancienne version, qui constatait qu'une // plus récente était installée et le disait. À chaque fois. Constaté en usage. @@ -209,7 +209,7 @@ func cmdUninstall(args []string) error { // Stop the background server if it's running. _ = svcStop(false) - // Pull AJEAN_HOME\bin off the user PATH (best-effort). + // Pull LOKI_HOME\bin off the user PATH (best-effort). dir := binDir() if removed, err := removeFromUserPath(dir); err == nil && removed { fmt.Printf(" %s %s retiré du PATH utilisateur\n", green("✓"), dir) @@ -217,15 +217,15 @@ func cmdUninstall(args []string) error { // Raccourcis posés à l'installation (menu Démarrer + Bureau). if removeShortcuts() { - fmt.Printf(" %s raccourcis « AJEAN » supprimés\n", green("✓")) + fmt.Printf(" %s raccourcis « Loki » supprimés\n", green("✓")) } if !keepData { - ajeanHome := AjeanHome() - if err := os.RemoveAll(ajeanHome); err != nil { - return fmt.Errorf("suppression de %s: %w", ajeanHome, err) + lokiHome := LokiHome() + if err := os.RemoveAll(lokiHome); err != nil { + return fmt.Errorf("suppression de %s: %w", lokiHome, err) } - fmt.Printf(" %s %s supprimé\n", green("✓"), ajeanHome) + fmt.Printf(" %s %s supprimé\n", green("✓"), lokiHome) } else { fmt.Println(dim("(données utilisateur conservées — relance avec --purge pour tout supprimer)")) } diff --git a/internal/ajean/sys_network.go b/internal/loki/sys_network.go similarity index 91% rename from internal/ajean/sys_network.go rename to internal/loki/sys_network.go index ac245ea..3bdfe55 100644 --- a/internal/ajean/sys_network.go +++ b/internal/loki/sys_network.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" @@ -11,19 +11,19 @@ import ( // // Le moteur écoute à l'adresse HOST. Sous Windows, l'installation pose // HOST=127.0.0.1 : le chat du navigateur marche (il passe par le serveur web -// d'AJEAN, sur la même machine), mais l'endpoint OpenAI :8080/v1 est INVISIBLE +// d'Loki, sur la même machine), mais l'endpoint OpenAI :8080/v1 est INVISIBLE // depuis le reste du réseau. Or c'est très exactement ce que les gens viennent // chercher : brancher un logiciel tiers sur le modèle qui tourne dans le salon. // // Deux choses manquaient, et pas une seule : // 1. HOST n'était réglable NULLE PART dans l'interface — il fallait connaître -// « ajean edit » et savoir quoi y écrire. +// « loki edit » et savoir quoi y écrire. // 2. Même à 0.0.0.0, le pare-feu Windows bloque les connexions entrantes tant -// qu'aucune règle n'autorise le port. AJEAN n'en posait aucune. +// qu'aucune règle n'autorise le port. Loki n'en posait aucune. // // D'où un seul interrupteur qui fait les deux, et qui DIT ce qu'il n'a pas pu // faire (poser une règle de pare-feu exige les droits administrateur, que -// l'installation d'AJEAN ne réclame pas). +// l'installation d'Loki ne réclame pas). // firewallInert coupe tout pilotage du pare-feu. Positionné par les tests : une // suite qui laisse une règle entrante derrière elle est un effet de bord @@ -55,7 +55,7 @@ func lanExposed() bool { return h != hostLocalOnly && h != "::1" && !strings.EqualFold(h, "localhost") } -// netStatus est ce que l'interface affiche, et ce que renvoie `ajean network`. +// netStatus est ce que l'interface affiche, et ce que renvoie `loki network`. type netStatus struct { Exposed bool `json:"exposed"` // le moteur écoute-t-il sur le réseau Host string `json:"host"` // adresse d'écoute effective @@ -103,7 +103,7 @@ func setLANExposure(on bool) (netStatus, error) { return networkStatus(), nil } -// cmdNetwork : `ajean network [on|off|status]`. +// cmdNetwork : `loki network [on|off|status]`. func cmdNetwork(args []string) error { sub := "status" if len(args) > 0 { @@ -121,7 +121,7 @@ func cmdNetwork(args []string) error { fmt.Printf("%s le moteur n'écoutera plus que sur cette machine\n", green("[ok]")) } printNetHint(st) - fmt.Printf("%s redémarre le moteur pour appliquer : %s\n", dim("[info]"), bold("ajean restart")) + fmt.Printf("%s redémarre le moteur pour appliquer : %s\n", dim("[info]"), bold("loki restart")) return nil case "status", "": st := networkStatus() @@ -136,7 +136,7 @@ func cmdNetwork(args []string) error { case "ferme": fmt.Printf(" pare-feu : %s\n", yellow("aucune règle pour ce port")) default: - fmt.Printf(" pare-feu : %s\n", dim("non géré par AJEAN sur cette plateforme")) + fmt.Printf(" pare-feu : %s\n", dim("non géré par Loki sur cette plateforme")) } if st.Exposed { fmt.Printf(" endpoint OpenAI : %s\n", bold(st.URL)) @@ -144,7 +144,7 @@ func cmdNetwork(args []string) error { printNetHint(st) return nil } - return fmt.Errorf("usage: ajean network [on|off|status]") + return fmt.Errorf("usage: loki network [on|off|status]") } func printNetHint(st netStatus) { diff --git a/internal/ajean/sys_network_test.go b/internal/loki/sys_network_test.go similarity index 99% rename from internal/ajean/sys_network_test.go rename to internal/loki/sys_network_test.go index 8f1c1b6..98c4193 100644 --- a/internal/ajean/sys_network_test.go +++ b/internal/loki/sys_network_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "encoding/json" diff --git a/internal/ajean/sys_paths.go b/internal/loki/sys_paths.go similarity index 78% rename from internal/ajean/sys_paths.go rename to internal/loki/sys_paths.go index 37c0194..829e4e7 100644 --- a/internal/ajean/sys_paths.go +++ b/internal/loki/sys_paths.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "fmt" @@ -13,11 +13,11 @@ import ( // emplacements invisibles : on ne sait plus si le .exe qu'on a double-cliqué EST // l'application ou seulement un installateur, ni où atterrissent la config, les // modèles et les fichiers que l'agent écrit. On centralise donc la réponse ici, -// et on l'expose à la fois en ligne de commande (`ajean where`) et dans l'UI. +// et on l'expose à la fois en ligne de commande (`loki where`) et dans l'UI. -type ajeanPaths struct { - Home string `json:"home"` // AJEAN_HOME : racine des données - Database string `json:"database"` // ajean.db : config, préférences, conversation, clés +type lokiPaths struct { + Home string `json:"home"` // LOKI_HOME : racine des données + Database string `json:"database"` // loki.db : config, préférences, conversation, clés Exe string `json:"exe"` // binaire en cours d'exécution Installed string `json:"installed"` // binaire installé (peut différer de Exe) Workspace string `json:"workspace"` // dossier de travail du mode agent @@ -28,23 +28,23 @@ type ajeanPaths struct { } // installedExePath est l'emplacement canonique du binaire après installation. -// Il diffère par plateforme : sous Unix les installateurs posent /usr/local/bin/ajean +// Il diffère par plateforme : sous Unix les installateurs posent /usr/local/bin/loki // (c'est ce que référencent les unités systemd et le plist launchd) ; sous Windows, -// faute d'équivalent, on utilise AJEAN_HOME\bin ajouté au PATH utilisateur. +// faute d'équivalent, on utilise LOKI_HOME\bin ajouté au PATH utilisateur. func installedExePath() string { if runtime.GOOS == "windows" { - return filepath.Join(binDir(), "ajean.exe") + return filepath.Join(binDir(), "loki.exe") } - return "/usr/local/bin/ajean" + return "/usr/local/bin/loki" } -func currentPaths() ajeanPaths { +func currentPaths() lokiPaths { exe, _ := os.Executable() if resolved, err := filepath.EvalSymlinks(exe); err == nil { exe = resolved } - return ajeanPaths{ - Home: AjeanHome(), + return lokiPaths{ + Home: LokiHome(), Database: dbPath(), Exe: exe, Installed: installedExePath(), @@ -58,9 +58,9 @@ func currentPaths() ajeanPaths { func cmdWhere(args []string) error { p := currentPaths() - fmt.Printf("Emplacements AJEAN\n\n") + fmt.Printf("Emplacements Loki\n\n") for _, row := range [][2]string{ - {"données (AJEAN_HOME)", p.Home}, + {"données (LOKI_HOME)", p.Home}, {"base", p.Database}, {"binaire en cours", p.Exe}, {"binaire installé", p.Installed}, diff --git a/internal/ajean/sys_platform_unix.go b/internal/loki/sys_platform_unix.go similarity index 94% rename from internal/ajean/sys_platform_unix.go rename to internal/loki/sys_platform_unix.go index 103f7d1..1e708da 100644 --- a/internal/ajean/sys_platform_unix.go +++ b/internal/loki/sys_platform_unix.go @@ -1,6 +1,6 @@ //go:build unix -package ajean +package loki import ( "context" @@ -16,22 +16,22 @@ import ( "time" ) -// defaultAjeanHome est la racine des données quand ni $AJEAN_HOME ni -// /etc/default/ajean n'en imposent une. +// defaultLokiHome est la racine des données quand ni $LOKI_HOME ni +// /etc/default/loki n'en imposent une. // -// 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, +// Linux : /etc/loki (machine de prod, service systemd lancé par root). +// macOS : /etc/loki n'est PAS écrivable par une app lancée depuis le Finder +// (« mkdir /etc/loki: 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 ajean install` a déjà créé /etc/ajean à notre nom. -func defaultAjeanHome() string { return unixHome("ajean") } +// un `sudo loki install` a déjà créé /etc/loki à notre nom. +func defaultLokiHome() string { return unixHome("loki") } func unixHome(name string) string { if runtime.GOOS != "darwin" || os.Geteuid() == 0 { return "/etc/" + name } if isWritableDir("/etc/" + name) { - return "/etc/" + name // installé par `sudo ajean install` puis chown à l'utilisateur + return "/etc/" + name // installé par `sudo loki install` puis chown à l'utilisateur } if home, err := os.UserHomeDir(); err == nil { return filepath.Join(home, "Library", "Application Support", name) @@ -46,7 +46,7 @@ func isWritableDir(dir string) bool { if err != nil || !fi.IsDir() { return false } - probe := filepath.Join(dir, ".ajean-write-test") + probe := filepath.Join(dir, ".loki-write-test") f, err := os.OpenFile(probe, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return false @@ -56,7 +56,7 @@ func isWritableDir(dir string) bool { return true } -// defaultEditor is used by `ajean edit` when $EDITOR is unset. +// defaultEditor is used by `loki edit` when $EDITOR is unset. func defaultEditor() string { return "nano" } // hideCmd : no-op sur Unix (pas de fenêtre de console à masquer). @@ -332,7 +332,7 @@ func ramUsageMB() (used, total int) { // --- Supervision de processus détachés (worker de lien, service en mode // utilisateur). Pendant Unix de sys_platform_windows.go. -// spawnDetached prépare une commande qui survivra à la mort de AJEAN : Setsid la +// spawnDetached prépare une commande qui survivra à la mort de Loki : Setsid la // place dans une nouvelle session, donc elle n'est pas tuée avec notre groupe. func spawnDetached(name string, args ...string) *exec.Cmd { cmd := exec.Command(name, args...) diff --git a/internal/ajean/sys_platform_windows.go b/internal/loki/sys_platform_windows.go similarity index 95% rename from internal/ajean/sys_platform_windows.go rename to internal/loki/sys_platform_windows.go index 64a55b9..1e392bc 100644 --- a/internal/ajean/sys_platform_windows.go +++ b/internal/loki/sys_platform_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "context" @@ -15,7 +15,7 @@ import ( ) // init makes the Windows console behave like a modern terminal: UTF-8 so the -// Unicode glyphs ajean prints (✓ ▶ …) and child-process output don't turn into +// Unicode glyphs loki prints (✓ ▶ …) and child-process output don't turn into // mojibake (the default OEM codepage, e.g. cp850, renders "✓" as "├ö"), and VT // processing so the ANSI colour/cursor escapes (including the build progress // line) are interpreted instead of printed literally. Best-effort: a redirected @@ -43,7 +43,7 @@ func init() { // 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 ajean → ajean est un rename intra-volume (voir sys_migrate.go). +// migration loki → loki est un rename intra-volume (voir sys_migrate.go). func homeRoot() string { if pd := os.Getenv("ProgramData"); pd != "" { return pd @@ -54,10 +54,10 @@ func homeRoot() string { return os.TempDir() } -// defaultAjeanHome est la racine des données quand $AJEAN_HOME est absent. -func defaultAjeanHome() string { return filepath.Join(homeRoot(), "ajean") } +// defaultLokiHome est la racine des données quand $LOKI_HOME est absent. +func defaultLokiHome() string { return filepath.Join(homeRoot(), "loki") } -// defaultEditor is used by `ajean edit` when $EDITOR is unset. +// defaultEditor is used by `loki edit` when $EDITOR is unset. func defaultEditor() string { return "notepad" } // openBrowser ouvre l'URL dans le navigateur par défaut. rundll32 évite les @@ -67,7 +67,7 @@ func openBrowser(url string) error { } // hideCmd empêche une commande externe d'ouvrir une fenêtre de console -// (CREATE_NO_WINDOW). Indispensable quand AJEAN tourne sans console (mode app) : +// (CREATE_NO_WINDOW). Indispensable quand Loki tourne sans console (mode app) : // sinon chaque `nvidia-smi`/`git`/… ferait clignoter une fenêtre noire. Fusionne // avec les flags déjà présents pour ne pas écraser un éventuel détachement. func hideCmd(cmd *exec.Cmd) *exec.Cmd { @@ -144,7 +144,7 @@ func libraryPathEnv(dir string) []string { } // execServer runs llama-server as a child process and waits for it. Windows has -// no exec() that replaces the current image, so `ajean serve` stays alive as the +// no exec() that replaces the current image, so `loki serve` stays alive as the // parent (this is the detached process the service supervisor tracks). // args[0] is the binary path; the rest are its arguments. func execServer(bin string, args []string) error { @@ -246,7 +246,7 @@ func msvcGenerator() string { // ensureCompiler makes sure an MSVC C++ toolchain is available, installing the // Visual Studio 2022 Build Tools (VCTools workload) via winget if not. This is a -// large download but keeps `ajean llamacpp install` fully unattended on a bare +// large download but keeps `loki llamacpp install` fully unattended on a bare // Windows box. func ensureCompiler() error { if msvcInstallVersion() != "" { @@ -368,7 +368,7 @@ Sans elle, CMake échoue sur « No CUDA toolset found ». Pour corriger, au choi 1. relance l'installeur du CUDA Toolkit (installation personnalisée) et coche « CUDA → Visual Studio Integration » — Visual Studio doit déjà être installé à ce moment-là ; 2. ou copie (en admin) les fichiers de %s - vers le dossier BuildCustomizations ci-dessus, puis relance ajean llamacpp install`, installPath, src) + vers le dossier BuildCustomizations ci-dessus, puis relance loki llamacpp install`, installPath, src) } // cudaPathEnv returns the CUDA toolkit env vars the MSBuild CUDA integration @@ -386,7 +386,7 @@ func cudaPathEnv(toolkitDir string) []string { } // newShellCmd builds the command used by the run_shell tool. hideCmd évite un -// flash de console à chaque commande d'agent quand AJEAN tourne en mode app. +// flash de console à chaque commande d'agent quand Loki tourne en mode app. func newShellCmd(ctx context.Context, command string) *exec.Cmd { return hideCmd(exec.CommandContext(ctx, "cmd", "/C", command)) } diff --git a/internal/ajean/sys_restart_other.go b/internal/loki/sys_restart_other.go similarity index 96% rename from internal/ajean/sys_restart_other.go rename to internal/loki/sys_restart_other.go index 7a134b6..b7052e8 100644 --- a/internal/ajean/sys_restart_other.go +++ b/internal/loki/sys_restart_other.go @@ -1,6 +1,6 @@ //go:build !windows -package ajean +package loki // Pendant non-Windows de sys_restart_windows.go : sous Linux le redemarrage // passe par systemd (restartAfterUpdate), qui sait deja relancer proprement un diff --git a/internal/ajean/sys_restart_windows.go b/internal/loki/sys_restart_windows.go similarity index 95% rename from internal/ajean/sys_restart_windows.go rename to internal/loki/sys_restart_windows.go index 57f127d..6fa735b 100644 --- a/internal/ajean/sys_restart_windows.go +++ b/internal/loki/sys_restart_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "fmt" @@ -17,7 +17,7 @@ import ( // lancée avec une sous-commande interne — qui attend notre disparition, profite // de cette fenêtre pour terminer la migration, puis relance l'application. // -// C'est cette fenêtre qui manquait. Une mise à jour en place laisse AJEAN +// C'est cette fenêtre qui manquait. Une mise à jour en place laisse Loki // tourner, et donc le dossier tenu : le renommage échouait avec « Accès refusé », // qui sous Windows ne distingue pas un verrou d'un manque de droits. D'où des // postes qui restaient indéfiniment sur l'ancien nom. @@ -44,7 +44,7 @@ func scheduleAppRestart() (bool, string) { time.Sleep(1500 * time.Millisecond) // laisser la réponse atteindre le navigateur os.Exit(0) }() - return true, "AJEAN redémarre — la page se reconnectera toute seule dans quelques secondes." + return true, "Loki redémarre — la page se reconnectera toute seule dans quelques secondes." } // cmdRestartAfterUpdate est exécuté par l'accompagnateur détaché. diff --git a/internal/ajean/sys_service.go b/internal/loki/sys_service.go similarity index 88% rename from internal/ajean/sys_service.go rename to internal/loki/sys_service.go index 23e6511..9a8e80f 100644 --- a/internal/ajean/sys_service.go +++ b/internal/loki/sys_service.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "fmt" @@ -19,33 +19,33 @@ import ( // preflightEngine vérifie ce sans quoi le moteur ne PEUT pas démarrer, avant de // lancer le service. Sinon llama-server sortait en erreur, systemd le relançait -// toutes les 3 s, et `ajean start` affichait un « activating » rassurant pendant -// que `ajean test` répondait « /health ne répond pas ». Le diagnostic n'était +// toutes les 3 s, et `loki start` affichait un « activating » rassurant pendant +// que `loki test` répondait « /health ne répond pas ». Le diagnostic n'était // visible que dans le journal. func preflightEngine() error { cfg := ReadConfig() bin := strings.TrimSpace(cfg["BIN"]) if bin == "" { return fmt.Errorf("BIN non défini — installe un moteur : %s (ou renseigne BIN avec %s)", - bold("ajean llamacpp install"), bold("ajean edit")) + bold("loki llamacpp install"), bold("loki edit")) } if !filepath.IsAbs(bin) { - bin = filepath.Join(AjeanHome(), bin) + bin = filepath.Join(LokiHome(), bin) } if _, err := os.Stat(prebuiltResolveBin(bin)); err != nil { - return fmt.Errorf("moteur introuvable : %s — relance %s", bin, bold("ajean llamacpp install")) + return fmt.Errorf("moteur introuvable : %s — relance %s", bin, bold("loki llamacpp install")) } model := strings.TrimSpace(cfg["MODEL"]) if model == "" { return fmt.Errorf("MODEL non défini — indique un .gguf avec %s (dossier des modèles : %s)", - bold("ajean edit"), modelsDir()) + bold("loki edit"), modelsDir()) } p, err := resolveServeModelPath(model) if err != nil { return fmt.Errorf("MODEL=%s : %w", model, err) } if _, err := os.Stat(p); err != nil { - return fmt.Errorf("modèle introuvable : %s — corrige MODEL avec %s", p, bold("ajean edit")) + return fmt.Errorf("modèle introuvable : %s — corrige MODEL avec %s", p, bold("loki edit")) } // Modèle en plusieurs fichiers : une tranche manquante ne se voit qu'au moment // où llama-server réclame un tenseur absent, dans le journal du service. @@ -56,13 +56,13 @@ func preflightEngine() error { return nil } -// configTemplate est le squelette commenté proposé par `ajean edit` : la +// configTemplate est le squelette commenté proposé par `loki edit` : la // configuration vit en base, donc sur une installation neuve le fichier // temporaire était QUASI VIDE — impossible de deviner quoi écrire. On y déroule // donc les clés utiles avec leur rôle, les valeurs déjà définies telles quelles, // les autres commentées. var configTemplate = []struct{ key, help string }{ - {"BIN", "chemin de llama-server (posé par « ajean llamacpp install »)"}, + {"BIN", "chemin de llama-server (posé par « loki llamacpp install »)"}, {"MODEL", "nom de fichier .gguf ou chemin complet"}, {"HOST", "adresse d'écoute du moteur (défaut 0.0.0.0)"}, {"PORT", "port du moteur (défaut 8080)"}, @@ -83,9 +83,9 @@ var configTemplate = []struct{ key, help string }{ // configEditorText rend la configuration au format présenté dans $EDITOR. func configEditorText(cfg map[string]string) string { var b strings.Builder - b.WriteString("# Configuration du moteur AJEAN (ajean-engine).\n") + b.WriteString("# Configuration du moteur Loki (loki-engine).\n") b.WriteString("# Une clé par ligne : CLE=valeur. Les lignes commentées (#) sont ignorées :\n") - b.WriteString("# décommente celles dont tu as besoin. « ajean restart » applique.\n\n") + b.WriteString("# décommente celles dont tu as besoin. « loki restart » applique.\n\n") seen := map[string]bool{} for _, f := range configTemplate { seen[f.key] = true @@ -120,7 +120,7 @@ func editConfig() error { if editor == "" { editor = defaultEditor() } - tmp, err := os.CreateTemp("", "ajean-config-*.env") + tmp, err := os.CreateTemp("", "loki-config-*.env") if err != nil { return err } @@ -144,7 +144,7 @@ func editConfig() error { if err := WriteConfig(parseEnv(string(b))); err != nil { return err } - fmt.Println(dim("[info] ajean restart pour appliquer")) + fmt.Println(dim("[info] loki restart pour appliquer")) return nil } diff --git a/internal/ajean/sys_service_darwin.go b/internal/loki/sys_service_darwin.go similarity index 83% rename from internal/ajean/sys_service_darwin.go rename to internal/loki/sys_service_darwin.go index 3a5f105..4ee0fbb 100644 --- a/internal/ajean/sys_service_darwin.go +++ b/internal/loki/sys_service_darwin.go @@ -1,6 +1,6 @@ //go:build darwin -package ajean +package loki import ( "fmt" @@ -18,16 +18,16 @@ import ( // le support macOS était absent (le code systemd/Linux était utilisé par erreur, // cf. issue #4). Implémentation prudente basée sur launchctl load/unload/list. -// launchdLabel dérive le label launchd du service (ex. "com.ajean.ajean"), en -// conservant le préfixe hérité « com.ajean. » tant que c'est LUI qui est +// launchdLabel dérive le label launchd du service (ex. "com.loki.loki"), en +// conservant le préfixe hérité « com.loki. » tant que c'est LUI qui est // réellement installé : le plist d'un daemon déjà chargé ne se renomme pas tout // seul, et viser le mauvais label reviendrait à ne plus voir le service. func launchdLabel(svc string) string { - modern := "com.ajean." + svc + modern := "com.loki." + svc if _, err := os.Stat("/Library/LaunchDaemons/" + modern + ".plist"); err == nil { return modern } - if legacy := "com.ajean." + svc; fileExists("/Library/LaunchDaemons/" + legacy + ".plist") { + if legacy := "com.loki." + svc; fileExists("/Library/LaunchDaemons/" + legacy + ".plist") { return legacy } return modern @@ -44,18 +44,18 @@ func launchdPlistPath(svc string) string { return "/Library/LaunchDaemons/" + launchdLabel(svc) + ".plist" } -// launchdLogPath : sortie standard/erreur du service, sous AJEAN_HOME (accessible +// launchdLogPath : sortie standard/erreur du service, sous LOKI_HOME (accessible // en écriture par l'utilisateur du service après le chown de l'installation). -func launchdLogPath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } +func launchdLogPath() string { return filepath.Join(LokiHome(), 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. func serviceAction(action string) error { svc := serviceName() plist := launchdPlistPath(svc) - // Pas de LaunchDaemon installé — cas normal d'un Mac de bureau, où AJEAN tourne - // via AJEAN.app sans jamais passer par `sudo ajean install` : on gère alors le - // service comme sous Windows, avec un `ajean serve` détaché suivi par un fichier + // Pas de LaunchDaemon installé — cas normal d'un Mac de bureau, où Loki tourne + // via Loki.app sans jamais passer par `sudo loki install` : on gère alors le + // service comme sous Windows, avec un `loki serve` détaché suivi par un fichier // PID. Aucun droit root requis. Sans ça, `start` échouait sur un plist absent // et l'UI affichait éternellement « service arrêté ». if _, err := os.Stat(plist); err != nil { @@ -116,7 +116,7 @@ func checkStarted(svc string) error { fmt.Println(strings.Join(lines, "\n")) } fmt.Println("------------------------------------------------") - fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") + fmt.Printf("→ loki logs pour plus de détails\n→ loki edit pour corriger config.env\n") return fmt.Errorf("service %s non démarré", svc) } @@ -143,14 +143,14 @@ func serviceIsActive() bool { } // --------------------------------------------------------------------------- -// Mode utilisateur (sans launchd) — même approche que Windows : `ajean serve` est -// lancé détaché, son PID va dans AJEAN_HOME/.pid et sa sortie dans -// AJEAN_HOME/.log. Setsid le détache de notre session pour qu'il survive à -// la fermeture de AJEAN.app, et permet de tuer tout le groupe (llama-server +// Mode utilisateur (sans launchd) — même approche que Windows : `loki serve` est +// lancé détaché, son PID va dans LOKI_HOME/.pid et sa sortie dans +// LOKI_HOME/.log. Setsid le détache de notre session pour qu'il survive à +// la fermeture de Loki.app, et permet de tuer tout le groupe (llama-server // enfant compris) d'un seul signal. -func pidFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".pid") } -func logFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } +func pidFilePath() string { return filepath.Join(LokiHome(), serviceName()+".pid") } +func logFilePath() string { return filepath.Join(LokiHome(), serviceName()+".log") } func userSvcAction(action string) error { switch action { @@ -173,7 +173,7 @@ func userSvcAction(action string) error { return nil case "enable", "disable": fmt.Printf("%s '%s' réclame un service système : lance %s pour installer le LaunchDaemon.\n", - yellow("[info]"), action, bold("sudo ajean install")) + yellow("[info]"), action, bold("sudo loki install")) return nil } return fmt.Errorf("action inconnue: %s", action) @@ -188,7 +188,7 @@ func userSvcStart() error { if err != nil { return err } - if err := os.MkdirAll(AjeanHome(), 0o755); err != nil { + if err := os.MkdirAll(LokiHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(logFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -200,12 +200,12 @@ func userSvcStart() error { cmd := exec.Command(self, "serve") cmd.Stdout, cmd.Stderr = logf, logf // Même répertoire de travail que sous systemd/launchd : les chemins relatifs - // de config.env (MODEL=…gguf) se résolvent depuis AJEAN_HOME, pas depuis « / » + // de config.env (MODEL=…gguf) se résolvent depuis LOKI_HOME, pas depuis « / » // que nous hérite le Finder. - cmd.Dir = AjeanHome() + cmd.Dir = LokiHome() cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de 'ajean serve': %w", err) + return fmt.Errorf("démarrage de 'loki serve': %w", err) } pid := cmd.Process.Pid if err := os.WriteFile(pidFilePath(), []byte(strconv.Itoa(pid)), 0o644); err != nil { @@ -219,14 +219,14 @@ func userCheckStarted(pid int) error { time.Sleep(2 * time.Second) if processAlive(pid) { fmt.Printf("%s %s: démarré (PID %d)\n", green("[ok]"), serviceName(), pid) - fmt.Printf(" logs: %s (ajean logs pour suivre)\n", dim(logFilePath())) + fmt.Printf(" logs: %s (loki logs pour suivre)\n", dim(logFilePath())) return nil } fmt.Printf("%s %s: le processus s'est arrêté — derniers logs :\n", red("[ERREUR]"), serviceName()) fmt.Println("------------------------------------------------") fmt.Print(tailFile(logFilePath(), 20)) fmt.Println("------------------------------------------------") - fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") + fmt.Printf("→ loki logs pour plus de détails\n→ loki edit pour corriger config.env\n") _ = os.Remove(pidFilePath()) return fmt.Errorf("service %s non démarré", serviceName()) } diff --git a/internal/ajean/sys_service_linux.go b/internal/loki/sys_service_linux.go similarity index 91% rename from internal/ajean/sys_service_linux.go rename to internal/loki/sys_service_linux.go index dbc4c6a..25a2da9 100644 --- a/internal/ajean/sys_service_linux.go +++ b/internal/loki/sys_service_linux.go @@ -1,6 +1,6 @@ //go:build linux -package ajean +package loki import ( "fmt" @@ -65,9 +65,9 @@ func unitState(svc string) (active, sub string) { // // ⚠️ « activating » n'est PAS un succès : avec Restart=on-failure, un moteur qui // meurt au démarrage repasse en boucle par activating (SubState auto-restart). -// L'ancien test l'acceptait, et `ajean start` répondait « [ok] activating » sur -// un service qui ne démarrerait jamais — d'où des rapports « ajean start dit ok -// mais ajean test ne répond pas ». On distingue donc le SubState, et on laisse +// L'ancien test l'acceptait, et `loki start` répondait « [ok] activating » sur +// un service qui ne démarrerait jamais — d'où des rapports « loki start dit ok +// mais loki test ne répond pas ». On distingue donc le SubState, et on laisse // au moteur le temps de charger un gros modèle (il reste en activating, mais // PAS en auto-restart). func checkStarted(svc string) error { @@ -86,7 +86,7 @@ func checkStarted(svc string) error { if state == "activating" && sub != "auto-restart" { // Chargement en cours (un gros .gguf prend des minutes) : légitime. fmt.Printf("%s %s: démarrage en cours (chargement du modèle) — %s pour suivre\n", - green("[ok]"), svc, bold("ajean logs")) + green("[ok]"), svc, bold("loki logs")) return nil } what := state @@ -98,7 +98,7 @@ func checkStarted(svc string) error { logs, _ := exec.Command("journalctl", "-u", svc, "-n", "20", "--no-pager").Output() fmt.Print(string(logs)) fmt.Println("------------------------------------------------") - fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger la configuration (BIN, MODEL…)\n") + fmt.Printf("→ loki logs pour plus de détails\n→ loki edit pour corriger la configuration (BIN, MODEL…)\n") return fmt.Errorf("service %s non démarré", svc) } diff --git a/internal/ajean/sys_service_windows.go b/internal/loki/sys_service_windows.go similarity index 86% rename from internal/ajean/sys_service_windows.go rename to internal/loki/sys_service_windows.go index b725532..e8b1502 100644 --- a/internal/ajean/sys_service_windows.go +++ b/internal/loki/sys_service_windows.go @@ -1,6 +1,6 @@ //go:build windows -package ajean +package loki import ( "fmt" @@ -16,8 +16,8 @@ import ( ) // On Windows there is no systemd, so "the service" is a background copy of -// `ajean serve` that this process launches detached. We track it with a PID file -// and stream its output to a log file under AJEAN_HOME. This needs no admin +// `loki serve` that this process launches detached. We track it with a PID file +// and stream its output to a log file under LOKI_HOME. This needs no admin // rights and no external tools beyond the always-present tasklist/taskkill. const ( @@ -26,8 +26,8 @@ const ( createNoWindow = 0x08000000 // CREATE_NO_WINDOW (aucune console pour l'enfant) ) -func pidFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".pid") } -func logFilePath() string { return filepath.Join(AjeanHome(), serviceName()+".log") } +func pidFilePath() string { return filepath.Join(LokiHome(), serviceName()+".pid") } +func logFilePath() string { return filepath.Join(LokiHome(), 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(AjeanHome(), 0o755); err != nil { + if err := os.MkdirAll(LokiHome(), 0o755); err != nil { return err } logf, err := os.OpenFile(logFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) @@ -71,15 +71,15 @@ func svcStart() error { cmd := exec.Command(self, "serve") cmd.Stdout = logf cmd.Stderr = logf - cmd.Dir = AjeanHome() // les chemins relatifs de config.env se résolvent depuis AJEAN_HOME - // createNoWindow + HideWindow : le service enfant (`ajean serve`) ne doit JAMAIS - // faire clignoter de console noire quand AJEAN est lancé en mode app (double-clic). + cmd.Dir = LokiHome() // les chemins relatifs de config.env se résolvent depuis LOKI_HOME + // createNoWindow + HideWindow : le service enfant (`loki serve`) ne doit JAMAIS + // faire clignoter de console noire quand Loki est lancé en mode app (double-clic). cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: createNewProcessGroup | detachedProcess | createNoWindow, } if err := cmd.Start(); err != nil { - return fmt.Errorf("démarrage de 'ajean serve': %w", err) + return fmt.Errorf("démarrage de 'loki serve': %w", err) } pid := cmd.Process.Pid if err := os.WriteFile(pidFilePath(), []byte(strconv.Itoa(pid)), 0o644); err != nil { @@ -94,14 +94,14 @@ func checkStarted(pid int) error { time.Sleep(2 * time.Second) if processAlive(pid) { fmt.Printf("%s %s: démarré (PID %d)\n", green("[ok]"), serviceName(), pid) - fmt.Printf(" logs: %s (ajean logs pour suivre)\n", dim(logFilePath())) + fmt.Printf(" logs: %s (loki logs pour suivre)\n", dim(logFilePath())) return nil } fmt.Printf("%s %s: le processus s'est arrêté — derniers logs :\n", red("[ERREUR]"), serviceName()) fmt.Println("------------------------------------------------") fmt.Print(tailFile(logFilePath(), 20)) fmt.Println("------------------------------------------------") - fmt.Printf("→ ajean logs pour plus de détails\n→ ajean edit pour corriger config.env\n") + fmt.Printf("→ loki logs pour plus de détails\n→ loki edit pour corriger config.env\n") _ = os.Remove(pidFilePath()) return fmt.Errorf("service %s non démarré", serviceName()) } diff --git a/internal/ajean/sys_splash_other.go b/internal/loki/sys_splash_other.go similarity index 94% rename from internal/ajean/sys_splash_other.go rename to internal/loki/sys_splash_other.go index f91f5bc..8e8368a 100644 --- a/internal/ajean/sys_splash_other.go +++ b/internal/loki/sys_splash_other.go @@ -1,6 +1,6 @@ //go:build !windows -package ajean +package loki // sys_splash_other.go — pas de fenêtre de démarrage hors Windows (no-op). diff --git a/internal/ajean/sys_splash_windows.go b/internal/loki/sys_splash_windows.go similarity index 97% rename from internal/ajean/sys_splash_windows.go rename to internal/loki/sys_splash_windows.go index 9ef2e41..e7e1aad 100644 --- a/internal/ajean/sys_splash_windows.go +++ b/internal/loki/sys_splash_windows.go @@ -1,8 +1,8 @@ //go:build windows -package ajean +package loki -// sys_splash_windows.go — écran de démarrage « Lancement de AJEAN… ». +// sys_splash_windows.go — écran de démarrage « Lancement de Loki… ». // // Petite fenêtre sans bordure, à coins arrondis, aux couleurs de la marque // (fond bleu #1f6feb, logo « j » blanc, texte blanc), affichée le temps que le @@ -127,7 +127,7 @@ func (s *splash) close() { } func createSplashWindow() uintptr { - className, _ := syscall.UTF16PtrFromString("AJEANSplash") + className, _ := syscall.UTF16PtrFromString("LokiSplash") hInst, _, _ := pGetModuleHandleW.Call(0) splashClassOnce.Do(func() { @@ -154,7 +154,7 @@ func createSplashWindow() uintptr { cy, _, _ := pGetSystemMetrics.Call(smCY) x := (int(cx) - splashW) / 2 y := (int(cy) - splashH) / 2 - title, _ := syscall.UTF16PtrFromString("AJEAN") + title, _ := syscall.UTF16PtrFromString("Loki") hwnd, _, _ := pCreateWindowExW.Call( uintptr(exTopmost|exToolwindow), @@ -243,7 +243,7 @@ func paintSplash(hdc uintptr) { pSetTextColor.Call(hdc, colWhite) textX := ox + logo + 26 - drawText(hdc, "AJEAN", -30, 700, rect{textX, 40, splashW - 20, 82}) + drawText(hdc, "Loki", -30, 700, rect{textX, 40, splashW - 20, 82}) drawText(hdc, "Lancement en cours…", -17, 400, rect{textX, 84, splashW - 20, 118}) } diff --git a/internal/ajean/sys_tray_darwin.go b/internal/loki/sys_tray_darwin.go similarity index 79% rename from internal/ajean/sys_tray_darwin.go rename to internal/loki/sys_tray_darwin.go index 4ffe57f..2b69504 100644 --- a/internal/ajean/sys_tray_darwin.go +++ b/internal/loki/sys_tray_darwin.go @@ -1,11 +1,11 @@ //go:build darwin -package ajean +package loki -// sys_tray_darwin.go — icône de AJEAN dans la barre de menus macOS (en haut à +// sys_tray_darwin.go — icône de Loki dans la barre de menus macOS (en haut à // droite, à côté de l'horloge), pendant exact du system tray Windows : quand on -// ouvre AJEAN.app, l'UI web s'ouvre dans le navigateur ET l'icône apparaît, avec -// le menu « Ouvrir AJEAN » / « Quitter ». +// ouvre Loki.app, l'UI web s'ouvre dans le navigateur ET l'icône apparaît, avec +// le menu « Ouvrir Loki » / « Quitter ». // // Contrairement à Windows, systray passe ici par Cocoa → CGO obligatoire : les // binaires macOS sont donc compilés sur un runner macOS (voir release.yml), et @@ -26,10 +26,10 @@ func runTray(url string) { // colore selon le thème. Indispensable depuis que la marque est noire — // une icône noire « en dur » est illisible sur une barre de menus sombre. systray.SetTemplateIcon(brandTemplatePNG(trayIconSize), BrandIconPNG(trayIconSize)) - systray.SetTooltip("AJEAN — votre IA locale") - mOpen := systray.AddMenuItem("Ouvrir AJEAN", "Ouvrir l'interface") + systray.SetTooltip("Loki — votre IA locale") + mOpen := systray.AddMenuItem("Ouvrir Loki", "Ouvrir l'interface") systray.AddSeparator() - mQuit := systray.AddMenuItem("Quitter", "Arrêter AJEAN") + mQuit := systray.AddMenuItem("Quitter", "Arrêter Loki") go func() { for { diff --git a/internal/ajean/sys_tray_other.go b/internal/loki/sys_tray_other.go similarity index 97% rename from internal/ajean/sys_tray_other.go rename to internal/loki/sys_tray_other.go index d1992c8..5fae370 100644 --- a/internal/ajean/sys_tray_other.go +++ b/internal/loki/sys_tray_other.go @@ -1,6 +1,6 @@ //go:build !windows && !darwin -package ajean +package loki // 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/ajean/sys_tray_windows.go b/internal/loki/sys_tray_windows.go similarity index 78% rename from internal/ajean/sys_tray_windows.go rename to internal/loki/sys_tray_windows.go index 0456d26..eae5fc6 100644 --- a/internal/ajean/sys_tray_windows.go +++ b/internal/loki/sys_tray_windows.go @@ -1,11 +1,11 @@ //go:build windows -package ajean +package loki -// sys_tray_windows.go — icône de AJEAN dans la zone de notification Windows (system -// tray). Quand on lance l'app (double-clic ou `ajean app`), l'UI web s'ouvre dans +// sys_tray_windows.go — icône de Loki dans la zone de notification Windows (system +// tray). Quand on lance l'app (double-clic ou `loki app`), l'UI web s'ouvre dans // le navigateur ET une petite icône apparaît près de l'horloge : elle montre que -// AJEAN tourne et offre un menu « Ouvrir AJEAN » / « Quitter ». +// Loki tourne et offre un menu « Ouvrir Loki » / « Quitter ». // // getlantern/systray est pur Go sur Windows (Win32 via x/sys, aucun CGO) — le // binaire reste statique. L'équivalent macOS vit dans sys_tray_darwin.go (Cocoa, @@ -22,11 +22,11 @@ import ( func runTray(url string) { systray.Run(func() { systray.SetIcon(trayIcon()) - systray.SetTitle("AJEAN") - systray.SetTooltip("AJEAN — votre IA locale") - mOpen := systray.AddMenuItem("Ouvrir AJEAN", "Ouvrir l'interface") + systray.SetTitle("Loki") + systray.SetTooltip("Loki — votre IA locale") + mOpen := systray.AddMenuItem("Ouvrir Loki", "Ouvrir l'interface") systray.AddSeparator() - mQuit := systray.AddMenuItem("Quitter", "Arrêter AJEAN et décharger le modèle") + mQuit := systray.AddMenuItem("Quitter", "Arrêter Loki et décharger le modèle") go func() { for { diff --git a/internal/ajean/sys_tty.go b/internal/loki/sys_tty.go similarity index 94% rename from internal/ajean/sys_tty.go rename to internal/loki/sys_tty.go index d3d6329..2839f7a 100644 --- a/internal/ajean/sys_tty.go +++ b/internal/loki/sys_tty.go @@ -1,4 +1,4 @@ -package ajean +package loki import "os" diff --git a/internal/ajean/sys_update.go b/internal/loki/sys_update.go similarity index 89% rename from internal/ajean/sys_update.go rename to internal/loki/sys_update.go index aa7b8e0..7147ffb 100644 --- a/internal/ajean/sys_update.go +++ b/internal/loki/sys_update.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "crypto/sha256" @@ -18,12 +18,12 @@ import ( "golang.org/x/mod/semver" ) -// ajean update — met à jour le binaire depuis les releases GitHub du projet. +// loki update — met à jour le binaire depuis les releases GitHub du projet. // Périmètre minimal : compare la version, télécharge l'asset correspondant à // l'OS/arch courant, remplace le binaire en place, puis affiche quoi redémarrer. // AUCUN redémarrage de service automatique (choix volontaire, plus sûr). -const updateRepo = "nathaninline/ajean" +const updateRepo = "R0m1k3/Loki" type ghRelease struct { TagName string `json:"tag_name"` @@ -36,12 +36,12 @@ type ghRelease struct { } // updateAssetName est le nom de l'asset de release pour la plateforme courante : -// ajean-linux, ajean-linux-arm, ajean-macos, ajean-macos-arm, ajean-windows.exe, -// ajean-windows-arm.exe. Un seul nom publié, un seul nom cherché. +// loki-linux, loki-linux-arm, loki-macos, loki-macos-arm, loki-windows.exe, +// loki-windows-arm.exe. Un seul nom publié, un seul nom cherché. // // Ces noms sont volontairement lisibles — « macos » plutôt que « darwin », pas // de « amd64 » pour le cas courant — et volontairement DIFFÉRENTS du schéma -// ajean-- qu'utilisaient les versions 0.7 : leur mise à jour +// loki-- qu'utilisaient les versions 0.7 : leur mise à jour // automatique ne trouve donc aucun asset et échoue sans rien remplacer, au lieu // d'installer un binaire 0.8 sur une machine encore agencée en 0.7 — ce qui // laissait le service de lien en boucle d'échec. La 0.7 se met à jour à la main, @@ -51,7 +51,7 @@ func updateAssetName() string { return assetNameFor(runtime.GOOS, runtime.GOARCH // assetNameFor est séparée pour être vérifiable sur les six plateformes, et pas // seulement sur celle qui exécute les tests. func assetNameFor(goos, goarch string) string { - name := "ajean-" + map[string]string{ + name := "loki-" + map[string]string{ "darwin": "macos", "linux": "linux", "windows": "windows", @@ -89,7 +89,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", "ajean-update") + req.Header.Set("User-Agent", "loki-update") resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req) if err != nil { return nil, err @@ -114,7 +114,7 @@ type updateInfo struct { } // checkForUpdate interroge GitHub et compare à la version courante. Réutilisé -// par `ajean update` (CLI) et par l'endpoint web /api/update. +// par `loki update` (CLI) et par l'endpoint web /api/update. func checkForUpdate() (updateInfo, error) { info := updateInfo{Current: Version} rel, err := fetchLatestRelease() @@ -162,12 +162,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/.ajean-update.tmp: permission denied » incompréhensible, - // notamment depuis le bouton de l'UI web (ajean web lancé sans sudo). + // /usr/local/bin/.loki-update.tmp: permission denied » incompréhensible, + // notamment depuis le bouton de l'UI web (loki web lancé sans sudo). if err := checkUpdateWritable(exe); err != nil { return "", err } - tmp := filepath.Join(filepath.Dir(exe), ".ajean-update.tmp") + tmp := filepath.Join(filepath.Dir(exe), ".loki-update.tmp") if err := downloadTo(url, tmp); err != nil { os.Remove(tmp) @@ -202,16 +202,16 @@ func applyUpdate() (string, error) { return strings.TrimPrefix(latest, "v"), nil } -// updatePermissionError formule le seul message utile quand ajean n'a pas les +// updatePermissionError formule le seul message utile quand loki n'a pas les // droits d'écrire son propre binaire : la commande exacte à lancer. func updatePermissionError(exe string) error { if runtime.GOOS == "windows" { // Windows renvoie le MÊME code (5, accès refusé) pour « droits // insuffisants » et pour « fichier utilisé par un processus » : on nomme // les deux causes au lieu d'affirmer la mauvaise. - return fmt.Errorf("impossible de remplacer %s : soit un autre AJEAN utilise ce fichier (ferme l'application et arrête le service, puis réessaie), soit les droits manquent (relance AJEAN en administrateur)", exe) + return fmt.Errorf("impossible de remplacer %s : soit un autre Loki utilise ce fichier (ferme l'application et arrête le service, puis réessaie), soit les droits manquent (relance Loki en administrateur)", exe) } - return fmt.Errorf("droits insuffisants pour remplacer %s (le binaire appartient à root) — lance la mise à jour en ligne de commande : sudo ajean update", exe) + return fmt.Errorf("droits insuffisants pour remplacer %s (le binaire appartient à root) — lance la mise à jour en ligne de commande : sudo loki update", exe) } // checkUpdateWritable vérifie qu'on peut écrire dans le dossier du binaire, en @@ -219,7 +219,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, ".ajean-update-probe-*") + probe, err := os.CreateTemp(dir, ".loki-update-probe-*") if err != nil { if os.IsPermission(err) { return updatePermissionError(exe) @@ -245,13 +245,13 @@ func cmdUpdate(args []string) error { return fmt.Errorf("impossible de contacter GitHub : %w", err) } if !info.Available { - fmt.Printf("ajean est déjà à jour (%s).\n", Version) + fmt.Printf("loki est déjà à jour (%s).\n", Version) return nil } fmt.Printf("nouvelle version disponible : %s (actuelle : %s)\n", info.Latest, Version) fmt.Printf(" %s\n", info.URL) if checkOnly { - fmt.Println("lance 'ajean update' pour l'installer.") + fmt.Println("lance 'loki update' pour l'installer.") return nil } fmt.Printf("téléchargement de %s…\n", updateAssetName()) @@ -259,14 +259,14 @@ func cmdUpdate(args []string) error { if err != nil { return err } - fmt.Printf("✓ ajean mis à jour en %s\n", newVer) + fmt.Printf("✓ loki mis à jour en %s\n", newVer) printRestartHint() return nil } func downloadTo(url, dst string) error { req, _ := http.NewRequest("GET", url, nil) - req.Header.Set("User-Agent", "ajean-update") + req.Header.Set("User-Agent", "loki-update") req.Header.Set("Accept", "application/octet-stream") resp, err := (&http.Client{Timeout: 5 * time.Minute}).Do(req) if err != nil { @@ -303,7 +303,7 @@ func verifyChecksum(rel *ghRelease, assetName, path string) error { return nil } req, _ := http.NewRequest("GET", sumsURL, nil) - req.Header.Set("User-Agent", "ajean-update") + req.Header.Set("User-Agent", "loki-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) @@ -377,7 +377,7 @@ func replaceBinary(exe, tmp string) error { // ENCORE depuis le .old d'une mise à jour précédente, ce fichier ne peut être ni // supprimé ni écrasé. Le renommage échoue alors avec « Accès refusé » (errno 5), // que os.IsPermission rapporte comme un problème de droits — d'où le message -// « relance AJEAN en administrateur », un conseil FAUX : aucun privilège ne +// « relance Loki en administrateur », un conseil FAUX : aucun privilège ne // permet de remplacer l'image d'un exécutable en cours d'exécution. Reproduit // puis vérifié corrigé sur banc d'essai, deux processus vivants sur les deux // noms : avec un suffixe unique le renommage passe. @@ -419,7 +419,7 @@ func cleanupOldBinary() { removeOldBinaries(exe) // L'alias herite laisse le meme reliquat quand il etait en cours d'execution // au moment ou on l'a remplace (voir replaceExe). - removeOldBinaries(filepath.Join(filepath.Dir(exe), "ajean.exe")) + removeOldBinaries(filepath.Join(filepath.Dir(exe), "loki.exe")) } // handleUpdateCheck (GET /api/update) : renvoie l'état de mise à jour pour l'UI. @@ -434,7 +434,7 @@ func handleUpdateCheck(w http.ResponseWriter, r *http.Request) { // handleUpdateApply (POST /api/update/apply) : télécharge et installe la dernière // version. Sur un serveur Linux/systemd, relance ensuite AUTOMATIQUEMENT le service -// d'UI (ajean-ui : il sert l'UI, le chat et le tunnel) pour appliquer la MAJ sans +// d'UI (loki-ui : il sert l'UI, le chat et le tunnel) pour appliquer la MAJ sans // avoir à SSH — voir restartAfterUpdate. Renvoie la version + le message de statut. func handleUpdateApply(w http.ResponseWriter, r *http.Request) { newVer, err := applyUpdate() @@ -450,10 +450,10 @@ func handleUpdateApply(w http.ResponseWriter, r *http.Request) { sendJSON(w, 200, map[string]any{"ok": true, "version": newVer, "restart": msg, "restarting": restarting}) } -// restartAfterUpdate relance le service d'UI (uiServiceName, « ajean-ui » : il sert +// restartAfterUpdate relance le service d'UI (uiServiceName, « loki-ui » : il sert // l'interface, le chat et le tunnel) juste après une MAJ déclenchée depuis l'UI, pour // éviter le SSH manuel. Conditions : Linux/systemd + service actif. On NE touche PAS à -// ajean-engine (llama-server) pour ne pas recharger le modèle (long et inattendu depuis +// loki-engine (llama-server) pour ne pas recharger le modèle (long et inattendu depuis // un bouton « mettre à jour »). Le redémarrage est DIFFÉRÉ (la réponse HTTP doit partir // d'abord) et lancé en --no-block : systemd enregistre le job puis nous arrête/relance ; // la clé E2E (.e2e_key) survit au restart, donc pas de ré-appairage et l'UI se reconnecte @@ -474,7 +474,7 @@ func restartAfterUpdate() (bool, string) { time.Sleep(1500 * time.Millisecond) // laisser la réponse HTTP atteindre le client // Le service tourne souvent en User=nathan (pas root) : systemctl brut // échouerait alors (polkit). On repli sur « sudo -n systemctl » comme - // uiServiceCtl, les sudoers /etc/sudoers.d/ajean-ui autorisant le restart. + // uiServiceCtl, les sudoers /etc/sudoers.d/loki-ui autorisant le restart. // --no-block : on enregistre le job puis on rend la main ; systemd exécute le // stop/start même si ce process (et le client systemctl) sont tués entre-temps. bin, args := "systemctl", []string{"--no-block", "restart", uiServiceName()} @@ -488,7 +488,7 @@ func restartAfterUpdate() (bool, string) { func restartHintText() string { if runtime.GOOS == "windows" { - return "Redémarre AJEAN (quitte puis relance) pour appliquer la mise à jour." + return "Redémarre Loki (quitte puis relance) pour appliquer la mise à jour." } return "Redémarre pour appliquer : sudo systemctl restart " + uiServiceName() + " (ajoute " + serviceName() + " si la mise à jour touche le moteur)." diff --git a/internal/ajean/sys_update_aside_test.go b/internal/loki/sys_update_aside_test.go similarity index 89% rename from internal/ajean/sys_update_aside_test.go rename to internal/loki/sys_update_aside_test.go index b40b81d..c21f4bb 100644 --- a/internal/ajean/sys_update_aside_test.go +++ b/internal/loki/sys_update_aside_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "os" @@ -10,11 +10,11 @@ import ( // Le remplacement du binaire doit fonctionner MÊME si un écartement précédent // est toujours là et impossible à supprimer (processus encore vivant dessus). // Avec un nom fixe « .old », le renommage échouait alors en « Accès refusé », -// que ajean traduisait par « droits insuffisants, relance en administrateur » : +// que loki traduisait par « droits insuffisants, relance en administrateur » : // un conseil faux, aucun privilège ne remplace une image en cours d'exécution. func TestRenameAsideNeBloquePasSurUnAncienEcartement(t *testing.T) { dir := t.TempDir() - exe := filepath.Join(dir, "ajean.exe") + exe := filepath.Join(dir, "loki.exe") if err := os.WriteFile(exe, []byte("v1"), 0o755); err != nil { t.Fatal(err) } @@ -29,7 +29,7 @@ func TestRenameAsideNeBloquePasSurUnAncienEcartement(t *testing.T) { if aside == exe+".old" { t.Error("nom d'écartement fixe : c'est précisément ce qui bloquait") } - if !strings.HasPrefix(filepath.Base(aside), "ajean.exe.old-") { + if !strings.HasPrefix(filepath.Base(aside), "loki.exe.old-") { t.Errorf("nom inattendu : %s", aside) } if _, err := os.Stat(exe); !os.IsNotExist(err) { @@ -51,7 +51,7 @@ func TestRenameAsideNeBloquePasSurUnAncienEcartement(t *testing.T) { // Le ménage doit ramasser l'ancien nom fixe ET les noms uniques. func TestRemoveOldBinaries(t *testing.T) { dir := t.TempDir() - exe := filepath.Join(dir, "ajean.exe") + exe := filepath.Join(dir, "loki.exe") for _, n := range []string{exe + ".old", exe + ".old-1", exe + ".old-2"} { if err := os.WriteFile(n, []byte("x"), 0o644); err != nil { t.Fatal(err) diff --git a/internal/ajean/sys_update_asset_test.go b/internal/loki/sys_update_asset_test.go similarity index 75% rename from internal/ajean/sys_update_asset_test.go rename to internal/loki/sys_update_asset_test.go index ce87a7a..a00946a 100644 --- a/internal/ajean/sys_update_asset_test.go +++ b/internal/loki/sys_update_asset_test.go @@ -1,4 +1,4 @@ -package ajean +package loki import ( "runtime" @@ -22,7 +22,7 @@ func relWith(names ...string) *ghRelease { // est bien celui qui servira à vérifier le SHA-256. func TestPickAssetTrouveSaPlateforme(t *testing.T) { want := updateAssetName() - got, url, size := pickAsset(relWith("SHA256SUMS", "ajean-plan9-386", want)) + got, url, size := pickAsset(relWith("SHA256SUMS", "loki-plan9-386", want)) if got != want { t.Fatalf("got %q, attendu %q", got, want) } @@ -33,16 +33,16 @@ func TestPickAssetTrouveSaPlateforme(t *testing.T) { // Aucun asset pour cette plateforme → pas d'URL, l'appelant doit pouvoir le voir. func TestPickAssetSansCorrespondance(t *testing.T) { - if _, url, _ := pickAsset(relWith("ajean-plan9-386", "SHA256SUMS")); url != "" { + if _, url, _ := pickAsset(relWith("loki-plan9-386", "SHA256SUMS")); url != "" { t.Fatalf("URL inattendue: %q", url) } } -// Le schéma de nommage ne doit PAS coïncider avec ajean-- : c'est +// Le schéma de nommage ne doit PAS coïncider avec loki-- : c'est // ce qui empêche la mise à jour automatique d'une 0.7 de s'installer sur une // machine encore agencée en 0.7. func TestNomDAssetDistinctDuSchema07(t *testing.T) { - legacy := "ajean-" + runtime.GOOS + "-" + runtime.GOARCH + legacy := "loki-" + runtime.GOOS + "-" + runtime.GOARCH if runtime.GOOS == "windows" { legacy += ".exe" } @@ -54,12 +54,12 @@ func TestNomDAssetDistinctDuSchema07(t *testing.T) { // Les six noms publiés par la release, vérifiés un par un. func TestNomsDAssetParPlateforme(t *testing.T) { for _, c := range []struct{ goos, goarch, want string }{ - {"linux", "amd64", "ajean-linux"}, - {"linux", "arm64", "ajean-linux-arm"}, - {"darwin", "amd64", "ajean-macos"}, - {"darwin", "arm64", "ajean-macos-arm"}, - {"windows", "amd64", "ajean-windows.exe"}, - {"windows", "arm64", "ajean-windows-arm.exe"}, + {"linux", "amd64", "loki-linux"}, + {"linux", "arm64", "loki-linux-arm"}, + {"darwin", "amd64", "loki-macos"}, + {"darwin", "arm64", "loki-macos-arm"}, + {"windows", "amd64", "loki-windows.exe"}, + {"windows", "arm64", "loki-windows-arm.exe"}, } { if got := assetNameFor(c.goos, c.goarch); got != c.want { t.Errorf("%s/%s → %q, attendu %q", c.goos, c.goarch, got, c.want) diff --git a/internal/ajean/ui/index.html b/internal/loki/ui/index.html similarity index 98% rename from internal/ajean/ui/index.html rename to internal/loki/ui/index.html index 364c8dc..5645774 100644 --- a/internal/ajean/ui/index.html +++ b/internal/loki/ui/index.html @@ -1,5 +1,5 @@ - + @@ -22,7 +22,7 @@ garde `display-mode` en second pour les PWA Android/desktop à manifeste. --> - + @@ -34,7 +34,7 @@
-

+

…
@@ -180,7 +180,7 @@ trois îlots pour un même réglage. -->
-
Adresse locale (réseau) — modèle ajean
+
Adresse locale (réseau) — modèle loki
@@ -216,7 +216,7 @@
Accès distant -
ajean.link?Accédez à ce serveur AJEAN depuis n'importe où, sans ouvrir de port, à travers le relais ajean.link. Le trafic est chiffré de bout en bout : le relais ne voit rien. Abonnement 4,80 €/mois.
+
ajean.link?Accédez à ce serveur Loki depuis n'importe où, sans ouvrir de port, à travers le relais ajean.link. Le trafic est chiffré de bout en bout : le relais ne voit rien. Abonnement 4,80 €/mois.

Ouvrez l'accès distant à ce serveur en quelques clics, sans terminal.

@@ -291,7 +291,7 @@
Actions
@@ -570,13 +570,13 @@
- +