Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions site/app/cli/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import {
ArrowLeft,
Binary,
Edit3,
FileCode2,
Network,
Search,
TerminalSquare,
} from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";

import { Wordmark } from "@/components/logo";
import { ThemeToggle } from "@/components/theme-toggle";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

export const metadata: Metadata = {
title: "CLI reference | Graph-sitter",
description:
"High-level graph and codemod commands for inspecting and editing repositories with graph-sitter.",
};

const graphCommands = [
{
icon: FileCode2,
name: "inspect",
signature: "graph-sitter inspect FILE [PATH]",
text: "Shows line counts, imports, classes, functions, and per-function call summaries for a file.",
example:
"uvx graph-sitter inspect packages/app/src/index.ts ./repo --level calls",
},
{
icon: Search,
name: "symbols",
signature: "graph-sitter symbols [QUERY] [PATH]",
text: "Finds functions, classes, and symbols and prints copyable target strings for later commands.",
example:
"uvx graph-sitter symbols runInference ./repo --kind function --backend rust",
},
{
icon: Network,
name: "callgraph",
signature: "graph-sitter callgraph TARGET [PATH]",
text: "Traces outbound callees or inbound callers with clean local, resolved, deduped edges by default.",
example:
"uvx graph-sitter callgraph packages/app/src/index.ts.main ./repo --depth 2",
},
{
icon: Binary,
name: "using",
signature: "graph-sitter using TARGET [PATH]",
text: "Traces the functions and methods a target calls, recursively up to the requested depth.",
example:
"uvx graph-sitter using src/app.py:handler ./repo --depth 3 --resolved-only",
},
{
icon: Network,
name: "usages",
signature: "graph-sitter usages TARGET [PATH]",
text: "Finds callers and usage sites for a target, with optional recursive inbound traversal.",
example:
"uvx graph-sitter usages src/app.py:helper ./repo --depth 2 --dedupe",
},
{
icon: Edit3,
name: "rename",
signature: "graph-sitter rename TARGET --to NAME [PATH]",
text: "Applies a graph-aware rename and reports affected files in check mode before writing.",
example:
"uvx graph-sitter rename src/app.py:helper ./repo --to execute_helper --check",
},
];

const parseOptions = [
["--backend python|rust|auto", "Select the graph backend."],
["--fallback python|error", "Control behavior when Rust is unavailable."],
["--language auto|python|typescript", "Set language detection explicitly."],
["--subdir PATH", "Limit parsing to one or more repo-relative paths."],
[
"--format summary|json",
"Choose human-readable or machine-readable output.",
],
];

