golf-mcp 0.1.10__py3-none-any.whl → 0.1.12__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 golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +1 -1
- golf/auth/__init__.py +38 -26
- golf/auth/api_key.py +16 -23
- golf/auth/helpers.py +68 -54
- golf/auth/oauth.py +340 -277
- golf/auth/provider.py +58 -53
- golf/cli/__init__.py +1 -1
- golf/cli/main.py +202 -82
- golf/commands/__init__.py +1 -1
- golf/commands/build.py +31 -25
- golf/commands/init.py +119 -80
- golf/commands/run.py +14 -13
- golf/core/__init__.py +1 -1
- golf/core/builder.py +478 -353
- golf/core/builder_auth.py +115 -107
- golf/core/builder_telemetry.py +12 -9
- golf/core/config.py +62 -46
- golf/core/parser.py +174 -136
- golf/core/telemetry.py +169 -69
- golf/core/transformer.py +53 -55
- golf/examples/__init__.py +0 -1
- golf/examples/api_key/pre_build.py +2 -2
- golf/examples/api_key/tools/issues/create.py +35 -36
- golf/examples/api_key/tools/issues/list.py +42 -37
- golf/examples/api_key/tools/repos/list.py +50 -29
- golf/examples/api_key/tools/search/code.py +50 -37
- golf/examples/api_key/tools/users/get.py +21 -20
- golf/examples/basic/pre_build.py +4 -4
- golf/examples/basic/prompts/welcome.py +6 -7
- golf/examples/basic/resources/current_time.py +10 -9
- golf/examples/basic/resources/info.py +6 -5
- golf/examples/basic/resources/weather/common.py +16 -10
- golf/examples/basic/resources/weather/current.py +15 -11
- golf/examples/basic/resources/weather/forecast.py +15 -11
- golf/examples/basic/tools/github_user.py +19 -21
- golf/examples/basic/tools/hello.py +10 -6
- golf/examples/basic/tools/payments/charge.py +34 -25
- golf/examples/basic/tools/payments/common.py +8 -6
- golf/examples/basic/tools/payments/refund.py +29 -25
- golf/telemetry/__init__.py +6 -6
- golf/telemetry/instrumentation.py +781 -276
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/METADATA +1 -1
- golf_mcp-0.1.12.dist-info/RECORD +55 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/WHEEL +1 -1
- golf_mcp-0.1.10.dist-info/RECORD +0 -55
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.10.dist-info → golf_mcp-0.1.12.dist-info}/top_level.txt +0 -0
golf/auth/provider.py
CHANGED
|
@@ -4,107 +4,112 @@ This module defines the ProviderConfig class used to configure
|
|
|
4
4
|
OAuth authentication for GolfMCP servers.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
8
9
|
from pydantic import BaseModel, Field, field_validator
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ProviderConfig(BaseModel):
|
|
12
13
|
"""Configuration for an OAuth2 provider.
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
This class defines the configuration for an OAuth2 provider,
|
|
15
16
|
including the endpoints, credentials, and other settings needed
|
|
16
17
|
to authenticate with the provider.
|
|
17
18
|
"""
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
# Provider identification
|
|
20
21
|
provider: str = Field(
|
|
21
|
-
...,
|
|
22
|
-
description="Provider type (e.g., 'github', 'google', 'custom')"
|
|
22
|
+
..., description="Provider type (e.g., 'github', 'google', 'custom')"
|
|
23
23
|
)
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
# OAuth credentials - names of environment variables to read at runtime
|
|
26
|
-
client_id_env_var: str = Field(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
client_id_env_var: str = Field(
|
|
27
|
+
..., description="Name of environment variable for Client ID"
|
|
28
|
+
)
|
|
29
|
+
client_secret_env_var: str = Field(
|
|
30
|
+
..., description="Name of environment variable for Client Secret"
|
|
31
|
+
)
|
|
32
|
+
|
|
29
33
|
# These fields will store the actual values read at runtime in dist/server.py
|
|
30
34
|
# They are made optional here as they are resolved in the generated code.
|
|
31
|
-
client_id:
|
|
32
|
-
|
|
35
|
+
client_id: str | None = Field(
|
|
36
|
+
None, description="OAuth client ID (resolved at runtime)"
|
|
37
|
+
)
|
|
38
|
+
client_secret: str | None = Field(
|
|
39
|
+
None, description="OAuth client secret (resolved at runtime)"
|
|
40
|
+
)
|
|
33
41
|
|
|
34
42
|
# OAuth endpoints (can be baked in)
|
|
35
43
|
authorize_url: str = Field(..., description="Authorization endpoint URL")
|
|
36
44
|
token_url: str = Field(..., description="Token endpoint URL")
|
|
37
|
-
userinfo_url:
|
|
38
|
-
None,
|
|
39
|
-
description="User info endpoint URL (for OIDC providers)"
|
|
45
|
+
userinfo_url: str | None = Field(
|
|
46
|
+
None, description="User info endpoint URL (for OIDC providers)"
|
|
40
47
|
)
|
|
41
|
-
|
|
42
|
-
jwks_uri:
|
|
43
|
-
None,
|
|
44
|
-
description="JSON Web Key Set URI (for token validation)"
|
|
48
|
+
|
|
49
|
+
jwks_uri: str | None = Field(
|
|
50
|
+
None, description="JSON Web Key Set URI (for token validation)"
|
|
45
51
|
)
|
|
46
|
-
|
|
47
|
-
scopes:
|
|
48
|
-
default_factory=list,
|
|
49
|
-
description="OAuth scopes to request from the provider"
|
|
52
|
+
|
|
53
|
+
scopes: list[str] = Field(
|
|
54
|
+
default_factory=list, description="OAuth scopes to request from the provider"
|
|
50
55
|
)
|
|
51
|
-
|
|
52
|
-
issuer_url:
|
|
53
|
-
None,
|
|
54
|
-
description="OIDC issuer URL for discovery (if using OIDC) - will be overridden by runtime value in server.py"
|
|
56
|
+
|
|
57
|
+
issuer_url: str | None = Field(
|
|
58
|
+
None,
|
|
59
|
+
description="OIDC issuer URL for discovery (if using OIDC) - will be overridden by runtime value in server.py",
|
|
55
60
|
)
|
|
56
|
-
|
|
61
|
+
|
|
57
62
|
callback_path: str = Field(
|
|
58
|
-
"/auth/callback",
|
|
59
|
-
description="Path on this server where the IdP should redirect after authentication"
|
|
63
|
+
"/auth/callback",
|
|
64
|
+
description="Path on this server where the IdP should redirect after authentication",
|
|
60
65
|
)
|
|
61
|
-
|
|
66
|
+
|
|
62
67
|
# JWT configuration
|
|
63
|
-
jwt_secret_env_var: str = Field(
|
|
64
|
-
|
|
68
|
+
jwt_secret_env_var: str = Field(
|
|
69
|
+
..., description="Name of environment variable for JWT Secret"
|
|
70
|
+
)
|
|
71
|
+
jwt_secret: str | None = Field(
|
|
72
|
+
None, description="Secret key for signing JWT tokens (resolved at runtime)"
|
|
73
|
+
)
|
|
65
74
|
token_expiration: int = Field(
|
|
66
|
-
3600,
|
|
67
|
-
description="JWT token expiration time in seconds",
|
|
68
|
-
ge=60,
|
|
69
|
-
le=86400
|
|
75
|
+
3600, description="JWT token expiration time in seconds", ge=60, le=86400
|
|
70
76
|
)
|
|
71
|
-
|
|
72
|
-
settings:
|
|
73
|
-
default_factory=dict,
|
|
74
|
-
description="Additional provider-specific settings"
|
|
77
|
+
|
|
78
|
+
settings: dict[str, Any] = Field(
|
|
79
|
+
default_factory=dict, description="Additional provider-specific settings"
|
|
75
80
|
)
|
|
76
|
-
|
|
77
|
-
@field_validator(
|
|
81
|
+
|
|
82
|
+
@field_validator("provider")
|
|
78
83
|
@classmethod
|
|
79
84
|
def validate_provider(cls, value: str) -> str:
|
|
80
85
|
"""Validate the provider type.
|
|
81
|
-
|
|
86
|
+
|
|
82
87
|
Ensures the provider type is a valid, supported provider.
|
|
83
|
-
|
|
88
|
+
|
|
84
89
|
Args:
|
|
85
90
|
value: The provider type
|
|
86
|
-
|
|
91
|
+
|
|
87
92
|
Returns:
|
|
88
93
|
The validated provider type
|
|
89
|
-
|
|
94
|
+
|
|
90
95
|
Raises:
|
|
91
96
|
ValueError: If the provider type is not supported
|
|
92
97
|
"""
|
|
93
|
-
known_providers = {
|
|
94
|
-
|
|
95
|
-
if value not in known_providers and not value.startswith(
|
|
98
|
+
known_providers = {"custom", "github", "google", "jwks"}
|
|
99
|
+
|
|
100
|
+
if value not in known_providers and not value.startswith("custom:"):
|
|
96
101
|
raise ValueError(
|
|
97
102
|
f"Unknown provider: '{value}'. Must be one of {known_providers} "
|
|
98
103
|
"or start with 'custom:'"
|
|
99
104
|
)
|
|
100
105
|
return value
|
|
101
|
-
|
|
106
|
+
|
|
102
107
|
def get_provider_name(self) -> str:
|
|
103
108
|
"""Get a clean provider name for display purposes.
|
|
104
|
-
|
|
109
|
+
|
|
105
110
|
Returns:
|
|
106
111
|
A human-readable provider name
|
|
107
112
|
"""
|
|
108
|
-
if self.provider.startswith(
|
|
113
|
+
if self.provider.startswith("custom:"):
|
|
109
114
|
return self.provider[7:] # Remove 'custom:' prefix
|
|
110
|
-
return self.provider.capitalize()
|
|
115
|
+
return self.provider.capitalize()
|
golf/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"""CLI package for the GolfMCP framework."""
|
|
1
|
+
"""CLI package for the GolfMCP framework."""
|
golf/cli/main.py
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
"""CLI entry points for GolfMCP."""
|
|
2
2
|
|
|
3
|
+
import atexit
|
|
3
4
|
import os
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
import atexit
|
|
7
6
|
|
|
8
7
|
import typer
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
from rich.panel import Panel
|
|
11
10
|
|
|
12
11
|
from golf import __version__
|
|
13
|
-
from golf.core.config import
|
|
14
|
-
from golf.core.telemetry import
|
|
12
|
+
from golf.core.config import find_project_root, load_settings
|
|
13
|
+
from golf.core.telemetry import (
|
|
14
|
+
is_telemetry_enabled,
|
|
15
|
+
set_telemetry_enabled,
|
|
16
|
+
shutdown,
|
|
17
|
+
track_event,
|
|
18
|
+
)
|
|
15
19
|
|
|
16
20
|
# Create console for rich output
|
|
17
21
|
console = Console()
|
|
@@ -48,24 +52,39 @@ def callback(
|
|
|
48
52
|
False, "--verbose", "-v", help="Increase verbosity of output."
|
|
49
53
|
),
|
|
50
54
|
no_telemetry: bool = typer.Option(
|
|
51
|
-
False,
|
|
55
|
+
False,
|
|
56
|
+
"--no-telemetry",
|
|
57
|
+
help="Disable telemetry collection (persists for future commands).",
|
|
58
|
+
),
|
|
59
|
+
test: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"--test",
|
|
62
|
+
hidden=True,
|
|
63
|
+
help="Run in test mode (disables telemetry for this execution only).",
|
|
52
64
|
),
|
|
53
65
|
) -> None:
|
|
54
66
|
"""GolfMCP: A Pythonic framework for building MCP servers with zero boilerplate."""
|
|
55
67
|
# Set verbosity in environment for other components to access
|
|
56
68
|
if verbose:
|
|
57
69
|
os.environ["GOLF_VERBOSE"] = "1"
|
|
58
|
-
|
|
59
|
-
# Set
|
|
70
|
+
|
|
71
|
+
# Set test mode if flag is used (temporary, just for this execution)
|
|
72
|
+
if test:
|
|
73
|
+
set_telemetry_enabled(False, persist=False)
|
|
74
|
+
os.environ["GOLF_TEST_MODE"] = "1"
|
|
75
|
+
|
|
76
|
+
# Set telemetry preference if flag is used (permanent)
|
|
60
77
|
if no_telemetry:
|
|
61
78
|
set_telemetry_enabled(False, persist=True)
|
|
62
|
-
console.print(
|
|
79
|
+
console.print(
|
|
80
|
+
"[dim]Telemetry has been disabled. You can re-enable it with: golf telemetry enable[/dim]"
|
|
81
|
+
)
|
|
63
82
|
|
|
64
83
|
|
|
65
84
|
@app.command()
|
|
66
85
|
def init(
|
|
67
86
|
project_name: str = typer.Argument(..., help="Name of the project to create"),
|
|
68
|
-
output_dir:
|
|
87
|
+
output_dir: Path | None = typer.Option(
|
|
69
88
|
None, "--output-dir", "-o", help="Directory to create the project in"
|
|
70
89
|
),
|
|
71
90
|
template: str = typer.Option(
|
|
@@ -73,19 +92,21 @@ def init(
|
|
|
73
92
|
),
|
|
74
93
|
) -> None:
|
|
75
94
|
"""Initialize a new GolfMCP project.
|
|
76
|
-
|
|
95
|
+
|
|
77
96
|
Creates a new directory with the project scaffold, including
|
|
78
97
|
examples for tools, resources, and prompts.
|
|
79
98
|
"""
|
|
80
99
|
# Import here to avoid circular imports
|
|
81
100
|
from golf.commands.init import initialize_project
|
|
82
|
-
|
|
101
|
+
|
|
83
102
|
# Use the current directory if no output directory is specified
|
|
84
103
|
if output_dir is None:
|
|
85
104
|
output_dir = Path.cwd() / project_name
|
|
86
|
-
|
|
105
|
+
|
|
87
106
|
# Execute the initialization command (it handles its own tracking)
|
|
88
|
-
initialize_project(
|
|
107
|
+
initialize_project(
|
|
108
|
+
project_name=project_name, output_dir=output_dir, template=template
|
|
109
|
+
)
|
|
89
110
|
|
|
90
111
|
|
|
91
112
|
# Create a build group with subcommands
|
|
@@ -95,166 +116,265 @@ app.add_typer(build_app, name="build")
|
|
|
95
116
|
|
|
96
117
|
@build_app.command("dev")
|
|
97
118
|
def build_dev(
|
|
98
|
-
output_dir:
|
|
119
|
+
output_dir: str | None = typer.Option(
|
|
99
120
|
None, "--output-dir", "-o", help="Directory to output the built project"
|
|
100
|
-
)
|
|
101
|
-
):
|
|
121
|
+
),
|
|
122
|
+
) -> None:
|
|
102
123
|
"""Build a development version with environment variables copied."""
|
|
103
124
|
# Find project root directory
|
|
104
125
|
project_root, config_path = find_project_root()
|
|
105
|
-
|
|
126
|
+
|
|
106
127
|
if not project_root:
|
|
107
|
-
console.print(
|
|
128
|
+
console.print(
|
|
129
|
+
"[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]"
|
|
130
|
+
)
|
|
108
131
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
109
|
-
track_event(
|
|
132
|
+
track_event(
|
|
133
|
+
"cli_build_failed",
|
|
134
|
+
{
|
|
135
|
+
"success": False,
|
|
136
|
+
"environment": "dev",
|
|
137
|
+
"error_type": "NoProjectFound",
|
|
138
|
+
"error_message": "No GolfMCP project found",
|
|
139
|
+
},
|
|
140
|
+
)
|
|
110
141
|
raise typer.Exit(code=1)
|
|
111
|
-
|
|
142
|
+
|
|
112
143
|
# Load settings from the found project
|
|
113
144
|
settings = load_settings(project_root)
|
|
114
|
-
|
|
145
|
+
|
|
115
146
|
# Set default output directory if not specified
|
|
116
|
-
if output_dir is None
|
|
117
|
-
|
|
118
|
-
else:
|
|
119
|
-
output_dir = Path(output_dir)
|
|
120
|
-
|
|
147
|
+
output_dir = project_root / "dist" if output_dir is None else Path(output_dir)
|
|
148
|
+
|
|
121
149
|
try:
|
|
122
150
|
# Build the project with environment variables copied
|
|
123
151
|
from golf.commands.build import build_project
|
|
124
|
-
|
|
152
|
+
|
|
153
|
+
build_project(
|
|
154
|
+
project_root, settings, output_dir, build_env="dev", copy_env=True
|
|
155
|
+
)
|
|
125
156
|
# Track successful build with environment
|
|
126
157
|
track_event("cli_build_success", {"success": True, "environment": "dev"})
|
|
127
|
-
except Exception:
|
|
128
|
-
|
|
158
|
+
except Exception as e:
|
|
159
|
+
error_type = type(e).__name__
|
|
160
|
+
error_message = str(e)
|
|
161
|
+
track_event(
|
|
162
|
+
"cli_build_failed",
|
|
163
|
+
{
|
|
164
|
+
"success": False,
|
|
165
|
+
"environment": "dev",
|
|
166
|
+
"error_type": error_type,
|
|
167
|
+
"error_message": error_message,
|
|
168
|
+
},
|
|
169
|
+
)
|
|
129
170
|
raise
|
|
130
171
|
|
|
131
172
|
|
|
132
173
|
@build_app.command("prod")
|
|
133
174
|
def build_prod(
|
|
134
|
-
output_dir:
|
|
175
|
+
output_dir: str | None = typer.Option(
|
|
135
176
|
None, "--output-dir", "-o", help="Directory to output the built project"
|
|
136
|
-
)
|
|
137
|
-
):
|
|
177
|
+
),
|
|
178
|
+
) -> None:
|
|
138
179
|
"""Build a production version without copying environment variables."""
|
|
139
180
|
# Find project root directory
|
|
140
181
|
project_root, config_path = find_project_root()
|
|
141
|
-
|
|
182
|
+
|
|
142
183
|
if not project_root:
|
|
143
|
-
console.print(
|
|
184
|
+
console.print(
|
|
185
|
+
"[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]"
|
|
186
|
+
)
|
|
144
187
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
145
|
-
track_event(
|
|
188
|
+
track_event(
|
|
189
|
+
"cli_build_failed",
|
|
190
|
+
{
|
|
191
|
+
"success": False,
|
|
192
|
+
"environment": "prod",
|
|
193
|
+
"error_type": "NoProjectFound",
|
|
194
|
+
"error_message": "No GolfMCP project found",
|
|
195
|
+
},
|
|
196
|
+
)
|
|
146
197
|
raise typer.Exit(code=1)
|
|
147
|
-
|
|
198
|
+
|
|
148
199
|
# Load settings from the found project
|
|
149
200
|
settings = load_settings(project_root)
|
|
150
|
-
|
|
201
|
+
|
|
151
202
|
# Set default output directory if not specified
|
|
152
|
-
if output_dir is None
|
|
153
|
-
|
|
154
|
-
else:
|
|
155
|
-
output_dir = Path(output_dir)
|
|
156
|
-
|
|
203
|
+
output_dir = project_root / "dist" if output_dir is None else Path(output_dir)
|
|
204
|
+
|
|
157
205
|
try:
|
|
158
206
|
# Build the project without copying environment variables
|
|
159
207
|
from golf.commands.build import build_project
|
|
160
|
-
|
|
208
|
+
|
|
209
|
+
build_project(
|
|
210
|
+
project_root, settings, output_dir, build_env="prod", copy_env=False
|
|
211
|
+
)
|
|
161
212
|
# Track successful build with environment
|
|
162
213
|
track_event("cli_build_success", {"success": True, "environment": "prod"})
|
|
163
|
-
except Exception:
|
|
164
|
-
|
|
214
|
+
except Exception as e:
|
|
215
|
+
error_type = type(e).__name__
|
|
216
|
+
error_message = str(e)
|
|
217
|
+
track_event(
|
|
218
|
+
"cli_build_failed",
|
|
219
|
+
{
|
|
220
|
+
"success": False,
|
|
221
|
+
"environment": "prod",
|
|
222
|
+
"error_type": error_type,
|
|
223
|
+
"error_message": error_message,
|
|
224
|
+
},
|
|
225
|
+
)
|
|
165
226
|
raise
|
|
166
227
|
|
|
167
228
|
|
|
168
229
|
@app.command()
|
|
169
230
|
def run(
|
|
170
|
-
dist_dir:
|
|
231
|
+
dist_dir: str | None = typer.Option(
|
|
171
232
|
None, "--dist-dir", "-d", help="Directory containing the built server"
|
|
172
233
|
),
|
|
173
|
-
host:
|
|
234
|
+
host: str | None = typer.Option(
|
|
174
235
|
None, "--host", "-h", help="Host to bind to (overrides settings)"
|
|
175
236
|
),
|
|
176
|
-
port:
|
|
237
|
+
port: int | None = typer.Option(
|
|
177
238
|
None, "--port", "-p", help="Port to bind to (overrides settings)"
|
|
178
239
|
),
|
|
179
240
|
build_first: bool = typer.Option(
|
|
180
241
|
True, "--build/--no-build", help="Build the project before running"
|
|
181
242
|
),
|
|
182
|
-
):
|
|
243
|
+
) -> None:
|
|
183
244
|
"""Run the built FastMCP server.
|
|
184
|
-
|
|
245
|
+
|
|
185
246
|
This command runs the built server from the dist directory.
|
|
186
247
|
By default, it will build the project first if needed.
|
|
187
248
|
"""
|
|
188
249
|
# Find project root directory
|
|
189
250
|
project_root, config_path = find_project_root()
|
|
190
|
-
|
|
251
|
+
|
|
191
252
|
if not project_root:
|
|
192
|
-
console.print(
|
|
253
|
+
console.print(
|
|
254
|
+
"[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]"
|
|
255
|
+
)
|
|
193
256
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
194
|
-
track_event(
|
|
257
|
+
track_event(
|
|
258
|
+
"cli_run_failed",
|
|
259
|
+
{
|
|
260
|
+
"success": False,
|
|
261
|
+
"error_type": "NoProjectFound",
|
|
262
|
+
"error_message": "No GolfMCP project found",
|
|
263
|
+
},
|
|
264
|
+
)
|
|
195
265
|
raise typer.Exit(code=1)
|
|
196
|
-
|
|
266
|
+
|
|
197
267
|
# Load settings from the found project
|
|
198
268
|
settings = load_settings(project_root)
|
|
199
|
-
|
|
269
|
+
|
|
200
270
|
# Set default dist directory if not specified
|
|
201
|
-
if dist_dir is None
|
|
202
|
-
|
|
203
|
-
else:
|
|
204
|
-
dist_dir = Path(dist_dir)
|
|
205
|
-
|
|
271
|
+
dist_dir = project_root / "dist" if dist_dir is None else Path(dist_dir)
|
|
272
|
+
|
|
206
273
|
# Check if dist directory exists
|
|
207
274
|
if not dist_dir.exists():
|
|
208
275
|
if build_first:
|
|
209
|
-
console.print(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
276
|
+
console.print(
|
|
277
|
+
f"[yellow]Dist directory {dist_dir} not found. Building first...[/yellow]"
|
|
278
|
+
)
|
|
279
|
+
try:
|
|
280
|
+
# Build the project
|
|
281
|
+
from golf.commands.build import build_project
|
|
282
|
+
|
|
283
|
+
build_project(project_root, settings, dist_dir)
|
|
284
|
+
except Exception as e:
|
|
285
|
+
error_type = type(e).__name__
|
|
286
|
+
error_message = str(e)
|
|
287
|
+
console.print(
|
|
288
|
+
f"[bold red]Error building project:[/bold red] {error_message}"
|
|
289
|
+
)
|
|
290
|
+
track_event(
|
|
291
|
+
"cli_run_failed",
|
|
292
|
+
{
|
|
293
|
+
"success": False,
|
|
294
|
+
"error_type": f"BuildError.{error_type}",
|
|
295
|
+
"error_message": error_message,
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
raise
|
|
213
299
|
else:
|
|
214
|
-
console.print(
|
|
215
|
-
|
|
216
|
-
|
|
300
|
+
console.print(
|
|
301
|
+
f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]"
|
|
302
|
+
)
|
|
303
|
+
console.print(
|
|
304
|
+
"Run 'golf build' first or use --build to build automatically."
|
|
305
|
+
)
|
|
306
|
+
track_event(
|
|
307
|
+
"cli_run_failed",
|
|
308
|
+
{
|
|
309
|
+
"success": False,
|
|
310
|
+
"error_type": "DistNotFound",
|
|
311
|
+
"error_message": "Dist directory not found",
|
|
312
|
+
},
|
|
313
|
+
)
|
|
217
314
|
raise typer.Exit(code=1)
|
|
218
|
-
|
|
315
|
+
|
|
219
316
|
try:
|
|
220
317
|
# Import and run the server
|
|
221
318
|
from golf.commands.run import run_server
|
|
319
|
+
|
|
222
320
|
return_code = run_server(
|
|
223
321
|
project_path=project_root,
|
|
224
322
|
settings=settings,
|
|
225
323
|
dist_dir=dist_dir,
|
|
226
324
|
host=host,
|
|
227
|
-
port=port
|
|
325
|
+
port=port,
|
|
228
326
|
)
|
|
229
|
-
|
|
327
|
+
|
|
230
328
|
# Track based on return code
|
|
231
329
|
if return_code == 0:
|
|
232
330
|
track_event("cli_run_success", {"success": True})
|
|
233
331
|
else:
|
|
234
|
-
track_event(
|
|
235
|
-
|
|
332
|
+
track_event(
|
|
333
|
+
"cli_run_failed",
|
|
334
|
+
{
|
|
335
|
+
"success": False,
|
|
336
|
+
"error_type": "NonZeroExit",
|
|
337
|
+
"error_message": f"Server exited with code {return_code}",
|
|
338
|
+
},
|
|
339
|
+
)
|
|
340
|
+
|
|
236
341
|
# Exit with the same code as the server
|
|
237
342
|
if return_code != 0:
|
|
238
343
|
raise typer.Exit(code=return_code)
|
|
239
|
-
except Exception:
|
|
240
|
-
|
|
344
|
+
except Exception as e:
|
|
345
|
+
error_type = type(e).__name__
|
|
346
|
+
error_message = str(e)
|
|
347
|
+
track_event(
|
|
348
|
+
"cli_run_failed",
|
|
349
|
+
{
|
|
350
|
+
"success": False,
|
|
351
|
+
"error_type": error_type,
|
|
352
|
+
"error_message": error_message,
|
|
353
|
+
},
|
|
354
|
+
)
|
|
241
355
|
raise
|
|
242
356
|
|
|
243
357
|
|
|
244
358
|
# Add telemetry command group
|
|
245
359
|
@app.command()
|
|
246
360
|
def telemetry(
|
|
247
|
-
action: str = typer.Argument(..., help="Action to perform: 'enable' or 'disable'")
|
|
248
|
-
):
|
|
361
|
+
action: str = typer.Argument(..., help="Action to perform: 'enable' or 'disable'"),
|
|
362
|
+
) -> None:
|
|
249
363
|
"""Manage telemetry settings."""
|
|
250
364
|
if action.lower() == "enable":
|
|
251
365
|
set_telemetry_enabled(True, persist=True)
|
|
252
|
-
console.print(
|
|
366
|
+
console.print(
|
|
367
|
+
"[green]✓[/green] Telemetry enabled. Thank you for helping improve Golf!"
|
|
368
|
+
)
|
|
253
369
|
elif action.lower() == "disable":
|
|
254
370
|
set_telemetry_enabled(False, persist=True)
|
|
255
|
-
console.print(
|
|
371
|
+
console.print(
|
|
372
|
+
"[yellow]Telemetry disabled.[/yellow] You can re-enable it anytime with: golf telemetry enable"
|
|
373
|
+
)
|
|
256
374
|
else:
|
|
257
|
-
console.print(
|
|
375
|
+
console.print(
|
|
376
|
+
f"[red]Unknown action '{action}'. Use 'enable' or 'disable'.[/red]"
|
|
377
|
+
)
|
|
258
378
|
raise typer.Exit(code=1)
|
|
259
379
|
|
|
260
380
|
|
|
@@ -267,13 +387,13 @@ if __name__ == "__main__":
|
|
|
267
387
|
border_style="green",
|
|
268
388
|
)
|
|
269
389
|
)
|
|
270
|
-
|
|
390
|
+
|
|
271
391
|
# Add telemetry notice if enabled
|
|
272
392
|
if is_telemetry_enabled():
|
|
273
393
|
console.print(
|
|
274
394
|
"[dim]📊 Anonymous usage data is collected to improve Golf. "
|
|
275
395
|
"Disable with: golf telemetry disable[/dim]\n"
|
|
276
396
|
)
|
|
277
|
-
|
|
397
|
+
|
|
278
398
|
# Run the CLI app
|
|
279
|
-
app()
|
|
399
|
+
app()
|
golf/commands/__init__.py
CHANGED