+ Drei Kanäle, über die Claude Code zusätzlich zur eigentlichen
+ Anfrage Daten verschickt. Gesetzt wird in ~/.claude/settings.json —
+ greift bei jeder neuen Anfrage, kein Neustart nötig.
+
+ Davon unabhängig ist, ob Anthropic mit deinen Unterhaltungen Modelle
+ trainiert — das steht in deinem Konto unter
+ Datenschutz → „Help improve our AI models"
+ und lässt sich hier nicht umstellen.
+
+
+ (noch nicht geprüft)
+
+
+
+
+
+
+
+
+
Lokales LLM (schnelle Antworten)
@@ -2723,6 +2748,14 @@
return;
}
+ if (msg.type === 'privacy_flags') { renderPrivacyFlags(msg); return; }
+ if (msg.type === 'privacy_flags_saved') {
+ const st = document.getElementById('privacy-flags-status');
+ if (st && msg.error) { st.textContent = 'Speichern fehlgeschlagen: ' + msg.error; st.style.color = '#FF6B6B'; }
+ // Erfolg: der direkt folgende privacy_flags-Broadcast malt den Stand neu.
+ return;
+ }
+
if (msg.type === 'model_test_result') {
const box = document.getElementById('model-test-result');
const btn = document.getElementById('btn-test-model');
@@ -4720,6 +4753,7 @@
loadJellyfinConfig();
refreshVoiceIdStatus();
send({ action: 'get_attachment_limit' });
+ send({ action: 'get_privacy_flags' });
} else if (tab === 'brain') {
loadBrainStatus();
loadBrainMemoryList();
@@ -8019,6 +8053,68 @@
}
}
+ // ── Datenschutz-Schalter ──────────────────────────────────────────
+ function loadPrivacyFlags() {
+ const st = document.getElementById('privacy-flags-status');
+ if (st) { st.textContent = 'Lese den Proxy-Container …'; st.style.color = '#8888AA'; }
+ send({ action: 'get_privacy_flags' });
+ }
+
+ function savePrivacyFlags() {
+ const flags = {};
+ document.querySelectorAll('#privacy-flags-list input[type=checkbox][data-key]')
+ .forEach(cb => { flags[cb.dataset.key] = cb.checked; });
+ if (!Object.keys(flags).length) {
+ const st = document.getElementById('privacy-flags-status');
+ if (st) { st.textContent = 'Erst „Prüfen" drücken.'; st.style.color = '#FFD60A'; }
+ return;
+ }
+ send({ action: 'set_privacy_flags', flags });
+ }
+
+ function renderPrivacyFlags(msg) {
+ const box = document.getElementById('privacy-flags-list');
+ const st = document.getElementById('privacy-flags-status');
+ if (!box) return;
+ if (msg.error) {
+ box.innerHTML = '';
+ if (st) { st.textContent = 'Fehler: ' + msg.error; st.style.color = '#FF6B6B'; }
+ return;
+ }
+ box.innerHTML = (msg.flags || []).map(f => {
+ // "container" = per docker-compose gesetzt. Das gewinnt gegen
+ // settings.json, also waere ein Haken hier wirkungslos — darum sperren
+ // und ehrlich sagen warum.
+ const locked = f.source === 'container';
+ return '';
+ }).join('') || '(keine Angaben)';
+
+ const offen = (msg.flags || []).filter(f => !f.disabled).length;
+ if (st) {
+ if (msg.bulkDisabled) {
+ st.textContent = 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC ist gesetzt — das schaltet ohnehin alles ab.';
+ st.style.color = '#3FFF9F';
+ } else if (offen === 0) {
+ st.textContent = '✅ Alle drei Kanäle sind zu.';
+ st.style.color = '#3FFF9F';
+ } else {
+ st.textContent = '⚠ ' + offen + ' von ' + (msg.flags || []).length + ' Kanälen senden noch. '
+ + 'Haken setzen und speichern.';
+ st.style.color = '#FFD60A';
+ }
+ }
+ }
+
/**
* Testet ein Modell mit einer echten Mini-Anfrage.
* fromFreeText=true nimmt die handgetippte ID, sonst das Dropdown.
diff --git a/diagnostic/server.js b/diagnostic/server.js
index 9b9103f..221d900 100644
--- a/diagnostic/server.js
+++ b/diagnostic/server.js
@@ -3217,6 +3217,10 @@ wss.on("connection", (ws) => {
handleSetModel(ws, msg.model);
} else if (msg.action === "test_model") {
handleTestModel(ws, msg.model);
+ } else if (msg.action === "get_privacy_flags") {
+ handleGetPrivacyFlags(ws);
+ } else if (msg.action === "set_privacy_flags") {
+ handleSetPrivacyFlags(ws, msg.flags);
} else if (msg.action === "get_attachment_limit") {
handleGetAttachmentLimit(ws);
} else if (msg.action === "set_attachment_limit") {
@@ -3643,6 +3647,97 @@ function handleSetModel(clientWs, model) {
return;
}
+// ── Einstellungen: Datenschutz-Schalter von Claude Code ─────────────
+//
+// Drei Kanaele, ueber die Claude Code von sich aus Daten an Anthropic bzw. an
+// einen Fehler-Dienst schickt. Sie liegen als Umgebungsvariablen vor und lassen
+// sich in ~/.claude/settings.json unter "env" setzen — die Datei ist in den
+// Proxy-Container gemountet, also koennen wir sie von hier aus pflegen, ohne
+// die docker-compose.yml anzufassen.
+//
+// WICHTIG: gesetzt wird in settings.json. Steht dieselbe Variable ZUSAETZLICH
+// als echte Container-Umgebungsvariable (docker-compose), gewinnt die — darum
+// prueft "Pruefen" beides und sagt, was tatsaechlich im Prozess ankommt.
+const PRIVACY_FLAGS = [
+ { key: "DISABLE_ERROR_REPORTING",
+ label: "Fehlerberichte",
+ info: "Stacktraces an einen Fehler-Dienst. Bei Pro/Max ab CLI 2.1.198 standardmäßig AN." },
+ { key: "DISABLE_TELEMETRY",
+ label: "Nutzungs-Metriken",
+ info: "Latenz/Zuverlässigkeit. Enthält laut Anthropic nie Code, Prompts oder Pfade." },
+ { key: "DISABLE_FEEDBACK_COMMAND",
+ label: "/feedback-Versand",
+ info: "Sendet sonst auf Zuruf die ganze Unterhaltung inkl. Code — 5 Jahre Aufbewahrung." },
+];
+const CLAUDE_SETTINGS_PATH = "/root/.claude/settings.json";
+
+function _shq(s) { return "'" + String(s).replace(/'/g, "'\\''") + "'"; }
+
+async function handleGetPrivacyFlags(clientWs) {
+ try {
+ // 1) Was kommt im PROZESS wirklich an? Das ist die Wahrheit.
+ const envOut = await dockerExec("aria-proxy",
+ "env | grep -E '^(DISABLE_ERROR_REPORTING|DISABLE_TELEMETRY|DISABLE_FEEDBACK_COMMAND|CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)=' || true");
+ const liveEnv = {};
+ for (const line of String(envOut || "").split("\n")) {
+ const i = line.indexOf("=");
+ if (i > 0) liveEnv[line.slice(0, i).trim()] = line.slice(i + 1).trim();
+ }
+ // 2) Was steht in settings.json?
+ const settingsRaw = await dockerExec("aria-proxy",
+ `cat ${CLAUDE_SETTINGS_PATH} 2>/dev/null || echo '{}'`);
+ let settingsEnv = {};
+ try { settingsEnv = (JSON.parse(settingsRaw || "{}").env) || {}; } catch (_) {}
+
+ const isOn = (v) => v === "1" || String(v).toLowerCase() === "true";
+ const flags = PRIVACY_FLAGS.map(f => {
+ const fromEnv = liveEnv[f.key];
+ const fromSettings = settingsEnv[f.key];
+ // Container-Env gewinnt gegen settings.json.
+ const effective = fromEnv !== undefined ? isOn(fromEnv) : isOn(fromSettings);
+ return {
+ key: f.key, label: f.label, info: f.info,
+ disabled: effective,
+ source: fromEnv !== undefined ? "container" : (fromSettings !== undefined ? "settings" : "none"),
+ };
+ });
+ const bulk = isOn(liveEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC);
+ clientWs.send(JSON.stringify({ type: "privacy_flags", flags, bulkDisabled: bulk }));
+ } catch (err) {
+ clientWs.send(JSON.stringify({ type: "privacy_flags", error: String((err && err.message) || err) }));
+ }
+}
+
+async function handleSetPrivacyFlags(clientWs, wanted) {
+ try {
+ const keys = PRIVACY_FLAGS.map(f => f.key);
+ const raw = await dockerExec("aria-proxy",
+ `cat ${CLAUDE_SETTINGS_PATH} 2>/dev/null || echo '{}'`);
+ let cfg = {};
+ try { cfg = JSON.parse(raw || "{}"); } catch (_) { cfg = {}; }
+ if (!cfg.env || typeof cfg.env !== "object") cfg.env = {};
+ for (const k of keys) {
+ if (wanted && wanted[k]) cfg.env[k] = "1";
+ else delete cfg.env[k];
+ }
+ if (Object.keys(cfg.env).length === 0) delete cfg.env;
+
+ // Atomar schreiben (temp + mv), damit eine laufende CLI nie eine halbe
+ // Datei liest.
+ const json = JSON.stringify(cfg, null, 2);
+ await dockerExec("aria-proxy",
+ `printf '%s' ${_shq(json)} > ${CLAUDE_SETTINGS_PATH}.tmp && mv ${CLAUDE_SETTINGS_PATH}.tmp ${CLAUDE_SETTINGS_PATH}`);
+ log("info", "server", `Datenschutz-Schalter gesetzt: ${JSON.stringify(wanted || {})}`);
+ clientWs.send(JSON.stringify({
+ type: "privacy_flags_saved",
+ info: "Gespeichert. Gilt für JEDE neue Anfrage — ein laufender Turn nutzt noch die alte Einstellung.",
+ }));
+ await handleGetPrivacyFlags(clientWs);
+ } catch (err) {
+ clientWs.send(JSON.stringify({ type: "privacy_flags_saved", error: String((err && err.message) || err) }));
+ }
+}
+
// ── Einstellungen: Model-Test ───────────────────────────────────────
//
// Schickt eine winzige ECHTE Anfrage durch den kompletten Weg