windsurf-throttle 1.0.1__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.
@@ -0,0 +1,8 @@
1
+ # Windsurf Credit Throttle Configuration
2
+ # Copy this to .env and fill in your actual values
3
+
4
+ # Required: Your Windsurf service key (get from admin panel)
5
+ WINDSURF_SERVICE_KEY=your_service_key_here
6
+
7
+ # Optional: Override default API base URL
8
+ # WINDSURF_BASE_URL=https://server.codeium.com
@@ -0,0 +1,50 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-n-publish:
9
+ name: Build and publish Python distro to PyPI
10
+ runs-on: ubuntu-latest
11
+ environment:
12
+ name: pypi
13
+ url: https://pypi.org/p/windsurf-throttle # Replace this
14
+ permissions:
15
+ id-token: write # Mandatory for Trusted Publishing
16
+ contents: read
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.x"
25
+
26
+ - name: Install andd upgrade build tools
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install --upgrade build setuptools wheel
30
+
31
+ - name: Verify pyproject.toml
32
+ run: |
33
+ echo "Checking pyproject.toml hatchling config:"
34
+ grep -A 2 "tool.hatch.build.targets.wheel" pyproject.toml
35
+
36
+ - name: Clean build artifacts
37
+ run: rm -rf dist/ build/ *.egg-info
38
+
39
+ - name: Build binary wheel and source tarball
40
+ run: python -m build
41
+
42
+ - name: Verify wheel metadata
43
+ run: |
44
+ echo "Checking wheel contents:"
45
+ unzip -l dist/*.whl | grep METADATA
46
+ echo "Extracting metadata:"
47
+ unzip -p dist/*.whl */METADATA | head -20
48
+
49
+ - name: Publish to PyPI
50
+ uses: pypa/gh-action-pypi-publish@release/v1.12
@@ -0,0 +1,50 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+
29
+ # Environment files
30
+ .env
31
+
32
+ # IDE
33
+ .idea/
34
+ .vscode/
35
+ *.swp
36
+ *.swo
37
+
38
+ # Testing
39
+ .pytest_cache/
40
+ .coverage
41
+ htmlcov/
42
+ .mypy_cache/
43
+
44
+ # Logs
45
+ logs/
46
+ *.log
47
+
48
+ # OS
49
+ .DS_Store
50
+ Thumbs.db
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.8.0
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/mirrors-mypy
10
+ rev: v1.13.0
11
+ hooks:
12
+ - id: mypy
13
+ additional_dependencies:
14
+ - httpx
15
+ - pandas-stubs
16
+ - types-requests
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: windsurf-throttle
3
+ Version: 1.0.1
4
+ Summary: Streamlit app for managing Windsurf credit caps
5
+ Project-URL: Homepage, https://github.com/PFPT-Internal/windsurf-throttle
6
+ Project-URL: Repository, https://github.com/PFPT-Internal/windsurf-throttle
7
+ Author-email: Your Name <your.email@example.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: credit,streamlit,throttle,windsurf
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: httpx>=0.27.0
20
+ Requires-Dist: pandas>=2.0.0
21
+ Requires-Dist: python-dotenv>=1.0.0
22
+ Requires-Dist: streamlit>=1.40.0
23
+ Requires-Dist: watchdog>=4.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.13.0; extra == 'dev'
26
+ Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Windsurf Credit Throttle
32
+
33
+ A Streamlit application for managing Windsurf add-on credit caps.
34
+
35
+ ## Features
36
+
37
+ - **Verify Credit Caps**: Check team-level and individual user credit configurations
38
+ - **Set Team Cap**: Configure organization-wide add-on credit limits
39
+ - **Set Individual Caps**: Set custom caps for specific users or bulk import from CSV
40
+
41
+ ## Installation
42
+
43
+ ### Using uvx (recommended)
44
+
45
+ ```bash
46
+ # Run directly from GitHub
47
+ uvx --from git+https://github.com/PFPT-Internal/windsurf-throttle windsurf-throttle
48
+
49
+ # Or from PyPI (when published)
50
+ uvx windsurf-throttle
51
+ ```
52
+
53
+ ### Using pip
54
+
55
+ ```bash
56
+ pip install windsurf-throttle
57
+ windsurf-throttle
58
+ ```
59
+
60
+ ### From source
61
+
62
+ ```bash
63
+ git clone https://github.com/PFPT-Internal/windsurf-throttle.git
64
+ cd windsurf-throttle
65
+ uv sync
66
+ uv run windsurf-throttle
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ Set your Windsurf service key as an environment variable:
72
+
73
+ ```bash
74
+ export WINDSURF_SERVICE_KEY=your_service_key_here
75
+ ```
76
+
77
+ Or create a `.env` file in your working directory:
78
+
79
+ ```
80
+ WINDSURF_SERVICE_KEY=your_service_key_here
81
+ ```
82
+
83
+ ### Optional Configuration
84
+
85
+ ```bash
86
+ # Override the API base URL (default: https://server.codeium.com)
87
+ export WINDSURF_BASE_URL=https://custom.server.com
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ### Running the App
93
+
94
+ ```bash
95
+ # Using the CLI
96
+ windsurf-throttle
97
+
98
+ # Or run Streamlit directly
99
+ streamlit run src/windsurf_throttle/app.py
100
+ ```
101
+
102
+ ### Credit Cap Strategy
103
+
104
+ The app implements the following strategy:
105
+
106
+ 1. **Base Credits**: Each user gets 500 base credits
107
+ 2. **Organization Cap**: Set a default add-on cap for all users (e.g., 1000)
108
+ 3. **Individual Caps**: For high-usage users, set individual caps = current add-on usage + buffer
109
+
110
+ Example:
111
+ - User with 1200 total credits used → 700 add-on credits used
112
+ - Individual cap = 700 + 500 buffer = 1200 add-on credits
113
+ - Total available = 500 base + 1200 add-on = 1700 credits
114
+
115
+ ### Bulk CSV Import
116
+
117
+ Upload a CSV with these columns:
118
+ - `email` (required): User email address
119
+ - `credits_used` (required): Current total credit usage
120
+ - `name` (optional): User display name
121
+
122
+ ## Development
123
+
124
+ ```bash
125
+ # Clone and install dev dependencies
126
+ git clone https://github.com/PFPT-Internal/windsurf-throttle.git
127
+ cd windsurf-throttle
128
+ uv sync --all-extras
129
+
130
+ # Run tests
131
+ uv run pytest
132
+
133
+ # Run linting
134
+ uv run ruff check src tests
135
+ uv run mypy src
136
+
137
+ # Install pre-commit hooks
138
+ uv run pre-commit install
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,113 @@
1
+ # Windsurf Credit Throttle
2
+
3
+ A Streamlit application for managing Windsurf add-on credit caps.
4
+
5
+ ## Features
6
+
7
+ - **Verify Credit Caps**: Check team-level and individual user credit configurations
8
+ - **Set Team Cap**: Configure organization-wide add-on credit limits
9
+ - **Set Individual Caps**: Set custom caps for specific users or bulk import from CSV
10
+
11
+ ## Installation
12
+
13
+ ### Using uvx (recommended)
14
+
15
+ ```bash
16
+ # Run directly from GitHub
17
+ uvx --from git+https://github.com/PFPT-Internal/windsurf-throttle windsurf-throttle
18
+
19
+ # Or from PyPI (when published)
20
+ uvx windsurf-throttle
21
+ ```
22
+
23
+ ### Using pip
24
+
25
+ ```bash
26
+ pip install windsurf-throttle
27
+ windsurf-throttle
28
+ ```
29
+
30
+ ### From source
31
+
32
+ ```bash
33
+ git clone https://github.com/PFPT-Internal/windsurf-throttle.git
34
+ cd windsurf-throttle
35
+ uv sync
36
+ uv run windsurf-throttle
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ Set your Windsurf service key as an environment variable:
42
+
43
+ ```bash
44
+ export WINDSURF_SERVICE_KEY=your_service_key_here
45
+ ```
46
+
47
+ Or create a `.env` file in your working directory:
48
+
49
+ ```
50
+ WINDSURF_SERVICE_KEY=your_service_key_here
51
+ ```
52
+
53
+ ### Optional Configuration
54
+
55
+ ```bash
56
+ # Override the API base URL (default: https://server.codeium.com)
57
+ export WINDSURF_BASE_URL=https://custom.server.com
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ ### Running the App
63
+
64
+ ```bash
65
+ # Using the CLI
66
+ windsurf-throttle
67
+
68
+ # Or run Streamlit directly
69
+ streamlit run src/windsurf_throttle/app.py
70
+ ```
71
+
72
+ ### Credit Cap Strategy
73
+
74
+ The app implements the following strategy:
75
+
76
+ 1. **Base Credits**: Each user gets 500 base credits
77
+ 2. **Organization Cap**: Set a default add-on cap for all users (e.g., 1000)
78
+ 3. **Individual Caps**: For high-usage users, set individual caps = current add-on usage + buffer
79
+
80
+ Example:
81
+ - User with 1200 total credits used → 700 add-on credits used
82
+ - Individual cap = 700 + 500 buffer = 1200 add-on credits
83
+ - Total available = 500 base + 1200 add-on = 1700 credits
84
+
85
+ ### Bulk CSV Import
86
+
87
+ Upload a CSV with these columns:
88
+ - `email` (required): User email address
89
+ - `credits_used` (required): Current total credit usage
90
+ - `name` (optional): User display name
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ # Clone and install dev dependencies
96
+ git clone https://github.com/PFPT-Internal/windsurf-throttle.git
97
+ cd windsurf-throttle
98
+ uv sync --all-extras
99
+
100
+ # Run tests
101
+ uv run pytest
102
+
103
+ # Run linting
104
+ uv run ruff check src tests
105
+ uv run mypy src
106
+
107
+ # Install pre-commit hooks
108
+ uv run pre-commit install
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "windsurf-throttle"
3
+ version = "1.0.1"
4
+ description = "Streamlit app for managing Windsurf credit caps"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Your Name", email = "your.email@example.com" }]
9
+ keywords = ["windsurf", "credit", "throttle", "streamlit"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ ]
19
+ dependencies = [
20
+ "httpx>=0.27.0",
21
+ "python-dotenv>=1.0.0",
22
+ "streamlit>=1.40.0",
23
+ "pandas>=2.0.0",
24
+ "watchdog>=4.0.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/PFPT-Internal/windsurf-throttle"
29
+ Repository = "https://github.com/PFPT-Internal/windsurf-throttle"
30
+
31
+ [project.scripts]
32
+ windsurf-throttle = "windsurf_throttle.cli:main"
33
+
34
+ [project.gui-scripts]
35
+ windsurf-throttle-gui = "windsurf_throttle.app:run"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=8.0.0",
40
+ "ruff>=0.8.0",
41
+ "mypy>=1.13.0",
42
+ "pre-commit>=4.0.0",
43
+ ]
44
+
45
+ [build-system]
46
+ requires = ["hatchling"]
47
+ build-backend = "hatchling.build"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/windsurf_throttle"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py310"
55
+
56
+ [tool.ruff.lint]
57
+ select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
58
+ ignore = ["E501"]
59
+
60
+ [tool.mypy]
61
+ python_version = "3.10"
62
+ warn_return_any = true
63
+ warn_unused_configs = true
64
+ disallow_untyped_defs = true
65
+
66
+ [tool.pytest.ini_options]
67
+ testpaths = ["tests"]
68
+ pythonpath = ["src"]
@@ -0,0 +1,3 @@
1
+ """Windsurf Throttle - Credit cap management for Windsurf."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,175 @@
1
+ """Windsurf API client for credit cap management."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ import httpx
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+ API_BASE_URL = os.getenv("WINDSURF_BASE_URL", "https://server.codeium.com")
12
+ SERVICE_KEY = os.getenv("WINDSURF_SERVICE_KEY")
13
+
14
+ BASE_CREDITS = 500
15
+ DEFAULT_ORG_ADDON_CAP = 1000
16
+ DEFAULT_INDIVIDUAL_CAP_THRESHOLD = 1000
17
+ DEFAULT_INDIVIDUAL_CAP_BUFFER = 500
18
+
19
+
20
+ class WindsurfAPIError(Exception):
21
+ """Exception raised for Windsurf API errors."""
22
+
23
+ pass
24
+
25
+
26
+ def get_service_key() -> str:
27
+ """Get the service key from environment, raising if not found."""
28
+ if not SERVICE_KEY:
29
+ raise WindsurfAPIError("WINDSURF_SERVICE_KEY not found in environment")
30
+ return SERVICE_KEY
31
+
32
+
33
+ def get_usage_config(
34
+ team_level: bool = False,
35
+ group_id: str | None = None,
36
+ user_email: str | None = None,
37
+ ) -> dict[str, Any]:
38
+ """Get usage configuration via Windsurf API.
39
+
40
+ Args:
41
+ team_level: If True, get team-level configuration.
42
+ group_id: Get configuration for a specific group.
43
+ user_email: Get configuration for a specific user.
44
+
45
+ Returns:
46
+ API response as a dictionary.
47
+
48
+ Raises:
49
+ WindsurfAPIError: If the API call fails or no target is specified.
50
+ """
51
+ service_key = get_service_key()
52
+ payload: dict[str, Any] = {"service_key": service_key}
53
+
54
+ if team_level:
55
+ payload["team_level"] = True
56
+ elif group_id:
57
+ payload["group_id"] = group_id
58
+ elif user_email:
59
+ payload["user_email"] = user_email
60
+ else:
61
+ raise WindsurfAPIError("Must specify one of: team_level, group_id, or user_email")
62
+
63
+ try:
64
+ with httpx.Client(timeout=30.0) as client:
65
+ response = client.post(
66
+ f"{API_BASE_URL}/api/v1/GetUsageConfig",
67
+ json=payload,
68
+ headers={"Content-Type": "application/json"},
69
+ )
70
+ response.raise_for_status()
71
+ return response.json()
72
+ except httpx.HTTPStatusError as e:
73
+ raise WindsurfAPIError(f"HTTP error: {e.response.status_code} - {e.response.text}")
74
+ except Exception as e:
75
+ raise WindsurfAPIError(f"Error getting usage config: {e}")
76
+
77
+
78
+ def get_team_users(
79
+ group_name: str | None = None,
80
+ start_timestamp: str | None = None,
81
+ end_timestamp: str | None = None,
82
+ ) -> list[dict[str, Any]]:
83
+ """Get list of team users via Windsurf API.
84
+
85
+ Args:
86
+ group_name: Filter results to users in a specific group (optional).
87
+ start_timestamp: Start time in RFC 3339 format (optional).
88
+ end_timestamp: End time in RFC 3339 format (optional).
89
+
90
+ Returns:
91
+ List of user statistics objects with name, email, etc.
92
+
93
+ Raises:
94
+ WindsurfAPIError: If the API call fails.
95
+ """
96
+ service_key = get_service_key()
97
+ payload: dict[str, Any] = {"service_key": service_key}
98
+
99
+ if group_name:
100
+ payload["group_name"] = group_name
101
+ if start_timestamp:
102
+ payload["start_timestamp"] = start_timestamp
103
+ if end_timestamp:
104
+ payload["end_timestamp"] = end_timestamp
105
+
106
+ try:
107
+ with httpx.Client(timeout=60.0) as client:
108
+ response = client.post(
109
+ f"{API_BASE_URL}/api/v1/UserPageAnalytics",
110
+ json=payload,
111
+ headers={"Content-Type": "application/json"},
112
+ )
113
+ response.raise_for_status()
114
+ data = response.json()
115
+ return data.get("userTableStats", [])
116
+ except httpx.HTTPStatusError as e:
117
+ raise WindsurfAPIError(f"HTTP error: {e.response.status_code} - {e.response.text}") from e
118
+ except Exception as e:
119
+ raise WindsurfAPIError(f"Error getting team users: {e}") from e
120
+
121
+
122
+ def set_usage_config(
123
+ set_add_on_credit_cap: int | None = None,
124
+ clear_add_on_credit_cap: bool = False,
125
+ team_level: bool = False,
126
+ group_id: str | None = None,
127
+ user_email: str | None = None,
128
+ ) -> dict[str, Any]:
129
+ """Set or clear add-on credit cap via Windsurf API.
130
+
131
+ Args:
132
+ set_add_on_credit_cap: The add-on credit cap to set.
133
+ clear_add_on_credit_cap: If True, clear the add-on credit cap.
134
+ team_level: If True, set team-level configuration.
135
+ group_id: Set configuration for a specific group.
136
+ user_email: Set configuration for a specific user.
137
+
138
+ Returns:
139
+ API response as a dictionary.
140
+
141
+ Raises:
142
+ WindsurfAPIError: If the API call fails or required args are missing.
143
+ """
144
+ service_key = get_service_key()
145
+ payload: dict[str, Any] = {"service_key": service_key}
146
+
147
+ if clear_add_on_credit_cap:
148
+ payload["clear_add_on_credit_cap"] = True
149
+ elif set_add_on_credit_cap is not None:
150
+ payload["set_add_on_credit_cap"] = set_add_on_credit_cap
151
+ else:
152
+ raise WindsurfAPIError("Must specify either set_add_on_credit_cap or clear_add_on_credit_cap")
153
+
154
+ if team_level:
155
+ payload["team_level"] = True
156
+ elif group_id:
157
+ payload["group_id"] = group_id
158
+ elif user_email:
159
+ payload["user_email"] = user_email
160
+ else:
161
+ raise WindsurfAPIError("Must specify one of: team_level, group_id, or user_email")
162
+
163
+ try:
164
+ with httpx.Client(timeout=30.0) as client:
165
+ response = client.post(
166
+ f"{API_BASE_URL}/api/v1/UsageConfig",
167
+ json=payload,
168
+ headers={"Content-Type": "application/json"},
169
+ )
170
+ response.raise_for_status()
171
+ return response.json()
172
+ except httpx.HTTPStatusError as e:
173
+ raise WindsurfAPIError(f"HTTP error: {e.response.status_code} - {e.response.text}")
174
+ except Exception as e:
175
+ raise WindsurfAPIError(f"Error setting usage config: {e}")