Projekt-Registry statt festem ~/claude-projects-Root: projects.json (Name → Pfad, ~-relativ) mit Alt-Layout-Fallback; Ordner frei mappbar (add/remove_project, Wizard-Ablageort); Session-Sync per Projekt-Repo (add/commit/pull --rebase/push) statt git-sync-Skript; README + GitHub-Release-Fassung

This commit is contained in:
marcus.hinz
2026-07-05 13:58:52 +02:00
parent ddd8984462
commit ec3d2f4fb3
5 changed files with 419 additions and 111 deletions
+69
View File
@@ -205,6 +205,8 @@ async function saveSettings() {
interface Wizard {
name: string;
pool: string;
dirMode: "default" | "custom";
dir: string | null;
workDirMode: "none" | "default" | "custom";
workDir: string | null;
title: string;
@@ -219,6 +221,8 @@ function openWizard() {
wizard.value = {
name: "",
pool: "",
dirMode: "default",
dir: null,
workDirMode: "none",
workDir: null,
title: "",
@@ -227,6 +231,26 @@ function openWizard() {
};
}
// Bestehenden Ordner als Projekt aufnehmen — Name ist der Ordnername.
async function addFolder() {
const dir = await open({ directory: true, multiple: false });
if (typeof dir !== "string") return;
try {
await invoke("add_project", { path: dir });
await refresh();
} catch (e) {
error.value = String(e);
}
}
async function pickProjectDir() {
const dir = await open({ directory: true, multiple: false });
if (typeof dir === "string") {
wizard.value!.dir = dir;
wizard.value!.dirMode = "custom";
}
}
async function pickWorkDir() {
const dir = await open({ directory: true, multiple: false });
if (typeof dir === "string") {
@@ -248,6 +272,8 @@ async function createProject() {
try {
await invoke("create_project_full", {
name,
// custom: gewählter Ordner ist der Ablageort, das Projekt entsteht darin
dir: w.dirMode === "custom" ? `${w.dir}/${name}` : null,
pool: w.pool === "" ? null : w.pool,
workDir,
createWorkDir: w.workDirMode === "default",
@@ -300,6 +326,19 @@ async function confirmDelete() {
}
}
// Nur den Registry-Eintrag entfernen; der Ordner bleibt.
async function unlinkProject() {
const d = pendingDelete.value!;
try {
await invoke("remove_project", { name: d.name });
pendingDelete.value = null;
await refresh();
} catch (e) {
error.value = String(e);
pendingDelete.value = null;
}
}
let timer: number;
onMounted(() => {
refresh();
@@ -313,6 +352,9 @@ onUnmounted(() => window.clearInterval(timer));
<button class="primary" @click="openWizard">
{{ $t("projects.newProject") }}
</button>
<button :title="$t('projects.addFolderTitle')" @click="addFolder">
{{ $t("projects.addFolder") }}
</button>
</div>
<p v-if="error" class="error">{{ error }}</p>
@@ -393,6 +435,28 @@ onUnmounted(() => window.clearInterval(timer));
{{ $t("projects.name") }}
<input v-model="wizard.name" autofocus required />
</label>
<label class="field">
{{ $t("projects.projectDir") }}
<span class="icon-row">
<select v-model="wizard.dirMode">
<option value="default">
~/claude-projects/{{ wizard.name.trim() || "…" }}
</option>
<option value="custom">{{ $t("projects.projectDirCustom") }}</option>
</select>
<button
v-if="wizard.dirMode === 'custom'"
type="button"
@click="pickProjectDir"
>
{{ $t("projects.chooseFolder") }}
</button>
</span>
</label>
<label v-if="wizard.dirMode === 'custom' && wizard.dir" class="field">
<span></span>
<span class="icon-path">{{ wizard.dir }}/{{ wizard.name.trim() || "…" }}</span>
</label>
<label class="field">
{{ $t("projects.pool") }}
<select v-model="wizard.pool">
@@ -454,6 +518,7 @@ onUnmounted(() => window.clearInterval(timer));
class="primary"
:disabled="
wizard.name.trim() === '' ||
(wizard.dirMode === 'custom' && !wizard.dir) ||
(wizard.workDirMode === 'custom' && !wizard.workDir)
"
>
@@ -479,10 +544,14 @@ onUnmounted(() => window.clearInterval(timer));
{{ $t("projects.workDirsStay") }}
</p>
</template>
<p class="hint">{{ $t("projects.unlinkHint") }}</p>
<div class="actions">
<button type="button" @click="pendingDelete = null">
{{ $t("projects.cancel") }}
</button>
<button @click="unlinkProject">
{{ $t("projects.unlink") }}
</button>
<button class="danger" @click="confirmDelete">
{{ $t("projects.delete") }}
</button>
+16 -2
View File
@@ -51,8 +51,12 @@ const de = {
needsPool: "Kein Pool zugeordnet",
needsKey: "Pool hat keinen API-Key",
newProject: "+ Projekt",
addFolder: "+ Ordner",
addFolderTitle: "Bestehenden Ordner als Projekt aufnehmen",
wizardTitle: "Neues Projekt",
name: "Name",
projectDir: "Projektordner",
projectDirCustom: "Ordner wählen …",
workDir: "Arbeitsordner",
workDirNone: "keiner",
workDirCustom: "vorhandenen Ordner verknüpfen",
@@ -64,9 +68,12 @@ const de = {
deleteRunning: "Läuft noch — erst beenden",
deleteTitle: "{name} löschen",
deleteWarning:
"Der Projektordner unter ~/claude-projects (inkl. memory/, Settings und Pool-Zuordnung) wird endgültig gelöscht.",
"Der Projektordner (inkl. memory/, Settings und Pool-Zuordnung) wird endgültig gelöscht.",
deleteWorkDirs: "Arbeitsordner mitlöschen:",
workDirsStay: "Die Arbeitsordner bleiben unangetastet.",
unlink: "Nur aus Liste nehmen",
unlinkHint:
"Nur aus Liste nehmen entfernt den Eintrag; der Ordner bleibt unangetastet.",
todo: "Todoliste",
todoDesc: "OFFENE-PUNKTE.md bei jedem Sessionstart einspielen",
},
@@ -166,8 +173,12 @@ const en: typeof de = {
needsPool: "No pool assigned",
needsKey: "Pool has no API key",
newProject: "+ Project",
addFolder: "+ Folder",
addFolderTitle: "Add an existing folder as a project",
wizardTitle: "New project",
name: "Name",
projectDir: "Project folder",
projectDirCustom: "Choose folder …",
workDir: "Working folder",
workDirNone: "none",
workDirCustom: "link existing folder",
@@ -179,9 +190,12 @@ const en: typeof de = {
deleteRunning: "Still running — stop it first",
deleteTitle: "Delete {name}",
deleteWarning:
"The project folder under ~/claude-projects (incl. memory/, settings and pool assignment) will be deleted permanently.",
"The project folder (incl. memory/, settings and pool assignment) will be deleted permanently.",
deleteWorkDirs: "Also delete working folders:",
workDirsStay: "The working folders stay untouched.",
unlink: "Remove from list only",
unlinkHint:
"Remove from list only drops the entry; the folder stays untouched.",
todo: "Todo list",
todoDesc: "Inject OFFENE-PUNKTE.md at every session start",
},