#!/bin/sh # SeibertGPT – Einrichtung für pi (macOS). Quelle: https://gpt.seibert.ai/clients # Der Key wird nur lokal abgefragt und landet im Schlüsselbund, nie in einer Datei; gesendet wird er nur an das Gateway. set -eu say() { printf '%s\n' "$*"; } our_config() { cat <<'SGPT_CONFIG_EOF' { "providers": { "seibertgpt": { "baseUrl": "https://api.gpt.seibert.ai/v1", "api": "openai-completions", "apiKey": "!security find-generic-password -ws seibertgpt", "compat": { "supportsStore": false, "supportsDeveloperRole": false, "maxTokensField": "max_tokens" }, "models": [ { "id": "deepseek-v41-flash", "name": "Standard mit Denkmodus (SeibertGPT)", "contextWindow": 1000000, "input": [ "text", "image" ] }, { "id": "deepseek-v41-flash-fast", "name": "ohne Denkmodus – schnell (SeibertGPT)", "contextWindow": 1000000, "input": [ "text", "image" ] }, { "id": "deepseek-v41-flash-low", "name": "Denkaufwand niedrig (SeibertGPT)", "contextWindow": 1000000, "input": [ "text", "image" ] }, { "id": "deepseek-v41-flash-high", "name": "Denkaufwand hoch (SeibertGPT)", "contextWindow": 1000000, "input": [ "text", "image" ] }, { "id": "deepseek-v41-flash-max", "name": "Denkaufwand maximal (SeibertGPT)", "contextWindow": 1000000, "input": [ "text", "image" ] } ] } } } SGPT_CONFIG_EOF } merge_jxa() { cat <<'SGPT_JXA_EOF' ObjC.import("Foundation"); function run(argv) { var target = argv[0], ours = JSON.parse(argv[1]), merge = JSON.parse(argv[2]), absent = JSON.parse(argv[3]); var fm = $.NSFileManager.defaultManager; function write(path, obj) { var ok = $(JSON.stringify(obj, null, 2) + "\n").writeToFileAtomicallyEncodingError(path, true, $.NSUTF8StringEncoding, null); if (!ok) throw new Error("Konnte " + path + " nicht schreiben."); } function get(o, p) { for (var i = 0; i < p.length; i++) { if (o == null || typeof o !== "object") return undefined; o = o[p[i]]; } return o; } function set(o, p, v) { for (var i = 0; i < p.length - 1; i++) { if (typeof o[p[i]] !== "object" || o[p[i]] === null) o[p[i]] = {}; o = o[p[i]]; } o[p[p.length - 1]] = v; } if (!fm.fileExistsAtPath(target)) { write(target, ours); return "created"; } var text = null; try { text = $.NSString.stringWithContentsOfFileEncodingError(target, $.NSUTF8StringEncoding, null).js; } catch (e) { text = null; } if (typeof text === "string" && text.trim() === "") { write(target, ours); return "created"; } var existing; try { existing = JSON.parse(text); } catch (e) { existing = null; } if (existing === null || typeof existing !== "object" || Array.isArray(existing)) { write(target.replace(/\.jsonc?$/, "") + ".seibertgpt.json", ours); return "sidecar"; } var stamp = new Date().toISOString().replace(/[-:]/g, "").replace("T", "-").slice(0, 15); var backup = target + ".bak-" + stamp; if (!fm.copyItemAtPathToPathError(target, backup, null) && !fm.fileExistsAtPath(backup)) { throw new Error("Konnte keine Sicherung von " + target + " anlegen."); } merge.forEach(function (p) { var v = get(ours, p); if (v !== undefined) set(existing, p, v); }); absent.forEach(function (p) { var v = get(ours, p); if (v !== undefined && get(existing, p) === undefined) set(existing, p, v); }); write(target, existing); return "merged"; } SGPT_JXA_EOF } main() { say 'SeibertGPT: Einrichtung für pi' if ! { command -v pi >/dev/null 2>&1; }; then say 'pi scheint nicht installiert zu sein. Download: https://nodejs.org/' printf '%s' 'Trotzdem fortfahren? (j/N) '; read -r ans /dev/null || true' EXIT trap 'exit 130' INT TERM while :; do # Echo vor dem Prompt abschalten, sonst erscheint ein sofort eingefügter Key doch im Terminal. stty -echo (oder /model in pi).' say 'Fertig.' } main "$@"