Skip to content

Commit 7969ebe

Browse files
ithiria894claude
andcommitted
feat: v0.5.0 — add commands, agents, rules (8 → 11 categories)
New scannable + movable categories: - Commands (.claude/commands/*.md) — slash commands, global ↔ project - Agents (.claude/agents/*.md) — subagents, global ↔ project - Rules (.claude/rules/*.md) — project constraints, global ↔ project Also: - Smart filter pills: hide zero-count pills behind "+N more" toggle - 92 E2E tests (was 81): 12 new tests for scan/move/delete of new categories - MCP zod schema updated with all 11 categories - CC prompt actions for commands, agents, and rules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8516c58 commit 7969ebe

7 files changed

Lines changed: 309 additions & 22 deletions

File tree

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
English | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Bahasa Indonesia](README.id.md) | [Italiano](README.it.md) | [Português](README.pt-BR.md) | [Türkçe](README.tr.md) | [Tiếng Việt](README.vi.md) | [ไทย](README.th.md)
1111

12-
**Organize all your Claude Code memories, skills, MCP servers, and hooks — view by scope hierarchy, move between scopes via drag-and-drop.**
12+
**Organize all your Claude Code memories, skills, MCP servers, commands, agents, rules, and hooks — view by scope hierarchy, move between scopes via drag-and-drop.**
1313

1414
![Claude Code Organizer Demo](docs/demo.gif)
1515

@@ -70,15 +70,15 @@ We analyzed the source code of every Claude Code tool we could find — analytic
7070
- **Undo everything** — Every move and delete has an undo button — restore instantly, including MCP JSON entries
7171
- **Bulk operations** — Select mode: tick multiple items, move or delete all at once
7272
- **Same-type safety** — Memories can only move to memory folders, skills to skill folders, MCP to MCP configs
73-
- **Search & filter** — Real-time search across all items, filter by category (Memory, Skills, MCP, Config, Hooks, Plugins, Plans, Sessions)
73+
- **Search & filter** — Real-time search across all items, filter by category with smart pill hiding (zero-count pills collapse into "+N more")
7474
- **Detail panel** — Click any item to see full metadata, content preview, file path, and open in VS Code
7575
- **Session inspector** — Parsed conversation previews with speaker labels, session titles, and metadata
76-
- **8 categories** — Memories, skills, MCP servers, configs, hooks, plugins, plans, and sessions
76+
- **11 categories** — Memories, skills, MCP servers, commands, agents, rules, configs, hooks, plugins, plans, and sessions
7777
- **Bundled skill detection** — Groups skills by source bundle via `skills-lock.json`
7878
- **Contextual Claude Code prompts** — "Explain This", "Edit Content", "Resume Session" buttons that copy to clipboard
7979
- **Resizable panels** — Drag dividers to resize sidebar, content area, and detail panel
8080
- **Real file moves** — Actually moves files in `~/.claude/`, not just a viewer
81-
- **61 E2E tests** — Playwright test suite with real filesystem verification after every operation
81+
- **92 E2E tests** — Playwright test suite with real filesystem verification, security tests, and new category coverage
8282

8383
## Why a Visual Dashboard?
8484

@@ -123,6 +123,9 @@ Opens a dashboard at `http://localhost:3847`. Works with your real `~/.claude/`
123123
| Memories (feedback, user, project, reference) | Yes | Yes | Yes | Global + Project |
124124
| Skills (with bundle detection) | Yes | Yes | Yes | Global + Project |
125125
| MCP Servers | Yes | Yes | Yes | Global + Project |
126+
| Commands (slash commands) | Yes | Yes | Yes | Global + Project |
127+
| Agents (subagents) | Yes | Yes | Yes | Global + Project |
128+
| Rules (project constraints) | Yes | Yes | Yes | Global + Project |
126129
| Plans | Yes | Yes | Yes | Global + Project |
127130
| Sessions | Yes || Yes | Project only |
128131
| Config (CLAUDE.md, settings.json) | Yes | Locked || Global + Project |
@@ -144,7 +147,7 @@ Child scopes inherit parent scope's memories, skills, and MCP servers.
144147

145148
## How It Works
146149

147-
1. **Scans** `~/.claude/` — discovers all projects, memories, skills, MCP servers, hooks, plugins, plans, and sessions
150+
1. **Scans** `~/.claude/` — discovers all projects, memories, skills, MCP servers, commands, agents, rules, hooks, plugins, plans, and sessions
148151
2. **Resolves scope hierarchy** — determines parent-child relationships from filesystem paths
149152
3. **Renders dashboard** — three-panel layout: sidebar scope tree, category-grouped items, detail panel with content preview
150153
4. **Handles moves** — drag or click "Move to...", moves files on disk with safety checks, undo support
@@ -163,7 +166,7 @@ Child scopes inherit parent scope's memories, skills, and MCP servers.
163166

164167
```
165168
src/
166-
scanner.mjs # Scans ~/.claude/ — 8 categories, pure data, no side effects
169+
scanner.mjs # Scans ~/.claude/ — 11 categories, pure data, no side effects
167170
mover.mjs # Moves/deletes files between scopes — safety checks + undo support
168171
server.mjs # HTTP server — 8 REST endpoints
169172
mcp-server.mjs # MCP server — 4 tools for AI clients (scan, move, delete, destinations)
@@ -185,7 +188,7 @@ The dashboard is backed by a REST API:
185188
|----------|--------|-------------|
186189
| `/api/scan` | GET | Scan all customizations, returns scopes + items + counts |
187190
| `/api/move` | POST | Move an item to a different scope (supports category/name disambiguation) |
188-
| `/api/delete` | POST | Delete an item (memory, skill, MCP, plan, session) |
191+
| `/api/delete` | POST | Delete an item (memory, skill, MCP, command, agent, rule, plan, session) |
189192
| `/api/restore` | POST | Restore a deleted file (undo support) |
190193
| `/api/restore-mcp` | POST | Restore a deleted MCP server JSON entry (undo support) |
191194
| `/api/destinations` | GET | Get valid move destinations for an item |

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@mcpware/claude-code-organizer",
3-
"version": "0.4.1",
4-
"description": "Organize all your Claude Code memories, skills, MCP servers, and hooks — view by scope hierarchy, move between scopes via drag-and-drop",
3+
"version": "0.5.0",
4+
"description": "Organize all your Claude Code memories, skills, MCP servers, commands, agents, rules, and hooks — view by scope hierarchy, move between scopes via drag-and-drop",
55
"type": "module",
66
"bin": {
77
"claude-code-organizer": "./bin/cli.mjs"

src/mcp-server.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { moveItem, deleteItem, getValidDestinations } from './mover.mjs';
1414

1515
const server = new McpServer({
1616
name: 'claude-code-organizer',
17-
version: '0.4.0',
17+
version: '0.5.0',
1818
});
1919

2020
// Cache scan data so move/delete can look up items
@@ -54,7 +54,7 @@ server.tool(
5454
'move_item',
5555
'Move a Claude Code configuration item (memory, skill, MCP server) from one scope to another. Run scan_inventory first to see available items and scope IDs.',
5656
{
57-
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session']).describe('Category of item to move'),
57+
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session', 'command', 'agent', 'rule']).describe('Category of item to move'),
5858
name: z.string().describe('Name of the item (as shown in scan_inventory results)'),
5959
fromScopeId: z.string().describe('Source scope ID (e.g. "global" or the encoded project directory name)'),
6060
toScopeId: z.string().describe('Destination scope ID'),
@@ -82,7 +82,7 @@ server.tool(
8282
'delete_item',
8383
'Delete a Claude Code configuration item (memory, skill, MCP server entry). Run scan_inventory first to see available items and scope IDs.',
8484
{
85-
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session']).describe('Category of item to delete'),
85+
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session', 'command', 'agent', 'rule']).describe('Category of item to delete'),
8686
name: z.string().describe('Name of the item (as shown in scan_inventory results)'),
8787
scopeId: z.string().describe('Scope ID where the item lives'),
8888
},
@@ -109,7 +109,7 @@ server.tool(
109109
'list_destinations',
110110
'List valid destination scopes for a specific item. Shows where this item can be moved to.',
111111
{
112-
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session']).describe('Category of item'),
112+
category: z.enum(['memory', 'skill', 'mcp', 'plan', 'session', 'command', 'agent', 'rule']).describe('Category of item'),
113113
name: z.string().describe('Name of the item'),
114114
scopeId: z.string().describe('Current scope ID of the item'),
115115
},

src/mover.mjs

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ function resolvePlanDir(scopeId) {
5454
return join(CLAUDE_DIR, "projects", scopeId, "plans");
5555
}
5656

57+
function resolveRuleDir(scopeId, scopes) {
58+
if (scopeId === "global") return join(CLAUDE_DIR, "rules");
59+
const scope = scopes.find(s => s.id === scopeId);
60+
if (!scope || !scope.repoDir) return null;
61+
return join(scope.repoDir, ".claude", "rules");
62+
}
63+
64+
function resolveCommandDir(scopeId, scopes) {
65+
if (scopeId === "global") return join(CLAUDE_DIR, "commands");
66+
const scope = scopes.find(s => s.id === scopeId);
67+
if (!scope || !scope.repoDir) return null;
68+
return join(scope.repoDir, ".claude", "commands");
69+
}
70+
71+
function resolveAgentDir(scopeId, scopes) {
72+
if (scopeId === "global") return join(CLAUDE_DIR, "agents");
73+
const scope = scopes.find(s => s.id === scopeId);
74+
if (!scope || !scope.repoDir) return null;
75+
return join(scope.repoDir, ".claude", "agents");
76+
}
77+
5778
function resolveMcpJson(scopeId, scopes) {
5879
if (scopeId === "global") return join(CLAUDE_DIR, ".mcp.json");
5980
const scope = scopes.find(s => s.id === scopeId);
@@ -74,8 +95,8 @@ function validateMove(item, toScopeId) {
7495
return { ok: false, error: "Item is already in this scope" };
7596
}
7697

77-
// Only memory, skill, mcp, plan can move
78-
const movableCategories = ["memory", "skill", "mcp", "plan"];
98+
// Only memory, skill, mcp, plan, command, agent, rule can move
99+
const movableCategories = ["memory", "skill", "mcp", "plan", "command", "agent", "rule"];
79100
if (!movableCategories.includes(item.category)) {
80101
return { ok: false, error: `${item.category} items cannot be moved` };
81102
}
@@ -151,6 +172,78 @@ async function movePlan(item, toScopeId) {
151172
};
152173
}
153174

175+
// ── Move rule file ──────────────────────────────────────────────────
176+
177+
async function moveRule(item, toScopeId, scopes) {
178+
const toDir = resolveRuleDir(toScopeId, scopes);
179+
if (!toDir) {
180+
return { ok: false, error: `Cannot resolve rules directory for scope: ${toScopeId}` };
181+
}
182+
const toPath = join(toDir, item.fileName);
183+
184+
if (existsSync(toPath)) {
185+
return { ok: false, error: `Rule already exists at destination: ${item.fileName}` };
186+
}
187+
188+
await mkdir(toDir, { recursive: true });
189+
await safeRename(item.path, toPath);
190+
191+
return {
192+
ok: true,
193+
from: item.path,
194+
to: toPath,
195+
message: `Moved rule "${item.name}" from ${item.scopeId} to ${toScopeId}`,
196+
};
197+
}
198+
199+
// ── Move command file ───────────────────────────────────────────────
200+
201+
async function moveCommand(item, toScopeId, scopes) {
202+
const toDir = resolveCommandDir(toScopeId, scopes);
203+
if (!toDir) {
204+
return { ok: false, error: `Cannot resolve commands directory for scope: ${toScopeId}` };
205+
}
206+
const toPath = join(toDir, item.fileName);
207+
208+
if (existsSync(toPath)) {
209+
return { ok: false, error: `Command already exists at destination: ${item.fileName}` };
210+
}
211+
212+
await mkdir(toDir, { recursive: true });
213+
await safeRename(item.path, toPath);
214+
215+
return {
216+
ok: true,
217+
from: item.path,
218+
to: toPath,
219+
message: `Moved command "${item.name}" from ${item.scopeId} to ${toScopeId}`,
220+
};
221+
}
222+
223+
// ── Move agent file ────────────────────────────────────────────────
224+
225+
async function moveAgent(item, toScopeId, scopes) {
226+
const toDir = resolveAgentDir(toScopeId, scopes);
227+
if (!toDir) {
228+
return { ok: false, error: `Cannot resolve agents directory for scope: ${toScopeId}` };
229+
}
230+
const toPath = join(toDir, item.fileName);
231+
232+
if (existsSync(toPath)) {
233+
return { ok: false, error: `Agent already exists at destination: ${item.fileName}` };
234+
}
235+
236+
await mkdir(toDir, { recursive: true });
237+
await safeRename(item.path, toPath);
238+
239+
return {
240+
ok: true,
241+
from: item.path,
242+
to: toPath,
243+
message: `Moved agent "${item.name}" from ${item.scopeId} to ${toScopeId}`,
244+
};
245+
}
246+
154247
// ── Move MCP server entry ────────────────────────────────────────────
155248

156249
async function moveMcp(item, toScopeId, scopes) {
@@ -230,6 +323,12 @@ export async function moveItem(item, toScopeId, scopes) {
230323
return await moveMcp(item, toScopeId, scopes);
231324
case "plan":
232325
return await movePlan(item, toScopeId);
326+
case "command":
327+
return await moveCommand(item, toScopeId, scopes);
328+
case "agent":
329+
return await moveAgent(item, toScopeId, scopes);
330+
case "rule":
331+
return await moveRule(item, toScopeId, scopes);
233332
default:
234333
return { ok: false, error: `Unknown category: ${item.category}` };
235334
}
@@ -285,7 +384,7 @@ export async function deleteItem(item, scopes) {
285384
return { ok: false, error: `${item.name} is locked and cannot be deleted` };
286385
}
287386

288-
const deletableCategories = ["memory", "skill", "mcp", "plan", "session"];
387+
const deletableCategories = ["memory", "skill", "mcp", "plan", "session", "command", "agent", "rule"];
289388
if (!deletableCategories.includes(item.category)) {
290389
return { ok: false, error: `${item.category} items cannot be deleted` };
291390
}
@@ -294,7 +393,10 @@ export async function deleteItem(item, scopes) {
294393
switch (item.category) {
295394
case "memory":
296395
case "plan":
297-
return await deleteMemory(item); // both are single .md files
396+
case "command":
397+
case "agent":
398+
case "rule":
399+
return await deleteMemory(item); // all are single .md files
298400
case "session":
299401
return await deleteSession(item);
300402
case "skill":
@@ -329,6 +431,12 @@ export function getValidDestinations(item, scopes) {
329431
return true; // mcp can go to any scope
330432
case "plan":
331433
return true; // plans can go to any scope
434+
case "command":
435+
return s.id === "global" || s.repoDir;
436+
case "agent":
437+
return s.id === "global" || s.repoDir;
438+
case "rule":
439+
return s.id === "global" || s.repoDir;
332440
default:
333441
return false;
334442
}

0 commit comments

Comments
 (0)