golf-mcp 0.1.16__tar.gz → 0.1.18__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 (79) hide show
  1. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/docs.md +2 -2
  2. {golf_mcp-0.1.16/src/golf_mcp.egg-info → golf_mcp-0.1.18}/PKG-INFO +10 -3
  3. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/README.md +6 -1
  4. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/pyproject.toml +9 -4
  5. golf_mcp-0.1.18/src/golf/__init__.py +1 -0
  6. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/cli/main.py +13 -2
  7. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/commands/init.py +63 -1
  8. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/builder.py +220 -59
  9. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/builder_auth.py +5 -0
  10. golf_mcp-0.1.18/src/golf/core/builder_metrics.py +232 -0
  11. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/config.py +12 -0
  12. golf_mcp-0.1.18/src/golf/core/parser.py +1059 -0
  13. golf_mcp-0.1.18/src/golf/core/platform.py +180 -0
  14. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/telemetry.py +28 -8
  15. golf_mcp-0.1.18/src/golf/examples/api_key/.env.example +1 -0
  16. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/README.md +10 -10
  17. golf_mcp-0.1.18/src/golf/examples/api_key/golf.json +8 -0
  18. golf_mcp-0.1.18/src/golf/examples/basic/.env.example +4 -0
  19. golf_mcp-0.1.18/src/golf/examples/basic/golf.json +8 -0
  20. golf_mcp-0.1.18/src/golf/metrics/__init__.py +10 -0
  21. golf_mcp-0.1.18/src/golf/metrics/collector.py +239 -0
  22. golf_mcp-0.1.18/src/golf/metrics/registry.py +12 -0
  23. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/telemetry/instrumentation.py +177 -144
  24. {golf_mcp-0.1.16 → golf_mcp-0.1.18/src/golf_mcp.egg-info}/PKG-INFO +10 -3
  25. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/SOURCES.txt +5 -0
  26. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/requires.txt +4 -1
  27. golf_mcp-0.1.16/src/golf/__init__.py +0 -1
  28. golf_mcp-0.1.16/src/golf/core/parser.py +0 -560
  29. golf_mcp-0.1.16/src/golf/examples/api_key/.env.example +0 -5
  30. golf_mcp-0.1.16/src/golf/examples/api_key/golf.json +0 -12
  31. golf_mcp-0.1.16/src/golf/examples/basic/.env.example +0 -5
  32. golf_mcp-0.1.16/src/golf/examples/basic/golf.json +0 -12
  33. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/fast-mcp.md +0 -0
  34. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/fastmcp-example-1.py +0 -0
  35. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/fastmcp-example-2.py +0 -0
  36. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/mcp.md +0 -0
  37. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/oauth-implementation.md +0 -0
  38. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/.docs/oauth.md +0 -0
  39. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/LICENSE +0 -0
  40. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/MANIFEST.in +0 -0
  41. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/setup.cfg +0 -0
  42. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/auth/__init__.py +0 -0
  43. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/auth/api_key.py +0 -0
  44. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/auth/helpers.py +0 -0
  45. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/auth/oauth.py +0 -0
  46. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/auth/provider.py +0 -0
  47. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/cli/__init__.py +0 -0
  48. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/commands/__init__.py +0 -0
  49. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/commands/build.py +0 -0
  50. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/commands/run.py +0 -0
  51. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/__init__.py +0 -0
  52. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/builder_telemetry.py +0 -0
  53. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/core/transformer.py +0 -0
  54. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/__init__.py +0 -0
  55. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/.env +0 -0
  56. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/pre_build.py +0 -0
  57. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/issues/create.py +0 -0
  58. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/issues/list.py +0 -0
  59. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/repos/list.py +0 -0
  60. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/search/code.py +0 -0
  61. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/api_key/tools/users/get.py +0 -0
  62. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/.env +0 -0
  63. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/README.md +0 -0
  64. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/pre_build.py +0 -0
  65. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/prompts/welcome.py +0 -0
  66. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/current_time.py +0 -0
  67. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/info.py +0 -0
  68. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/common.py +0 -0
  69. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/current.py +0 -0
  70. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
  71. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/github_user.py +0 -0
  72. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/hello.py +0 -0
  73. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/charge.py +0 -0
  74. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/common.py +0 -0
  75. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/examples/basic/tools/payments/refund.py +0 -0
  76. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf/telemetry/__init__.py +0 -0
  77. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
  78. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/src/golf_mcp.egg-info/entry_points.txt +0 -0
  79. {golf_mcp-0.1.16 → golf_mcp-0.1.18}/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.16
3
+ Version: 0.1.18
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
@@ -34,6 +34,8 @@ Requires-Dist: opentelemetry-sdk>=1.33.1; extra == "telemetry"
34
34
  Requires-Dist: opentelemetry-instrumentation-asgi>=0.40b0; extra == "telemetry"
