golf-mcp 0.1.2__tar.gz → 0.1.4__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 (56) hide show
  1. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/docs.md +1 -18
  2. {golf_mcp-0.1.2/src/golf_mcp.egg-info → golf_mcp-0.1.4}/PKG-INFO +78 -18
  3. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/README.md +76 -17
  4. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/pyproject.toml +4 -3
  5. golf_mcp-0.1.4/src/golf/__init__.py +1 -0
  6. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/cli/main.py +79 -23
  7. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/commands/init.py +14 -0
  8. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/commands/run.py +5 -1
  9. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/config.py +0 -1
  10. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/parser.py +1 -42
  11. golf_mcp-0.1.4/src/golf/core/telemetry.py +257 -0
  12. {golf_mcp-0.1.2 → golf_mcp-0.1.4/src/golf_mcp.egg-info}/PKG-INFO +78 -18
  13. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf_mcp.egg-info/SOURCES.txt +1 -0
  14. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf_mcp.egg-info/requires.txt +1 -0
  15. golf_mcp-0.1.2/src/golf/__init__.py +0 -1
  16. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/fast-mcp.md +0 -0
  17. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/fastmcp-example-1.py +0 -0
  18. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/fastmcp-example-2.py +0 -0
  19. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/mcp.md +0 -0
  20. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/oauth-implementation.md +0 -0
  21. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/.docs/oauth.md +0 -0
  22. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/LICENSE +0 -0
  23. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/MANIFEST.in +0 -0
  24. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/setup.cfg +0 -0
  25. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/auth/__init__.py +0 -0
  26. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/auth/helpers.py +0 -0
  27. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/auth/oauth.py +0 -0
  28. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/auth/provider.py +0 -0
  29. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/cli/__init__.py +0 -0
  30. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/commands/__init__.py +0 -0
  31. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/commands/build.py +0 -0
  32. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/__init__.py +0 -0
  33. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/builder.py +0 -0
  34. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/builder_auth.py +0 -0
  35. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/builder_telemetry.py +0 -0
  36. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/core/transformer.py +0 -0
  37. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/__init__.py +0 -0
  38. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/.env +0 -0
  39. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/.env.example +0 -0
  40. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/README.md +0 -0
  41. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/golf.json +0 -0
  42. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/pre_build.py +0 -0
  43. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/prompts/welcome.py +0 -0
  44. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/resources/current_time.py +0 -0
  45. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/resources/info.py +0 -0
  46. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/resources/weather/common.py +0 -0
  47. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/resources/weather/current.py +0 -0
  48. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  49. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/tools/github_user.py +0 -0
  50. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/tools/hello.py +0 -0
  51. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/tools/payments/charge.py +0 -0
  52. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/tools/payments/common.py +0 -0
  53. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf/examples/basic/tools/payments/refund.py +0 -0
  54. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  55. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  56. {golf_mcp-0.1.2 → golf_mcp-0.1.4}/src/golf_mcp.egg-info/top_level.txt +0 -0
@@ -113,15 +113,13 @@ The parser SHALL fail the build if:
113
113
  \| **6 Packaging** | Copy auxiliary files (e.g. TLS certs) if declared. |
114
114
  \| **7 Completion** | Write artifact map & build log. |
115
115
 
116
- Build is **incremental**: SHA‑1 of each file stored; unchanged files skip parsing.
117
-
118
116
  ---
119
117
 
120
118
  ## 7  CLI Contract
121
119
 
122
120
  ```bash
123
121
  $ golf init [--template <repo‑url>] # scaffold
124
- $ golf build [--outdir DIR] [--watch] # compile once or watch mode
122
+ $ golf build [--outdir DIR] # compile once
125
123
  $ golf run [--host HOST] [--port P] # build if stale then exec app.py
126
124
  ```
127
125
 
