@bacnh85/pi-sub 0.1.35 → 0.1.37
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 +53 -0
- package/extensions/index.ts +85 -31
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.37 (2026-09-07)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Legible provider API errors**: structured API failures (e.g. Z.ai's
|
|
8
|
+
`500 Internal service error` returned by `api.z.ai/api/monitor/usage/quota/limit`
|
|
9
|
+
during its 2026-09-07 outage) no longer masquerade as a generic
|
|
10
|
+
"usage unavailable" — the footer shows the server's own message
|
|
11
|
+
(`Sub Z.ai (Anthropic) API error: Internal service error`), with
|
|
12
|
+
credential-shaped material (`sk-…`, `Bearer …`, JWTs) scrubbed and length
|
|
13
|
+
capped; auth-looking messages stay redacted.
|
|
14
|
+
- **Crash (pi exits) on session replacement (`/new`, fork, switch, `/reload`)**:
|
|
15
|
+
a usage-refresh debounce timer armed by a late `after_provider_response` event
|
|
16
|
+
— delivered after `session_shutdown` had already cleared the previous timers —
|
|
17
|
+
captured the old extension ctx; ~2s later the timer fired, touched the now
|
|
18
|
+
stale `ctx.ui`, and the uncaught error killed pi. Deferred helpers
|
|
19
|
+
(`renderSubscriptionLine`, `refreshUsage`, `startTimer`, `scheduleRefresh`,
|
|
20
|
+
`updateActiveAdapter`) no longer take a captured ctx: they resolve `state.ctx`
|
|
21
|
+
at execution time. `state.ctx` is installed only by `session_start` (fires
|
|
22
|
+
before a session's other events, always fresh) and cleared by
|
|
23
|
+
`session_shutdown`, so mid-session handlers can never reinstall a stale ctx
|
|
24
|
+
and timers firing in the teardown window safely no-op. `session_shutdown`
|
|
25
|
+
now no-ops entirely unless it belongs to the installed session
|
|
26
|
+
(`state.ctx === ctx`), so a late old-session shutdown delivered after the
|
|
27
|
+
next `session_start` can neither touch an invalidated ctx nor stop the live
|
|
28
|
+
session's refresh timer / drop its in-flight fetch. Regression tests in
|
|
29
|
+
`extensions/test/stale-ctx-regression.test.ts`.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Tests are now gated**: `npm test` runs both test files via
|
|
34
|
+
`node --import tsx --test` (new `tsx` devDependency), and CI runs
|
|
35
|
+
`npm ci && npm test` for pi-sub instead of only `npm pack --dry-run` — the
|
|
36
|
+
stale-ctx regression tests can no longer be bypassed by a refactor.
|
|
37
|
+
|
|
38
|
+
## 0.1.36 (2026-09-07)
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **Removed the incorrect dead `tok-per-sec` module** (added in 0.1.35, never
|
|
43
|
+
wired in): its `withThinking` formula summed `reasoning` on top of `output`,
|
|
44
|
+
but Pi's `usage.output` already includes reasoning tokens (`reasoning` is a
|
|
45
|
+
subset), which would have inflated thinking-mode tok/s up to ~2×. The live
|
|
46
|
+
footer/footer tok/s math (`output / elapsed`) was and remains correct in both
|
|
47
|
+
thinking and normal mode.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **`/sub` thinking/answer split** — when the model reasoned, the details line
|
|
52
|
+
now reads e.g. `Last response: 46 tok/s (36 think + 10 answer)` instead of a
|
|
53
|
+
bare total, using the correct subset math (`answer = output − reasoning`).
|
|
54
|
+
Footer keeps the single total number.
|
|
55
|
+
|
|
3
56
|
## 0.1.35 (2026-09-05)
|
|
4
57
|
|
|
5
58
|
### Added
|
package/extensions/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ const MESSAGE_TYPE = "pi-sub-status";
|
|
|
31
31
|
const USAGE_ENDPOINT = "https://chatgpt.com/backend-api/wham/usage";
|
|
32
32
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
33
33
|
const REFRESH_TTL_MS = 30_000;
|
|
34
|
-
const REFRESH_DEBOUNCE_MS = 2_000;
|
|
34
|
+
export const REFRESH_DEBOUNCE_MS = 2_000;
|
|
35
35
|
const CODEX_PROVIDER = "openai-codex";
|
|
36
36
|
const OPC_PROVIDER = "opencode-go";
|
|
37
37
|
const ZAI_PROVIDER = "zai";
|
|
@@ -115,10 +115,11 @@ type SubscriptionProviderAdapter = {
|
|
|
115
115
|
fetchUsage(signal?: AbortSignal): Promise<SubscriptionUsageSnapshot>;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
-
interface State {
|
|
118
|
+
export interface State {
|
|
119
119
|
model?: ModelLike;
|
|
120
120
|
adapter?: SubscriptionProviderAdapter;
|
|
121
121
|
adapterId?: string;
|
|
122
|
+
ctx?: ExtensionContext;
|
|
122
123
|
snapshot?: SubscriptionUsageSnapshot;
|
|
123
124
|
lastRefreshAt: number;
|
|
124
125
|
refreshGeneration: number;
|
|
@@ -127,6 +128,7 @@ interface State {
|
|
|
127
128
|
debounceTimer?: NodeJS.Timeout;
|
|
128
129
|
responseStartTime?: number;
|
|
129
130
|
lastTokPerSec?: number;
|
|
131
|
+
lastTokPerSecLabel?: string;
|
|
130
132
|
cumulativeOutput: number;
|
|
131
133
|
cumulativeDurationMs: number;
|
|
132
134
|
cumulativeCost: number;
|
|
@@ -462,6 +464,9 @@ if (process.env.PI_SUB_SELF_CHECK === "1") {
|
|
|
462
464
|
assert(p.personalWeekly?.remaining === 90, "personal weekly 90");
|
|
463
465
|
assert(p.session?.remaining === 47, "session 47");
|
|
464
466
|
assert(p.providerWeekly?.remaining === 28, "provider weekly 28");
|
|
467
|
+
// tok/s split label: usage.reasoning ⊂ usage.output, never summed.
|
|
468
|
+
assert(tokPerSecLabel(3200, 2500, 70_000) === "46 tok/s (36 think + 10 answer)", "tok/s split label");
|
|
469
|
+
assert(tokPerSecLabel(200, 0, 10_000) === "20 tok/s", "tok/s plain label");
|
|
465
470
|
assert(p.personalDaily?.resetLabel?.includes("15h") === true, "daily reset label");
|
|
466
471
|
const disabled = parseOmniUsageText("Usage command is disabled for this API key.");
|
|
467
472
|
assert(Object.keys(disabled).length === 0, "disabled text parses empty");
|
|
@@ -503,7 +508,11 @@ async function fetchUsageFromPiAuth(entry: PiAuthEntry, signal?: AbortSignal): P
|
|
|
503
508
|
return parseUsageResponse(await response.json());
|
|
504
509
|
}
|
|
505
510
|
|
|
506
|
-
|
|
511
|
+
// ponytail: shared redaction — show provider + failure class, never leak keys.
|
|
512
|
+
// Structured API errors carry the server's own message (e.g. Z.ai's
|
|
513
|
+
// "Internal service error" outage); auth-looking messages were already
|
|
514
|
+
// redacted above, so surface the rest verbatim for diagnosability.
|
|
515
|
+
export function redactedError(error: unknown, provider = "Codex"): string {
|
|
507
516
|
const message = error instanceof Error ? error.message : String(error || "Unknown error");
|
|
508
517
|
if (/ENOENT|no such file/i.test(message)) return "Pi auth not found";
|
|
509
518
|
if (/missing openai-codex/i.test(message)) return "openai-codex auth not found";
|
|
@@ -512,7 +521,14 @@ function redactedError(error: unknown, provider = "Codex"): string {
|
|
|
512
521
|
if (/missing commandcode/i.test(message)) return "commandcode auth not found";
|
|
513
522
|
if (/timed out|timeout|aborted/i.test(message)) return `${provider} usage refresh timed out`;
|
|
514
523
|
if (/401|403|auth|token|unauthorized|forbidden/i.test(message)) return `${provider} auth unavailable`;
|
|
515
|
-
|
|
524
|
+
const apiMatch = / API error: (.+)$/.exec(message);
|
|
525
|
+
if (!apiMatch) return `${provider} usage unavailable`;
|
|
526
|
+
// Trust boundary: the msg is remote-controlled — scrub credential-shaped
|
|
527
|
+
// material and cap length before it reaches the status bar.
|
|
528
|
+
const scrubbed = apiMatch[1]
|
|
529
|
+
.replace(/sk-[A-Za-z0-9_-]+|Bearer\s+\S+|eyJ[A-Za-z0-9._-]+/g, "[REDACTED]")
|
|
530
|
+
.slice(0, 120);
|
|
531
|
+
return scrubbed ? `${provider} API error: ${scrubbed}` : `${provider} usage unavailable`;
|
|
516
532
|
}
|
|
517
533
|
|
|
518
534
|
async function fetchCodexUsage(signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
|
|
@@ -1081,7 +1097,11 @@ function windowSegments(account: SubscriptionAccountSnapshot | undefined): strin
|
|
|
1081
1097
|
return segments;
|
|
1082
1098
|
}
|
|
1083
1099
|
|
|
1084
|
-
function renderSubscriptionLine(
|
|
1100
|
+
export function renderSubscriptionLine(state: State): void {
|
|
1101
|
+
// ponytail: resolve ctx at render time — any captured ctx goes stale on
|
|
1102
|
+
// session replacement (new/fork/switch/reload) and ctx.ui then throws.
|
|
1103
|
+
const ctx = state.ctx;
|
|
1104
|
+
if (!ctx) return;
|
|
1085
1105
|
const theme = ctx.ui.theme;
|
|
1086
1106
|
if (!state.adapter) {
|
|
1087
1107
|
// Unsupported provider (e.g. Ollama): still show the last response speed.
|
|
@@ -1120,10 +1140,10 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
|
|
|
1120
1140
|
ctx.ui.setStatus(STATUS_KEY, theme.fg(color, line));
|
|
1121
1141
|
}
|
|
1122
1142
|
|
|
1123
|
-
function startTimer(
|
|
1143
|
+
function startTimer(state: State): void {
|
|
1124
1144
|
if (state.refreshTimer || !state.adapter) return;
|
|
1125
1145
|
state.refreshTimer = setInterval(() => {
|
|
1126
|
-
void refreshUsage(
|
|
1146
|
+
void refreshUsage(state, false);
|
|
1127
1147
|
}, REFRESH_INTERVAL_MS);
|
|
1128
1148
|
}
|
|
1129
1149
|
|
|
@@ -1134,7 +1154,7 @@ function stopTimer(state: State): void {
|
|
|
1134
1154
|
state.debounceTimer = undefined;
|
|
1135
1155
|
}
|
|
1136
1156
|
|
|
1137
|
-
function updateActiveAdapter(
|
|
1157
|
+
function updateActiveAdapter(state: State, model: ModelLike): void {
|
|
1138
1158
|
const nextAdapter = supportedAdapter(model);
|
|
1139
1159
|
const adapterChanged = state.adapterId !== nextAdapter?.id;
|
|
1140
1160
|
|
|
@@ -1152,25 +1172,28 @@ function updateActiveAdapter(ctx: ExtensionContext, state: State, model: ModelLi
|
|
|
1152
1172
|
if (!state.adapter) {
|
|
1153
1173
|
stopTimer(state);
|
|
1154
1174
|
}
|
|
1155
|
-
renderSubscriptionLine(
|
|
1156
|
-
if (state.adapter) startTimer(
|
|
1175
|
+
renderSubscriptionLine(state);
|
|
1176
|
+
if (state.adapter) startTimer(state);
|
|
1157
1177
|
}
|
|
1158
1178
|
|
|
1159
|
-
async function refreshUsage(
|
|
1179
|
+
async function refreshUsage(state: State, force: boolean): Promise<SubscriptionUsageSnapshot | undefined> {
|
|
1160
1180
|
const adapter = state.adapter;
|
|
1161
|
-
|
|
1162
|
-
|
|
1181
|
+
// ponytail: resolve ctx at call time, never capture it across the fetch —
|
|
1182
|
+
// the session can be replaced while the promise is in flight.
|
|
1183
|
+
const ctx = state.ctx;
|
|
1184
|
+
if (!adapter || !ctx) {
|
|
1185
|
+
renderSubscriptionLine(state);
|
|
1163
1186
|
return undefined;
|
|
1164
1187
|
}
|
|
1165
1188
|
if (!force && state.snapshot && Date.now() - state.lastRefreshAt < REFRESH_TTL_MS) return state.snapshot;
|
|
1166
1189
|
if (state.inFlight) return state.inFlight;
|
|
1167
1190
|
const generation = state.refreshGeneration;
|
|
1168
|
-
renderSubscriptionLine(
|
|
1191
|
+
renderSubscriptionLine(state);
|
|
1169
1192
|
state.inFlight = adapter.fetchUsage(ctx.signal).then((snapshot) => {
|
|
1170
1193
|
if (state.refreshGeneration !== generation) return snapshot;
|
|
1171
1194
|
state.snapshot = snapshot;
|
|
1172
1195
|
state.lastRefreshAt = Date.now();
|
|
1173
|
-
renderSubscriptionLine(
|
|
1196
|
+
renderSubscriptionLine(state);
|
|
1174
1197
|
return snapshot;
|
|
1175
1198
|
}).finally(() => {
|
|
1176
1199
|
if (state.refreshGeneration === generation) {
|
|
@@ -1180,12 +1203,12 @@ async function refreshUsage(ctx: ExtensionContext, state: State, force: boolean)
|
|
|
1180
1203
|
return state.inFlight;
|
|
1181
1204
|
}
|
|
1182
1205
|
|
|
1183
|
-
function scheduleRefresh(
|
|
1206
|
+
export function scheduleRefresh(state: State): void {
|
|
1184
1207
|
if (!state.adapter) return;
|
|
1185
1208
|
if (state.debounceTimer) clearTimeout(state.debounceTimer);
|
|
1186
1209
|
state.debounceTimer = setTimeout(() => {
|
|
1187
1210
|
state.debounceTimer = undefined;
|
|
1188
|
-
void refreshUsage(
|
|
1211
|
+
void refreshUsage(state, true);
|
|
1189
1212
|
}, REFRESH_DEBOUNCE_MS);
|
|
1190
1213
|
}
|
|
1191
1214
|
|
|
@@ -1193,11 +1216,21 @@ function pad(value: string, width: number): string {
|
|
|
1193
1216
|
return value.length >= width ? value : value + " ".repeat(width - value.length);
|
|
1194
1217
|
}
|
|
1195
1218
|
|
|
1219
|
+
/** "46 tok/s (36 think + 10 answer)" — split shown only when the model
|
|
1220
|
+
* reasoned. usage.reasoning is a subset of usage.output (Pi SDK contract),
|
|
1221
|
+
* so answer speed = (output − reasoning)/s, never output + reasoning. */
|
|
1222
|
+
function tokPerSecLabel(output: number, thinking: number, elapsedMs: number): string {
|
|
1223
|
+
const total = Math.round(output / (elapsedMs / 1000));
|
|
1224
|
+
if (thinking <= 0) return `${total} tok/s`;
|
|
1225
|
+
const secs = elapsedMs / 1000;
|
|
1226
|
+
return `${total} tok/s (${Math.round(thinking / secs)} think + ${Math.round((output - thinking) / secs)} answer)`;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1196
1229
|
function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: State): string {
|
|
1197
1230
|
if (!state.adapter) {
|
|
1198
1231
|
const header = `Provider: ${state.model?.provider ?? "unknown"}${state.model?.id ? ` · Model: ${state.model.id}` : ""}`;
|
|
1199
1232
|
if (state.lastTokPerSec === undefined) return `${header}\nSubscription tracking inactive for this provider.`;
|
|
1200
|
-
const tokPerSecLine = `Last response: ${state.
|
|
1233
|
+
const tokPerSecLine = `Last response: ${state.lastTokPerSecLabel}` +
|
|
1201
1234
|
(state.cumulativeDurationMs > 0
|
|
1202
1235
|
? ` · Session avg: ${Math.round(state.cumulativeOutput / (state.cumulativeDurationMs / 1000))} tok/s`
|
|
1203
1236
|
: "");
|
|
@@ -1240,7 +1273,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
|
|
|
1240
1273
|
|
|
1241
1274
|
const costLine = state.cumulativeCost > 0 ? `\nSession cost: $${state.cumulativeCost.toFixed(2)}` : "";
|
|
1242
1275
|
const tokPerSecLine = state.lastTokPerSec !== undefined
|
|
1243
|
-
? `\nLast response: ${state.
|
|
1276
|
+
? `\nLast response: ${state.lastTokPerSecLabel}` +
|
|
1244
1277
|
(state.cumulativeDurationMs > 0
|
|
1245
1278
|
? ` · Session avg: ${Math.round(state.cumulativeOutput / (state.cumulativeDurationMs / 1000))} tok/s`
|
|
1246
1279
|
: "")
|
|
@@ -1260,44 +1293,58 @@ export default function (pi: ExtensionAPI) {
|
|
|
1260
1293
|
const state: State = { lastRefreshAt: 0, refreshGeneration: 0, cumulativeOutput: 0, cumulativeDurationMs: 0, cumulativeCost: 0 };
|
|
1261
1294
|
|
|
1262
1295
|
pi.on("session_start", async (_event, ctx) => {
|
|
1263
|
-
|
|
1264
|
-
|
|
1296
|
+
// Only session_start installs state.ctx: it fires (startup/new/fork/switch/
|
|
1297
|
+
// reload) before the session's other events, so mid-session handlers never
|
|
1298
|
+
// need to — and a late old-session event must not reinstall a stale ctx.
|
|
1299
|
+
state.ctx = ctx;
|
|
1300
|
+
updateActiveAdapter(state, ctx.model);
|
|
1301
|
+
if (state.adapter) void refreshUsage(state, true);
|
|
1265
1302
|
});
|
|
1266
1303
|
|
|
1267
|
-
pi.on("model_select", async (event,
|
|
1268
|
-
updateActiveAdapter(
|
|
1269
|
-
if (state.adapter) void refreshUsage(
|
|
1304
|
+
pi.on("model_select", async (event, _ctx) => {
|
|
1305
|
+
updateActiveAdapter(state, event.model);
|
|
1306
|
+
if (state.adapter) void refreshUsage(state, true);
|
|
1270
1307
|
});
|
|
1271
1308
|
|
|
1272
1309
|
pi.on("before_provider_request", async (_event, _ctx) => {
|
|
1273
1310
|
state.responseStartTime = Date.now();
|
|
1274
1311
|
});
|
|
1275
1312
|
|
|
1276
|
-
pi.on("message_end", async (event,
|
|
1313
|
+
pi.on("message_end", async (event, _ctx) => {
|
|
1277
1314
|
if (event.message.role === "assistant") {
|
|
1278
1315
|
state.cumulativeCost += (event.message.usage as any)?.cost?.total ?? 0;
|
|
1279
1316
|
if (state.responseStartTime) {
|
|
1317
|
+
// usage.output already includes reasoning tokens (Pi SDK contract) —
|
|
1318
|
+
// this is total tok/s in both thinking and normal mode.
|
|
1280
1319
|
const output = (event.message.usage as any)?.output ?? 0;
|
|
1320
|
+
const reasoning = (event.message.usage as any)?.reasoning ?? 0;
|
|
1281
1321
|
const elapsed = Date.now() - state.responseStartTime;
|
|
1282
1322
|
state.responseStartTime = undefined;
|
|
1283
1323
|
if (elapsed > 0 && output > 0) {
|
|
1284
1324
|
state.lastTokPerSec = Math.round(output / (elapsed / 1000));
|
|
1325
|
+
state.lastTokPerSecLabel = tokPerSecLabel(output, reasoning, elapsed);
|
|
1285
1326
|
state.cumulativeOutput += output;
|
|
1286
1327
|
state.cumulativeDurationMs += elapsed;
|
|
1287
1328
|
}
|
|
1288
1329
|
}
|
|
1289
|
-
renderSubscriptionLine(
|
|
1330
|
+
renderSubscriptionLine(state);
|
|
1290
1331
|
}
|
|
1291
1332
|
});
|
|
1292
1333
|
|
|
1293
|
-
pi.on("after_provider_response", async (event,
|
|
1334
|
+
pi.on("after_provider_response", async (event, _ctx) => {
|
|
1294
1335
|
if (event.status >= 400) {
|
|
1295
1336
|
state.responseStartTime = undefined;
|
|
1296
1337
|
}
|
|
1297
|
-
if (state.adapter) scheduleRefresh(
|
|
1338
|
+
if (state.adapter) scheduleRefresh(state);
|
|
1298
1339
|
});
|
|
1299
1340
|
|
|
1300
1341
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1342
|
+
// Only tear down if this shutdown belongs to the installed session: a late
|
|
1343
|
+
// old-session shutdown (delivered after the next session_start) must not
|
|
1344
|
+
// stop the live refresh timer, drop the live in-flight fetch, or touch a
|
|
1345
|
+
// ctx that may already be invalidated. Normal flow: session_start installed
|
|
1346
|
+
// this ctx, so the identity always matches for the session being torn down.
|
|
1347
|
+
if (state.ctx !== ctx) return;
|
|
1301
1348
|
stopTimer(state);
|
|
1302
1349
|
// ponytail: session is being torn down (new/fork/switch/reload). Pi invalidates
|
|
1303
1350
|
// this ctx next; no-op any in-flight fetch .then that captured it, and drop the
|
|
@@ -1305,6 +1352,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1305
1352
|
state.inFlight = undefined;
|
|
1306
1353
|
state.refreshGeneration++;
|
|
1307
1354
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
1355
|
+
state.ctx = undefined;
|
|
1308
1356
|
});
|
|
1309
1357
|
|
|
1310
1358
|
pi.registerCommand("sub", {
|
|
@@ -1316,13 +1364,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
1316
1364
|
return items.length > 0 ? items : null;
|
|
1317
1365
|
},
|
|
1318
1366
|
handler: async (args, ctx) => {
|
|
1319
|
-
updateActiveAdapter(
|
|
1367
|
+
updateActiveAdapter(state, ctx.model);
|
|
1320
1368
|
const command = args.trim().toLowerCase();
|
|
1321
1369
|
const force = command === "refresh";
|
|
1322
|
-
const snapshot = state.adapter ? await refreshUsage(
|
|
1370
|
+
const snapshot = state.adapter ? await refreshUsage(state, force || !state.snapshot) : undefined;
|
|
1323
1371
|
const details = buildDetails(snapshot ?? state.snapshot, state);
|
|
1324
1372
|
pi.sendMessage({ customType: MESSAGE_TYPE, content: details, display: true });
|
|
1325
|
-
|
|
1373
|
+
// state.ctx (not captured ctx): the session could be replaced during the
|
|
1374
|
+
// await above; if it was, skip the notification instead of touching a
|
|
1375
|
+
// stale ctx.
|
|
1376
|
+
if (force) state.ctx?.ui.notify("Subscription usage refreshed", "info");
|
|
1326
1377
|
},
|
|
1327
1378
|
});
|
|
1379
|
+
|
|
1380
|
+
// Returned for tests only — Pi ignores the extension setup return value.
|
|
1381
|
+
return state;
|
|
1328
1382
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-sub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Pi extension showing subscription usage for supported model providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"@earendil-works/pi-coding-agent": ">=0.80.8 <0.86.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"
|
|
45
|
+
"tsx": "^4.23.13"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "node --import tsx --test extensions/test/*.test.ts"
|
|
46
49
|
}
|
|
47
50
|
}
|