foundry-mcp 0.8.22__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.
Potentially problematic release.
This version of foundry-mcp might be problematic. Click here for more details.
- foundry_mcp/__init__.py +13 -0
- foundry_mcp/cli/__init__.py +67 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +640 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +667 -0
- foundry_mcp/cli/commands/session.py +472 -0
- foundry_mcp/cli/commands/specs.py +686 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +298 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +1454 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1773 -0
- foundry_mcp/core/batch_operations.py +1202 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/journal.py +700 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1376 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +146 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +387 -0
- foundry_mcp/core/prometheus.py +564 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +691 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
- foundry_mcp/core/prompts/plan_review.py +627 -0
- foundry_mcp/core/providers/__init__.py +237 -0
- foundry_mcp/core/providers/base.py +515 -0
- foundry_mcp/core/providers/claude.py +472 -0
- foundry_mcp/core/providers/codex.py +637 -0
- foundry_mcp/core/providers/cursor_agent.py +630 -0
- foundry_mcp/core/providers/detectors.py +515 -0
- foundry_mcp/core/providers/gemini.py +426 -0
- foundry_mcp/core/providers/opencode.py +718 -0
- foundry_mcp/core/providers/opencode_wrapper.js +308 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +857 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1234 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4142 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +1624 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +4119 -0
- foundry_mcp/core/task.py +2463 -0
- foundry_mcp/core/testing.py +839 -0
- foundry_mcp/core/validation.py +2357 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +177 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +300 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +164 -0
- foundry_mcp/dashboard/views/overview.py +96 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +414 -0
- foundry_mcp/server.py +150 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +92 -0
- foundry_mcp/tools/unified/authoring.py +3620 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +268 -0
- foundry_mcp/tools/unified/environment.py +1341 -0
- foundry_mcp/tools/unified/error.py +479 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +640 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +876 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +589 -0
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +1042 -0
- foundry_mcp/tools/unified/review_helpers.py +314 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +565 -0
- foundry_mcp/tools/unified/spec.py +1283 -0
- foundry_mcp/tools/unified/task.py +3846 -0
- foundry_mcp/tools/unified/test.py +431 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.8.22.dist-info/METADATA +344 -0
- foundry_mcp-0.8.22.dist-info/RECORD +153 -0
- foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
- foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""Development utility commands for SDD CLI.
|
|
2
|
+
|
|
3
|
+
Provides commands for skills development including:
|
|
4
|
+
- Documentation generation helpers
|
|
5
|
+
- Installation helpers
|
|
6
|
+
- Server start helpers
|
|
7
|
+
- Development tooling
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from foundry_mcp.cli.logging import cli_command, get_cli_logger
|
|
18
|
+
from foundry_mcp.cli.output import emit_error, emit_success
|
|
19
|
+
from foundry_mcp.cli.registry import get_context
|
|
20
|
+
from foundry_mcp.cli.resilience import (
|
|
21
|
+
FAST_TIMEOUT,
|
|
22
|
+
MEDIUM_TIMEOUT,
|
|
23
|
+
handle_keyboard_interrupt,
|
|
24
|
+
with_sync_timeout,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = get_cli_logger()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.group("dev")
|
|
31
|
+
def dev_group() -> None:
|
|
32
|
+
"""Development utility commands."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dev_group.command("gendocs")
|
|
37
|
+
@click.option(
|
|
38
|
+
"--output-dir",
|
|
39
|
+
default="docs",
|
|
40
|
+
help="Output directory for generated documentation.",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--format",
|
|
44
|
+
"doc_format",
|
|
45
|
+
type=click.Choice(["markdown", "html", "json"]),
|
|
46
|
+
default="markdown",
|
|
47
|
+
help="Output format for documentation.",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--include-private",
|
|
51
|
+
is_flag=True,
|
|
52
|
+
help="Include private/internal APIs in documentation.",
|
|
53
|
+
)
|
|
54
|
+
@click.pass_context
|
|
55
|
+
@cli_command("gendocs")
|
|
56
|
+
@handle_keyboard_interrupt()
|
|
57
|
+
@with_sync_timeout(MEDIUM_TIMEOUT, "Documentation generation timed out")
|
|
58
|
+
def dev_gendocs_cmd(
|
|
59
|
+
ctx: click.Context,
|
|
60
|
+
output_dir: str,
|
|
61
|
+
doc_format: str,
|
|
62
|
+
include_private: bool,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Generate documentation from source code.
|
|
65
|
+
|
|
66
|
+
Scans the codebase and generates API documentation.
|
|
67
|
+
"""
|
|
68
|
+
start_time = time.perf_counter()
|
|
69
|
+
cli_ctx = get_context(ctx)
|
|
70
|
+
|
|
71
|
+
# Determine project root
|
|
72
|
+
project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
|
|
73
|
+
output_path = project_root / output_dir
|
|
74
|
+
|
|
75
|
+
# Create output directory if needed
|
|
76
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
# Check for common doc generators
|
|
79
|
+
generators = []
|
|
80
|
+
|
|
81
|
+
# Check pdoc
|
|
82
|
+
try:
|
|
83
|
+
result = subprocess.run(
|
|
84
|
+
["pdoc", "--version"],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
timeout=5,
|
|
87
|
+
)
|
|
88
|
+
if result.returncode == 0:
|
|
89
|
+
generators.append("pdoc")
|
|
90
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
# Check sphinx
|
|
94
|
+
try:
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
["sphinx-build", "--version"],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
timeout=5,
|
|
99
|
+
)
|
|
100
|
+
if result.returncode == 0:
|
|
101
|
+
generators.append("sphinx")
|
|
102
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
106
|
+
|
|
107
|
+
if not generators:
|
|
108
|
+
emit_success(
|
|
109
|
+
{
|
|
110
|
+
"status": "no_generator",
|
|
111
|
+
"output_dir": str(output_path),
|
|
112
|
+
"format": doc_format,
|
|
113
|
+
"available_generators": [],
|
|
114
|
+
"recommendations": [
|
|
115
|
+
"Install pdoc: pip install pdoc",
|
|
116
|
+
"Or install sphinx: pip install sphinx",
|
|
117
|
+
],
|
|
118
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
emit_success(
|
|
124
|
+
{
|
|
125
|
+
"status": "ready",
|
|
126
|
+
"output_dir": str(output_path),
|
|
127
|
+
"format": doc_format,
|
|
128
|
+
"available_generators": generators,
|
|
129
|
+
"include_private": include_private,
|
|
130
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dev_group.command("install")
|
|
136
|
+
@click.option(
|
|
137
|
+
"--dev",
|
|
138
|
+
is_flag=True,
|
|
139
|
+
help="Install with development dependencies.",
|
|
140
|
+
)
|
|
141
|
+
@click.option(
|
|
142
|
+
"--editable/--no-editable",
|
|
143
|
+
default=True,
|
|
144
|
+
help="Install in editable mode (default: true).",
|
|
145
|
+
)
|
|
146
|
+
@click.option(
|
|
147
|
+
"--extras",
|
|
148
|
+
help="Comma-separated list of extras to install.",
|
|
149
|
+
)
|
|
150
|
+
@click.pass_context
|
|
151
|
+
@cli_command("install")
|
|
152
|
+
@handle_keyboard_interrupt()
|
|
153
|
+
@with_sync_timeout(300, "Installation timed out")
|
|
154
|
+
def dev_install_cmd(
|
|
155
|
+
ctx: click.Context,
|
|
156
|
+
dev: bool,
|
|
157
|
+
editable: bool,
|
|
158
|
+
extras: Optional[str],
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Install the package for development.
|
|
161
|
+
|
|
162
|
+
Installs the current package with optional dev dependencies.
|
|
163
|
+
"""
|
|
164
|
+
start_time = time.perf_counter()
|
|
165
|
+
cli_ctx = get_context(ctx)
|
|
166
|
+
|
|
167
|
+
# Determine project root
|
|
168
|
+
project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
|
|
169
|
+
|
|
170
|
+
# Check for pyproject.toml or setup.py
|
|
171
|
+
has_pyproject = (project_root / "pyproject.toml").exists()
|
|
172
|
+
has_setup = (project_root / "setup.py").exists()
|
|
173
|
+
|
|
174
|
+
if not has_pyproject and not has_setup:
|
|
175
|
+
emit_error(
|
|
176
|
+
"No Python project found",
|
|
177
|
+
code="NO_PROJECT",
|
|
178
|
+
error_type="validation",
|
|
179
|
+
remediation="Ensure pyproject.toml or setup.py exists in the project root",
|
|
180
|
+
details={
|
|
181
|
+
"hint": "Ensure pyproject.toml or setup.py exists",
|
|
182
|
+
"project_root": str(project_root),
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
# Build pip install command
|
|
188
|
+
cmd = ["pip", "install"]
|
|
189
|
+
|
|
190
|
+
if editable:
|
|
191
|
+
cmd.append("-e")
|
|
192
|
+
|
|
193
|
+
# Build package specifier
|
|
194
|
+
package_spec = "."
|
|
195
|
+
if extras:
|
|
196
|
+
package_spec = f".[{extras}]"
|
|
197
|
+
if dev:
|
|
198
|
+
if extras:
|
|
199
|
+
package_spec = f".[dev,{extras}]"
|
|
200
|
+
else:
|
|
201
|
+
package_spec = ".[dev]"
|
|
202
|
+
|
|
203
|
+
cmd.append(package_spec)
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
cmd,
|
|
208
|
+
capture_output=True,
|
|
209
|
+
text=True,
|
|
210
|
+
timeout=300,
|
|
211
|
+
cwd=str(project_root),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
215
|
+
|
|
216
|
+
if result.returncode != 0:
|
|
217
|
+
emit_error(
|
|
218
|
+
"Installation failed",
|
|
219
|
+
code="INSTALL_FAILED",
|
|
220
|
+
error_type="internal",
|
|
221
|
+
remediation="Check the error output and ensure all dependencies are available",
|
|
222
|
+
details={
|
|
223
|
+
"exit_code": result.returncode,
|
|
224
|
+
"stderr": result.stderr[:500] if result.stderr else None,
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
emit_success(
|
|
230
|
+
{
|
|
231
|
+
"status": "installed",
|
|
232
|
+
"editable": editable,
|
|
233
|
+
"dev": dev,
|
|
234
|
+
"extras": extras,
|
|
235
|
+
"project_root": str(project_root),
|
|
236
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
except subprocess.TimeoutExpired:
|
|
241
|
+
emit_error(
|
|
242
|
+
"Installation timed out",
|
|
243
|
+
code="TIMEOUT",
|
|
244
|
+
error_type="internal",
|
|
245
|
+
remediation="Try again with a faster network or fewer dependencies",
|
|
246
|
+
details={"timeout_seconds": 300},
|
|
247
|
+
)
|
|
248
|
+
except FileNotFoundError:
|
|
249
|
+
emit_error(
|
|
250
|
+
"pip not found",
|
|
251
|
+
code="PIP_NOT_FOUND",
|
|
252
|
+
error_type="internal",
|
|
253
|
+
remediation="Ensure pip is installed and in PATH",
|
|
254
|
+
details={"hint": "Ensure pip is installed and in PATH"},
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dev_group.command("start")
|
|
259
|
+
@click.option(
|
|
260
|
+
"--port",
|
|
261
|
+
type=int,
|
|
262
|
+
default=8000,
|
|
263
|
+
help="Port to run the development server on.",
|
|
264
|
+
)
|
|
265
|
+
@click.option(
|
|
266
|
+
"--host",
|
|
267
|
+
default="127.0.0.1",
|
|
268
|
+
help="Host to bind the server to.",
|
|
269
|
+
)
|
|
270
|
+
@click.option(
|
|
271
|
+
"--reload/--no-reload",
|
|
272
|
+
default=True,
|
|
273
|
+
help="Enable auto-reload on file changes.",
|
|
274
|
+
)
|
|
275
|
+
@click.pass_context
|
|
276
|
+
@cli_command("start")
|
|
277
|
+
@handle_keyboard_interrupt()
|
|
278
|
+
@with_sync_timeout(FAST_TIMEOUT, "Server check timed out")
|
|
279
|
+
def dev_start_cmd(
|
|
280
|
+
ctx: click.Context,
|
|
281
|
+
port: int,
|
|
282
|
+
host: str,
|
|
283
|
+
reload: bool,
|
|
284
|
+
) -> None:
|
|
285
|
+
"""Start a development server.
|
|
286
|
+
|
|
287
|
+
Checks for common development server configurations and
|
|
288
|
+
provides instructions for starting.
|
|
289
|
+
"""
|
|
290
|
+
start_time = time.perf_counter()
|
|
291
|
+
cli_ctx = get_context(ctx)
|
|
292
|
+
|
|
293
|
+
# Determine project root
|
|
294
|
+
project_root = cli_ctx.specs_dir.parent if cli_ctx.specs_dir else Path.cwd()
|
|
295
|
+
|
|
296
|
+
# Check for various server configurations
|
|
297
|
+
server_configs = []
|
|
298
|
+
|
|
299
|
+
# Check for uvicorn (FastAPI/Starlette)
|
|
300
|
+
try:
|
|
301
|
+
result = subprocess.run(
|
|
302
|
+
["uvicorn", "--version"],
|
|
303
|
+
capture_output=True,
|
|
304
|
+
timeout=5,
|
|
305
|
+
)
|
|
306
|
+
if result.returncode == 0:
|
|
307
|
+
server_configs.append(
|
|
308
|
+
{
|
|
309
|
+
"name": "uvicorn",
|
|
310
|
+
"command": f"uvicorn main:app --host {host} --port {port}"
|
|
311
|
+
+ (" --reload" if reload else ""),
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
# Check for flask
|
|
318
|
+
try:
|
|
319
|
+
result = subprocess.run(
|
|
320
|
+
["flask", "--version"],
|
|
321
|
+
capture_output=True,
|
|
322
|
+
timeout=5,
|
|
323
|
+
)
|
|
324
|
+
if result.returncode == 0:
|
|
325
|
+
server_configs.append(
|
|
326
|
+
{
|
|
327
|
+
"name": "flask",
|
|
328
|
+
"command": f"flask run --host {host} --port {port}"
|
|
329
|
+
+ (" --reload" if reload else ""),
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
# Check for MCP server config
|
|
336
|
+
mcp_config = project_root / "mcp.json"
|
|
337
|
+
if mcp_config.exists():
|
|
338
|
+
server_configs.append(
|
|
339
|
+
{
|
|
340
|
+
"name": "mcp",
|
|
341
|
+
"command": f"python -m foundry_mcp.server --port {port}",
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
346
|
+
|
|
347
|
+
emit_success(
|
|
348
|
+
{
|
|
349
|
+
"host": host,
|
|
350
|
+
"port": port,
|
|
351
|
+
"reload": reload,
|
|
352
|
+
"available_servers": server_configs,
|
|
353
|
+
"project_root": str(project_root),
|
|
354
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@dev_group.command("check")
|
|
360
|
+
@click.pass_context
|
|
361
|
+
@cli_command("check")
|
|
362
|
+
@handle_keyboard_interrupt()
|
|
363
|
+
@with_sync_timeout(FAST_TIMEOUT, "Environment check timed out")
|
|
364
|
+
def dev_check_cmd(ctx: click.Context) -> None:
|
|
365
|
+
"""Check development environment status.
|
|
366
|
+
|
|
367
|
+
Verifies that all development tools are available.
|
|
368
|
+
"""
|
|
369
|
+
start_time = time.perf_counter()
|
|
370
|
+
get_context(ctx)
|
|
371
|
+
|
|
372
|
+
tools = {}
|
|
373
|
+
|
|
374
|
+
# Check Python
|
|
375
|
+
try:
|
|
376
|
+
result = subprocess.run(
|
|
377
|
+
["python", "--version"],
|
|
378
|
+
capture_output=True,
|
|
379
|
+
text=True,
|
|
380
|
+
timeout=5,
|
|
381
|
+
)
|
|
382
|
+
tools["python"] = {
|
|
383
|
+
"available": result.returncode == 0,
|
|
384
|
+
"version": result.stdout.strip() if result.returncode == 0 else None,
|
|
385
|
+
}
|
|
386
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
387
|
+
tools["python"] = {"available": False}
|
|
388
|
+
|
|
389
|
+
# Check pip
|
|
390
|
+
try:
|
|
391
|
+
result = subprocess.run(
|
|
392
|
+
["pip", "--version"],
|
|
393
|
+
capture_output=True,
|
|
394
|
+
text=True,
|
|
395
|
+
timeout=5,
|
|
396
|
+
)
|
|
397
|
+
tools["pip"] = {
|
|
398
|
+
"available": result.returncode == 0,
|
|
399
|
+
"version": result.stdout.split()[1] if result.returncode == 0 else None,
|
|
400
|
+
}
|
|
401
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
402
|
+
tools["pip"] = {"available": False}
|
|
403
|
+
|
|
404
|
+
# Check git
|
|
405
|
+
try:
|
|
406
|
+
result = subprocess.run(
|
|
407
|
+
["git", "--version"],
|
|
408
|
+
capture_output=True,
|
|
409
|
+
text=True,
|
|
410
|
+
timeout=5,
|
|
411
|
+
)
|
|
412
|
+
tools["git"] = {
|
|
413
|
+
"available": result.returncode == 0,
|
|
414
|
+
"version": result.stdout.strip() if result.returncode == 0 else None,
|
|
415
|
+
}
|
|
416
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
417
|
+
tools["git"] = {"available": False}
|
|
418
|
+
|
|
419
|
+
# Check node (optional)
|
|
420
|
+
try:
|
|
421
|
+
result = subprocess.run(
|
|
422
|
+
["node", "--version"],
|
|
423
|
+
capture_output=True,
|
|
424
|
+
text=True,
|
|
425
|
+
timeout=5,
|
|
426
|
+
)
|
|
427
|
+
tools["node"] = {
|
|
428
|
+
"available": result.returncode == 0,
|
|
429
|
+
"version": result.stdout.strip() if result.returncode == 0 else None,
|
|
430
|
+
}
|
|
431
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
432
|
+
tools["node"] = {"available": False}
|
|
433
|
+
|
|
434
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
435
|
+
|
|
436
|
+
all_required = all(
|
|
437
|
+
tools.get(t, {}).get("available", False) for t in ["python", "pip", "git"]
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
emit_success(
|
|
441
|
+
{
|
|
442
|
+
"tools": tools,
|
|
443
|
+
"all_required_available": all_required,
|
|
444
|
+
"telemetry": {"duration_ms": round(duration_ms, 2)},
|
|
445
|
+
}
|
|
446
|
+
)
|