foundry-mcp 0.3.3__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.
- foundry_mcp/__init__.py +7 -0
- foundry_mcp/cli/__init__.py +80 -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 +633 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +652 -0
- foundry_mcp/cli/commands/session.py +479 -0
- foundry_mcp/cli/commands/specs.py +856 -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 +259 -0
- foundry_mcp/cli/flags.py +266 -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 +850 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1636 -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/feature_flags.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/journal.py +694 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1350 -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 +123 -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 +317 -0
- foundry_mcp/core/prometheus.py +577 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +546 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +511 -0
- foundry_mcp/core/prompts/plan_review.py +623 -0
- foundry_mcp/core/providers/__init__.py +225 -0
- foundry_mcp/core/providers/base.py +476 -0
- foundry_mcp/core/providers/claude.py +460 -0
- foundry_mcp/core/providers/codex.py +619 -0
- foundry_mcp/core/providers/cursor_agent.py +642 -0
- foundry_mcp/core/providers/detectors.py +488 -0
- foundry_mcp/core/providers/gemini.py +405 -0
- foundry_mcp/core/providers/opencode.py +616 -0
- foundry_mcp/core/providers/opencode_wrapper.js +302 -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 +729 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +934 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +1650 -0
- foundry_mcp/core/task.py +1289 -0
- foundry_mcp/core/testing.py +450 -0
- foundry_mcp/core/validation.py +2081 -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 +234 -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 +289 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +174 -0
- foundry_mcp/dashboard/views/overview.py +160 -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/sdd-spec-schema.json +386 -0
- foundry_mcp/server.py +164 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +71 -0
- foundry_mcp/tools/unified/authoring.py +1487 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +198 -0
- foundry_mcp/tools/unified/environment.py +939 -0
- foundry_mcp/tools/unified/error.py +462 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +632 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +745 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +629 -0
- foundry_mcp/tools/unified/review.py +685 -0
- foundry_mcp/tools/unified/review_helpers.py +299 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +580 -0
- foundry_mcp/tools/unified/spec.py +808 -0
- foundry_mcp/tools/unified/task.py +2202 -0
- foundry_mcp/tools/unified/test.py +370 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.3.3.dist-info/METADATA +337 -0
- foundry_mcp-0.3.3.dist-info/RECORD +135 -0
- foundry_mcp-0.3.3.dist-info/WHEEL +4 -0
- foundry_mcp-0.3.3.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.3.3.dist-info/licenses/LICENSE +21 -0
foundry_mcp/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""SDD CLI - Native command-line interface for Spec-Driven Development.
|
|
2
|
+
|
|
3
|
+
This CLI provides JSON-only output designed for AI coding assistants.
|
|
4
|
+
All commands emit structured JSON to stdout for reliable parsing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from foundry_mcp.cli.config import CLIContext, create_context
|
|
8
|
+
from foundry_mcp.cli.flags import (
|
|
9
|
+
CLIFlagRegistry,
|
|
10
|
+
apply_cli_flag_overrides,
|
|
11
|
+
flags_for_discovery,
|
|
12
|
+
get_cli_flags,
|
|
13
|
+
with_flag_options,
|
|
14
|
+
)
|
|
15
|
+
from foundry_mcp.cli.logging import (
|
|
16
|
+
CLILogContext,
|
|
17
|
+
cli_command,
|
|
18
|
+
get_cli_logger,
|
|
19
|
+
get_request_id,
|
|
20
|
+
set_request_id,
|
|
21
|
+
)
|
|
22
|
+
from foundry_mcp.cli.main import cli
|
|
23
|
+
from foundry_mcp.cli.output import emit, emit_error, emit_success
|
|
24
|
+
from foundry_mcp.cli.registry import get_context, set_context
|
|
25
|
+
from foundry_mcp.cli.resilience import (
|
|
26
|
+
FAST_TIMEOUT,
|
|
27
|
+
MEDIUM_TIMEOUT,
|
|
28
|
+
SLOW_TIMEOUT,
|
|
29
|
+
with_sync_timeout,
|
|
30
|
+
cli_retryable,
|
|
31
|
+
handle_keyboard_interrupt,
|
|
32
|
+
)
|
|
33
|
+
from foundry_mcp.cli.context import (
|
|
34
|
+
ContextSession,
|
|
35
|
+
ContextTracker,
|
|
36
|
+
get_context_tracker,
|
|
37
|
+
get_session_status,
|
|
38
|
+
record_consultation,
|
|
39
|
+
start_cli_session,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Entry point
|
|
44
|
+
"cli",
|
|
45
|
+
# Context
|
|
46
|
+
"CLIContext",
|
|
47
|
+
"create_context",
|
|
48
|
+
"get_context",
|
|
49
|
+
"set_context",
|
|
50
|
+
# Output
|
|
51
|
+
"emit",
|
|
52
|
+
"emit_error",
|
|
53
|
+
"emit_success",
|
|
54
|
+
# Feature flags
|
|
55
|
+
"CLIFlagRegistry",
|
|
56
|
+
"apply_cli_flag_overrides",
|
|
57
|
+
"flags_for_discovery",
|
|
58
|
+
"get_cli_flags",
|
|
59
|
+
"with_flag_options",
|
|
60
|
+
# Logging
|
|
61
|
+
"CLILogContext",
|
|
62
|
+
"cli_command",
|
|
63
|
+
"get_cli_logger",
|
|
64
|
+
"get_request_id",
|
|
65
|
+
"set_request_id",
|
|
66
|
+
# Resilience
|
|
67
|
+
"FAST_TIMEOUT",
|
|
68
|
+
"MEDIUM_TIMEOUT",
|
|
69
|
+
"SLOW_TIMEOUT",
|
|
70
|
+
"with_sync_timeout",
|
|
71
|
+
"cli_retryable",
|
|
72
|
+
"handle_keyboard_interrupt",
|
|
73
|
+
# Session/Context tracking
|
|
74
|
+
"ContextSession",
|
|
75
|
+
"ContextTracker",
|
|
76
|
+
"get_context_tracker",
|
|
77
|
+
"get_session_status",
|
|
78
|
+
"record_consultation",
|
|
79
|
+
"start_cli_session",
|
|
80
|
+
]
|
foundry_mcp/cli/agent.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Agent detection and feature gating for CLI commands.
|
|
2
|
+
|
|
3
|
+
Provides mechanisms to detect which AI coding assistant is running the CLI
|
|
4
|
+
and gate features that are only available for specific agents.
|
|
5
|
+
|
|
6
|
+
Agent types:
|
|
7
|
+
- claude-code: Anthropic's Claude Code (has transcript access)
|
|
8
|
+
- cursor: Cursor IDE
|
|
9
|
+
- generic: Unknown/default agent
|
|
10
|
+
|
|
11
|
+
Configuration:
|
|
12
|
+
- Set FOUNDRY_MCP_AGENT_TYPE in MCP server config (env section)
|
|
13
|
+
- The CLI inherits this from the MCP server environment
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from functools import wraps
|
|
18
|
+
from typing import Callable, TypeVar
|
|
19
|
+
|
|
20
|
+
from foundry_mcp.cli.output import emit_success
|
|
21
|
+
|
|
22
|
+
# Valid agent types
|
|
23
|
+
AGENT_TYPES = frozenset({"claude-code", "cursor", "generic"})
|
|
24
|
+
DEFAULT_AGENT_TYPE = "generic"
|
|
25
|
+
|
|
26
|
+
F = TypeVar("F", bound=Callable)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_agent_type() -> str:
|
|
30
|
+
"""Get the configured agent type from environment.
|
|
31
|
+
|
|
32
|
+
Set via MCP server config:
|
|
33
|
+
"env": {"FOUNDRY_MCP_AGENT_TYPE": "claude-code"}
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Agent type string (claude-code, cursor, generic).
|
|
37
|
+
"""
|
|
38
|
+
env_agent = os.environ.get("FOUNDRY_MCP_AGENT_TYPE", "")
|
|
39
|
+
agent = env_agent.lower().strip()
|
|
40
|
+
return agent if agent in AGENT_TYPES else DEFAULT_AGENT_TYPE
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def agent_gated(required_agent: str) -> Callable[[F], F]:
|
|
44
|
+
"""Decorator for agent-specific commands.
|
|
45
|
+
|
|
46
|
+
When the current agent type doesn't match the required agent,
|
|
47
|
+
returns a success response with a warning indicating the feature
|
|
48
|
+
is unavailable, rather than failing.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
required_agent: The agent type required for this command (e.g., "claude-code").
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Decorated function that gates execution by agent type.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
@session.command("token-usage")
|
|
58
|
+
@agent_gated("claude-code")
|
|
59
|
+
def token_usage_cmd():
|
|
60
|
+
# Only runs when agent_type == "claude-code"
|
|
61
|
+
...
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def decorator(f: F) -> F:
|
|
65
|
+
@wraps(f)
|
|
66
|
+
def wrapper(*args, **kwargs):
|
|
67
|
+
current_agent = get_agent_type()
|
|
68
|
+
|
|
69
|
+
if current_agent != required_agent:
|
|
70
|
+
emit_success(
|
|
71
|
+
{
|
|
72
|
+
"available": False,
|
|
73
|
+
"reason": f"This feature requires agent_type='{required_agent}'",
|
|
74
|
+
"current_agent": current_agent,
|
|
75
|
+
"hint": f"Set FOUNDRY_MCP_AGENT_TYPE={required_agent} or configure in foundry-mcp.toml",
|
|
76
|
+
},
|
|
77
|
+
meta={
|
|
78
|
+
"warning": f"Feature unavailable for agent_type='{current_agent}'"
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
return f(*args, **kwargs)
|
|
84
|
+
|
|
85
|
+
return wrapper # type: ignore
|
|
86
|
+
|
|
87
|
+
return decorator
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def is_claude_code() -> bool:
|
|
91
|
+
"""Check if the current agent is Claude Code.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if agent_type is "claude-code".
|
|
95
|
+
"""
|
|
96
|
+
return get_agent_type() == "claude-code"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CLI command groups.
|
|
2
|
+
|
|
3
|
+
The CLI is organized into domain groups (e.g. `specs`, `tasks`, `test`).
|
|
4
|
+
Legacy top-level aliases are intentionally not exported.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from foundry_mcp.cli.commands.cache import cache
|
|
8
|
+
from foundry_mcp.cli.commands.dashboard import dashboard_group
|
|
9
|
+
from foundry_mcp.cli.commands.dev import dev_group
|
|
10
|
+
from foundry_mcp.cli.commands.journal import journal
|
|
11
|
+
from foundry_mcp.cli.commands.lifecycle import lifecycle
|
|
12
|
+
from foundry_mcp.cli.commands.modify import modify_group
|
|
13
|
+
from foundry_mcp.cli.commands.plan import plan_group
|
|
14
|
+
from foundry_mcp.cli.commands.pr import pr_group
|
|
15
|
+
from foundry_mcp.cli.commands.review import review_group
|
|
16
|
+
from foundry_mcp.cli.commands.session import session
|
|
17
|
+
from foundry_mcp.cli.commands.specs import specs
|
|
18
|
+
from foundry_mcp.cli.commands.tasks import tasks
|
|
19
|
+
from foundry_mcp.cli.commands.testing import test_group
|
|
20
|
+
from foundry_mcp.cli.commands.validate import validate_group
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"cache",
|
|
24
|
+
"dashboard_group",
|
|
25
|
+
"dev_group",
|
|
26
|
+
"journal",
|
|
27
|
+
"lifecycle",
|
|
28
|
+
"modify_group",
|
|
29
|
+
"plan_group",
|
|
30
|
+
"pr_group",
|
|
31
|
+
"review_group",
|
|
32
|
+
"session",
|
|
33
|
+
"specs",
|
|
34
|
+
"tasks",
|
|
35
|
+
"test_group",
|
|
36
|
+
"validate_group",
|
|
37
|
+
]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Cache management commands for SDD CLI.
|
|
2
|
+
|
|
3
|
+
Provides commands for inspecting and managing the AI consultation cache.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from foundry_mcp.cli.logging import cli_command, get_cli_logger
|
|
11
|
+
from foundry_mcp.cli.output import emit_success
|
|
12
|
+
from foundry_mcp.cli.resilience import (
|
|
13
|
+
FAST_TIMEOUT,
|
|
14
|
+
handle_keyboard_interrupt,
|
|
15
|
+
with_sync_timeout,
|
|
16
|
+
)
|
|
17
|
+
from foundry_mcp.core.cache import CacheManager, is_cache_enabled
|
|
18
|
+
|
|
19
|
+
logger = get_cli_logger()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group("cache")
|
|
23
|
+
def cache() -> None:
|
|
24
|
+
"""AI consultation cache management."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@cache.command("info")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
@cli_command("info")
|
|
31
|
+
@handle_keyboard_interrupt()
|
|
32
|
+
@with_sync_timeout(FAST_TIMEOUT, "Cache info lookup timed out")
|
|
33
|
+
def cache_info_cmd(ctx: click.Context) -> None:
|
|
34
|
+
"""Show cache information and statistics.
|
|
35
|
+
|
|
36
|
+
Displays cache location, size, and entry counts.
|
|
37
|
+
"""
|
|
38
|
+
if not is_cache_enabled():
|
|
39
|
+
emit_success(
|
|
40
|
+
{
|
|
41
|
+
"enabled": False,
|
|
42
|
+
"message": "Cache is disabled",
|
|
43
|
+
"hint": "Unset FOUNDRY_MCP_CACHE_DISABLED to enable caching",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
manager = CacheManager()
|
|
49
|
+
stats = manager.get_stats()
|
|
50
|
+
|
|
51
|
+
emit_success(
|
|
52
|
+
{
|
|
53
|
+
"enabled": True,
|
|
54
|
+
**stats,
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@cache.command("clear")
|
|
60
|
+
@click.option("--spec-id", help="Only clear entries for this spec ID.")
|
|
61
|
+
@click.option(
|
|
62
|
+
"--review-type",
|
|
63
|
+
type=click.Choice(["fidelity", "plan"]),
|
|
64
|
+
help="Only clear entries of this review type.",
|
|
65
|
+
)
|
|
66
|
+
@click.pass_context
|
|
67
|
+
@cli_command("clear")
|
|
68
|
+
@handle_keyboard_interrupt()
|
|
69
|
+
@with_sync_timeout(FAST_TIMEOUT, "Cache clear timed out")
|
|
70
|
+
def cache_clear_cmd(
|
|
71
|
+
ctx: click.Context,
|
|
72
|
+
spec_id: Optional[str],
|
|
73
|
+
review_type: Optional[str],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Clear cache entries with optional filters.
|
|
76
|
+
|
|
77
|
+
Without filters, clears all cache entries.
|
|
78
|
+
Use --spec-id and/or --review-type to filter.
|
|
79
|
+
"""
|
|
80
|
+
if not is_cache_enabled():
|
|
81
|
+
emit_success(
|
|
82
|
+
{
|
|
83
|
+
"enabled": False,
|
|
84
|
+
"entries_deleted": 0,
|
|
85
|
+
"message": "Cache is disabled",
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
manager = CacheManager()
|
|
91
|
+
deleted = manager.clear(spec_id=spec_id, review_type=review_type)
|
|
92
|
+
|
|
93
|
+
filters = {}
|
|
94
|
+
if spec_id:
|
|
95
|
+
filters["spec_id"] = spec_id
|
|
96
|
+
if review_type:
|
|
97
|
+
filters["review_type"] = review_type
|
|
98
|
+
|
|
99
|
+
emit_success(
|
|
100
|
+
{
|
|
101
|
+
"entries_deleted": deleted,
|
|
102
|
+
"filters": filters if filters else None,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@cache.command("cleanup")
|
|
108
|
+
@click.pass_context
|
|
109
|
+
@cli_command("cleanup")
|
|
110
|
+
@handle_keyboard_interrupt()
|
|
111
|
+
@with_sync_timeout(FAST_TIMEOUT, "Cache cleanup timed out")
|
|
112
|
+
def cache_cleanup_cmd(ctx: click.Context) -> None:
|
|
113
|
+
"""Remove expired cache entries.
|
|
114
|
+
|
|
115
|
+
Cleans up entries that have exceeded their TTL.
|
|
116
|
+
"""
|
|
117
|
+
if not is_cache_enabled():
|
|
118
|
+
emit_success(
|
|
119
|
+
{
|
|
120
|
+
"enabled": False,
|
|
121
|
+
"entries_removed": 0,
|
|
122
|
+
"message": "Cache is disabled",
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
manager = CacheManager()
|
|
128
|
+
removed = manager.cleanup_expired()
|
|
129
|
+
|
|
130
|
+
emit_success(
|
|
131
|
+
{
|
|
132
|
+
"entries_removed": removed,
|
|
133
|
+
"message": f"Removed {removed} expired entries"
|
|
134
|
+
if removed
|
|
135
|
+
else "No expired entries found",
|
|
136
|
+
}
|
|
137
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Dashboard CLI commands.
|
|
2
|
+
|
|
3
|
+
Provides commands for starting, stopping, and managing the Streamlit dashboard.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from foundry_mcp.cli.output import emit, emit_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group("dashboard")
|
|
12
|
+
def dashboard_group() -> None:
|
|
13
|
+
"""Dashboard server management commands.
|
|
14
|
+
|
|
15
|
+
Start, stop, and manage the Streamlit-based observability dashboard.
|
|
16
|
+
"""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dashboard_group.command("start")
|
|
21
|
+
@click.option(
|
|
22
|
+
"--port",
|
|
23
|
+
"-p",
|
|
24
|
+
default=8501,
|
|
25
|
+
type=int,
|
|
26
|
+
help="Port to run dashboard on (default: 8501)",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--host",
|
|
30
|
+
"-H",
|
|
31
|
+
default="127.0.0.1",
|
|
32
|
+
help="Host to bind to (default: 127.0.0.1 for localhost only)",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--no-browser",
|
|
36
|
+
is_flag=True,
|
|
37
|
+
default=False,
|
|
38
|
+
help="Don't automatically open browser",
|
|
39
|
+
)
|
|
40
|
+
@click.pass_context
|
|
41
|
+
def dashboard_start_cmd(
|
|
42
|
+
ctx: click.Context,
|
|
43
|
+
port: int,
|
|
44
|
+
host: str,
|
|
45
|
+
no_browser: bool,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Start the Streamlit dashboard server.
|
|
48
|
+
|
|
49
|
+
Launches the dashboard in a background process and optionally opens
|
|
50
|
+
your browser to view it.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
# Start with defaults (localhost:8501, opens browser)
|
|
54
|
+
foundry-cli dashboard start
|
|
55
|
+
|
|
56
|
+
# Start on custom port without browser
|
|
57
|
+
foundry-cli dashboard start --port 8080 --no-browser
|
|
58
|
+
|
|
59
|
+
# Expose to network (be careful with security)
|
|
60
|
+
foundry-cli dashboard start --host 0.0.0.0
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
from foundry_mcp.dashboard import launch_dashboard
|
|
64
|
+
|
|
65
|
+
result = launch_dashboard(
|
|
66
|
+
host=host,
|
|
67
|
+
port=port,
|
|
68
|
+
open_browser=not no_browser,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if result.get("success"):
|
|
72
|
+
emit(
|
|
73
|
+
{
|
|
74
|
+
"success": True,
|
|
75
|
+
"message": result.get("message", "Dashboard started"),
|
|
76
|
+
"url": result.get("url"),
|
|
77
|
+
"pid": result.get("pid"),
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
emit_error(result.get("message", "Failed to start dashboard"))
|
|
82
|
+
|
|
83
|
+
except ImportError:
|
|
84
|
+
emit_error(
|
|
85
|
+
"Dashboard dependencies not installed. "
|
|
86
|
+
"Install with: pip install foundry-mcp[dashboard]"
|
|
87
|
+
)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
emit_error(f"Failed to start dashboard: {e}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dashboard_group.command("stop")
|
|
93
|
+
@click.pass_context
|
|
94
|
+
def dashboard_stop_cmd(ctx: click.Context) -> None:
|
|
95
|
+
"""Stop the running dashboard server.
|
|
96
|
+
|
|
97
|
+
Terminates the dashboard process if one is running.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
from foundry_mcp.dashboard import stop_dashboard
|
|
101
|
+
|
|
102
|
+
result = stop_dashboard()
|
|
103
|
+
|
|
104
|
+
if result.get("success"):
|
|
105
|
+
emit(
|
|
106
|
+
{
|
|
107
|
+
"success": True,
|
|
108
|
+
"message": result.get("message", "Dashboard stopped"),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
emit(
|
|
113
|
+
{
|
|
114
|
+
"success": False,
|
|
115
|
+
"message": result.get("message", "No dashboard to stop"),
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
except ImportError:
|
|
120
|
+
emit_error("Dashboard module not available")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
emit_error(f"Failed to stop dashboard: {e}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dashboard_group.command("status")
|
|
126
|
+
@click.pass_context
|
|
127
|
+
def dashboard_status_cmd(ctx: click.Context) -> None:
|
|
128
|
+
"""Check if dashboard is running.
|
|
129
|
+
|
|
130
|
+
Shows the current status of the dashboard server process.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
from foundry_mcp.dashboard import get_dashboard_status
|
|
134
|
+
|
|
135
|
+
status = get_dashboard_status()
|
|
136
|
+
|
|
137
|
+
emit(
|
|
138
|
+
{
|
|
139
|
+
"running": status.get("running", False),
|
|
140
|
+
"pid": status.get("pid"),
|
|
141
|
+
"exit_code": status.get("exit_code"),
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except ImportError:
|
|
146
|
+
emit({"running": False, "message": "Dashboard module not available"})
|
|
147
|
+
except Exception as e:
|
|
148
|
+
emit_error(f"Failed to get dashboard status: {e}")
|