Skip to content

Commit 9a2d523

Browse files
PatrickSysclaude
andcommitted
feat: expose all 10 MCP tools via CLI + document them
- Add handleCliCommand() in src/cli.ts with 8 new subcommands: search, metadata, status, reindex, style-guide, patterns, refs, cycles - Each maps 1:1 to existing tool handlers via dispatchTool() - Create initToolContext() that builds ToolContext from persisted index - Add required-flag validation (--query for search, --symbol for refs) - Support --json flag for scripting/piping - Update src/index.ts routing to send CLI_SUBCOMMANDS to handleCliCommand - Expand README "CLI Access" → "CLI Reference" with all 9 commands - Add "CLI Reference" section to docs/capabilities.md with command table CLI now enables vendor-neutral access for scripting, debugging, and CI. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent de93ae8 commit 9a2d523

File tree

4 files changed

+355
-41
lines changed

4 files changed

+355
-41
lines changed

README.md

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -287,31 +287,52 @@ Structured filters available: `framework`, `language`, `componentType`, `layer`
287287
!.codebase-context/memory.json
288288
```
289289

290-
## CLI Access (Vendor-Neutral)
290+
## CLI Reference
291291

292-
You can manage team memory directly from the terminal without any AI agent:
292+
All MCP tools are available as CLI commands — no AI agent required. Useful for scripting, debugging, and CI workflows.
293+
294+
Set `CODEBASE_ROOT` to your project root, or run from the project directory.
293295

294296
```bash
295-
# List all memories
296-
npx codebase-context memory list
297+
# Search the indexed codebase
298+
npx codebase-context search --query "authentication middleware"
299+
npx codebase-context search --query "auth" --intent edit --limit 5
297300

298-
# Filter by category or type
299-
npx codebase-context memory list --category conventions --type convention
301+
# Project structure, frameworks, and dependencies
302+
npx codebase-context metadata
300303

301-
# Search memories
302-
npx codebase-context memory list --query "auth"
304+
# Index state and progress
305+
npx codebase-context status
303306

304-
# Add a memory
305-
npx codebase-context memory add --type convention --category tooling --memory "Use pnpm, not npm" --reason "Workspace support and speed"
307+
# Re-index the codebase
308+
npx codebase-context reindex
309+
npx codebase-context reindex --incremental --reason "added new service"
306310

307-
# Remove a memory
308-
npx codebase-context memory remove <id>
311+
# Style guide rules
312+
npx codebase-context style-guide
313+
npx codebase-context style-guide --query "naming" --category patterns
314+
315+
# Team patterns (DI, state, testing, etc.)
316+
npx codebase-context patterns
317+
npx codebase-context patterns --category testing
309318