export default function CliReferencePage() {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 border-b border-border/60 bg-background/80 backdrop-blur-xl">
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-6 lg:px-8">
<Link href="/" aria-label="Graph-sitter home">
<Wordmark />
</Link>
<nav className="flex items-center gap-1">
<Button
asChild
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground"
>
<Link href="/docs">Docs</Link>
</Button>
<Button
asChild
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground"
>
<Link href="/explore">Explore</Link>
</Button>
<ThemeToggle />
</nav>
</div>
</header>

<main className="flex-1">
<section className="border-b border-border/60">
<div className="mx-auto grid w-full max-w-6xl gap-10 px-6 py-12 lg:grid-cols-[0.95fr_1.05fr] lg:px-8 lg:py-16">
<div>
<Button asChild variant="ghost" size="sm" className="-ml-3 mb-5">
<Link href="/">
<ArrowLeft />
Home
</Link>
</Button>
<Badge variant="outline" className="gap-1.5">
<TerminalSquare />
uvx graph-sitter
</Badge>
<h1 className="mt-4 max-w-xl text-4xl font-semibold leading-tight tracking-tight sm:text-5xl">
CLI reference for graph-aware agents.
</h1>
<p className="mt-4 max-w-2xl text-base leading-relaxed text-muted-foreground sm:text-lg">
Use the command line to parse repositories, inspect symbols,
trace call relationships, and run focused codemods without
writing a one-off script first.
</p>
</div>
<CodeBlock
lines={[
"uvx graph-sitter parse ./repo --format json",
"uvx graph-sitter symbols runInference ./repo --backend rust",
"uvx graph-sitter callgraph src/app.ts.main ./repo --depth 2",
"uvx graph-sitter rename src/app.py:helper ./repo --to execute_helper --check",
]}
/>
</div>
</section>

<section className="mx-auto w-full max-w-6xl px-6 py-12 lg:px-8 lg:py-16">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{graphCommands.map((command) => (
<article
key={command.name}
className="flex min-h-[17rem] flex-col rounded-lg border border-border bg-card p-5"
>
<div className="flex items-center gap-2">
<command.icon className="size-4 text-aura-green" />
<h2 className="font-mono text-sm font-semibold">
{command.name}
</h2>
</div>
<code className="mt-4 block text-wrap rounded-md border border-border bg-muted/50 px-3 py-2 font-mono text-xs leading-relaxed text-foreground">
{command.signature}
</code>
<p className="mt-3 flex-1 text-sm leading-relaxed text-muted-foreground">
{command.text}
</p>
<pre className="code-surface mt-4 overflow-x-auto px-3 py-2.5 font-mono text-xs leading-relaxed">
<code>{command.example}</code>
</pre>
</article>
))}
</div>
</section>

<section className="border-y border-border/60 bg-muted/35">
<div className="mx-auto grid w-full max-w-6xl gap-10 px-6 py-12 lg:grid-cols-[0.85fr_1.15fr] lg:px-8 lg:py-16">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Common parse controls
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
These options are shared by the graph commands and make the CLI
useful on large monorepos.
</p>
</div>
<div className="grid gap-px overflow-hidden rounded-lg border border-border bg-border">
{parseOptions.map(([option, text]) => (
<div
key={option}
className="grid gap-2 bg-card px-4 py-3 text-sm sm:grid-cols-[17rem_1fr]"
>
<code className="font-mono text-aura-purple">{option}</code>
<span className="text-muted-foreground">{text}</span>
</div>
))}
</div>
</div>
</section>

<section className="mx-auto w-full max-w-6xl px-6 py-12 lg:px-8 lg:py-16">
<div className="grid gap-8 lg:grid-cols-2">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Full-repo TypeScript
</h2>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
Use the Rust backend for broad discovery and outbound call graph
traversal. Scope to a package with the Python backend when you
need function-level inbound caller recursion.
</p>
</div>
<CodeBlock
lines={[
"uvx graph-sitter parse ./monorepo --language typescript --backend rust --fallback error --format json",
"uvx graph-sitter callgraph packages/app/src/index.ts.main ./monorepo --language typescript --backend rust --depth 2",
"uvx graph-sitter usages packages/app/src/index.ts.main ./monorepo --language typescript --backend python --subdir packages/app",
]}
/>
</div>
</section>
</main>
</div>
);
}