35
35
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=0.40b0; extra == "telemetry"
36
36
  Requires-Dist: wrapt>=1.17.0; extra == "telemetry"
37
+ Provides-Extra: metrics
38
+ Requires-Dist: prometheus-client>=0.22.1; extra == "metrics"
37
39
  Dynamic: license-file
38
40
 
39
41
  <div align="center">
@@ -128,7 +130,7 @@ A Golf project initialized with `golf init` will have a structure similar to thi
128
130
 
129
131
  - **`golf.json`**: Configures server name, port, transport, telemetry, and other build settings.
130
132
  - **`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).
133
+ - 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
134
  - **`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
135
 
134
136
  ## Example: Defining a Tool
@@ -178,6 +180,10 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
178
180
  // - "streamable-http": HTTP with streaming support
179
181
  // - "stdio": Standard I/O (for CLI integration)
180
182
 
183
+ // HTTP Transport Configuration (optional)
184
+ "stateless_http": false, // Make streamable-http transport stateless (new session per request)
185
+ // When true, server restarts won't break existing client connections
186
+
181
187
  // Health Check Configuration (optional)
182
188
  "health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
183
189
  "health_check_path": "/health", // HTTP path for health check endpoint
@@ -198,6 +204,7 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
198
204
  - `"streamable-http"` provides HTTP streaming for traditional API clients
199
205
  - `"stdio"` enables integration with command-line tools and scripts
200
206
  - **`host` & `port`**: Control where your server listens. Use `"127.0.0.1"` for local development or `"0.0.0.0"` to accept external connections.
207
+ - **`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.
201
208
  - **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
202
209
  - **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
203
210
  - **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
@@ -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,10 @@ 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
+
143
147
  // Health Check Configuration (optional)
144
148
  "health_check_enabled": false, // Enable health check endpoint for Kubernetes/load balancers
145
149
  "health_check_path": "/health", // HTTP path for health check endpoint
@@ -160,6 +164,7 @@ The `golf.json` file is the heart of your Golf project configuration. Here's wha
160
164
  - `"streamable-http"` provides HTTP streaming for traditional API clients
161
165
  - `"stdio"` enables integration with command-line tools and scripts
162
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.
163
168
  - **`health_check_enabled`**: When true, enables a health check endpoint for Kubernetes readiness/liveness probes and load balancers
164
169
  - **`health_check_path`**: Customizable path for the health check endpoint (defaults to "/health")
165
170
  - **`health_check_response`**: Customizable response text for successful health checks (defaults to "OK")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "golf-mcp"
7
- version = "0.1.16"
7
+ version = "0.1.18"
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",
@@ -45,6 +45,9 @@ telemetry = [
45
45
  "opentelemetry-exporter-otlp-proto-http>=0.40b0",
46
46
  "wrapt>=1.17.0"
47
47
  ]
48
+ metrics = [
49
+ "prometheus-client>=0.22.1"
50
+ ]
48
51
 
49
52
  [project.scripts]
50
53
  golf = "golf.cli.main:app"
@@ -64,7 +67,7 @@ golf = ["examples/**/*"]
64
67
 
65
68
  [tool.poetry]
66
69
  name = "golf-mcp"
67
- version = "0.1.16"
70
+ version = "0.1.18"
68
71
  description = "Framework for building MCP servers with zero boilerplate"
69
72
  authors = ["Antoni Gmitruk <antoni@golf.dev>"]
70
73
  license = "Apache-2.0"
@@ -86,7 +89,7 @@ classifiers = [
86
89
 
87
90
  [tool.poetry.dependencies]
88
91
  python = ">=3.8" # Match requires-python
89
- fastmcp = ">=2.0.0"
92
+ fastmcp = ">=2.0.0,<2.6.0"
90
93
  typer = {extras = ["all"], version = ">=0.15.4"}
91
94
  pydantic = ">=2.11.0"
92
95
  rich = ">=14.0.0"
@@ -100,9 +103,11 @@ opentelemetry-sdk = {version = ">=1.33.1", optional = true}
100
103
  opentelemetry-instrumentation-asgi = {version = ">=0.40b0", optional = true}
101
104
  opentelemetry-exporter-otlp-proto-http = {version = ">=0.40b0", optional = true}
102
105
  wrapt = {version = ">=1.17.0", optional = true}
106
+ prometheus-client = {version = ">=0.22.1", optional = true}
103
107
 
104
108
  [tool.poetry.extras]
105
109
  telemetry = ["opentelemetry-api", "opentelemetry-sdk", "opentelemetry-instrumentation-asgi", "opentelemetry-exporter-otlp-proto-http", "wrapt"]
110
+ metrics = ["prometheus-client"]
106
111
 
107
112
  [tool.poetry.group.dev.dependencies]
108
113
  pytest = "^7.4.0"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.18"
@@ -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
 
@@ -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
 
@@ -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