diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54ef5f5aa..d9d2e7bf3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### New Features
+- `codegraph install` now detects and configures **Factory Droid** (the Factory CLI). Running `codegraph install` on a machine with Droid CLI installs the MCP server entry into `~/.factory/mcp.json` (global) or `.factory/mcp.json` (local), so Droid users no longer need to run `droid mcp add codegraph` manually. Use `--target=droid` to target it explicitly.
- `codegraph status --json` now also reports the running CLI `version`, the index directory (`indexPath`), and a `lastIndexed` timestamp (ISO-8601, or null when nothing's indexed yet), so CI and scripts can pin the CLI version and check index freshness from a single command. A matching `CodeGraph.getLastIndexedAt()` library method exposes the same freshness check without shelling out. Thanks @12122J and @eddieran. (#329)
### Fixes
diff --git a/README.md b/README.md
index 1a9800ee3..4e9987804 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# CodeGraph
-### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, and Kiro with Semantic Code Intelligence
+### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, Kiro, and Factory Droid with Semantic Code Intelligence
**~16% cheaper · ~58% fewer tool calls · 100% local**
@@ -24,6 +24,7 @@
[](#supported-agents)
[](#supported-agents)
[](#supported-agents)
+[](#supported-agents)
@@ -57,7 +58,7 @@ In a **new terminal**, run the installer to connect CodeGraph to the agents you
codegraph install
```
-Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)
+Detects and auto-configures Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Factory Droid — wiring the CodeGraph MCP server into each. **This is the step that connects CodeGraph to your agent;** installing the CLI in step 1 does not do it on its own. (Shortcut: `npx @colbymchenry/codegraph` downloads and runs this in one go.)
### 3. Initialize each project
@@ -310,7 +311,7 @@ npx @colbymchenry/codegraph
```
The installer will:
-- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**
+- Ask which agent(s) to configure — auto-detects installed ones from: **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, **Factory Droid**
- Prompt to install `codegraph` on your PATH (so agents can launch the MCP server)
- Ask whether configs apply to all your projects or just this one
- Write each chosen agent's MCP server config (the codegraph usage guide is delivered by the MCP server itself, so no instructions file is added to `CLAUDE.md` / `AGENTS.md` / etc.)
@@ -336,7 +337,7 @@ codegraph install --print-config codex # print snippet, no file wr
### 2. Restart Your Agent
-Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.
+Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / Factory Droid) for the MCP server to load.
### 3. Initialize Projects
@@ -600,6 +601,7 @@ is written):
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
+- **Factory Droid**
## Supported Languages
@@ -661,7 +663,7 @@ MIT
-**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro**
+**Made for AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro, and Factory Droid**
[Report Bug](https://github.com/colbymchenry/codegraph/issues) · [Request Feature](https://github.com/colbymchenry/codegraph/issues)
diff --git a/__tests__/installer-targets.test.ts b/__tests__/installer-targets.test.ts
index 27fcbd6e8..355991444 100644
--- a/__tests__/installer-targets.test.ts
+++ b/__tests__/installer-targets.test.ts
@@ -494,6 +494,52 @@ describe('Installer targets — partial-state idempotency', () => {
expect(paths.some((p) => p.endsWith('/.kiro/steering/codegraph.md'))).toBe(false);
});
+ it('droid: global install writes ~/.factory/mcp.json (mcpServers.codegraph) and no instructions file (#529)', () => {
+ const droid = getTarget('droid')!;
+ const result = droid.install('global', { autoAllow: true });
+ const mcp = path.join(tmpHome, '.factory', 'mcp.json');
+ expect(fs.existsSync(mcp)).toBe(true);
+ const cfg = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
+ expect(cfg.mcpServers.codegraph).toEqual({ type: 'stdio', command: 'codegraph', args: ['serve', '--mcp'] });
+ // No markdown instructions file should be written.
+ expect(result.files.every((f) => !f.path.endsWith('.md'))).toBe(true);
+ });
+
+ it('droid: local install writes ./.factory/mcp.json and no instructions file (#529)', () => {
+ const droid = getTarget('droid')!;
+ const result = droid.install('local', { autoAllow: true });
+ const paths = result.files.map((f) => f.path.replace(/\\/g, '/'));
+ expect(paths.some((p) => p.endsWith('/.factory/mcp.json'))).toBe(true);
+ expect(result.files.every((f) => !f.path.endsWith('.md'))).toBe(true);
+ });
+
+ it('droid: install preserves pre-existing sibling MCP servers in mcp.json', () => {
+ const droid = getTarget('droid')!;
+ const mcp = path.join(tmpHome, '.factory', 'mcp.json');
+ fs.mkdirSync(path.dirname(mcp), { recursive: true });
+ fs.writeFileSync(mcp, JSON.stringify({ mcpServers: { other: { command: 'x' } } }, null, 2) + '\n');
+
+ droid.install('global', { autoAllow: true });
+
+ const after = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
+ expect(after.mcpServers.other).toBeDefined();
+ expect(after.mcpServers.codegraph).toBeDefined();
+ });
+
+ it('droid: uninstall strips codegraph but leaves sibling MCP servers intact', () => {
+ const droid = getTarget('droid')!;
+ const mcp = path.join(tmpHome, '.factory', 'mcp.json');
+ fs.mkdirSync(path.dirname(mcp), { recursive: true });
+ fs.writeFileSync(mcp, JSON.stringify({ mcpServers: { other: { command: 'x' } } }, null, 2) + '\n');
+
+ droid.install('global', { autoAllow: true });
+ droid.uninstall('global');
+
+ const after = JSON.parse(fs.readFileSync(mcp, 'utf-8'));
+ expect(after.mcpServers.other).toBeDefined();
+ expect(after.mcpServers.codegraph).toBeUndefined();
+ });
+
it('antigravity: install writes to LEGACY ~/.gemini/antigravity/mcp_config.json when no migration marker', () => {
const antigravity = getTarget('antigravity')!;
antigravity.install('global', { autoAllow: true });
diff --git a/site/src/content/docs/getting-started/installation.md b/site/src/content/docs/getting-started/installation.md
index 4e8a0e2ce..d3bef5e73 100644
--- a/site/src/content/docs/getting-started/installation.md
+++ b/site/src/content/docs/getting-started/installation.md
@@ -11,7 +11,7 @@ npx @colbymchenry/codegraph
The installer will:
-- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**.
+- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, **Kiro**, and **Factory Droid**.
- Prompt to install `codegraph` on your `PATH` (so agents can launch the MCP server).
- Ask whether configs apply to all your projects or just this one.
- Write each chosen agent's MCP server config plus an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`).
@@ -37,7 +37,7 @@ codegraph install --print-config codex # print snippet, no file wr
## 2. Restart your agent
-Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load.
+Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro / Factory Droid) for the MCP server to load.
## 3. Initialize projects
diff --git a/site/src/content/docs/reference/integrations.md b/site/src/content/docs/reference/integrations.md
index 67e7b96e4..65fd38a66 100644
--- a/site/src/content/docs/reference/integrations.md
+++ b/site/src/content/docs/reference/integrations.md
@@ -15,6 +15,7 @@ The interactive installer auto-detects and configures each supported agent — w
- **Gemini CLI**
- **Antigravity IDE**
- **Kiro**
+- **Factory Droid**
Run `npx @colbymchenry/codegraph` and pick your agent(s); see [Installation](/codegraph/getting-started/installation/) for the non-interactive flags.
diff --git a/src/installer/index.ts b/src/installer/index.ts
index edd48ecaf..fbb3de399 100644
--- a/src/installer/index.ts
+++ b/src/installer/index.ts
@@ -146,8 +146,8 @@ export async function runInstallerWithOptions(opts: RunInstallerOptions): Promis
const sel = await clack.select({
message: 'Apply agent configs to all your projects, or just this one?',
options: [
- { value: 'global' as const, label: 'All projects', hint: '~/.claude, ~/.cursor, etc.' },
- { value: 'local' as const, label: 'Just this project', hint: './.claude, ./.cursor, etc.' },
+ { value: 'global' as const, label: 'All projects', hint: '~/.claude, ~/.cursor, ~/.factory, etc.' },
+ { value: 'local' as const, label: 'Just this project', hint: './.claude, ./.cursor, ./.factory, etc.' },
],
initialValue: 'global' as const,
});
@@ -317,8 +317,8 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise
const sel = await clack.select({
message: 'Remove CodeGraph from all your projects, or just this one?',
options: [
- { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro' },
- { value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./opencode.jsonc, ./.gemini, ./.kiro' },
+ { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro, ~/.factory' },
+ { value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./opencode.jsonc, ./.gemini, ./.kiro, ./.factory' },
],
initialValue: 'global' as const,
});
diff --git a/src/installer/targets/droid.ts b/src/installer/targets/droid.ts
new file mode 100644
index 000000000..ae36f3eb9
--- /dev/null
+++ b/src/installer/targets/droid.ts
@@ -0,0 +1,125 @@
+/**
+ * Factory Droid target. Writes:
+ *
+ * - MCP server entry to `~/.factory/mcp.json` (global = user scope,
+ * loads in every project) or `./.factory/mcp.json` (local = project
+ * scope, committed to the repo). Standard `mcpServers.codegraph`
+ * shape, same as Claude / Cursor / Gemini / Kiro.
+ *
+ * No permissions concept — Droid gates tool runs through its own
+ * autonomy levels, not an external allowlist. `autoAllow` is ignored.
+ *
+ * No instructions file — the codegraph usage guidance ships in the MCP
+ * server's `initialize` response (issue #529), which Droid surfaces
+ * automatically.
+ *
+ * Docs: https://docs.factory.ai/cli/configuration/mcp
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import {
+ AgentTarget,
+ DetectionResult,
+ InstallOptions,
+ Location,
+ WriteResult,
+} from './types';
+import {
+ getMcpServerConfig,
+ jsonDeepEqual,
+ readJsonFile,
+ writeJsonFile,
+} from './shared';
+
+function configDir(loc: Location): string {
+ return loc === 'global'
+ ? path.join(os.homedir(), '.factory')
+ : path.join(process.cwd(), '.factory');
+}
+
+function mcpJsonPath(loc: Location): string {
+ return path.join(configDir(loc), 'mcp.json');
+}
+
+class FactoryDroidTarget implements AgentTarget {
+ readonly id = 'droid' as const;
+ readonly displayName = 'Factory Droid';
+ readonly docsUrl = 'https://docs.factory.ai/cli/configuration/mcp';
+
+ supportsLocation(_loc: Location): boolean {
+ return true;
+ }
+
+ detect(loc: Location): DetectionResult {
+ const file = mcpJsonPath(loc);
+ const config = readJsonFile(file);
+ const alreadyConfigured = !!config.mcpServers?.codegraph;
+ const installed =
+ loc === 'global'
+ ? fs.existsSync(configDir('global')) || fs.existsSync(file)
+ : fs.existsSync(file) || fs.existsSync(configDir('local'));
+ return { installed, alreadyConfigured, configPath: file };
+ }
+
+ install(loc: Location, _opts: InstallOptions): WriteResult {
+ return {
+ files: [writeMcpEntry(loc)],
+ notes: ['Restart Droid for MCP changes to take effect.'],
+ };
+ }
+
+ uninstall(loc: Location): WriteResult {
+ const file = mcpJsonPath(loc);
+ const config = readJsonFile(file);
+
+ if (!config.mcpServers?.codegraph) {
+ return { files: [{ path: file, action: 'not-found' }] };
+ }
+
+ delete config.mcpServers.codegraph;
+ if (Object.keys(config.mcpServers).length === 0) {
+ delete config.mcpServers;
+ }
+ writeJsonFile(file, config);
+ return { files: [{ path: file, action: 'removed' }] };
+ }
+
+ printConfig(loc: Location): string {
+ const target = mcpJsonPath(loc);
+ const snippet = JSON.stringify(
+ { mcpServers: { codegraph: getMcpServerConfig() } },
+ null,
+ 2,
+ );
+ return `# Add to ${target}\n\n${snippet}\n`;
+ }
+
+ describePaths(loc: Location): string[] {
+ return [mcpJsonPath(loc)];
+ }
+}
+
+function writeMcpEntry(loc: Location): WriteResult['files'][number] {
+ const file = mcpJsonPath(loc);
+ const dir = path.dirname(file);
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+
+ const existing = readJsonFile(file);
+ const before = existing.mcpServers?.codegraph;
+ const after = getMcpServerConfig();
+
+ if (jsonDeepEqual(before, after)) {
+ return { path: file, action: 'unchanged' };
+ }
+
+ const action: 'created' | 'updated' =
+ before ? 'updated' : fs.existsSync(file) ? 'updated' : 'created';
+ if (!existing.mcpServers) existing.mcpServers = {};
+ existing.mcpServers.codegraph = after;
+ writeJsonFile(file, existing);
+ return { path: file, action };
+}
+
+export const droidTarget: AgentTarget = new FactoryDroidTarget();
diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts
index 5e929d468..1df677a0c 100644
--- a/src/installer/targets/registry.ts
+++ b/src/installer/targets/registry.ts
@@ -16,6 +16,7 @@ import { hermesTarget } from './hermes';
import { geminiTarget } from './gemini';
import { antigravityTarget } from './antigravity';
import { kiroTarget } from './kiro';
+import { droidTarget } from './droid';
export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
claudeTarget,
@@ -26,6 +27,7 @@ export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([
geminiTarget,
antigravityTarget,
kiroTarget,
+ droidTarget,
]);
export function getTarget(id: string): AgentTarget | undefined {
diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts
index 4b3267e97..f06ab1ca1 100644
--- a/src/installer/targets/types.ts
+++ b/src/installer/targets/types.ts
@@ -19,7 +19,7 @@ export type Location = 'global' | 'local';
* lookup. New targets add a value here when they're added to the
* registry. Keep these short and lowercase.
*/
-export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro';
+export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro' | 'droid';
/**
* Result of `target.detect(location)`.