@eiei114/pi-sub-core 1.5.1

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +178 -0
  3. package/index.ts +540 -0
  4. package/package.json +35 -0
  5. package/src/cache.ts +546 -0
  6. package/src/config.ts +35 -0
  7. package/src/dependencies.ts +37 -0
  8. package/src/errors.ts +71 -0
  9. package/src/paths.ts +55 -0
  10. package/src/provider.ts +66 -0
  11. package/src/providers/detection.ts +51 -0
  12. package/src/providers/impl/anthropic.ts +174 -0
  13. package/src/providers/impl/antigravity.ts +226 -0
  14. package/src/providers/impl/codex.ts +186 -0
  15. package/src/providers/impl/copilot.ts +176 -0
  16. package/src/providers/impl/gemini.ts +130 -0
  17. package/src/providers/impl/kiro.ts +92 -0
  18. package/src/providers/impl/zai.ts +120 -0
  19. package/src/providers/index.ts +5 -0
  20. package/src/providers/metadata.ts +16 -0
  21. package/src/providers/registry.ts +54 -0
  22. package/src/providers/settings.ts +109 -0
  23. package/src/providers/status.ts +25 -0
  24. package/src/settings/behavior.ts +58 -0
  25. package/src/settings/menu.ts +83 -0
  26. package/src/settings/tools.ts +38 -0
  27. package/src/settings/ui.ts +450 -0
  28. package/src/settings-types.ts +95 -0
  29. package/src/settings-ui.ts +1 -0
  30. package/src/settings.ts +137 -0
  31. package/src/status.ts +245 -0
  32. package/src/storage/lock.ts +150 -0
  33. package/src/storage.ts +61 -0
  34. package/src/types.ts +33 -0
  35. package/src/ui/keybindings.ts +92 -0
  36. package/src/ui/settings-list.ts +290 -0
  37. package/src/usage/controller.ts +250 -0
  38. package/src/usage/fetch.ts +215 -0
  39. package/src/usage/types.ts +5 -0
  40. package/src/utils.ts +158 -0
  41. package/test/all.test.ts +9 -0
  42. package/test/cache.test.ts +157 -0
  43. package/test/controller.test.ts +101 -0
  44. package/test/detection.test.ts +24 -0
  45. package/test/extension.test.ts +233 -0
  46. package/test/helpers.ts +48 -0
  47. package/test/keybindings.test.ts +59 -0
  48. package/test/lock.test.ts +49 -0
  49. package/test/prioritize.test.ts +81 -0
  50. package/test/providers.test.ts +385 -0
  51. package/test/status.test.ts +70 -0
  52. package/tsconfig.json +5 -0
