Pools können auf ein vorhandenes Config-Verzeichnis verweisen
Wer claude schon benutzt, hat einen fertigen Login in ~/.claude. Bisher begann ai-control trotzdem bei null: jeder Pool war ein neu angelegtes Verzeichnis, der erste Sessionstart verlangte /login. Ein Pool kann jetzt stattdessen auf ein bestehendes Verzeichnis zeigen. pool.json trägt dafür optional `dir` (home-kontrahiert). Der Pool-Ordner ist dann nur die Hülle für diese Datei; pool_config_dir() löst auf und ist die einzige Stelle, über die Sessionstart, Verbrauchsstatistik, Panel-Provisionierung und Keychain-Name gehen. Zeigt ein Pool auf claudes Default-Verzeichnis, bleibt CLAUDE_CONFIG_DIR ungesetzt: claude nimmt dieses Verzeichnis von sich aus und damit den unsuffixierten Keychain-Eintrag, an dem der bestehende Login hängt (am Keychain gegengeprüft — "Claude Code-credentials" ohne Suffix neben den suffixierten Pool-Einträgen). Ein Verweis auf ein anderes Verzeichnis setzt die Variable wie gewohnt. Das verwiesene Verzeichnis gehört der App nicht: dort entsteht nur die Panel-Ausstattung (Skill, MCP-Eintrag, Freigabe-Ergänzung, alles additiv), keine settings.json-Grundausstattung. Löschen trifft nur die Hülle; Runtime-Symlinks und das Zurücksetzen des Keychain-Eintrags sind gesperrt. Ein Verzeichnis kann nur ein Pool belegen, Ziele innerhalb von pools/ sind ausgeschlossen. In der UI erscheint der Knopf nur, wenn ~/.claude existiert und noch kein Pool darauf zeigt; die Liste zeigt den Pfad unter dem Namen und statt "Zurücksetzen" den Hinweis, dass die Anmeldung claude gehört. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,13 +12,18 @@ interface Pool {
|
||||
projects: string[];
|
||||
running: string[];
|
||||
hasCredentials: boolean;
|
||||
/// Bei verwiesenen Pools das fremde Config-Verzeichnis, sonst nicht gesetzt.
|
||||
dir?: string;
|
||||
}
|
||||
|
||||
const pools = ref<Pool[]>([]);
|
||||
const error = ref("");
|
||||
const busy = ref(false);
|
||||
|
||||
type Mode = "oauth" | "apikey";
|
||||
// Claudes Default-Verzeichnis, solange es existiert und kein Pool darauf zeigt.
|
||||
const defaultDir = ref<string | null>(null);
|
||||
|
||||
type Mode = "oauth" | "apikey" | "reference";
|
||||
const dialog = ref<{ mode: Mode; editing: boolean } | null>(null);
|
||||
const dName = ref("");
|
||||
const dKey = ref("");
|
||||
@@ -28,12 +33,13 @@ const dPool = ref("");
|
||||
const dialogTitle = computed(() => {
|
||||
if (!dialog.value) return "";
|
||||
if (dialog.value.mode === "oauth") return t("pools.newOauthPool");
|
||||
if (dialog.value.mode === "reference") return t("pools.newReferencePool");
|
||||
if (dialog.value.editing) return t("pools.editKey", { name: dName.value });
|
||||
return t("pools.newApikeyPool");
|
||||
});
|
||||
|
||||
function openNew(mode: Mode) {
|
||||
dName.value = "";
|
||||
dName.value = mode === "reference" ? t("pools.referenceDefaultName") : "";
|
||||
dKey.value = "";
|
||||
dPool.value = "";
|
||||
error.value = "";
|
||||
@@ -56,6 +62,7 @@ function closeDialog() {
|
||||
async function refresh() {
|
||||
try {
|
||||
pools.value = await invoke<Pool[]>("list_pools");
|
||||
defaultDir.value = await invoke<string | null>("default_config_dir");
|
||||
error.value = "";
|
||||
} catch (e) {
|
||||
error.value = String(e);
|
||||
@@ -74,6 +81,8 @@ async function submit(allowFile = false) {
|
||||
try {
|
||||
if (d.mode === "oauth") {
|
||||
await invoke("create_oauth_pool", { name: dName.value });
|
||||
} else if (d.mode === "reference") {
|
||||
await invoke("create_reference_pool", { name: dName.value, dir: defaultDir.value });
|
||||
} else if (d.editing) {
|
||||
await invoke("set_apikey", { pool: dPool.value, key: dKey.value, allowFile });
|
||||
} else {
|
||||
@@ -197,6 +206,9 @@ onMounted(refresh);
|
||||
<button class="primary" :disabled="busy" @click="openNew('apikey')">
|
||||
{{ $t("pools.newApikey") }}
|
||||
</button>
|
||||
<button v-if="defaultDir" :disabled="busy" @click="openNew('reference')">
|
||||
{{ $t("pools.newReference") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
@@ -221,6 +233,7 @@ onMounted(refresh);
|
||||
<tr v-for="p in pools" :key="p.id">
|
||||
<td class="cell-name">
|
||||
<strong>{{ p.name }}</strong>
|
||||
<small v-if="p.dir">{{ p.dir }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" :class="p.credentialType">{{ p.credentialType }}</span>
|
||||
@@ -240,16 +253,19 @@ onMounted(refresh);
|
||||
<button :disabled="busy" @click="openRename(p)">
|
||||
{{ $t("pools.rename") }}
|
||||
</button>
|
||||
<template v-if="p.credentialType === 'oauth'">
|
||||
<template v-if="p.credentialType === 'oauth' && !p.dir">
|
||||
<button class="w-action" :disabled="busy" @click="askRelogin(p)">
|
||||
{{ $t("pools.relogin") }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-else-if="p.credentialType === 'apikey'">
|
||||
<button class="w-action" :disabled="busy" @click="openEditKey(p)">
|
||||
{{ p.hasCredentials ? $t("pools.changeKey") : $t("pools.insertKey") }}
|
||||
</button>
|
||||
</template>
|
||||
<!-- Verwiesener Pool: die Anmeldung gehört dem fremden
|
||||
Verzeichnis, die App fasst sie nicht an. -->
|
||||
<span v-else class="w-action quiet">{{ $t("pools.referenceLogin") }}</span>
|
||||
<span class="tip-wrap" :class="{ 'hover-pop': p.running.length }">
|
||||
<button
|
||||
class="danger"
|
||||
@@ -296,12 +312,16 @@ onMounted(refresh);
|
||||
{{ $t("pools.oauthHint") }}
|
||||
</p>
|
||||
|
||||
<p v-if="dialog.mode === 'reference'" class="hint">
|
||||
{{ $t("pools.referenceHint", { dir: defaultDir ?? "" }) }}
|
||||
</p>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" :disabled="busy" @click="closeDialog">
|
||||
{{ $t("pools.cancel") }}
|
||||
</button>
|
||||
<button type="submit" class="primary" :disabled="busy">
|
||||
{{ dialog.mode === "oauth" ? $t("pools.createPool") : $t("pools.save") }}
|
||||
{{ dialog.mode === "apikey" ? $t("pools.save") : $t("pools.createPool") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("Nachrichtentabellen", () => {
|
||||
"Cache ↓",
|
||||
"+ oAuth",
|
||||
"+ apiKey",
|
||||
"System",
|
||||
"Terminal",
|
||||
"Terminal — {name}",
|
||||
".desktop-Starter",
|
||||
|
||||
@@ -130,6 +130,12 @@ const de = {
|
||||
apiKey: "API-Key",
|
||||
oauthHint:
|
||||
"Der Pool wird angelegt; die Anmeldung macht claude beim ersten Start selbst per /login (Browser gegen dein Abo).",
|
||||
newReference: "+ vorhandene Anmeldung",
|
||||
newReferencePool: "Vorhandene Anmeldung übernehmen",
|
||||
referenceDefaultName: "System",
|
||||
referenceHint:
|
||||
"Der Pool verweist auf {dir} — claudes eigenes Verzeichnis. Die dortige Anmeldung gilt weiter, ein /login entfällt. Die App legt dort nur den Panel-Zugang an und löscht nichts.",
|
||||
referenceLogin: "Anmeldung von claude",
|
||||
cancel: "Abbrechen",
|
||||
createPool: "Anlegen",
|
||||
reset: "Zurücksetzen",
|
||||
@@ -349,6 +355,12 @@ const en: typeof de = {
|
||||
apiKey: "API key",
|
||||
oauthHint:
|
||||
"The pool is created; claude signs in itself on first start via /login (browser against your subscription).",
|
||||
newReference: "+ existing login",
|
||||
newReferencePool: "Adopt existing login",
|
||||
referenceDefaultName: "System",
|
||||
referenceHint:
|
||||
"The pool points at {dir} — claude's own directory. The login there keeps working, so no /login is needed. The app only adds panel access there and deletes nothing.",
|
||||
referenceLogin: "login from claude",
|
||||
cancel: "Cancel",
|
||||
createPool: "Create",
|
||||
reset: "Reset",
|
||||
|
||||
@@ -430,6 +430,15 @@ tbody tr:hover {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Platzhalter statt Aktionsknopf: verwiesene Pools haben keine Anmeldung,
|
||||
die die App zurücksetzen dürfte. */
|
||||
.cell-actions .quiet {
|
||||
color: var(--overlay);
|
||||
font-size: 0.75rem;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cell-name small {
|
||||
display: block;
|
||||
font-family: var(--mono);
|
||||
|
||||
Reference in New Issue
Block a user