seam-code 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- seam/__init__.py +3 -0
- seam/_data/schema.sql +225 -0
- seam/_web/assets/index-BL_tqprR.js +216 -0
- seam/_web/assets/index-GTKUhVyD.css +1 -0
- seam/_web/index.html +13 -0
- seam/analysis/__init__.py +14 -0
- seam/analysis/affected.py +254 -0
- seam/analysis/builtins.py +966 -0
- seam/analysis/byte_budget.py +217 -0
- seam/analysis/changes.py +709 -0
- seam/analysis/cluster_naming.py +260 -0
- seam/analysis/clustering.py +216 -0
- seam/analysis/confidence.py +699 -0
- seam/analysis/embeddings.py +195 -0
- seam/analysis/flows.py +708 -0
- seam/analysis/impact.py +444 -0
- seam/analysis/imports.py +994 -0
- seam/analysis/imports_ext.py +780 -0
- seam/analysis/imports_resolve.py +176 -0
- seam/analysis/processes.py +453 -0
- seam/analysis/relevance.py +155 -0
- seam/analysis/rwr.py +129 -0
- seam/analysis/staleness.py +328 -0
- seam/analysis/steer.py +282 -0
- seam/analysis/synthesis.py +253 -0
- seam/analysis/synthesis_channels.py +433 -0
- seam/analysis/testpaths.py +103 -0
- seam/analysis/traversal.py +470 -0
- seam/cli/__init__.py +0 -0
- seam/cli/install.py +232 -0
- seam/cli/main.py +2602 -0
- seam/cli/output.py +137 -0
- seam/cli/read.py +244 -0
- seam/cli/serve.py +145 -0
- seam/config.py +551 -0
- seam/indexer/__init__.py +0 -0
- seam/indexer/cluster_index.py +425 -0
- seam/indexer/db.py +496 -0
- seam/indexer/embedding_index.py +183 -0
- seam/indexer/field_access.py +536 -0
- seam/indexer/field_access_c_cpp.py +643 -0
- seam/indexer/field_access_ext.py +708 -0
- seam/indexer/field_access_ext2.py +408 -0
- seam/indexer/field_access_go_rust.py +737 -0
- seam/indexer/field_access_php_swift.py +888 -0
- seam/indexer/field_access_ts.py +626 -0
- seam/indexer/graph.py +321 -0
- seam/indexer/graph_c.py +562 -0
- seam/indexer/graph_c_cpp.py +39 -0
- seam/indexer/graph_common.py +644 -0
- seam/indexer/graph_cpp.py +615 -0
- seam/indexer/graph_csharp.py +651 -0
- seam/indexer/graph_go.py +723 -0
- seam/indexer/graph_go_rust.py +39 -0
- seam/indexer/graph_java.py +689 -0
- seam/indexer/graph_java_csharp.py +38 -0
- seam/indexer/graph_php.py +914 -0
- seam/indexer/graph_python.py +628 -0
- seam/indexer/graph_ruby.py +748 -0
- seam/indexer/graph_rust.py +653 -0
- seam/indexer/graph_scope_infer.py +902 -0
- seam/indexer/graph_scope_infer_ext.py +723 -0
- seam/indexer/graph_scope_infer_ext2.py +992 -0
- seam/indexer/graph_swift.py +1014 -0
- seam/indexer/graph_swift_infer.py +515 -0
- seam/indexer/graph_typescript.py +663 -0
- seam/indexer/migrations.py +816 -0
- seam/indexer/parser.py +204 -0
- seam/indexer/pipeline.py +197 -0
- seam/indexer/signatures.py +634 -0
- seam/indexer/signatures_ext.py +780 -0
- seam/indexer/sync.py +287 -0
- seam/indexer/synthesis_index.py +291 -0
- seam/indexer/tokenize.py +79 -0
- seam/installer/__init__.py +67 -0
- seam/installer/claude.py +97 -0
- seam/installer/codex.py +94 -0
- seam/installer/core.py +127 -0
- seam/installer/cursor.py +61 -0
- seam/installer/guide.py +110 -0
- seam/installer/jsonfile.py +85 -0
- seam/installer/markdownfile.py +146 -0
- seam/installer/tomlfile.py +72 -0
- seam/query/__init__.py +0 -0
- seam/query/clusters.py +206 -0
- seam/query/comments.py +217 -0
- seam/query/context.py +293 -0
- seam/query/engine.py +940 -0
- seam/query/fts.py +328 -0
- seam/query/names.py +470 -0
- seam/query/pack.py +433 -0
- seam/query/semantic.py +339 -0
- seam/query/structure.py +727 -0
- seam/server/__init__.py +0 -0
- seam/server/graph_api.py +437 -0
- seam/server/handler_common.py +323 -0
- seam/server/impact_handler.py +615 -0
- seam/server/mcp.py +556 -0
- seam/server/tools.py +697 -0
- seam/server/trace_handler.py +184 -0
- seam/server/web.py +922 -0
- seam/watcher/__init__.py +0 -0
- seam/watcher/__main__.py +56 -0
- seam/watcher/daemon.py +237 -0
- seam_code-0.3.0.dist-info/METADATA +318 -0
- seam_code-0.3.0.dist-info/RECORD +109 -0
- seam_code-0.3.0.dist-info/WHEEL +4 -0
- seam_code-0.3.0.dist-info/entry_points.txt +2 -0
- seam_code-0.3.0.dist-info/licenses/LICENSE +21 -0
seam/cli/install.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""`seam install` / `seam uninstall` CLI commands.
|
|
2
|
+
|
|
3
|
+
Lives in its own module (not main.py) because main.py is already large; these
|
|
4
|
+
two Typer command functions are registered onto the app in main.py.
|
|
5
|
+
|
|
6
|
+
CLI-first by default: `seam install` writes the token-lean CLI **guidance** (a
|
|
7
|
+
Claude Code skill, a Cursor rule, an AGENTS.md block) into the repo. `--with-mcp`
|
|
8
|
+
ALSO writes the MCP server config (the heavier, ~6k-token-standing path). Guidance
|
|
9
|
+
is project-scoped (it lives in the repo); only the MCP config respects `--location`.
|
|
10
|
+
The config-writing logic lives in seam/installer/* — this layer parses options,
|
|
11
|
+
orchestrates over the selected target(s), and renders Rich / JSON output.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, NoReturn
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
import seam.config as config
|
|
21
|
+
from seam.cli.output import emit_json, emit_json_error
|
|
22
|
+
from seam.installer import TARGETS, AgentTarget, get_target, resolve_seam_command
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
_ALL = "all"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _select_targets(target: str) -> list[AgentTarget] | None:
|
|
30
|
+
"""Resolve the --target value to a list of AgentTargets, or None if unknown."""
|
|
31
|
+
if target == _ALL:
|
|
32
|
+
return list(TARGETS.values())
|
|
33
|
+
one = get_target(target)
|
|
34
|
+
return [one] if one is not None else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _install_target(
|
|
38
|
+
tgt: AgentTarget, root: Path, location: str, command: str, args: list[str], with_mcp: bool
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""Write guidance (always) and, if requested, the MCP config for one target."""
|
|
41
|
+
entry: dict[str, Any] = {
|
|
42
|
+
"target": tgt.name,
|
|
43
|
+
"guidance": [{"action": r.action, "path": r.path} for r in tgt.install_guidance(root)],
|
|
44
|
+
"mcp": None,
|
|
45
|
+
}
|
|
46
|
+
if not with_mcp:
|
|
47
|
+
return entry
|
|
48
|
+
if location in tgt.supported_locations():
|
|
49
|
+
res = tgt.install(root, location, command, args)
|
|
50
|
+
entry["mcp"] = {"action": res.action, "path": res.path, "backed_up": res.backed_up}
|
|
51
|
+
else:
|
|
52
|
+
# Only reachable under `all` — an explicit single target is validated upfront.
|
|
53
|
+
entry["mcp"] = {
|
|
54
|
+
"action": "skipped",
|
|
55
|
+
"reason": f"MCP location '{location}' unsupported (supports {tgt.supported_locations()})",
|
|
56
|
+
"path": None,
|
|
57
|
+
}
|
|
58
|
+
return entry
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _preview_target(
|
|
62
|
+
tgt: AgentTarget, root: Path, location: str, command: str, args: list[str], with_mcp: bool
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
"""Build the --print-config preview for one target (writes nothing)."""
|
|
65
|
+
entry: dict[str, Any] = {
|
|
66
|
+
"target": tgt.name,
|
|
67
|
+
"guidance_preview": [{"path": p, "content": c} for p, c in tgt.guidance_previews(root)],
|
|
68
|
+
"mcp_preview": None,
|
|
69
|
+
}
|
|
70
|
+
if with_mcp and location in tgt.supported_locations():
|
|
71
|
+
entry["mcp_preview"] = {
|
|
72
|
+
"path": str(tgt.config_path(root, location)),
|
|
73
|
+
"config": tgt.render_entry(command, args),
|
|
74
|
+
}
|
|
75
|
+
return entry
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def install_command(
|
|
79
|
+
path: str = typer.Argument(".", help="Project root to index (default: current directory)."),
|
|
80
|
+
target: str = typer.Option("claude", "--target", help="claude | cursor | codex | all"),
|
|
81
|
+
location: str = typer.Option(
|
|
82
|
+
"project", "--location", help="MCP config scope: project | user (codex: user only)."
|
|
83
|
+
),
|
|
84
|
+
with_mcp: bool = typer.Option(
|
|
85
|
+
False, "--with-mcp", help="Also write the MCP server config (CLI guidance is the default)."
|
|
86
|
+
),
|
|
87
|
+
print_config: bool = typer.Option(
|
|
88
|
+
False, "--print-config", help="Print what would be written; write nothing."
|
|
89
|
+
),
|
|
90
|
+
json_: bool = typer.Option(False, "--json", help="Emit a structured JSON envelope to stdout."),
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Set up Seam for a coding agent (Claude Code / Cursor / Codex).
|
|
93
|
+
|
|
94
|
+
Default: writes the token-lean CLI guidance into the repo so the agent queries
|
|
95
|
+
via the `seam` CLI. Add `--with-mcp` to also wire the MCP server. Idempotent and
|
|
96
|
+
reversible with `seam uninstall`.
|
|
97
|
+
"""
|
|
98
|
+
root = Path(path).resolve()
|
|
99
|
+
if not root.is_dir():
|
|
100
|
+
_fail(json_, "INVALID_INPUT", f"'{root}' is not a directory.")
|
|
101
|
+
|
|
102
|
+
targets = _select_targets(target)
|
|
103
|
+
if targets is None:
|
|
104
|
+
_fail(json_, "INVALID_INPUT", f"unknown target '{target}' (expected: {', '.join(TARGETS)} or all)")
|
|
105
|
+
|
|
106
|
+
# Validate an explicit single target's MCP location BEFORE any write, so we
|
|
107
|
+
# never leave guidance written and then fail. (Under `all`, a bad location is
|
|
108
|
+
# skipped per-target instead.)
|
|
109
|
+
if with_mcp and target != _ALL and location not in targets[0].supported_locations():
|
|
110
|
+
_fail(
|
|
111
|
+
json_,
|
|
112
|
+
"INVALID_INPUT",
|
|
113
|
+
f"target '{targets[0].name}' MCP config does not support location '{location}' "
|
|
114
|
+
f"(supports: {', '.join(targets[0].supported_locations())})",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
command, found = resolve_seam_command()
|
|
118
|
+
args = ["start", str(root)]
|
|
119
|
+
index_present = config.get_db_path(root).exists()
|
|
120
|
+
|
|
121
|
+
results: list[dict[str, Any]] = []
|
|
122
|
+
for tgt in targets:
|
|
123
|
+
if print_config:
|
|
124
|
+
results.append(_preview_target(tgt, root, location, command, args, with_mcp))
|
|
125
|
+
else:
|
|
126
|
+
results.append(_install_target(tgt, root, location, command, args, with_mcp))
|
|
127
|
+
|
|
128
|
+
warnings: list[str] = []
|
|
129
|
+
if with_mcp and not found:
|
|
130
|
+
warnings.append(
|
|
131
|
+
"Could not locate the 'seam' executable — wrote the bare command 'seam' "
|
|
132
|
+
"(valid once Seam is on PATH / published)."
|
|
133
|
+
)
|
|
134
|
+
if not index_present:
|
|
135
|
+
warnings.append(f"No index at {root}/.seam — run 'seam init' so the agent has something to query.")
|
|
136
|
+
|
|
137
|
+
data = {
|
|
138
|
+
"with_mcp": with_mcp,
|
|
139
|
+
"seam_command": command if with_mcp else None,
|
|
140
|
+
"args": args if with_mcp else None,
|
|
141
|
+
"index_present": index_present,
|
|
142
|
+
"print_config": print_config,
|
|
143
|
+
"results": results,
|
|
144
|
+
"warnings": warnings,
|
|
145
|
+
}
|
|
146
|
+
if json_:
|
|
147
|
+
emit_json(data)
|
|
148
|
+
return
|
|
149
|
+
_render_install(data)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def uninstall_command(
|
|
153
|
+
path: str = typer.Argument(".", help="Project root the entry points at (default: current directory)."),
|
|
154
|
+
target: str = typer.Option("claude", "--target", help="claude | cursor | codex | all"),
|
|
155
|
+
location: str = typer.Option("project", "--location", help="MCP config scope: project | user."),
|
|
156
|
+
json_: bool = typer.Option(False, "--json", help="Emit a structured JSON envelope to stdout."),
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Remove Seam's guidance AND MCP config from a coding agent (reverses `seam install`)."""
|
|
159
|
+
root = Path(path).resolve()
|
|
160
|
+
targets = _select_targets(target)
|
|
161
|
+
if targets is None:
|
|
162
|
+
_fail(json_, "INVALID_INPUT", f"unknown target '{target}' (expected: {', '.join(TARGETS)} or all)")
|
|
163
|
+
|
|
164
|
+
results: list[dict[str, Any]] = []
|
|
165
|
+
for tgt in targets:
|
|
166
|
+
entry: dict[str, Any] = {
|
|
167
|
+
"target": tgt.name,
|
|
168
|
+
"guidance": [{"action": r.action, "path": r.path} for r in tgt.uninstall_guidance(root)],
|
|
169
|
+
}
|
|
170
|
+
if location in tgt.supported_locations():
|
|
171
|
+
res = tgt.uninstall(root, location)
|
|
172
|
+
entry["mcp"] = {"action": res.action, "path": res.path}
|
|
173
|
+
else:
|
|
174
|
+
entry["mcp"] = {"action": "skipped", "path": None}
|
|
175
|
+
results.append(entry)
|
|
176
|
+
|
|
177
|
+
data = {"results": results}
|
|
178
|
+
if json_:
|
|
179
|
+
emit_json(data)
|
|
180
|
+
return
|
|
181
|
+
for entry in results:
|
|
182
|
+
guide_str = ", ".join(f"{Path(g['path']).name}:{g['action']}" for g in entry["guidance"])
|
|
183
|
+
console.print(
|
|
184
|
+
f" [bold]{entry['target']}[/bold] guidance: [dim]{guide_str}[/dim] "
|
|
185
|
+
f"MCP: [dim]{entry['mcp']['action']}[/dim]"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _fail(json_: bool, code: str, message: str) -> NoReturn:
|
|
190
|
+
"""Emit an error in the requested format and exit non-zero (always raises)."""
|
|
191
|
+
if json_:
|
|
192
|
+
emit_json_error(code, message)
|
|
193
|
+
else:
|
|
194
|
+
console.print(f"[red]Error:[/red] {message}")
|
|
195
|
+
raise typer.Exit(code=1)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _render_install(data: dict[str, Any]) -> None:
|
|
199
|
+
"""Human-readable Rich output for `seam install`."""
|
|
200
|
+
if data["print_config"]:
|
|
201
|
+
for entry in data["results"]:
|
|
202
|
+
console.print(f"\n[bold]{entry['target']}[/bold]")
|
|
203
|
+
for prev in entry["guidance_preview"]:
|
|
204
|
+
console.print(f" [dim]{prev['path']}[/dim]")
|
|
205
|
+
console.print(prev["content"])
|
|
206
|
+
if entry["mcp_preview"]:
|
|
207
|
+
console.print(f" [dim]{entry['mcp_preview']['path']} (MCP)[/dim]")
|
|
208
|
+
console.print(entry["mcp_preview"]["config"])
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
mode = "guidance + MCP" if data["with_mcp"] else "guidance"
|
|
212
|
+
console.print(f"[bold]seam install[/bold] [dim]({mode})[/dim]")
|
|
213
|
+
for entry in data["results"]:
|
|
214
|
+
guide_str = ", ".join(f"{Path(g['path']).name}:{g['action']}" for g in entry["guidance"])
|
|
215
|
+
console.print(f" [green]✓ {entry['target']}[/green] guidance: [dim]{guide_str}[/dim]")
|
|
216
|
+
mcp = entry["mcp"]
|
|
217
|
+
if mcp is None:
|
|
218
|
+
continue
|
|
219
|
+
if mcp["action"] == "skipped":
|
|
220
|
+
console.print(f" [yellow]⊘ MCP skipped[/yellow] [dim]({mcp.get('reason', '')})[/dim]")
|
|
221
|
+
else:
|
|
222
|
+
backed = " [yellow](backed up corrupt config)[/yellow]" if mcp.get("backed_up") else ""
|
|
223
|
+
console.print(f" [green]MCP: {mcp['action']}[/green] [dim]{mcp['path']}[/dim]{backed}")
|
|
224
|
+
|
|
225
|
+
for warning in data["warnings"]:
|
|
226
|
+
console.print(f" [yellow]![/yellow] {warning}")
|
|
227
|
+
# Claude project-scoped MCP servers need a one-time approval the first time the agent runs.
|
|
228
|
+
if data["with_mcp"] and any(
|
|
229
|
+
e["target"] == "claude" and e["mcp"] and e["mcp"].get("action") in ("created", "updated")
|
|
230
|
+
for e in data["results"]
|
|
231
|
+
):
|
|
232
|
+
console.print(" [dim]Claude Code will prompt once to approve the project MCP server on next launch.[/dim]")
|