@@ -203,18 +201,3 @@ mcp.tool(
203
201
 
204
202
  * **Pre‑Build Plugins** — `pre_build.py` in project root can register callbacks to mutate `Component` objects.
205
203
  * **Transport Providers** — third‑party packages can expose `golf_transport` entry‑point returning `TransportFactory` classes recognised by codegen.
206
-
207
- ---
208
-
209
- ## 11  Compliance & Versioning
210
-
211
- * Spec version is tracked in `golf.json` under `"spec_version"` (default `0.2`).
212
- * Build engine MUST refuse to build projects with a higher spec\_version than supported.
213
-
214
- ---
215
-
216
- ## 12  Open Items
217
-
218
- * Auto‑watch & hot‑reload (`golf run --watch`).
219
- * Injection metadata (`@inject("gmail_client")`) vs type‑based.
220
- * Resource URI override strategy.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -27,6 +27,7 @@ Requires-Dist: python-dotenv>=1.1.0
27
27
  Requires-Dist: black>=24.10.0
28
28
  Requires-Dist: pyjwt>=2.0.0
29
29
  Requires-Dist: httpx>=0.28.1
30
+ Requires-Dist: posthog>=4.1.0
30
31
  Provides-Extra: telemetry
31
32
  Requires-Dist: opentelemetry-api>=1.33.1; extra == "telemetry"
32
33
  Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
@@ -38,9 +39,19 @@ Dynamic: license-file
38
39
  <div align="center">
39
40
  <img src="./golf-banner.png" alt="Golf Banner">
40
41
 
41
- <h1>Golf</h1>
42
+ <br>
42
43
 
43
- <p><strong>Easiest framework for building MCP servers.</strong></p>
44
+ <h1 align="center">
45
+ <br>
46
+ <span style="font-size: 80px;">⛳ Golf</span>
47
+ <br>
48
+ </h1>
49
+
50
+ <h3 align="center">
51
+ Easiest framework for building MCP servers
52
+ </h3>
53
+
54
+ <br>
44
55
 
45
56
  <p>
46
57
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
@@ -48,7 +59,9 @@ Dynamic: license-file
48
59
  <a href="https://github.com/golf-mcp/golf/issues"><img src="https://img.shields.io/badge/support-contact%20author-purple.svg" alt="Support"></a>
49
60
  </p>
50
61
 
51
- <p><a href="https://docs.golf.dev">Docs</a></p>
62
+ <p>
63
+ <a href="https://docs.golf.dev"><strong>📚 Documentation</strong></a>
64
+ </p>
52
65
  </div>
53
66
 
54
67
  ## Overview
@@ -87,7 +100,7 @@ cd your-project-name
87
100
  golf build dev
88
101
  golf run
89
102
  ```
90
- This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`). The `dev` command includes hot reloading, so changes to your component files will automatically restart the server.
103
+ This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`).
91
104
 
92
105
  That's it! Your Golf server is running and ready for integration.
93
106
 
@@ -151,29 +164,76 @@ Golf will automatically discover this file. The module docstring `"""Hello World
151
164
 
152
165
  ## Configuration (`golf.json`)
153
166
 
154
- Key aspects of your Golf server are configured in `golf.json`. The boilerplate provides a starting point like this:
167
+ The `golf.json` file is the heart of your Golf project configuration. Here's what each field controls:
155
168
 
156
169
  ```jsonc
157
170
  {
158
- "name": "{{project_name}}", // Will be replaced with your project name
159
- "description": "A Golf project", // A default description
160
- "host": "127.0.0.1", // Server host address
161
- "port": 3000, // Server port
162
- "transport": "sse", // 'sse', 'streamable-http', or 'stdio'
163
- "opentelemetry_enabled": false, // OpenTelemetry disabled by default - we're working on this as a feature
164
- "opentelemetry_default_exporter": "console"
171
+ "name": "{{project_name}}", // Your MCP server name (required)
172
+ "description": "A Golf project", // Brief description of your server's purpose
173
+ "host": "127.0.0.1", // Server host - use "0.0.0.0" to listen on all interfaces
174
+ "port": 3000, // Server port - any available port number
175
+ "transport": "sse", // Communication protocol:
176
+ // - "sse": Server-Sent Events (recommended for web clients)
177
+ // - "streamable-http": HTTP with streaming support
178
+ // - "stdio": Standard I/O (for CLI integration)
165
179
  }
166
180
  ```
167
- ## Roadmap
168
181
 
169
- We are actively developing Golf. Here's what's on our current roadmap:
182
+ ### Key Configuration Options:
183
+
184
+ - **`name`**: The identifier for your MCP server. This will be shown to clients connecting to your server.
185
+ - **`transport`**: Choose based on your client needs:
186
+ - `"sse"` is ideal for web-based clients and real-time communication
187
+ - `"streamable-http"` provides HTTP streaming for traditional API clients
188
+ - `"stdio"` enables integration with command-line tools and scripts
189
+ - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
+
191
+ ## Privacy & Telemetry
192
+
193
+ Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
194
+
195
+ - Commands run (init, build, run)
196
+ - Success/failure status (no error details)
197
+ - Golf version, Python version (major.minor only), and OS type
198
+ - Template name (for init command only)
199
+ - Build environment (dev/prod for build commands only)
170
200
 
201
+ **No personal information, project names, code content, or error messages are ever collected.**
202
+
203
+ ### Opting Out
204
+
205
+ You can disable telemetry in several ways:
206
+
207
+ 1. **Using the telemetry command** (recommended):
208
+ ```bash
209
+ golf telemetry disable
210
+ ```
211
+ This saves your preference permanently. To re-enable:
212
+ ```bash
213
+ golf telemetry enable
214
+ ```
215
+
216
+ 2. **During any command**: Add `--no-telemetry` to save your preference:
217
+ ```bash
218
+ golf init my-project --no-telemetry
219
+ ```
220
+
221
+ 3. **Environment variable** (temporary override):
222
+ ```bash
223
+ export GOLF_TELEMETRY=0
224
+ golf init my-project
225
+ ```
226
+
227
+ Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
+
229
+ ## Roadmap
171
230
 
172
- ## Documentation
231
+ Here are the things we are working hard on:
173
232
 
174
- For more information, please visit our official documentation:
233
+ * **Native OpenTelemetry implementation for tracing**
234
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
175
236
 
176
- [https://docs.golf.dev](https://docs.golf.dev)
177
237
 
178
238
  <div align="center">
179
239
  Made with ❤️ in Warsaw, Poland and SF
@@ -1,9 +1,19 @@
1
1
  <div align="center">
2
2
  <img src="./golf-banner.png" alt="Golf Banner">
3
3
 
4
- <h1>Golf</h1>
4
+ <br>
5
5
 
6
- <p><strong>Easiest framework for building MCP servers.</strong></p>
6
+ <h1 align="center">
7
+ <br>
8
+ <span style="font-size: 80px;">⛳ Golf</span>
9
+ <br>
10
+ </h1>
11
+
12
+ <h3 align="center">
13
+ Easiest framework for building MCP servers
14
+ </h3>
15
+
16
+ <br>
7
17
 
8
18
  <p>
9
19
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
@@ -11,7 +21,9 @@
11
21
  <a href="https://github.com/golf-mcp/golf/issues"><img src="https://img.shields.io/badge/support-contact%20author-purple.svg" alt="Support"></a>
12
22
  </p>
13
23
 
14
- <p><a href="https://docs.golf.dev">Docs</a></p>
24
+ <p>
25
+ <a href="https://docs.golf.dev"><strong>📚 Documentation</strong></a>
26
+ </p>
15
27
  </div>
16
28
 
17
29
  ## Overview
@@ -50,7 +62,7 @@ cd your-project-name
50
62
  golf build dev
51
63
  golf run
52
64
  ```
53
- This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`). The `dev` command includes hot reloading, so changes to your component files will automatically restart the server.
65
+ This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`).
54
66
 
55
67
  That's it! Your Golf server is running and ready for integration.
56
68
 
@@ -114,29 +126,76 @@ Golf will automatically discover this file. The module docstring `"""Hello World
114
126
 