310-
# JSON output for scripting
311-
npx codebase-context memory list --json
319+
# Symbol references
320+
npx codebase-context refs --symbol "UserService"
321+
npx codebase-context refs --symbol "handleLogin" --limit 20
322+
323+
# Circular dependency detection
324+
npx codebase-context cycles
325+
npx codebase-context cycles --scope src/features
326+
327+
# Memory management
328+
npx codebase-context memory list
329+
npx codebase-context memory list --category conventions --type convention
330+
npx codebase-context memory list --query "auth" --json
331+
npx codebase-context memory add --type convention --category tooling --memory "Use pnpm, not npm" --reason "Workspace support and speed"
332+
npx codebase-context memory remove <id>
312333
```
313334

314-
Set `CODEBASE_ROOT` to point to your project, or run from the project directory.
335+
All commands accept `--json` for raw JSON output suitable for piping and scripting.
315336

316337
## Tip: Ensuring your AI Agent recalls memory:
317338

docs/capabilities.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@
22

33
Technical reference for what `codebase-context` ships today. For the user-facing overview, see [README.md](../README.md).
44

5+
## CLI Reference
6+
7+
All 10 MCP tools are exposed as CLI subcommands. Set `CODEBASE_ROOT` or run from the project directory.
8+
9+
| Command | Flags | Maps to |
10+
|---|---|---|
11+
| `search --query <q>` | `--intent explore\|edit\|refactor\|migrate`, `--limit <n>`, `--lang <l>`, `--framework <f>`, `--layer <l>` | `search_codebase` |
12+
| `metadata` || `get_codebase_metadata` |
13+
| `status` || `get_indexing_status` |
14+
| `reindex` | `--incremental`, `--reason <r>` | `refresh_index` |
15+
| `style-guide` | `--query <q>`, `--category <c>` | `get_style_guide` |
16+
| `patterns` | `--category all\|di\|state\|testing\|libraries` | `get_team_patterns` |
17+
| `refs --symbol <name>` | `--limit <n>` | `get_symbol_references` |
18+
| `cycles` | `--scope <path>` | `detect_circular_dependencies` |
19+
| `memory list` | `--category`, `--type`, `--query`, `--json` ||
20+
| `memory add` | `--type`, `--category`, `--memory`, `--reason` | `remember` |
21+
| `memory remove <id>` |||
22+
23+
All commands accept `--json` for raw JSON output. Errors go to stderr with exit code 1.
24+
25+
```bash
26+
# Quick examples
27+
npx codebase-context status
28+
npx codebase-context search --query "auth middleware" --intent edit
29+
npx codebase-context refs --symbol "UserService" --limit 10
30+
npx codebase-context cycles --scope src/features
31+
npx codebase-context reindex --incremental
32+
```
33+
534
## Tool Surface
635

736
10 MCP tools + 1 optional resource (`codebase://context`). **Migration:** `get_component_usage` was removed; use `get_symbol_references` for symbol usage evidence.

src/cli.ts

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,288 @@
11
/**
22
* CLI subcommands for codebase-context.
33
* Memory list/add/remove — vendor-neutral access without any AI agent.
4+
* search/metadata/status/reindex/style-guide/patterns/refs/cycles — all MCP tools.
45
*/
56

