Skip to content

Commit d44524f

Browse files
ithiria894claude
andcommitted
chore: bump to v0.18.2
Fix security scan cache so NEW MCP badges do not reappear after reload. Fix Backup Center scheduler UX and restore macOS launchd path detection. Refresh E2E coverage for sessions, effective-scope badges, conflicts, and sidebar collapse. Update README privacy, platform, scheduler, and test-count docs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dee40cb commit d44524f

6 files changed

Lines changed: 138 additions & 38 deletions

File tree

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ English | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [
1717

1818
**Claude Code Organizer (CCO)** is a free, open-source dashboard that lets you manage all Claude Code configuration — memories, skills, MCP servers, settings, agents, rules, and hooks — across global and project scopes. It includes a security scanner for MCP tool poisoning and prompt injection, a per-item context token budget tracker, per-project MCP enable/disable controls, and bulk cleanup for duplicate configs. All without leaving the window.
1919

20-
> **v0.18.0** — Backup Center: one click backs up every memory, skill, MCP config, rule, plan, agent, and session to a private GitHub repo. Auto-runs every 4 hours via systemd. See git history. Never lose your Claude setup again.
20+
> **v0.18.0** — Backup Center: one click backs up every memory, skill, MCP config, rule, plan, agent, and session to a private GitHub repo. Auto-runs every 4 hours with the native scheduler on your platform. See git history. Never lose your Claude setup again.
2121
2222
> Scan for poisoned MCP servers. Reclaim wasted context tokens. Disable MCP servers per-project. Find and delete duplicate memories. Move misplaced configs where they belong.
2323
24-
> **Privacy:** CCO reads Claude Code config files on your machine (global and project-level). Nothing is sent externally. Zero telemetry.
24+
> **Privacy:** CCO reads Claude Code config files on your machine (global and project-level). It does not send usage telemetry. It does check the npm registry for version updates unless network access is blocked.
2525
2626
![Claude Code Organizer Demo](docs/demo.gif)
2727

28-
<sub>297 tests (124 unit + 173 E2E) | Zero dependencies | Demo recorded by AI using [Pagecast](https://github.com/mcpware/pagecast)</sub>
28+
<sub>324 tests (124 unit + 200 E2E) | Zero dependencies | Demo recorded by AI using [Pagecast](https://github.com/mcpware/pagecast)</sub>
2929

3030
> 100+ stars in 5 days. Built by a CS dropout who found 140 invisible config files controlling Claude and decided no one should have to `cat` each one. First open source project — thank you to everyone who starred, tested, and reported issues.
3131
@@ -219,9 +219,11 @@ Every constant, merge rule, and policy check cites the specific source file it w
219219
|----------|:------:|
220220
| Ubuntu / Linux | Supported |
221221
| macOS (Intel + Apple Silicon) | Supported |
222-
| Windows 11 | Supported |
222+
| Windows 11 | Partial (dashboard yes, backup scheduler no) |
223223
| WSL | Supported |
224224

225+
Automatic Backup Center scheduling currently uses `systemd` on Linux/WSL and `launchd` on macOS.
226+
225227
## Roadmap
226228

227229
| Feature | Status | Description |

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mcpware/claude-code-organizer",
3-
"version": "0.18.1",
3+
"version": "0.18.2",
44
"description": "Organize all your Claude Code memories, skills, MCP servers, commands, agents, rules, and hooks — see what loads globally vs per-project, then move items between scopes",
55
"type": "module",
66
"files": [

src/backup-scheduler.mjs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Used by Backup Center in CCO and by the standalone claude-code-backup CLI.
44
*/
55

6-
import { writeFile, mkdir, unlink, access } from "node:fs/promises";
6+
import { writeFile, mkdir, unlink, access, readFile } from "node:fs/promises";
77
import { join } from "node:path";
88
import { homedir, platform } from "node:os";
99
import { execFile } from "node:child_process";
@@ -169,6 +169,18 @@ async function isInstalledLaunchd() {
169169
}
170170
}
171171

172+
async function getNodeAndCliPathLaunchd() {
173+
try {
174+
const plistPath = join(launchdDir(), `${plistLabel()}.plist`);
175+
const content = await readFile(plistPath, "utf-8");
176+
const block = content.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/);
177+
if (!block) return null;
178+
const args = [...block[1].matchAll(/<string>([^<]+)<\/string>/g)].map((m) => m[1]);
179+
if (args.length >= 2) return { nodePath: args[0], cliPath: args[1] };
180+
} catch {}
181+
return null;
182+
}
183+
172184
// ── Public API ──────────────────────────────────────────────────────
173185

174186
export async function install(nodePath, cliPath, intervalHours = 4) {
@@ -192,6 +204,6 @@ export async function isInstalled() {
192204
}
193205

194206
export async function getNodeAndCliPath() {
195-
if (platform() === "darwin") return null; // not implemented for macOS
207+
if (platform() === "darwin") return getNodeAndCliPathLaunchd();
196208
return getNodeAndCliPathSystemd();
197209
}

src/ui/app.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ function renderSidebarScope(scope, overrideChildHtml) {
882882
${childHtml ? `<div class="s-children">${childHtml}</div>` : ""}
883883
</div>` : (hasNestedContent && !showBody ? `
884884
<div class="s-scope-body collapsed">
885-
${categoryRows ? `<div>${categoryRows}</div>` : ""}
885+
${(!isDragMode && categoryRows) ? `<div>${categoryRows}</div>` : ""}
886886
${childHtml ? `<div class="s-children">${childHtml}</div>` : ""}
887887
</div>` : "")}
888888
</div>`;
@@ -1748,7 +1748,7 @@ async function openBackupModal() {
17481748
document.getElementById("bkpSchedDesc").textContent =
17491749
status.schedulerInstalled ? `Every ${status.interval || 4} hours + on boot` : "Not running";
17501750
document.getElementById("bkpSchedNext").textContent =
1751-
status.schedulerInstalled ? "systemd timer active" : "";
1751+
status.schedulerInstalled ? "Background scheduler active" : "";
17521752

17531753
// Set interval selector to current value
17541754
const sel = document.getElementById("bkpInterval");
@@ -3995,12 +3995,27 @@ function renderSecurityResults(scanData) {
39953995
footerNote.textContent = `${scanTime}`;
39963996
}
39973997

3998+
/**
3999+
* The baseline file is updated during a successful scan, so "first scan"
4000+
* is only meaningful for the active in-memory result. If we persist it into
4001+
* cache, reopening the app will incorrectly resurrect stale NEW badges.
4002+
*/
4003+
function getPersistableSecurityScanData(scanData) {
4004+
if (!scanData || typeof scanData !== "object") return scanData;
4005+
return {
4006+
...scanData,
4007+
baselines: Array.isArray(scanData.baselines)
4008+
? scanData.baselines.map((b) => b?.isFirstScan ? { ...b, isFirstScan: false } : b)
4009+
: scanData.baselines,
4010+
};
4011+
}
4012+
39984013
/** Save security scan results to server for persistence across sessions. */
39994014
function saveSecurityResults(scanData) {
40004015
fetch("/api/security-cache", {
40014016
method: "POST",
40024017
headers: { "Content-Type": "application/json" },
4003-
body: JSON.stringify(scanData),
4018+
body: JSON.stringify(getPersistableSecurityScanData(scanData)),
40044019
}).catch(() => {}); // Fire and forget
40054020
}
40064021

@@ -4013,8 +4028,9 @@ async function loadCachedSecurityResults() {
40134028

40144029
securityScanResults = cached.data;
40154030

4016-
// Rebuild badge map (don't reset baselineStatus — checkForNewMcpServers may have set it)
4031+
// Rebuild badge map from cached results.
40174032
securityBadges = {};
4033+
securityBaselineStatus = {};
40184034
for (const server of (cached.data.servers || [])) {
40194035
if (server.findings?.length > 0) {
40204036
const maxSev = server.findings.reduce((max, f) => {
@@ -4026,9 +4042,11 @@ async function loadCachedSecurityResults() {
40264042
securityBadges[server.serverName] = "unreachable";
40274043
}
40284044
}
4045+
// Never restore NEW from cache. "New since last scan" must be computed
4046+
// against the current baseline file on startup, otherwise stale cache
4047+
// keeps re-flagging servers that were already acknowledged by a scan.
40294048
for (const b of (cached.data.baselines || [])) {
4030-
if (b.isFirstScan) securityBaselineStatus[b.serverName] = "new";
4031-
else if (b.hasChanges) securityBaselineStatus[b.serverName] = "changed";
4049+
if (b.hasChanges && !b.isFirstScan) securityBaselineStatus[b.serverName] = "changed";
40324050
}
40334051

40344052
// Hide intro (results will render when user opens security panel)

0 commit comments

Comments
 (0)