115
127
  ## Configuration (`golf.json`)
116
128
 
117
- Key aspects of your Golf server are configured in `golf.json`. The boilerplate provides a starting point like this:
129
+ The `golf.json` file is the heart of your Golf project configuration. Here's what each field controls:
118
130
 
119
131
  ```jsonc
120
132
  {
121
- "name": "{{project_name}}", // Will be replaced with your project name
122
- "description": "A Golf project", // A default description
123
- "host": "127.0.0.1", // Server host address
124
- "port": 3000, // Server port
125
- "transport": "sse", // 'sse', 'streamable-http', or 'stdio'
126
- "opentelemetry_enabled": false, // OpenTelemetry disabled by default - we're working on this as a feature
127
- "opentelemetry_default_exporter": "console"
133
+ "name": "{{project_name}}", // Your MCP server name (required)
134
+ "description": "A Golf project", // Brief description of your server's purpose
135
+ "host": "127.0.0.1", // Server host - use "0.0.0.0" to listen on all interfaces
136
+ "port": 3000, // Server port - any available port number
137
+ "transport": "sse", // Communication protocol:
138
+ // - "sse": Server-Sent Events (recommended for web clients)
139
+ // - "streamable-http": HTTP with streaming support
140
+ // - "stdio": Standard I/O (for CLI integration)
128
141
  }
129
142
  ```
130
- ## Roadmap
131
143
 
132
- We are actively developing Golf. Here's what's on our current roadmap:
144
+ ### Key Configuration Options:
145
+
146
+ - **`name`**: The identifier for your MCP server. This will be shown to clients connecting to your server.
147
+ - **`transport`**: Choose based on your client needs:
148
+ - `"sse"` is ideal for web-based clients and real-time communication
149
+ - `"streamable-http"` provides HTTP streaming for traditional API clients
150
+ - `"stdio"` enables integration with command-line tools and scripts
151
+ - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
152
+
153
+ ## Privacy & Telemetry
154
+
155
+ Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
156
+
157
+ - Commands run (init, build, run)
158
+ - Success/failure status (no error details)
159
+ - Golf version, Python version (major.minor only), and OS type
160
+ - Template name (for init command only)
161
+ - Build environment (dev/prod for build commands only)
133
162
 
163
+ **No personal information, project names, code content, or error messages are ever collected.**
164
+
165
+ ### Opting Out
166
+
167
+ You can disable telemetry in several ways:
168
+
169
+ 1. **Using the telemetry command** (recommended):
170
+ ```bash
171
+ golf telemetry disable
172
+ ```
173
+ This saves your preference permanently. To re-enable:
174
+ ```bash
175
+ golf telemetry enable
176
+ ```
177
+
178
+ 2. **During any command**: Add `--no-telemetry` to save your preference:
179
+ ```bash
180
+ golf init my-project --no-telemetry
181
+ ```
182
+
183
+ 3. **Environment variable** (temporary override):
184
+ ```bash
185
+ export GOLF_TELEMETRY=0
186
+ golf init my-project
187
+ ```
188
+
189
+ Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
190
+
191
+ ## Roadmap
134
192
 
135
- ## Documentation
193
+ Here are the things we are working hard on:
136
194
 
137
- For more information, please visit our official documentation:
195
+ * **Native OpenTelemetry implementation for tracing**
196
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
197
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
138
198
 