package/index.ts ADDED
@@ -0,0 +1,540 @@
1
+ /**
2
+ * sub-core - Shared usage data core for sub-* extensions.
3
+ */
4
+
5
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
6
+ import { Type } from "@sinclair/typebox";
7
+ import * as fs from "node:fs";
8
+ import type { Dependencies, ProviderName, SubCoreState, UsageSnapshot } from "./src/types.js";
9
+ import { getDefaultSettings, type Settings } from "./src/settings-types.js";
10
+ import type { ProviderUsageEntry } from "./src/usage/types.js";
11
+ import { createDefaultDependencies } from "./src/dependencies.js";
12
+ import { createUsageController, type UsageUpdate } from "./src/usage/controller.js";
13
+ import { fetchUsageEntries, getCachedUsageEntries } from "./src/usage/fetch.js";
14
+ import { onCacheSnapshot, onCacheUpdate, watchCacheUpdates, type Cache } from "./src/cache.js";
15
+ import { isExpectedMissingData } from "./src/errors.js";
16
+ import { prioritizeWindowsForModel } from "./src/utils.js";
17
+ import { getStorage } from "./src/storage.js";
18
+ import { clearSettingsCache, loadSettings, saveSettings, SETTINGS_PATH } from "./src/settings.js";
19
+ import { showSettingsUI } from "./src/settings-ui.js";
20
+
21
+ type SubCoreRequest =
22
+ | {
23
+ type?: "current";
24
+ includeSettings?: boolean;
25
+ reply: (payload: { state: SubCoreState; settings?: Settings }) => void;
26
+ }
27
+ | {
28
+ type: "entries";
29
+ force?: boolean;
30
+ reply: (payload: { entries: ProviderUsageEntry[] }) => void;
31
+ };
32
+
33
+ type SubCoreAction = {
34
+ type: "refresh" | "cycleProvider";
35
+ force?: boolean;
36
+ };
37
+
38
+ const TOOL_NAMES = {
39
+ usage: ["sub_get_usage", "get_current_usage"],
40
+ allUsage: ["sub_get_all_usage", "get_all_usage"],
41
+ } as const;
42
+
43
+ type ToolName = (typeof TOOL_NAMES)[keyof typeof TOOL_NAMES][number];
44
+
45
+ type SubCoreGlobalState = { active: boolean };
46
+ const subCoreGlobal = globalThis as typeof globalThis & { __piSubCore?: SubCoreGlobalState };
47
+
48
+ function deepMerge<T extends object>(target: T, source: Partial<T>): T {
49
+ const result = { ...target } as T;
50
+ for (const key of Object.keys(source) as (keyof T)[]) {
51
+ const sourceValue = source[key];
52
+ const targetValue = result[key];
53
+ if (
54
+ sourceValue !== undefined &&
55
+ typeof sourceValue === "object" &&
56
+ sourceValue !== null &&
57
+ !Array.isArray(sourceValue) &&
58
+ typeof targetValue === "object" &&
59
+ targetValue !== null &&
60
+ !Array.isArray(targetValue)
61
+ ) {
62
+ result[key] = deepMerge(targetValue as object, sourceValue as object) as T[keyof T];
63
+ } else if (sourceValue !== undefined) {
64
+ result[key] = sourceValue as T[keyof T];
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+
70
+ function stripUsageProvider(usage?: UsageSnapshot): Omit<UsageSnapshot, "provider"> | undefined {
71
+ if (!usage) return undefined;
72
+ const { provider: _provider, ...rest } = usage;
73
+ return rest;
74
+ }
75
+
76
+ /**
77
+ * Create the extension
78
+ */
79
+ export default function createExtension(pi: ExtensionAPI, deps: Dependencies = createDefaultDependencies()): void {
80
+ if (subCoreGlobal.__piSubCore?.active) {
81
+ return;
82
+ }
83
+ subCoreGlobal.__piSubCore = { active: true };
84
+
85
+ let usageRefreshInterval: ReturnType<typeof setInterval> | undefined;
86
+ let statusRefreshInterval: ReturnType<typeof setInterval> | undefined;
87
+ let lastContext: ExtensionContext | undefined;
88
+ let lastUsageRefreshAt = 0;
89
+ let lastStatusRefreshAt = 0;
90
+ let settings: Settings = getDefaultSettings();
91
+ let settingsLoaded = false;
92
+ let toolsRegistered = false;
93
+ let lastState: SubCoreState = {};
94
+ let settingsSnapshot = "";
95
+ let settingsMtimeMs = 0;
96
+ let settingsDebounce: NodeJS.Timeout | undefined;
97
+ let settingsWatcher: fs.FSWatcher | undefined;
98
+ let settingsPoll: NodeJS.Timeout | undefined;
99
+ let settingsWatchStarted = false;
100
+
101
+ const controller = createUsageController(deps);
102
+ const controllerState = {
103
+ currentProvider: undefined as ProviderName | undefined,
104
+ cachedUsage: undefined as UsageSnapshot | undefined,
105
+ providerCycleIndex: 0,
106
+ };
107
+
108
+ let lastAllSnapshot = "";
109
+ let lastCurrentSnapshot = "";
110
+
111
+ const emitCurrentUpdate = (provider?: ProviderName, usage?: UsageSnapshot): void => {
112
+ const model = lastContext?.model;
113
+ const sorted = usage && model
114
+ ? { ...usage, windows: prioritizeWindowsForModel(usage.windows, model) }
115
+ : usage;
116
+ lastState = { provider, usage: sorted };
117
+ const payload = JSON.stringify(lastState);
118
+ if (payload === lastCurrentSnapshot) return;
119
+ lastCurrentSnapshot = payload;
120
+ pi.events.emit("sub-core:update-current", { state: lastState });
121
+ };
122
+
123
+ const unsubscribeCacheSnapshot = onCacheSnapshot((cache: Cache) => {
124
+ const ttlMs = settings.behavior.refreshInterval * 1000;
125
+ const now = Date.now();
126
+ const entries: ProviderUsageEntry[] = [];
127
+ for (const provider of settings.providerOrder) {
128
+ const entry = cache[provider];
129
+ if (!entry || !entry.usage) continue;
130
+ if (now - entry.fetchedAt >= ttlMs) continue;
131
+ const usage = { ...entry.usage, status: entry.status };
132
+ if (usage.error && isExpectedMissingData(usage.error)) continue;
133
+ entries.push({ provider, usage });
134
+ }
135
+ const payload = JSON.stringify({ provider: controllerState.currentProvider, entries });
136
+ if (payload === lastAllSnapshot) return;
137
+ lastAllSnapshot = payload;
138
+ pi.events.emit("sub-core:update-all", {
139
+ state: { provider: controllerState.currentProvider, entries },
140
+ });
141
+ });
142
+
143
+ const unsubscribeCache = onCacheUpdate((provider, entry) => {
144
+ if (!controllerState.currentProvider || provider !== controllerState.currentProvider) return;
145
+ const usage = entry?.usage ? { ...entry.usage, status: entry.status } : undefined;
146
+ controllerState.cachedUsage = usage;
147
+ emitCurrentUpdate(controllerState.currentProvider, usage);
148
+ });
149
+
150
+ let stopCacheWatch: (() => void) | undefined;
151
+ let cacheWatchStarted = false;
152
+
153
+ const startCacheWatch = (): void => {
154
+ if (cacheWatchStarted) return;
155
+ cacheWatchStarted = true;
156
+ stopCacheWatch = watchCacheUpdates();
157
+ };
158
+
159
+ function emitUpdate(update: UsageUpdate): void {
160
+ emitCurrentUpdate(update.provider, update.usage);
161
+ }
162
+
163
+ async function refresh(
164
+ ctx: ExtensionContext,
165
+ options?: { force?: boolean; allowStaleCache?: boolean; skipFetch?: boolean }
166
+ ) {
167
+ lastContext = ctx;
168
+ ensureSettingsLoaded();
169
+ try {
170
+ await controller.refresh(ctx, settings, controllerState, emitUpdate, options);
171
+ } finally {
172
+ if (!options?.skipFetch) {
173
+ lastUsageRefreshAt = Date.now();
174
+ }
175
+ }
176
+ }
177
+
178
+ async function refreshStatus(
179
+ ctx: ExtensionContext,
180
+ options?: { force?: boolean; allowStaleCache?: boolean; skipFetch?: boolean }
181
+ ) {
182
+ lastContext = ctx;
183
+ ensureSettingsLoaded();
184
+ try {
185
+ await controller.refreshStatus(ctx, settings, controllerState, emitUpdate, options);
186
+ } finally {
187
+ if (!options?.skipFetch) {
188
+ lastStatusRefreshAt = Date.now();
189
+ }
190
+ }
191
+ }
192
+
193
+ async function cycleProvider(ctx: ExtensionContext): Promise<void> {
194
+ ensureSettingsLoaded();
195
+ await controller.cycleProvider(ctx, settings, controllerState, emitUpdate);
196
+ }
197
+
198
+ function setupRefreshInterval(): void {
199
+ if (usageRefreshInterval) {
200
+ clearInterval(usageRefreshInterval);
201
+ usageRefreshInterval = undefined;
202
+ }
203
+ if (statusRefreshInterval) {
204
+ clearInterval(statusRefreshInterval);
205
+ statusRefreshInterval = undefined;
206
+ }
207
+
208
+ const usageIntervalMs = settings.behavior.refreshInterval * 1000;
209
+ if (usageIntervalMs > 0) {
210
+ const usageTickMs = Math.min(usageIntervalMs, 10000);
211
+ usageRefreshInterval = setInterval(() => {
212
+ if (!lastContext) return;
213
+ const elapsed = lastUsageRefreshAt ? Date.now() - lastUsageRefreshAt : usageIntervalMs + 1;
214
+ if (elapsed >= usageIntervalMs) {
215
+ void refresh(lastContext);
216
+ }
217
+ }, usageTickMs);
218
+ usageRefreshInterval.unref?.();
219
+ }
220
+
221
+ const statusIntervalMs = settings.statusRefresh.refreshInterval * 1000;
222
+ if (statusIntervalMs > 0) {
223
+ const statusTickMs = Math.min(statusIntervalMs, 10000);
224
+ statusRefreshInterval = setInterval(() => {
225
+ if (!lastContext) return;
226
+ const elapsed = lastStatusRefreshAt ? Date.now() - lastStatusRefreshAt : statusIntervalMs + 1;
227
+ if (elapsed >= statusIntervalMs) {
228
+ void refreshStatus(lastContext);
229
+ }
230
+ }, statusTickMs);
231
+ statusRefreshInterval.unref?.();
232
+ }
233
+ }
234
+
235
+ function applySettingsPatch(patch: Partial<Settings>): void {
236
+ ensureSettingsLoaded();
237
+ settings = deepMerge(settings, patch);
238
+ saveSettings(settings);
239
+ setupRefreshInterval();
240
+ pi.events.emit("sub-core:settings:updated", { settings });
241
+ }
242
+
243
+ function readSettingsFile(): string | undefined {
244
+ try {
245
+ return fs.readFileSync(SETTINGS_PATH, "utf-8");
246
+ } catch {
247
+ return undefined;
248
+ }
249
+ }
250
+
251
+ function applySettingsFromDisk(): void {
252
+ clearSettingsCache();
253
+ settings = loadSettings();
254
+ registerToolsFromSettings(settings);
255
+ setupRefreshInterval();
256
+ pi.events.emit("sub-core:settings:updated", { settings });
257
+ if (lastContext) {
258
+ void refresh(lastContext, { allowStaleCache: true, skipFetch: true });
259
+ void refreshStatus(lastContext, { allowStaleCache: true, skipFetch: true });
260
+ }
261
+ }
262
+
263
+ function refreshSettingsSnapshot(): void {
264
+ const content = readSettingsFile();
265
+ if (!content || content === settingsSnapshot) return;
266
+ try {
267
+ JSON.parse(content);
268
+ } catch {
269
+ return;
270
+ }
271
+ settingsSnapshot = content;
272
+ applySettingsFromDisk();
273
+ }
274
+
275
+ function checkSettingsFile(): void {
276
+ try {
277
+ const stat = fs.statSync(SETTINGS_PATH, { throwIfNoEntry: false });
278
+ if (!stat || !stat.mtimeMs) return;
279
+ if (stat.mtimeMs === settingsMtimeMs) return;
280
+ settingsMtimeMs = stat.mtimeMs;
281
+ refreshSettingsSnapshot();
282
+ } catch {
283
+ // Ignore missing files
284
+ }
285
+ }
286
+
287
+ function scheduleSettingsRefresh(): void {
288
+ if (settingsDebounce) clearTimeout(settingsDebounce);
289
+ settingsDebounce = setTimeout(() => checkSettingsFile(), 200);
290
+ }
291
+
292
+ function startSettingsWatch(): void {
293
+ if (settingsWatchStarted) return;
294
+ settingsWatchStarted = true;
295
+ if (!settingsSnapshot) {
296
+ const content = readSettingsFile();
297
+ if (content) {
298
+ settingsSnapshot = content;
299
+ try {
300
+ const stat = fs.statSync(SETTINGS_PATH, { throwIfNoEntry: false });
301
+ if (stat?.mtimeMs) settingsMtimeMs = stat.mtimeMs;
302
+ } catch {
303
+ // Ignore
304
+ }
305
+ }
306
+ }
307
+ try {
308
+ settingsWatcher = fs.watch(SETTINGS_PATH, scheduleSettingsRefresh);
309
+ settingsWatcher.unref?.();
310
+ } catch {
311
+ settingsWatcher = undefined;
312
+ }
313
+ settingsPoll = setInterval(() => checkSettingsFile(), 2000);
314
+ settingsPoll.unref?.();
315
+ }
316
+
317
+ async function getEntries(force?: boolean): Promise<ProviderUsageEntry[]> {
318
+ ensureSettingsLoaded();
319
+ const enabledProviders = controller.getEnabledProviders(settings);
320
+ if (enabledProviders.length === 0) return [];
321
+ if (force) {
322
+ return fetchUsageEntries(deps, settings, enabledProviders, { force: true });
323
+ }
324
+ return getCachedUsageEntries(enabledProviders, settings);
325
+ }
326
+
327
+ const registerUsageTool = (name: ToolName): void => {
328
+ pi.registerTool({
329
+ name,
330
+ label: "Sub Usage",
331
+ description: "Refresh and return the latest subscription usage snapshot.",
332
+ parameters: Type.Object({
333
+ force: Type.Optional(Type.Boolean({ description: "Force refresh" })),
334
+ }),
335
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
336
+ const { force } = params as { force?: boolean };
337
+ await refresh(ctx, { force: force ?? true });
338
+ const payload = { provider: lastState.provider, usage: stripUsageProvider(lastState.usage) };
339
+ return {
340
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
341
+ details: payload,
342
+ };
343
+ },
344
+ });
345
+ };
346
+
347
+ const registerAllUsageTool = (name: ToolName): void => {
348
+ pi.registerTool({
349
+ name,
350
+ label: "Sub All Usage",
351
+ description: "Refresh and return usage snapshots for all enabled providers.",
352
+ parameters: Type.Object({
353
+ force: Type.Optional(Type.Boolean({ description: "Force refresh" })),
354
+ }),
355
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
356
+ const { force } = params as { force?: boolean };
357
+ const entries = await getEntries(force ?? true);
358
+ const payload = entries.map((entry) => ({
359
+ provider: entry.provider,
360
+ usage: stripUsageProvider(entry.usage),
361
+ }));
362
+ return {
363
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
364
+ details: { entries: payload },
365
+ };
366
+ },
367
+ });
368
+ };
369
+
370
+ function registerToolsFromSettings(nextSettings: Settings): void {
371
+ if (toolsRegistered) return;
372
+ const usageToolEnabled = nextSettings.tools?.usageTool ?? false;
373
+ const allUsageToolEnabled = nextSettings.tools?.allUsageTool ?? false;
374
+
375
+ if (usageToolEnabled) {
376
+ for (const name of TOOL_NAMES.usage) {
377
+ registerUsageTool(name);
378
+ }
379
+ }
380
+ if (allUsageToolEnabled) {
381
+ for (const name of TOOL_NAMES.allUsage) {
382
+ registerAllUsageTool(name);
383
+ }
384
+ }
385
+ toolsRegistered = true;
386
+ }
387
+
388
+ function ensureSettingsLoaded(): void {
389
+ if (settingsLoaded) return;
390
+ settings = loadSettings();
391
+ settingsLoaded = true;
392
+ registerToolsFromSettings(settings);
393
+ setupRefreshInterval();
394
+ const watchTimer = setTimeout(() => {
395
+ startCacheWatch();
396
+ startSettingsWatch();
397
+ }, 0);
398
+ watchTimer.unref?.();
399
+ }
400
+ pi.registerCommand("sub-core:settings", {
401
+ description: "Open sub-core settings",
402
+ handler: async (_args, ctx) => {
403
+ ensureSettingsLoaded();
404
+ const handleSettingsChange = async (updatedSettings: Settings) => {
405
+ applySettingsPatch(updatedSettings);
406
+ if (lastContext) {
407
+ await refresh(lastContext);
408
+ }
409
+ };
410
+
411
+ const newSettings = await showSettingsUI(ctx, handleSettingsChange);
412
+ settings = newSettings;
413
+ applySettingsPatch(newSettings);
414
+ if (lastContext) {
415
+ await refresh(lastContext);
416
+ }
417
+ },
418
+ });
419
+
420
+ pi.events.on("sub-core:request", async (payload) => {
421
+ ensureSettingsLoaded();
422
+ const request = payload as SubCoreRequest;
423
+ if (request.type === "entries") {
424
+ const entries = await getEntries(request.force);
425
+ if (lastContext && settings.statusRefresh.refreshInterval > 0) {
426
+ await refreshStatus(lastContext, { force: request.force });
427
+ }
428
+ request.reply({ entries });
429
+ return;
430
+ }
431
+ request.reply({
432
+ state: lastState,
433
+ settings: request.includeSettings ? settings : undefined,
434
+ });
435
+ });
436
+
437
+ pi.events.on("sub-core:settings:patch", (payload) => {
438
+ const patch = (payload as { patch?: Partial<Settings> }).patch;
439
+ if (!patch) return;
440
+ applySettingsPatch(patch);
441
+ if (lastContext) {
442
+ void refresh(lastContext);
443
+ }
444
+ });
445
+
446
+ pi.events.on("sub-core:action", (payload) => {
447
+ const action = payload as SubCoreAction;
448
+ if (!lastContext) return;
449
+ switch (action.type) {
450
+ case "refresh":
451
+ void refresh(lastContext, { force: action.force });
452
+ break;
453
+ case "cycleProvider":
454
+ void cycleProvider(lastContext);
455
+ break;
456
+ }
457
+ });
458
+
459
+ pi.on("session_start", async (_event, ctx) => {
460
+ lastContext = ctx;
461
+ ensureSettingsLoaded();
462
+ void refresh(ctx, { allowStaleCache: true, skipFetch: true });
463
+ void refreshStatus(ctx, { allowStaleCache: true, skipFetch: true });
464
+ pi.events.emit("sub-core:ready", { state: lastState, settings });
465
+ });
466
+
467
+ pi.on("turn_start", async (_event, ctx) => {
468
+ if (settings.behavior.refreshOnTurnStart) {
469
+ await refresh(ctx);
470
+ }
471
+ if (settings.statusRefresh.refreshOnTurnStart) {
472
+ await refreshStatus(ctx);
473
+ }
474
+ });
475
+
476
+ pi.on("tool_result", async (_event, ctx) => {
477
+ if (settings.behavior.refreshOnToolResult) {
478
+ await refresh(ctx);
479
+ }
480
+ if (settings.statusRefresh.refreshOnToolResult) {
481
+ await refreshStatus(ctx);
482
+ }
483
+ });
484
+
485
+ pi.on("turn_end", async (_event, ctx) => {
486
+ await refresh(ctx);
487
+ });
488
+
489
+ pi.on("session_switch", async (_event, ctx) => {
490
+ controllerState.currentProvider = undefined;
491
+ controllerState.cachedUsage = undefined;
492
+ await refresh(ctx);
493
+ await refreshStatus(ctx);
494
+ });
495
+
496
+ pi.on("session_branch" as unknown as "session_start", async (_event: unknown, ctx: ExtensionContext) => {
497
+ controllerState.currentProvider = undefined;
498
+ controllerState.cachedUsage = undefined;
499
+ await refresh(ctx);
500
+ await refreshStatus(ctx);
501
+ });
502
+
503
+ pi.on("model_select" as unknown as "session_start", async (_event: unknown, ctx: ExtensionContext) => {
504
+ controllerState.currentProvider = undefined;
505
+ controllerState.cachedUsage = undefined;
506
+ void refresh(ctx, { force: true, allowStaleCache: true });
507
+ void refreshStatus(ctx, { force: true, allowStaleCache: true });
508
+ });
509
+
510
+ pi.on("session_shutdown", async () => {
511
+ if (usageRefreshInterval) {
512
+ clearInterval(usageRefreshInterval);
513
+ usageRefreshInterval = undefined;
514
+ }
515
+ if (statusRefreshInterval) {
516
+ clearInterval(statusRefreshInterval);
517
+ statusRefreshInterval = undefined;
518
+ }
519
+ if (settingsDebounce) {
520
+ clearTimeout(settingsDebounce);
521
+ settingsDebounce = undefined;
522
+ }
523
+ if (settingsPoll) {
524
+ clearInterval(settingsPoll);
525
+ settingsPoll = undefined;
526
+ }
527
+ settingsWatcher?.close();
528
+ settingsWatcher = undefined;
529
+ settingsWatchStarted = false;
530
+ settingsSnapshot = "";
531
+ settingsMtimeMs = 0;
532
+ unsubscribeCache();
533
+ unsubscribeCacheSnapshot();
534
+ stopCacheWatch?.();
535
+ stopCacheWatch = undefined;
536
+ cacheWatchStarted = false;
537
+ lastContext = undefined;
538
+ subCoreGlobal.__piSubCore = undefined;
539
+ });
540
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@eiei114/pi-sub-core",
3
+ "version": "1.5.1",
4
+ "description": "Shared usage data core for pi extensions",
5
+ "keywords": [
6
+ "pi-package"
7
+ ],
8
+ "type": "module",
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "registry": "https://registry.npmjs.org"
13
+ },
14
+ "pi": {
15
+ "extensions": [
16
+ "./index.ts"
17
+ ]
18
+ },
19
+ "scripts": {
20
+ "check": "tsc --noEmit",
21
+ "check:watch": "tsc --noEmit --watch",
22
+ "test": "tsx test/all.test.ts"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "tsx": "^4.19.2",
27
+ "typescript": "^5.8.0"
28
+ },
29
+ "dependencies": {
30
+ "@eiei114/pi-sub-shared": "^1.5.1"
31
+ },
32
+ "peerDependencies": {
33
+ "@mariozechner/pi-coding-agent": "*"
34
+ }
35
+ }