Skip to content

Commit f00f3cb

Browse files
committed
feat: initial commit
0 parents  commit f00f3cb

15 files changed

Lines changed: 1008 additions & 0 deletions

‎.dev.vars.example‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TURSO_URL=http://127.0.0.1:8080
2+
TURSO_AUTH_TOKEN=

‎.github/workflows/ci.yml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
- uses: oven-sh/setup-bun@v2
15+
- run: bun install --frozen-lockfile
16+
- run: bun run check
17+
- run: bunx tsc --noEmit

‎.github/workflows/deploy.yml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
deploy:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v6
12+
- uses: oven-sh/setup-bun@v2
13+
- run: bun install --frozen-lockfile
14+
- run: bun run check
15+
- run: bunx tsc --noEmit
16+
- uses: cloudflare/wrangler-action@v3
17+
with:
18+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
.wrangler
4+
.dev.vars

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Leonard
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.

‎README.md‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# cursortab-api
2+
3+
Community data collection API for training an improved completion gating model.
4+
Built with [Hono](https://hono.dev) on Cloudflare Workers with
5+
[Turso](https://turso.tech) (LibSQL) for storage.
6+
7+
## Local Development
8+
9+
```bash
10+
bun install
11+
cp .dev.vars.example .dev.vars # add your Turso credentials
12+
turso dev --port 8080 # local LibSQL server
13+
bun run dev # http://localhost:8787
14+
```
15+
16+
## Deploy
17+
18+
Deploys automatically on push to `main` via GitHub Actions.
19+
20+
### First-time setup
21+
22+
1. Set Cloudflare API token in GitHub repo secrets as `CLOUDFLARE_API_TOKEN`
23+
2. Set Turso secrets on Cloudflare:
24+
```bash
25+
wrangler secret put TURSO_URL
26+
wrangler secret put TURSO_AUTH_TOKEN
27+
```
28+
29+
Database migrations run automatically on first request.
30+
31+
## Endpoints
32+
33+
| Method | Path | Rate limited | Description |
34+
| ------ | --------- | ------------ | ------------------------------------------------------ |
35+
| `POST` | `/events` | No | Ingest a completion event |
36+
| `GET` | `/events` | 60/min/IP | Paginated read (`?limit=&offset=&device_id=&outcome=`) |
37+
| `GET` | `/stats` | 60/min/IP | Aggregate statistics |
38+
39+
See [docs/community-data-schema.md](docs/community-data-schema.md) for the full
40+
schema.

‎biome.json‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"formatter": {
9+
"indentStyle": "space",
10+
"indentWidth": 2
11+
},
12+
"linter": {
13+
"rules": {
14+
"recommended": true
15+
}
16+
}
17+
}

‎bun.lock‎

Lines changed: 294 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎docs/community-data-schema.md‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Community Data Collection Schema
2+
3+
Anonymous completion metrics collected with opt-in consent for training an
4+
improved completion gating model. **No code content, no file paths, nothing
5+
identifying.**
6+
7+
## Schema
8+
9+
```json
10+
{
11+
"device_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
12+
"outcome": "accepted",
13+
"file_ext": ".go",
14+
"language": "go",
15+
"prefix_length": 15,
16+
"trimmed_prefix_length": 13,
17+
"line_count": 210,
18+
"relative_position": 0.14,
19+
"after_cursor_ws": false,
20+
"last_char": ")",
21+
"last_nonws_char": ")",
22+
"prev_filter_shown": true,
23+
"completion_lines": 3,
24+
"completion_additions": 3,
25+
"completion_deletions": 0,
26+
"completion_source": "typing",
27+
"manually_triggered": false,
28+
"display_duration_ms": 1200,
29+
"time_since_last_decision_ms": 4500,
30+
"recent_actions": ["IC", "IC", "IC", "DC", "IC"],
31+
"has_diagnostics": false,
32+
"treesitter_scope": "function",
33+
"provider": "sweepapi",
34+
"edit_count": 18,
35+
"predicted_edit_ratio": 0.33,
36+
"time_since_last_edit_ms": 800,
37+
"typing_speed": 4.2,
38+
"completions_since_accept": 3,
39+
"indentation_level": 2,
40+
"stage_index": 0,
41+
"cursor_target_distance": 12,
42+
"is_prefetched": false,
43+
"filter_score": 0.72
44+
}
45+
```
46+
47+
## Database Schema
48+
49+
Server-side fields are added on ingestion and are not sent by the client.
50+
51+
```sql
52+
CREATE TABLE events (
53+
-- Server-side fields
54+
id INTEGER PRIMARY KEY AUTOINCREMENT,
55+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
56+
ip_hash TEXT NOT NULL,
57+
plugin_version TEXT NOT NULL,
58+
59+
-- Identity
60+
device_id TEXT NOT NULL,
61+
62+
-- Outcome
63+
outcome TEXT NOT NULL, -- 'accepted', 'rejected', 'ignored'
64+
65+
-- File context
66+
file_ext TEXT NOT NULL,
67+
language TEXT NOT NULL,
68+
69+
-- Cursor context
70+
prefix_length INTEGER NOT NULL,
71+
trimmed_prefix_length INTEGER NOT NULL,
72+
line_count INTEGER NOT NULL,
73+
relative_position REAL NOT NULL,
74+
after_cursor_ws INTEGER NOT NULL, -- 0/1
75+
last_char TEXT NOT NULL DEFAULT '',
76+
last_nonws_char TEXT NOT NULL DEFAULT '',
77+
indentation_level INTEGER NOT NULL,
78+
79+
-- Filter state
80+
prev_filter_shown INTEGER NOT NULL, -- 0/1
81+
filter_score REAL NOT NULL,
82+
83+
-- Completion properties
84+
completion_lines INTEGER NOT NULL,
85+
completion_additions INTEGER NOT NULL,
86+
completion_deletions INTEGER NOT NULL,
87+
completion_source TEXT NOT NULL, -- 'typing', 'idle'
88+
manually_triggered INTEGER NOT NULL, -- 0/1
89+
provider TEXT NOT NULL,
90+
91+
-- Staging & cursor prediction
92+
stage_index INTEGER NOT NULL DEFAULT 0,
93+
cursor_target_distance INTEGER NOT NULL DEFAULT 0,
94+
is_prefetched INTEGER NOT NULL DEFAULT 0, -- 0/1
95+
96+
-- Timing
97+
display_duration_ms INTEGER NOT NULL,
98+
time_since_last_decision_ms INTEGER NOT NULL,
99+
time_since_last_edit_ms INTEGER NOT NULL,
100+
typing_speed REAL NOT NULL,
101+
102+
-- Session context
103+
recent_actions TEXT NOT NULL, -- JSON array, e.g. '["IC","IC","DC"]'
104+
has_diagnostics INTEGER NOT NULL, -- 0/1
105+
treesitter_scope TEXT NOT NULL,
106+
edit_count INTEGER NOT NULL,
107+
predicted_edit_ratio REAL NOT NULL,
108+
completions_since_accept INTEGER NOT NULL
109+
);
110+
111+
-- Indexes for queries and anomaly detection
112+
CREATE INDEX idx_events_device_id ON events(device_id);
113+
CREATE INDEX idx_events_created_at ON events(created_at);
114+
CREATE INDEX idx_events_outcome ON events(outcome);
115+
```
116+
117+
## API Payload
118+
119+
The client sends the following JSON body to `POST /events`. Server-side fields
120+
(`id`, `created_at`, `ip_hash`, `plugin_version`) are derived on ingestion —
121+
`plugin_version` is sent as a header.
122+
123+
## Field Reference
124+
125+
| Field | Type | Source | Description |
126+
| ----------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127+
| `id` | `int` | server | Auto-increment primary key |
128+
| `created_at` | `string` | server | ISO 8601 UTC timestamp when the event was received |
129+
| `ip_hash` | `string` | server | SHA-256 hash of the client IP (for rate limiting and anomaly detection, not identifying) |
130+
| `plugin_version` | `string` | header | Plugin version that sent the event (e.g. `"0.5.0"`) |
131+
| `device_id` | `string` | client | Persistent UUID4 generated once per install |
132+
| `outcome` | `string` | client | Completion result: `"accepted"`, `"rejected"`, or `"ignored"` |
133+
| `file_ext` | `string` | client | Lowercase file extension including dot (e.g. `".go"`, `".ts"`) |
134+
| `language` | `string` | client | Resolved language identifier (e.g. `"go"`, `"typescript"`, `"unknown"`) |
135+
| `prefix_length` | `int` | client | Length of the text on the current line before the cursor (byte offset within line) |
136+
| `trimmed_prefix_length` | `int` | client | Length of the prefix with trailing whitespace removed |
137+
| `line_count` | `int` | client | Total number of lines in the document |
138+
| `relative_position` | `float` | client | Normalized cursor position in the document (`0.0` = top, `1.0` = bottom) |
139+
| `after_cursor_ws` | `bool` | client | Whether all text after the cursor on the current line is whitespace (or cursor is at EOL) |
140+
| `last_char` | `string` | client | Last character of the prefix, or `""` if the prefix is empty |
141+
| `last_nonws_char` | `string` | client | Last non-whitespace character of the prefix, or `""` if none |
142+
| `indentation_level` | `int` | client | Number of leading indent units (tab stops) on the current line |
143+
| `prev_filter_shown` | `bool` | client | Whether the previous contextual filter invocation resulted in showing a completion |
144+
| `filter_score` | `float` | client | Score from the current contextual filter model (`0.0`-`1.0`) |
145+
| `completion_lines` | `int` | client | Number of lines in the completion suggestion |
146+
| `completion_additions` | `int` | client | Number of lines added by the completion |
147+
| `completion_deletions` | `int` | client | Number of lines deleted/replaced by the completion |
148+
| `completion_source` | `string` | client | What triggered the completion: `"typing"` or `"idle"` |
149+
| `manually_triggered` | `bool` | client | Whether the user explicitly triggered the completion (keybind) vs. automatic |
150+
| `provider` | `string` | client | Which provider generated the completion (e.g. `"sweepapi"`, `"copilot"`, `"mercuryapi"`) |
151+
| `stage_index` | `int` | client | Which stage of a multi-stage completion (`0` for single-stage or the first stage) |
152+
| `cursor_target_distance` | `int` | client | Lines between the cursor and the completion target (`0` when no navigation needed) |
153+
| `is_prefetched` | `bool` | client | Whether the completion was speculatively prefetched ahead of the user arriving at the target |
154+
| `display_duration_ms` | `int` | client | Milliseconds the completion was visible before the user acted (analytics only — not a gating model input) |
155+
| `time_since_last_decision_ms` | `int` | client | Milliseconds since the last accept/reject/suppress decision |
156+
| `time_since_last_edit_ms` | `int` | client | Milliseconds since the most recent edit in the current file |
157+
| `typing_speed` | `float` | client | Average typing speed (chars/sec) over the ~3 seconds preceding the completion trigger |
158+
| `recent_actions` | `string[]` | client | Last 5 user actions before the completion trigger. Values: `"IC"` (insert char), `"IS"` (insert selection), `"DC"` (delete char), `"DS"` (delete selection), `"CM"` (cursor movement) |
159+
| `has_diagnostics` | `bool` | client | Whether the file has active LSP diagnostics (errors or warnings) |
160+
| `treesitter_scope` | `string` | client | Enclosing AST scope: `"function"`, `"class"`, `"top_level"`, `"string"`, `"comment"`, or `"other"` |
161+
| `edit_count` | `int` | client | Total number of diff entries in the current file's session history |
162+
| `predicted_edit_ratio` | `float` | client | Ratio of AI-accepted edits (`predicted`) to total edits in the current file (`0.0`-`1.0`) |
163+
| `completions_since_accept` | `int` | client | Number of completions shown (and not accepted) since the last accept |

‎package.json‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "cursortab-api",
3+
"scripts": {
4+
"dev": "wrangler dev",
5+
"deploy": "wrangler deploy",
6+
"check": "biome check",
7+
"format": "biome check --write"
8+
},
9+
"dependencies": {
10+
"@hono/zod-validator": "^0.7.6",
11+
"@libsql/client": "^0.17.2",
12+
"hono": "^4.12.8",
13+
"zod": "^4.3.6"
14+
},
15+
"devDependencies": {
16+
"@biomejs/biome": "^2.4.8",
17+
"@cloudflare/workers-types": "^4.20260317.1",
18+
"typescript": "^5.9.3",
19+
"wrangler": "^4.76.0"
20+
}
21+
}

0 commit comments

Comments
 (0)