67
import path from 'path';
8+
import { promises as fs } from 'fs';
79
import type { Memory, MemoryCategory, MemoryType } from './types/index.js';
8-
import { CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME } from './constants/codebase-context.js';
10+
import {
11+
CODEBASE_CONTEXT_DIRNAME,
12+
MEMORY_FILENAME,
13+
INTELLIGENCE_FILENAME,
14+
KEYWORD_INDEX_FILENAME,
15+
VECTOR_DB_DIRNAME
16+
} from './constants/codebase-context.js';
917
import {
1018
appendMemoryFile,
1119
readMemoriesFile,
1220
removeMemory,
1321
filterMemories,
1422
withConfidence
1523
} from './memory/store.js';
24+
import { CodebaseIndexer } from './core/indexer.js';
25+
import { dispatchTool } from './tools/index.js';
26+
import type { ToolContext } from './tools/index.js';
27+
import type { IndexState } from './tools/types.js';
28+
import { analyzerRegistry } from './core/analyzer-registry.js';
29+
import { AngularAnalyzer } from './analyzers/angular/index.js';
30+
import { GenericAnalyzer } from './analyzers/generic/index.js';
31+
32+
analyzerRegistry.register(new AngularAnalyzer());
33+
analyzerRegistry.register(new GenericAnalyzer());
34+
35+
const CLI_COMMANDS = [
36+
'memory',
37+
'search',
38+
'metadata',
39+
'status',
40+
'reindex',
41+
'style-guide',
42+
'patterns',
43+
'refs',
44+
'cycles'
45+
] as const;
46+
47+
type CliCommand = (typeof CLI_COMMANDS)[number];
48+
49+
function printUsage(): void {
50+
console.log('codebase-context <command> [options]');
51+
console.log('');
52+
console.log('Commands:');
53+
console.log(' memory <list|add|remove> Memory CRUD');
54+
console.log(
55+
' search --query <q> Search the indexed codebase'
56+
);
57+
console.log(' [--intent explore|edit|refactor|migrate]');
58+
console.log(' [--limit <n>] [--lang <l>] [--framework <f>] [--layer <l>]');
59+
console.log(' metadata Project structure, frameworks, deps');
60+
console.log(' status Index state and progress');
61+
console.log(' reindex [--incremental] [--reason <r>] Re-index the codebase');
62+
console.log(' style-guide [--query <q>] [--category <c>] Style guide rules');
63+
console.log(
64+
' patterns [--category all|di|state|testing|libraries] Team patterns'
65+
);
66+
console.log(' refs --symbol <name> [--limit <n>] Symbol references');
67+
console.log(' cycles [--scope <path>] Circular dependency detection');
68+
console.log('');
69+
console.log('Global flags:');
70+
console.log(' --json Output raw JSON (default: human-readable)');
71+
console.log(' --help Show this help');
72+
console.log('');
73+
console.log('Environment:');
74+
console.log(' CODEBASE_ROOT Project root path (default: cwd)');
75+
}
76+
77+
async function initToolContext(): Promise<ToolContext> {
78+
const rootPath = path.resolve(process.env.CODEBASE_ROOT || process.cwd());
79+
80+
const paths = {
81+
baseDir: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME),
82+
memory: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, MEMORY_FILENAME),
83+
intelligence: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, INTELLIGENCE_FILENAME),
84+
keywordIndex: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, KEYWORD_INDEX_FILENAME),
85+
vectorDb: path.join(rootPath, CODEBASE_CONTEXT_DIRNAME, VECTOR_DB_DIRNAME)
86+
};
87+
88+
// Check if index exists to determine initial status
89+
let indexExists = false;
90+
try {
91+
await fs.access(paths.keywordIndex);
92+
indexExists = true;
93+
} catch {
94+
// no index on disk
95+
}
96+
97+
const indexState: IndexState = {
98+
status: indexExists ? 'ready' : 'idle'
99+
};
100+
101+
const performIndexing = async (incrementalOnly?: boolean): Promise<void> => {
102+
indexState.status = 'indexing';
103+
const mode = incrementalOnly ? 'incremental' : 'full';
104+
console.error(`Indexing (${mode}): ${rootPath}`);
105+
106+
try {
107+
let lastLoggedProgress = { phase: '', percentage: -1 };
108+
const indexer = new CodebaseIndexer({
109+
rootPath,
110+
incrementalOnly,
111+
onProgress: (progress) => {
112+
const shouldLog =
113+
progress.phase !== lastLoggedProgress.phase ||
114+
(progress.percentage % 10 === 0 &&
115+
progress.percentage !== lastLoggedProgress.percentage);
116+
if (shouldLog) {
117+
console.error(`[${progress.phase}] ${progress.percentage}%`);
118+
lastLoggedProgress = { phase: progress.phase, percentage: progress.percentage };
119+
}
120+
}
121+
});
122+
123+
indexState.indexer = indexer;
124+
const stats = await indexer.index();
125+
indexState.status = 'ready';
126+
indexState.lastIndexed = new Date();
127+
indexState.stats = stats;
128+
129+
console.error(
130+
`Complete: ${stats.indexedFiles} files, ${stats.totalChunks} chunks in ${(
131+
stats.duration / 1000
132+
).toFixed(2)}s`
133+
);
134+
} catch (error) {
135+
indexState.status = 'error';
136+
indexState.error = error instanceof Error ? error.message : String(error);
137+
console.error('Indexing failed:', indexState.error);
138+
}
139+
};
140+
141+
return { indexState, paths, rootPath, performIndexing };
142+
}
143+
144+
function extractText(result: { content?: Array<{ type: string; text: string }> }): string {
145+
return result.content?.[0]?.text ?? '';
146+
}
147+
148+
function formatJson(json: string, useJson: boolean): void {
149+
if (useJson) {
150+
console.log(json);
151+
return;
152+
}
153+
// Pretty-print already-formatted JSON as-is (it's already readable)
154+
console.log(json);
155+
}
156+
157+
export async function handleCliCommand(argv: string[]): Promise<void> {
158+
const command = argv[0] as CliCommand | '--help' | undefined;
159+
160+
if (!command || command === '--help') {
161+
printUsage();
162+
return;
163+
}
164+
165+
if (command === 'memory') {
166+
return handleMemoryCli(argv.slice(1));
167+
}
168+
169+
const useJson = argv.includes('--json');
170+
171+
// Parse flags into a map
172+
const flags: Record<string, string | boolean> = {};
173+
for (let i = 1; i < argv.length; i++) {
174+
const arg = argv[i];
175+
if (arg === '--json') continue;
176+
if (arg.startsWith('--')) {
177+
const key = arg.slice(2);
178+
const next = argv[i + 1];
179+
if (next && !next.startsWith('--')) {
180+
flags[key] = next;
181+
i++;
182+
} else {
183+
flags[key] = true;
184+
}
185+
}
186+
}
187+
188+
const ctx = await initToolContext();
189+
190+
let toolName: string;
191+
let toolArgs: Record<string, unknown> = {};
192+
193+
switch (command) {
194+
case 'search': {
195+
if (!flags['query']) {
196+
console.error('Error: --query is required');
197+
console.error('Usage: codebase-context search --query <text> [--intent <i>] [--limit <n>]');
198+
process.exit(1);
199+
}
200+
toolName = 'search_codebase';
201+
toolArgs = {
202+
query: flags['query'],
203+
...(flags['intent'] ? { intent: flags['intent'] } : {}),
204+
...(flags['limit'] ? { limit: Number(flags['limit']) } : {}),
205+
...(flags['lang'] ? { filters: { language: flags['lang'] } } : {}),
206+
...(flags['framework'] ? { filters: { framework: flags['framework'] } } : {}),
207+
...(flags['layer'] ? { filters: { layer: flags['layer'] } } : {})
208+
};
209+
break;
210+
}
211+
case 'metadata': {
212+
toolName = 'get_codebase_metadata';
213+
break;
214+
}
215+
case 'status': {
216+
toolName = 'get_indexing_status';
217+
break;
218+
}
219+
case 'reindex': {
220+
toolName = 'refresh_index';
221+
toolArgs = {
222+
...(flags['incremental'] ? { incrementalOnly: true } : {}),
223+
...(flags['reason'] ? { reason: flags['reason'] } : {})
224+
};
225+
// For CLI, reindex must be awaited (fire-and-forget won't work in a process that exits)
226+
await ctx.performIndexing(Boolean(flags['incremental']));
227+
const statusResult = await dispatchTool('get_indexing_status', {}, ctx);
228+
formatJson(extractText(statusResult), useJson);
229+
return;
230+
}
231+
case 'style-guide': {
232+
toolName = 'get_style_guide';
233+
toolArgs = {
234+
...(flags['query'] ? { query: flags['query'] } : {}),
235+
...(flags['category'] ? { category: flags['category'] } : {})
236+
};
237+
break;
238+
}
239+
case 'patterns': {
240+
toolName = 'get_team_patterns';
241+
toolArgs = {
242+
...(flags['category'] ? { category: flags['category'] } : {})
243+
};
244+
break;
245+
}
246+
case 'refs': {
247+
if (!flags['symbol']) {
248+
console.error('Error: --symbol is required');
249+
console.error('Usage: codebase-context refs --symbol <name> [--limit <n>]');
250+
process.exit(1);
251+
}
252+
toolName = 'get_symbol_references';
253+
toolArgs = {
254+
symbol: flags['symbol'],
255+
...(flags['limit'] ? { limit: Number(flags['limit']) } : {})
256+
};
257+
break;
258+
}
259+
case 'cycles': {
260+
toolName = 'detect_circular_dependencies';
261+
toolArgs = {
262+
...(flags['scope'] ? { scope: flags['scope'] } : {})
263+
};
264+
break;
265+
}
266+
default: {
267+
console.error(`Unknown command: ${command}`);
268+
console.error('');
269+
printUsage();
270+
process.exit(1);
271+
}
272+
}
273+
274+
try {
275+
const result = await dispatchTool(toolName, toolArgs, ctx);
276+
if (result.isError) {
277+
console.error(extractText(result));
278+
process.exit(1);
279+
}
280+
formatJson(extractText(result), useJson);
281+
} catch (error) {
282+
console.error('Error:', error instanceof Error ? error.message : String(error));
283+
process.exit(1);
284+
}
285+
}
16286

17287
export async function handleMemoryCli(args: string[]): Promise<void> {
18288
// Resolve project root: use CODEBASE_ROOT env or cwd (argv[2] is "memory", not a path)

0 commit comments

Comments
 (0)