feat(installer): add codegraph uninstall command (#313) (#318)

Adds a cross-channel uninstall that removes CodeGraph from every agent it's
configured on (Claude Code, Cursor, Codex CLI, opencode, Hermes). Prompts
global-vs-local up front (no flags required) and reports which providers it
actually hit; --location / --target / --yes supported for non-interactive use.
Removes only what install wrote; leaves the .codegraph/ index to `uninit`.

Also fixes Cursor uninstall leaving an orphaned .cursor/rules/codegraph.mdc
(its description: CodeGraph frontmatter lingered); the dedicated rules file is
now deleted outright while user content outside our markers is preserved.

Validated end-to-end on macOS and Docker Linux (global + local sweeps clean).
Adds 8 tests; full suite 730 passing. Bumps to 0.9.3 with CHANGELOG entry.

Resolves #313.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-05-22 04:51:42 -05:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 77e29c436c
commit bf73f4d05c
7 changed files with 435 additions and 8 deletions
+37
View File
@@ -7,6 +7,7 @@
* Usage:
* codegraph Run interactive installer (when no args)
* codegraph install Run interactive installer
* codegraph uninstall Remove CodeGraph from your agents
* codegraph init [path] Initialize CodeGraph in a project
* codegraph uninit [path] Remove CodeGraph from a project
* codegraph index [path] Index all files in the project
@@ -1398,6 +1399,42 @@ program
}
});
/**
* codegraph uninstall
*
* Inverse of `install`. Removes the codegraph MCP server entry,
* instructions block, and permissions from every agent (or a
* `--target` subset). Prompts global-vs-local when not given. Does NOT
* delete the `.codegraph/` index — that's `codegraph uninit`.
*/
program
.command('uninstall')
.description('Remove codegraph from your agents (Claude Code, Cursor, Codex CLI, opencode, Hermes Agent)')
.option('-t, --target <ids>', 'Target agent(s): comma-separated ids, or "all". Default: all')
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
.option('-y, --yes', 'Non-interactive: defaults to --location=global --target=all')
.action(async (opts: {
target?: string;
location?: string;
yes?: boolean;
}) => {
const { runUninstaller } = await import('../installer');
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
error(`--location must be "global" or "local" (got "${opts.location}").`);
process.exit(1);
}
try {
await runUninstaller({
target: opts.target,
location: opts.location as 'global' | 'local' | undefined,
yes: opts.yes,
});
} catch (err) {
error(err instanceof Error ? err.message : String(err));
process.exit(1);
}
});
// Parse and run
program.parse();