139
- [https://docs.golf.dev](https://docs.golf.dev)
140
199
 
141
200
  <div align="center">
142
201
  Made with ❤️ in Warsaw, Poland and SF
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Framework for building MCP servers"
9
9
  authors = [
10
10
  {name = "Antoni Gmitruk", email = "antoni@golf.dev"}
@@ -33,7 +33,8 @@ dependencies = [
33
33
  "python-dotenv>=1.1.0",
34
34
  "black>=24.10.0",
35
35
  "pyjwt>=2.0.0",
36
- "httpx>=0.28.1"
36
+ "httpx>=0.28.1",
37
+ "posthog>=4.1.0"
37
38
  ]
38
39
 
39
40
  [project.optional-dependencies]
@@ -63,7 +64,7 @@ golf = ["examples/**/*"]
63
64
 
64
65
  [tool.poetry]
65
66
  name = "golf-mcp"
66
- version = "0.1.0"
67
+ version = "0.1.4"
67
68
  description = "Framework for building MCP servers with zero boilerplate"
68
69
  authors = ["Antoni Gmitruk <antoni@golf.dev>"]
69
70
  license = "Apache-2.0"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -3,6 +3,7 @@
3
3
  import os
4
4
  from pathlib import Path
5
5
  from typing import Optional
6
+ import atexit
6
7
 
7
8
  import typer
8
9
  from rich.console import Console
@@ -10,6 +11,7 @@ from rich.panel import Panel
10
11
 
11
12
  from golf import __version__
12
13
  from golf.core.config import load_settings, find_project_root
14
+ from golf.core.telemetry import is_telemetry_enabled, shutdown, set_telemetry_enabled, track_event
13
15
 
14
16
  # Create console for rich output
15
17
  console = Console()
@@ -21,6 +23,9 @@ app = typer.Typer(
21
23
  add_completion=False,
22
24
  )
23
25
 
26
+ # Register telemetry shutdown on exit
27
+ atexit.register(shutdown)
28
+
24
29
 
25
30
  def _version_callback(value: bool) -> None:
26
31
  """Print version and exit if --version flag is used."""
@@ -43,7 +48,7 @@ def callback(
43
48
  False, "--verbose", "-v", help="Increase verbosity of output."
44
49
  ),
45
50
  no_telemetry: bool = typer.Option(
46
- False, "--no-telemetry", help="Disable telemetry collection."
51
+ False, "--no-telemetry", help="Disable telemetry collection (persists for future commands)."
47
52
  ),
48
53
  ) -> None:
49
54
  """GolfMCP: A Pythonic framework for building MCP servers with zero boilerplate."""
@@ -51,9 +56,10 @@ def callback(
51
56
  if verbose:
52
57
  os.environ["GOLF_VERBOSE"] = "1"
53
58
 
54
- # Set telemetry flag
59
+ # Set telemetry preference if flag is used
55
60
  if no_telemetry:
56
- os.environ["GOLF_TELEMETRY"] = "0"
61
+ set_telemetry_enabled(False, persist=True)
62
+ console.print("[dim]Telemetry has been disabled. You can re-enable it with: golf telemetry enable[/dim]")
57
63
 
58
64
 
59
65
  @app.command()
@@ -78,7 +84,7 @@ def init(
78
84
  if output_dir is None:
79
85
  output_dir = Path.cwd() / project_name
80
86
 
81
- # Execute the initialization command
87
+ # Execute the initialization command (it handles its own tracking)
82
88
  initialize_project(project_name=project_name, output_dir=output_dir, template=template)
83
89
 
84
90
 
@@ -100,6 +106,7 @@ def build_dev(
100
106
  if not project_root:
101
107
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
102
108
  console.print("Run 'golf init <project_name>' to create a new project.")
109
+ track_event("cli_build_failed", {"success": False, "environment": "dev"})
103
110
  raise typer.Exit(code=1)
104
111
 
105
112
  # Load settings from the found project
@@ -111,9 +118,15 @@ def build_dev(
111
118
  else:
112
119
  output_dir = Path(output_dir)
113
120
 
114
- # Build the project with environment variables copied
115
- from golf.commands.build import build_project
116
- build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
121
+ try:
122
+ # Build the project with environment variables copied
123
+ from golf.commands.build import build_project
124
+ build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
125
+ # Track successful build with environment
126
+ track_event("cli_build_success", {"success": True, "environment": "dev"})
127
+ except Exception:
128
+ track_event("cli_build_failed", {"success": False, "environment": "dev"})
129
+ raise
117
130
 
118
131
 
119
132
  @build_app.command("prod")
@@ -129,6 +142,7 @@ def build_prod(
129
142
  if not project_root:
130
143
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
131
144
  console.print("Run 'golf init <project_name>' to create a new project.")
145
+ track_event("cli_build_failed", {"success": False, "environment": "prod"})
132
146
  raise typer.Exit(code=1)
133
147
 
134
148
  # Load settings from the found project
@@ -140,9 +154,15 @@ def build_prod(
140
154
  else:
141
155
  output_dir = Path(output_dir)
142
156
 
143
- # Build the project without copying environment variables
144
- from golf.commands.build import build_project
145
- build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
157
+ try:
158
+ # Build the project without copying environment variables
159
+ from golf.commands.build import build_project
160
+ build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
161
+ # Track successful build with environment
162
+ track_event("cli_build_success", {"success": True, "environment": "prod"})
163
+ except Exception:
164
+ track_event("cli_build_failed", {"success": False, "environment": "prod"})
165
+ raise
146
166
 
147
167
 
148
168
  @app.command()
@@ -171,6 +191,7 @@ def run(
171
191
  if not project_root:
172
192
  console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
173
193
  console.print("Run 'golf init <project_name>' to create a new project.")
194
+ track_event("cli_run_failed", {"success": False})
174
195
  raise typer.Exit(code=1)
175
196
 
176
197
  # Load settings from the found project
@@ -192,21 +213,49 @@ def run(
192
213
  else:
193
214
  console.print(f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]")
194
215
  console.print("Run 'golf build' first or use --build to build automatically.")
216
+ track_event("cli_run_failed", {"success": False})
195
217
  raise typer.Exit(code=1)
196
218
 
197
- # Import and run the server
198
- from golf.commands.run import run_server
199
- return_code = run_server(
200
- project_path=project_root,
201
- settings=settings,
202
- dist_dir=dist_dir,
203
- host=host,
204
- port=port
205
- )
206
-
207
- # Exit with the same code as the server
208
- if return_code != 0:
209
- raise typer.Exit(code=return_code)
219
+ try:
220
+ # Import and run the server
221
+ from golf.commands.run import run_server
222
+ return_code = run_server(
223
+ project_path=project_root,
224
+ settings=settings,
225
+ dist_dir=dist_dir,
226
+ host=host,
227
+ port=port
228
+ )
229
+
230
+ # Track based on return code
231
+ if return_code == 0:
232
+ track_event("cli_run_success", {"success": True})
233
+ else:
234
+ track_event("cli_run_failed", {"success": False})
235
+
236
+ # Exit with the same code as the server
237
+ if return_code != 0:
238
+ raise typer.Exit(code=return_code)
239
+ except Exception:
240
+ track_event("cli_run_failed", {"success": False})
241
+ raise
242
+
243
+
244
+ # Add telemetry command group
245
+ @app.command()
246
+ def telemetry(
247
+ action: str = typer.Argument(..., help="Action to perform: 'enable' or 'disable'")
248
+ ):
249
+ """Manage telemetry settings."""
250
+ if action.lower() == "enable":
251
+ set_telemetry_enabled(True, persist=True)
252
+ console.print("[green]✓[/green] Telemetry enabled. Thank you for helping improve Golf!")
253
+ elif action.lower() == "disable":
254
+ set_telemetry_enabled(False, persist=True)
255
+ console.print("[yellow]Telemetry disabled.[/yellow] You can re-enable it anytime with: golf telemetry enable")
256
+ else:
257
+ console.print(f"[red]Unknown action '{action}'. Use 'enable' or 'disable'.[/red]")
258
+ raise typer.Exit(code=1)
210
259
 
211
260
 
212
261
  if __name__ == "__main__":
@@ -219,5 +268,12 @@ if __name__ == "__main__":
219
268
  )
220
269
  )
221
270
 
271
+ # Add telemetry notice if enabled
272
+ if is_telemetry_enabled():
273
+ console.print(
274
+ "[dim]📊 Anonymous usage data is collected to improve Golf. "
275
+ "Disable with: golf telemetry disable[/dim]\n"
276
+ )
277
+
222
278
  # Run the CLI app
223
279
  app()
@@ -1,12 +1,16 @@
1
1
  """Project initialization command implementation."""
2
2
 
3
+ import os
3
4
  import shutil
4
5
  from pathlib import Path
6
+ from typing import Optional
5
7
 
6
8
  from rich.console import Console
7
9
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
10
  from rich.prompt import Confirm
9
11
 
12
+ from golf.core.telemetry import track_event
13
+
10
14
  console = Console()
11
15
 
12
16
 
@@ -26,6 +30,7 @@ def initialize_project(
26
30
  if template not in ("basic", "advanced"):
27
31
  console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
28
32
  console.print("Available templates: basic, advanced")
33
+ track_event("cli_init_failed", {"success": False})
29
34
  return
30
35
 
31
36
  # Check if directory exists
@@ -34,6 +39,7 @@ def initialize_project(
34
39
  console.print(
35
40
  f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
36
41
  )
42
+ track_event("cli_init_failed", {"success": False})
37
43
  return
38
44
 
39
45
  # Check if directory is empty
@@ -43,6 +49,7 @@ def initialize_project(
43
49
  default=False,
44
50
  ):
45
51
  console.print("Initialization cancelled.")
52
+ track_event("cli_init_cancelled", {"success": False})
46
53
  return
47
54
  else:
48
55
  # Create the directory
@@ -59,6 +66,7 @@ def initialize_project(
59
66
  console.print(
60
67
  f"[bold red]Error:[/bold red] Could not find template '{template}'"
61
68
  )
69
+ track_event("cli_init_failed", {"success": False})
62
70
  return
63
71
 
64
72
  # Copy template files
@@ -77,6 +85,12 @@ def initialize_project(
77
85
  console.print(f"\nTo get started, run:")
78
86
  console.print(f" cd {output_dir.name}")
79
87
  console.print(f" golf build dev")
88
+
89
+ # Track successful initialization
90
+ track_event("cli_init_success", {
91
+ "success": True,
92
+ "template": template
93
+ })
80
94
 
81
95
 
82
96
  def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
@@ -62,7 +62,11 @@ def run_server(
62
62
  cwd=dist_dir,
63
63
  env=env,
64
64
  )
65
+
65
66
  return process.returncode
66
67
  except KeyboardInterrupt:
67
68
  console.print("\n[yellow]Server stopped by user[/yellow]")
68
- return 0
69
+ return 0
70
+ except Exception as e:
71
+ console.print(f"\n[bold red]Error running server:[/bold red] {e}")
72
+ return 1
@@ -83,7 +83,6 @@ class Settings(BaseSettings):
83
83
 
84
84
  # Feature flags
85
85
  telemetry: bool = Field(True, description="Enable anonymous telemetry")
86
- hot_reload: bool = Field(True, description="Enable hot reload in dev mode")
87
86
 
88
87
  # Project paths
89
88
  tools_dir: str = Field("tools", description="Directory containing tools")
@@ -6,7 +6,6 @@ from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
8
  from typing import Any, Dict, List, Optional
9
- import hashlib
10
9
 
11
10
  from rich.console import Console
12
11
 
@@ -436,47 +435,7 @@ def parse_project(project_path: Path) -> Dict[ComponentType, List[ParsedComponen
436
435
  return components
437
436
 
438
437
 
439
- def parse_project_incremental(project_path: Path, fingerprints: Dict[str, str]) -> Dict[ComponentType, List[ParsedComponent]]:
440
- """Parse a project with incremental file checking."""
441
- parser = AstParser(project_root=project_path)
442
-
443
- components: Dict[ComponentType, List[ParsedComponent]] = {
444
- ComponentType.TOOL: [],
445
- ComponentType.RESOURCE: [],
446
- ComponentType.PROMPT: []
447
- }
448
-
449
- # Process each directory
450
- for comp_type, dir_name in [
451
- (ComponentType.TOOL, "tools"),
452
- (ComponentType.RESOURCE, "resources"),
453
- (ComponentType.PROMPT, "prompts")
454
- ]:
455
- dir_path = project_path / dir_name
456
- if not dir_path.exists() or not dir_path.is_dir():
457
- continue
458
-
459
- # Parse only changed files using fingerprints
460
- for file_path in dir_path.glob("**/*.py"):
461
- if "__pycache__" in file_path.parts or file_path.name == "__init__.py":
462
- continue
463
-
464
- # Check if file changed
465
- with open(file_path, "rb") as f:
466
- content = f.read()
467
- file_hash = hashlib.sha1(content).hexdigest()
468
-
469
- rel_path = str(file_path.relative_to(project_path))
470
-
471
- if rel_path not in fingerprints or fingerprints[rel_path] != file_hash:
472
- try:
473
- file_components = parser.parse_file(file_path)
474
- components[comp_type].extend([c for c in file_components if c.type == comp_type])
475
- fingerprints[rel_path] = file_hash
476
- except Exception as e:
477
- console.print(f"[bold red]Error parsing {rel_path}: {e}[/bold red]")
478
-
479
- return components
438
+
480
439
 
481
440
 
482
441
  def parse_common_files(project_path: Path) -> Dict[str, Path]:
@@ -0,0 +1,257 @@
1
+ """Telemetry module for anonymous usage tracking with PostHog."""
2
+
3
+ import os
4
+ import hashlib
5
+ import platform
6
+ from pathlib import Path
7
+ from typing import Optional, Dict, Any
8
+ import json
9
+
10
+ import posthog
11
+ from rich.console import Console
12
+
13
+ from golf import __version__
14
+
15
+ console = Console()
16
+
17
+ # PostHog configuration
18
+ # This is a client-side API key, safe to be public
19
+ # Users can override with GOLF_POSTHOG_API_KEY environment variable
20
+ DEFAULT_POSTHOG_API_KEY = "phc_7ccsDDxoC5tK5hodlrs2moGC74cThRzcN63flRYPWGl" # Replace with your actual PostHog project API key
21
+ POSTHOG_API_KEY = os.environ.get("GOLF_POSTHOG_API_KEY", DEFAULT_POSTHOG_API_KEY)
22
+ POSTHOG_HOST = "https://us.i.posthog.com"
23
+
24
+ # Telemetry state
25
+ _telemetry_enabled: Optional[bool] = None
26
+ _anonymous_id: Optional[str] = None
27
+
28
+
29
+ def get_telemetry_config_path() -> Path:
30
+ """Get the path to the telemetry configuration file."""
31
+ return Path.home() / ".golf" / "telemetry.json"
32
+
33
+
34
+ def save_telemetry_preference(enabled: bool) -> None:
35
+ """Save telemetry preference to persistent storage."""
36
+ config_path = get_telemetry_config_path()
37
+ config_path.parent.mkdir(parents=True, exist_ok=True)
38
+
39
+ config = {
40
+ "enabled": enabled,
41
+ "version": 1
42
+ }
43
+
44
+ try:
45
+ with open(config_path, "w") as f:
46
+ json.dump(config, f)
47
+ except Exception:
48
+ # Don't fail if we can't save the preference
49
+ pass
50
+
51
+
52
+ def load_telemetry_preference() -> Optional[bool]:
53
+ """Load telemetry preference from persistent storage."""
54
+ config_path = get_telemetry_config_path()
55
+
56
+ if not config_path.exists():
57
+ return None
58
+
59
+ try:
60
+ with open(config_path, "r") as f:
61
+ config = json.load(f)
62
+ return config.get("enabled")
63
+ except Exception:
64
+ return None
65
+
66
+
67
+ def is_telemetry_enabled() -> bool:
68
+ """Check if telemetry is enabled.
69
+
70
+ Checks in order:
71
+ 1. Cached value
72
+ 2. GOLF_TELEMETRY environment variable
73
+ 3. Persistent preference file
74
+ 4. Default to True (opt-out model)
75
+ """
76
+ global _telemetry_enabled
77
+
78
+ if _telemetry_enabled is not None:
79
+ return _telemetry_enabled
80
+
81
+ # Check environment variables (highest priority)
82
+ env_telemetry = os.environ.get("GOLF_TELEMETRY", "").lower()
83
+ if env_telemetry in ("0", "false", "no", "off"):
84
+ _telemetry_enabled = False
85
+ return False
86
+ elif env_telemetry in ("1", "true", "yes", "on"):
87
+ _telemetry_enabled = True
88
+ return True
89
+
90
+ # Check persistent preference
91
+ saved_preference = load_telemetry_preference()
92
+ if saved_preference is not None:
93
+ _telemetry_enabled = saved_preference
94
+ return saved_preference
95
+
96
+ # Default to enabled (opt-out model)
97
+ _telemetry_enabled = True
98
+ return True
99
+
100
+
101
+ def set_telemetry_enabled(enabled: bool, persist: bool = True) -> None:
102
+ """Set telemetry enabled state.
103
+
104
+ Args:
105
+ enabled: Whether telemetry should be enabled
106
+ persist: Whether to save this preference persistently
107
+ """
108
+ global _telemetry_enabled
109
+ _telemetry_enabled = enabled
110
+
111
+ if persist:
112
+ save_telemetry_preference(enabled)
113
+
114
+
115
+ def get_anonymous_id() -> str:
116
+ """Get or create a persistent anonymous ID for this machine.
117
+
118
+ The ID is stored in the user's home directory and is based on
119
+ machine characteristics to be consistent across sessions.
120
+ """
121
+ global _anonymous_id
122
+
123
+ if _anonymous_id:
124
+ return _anonymous_id
125
+
126
+ # Try to load existing ID
127
+ id_file = Path.home() / ".golf" / "telemetry_id"
128
+
129
+ if id_file.exists():
130
+ try:
131
+ _anonymous_id = id_file.read_text().strip()
132
+ if _anonymous_id:
133
+ return _anonymous_id
134
+ except Exception:
135
+ pass
136
+
137
+ # Generate new ID based on machine characteristics
138
+ # This ensures the same ID across sessions on the same machine
139
+ machine_data = f"{platform.node()}-{platform.machine()}-{platform.system()}"
140
+ machine_hash = hashlib.sha256(machine_data.encode()).hexdigest()[:16]
141
+ _anonymous_id = f"golf-{machine_hash}"
142
+
143
+ # Try to save for next time
144
+ try:
145
+ id_file.parent.mkdir(parents=True, exist_ok=True)
146
+ id_file.write_text(_anonymous_id)
147
+ except Exception:
148
+ # Not critical if we can't save
149
+ pass
150
+
151
+ return _anonymous_id
152
+
153
+
154
+ def initialize_telemetry() -> None:
155
+ """Initialize PostHog telemetry if enabled."""
156
+ if not is_telemetry_enabled():
157
+ return
158
+
159
+ # Skip initialization if no valid API key
160
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
161
+ return
162
+
163
+ try:
164
+ posthog.project_api_key = POSTHOG_API_KEY
165
+ posthog.host = POSTHOG_HOST
166
+
167
+ # Disable PostHog's own logging to avoid noise
168
+ posthog.disabled = False
169
+ posthog.debug = False
170
+
171
+ except Exception:
172
+ # Telemetry should never break the application
173
+ pass
174
+
175
+
176
+ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) -> None:
177
+ """Track an anonymous event with minimal data.
178
+
179
+ Args:
180
+ event_name: Name of the event (e.g., "cli_init", "cli_build")
181
+ properties: Optional properties to include with the event
182
+ """
183
+ if not is_telemetry_enabled():
184
+ return
185
+
186
+ # Skip if no valid API key
187
+ if not POSTHOG_API_KEY or POSTHOG_API_KEY == DEFAULT_POSTHOG_API_KEY:
188
+ return
189
+
190
+ try:
191
+ # Initialize if needed
192
+ if posthog.project_api_key != POSTHOG_API_KEY:
193
+ initialize_telemetry()
194
+
195
+ # Get anonymous ID
196
+ anonymous_id = get_anonymous_id()
197
+
198
+ # Only include minimal, non-identifying properties
199
+ safe_properties = {
200
+ "golf_version": __version__,
201
+ "python_version": f"{platform.python_version_tuple()[0]}.{platform.python_version_tuple()[1]}",
202
+ "os": platform.system(),
203
+ }
204
+
205
+ # Filter properties to only include safe ones
206
+ if properties:
207
+ # Only include specific safe properties
208
+ safe_keys = {"success", "environment", "template", "command_type"}
209
+ for key in safe_keys:
210
+ if key in properties:
211
+ safe_properties[key] = properties[key]
212
+
213
+ # Send event
214
+ posthog.capture(
215
+ distinct_id=anonymous_id,
216
+ event=event_name,
217
+ properties=safe_properties,
218
+ )
219
+
220
+ except Exception:
221
+ # Telemetry should never break the application
222
+ pass
223
+
224
+
225
+ def track_command(command: str, success: bool = True) -> None:
226
+ """Track a CLI command execution with minimal info.
227
+
228
+ Args:
229
+ command: The command being executed (e.g., "init", "build", "run")
230
+ success: Whether the command was successful
231
+ """
232
+ # Simplify the event to just command and success
233
+ track_event(f"cli_{command}", {"success": success})
234
+
235
+
236
+ def flush() -> None:
237
+ """Flush any pending telemetry events."""
238
+ if not is_telemetry_enabled():
239
+ return
240
+
241
+ try:
242
+ posthog.flush()
243
+ except Exception:
244
+ # Ignore flush errors
245
+ pass
246
+
247
+
248
+ def shutdown() -> None:
249
+ """Shutdown telemetry and flush pending events."""
250
+ if not is_telemetry_enabled():
251
+ return
252
+
253
+ try:
254
+ posthog.shutdown()
255
+ except Exception:
256
+ # Ignore shutdown errors
257
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: golf-mcp
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Framework for building MCP servers
5
5
  Author-email: Antoni Gmitruk <antoni@golf.dev>
6
6
  License-Expression: Apache-2.0
@@ -27,6 +27,7 @@ Requires-Dist: python-dotenv>=1.1.0
27
27
  Requires-Dist: black>=24.10.0
28
28
  Requires-Dist: pyjwt>=2.0.0
29
29
  Requires-Dist: httpx>=0.28.1
30
+ Requires-Dist: posthog>=4.1.0
30
31
  Provides-Extra: telemetry
31
32
  Requires-Dist: opentelemetry-api>=1.33.1; extra == "telemetry"
32
33
  Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
@@ -38,9 +39,19 @@ Dynamic: license-file
38
39
  <div align="center">
39
40
  <img src="./golf-banner.png" alt="Golf Banner">
40
41
 
41
- <h1>Golf</h1>
42
+ <br>
42
43
 
43
- <p><strong>Easiest framework for building MCP servers.</strong></p>
44
+ <h1 align="center">
45
+ <br>
46
+ <span style="font-size: 80px;">⛳ Golf</span>
47
+ <br>
48
+ </h1>
49
+
50
+ <h3 align="center">
51
+ Easiest framework for building MCP servers
52
+ </h3>
53
+
54
+ <br>
44
55
 
45
56
  <p>
46
57
  <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
@@ -48,7 +59,9 @@ Dynamic: license-file
48
59
  <a href="https://github.com/golf-mcp/golf/issues"><img src="https://img.shields.io/badge/support-contact%20author-purple.svg" alt="Support"></a>
49
60
  </p>
50
61
 
51
- <p><a href="https://docs.golf.dev">Docs</a></p>
62
+ <p>
63
+ <a href="https://docs.golf.dev"><strong>📚 Documentation</strong></a>
64
+ </p>
52
65
  </div>
53
66
 
54
67
  ## Overview
@@ -87,7 +100,7 @@ cd your-project-name
87
100
  golf build dev
88
101
  golf run
89
102
  ```
90
- This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`). The `dev` command includes hot reloading, so changes to your component files will automatically restart the server.
103
+ This will start the FastMCP server, typically on `http://127.0.0.1:3000` (configurable in `golf.json`).
91
104
 
92
105
  That's it! Your Golf server is running and ready for integration.
93
106
 
@@ -151,29 +164,76 @@ Golf will automatically discover this file. The module docstring `"""Hello World
151
164
 
152
165
  ## Configuration (`golf.json`)
153
166
 
154
- Key aspects of your Golf server are configured in `golf.json`. The boilerplate provides a starting point like this:
167
+ The `golf.json` file is the heart of your Golf project configuration. Here's what each field controls:
155
168
 
156
169
  ```jsonc
157
170
  {
158
- "name": "{{project_name}}", // Will be replaced with your project name
159
- "description": "A Golf project", // A default description
160
- "host": "127.0.0.1", // Server host address
161
- "port": 3000, // Server port
162
- "transport": "sse", // 'sse', 'streamable-http', or 'stdio'
163
- "opentelemetry_enabled": false, // OpenTelemetry disabled by default - we're working on this as a feature
164
- "opentelemetry_default_exporter": "console"
171
+ "name": "{{project_name}}", // Your MCP server name (required)
172
+ "description": "A Golf project", // Brief description of your server's purpose
173
+ "host": "127.0.0.1", // Server host - use "0.0.0.0" to listen on all interfaces
174
+ "port": 3000, // Server port - any available port number
175
+ "transport": "sse", // Communication protocol:
176
+ // - "sse": Server-Sent Events (recommended for web clients)
177
+ // - "streamable-http": HTTP with streaming support
178
+ // - "stdio": Standard I/O (for CLI integration)
165
179
  }
166
180
  ```
167
- ## Roadmap
168
181
 
169
- We are actively developing Golf. Here's what's on our current roadmap:
182
+ ### Key Configuration Options:
183
+
184
+ - **`name`**: The identifier for your MCP server. This will be shown to clients connecting to your server.
185
+ - **`transport`**: Choose based on your client needs:
186
+ - `"sse"` is ideal for web-based clients and real-time communication
187
+ - `"streamable-http"` provides HTTP streaming for traditional API clients
188
+ - `"stdio"` enables integration with command-line tools and scripts
189
+ - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
190
+
191
+ ## Privacy & Telemetry
192
+
193
+ Golf collects **anonymous** usage data to help us understand how the framework is being used and improve it over time. The data collected includes:
194
+
195
+ - Commands run (init, build, run)
196
+ - Success/failure status (no error details)
197
+ - Golf version, Python version (major.minor only), and OS type
198
+ - Template name (for init command only)
199
+ - Build environment (dev/prod for build commands only)
170
200
 
201
+ **No personal information, project names, code content, or error messages are ever collected.**
202
+
203
+ ### Opting Out
204
+
205
+ You can disable telemetry in several ways:
206
+
207
+ 1. **Using the telemetry command** (recommended):
208
+ ```bash
209
+ golf telemetry disable
210
+ ```
211
+ This saves your preference permanently. To re-enable:
212
+ ```bash
213
+ golf telemetry enable
214
+ ```
215
+
216
+ 2. **During any command**: Add `--no-telemetry` to save your preference:
217
+ ```bash
218
+ golf init my-project --no-telemetry
219
+ ```
220
+
221
+ 3. **Environment variable** (temporary override):
222
+ ```bash
223
+ export GOLF_TELEMETRY=0
224
+ golf init my-project
225
+ ```
226
+
227
+ Your telemetry preference is stored in `~/.golf/telemetry.json` and persists across all Golf commands.
228
+
229
+ ## Roadmap
171
230
 
172
- ## Documentation
231
+ Here are the things we are working hard on:
173
232
 
174
- For more information, please visit our official documentation:
233
+ * **Native OpenTelemetry implementation for tracing**
234
+ * **`golf deploy` command for one click deployments to Vercel, Blaxel and other providers**
235
+ * **Production-ready OAuth token management, to allow for persistent, encrypted token storage and client mapping**
175
236
 
176
- [https://docs.golf.dev](https://docs.golf.dev)
177
237
 
178
238
  <div align="center">
179
239
  Made with ❤️ in Warsaw, Poland and SF
@@ -26,6 +26,7 @@ src/golf/core/builder_auth.py
26
26
  src/golf/core/builder_telemetry.py
27
27
  src/golf/core/config.py
28
28
  src/golf/core/parser.py
29
+ src/golf/core/telemetry.py
29
30
  src/golf/core/transformer.py
30
31
  src/golf/examples/__init__.py
31
32
  src/golf/examples/basic/.env
@@ -6,6 +6,7 @@ python-dotenv>=1.1.0
6
6
  black>=24.10.0
7
7
  pyjwt>=2.0.0
8
8
  httpx>=0.28.1
9
+ posthog>=4.1.0
9
10
 
10
11
  [telemetry]
11
12
  opentelemetry-api>=1.33.1
@@ -1 +0,0 @@
1
- __version__ = "0.1.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes