pi-clinepass 0.1.1 → 0.1.3

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 0.1.3 - 2026-08-31
6
+
7
+ - Corrected model input modalities: `glm-5.3` and `deepseek-v4-flash` are text-only, `mimo-v2.5` and `qwen3.8-max` accept images
8
+
9
+ ## 0.1.2 - 2026-08-30
10
+
11
+ - New paid model: `cline-pass/glm-5.3-flash` ($0.15/$0.50/$0.03)
12
+ - New free model: `cline-free/longcat-2.0`
13
+ - Model picker shows prices
14
+ - Price calibration via `/clinepass` measures real billing and updates the whole panel; fatal errors abort the run
15
+ - `/clinepass` runs in a centered modal: dashboard (prices, plan sidebar) and calibration (live per-model progress, esc-esc cancel)
16
+ - Prices stored in `clinepass-prices.json`, seeded on install, synced per release
17
+ - Gateway thinking-stream corruption repaired
18
+ - pi's built-in cost estimate off for ClinePass (the footer meter is the single cost display, turn cost includes every tool-calling round)
19
+ - Free-route headers applied to every free model
20
+ - 403 on free routes classified as route gate, not subscription
21
+
5
22
  ## 0.1.1 - 2026-08-27
6
23
 
7
24
  - Catalog: `stealth/ox-alpha` is now `z-ai/glm-5.3-flash` (renamed)
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # pi-clinepass
4
4
 
5
- **ClinePass provider for [pi](https://pi.dev) coding agent**
5
+ **ClinePass for [pi](https://pi.dev) coding agent**
6
6
 
7
7
  Dollar-based limits, live cost tracking, and plan cap reporting.
8
8
 
@@ -25,9 +25,10 @@ Dollar-based limits, live cost tracking, and plan cap reporting.
25
25
 
26
26
  ## Highlights
27
27
 
28
- - **16 Models**: 13 ClinePass models on a dollar-based limit system plus 3 free tier models
28
+ - **18 Models**: 14 ClinePass models on a dollar-based limit system plus 4 free tier models
29
29
  - **Live Status Meter**: Per-turn and session cost directly in the pi footer
30
30
  - **Plan Utilization**: 5-hour, weekly, and monthly caps tracked via `/clinepass`
31
+ - **Price Calibration**: measure real gateway billing from `/clinepass` to keep displayed prices honest
31
32
  - **Simple Auth**: One-time login via Cline CLI, browser, or API key
32
33
 
33
34
  ---
@@ -68,7 +69,7 @@ Select **ClinePass** to choose your sign-in method:
68
69
 
69
70
  ### 2. Select a Model
70
71
 
71
- Pick any model using the interactive selector:
72
+ Pick any model using the interactive selector — prices are shown right in the picker:
72
73
 
73
74
  ```
74
75
  /model
@@ -84,6 +85,10 @@ View the live rate sheet and plan limit utilization anytime:
84
85
  /clinepass
85
86
  ```
86
87
 
88
+ The command opens a menu: the dashboard shows model rates and plan utilization
89
+ in a centered modal, or run **price calibration** to measure real gateway
90
+ billing and update the whole panel (uses ~5-10% of the 5-hour quota).
91
+
87
92
  <div align="center">
88
93
  <img src="https://raw.githubusercontent.com/fifidayone/pi-clinepass/main/assets/report.png" alt="ClinePass report with pricing and plan limits" width="620">
89
94
  </div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-clinepass",
3
- "version": "0.1.1",
4
- "description": "ClinePass provider for pi — static measured-price catalog, server-truth billing meter, free models",
3
+ "version": "0.1.3",
4
+ "description": "ClinePass for pi — static measured-price catalog, server-truth billing meter, free models",
5
5
  "keywords": [
6
6
  "ai",
7
7
  "cline",
@@ -37,7 +37,8 @@
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-ai": "*",
40
- "@earendil-works/pi-coding-agent": "*"
40
+ "@earendil-works/pi-coding-agent": "*",
41
+ "@earendil-works/pi-tui": "*"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/node": "^26.3.0",
package/src/auth.ts CHANGED
@@ -178,7 +178,10 @@ export function resolvePiStoredCredential(options: AuthOptions = {}): ClineAuthC
178
178
  * produced rotated tokens — if the server rotates single-use refresh
179
179
  * tokens, discarding the new one would leave pi's stored credential dead
180
180
  * and force a re-login. Waits for pi's lock file (`<auth.json>.lock`, held
181
- * by proper-lockfile while pi writes) to clear before merging.
181
+ * by proper-lockfile while pi writes) to clear before merging, then verifies
182
+ * after writing: the meter refresh and pi's own chat-path refresh can fire at
183
+ * the same near-expiry moment, so if pi rewrote the file in between, the
184
+ * merge is redone once on top of the newer content instead of dropping it.
182
185
  */
183
186
  export async function persistOAuthCredential(
184
187
  credential: OAuthCredentials,
@@ -187,26 +190,35 @@ export async function persistOAuthCredential(
187
190
  const { homeDir, readFile, fileExists, writeFile } = resolveOptions(options);
188
191
  const authPath = join(homeDir(), ".pi", "agent", "auth.json");
189
192
  const lockPath = `${authPath}.lock`;
193
+ const entry = {
194
+ type: "oauth",
195
+ access: credential.access,
196
+ refresh: credential.refresh,
197
+ expires: credential.expires,
198
+ };
190
199
 
191
- for (let attempt = 0; attempt < 20 && fileExists(lockPath); attempt++) {
192
- await new Promise((resolve) => setTimeout(resolve, 100));
193
- }
200
+ for (let pass = 0; pass < 2; pass++) {
201
+ for (let attempt = 0; attempt < 20 && fileExists(lockPath); attempt++) {
202
+ await new Promise((resolve) => setTimeout(resolve, 100));
203
+ }
204
+
205
+ const current = fileExists(authPath) ? readFile(authPath) : "{}";
206
+ const parsed: unknown = current.trim() ? JSON.parse(current) : {};
207
+ if (!isRecord(parsed)) {
208
+ throw new Error("auth.json is not a JSON object");
209
+ }
210
+ const serialized = JSON.stringify({ ...parsed, clinepass: entry }, null, 2);
211
+ writeFile(authPath, serialized);
194
212
 
195
- const current = fileExists(authPath) ? readFile(authPath) : "{}";
196
- const parsed: unknown = current.trim() ? JSON.parse(current) : {};
197
- if (!isRecord(parsed)) {
198
- throw new Error("auth.json is not a JSON object");
213
+ let contended = false;
214
+ for (let attempt = 0; attempt < 20 && fileExists(lockPath); attempt++) {
215
+ contended = true;
216
+ await new Promise((resolve) => setTimeout(resolve, 100));
217
+ }
218
+ // Done only when pi never contended and the file still holds exactly what
219
+ // we wrote; otherwise loop once more and merge over the newer content.
220
+ if (!contended && (!fileExists(authPath) || readFile(authPath) === serialized)) return;
199
221
  }
200
- const next = {
201
- ...parsed,
202
- clinepass: {
203
- type: "oauth",
204
- access: credential.access,
205
- refresh: credential.refresh,
206
- expires: credential.expires,
207
- },
208
- };
209
- writeFile(authPath, JSON.stringify(next, null, 2));
210
222
  }
211
223
 
212
224
  // ─── Login chain ──────────────────────────────────────────────────────────