golf-mcp 0.1.10__tar.gz → 0.1.11__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.
- {golf_mcp-0.1.10/src/golf_mcp.egg-info → golf_mcp-0.1.11}/PKG-INFO +1 -1
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/pyproject.toml +2 -2
- golf_mcp-0.1.11/src/golf/__init__.py +1 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/cli/main.py +27 -14
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/commands/init.py +73 -62
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/builder.py +15 -4
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/telemetry.py +57 -4
- golf_mcp-0.1.11/src/golf/telemetry/instrumentation.py +900 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11/src/golf_mcp.egg-info}/PKG-INFO +1 -1
- golf_mcp-0.1.10/src/golf/__init__.py +0 -1
- golf_mcp-0.1.10/src/golf/telemetry/instrumentation.py +0 -540
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/docs.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/fast-mcp.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/fastmcp-example-1.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/fastmcp-example-2.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/mcp.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/oauth-implementation.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/.docs/oauth.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/LICENSE +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/MANIFEST.in +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/README.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/setup.cfg +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/auth/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/auth/api_key.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/auth/helpers.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/auth/oauth.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/auth/provider.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/cli/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/commands/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/commands/build.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/commands/run.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/builder_auth.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/builder_telemetry.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/config.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/parser.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/core/transformer.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/.env +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/.env.example +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/README.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/golf.json +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/pre_build.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/issues/create.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/issues/list.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/repos/list.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/search/code.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/api_key/tools/users/get.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/.env +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/.env.example +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/README.md +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/golf.json +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/pre_build.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/prompts/welcome.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/current_time.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/info.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/common.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/current.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/resources/weather/forecast.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/github_user.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/hello.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/charge.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/common.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/examples/basic/tools/payments/refund.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf/telemetry/__init__.py +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/SOURCES.txt +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/dependency_links.txt +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/entry_points.txt +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/requires.txt +0 -0
- {golf_mcp-0.1.10 → golf_mcp-0.1.11}/src/golf_mcp.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "golf-mcp"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.11"
|
|
8
8
|
description = "Framework for building MCP servers"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Antoni Gmitruk", email = "antoni@golf.dev"}
|
|
@@ -64,7 +64,7 @@ golf = ["examples/**/*"]
|
|
|
64
64
|
|
|
65
65
|
[tool.poetry]
|
|
66
66
|
name = "golf-mcp"
|
|
67
|
-
version = "0.1.
|
|
67
|
+
version = "0.1.11"
|
|
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"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.11"
|
|
@@ -106,7 +106,7 @@ def build_dev(
|
|
|
106
106
|
if not project_root:
|
|
107
107
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
108
108
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
109
|
-
track_event("cli_build_failed", {"success": False, "environment": "dev"})
|
|
109
|
+
track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
110
110
|
raise typer.Exit(code=1)
|
|
111
111
|
|
|
112
112
|
# Load settings from the found project
|
|
@@ -124,8 +124,10 @@ def build_dev(
|
|
|
124
124
|
build_project(project_root, settings, output_dir, build_env="dev", copy_env=True)
|
|
125
125
|
# Track successful build with environment
|
|
126
126
|
track_event("cli_build_success", {"success": True, "environment": "dev"})
|
|
127
|
-
except Exception:
|
|
128
|
-
|
|
127
|
+
except Exception as e:
|
|
128
|
+
error_type = type(e).__name__
|
|
129
|
+
error_message = str(e)
|
|
130
|
+
track_event("cli_build_failed", {"success": False, "environment": "dev", "error_type": error_type, "error_message": error_message})
|
|
129
131
|
raise
|
|
130
132
|
|
|
131
133
|
|
|
@@ -142,7 +144,7 @@ def build_prod(
|
|
|
142
144
|
if not project_root:
|
|
143
145
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
144
146
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
145
|
-
track_event("cli_build_failed", {"success": False, "environment": "prod"})
|
|
147
|
+
track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
146
148
|
raise typer.Exit(code=1)
|
|
147
149
|
|
|
148
150
|
# Load settings from the found project
|
|
@@ -160,8 +162,10 @@ def build_prod(
|
|
|
160
162
|
build_project(project_root, settings, output_dir, build_env="prod", copy_env=False)
|
|
161
163
|
# Track successful build with environment
|
|
162
164
|
track_event("cli_build_success", {"success": True, "environment": "prod"})
|
|
163
|
-
except Exception:
|
|
164
|
-
|
|
165
|
+
except Exception as e:
|
|
166
|
+
error_type = type(e).__name__
|
|
167
|
+
error_message = str(e)
|
|
168
|
+
track_event("cli_build_failed", {"success": False, "environment": "prod", "error_type": error_type, "error_message": error_message})
|
|
165
169
|
raise
|
|
166
170
|
|
|
167
171
|
|
|
@@ -191,7 +195,7 @@ def run(
|
|
|
191
195
|
if not project_root:
|
|
192
196
|
console.print("[bold red]Error: No GolfMCP project found in the current directory or any parent directory.[/bold red]")
|
|
193
197
|
console.print("Run 'golf init <project_name>' to create a new project.")
|
|
194
|
-
track_event("cli_run_failed", {"success": False})
|
|
198
|
+
track_event("cli_run_failed", {"success": False, "error_type": "NoProjectFound", "error_message": "No GolfMCP project found"})
|
|
195
199
|
raise typer.Exit(code=1)
|
|
196
200
|
|
|
197
201
|
# Load settings from the found project
|
|
@@ -207,13 +211,20 @@ def run(
|
|
|
207
211
|
if not dist_dir.exists():
|
|
208
212
|
if build_first:
|
|
209
213
|
console.print(f"[yellow]Dist directory {dist_dir} not found. Building first...[/yellow]")
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
try:
|
|
215
|
+
# Build the project
|
|
216
|
+
from golf.commands.build import build_project
|
|
217
|
+
build_project(project_root, settings, dist_dir)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
error_type = type(e).__name__
|
|
220
|
+
error_message = str(e)
|
|
221
|
+
console.print(f"[bold red]Error building project:[/bold red] {error_message}")
|
|
222
|
+
track_event("cli_run_failed", {"success": False, "error_type": f"BuildError.{error_type}", "error_message": error_message})
|
|
223
|
+
raise
|
|
213
224
|
else:
|
|
214
225
|
console.print(f"[bold red]Error: Dist directory {dist_dir} not found.[/bold red]")
|
|
215
226
|
console.print("Run 'golf build' first or use --build to build automatically.")
|
|
216
|
-
track_event("cli_run_failed", {"success": False})
|
|
227
|
+
track_event("cli_run_failed", {"success": False, "error_type": "DistNotFound", "error_message": "Dist directory not found"})
|
|
217
228
|
raise typer.Exit(code=1)
|
|
218
229
|
|
|
219
230
|
try:
|
|
@@ -231,13 +242,15 @@ def run(
|
|
|
231
242
|
if return_code == 0:
|
|
232
243
|
track_event("cli_run_success", {"success": True})
|
|
233
244
|
else:
|
|
234
|
-
track_event("cli_run_failed", {"success": False})
|
|
245
|
+
track_event("cli_run_failed", {"success": False, "error_type": "NonZeroExit", "error_message": f"Server exited with code {return_code}"})
|
|
235
246
|
|
|
236
247
|
# Exit with the same code as the server
|
|
237
248
|
if return_code != 0:
|
|
238
249
|
raise typer.Exit(code=return_code)
|
|
239
|
-
except Exception:
|
|
240
|
-
|
|
250
|
+
except Exception as e:
|
|
251
|
+
error_type = type(e).__name__
|
|
252
|
+
error_message = str(e)
|
|
253
|
+
track_event("cli_run_failed", {"success": False, "error_type": error_type, "error_message": error_message})
|
|
241
254
|
raise
|
|
242
255
|
|
|
243
256
|
|
|
@@ -9,7 +9,7 @@ from rich.console import Console
|
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
from rich.prompt import Confirm
|
|
11
11
|
|
|
12
|
-
from golf.core.telemetry import track_event
|
|
12
|
+
from golf.core.telemetry import track_event, track_command
|
|
13
13
|
|
|
14
14
|
console = Console()
|
|
15
15
|
|
|
@@ -26,72 +26,83 @@ def initialize_project(
|
|
|
26
26
|
output_dir: Directory where the project will be created
|
|
27
27
|
template: Template to use (basic or api_key)
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if
|
|
29
|
+
try:
|
|
30
|
+
# Validate template
|
|
31
|
+
valid_templates = ("basic", "api_key")
|
|
32
|
+
if template not in valid_templates:
|
|
33
|
+
console.print(f"[bold red]Error:[/bold red] Unknown template '{template}'")
|
|
34
|
+
console.print(f"Available templates: {', '.join(valid_templates)}")
|
|
35
|
+
track_command("init", success=False, error_type="InvalidTemplate", error_message=f"Unknown template: {template}")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Check if directory exists
|
|
39
|
+
if output_dir.exists():
|
|
40
|
+
if not output_dir.is_dir():
|
|
41
|
+
console.print(
|
|
42
|
+
f"[bold red]Error:[/bold red] '{output_dir}' exists but is not a directory."
|
|
43
|
+
)
|
|
44
|
+
track_command("init", success=False, error_type="NotADirectory", error_message="Target exists but is not a directory")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# Check if directory is empty
|
|
48
|
+
if any(output_dir.iterdir()):
|
|
49
|
+
if not Confirm.ask(
|
|
50
|
+
f"Directory '{output_dir}' is not empty. Continue anyway?",
|
|
51
|
+
default=False,
|
|
52
|
+
):
|
|
53
|
+
console.print("Initialization cancelled.")
|
|
54
|
+
track_event("cli_init_cancelled", {"success": False})
|
|
55
|
+
return
|
|
56
|
+
else:
|
|
57
|
+
# Create the directory
|
|
58
|
+
output_dir.mkdir(parents=True)
|
|
59
|
+
|
|
60
|
+
# Find template directory within the installed package
|
|
61
|
+
import golf
|
|
62
|
+
package_init_file = Path(golf.__file__)
|
|
63
|
+
# The 'examples' directory is now inside the 'golf' package directory
|
|
64
|
+
# e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
|
|
65
|
+
template_dir = package_init_file.parent / "examples" / template
|
|
66
|
+
|
|
67
|
+
if not template_dir.exists():
|
|
40
68
|
console.print(
|
|
41
|
-
f"[bold red]Error:[/bold red] '{
|
|
69
|
+
f"[bold red]Error:[/bold red] Could not find template '{template}'"
|
|
42
70
|
)
|
|
43
|
-
|
|
71
|
+
track_command("init", success=False, error_type="TemplateNotFound", error_message=f"Template directory not found: {template}")
|
|
44
72
|
return
|
|
45
73
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Create the directory
|
|
57
|
-
output_dir.mkdir(parents=True)
|
|
58
|
-
|
|
59
|
-
# Find template directory within the installed package
|
|
60
|
-
import golf
|
|
61
|
-
package_init_file = Path(golf.__file__)
|
|
62
|
-
# The 'examples' directory is now inside the 'golf' package directory
|
|
63
|
-
# e.g. golf/examples/basic, so go up one from __init__.py to get to 'golf'
|
|
64
|
-
template_dir = package_init_file.parent / "examples" / template
|
|
65
|
-
|
|
66
|
-
if not template_dir.exists():
|
|
67
|
-
console.print(
|
|
68
|
-
f"[bold red]Error:[/bold red] Could not find template '{template}'"
|
|
69
|
-
)
|
|
70
|
-
track_event("cli_init_failed", {"success": False})
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
# Copy template files
|
|
74
|
-
with Progress(
|
|
75
|
-
SpinnerColumn(),
|
|
76
|
-
TextColumn("[bold green]Creating project structure...[/bold green]"),
|
|
77
|
-
transient=True,
|
|
78
|
-
) as progress:
|
|
79
|
-
progress.add_task("copying", total=None)
|
|
74
|
+
# Copy template files
|
|
75
|
+
with Progress(
|
|
76
|
+
SpinnerColumn(),
|
|
77
|
+
TextColumn("[bold green]Creating project structure...[/bold green]"),
|
|
78
|
+
transient=True,
|
|
79
|
+
) as progress:
|
|
80
|
+
progress.add_task("copying", total=None)
|
|
81
|
+
|
|
82
|
+
# Copy directory structure
|
|
83
|
+
_copy_template(template_dir, output_dir, project_name)
|
|
80
84
|
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
# Create virtual environment
|
|
86
|
+
console.print("[bold green]Project initialized successfully![/bold green]")
|
|
87
|
+
console.print(f"\nTo get started, run:")
|
|
88
|
+
console.print(f" cd {output_dir.name}")
|
|
89
|
+
console.print(f" golf build dev")
|
|
90
|
+
|
|
91
|
+
# Track successful initialization
|
|
92
|
+
track_event("cli_init_success", {
|
|
93
|
+
"success": True,
|
|
94
|
+
"template": template
|
|
95
|
+
})
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Capture error details for telemetry
|
|
98
|
+
error_type = type(e).__name__
|
|
99
|
+
error_message = str(e)
|
|
100
|
+
|
|
101
|
+
console.print(f"[bold red]Error during initialization:[/bold red] {error_message}")
|
|
102
|
+
track_command("init", success=False, error_type=error_type, error_message=error_message)
|
|
103
|
+
|
|
104
|
+
# Re-raise to maintain existing behavior
|
|
105
|
+
raise
|
|
95
106
|
|
|
96
107
|
|
|
97
108
|
def _copy_template(source_dir: Path, target_dir: Path, project_name: str) -> None:
|
|
@@ -765,14 +765,25 @@ class CodeGenerator:
|
|
|
765
765
|
" # For SSE with API key auth, we need to get the app and add middleware",
|
|
766
766
|
" app = mcp.http_app(transport=\"sse\")",
|
|
767
767
|
" app.add_middleware(ApiKeyMiddleware)",
|
|
768
|
-
" # Run with the configured app",
|
|
769
|
-
" uvicorn.run(app, host=host, port=port, log_level=\"info\")"
|
|
770
768
|
])
|
|
771
769
|
else:
|
|
772
770
|
main_code.extend([
|
|
773
|
-
" # For SSE,
|
|
774
|
-
" mcp.
|
|
771
|
+
" # For SSE, get the app to add middleware",
|
|
772
|
+
" app = mcp.http_app(transport=\"sse\")",
|
|
775
773
|
])
|
|
774
|
+
|
|
775
|
+
# Add OpenTelemetry middleware to the SSE app if enabled
|
|
776
|
+
if self.settings.opentelemetry_enabled:
|
|
777
|
+
main_code.extend([
|
|
778
|
+
" # Apply OpenTelemetry middleware to the SSE app",
|
|
779
|
+
" from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware",
|
|
780
|
+
" app = OpenTelemetryMiddleware(app)",
|
|
781
|
+
])
|
|
782
|
+
|
|
783
|
+
main_code.extend([
|
|
784
|
+
" # Run with the configured app",
|
|
785
|
+
" uvicorn.run(app, host=host, port=port, log_level=\"info\")"
|
|
786
|
+
])
|
|
776
787
|
elif self.settings.transport in ["streamable-http", "http"]:
|
|
777
788
|
main_code.extend([
|
|
778
789
|
" # Create HTTP app and run with uvicorn",
|
|
@@ -243,7 +243,7 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
|
|
|
243
243
|
# Filter properties to only include safe ones
|
|
244
244
|
if properties:
|
|
245
245
|
# Only include specific safe properties
|
|
246
|
-
safe_keys = {"success", "environment", "template", "command_type"}
|
|
246
|
+
safe_keys = {"success", "environment", "template", "command_type", "error_type", "error_message"}
|
|
247
247
|
for key in safe_keys:
|
|
248
248
|
if key in properties:
|
|
249
249
|
safe_properties[key] = properties[key]
|
|
@@ -260,15 +260,68 @@ def track_event(event_name: str, properties: Optional[Dict[str, Any]] = None) ->
|
|
|
260
260
|
pass
|
|
261
261
|
|
|
262
262
|
|
|
263
|
-
def track_command(command: str, success: bool = True) -> None:
|
|
263
|
+
def track_command(command: str, success: bool = True, error_type: Optional[str] = None, error_message: Optional[str] = None) -> None:
|
|
264
264
|
"""Track a CLI command execution with minimal info.
|
|
265
265
|
|
|
266
266
|
Args:
|
|
267
267
|
command: The command being executed (e.g., "init", "build", "run")
|
|
268
268
|
success: Whether the command was successful
|
|
269
|
+
error_type: Type of error if command failed (e.g., "ValueError", "FileNotFoundError")
|
|
270
|
+
error_message: Sanitized error message (no sensitive data)
|
|
269
271
|
"""
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
properties = {"success": success}
|
|
273
|
+
|
|
274
|
+
# Add error details if command failed
|
|
275
|
+
if not success and (error_type or error_message):
|
|
276
|
+
if error_type:
|
|
277
|
+
properties["error_type"] = error_type
|
|
278
|
+
if error_message:
|
|
279
|
+
# Sanitize error message - remove file paths and sensitive info
|
|
280
|
+
sanitized_message = _sanitize_error_message(error_message)
|
|
281
|
+
properties["error_message"] = sanitized_message
|
|
282
|
+
|
|
283
|
+
track_event(f"cli_{command}", properties)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _sanitize_error_message(message: str) -> str:
|
|
287
|
+
"""Sanitize error message to remove sensitive information.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
message: Raw error message
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Sanitized error message
|
|
294
|
+
"""
|
|
295
|
+
import re
|
|
296
|
+
|
|
297
|
+
# Remove absolute file paths but keep the filename
|
|
298
|
+
# Unix-style paths
|
|
299
|
+
message = re.sub(r'/(?:Users|home|var|tmp|opt|usr|etc)/[^\s"\']+/([^/\s"\']+)', r'\1', message)
|
|
300
|
+
# Windows-style paths
|
|
301
|
+
message = re.sub(r'[A-Za-z]:\\[^\s"\']+\\([^\\s"\']+)', r'\1', message)
|
|
302
|
+
# Generic path pattern (catches remaining paths)
|
|
303
|
+
message = re.sub(r'(?:^|[\s"])(/[^\s"\']+/)+([^/\s"\']+)', r'\2', message)
|
|
304
|
+
|
|
305
|
+
# Remove potential API keys or tokens (common patterns)
|
|
306
|
+
# Generic API keys (20+ alphanumeric with underscores/hyphens)
|
|
307
|
+
message = re.sub(r'\b[a-zA-Z0-9_-]{32,}\b', '[REDACTED]', message)
|
|
308
|
+
# Bearer tokens
|
|
309
|
+
message = re.sub(r'Bearer\s+[a-zA-Z0-9_.-]+', 'Bearer [REDACTED]', message)
|
|
310
|
+
|
|
311
|
+
# Remove email addresses
|
|
312
|
+
message = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[EMAIL]', message)
|
|
313
|
+
|
|
314
|
+
# Remove IP addresses
|
|
315
|
+
message = re.sub(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', '[IP]', message)
|
|
316
|
+
|
|
317
|
+
# Remove port numbers in URLs
|
|
318
|
+
message = re.sub(r':[0-9]{2,5}(?=/|$|\s)', ':[PORT]', message)
|
|
319
|
+
|
|
320
|
+
# Truncate to reasonable length
|
|
321
|
+
if len(message) > 200:
|
|
322
|
+
message = message[:197] + "..."
|
|
323
|
+
|
|
324
|
+
return message
|
|
272
325
|
|
|
273
326
|
|
|
274
327
|
def flush() -> None:
|