function CodeBlock({ lines }: { lines: string[] }) {
return (
<div className="dark">
<div className="overflow-hidden rounded-lg border border-border bg-background shadow-2xl shadow-black/35">
<div className="flex items-center gap-2 border-b border-border px-4 py-3">
<TerminalSquare className="size-4 text-aura-green" />
<span className="font-mono text-xs text-muted-foreground">
terminal
</span>
</div>
<pre className="overflow-x-auto bg-background px-5 py-4 font-mono text-[0.78rem] leading-[1.9] text-foreground">
<code>
{lines.map((line) => (
<span key={line} className="block">
<span className="text-muted-foreground">$</span>{" "}
<span className="text-aura-green">{line.split(" ")[0]}</span>
{line.slice(line.indexOf(" "))}
</span>
))}
</code>
</pre>
</div>
</div>
);
}
15 changes: 13 additions & 2 deletions site/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ export default function Home() {
<Wordmark />
</Link>
<nav className="flex items-center gap-1">
<Button
asChild
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground"
>
<Link href="/cli">CLI</Link>
</Button>
<Button
asChild
variant="ghost"
Expand Down Expand Up @@ -229,9 +237,9 @@ export default function Home() {
heaviest indexes moving into Rust for scale.
</p>
<Button asChild size="lg" variant="outline" className="mt-6">
<Link href={`${docsUrl}/introduction/installation`}>
<Link href="/cli">
<TerminalSquare />
Get started with uvx
Open CLI reference
</Link>
</Button>
</div>
Expand All @@ -253,6 +261,9 @@ export default function Home() {
</div>
</div>
<nav className="flex items-center gap-6 text-sm text-muted-foreground">
<Link href="/cli" className="hover:text-foreground">
CLI
</Link>
<Link href={docsUrl} className="hover:text-foreground">
Docs
</Link>
Expand Down
8 changes: 4 additions & 4 deletions site/scripts/gen-nextjs-depgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
from collections import defaultdict
from pathlib import Path

from graph_sitter.codebase.config import ProjectConfig
from graph_sitter.configs.models.codebase import CodebaseConfig, GraphBackend, RustFallbackMode
from graph_sitter.core.codebase import Codebase

REPO = os.environ.get("NEXTJS_REPO", "/Users/jayhack/.codex/worktrees/0554/nextjs-sample")
SUBDIR = "packages/next/src/"
PREFIX = "packages/next/src/"
OUT = Path(__file__).resolve().parents[1] / "lib" / "data" / "nextjs-depgraph.json"

from graph_sitter.codebase.config import ProjectConfig
from graph_sitter.configs.models.codebase import CodebaseConfig, GraphBackend, RustFallbackMode
from graph_sitter.core.codebase import Codebase


def module_of(rel: str) -> str:
parts = rel.split("/")
Expand Down
4 changes: 4 additions & 0 deletions src/graph_sitter/cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import rich_click as click
from rich.traceback import install

from graph_sitter.cli.commands.callgraph.main import callgraph_command
from graph_sitter.cli.commands.config.main import config_command
from graph_sitter.cli.commands.create.main import create_command
from graph_sitter.cli.commands.diagnose.main import diagnose_command
Expand All @@ -16,6 +17,7 @@
from graph_sitter.cli.commands.run.main import run_command
from graph_sitter.cli.commands.start.main import start_command
from graph_sitter.cli.commands.style_debug.main import style_debug_command
from graph_sitter.cli.commands.symbols.main import symbols_command
from graph_sitter.cli.commands.transform.main import transform_command
from graph_sitter.cli.commands.update.main import update_command
from graph_sitter.cli.commands.usages.main import usages_command
Expand All @@ -37,6 +39,8 @@ def main():
main.add_command(diagnose_command)
main.add_command(parse_command)
main.add_command(inspect_command)
main.add_command(symbols_command)
main.add_command(callgraph_command)
main.add_command(usages_command)
main.add_command(using_command)
main.add_command(rename_command)
Expand Down
61 changes: 61 additions & 0 deletions src/graph_sitter/cli/commands/callgraph/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from pathlib import Path

import rich_click as click

from graph_sitter.cli.commands.graph.common import (
GRAPH_COMMAND_JSON_SCHEMA_VERSION,
emit_json,
filter_edge_records,
graph_options,
load_codebase,
print_edge_table,
resolve_target,
trace_edges,
)


@click.command(name="callgraph")
@click.argument("target", type=str)
@click.argument("path", required=False, type=click.Path(path_type=Path, exists=True, file_okay=False), default=Path("."))
@click.option("--direction", type=click.Choice(["outbound", "inbound"]), default="outbound", show_default=True, help="Trace callees or callers.")
@click.option("--depth", type=click.IntRange(min=0), default=2, show_default=True, help="Recursion depth through resolved call edges.")
@click.option("--max-results", type=click.IntRange(min=1), default=200, show_default=True, help="Maximum call edges to print.")
@click.option("--raw", is_flag=True, help="Include unresolved runtime/library calls and repeated call sites.")
@click.option("--format", "output_format", type=click.Choice(["summary", "json"]), default="summary", show_default=True, help="Output format.")
@graph_options
def callgraph_command(
target: str,
path: Path,
direction: str,
depth: int,
max_results: int,
raw: bool,
output_format: str,
backend: str,
fallback: str,
language: str,
subdirectories: tuple[str, ...],
) -> None:
"""Trace a clean first-party call graph for a target."""
codebase = load_codebase(path, backend, fallback, language, subdirectories, quiet=output_format == "json")
resolved = resolve_target(codebase, target)
trace_limit = max_results if raw else max_results * 5
edges = trace_edges(resolved.symbol, direction=direction, depth=depth, max_results=trace_limit)
if not raw:
edges = filter_edge_records(edges, resolved_only=True, local_only=True, hide_runtime=True, dedupe=True)
edges = edges[:max_results]
payload = {
"schema_version": GRAPH_COMMAND_JSON_SCHEMA_VERSION,
"direction": direction,
"target": target,
"depth": depth,
"max_results": max_results,
"raw": raw,
"edges": edges,
}

if output_format == "json":
emit_json(payload)
return

print_edge_table("Graph-sitter callgraph", target, edges, inbound=direction == "inbound")
Loading
Loading