golf-mcp 0.1.14__tar.gz → 0.1.17__tar.gz

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.

Files changed (73) hide show
  1. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/docs.md +2 -2
  2. {golf_mcp-0.1.14/src/golf_mcp.egg-info → golf_mcp-0.1.17}/PKG-INFO +43 -3
  3. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/README.md +41 -1
  4. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/pyproject.toml +4 -4
  5. golf_mcp-0.1.17/src/golf/__init__.py +1 -0
  6. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/cli/main.py +21 -12
  7. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/commands/init.py +63 -1
  8. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/commands/run.py +12 -4
  9. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/builder.py +181 -61
  10. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/config.py +13 -0
  11. golf_mcp-0.1.17/src/golf/core/parser.py +1059 -0
  12. golf_mcp-0.1.17/src/golf/core/platform.py +180 -0
  13. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/telemetry.py +48 -17
  14. golf_mcp-0.1.17/src/golf/examples/api_key/.env.example +1 -0
  15. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/README.md +10 -10
  16. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/golf.json +1 -2
  17. golf_mcp-0.1.17/src/golf/examples/basic/.env.example +4 -0
  18. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/golf.json +1 -2
  19. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/telemetry/instrumentation.py +26 -48
  20. {golf_mcp-0.1.14 → golf_mcp-0.1.17/src/golf_mcp.egg-info}/PKG-INFO +43 -3
  21. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/SOURCES.txt +1 -0
  22. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/requires.txt +1 -1
  23. golf_mcp-0.1.14/src/golf/__init__.py +0 -1
  24. golf_mcp-0.1.14/src/golf/core/parser.py +0 -506
  25. golf_mcp-0.1.14/src/golf/examples/api_key/.env.example +0 -5
  26. golf_mcp-0.1.14/src/golf/examples/basic/.env.example +0 -5
  27. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/fast-mcp.md +0 -0
  28. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/fastmcp-example-1.py +0 -0
  29. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/fastmcp-example-2.py +0 -0
  30. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/mcp.md +0 -0
  31. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/oauth-implementation.md +0 -0
  32. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/.docs/oauth.md +0 -0
  33. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/LICENSE +0 -0
  34. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/MANIFEST.in +0 -0
  35. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/setup.cfg +0 -0
  36. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/auth/__init__.py +0 -0
  37. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/auth/api_key.py +0 -0
  38. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/auth/helpers.py +0 -0
  39. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/auth/oauth.py +0 -0
  40. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/auth/provider.py +0 -0
  41. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/cli/__init__.py +0 -0
  42. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/commands/__init__.py +0 -0
  43. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/commands/build.py +0 -0
  44. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/__init__.py +0 -0
  45. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/builder_auth.py +0 -0
  46. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/builder_telemetry.py +0 -0
  47. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/core/transformer.py +0 -0
  48. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/__init__.py +0 -0
  49. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/.env +0 -0
  50. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/pre_build.py +0 -0
  51. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/issues/create.py +0 -0
  52. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/issues/list.py +0 -0
  53. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/repos/list.py +0 -0
  54. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/search/code.py +0 -0
  55. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/api_key/tools/users/get.py +0 -0
  56. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/.env +0 -0
  57. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/README.md +0 -0
  58. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/pre_build.py +0 -0
  59. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/prompts/welcome.py +0 -0
  60. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/current_time.py +0 -0
  61. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/info.py +0 -0
  62. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/common.py +0 -0
  63. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/current.py +0 -0
  64. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  65. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/github_user.py +0 -0
  66. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/hello.py +0 -0
  67. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/charge.py +0 -0
  68. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/common.py +0 -0
  69. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/examples/basic/tools/payments/refund.py +0 -0
  70. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf/telemetry/__init__.py +0 -0
  71. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  72. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  73. {golf_mcp-0.1.14 → golf_mcp-0.1.17}/src/golf_mcp.egg-info/top_level.txt +0 -0
@@ -49,7 +49,7 @@ Given a component file `C` with absolute path
49
49
  let `PathRev = [pₙ, …, p₁]` (reverse order of parent dirs under the category). The **ID** is
50
50
 
51
51
  ```
52
- <filename> + ("-" + "-".join(PathRev) if PathRev else "")
52
+ <filename> + ("_" + "_".join(PathRev) if PathRev else "")
53
53
  ```
54
54
 
55
55
  ### Formal Definition (BNF)
@@ -190,7 +190,7 @@ def run(charge_id: str, amount: int) -> dict:
190
190
  from tools.payments.refund import submit as _submit
191
191
 
192
192
  mcp.tool(
193
- name="submit-refund-payments",
193
+ name="submit_refund_payments",
194
194
  description="Submit a refund request to Stripe."
195
195
  )(_submit.run)
196
196
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.14
3
+ Version: 0.1.17
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: typer>=0.15.4
23
23
  Requires-Dist: rich>=14.0.0
24
- Requires-Dist: fastmcp>=2.0.0
24
+ Requires-Dist: fastmcp<2.6.0,>=2.0.0
25
25
  Requires-Dist: pydantic>=2.11.0
26
26
  Requires-Dist: python-dotenv>=1.1.0
27
27
  Requires-Dist: black>=24.10.0
@@ -128,7 +128,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
128
128
 
129
129
  - **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
130
130
  - **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
131
- - Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit-payments` (filename, followed by reversed parent directories under the main category, joined by hyphens).
131
+ - Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
132
132
  - **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
133
133
 
134
134
  ## Example: Defining a Tool
@@ -178,6 +178,15 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
178
178
  // - "streamable-http": HTTP with streaming support
179
179
  // - "stdio": Standard I/O (for CLI integration)
180
180
 
181
+ // HTTP Transport Configuration (optional)
182
+ "stateless_http": false, // Make streamable-http transport stateless (new session per request)
183
+ // When true, server restarts won't break existing client connections
184
+
185
+ // Health Check Configuration (optional)
186
+ "health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
187
+ "health_check_path": "/health", // HTTP path for health check endpoint
188
+ "health_check_response": "OK", // Response text returned by health check
189
+
181
190
  // OpenTelemetry Configuration (optional)
182
191
  "opentelemetry_enabled": false, // Enable distributed tracing
183
192
  "opentelemetry_default_exporter": "console" // Default exporter if OTEL_TRACES_EXPORTER not set
@@ -193,11 +202,42 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
193
202
  - `"streamable-http"` provides HTTP streaming for traditional API clients
194
203
  - `"stdio"` enables integration with command-line tools and scripts
195
204
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
205
+ - **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
206
+ - **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
207
+ - **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
208
+ - **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
196
209
  - **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
197
210
  - **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
198
211
 
199
212
  ## Features
200
213
 
214
+ ### 🏥 Health Check Support
215
+
216
+ Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
217
+ - Kubernetes readiness and liveness probes
218
+ - Load balancers and reverse proxies
219
+ - Monitoring systems
220
+ - Container orchestration platforms
221
+
222
+ #### Configuration
223
+
224
+ Enable health checks in your `golf.json`:
225
+ ```json
226
+ {
227
+ "health_check_enabled": true,
228
+ "health_check_path": "/health",
229
+ "health_check_response": "Service is healthy"
230
+ }
231
+ ```
232
+
233
+ The generated server will include a route like:
234
+ ```python
235
+ @mcp.custom_route('/health', methods=["GET"])
236
+ async def health_check(request: Request) -> PlainTextResponse:
237
+ """Health check endpoint for Kubernetes and load balancers."""
238
+ return PlainTextResponse("Service is healthy")
239
+ ```
240
+
201
241
  ### 🔍 OpenTelemetry Support
202
242
 
203
243
  Golf includes built-in OpenTelemetry instrumentation for distributed tracing. When enabled, it automatically traces:
@@ -90,7 +90,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
90
90
 
91
91
  - **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
92
92
  - **`tools/`**, **`resources/`**, **`prompts/`**: Contain your Python files, each defining a single component. These directories can also contain nested subdirectories to further organize your components (e.g., `tools/payments/charge.py`). The module docstring of each file serves as the component's description.
93
- - Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit-payments` (filename, followed by reversed parent directories under the main category, joined by hyphens).
93
+ - Component IDs are automatically derived from their file path. For example, `tools/hello.py` becomes `hello`, and a nested file like `tools/payments/submit.py` would become `submit_payments` (filename, followed by reversed parent directories under the main category, joined by underscores).
94
94
  - **`common.py`** (not shown, but can be placed in subdirectories like `tools/payments/common.py`): Used to share code (clients, models, etc.) among components in the same subdirectory.
95
95
 
96
96
  ## Example: Defining a Tool
@@ -140,6 +140,15 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
140
140
  // - "streamable-http": HTTP with streaming support
141
141
  // - "stdio": Standard I/O (for CLI integration)
142
142
 
143
+ // HTTP Transport Configuration (optional)
144
+ "stateless_http": false, // Make streamable-http transport stateless (new session per request)
145
+ // When true, server restarts won't break existing client connections
146
+
147
+ // Health Check Configuration (optional)
148
+ "health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
149
+ "health_check_path": "/health", // HTTP path for health check endpoint
150
+ "health_check_response": "OK", // Response text returned by health check
151
+
143
152
  // OpenTelemetry Configuration (optional)
144
153
  "opentelemetry_enabled": false, // Enable distributed tracing
145
154
  "opentelemetry_default_exporter": "console" // Default exporter if OTEL_TRACES_EXPORTER not set
@@ -155,11 +164,42 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
155
164
  - `"streamable-http"` provides HTTP streaming for traditional API clients
156
165
  - `"stdio"` enables integration with command-line tools and scripts
157
166
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
167
+ - **`stateless_http`**: When true, makes the streamable-http transport stateless by creating a new session for each request. This ensures that server restarts don't break existing client connections, making the server truly stateless.
168
+ - **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
169
+ - **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
170
+ - **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
158
171
  - **`opentelemetry_enabled`**: When true, enables distributed tracing for debugging and monitoring your MCP server
159
172
  - **`opentelemetry_default_exporter`**: Sets the default trace exporter. Can be overridden by the `OTEL_TRACES_EXPORTER` environment variable
160
173
 
161
174
  ## Features
162
175
 
176
+ ### 🏥 Health Check Support
177
+
178
+ Golf includes built-in health check endpoint support for production deployments. When enabled, it automatically adds a custom HTTP route that can be used by:
179
+ - Kubernetes readiness and liveness probes
180
+ - Load balancers and reverse proxies
181
+ - Monitoring systems
182
+ - Container orchestration platforms
183
+
184
+ #### Configuration
185
+
186
+ Enable health checks in your `golf.json`:
187
+ ```json
188
+ {
189
+ "health_check_enabled": true,
190
+ "health_check_path": "/health",
191
+ "health_check_response": "Service is healthy"
192
+ }
193
+ ```
194
+
195
+ The generated server will include a route like:
196
+ ```python
197
+ @mcp.custom_route('/health', methods=["GET"])
198
+ async def health_check(request: Request) -> PlainTextResponse:
199
+ """Health check endpoint for Kubernetes and load balancers."""
200
+ return PlainTextResponse("Service is healthy")
201
+ ```
202
+
163
203
  ### 🔍 OpenTelemetry Support
164
204
 
165
205
  Golf includes built-in OpenTelemetry instrumentation for distributed tracing. When enabled, it automatically traces:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.1.14"
7
+ version = "0.1.17"
8
8
  description = "Framework for building MCP servers"
9
9
  authors = [
10
10
  {name = "Antoni Gmitruk", email = "antoni@golf.dev"}
@@ -28,7 +28,7 @@ classifiers = [
28
28
  dependencies = [
29
29
  "typer>=0.15.4",
30
30
  "rich>=14.0.0",
31
- "fastmcp>=2.0.0",
31
+ "fastmcp>=2.0.0,<2.6.0",
32
32
  "pydantic>=2.11.0",
33
33
  "python-dotenv>=1.1.0",
34
34
  "black>=24.10.0",
@@ -64,7 +64,7 @@ golf = ["examples/**/*"]
64
64
 
65
65
  [tool.poetry]
66
66
  name = "golf-mcp"
67
- version = "0.1.14"
67
+ version = "0.1.17"
68
68
  description = "Framework for building MCP servers with zero boilerplate"
69
69
  authors = ["Antoni Gmitruk <antoni@golf.dev>"]
70
70
  license = "Apache-2.0"
@@ -86,7 +86,7 @@ classifiers = [
86
86
 
87
87
  [tool.poetry.dependencies]
88
88
  python = ">=3.8" # Match requires-python
89
- fastmcp = ">=2.0.0"
89
+ fastmcp = ">=2.0.0,<2.6.0"
90
90
  typer = {extras = ["all"], version = ">=0.15.4"}
91
91
  pydantic = ">=2.11.0"
92
92
  rich = ">=14.0.0"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.17"
@@ -121,7 +121,11 @@ def build_dev(
121
121
  None, "--output-dir", "-o", help="Directory to output the built project"
122
122
  ),
123
123
  ) -> None:
124
- """Build a development version with environment variables copied."""
124
+ """Build a development version with app environment variables copied.
125
+
126
+ Golf credentials (GOLF_*) are always loaded from .env for build operations.
127
+ All environment variables are copied to the built project for development.
128
+ """
125
129
  # Find project root directory
126
130
  project_root, config_path = find_project_root()
127
131
 
@@ -162,7 +166,7 @@ def build_dev(
162
166
  e,
163
167
  context="Development build with environment variables",
164
168
  operation="build_dev",
165
- additional_props={"environment": "dev", "copy_env": True}
169
+ additional_props={"environment": "dev", "copy_env": True},
166
170
  )
167
171
  raise
168
172
 
@@ -173,7 +177,14 @@ def build_prod(
173
177
  None, "--output-dir", "-o", help="Directory to output the built project"
174
178
  ),
175
179
  ) -> None:
176
- """Build a production version without copying environment variables."""
180
+ """Build a production version for deployment.
181
+
182
+ Golf credentials (GOLF_*) are always loaded from .env for build operations
183
+ (platform registration, resource updates). App environment variables are
184
+ NOT copied for security - provide them in your deployment environment.
185
+
186
+ Your production deployment must include GOLF_* vars for runtime telemetry.
187
+ """
177
188
  # Find project root directory
178
189
  project_root, config_path = find_project_root()
179
190
 
@@ -214,7 +225,7 @@ def build_prod(
214
225
  e,
215
226
  context="Production build without environment variables",
216
227
  operation="build_prod",
217
- additional_props={"environment": "prod", "copy_env": False}
228
+ additional_props={"environment": "prod", "copy_env": False},
218
229
  )
219
230
  raise
220
231
 
@@ -275,15 +286,13 @@ def run(
275
286
 
276
287
  build_project(project_root, settings, dist_dir)
277
288
  except Exception as e:
278
- console.print(
279
- f"[bold red]Error building project:[/bold red] {str(e)}"
280
- )
289
+ console.print(f"[bold red]Error building project:[/bold red] {str(e)}")
281
290
  track_detailed_error(
282
291
  "cli_run_failed",
283
292
  e,
284
293
  context="Auto-build before running server",
285
294
  operation="auto_build_before_run",
286
- additional_props={"auto_build": True}
295
+ additional_props={"auto_build": True},
287
296
  )
288
297
  raise
289
298
  else:
@@ -326,11 +335,11 @@ def run(
326
335
  # 2: General interrupt/graceful shutdown
327
336
  shutdown_type = {
328
337
  130: "UserInterrupt",
329
- 143: "GracefulShutdown",
338
+ 143: "GracefulShutdown",
330
339
  137: "ForcedShutdown",
331
- 2: "Interrupt"
340
+ 2: "Interrupt",
332
341
  }.get(return_code, "GracefulShutdown")
333
-
342
+
334
343
  track_event(
335
344
  "cli_run_shutdown",
336
345
  {
@@ -362,7 +371,7 @@ def run(
362
371
  e,
363
372
  context="Server execution or startup failure",
364
373
  operation="run_server_execution",
365
- additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False}
374
+ additional_props={"has_dist_dir": dist_dir.exists() if dist_dir else False},
366
375
  )
367
376
  raise
368
377
 
@@ -7,7 +7,12 @@ from rich.console import Console
7
7
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
8
  from rich.prompt import Confirm
9
9
 
10
- from golf.core.telemetry import track_command, track_event
10
+ from golf.core.telemetry import (
11
+ track_command,
12
+ track_event,
13
+ set_telemetry_enabled,
14
+ load_telemetry_preference,
15
+ )
11
16
 
12
17
  console = Console()
13
18
 
@@ -95,6 +100,9 @@ def initialize_project(
95
100
  # Copy directory structure
96
101
  _copy_template(template_dir, output_dir, project_name)
97
102
 
103
+ # Ask for telemetry consent
104
+ _prompt_for_telemetry_consent()
105
+
98
106
  # Create virtual environment
99
107
  console.print("[bold green]Project initialized successfully![/bold green]")
100
108
  console.print("\nTo get started, run:")
@@ -207,6 +215,60 @@ def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> Non
207
215
  f.write("dist/\n")
208
216
 
209
217
 
218
+ def _prompt_for_telemetry_consent() -> None:
219
+ """Prompt user for telemetry consent and save their preference."""
220
+ import os
221
+
222
+ # Skip prompt in test mode, when telemetry is explicitly disabled, or if preference already exists
223
+ if os.environ.get("GOLF_TEST_MODE", "").lower() in ("1", "true", "yes", "on"):
224
+ return
225
+
226
+ # Skip if telemetry is explicitly disabled in environment
227
+ if os.environ.get("GOLF_TELEMETRY", "").lower() in ("0", "false", "no", "off"):
228
+ return
229
+
230
+ # Check if user already has a saved preference
231
+ existing_preference = load_telemetry_preference()
232
+ if existing_preference is not None:
233
+ return # User already made a choice
234
+
235
+ console.print()
236
+ console.rule("[bold blue]Anonymous usage analytics[/bold blue]", style="blue")
237
+ console.print()
238
+ console.print(
239
+ "Golf can collect [bold]anonymous usage analytics[/bold] to help improve the tool."
240
+ )
241
+ console.print()
242
+ console.print("[dim]What we collect:[/dim]")
243
+ console.print(" • Command usage (init, build, run)")
244
+ console.print(" • Error types (to fix bugs)")
245
+ console.print(" • Golf version and Python version")
246
+ console.print(" • Operating system type")
247
+ console.print()
248
+ console.print("[dim]What we DON'T collect:[/dim]")
249
+ console.print(" • Your code or project content")
250
+ console.print(" • File paths or project names")
251
+ console.print(" • Personal information")
252
+ console.print(" • IP addresses")
253
+ console.print()
254
+ console.print(
255
+ "You can change this anytime by setting GOLF_TELEMETRY=0 in your environment."
256
+ )
257
+ console.print()
258
+
259
+ enable_telemetry = Confirm.ask(
260
+ "[bold]Enable anonymous usage analytics?[/bold]", default=False
261
+ )
262
+
263
+ set_telemetry_enabled(enable_telemetry, persist=True)
264
+
265
+ if enable_telemetry:
266
+ console.print("[green]✓[/green] Anonymous analytics enabled")
267
+ else:
268
+ console.print("[yellow]○[/yellow] Anonymous analytics disabled")
269
+ console.print()
270
+
271
+
210
272
  def _is_text_file(path: Path) -> bool:
211
273
  """Check if a file is a text file that needs variable substitution.
212
274
 
@@ -70,13 +70,21 @@ def run_server(
70
70
  elif process.returncode == 130:
71
71
  console.print("[yellow]Server stopped by user interrupt (Ctrl+C)[/yellow]")
72
72
  elif process.returncode == 143:
73
- console.print("[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]")
73
+ console.print(
74
+ "[yellow]Server stopped by SIGTERM (graceful shutdown)[/yellow]"
75
+ )
74
76
  elif process.returncode == 137:
75
- console.print("[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]")
77
+ console.print(
78
+ "[yellow]Server stopped by SIGKILL (forced shutdown)[/yellow]"
79
+ )
76
80
  elif process.returncode in [1, 2]:
77
- console.print(f"[red]Server exited with error code {process.returncode}[/red]")
81
+ console.print(
82
+ f"[red]Server exited with error code {process.returncode}[/red]"
83
+ )
78
84
  else:
79
- console.print(f"[orange]Server exited with code {process.returncode}[/orange]")
85
+ console.print(
86
+ f"[orange]Server exited with code {process.returncode}[/orange]"
87
+ )
80
88
 
81
89
  return process.returncode
82
90
  except KeyboardInterrupt: