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.
- windsurf_throttle-1.0.1/.env.example +8 -0
- windsurf_throttle-1.0.1/.github/workflows/publish.yml +50 -0
- windsurf_throttle-1.0.1/.gitignore +50 -0
- windsurf_throttle-1.0.1/.pre-commit-config.yaml +16 -0
- windsurf_throttle-1.0.1/LICENSE +21 -0
- windsurf_throttle-1.0.1/PKG-INFO +143 -0
- windsurf_throttle-1.0.1/README.md +113 -0
- windsurf_throttle-1.0.1/pyproject.toml +68 -0
- windsurf_throttle-1.0.1/src/windsurf_throttle/__init__.py +3 -0
- windsurf_throttle-1.0.1/src/windsurf_throttle/api.py +175 -0
- windsurf_throttle-1.0.1/src/windsurf_throttle/app.py +442 -0
- windsurf_throttle-1.0.1/src/windsurf_throttle/cli.py +67 -0
- windsurf_throttle-1.0.1/tests/__init__.py +1 -0
- windsurf_throttle-1.0.1/tests/test_api.py +27 -0
- windsurf_throttle-1.0.1/uv.lock +1586 -0
|
@@ -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,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}")
|