@ereinha/opencode-enhanced-quotas 1.0.0

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 (98) hide show
  1. package/README.md +333 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +42 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/constants.d.ts +13 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/constants.js +19 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/defaults.d.ts +3 -0
  11. package/dist/defaults.d.ts.map +1 -0
  12. package/dist/defaults.js +56 -0
  13. package/dist/defaults.js.map +1 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +449 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/interfaces.d.ts +210 -0
  19. package/dist/interfaces.d.ts.map +1 -0
  20. package/dist/interfaces.js +2 -0
  21. package/dist/interfaces.js.map +1 -0
  22. package/dist/logger.d.ts +15 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +47 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/plugin-state.d.ts +32 -0
  27. package/dist/plugin-state.d.ts.map +1 -0
  28. package/dist/plugin-state.js +81 -0
  29. package/dist/plugin-state.js.map +1 -0
  30. package/dist/providers/antigravity/auth.d.ts +12 -0
  31. package/dist/providers/antigravity/auth.d.ts.map +1 -0
  32. package/dist/providers/antigravity/auth.js +109 -0
  33. package/dist/providers/antigravity/auth.js.map +1 -0
  34. package/dist/providers/antigravity/index.d.ts +2 -0
  35. package/dist/providers/antigravity/index.d.ts.map +1 -0
  36. package/dist/providers/antigravity/index.js +2 -0
  37. package/dist/providers/antigravity/index.js.map +1 -0
  38. package/dist/providers/antigravity/provider.d.ts +34 -0
  39. package/dist/providers/antigravity/provider.d.ts.map +1 -0
  40. package/dist/providers/antigravity/provider.js +221 -0
  41. package/dist/providers/antigravity/provider.js.map +1 -0
  42. package/dist/providers/codex.d.ts +4 -0
  43. package/dist/providers/codex.d.ts.map +1 -0
  44. package/dist/providers/codex.js +233 -0
  45. package/dist/providers/codex.js.map +1 -0
  46. package/dist/providers/github.d.ts +4 -0
  47. package/dist/providers/github.d.ts.map +1 -0
  48. package/dist/providers/github.js +139 -0
  49. package/dist/providers/github.js.map +1 -0
  50. package/dist/quota-cache.d.ts +26 -0
  51. package/dist/quota-cache.d.ts.map +1 -0
  52. package/dist/quota-cache.js +107 -0
  53. package/dist/quota-cache.js.map +1 -0
  54. package/dist/registry.d.ts +3 -0
  55. package/dist/registry.d.ts.map +1 -0
  56. package/dist/registry.js +23 -0
  57. package/dist/registry.js.map +1 -0
  58. package/dist/services/aggregation-service.d.ts +34 -0
  59. package/dist/services/aggregation-service.d.ts.map +1 -0
  60. package/dist/services/aggregation-service.js +89 -0
  61. package/dist/services/aggregation-service.js.map +1 -0
  62. package/dist/services/config-loader.d.ts +32 -0
  63. package/dist/services/config-loader.d.ts.map +1 -0
  64. package/dist/services/config-loader.js +168 -0
  65. package/dist/services/config-loader.js.map +1 -0
  66. package/dist/services/history-service.d.ts +39 -0
  67. package/dist/services/history-service.d.ts.map +1 -0
  68. package/dist/services/history-service.js +207 -0
  69. package/dist/services/history-service.js.map +1 -0
  70. package/dist/services/prediction-engine.d.ts +51 -0
  71. package/dist/services/prediction-engine.d.ts.map +1 -0
  72. package/dist/services/prediction-engine.js +108 -0
  73. package/dist/services/prediction-engine.js.map +1 -0
  74. package/dist/services/quota-service.d.ts +42 -0
  75. package/dist/services/quota-service.d.ts.map +1 -0
  76. package/dist/services/quota-service.js +339 -0
  77. package/dist/services/quota-service.js.map +1 -0
  78. package/dist/ui/progress-bar.d.ts +20 -0
  79. package/dist/ui/progress-bar.d.ts.map +1 -0
  80. package/dist/ui/progress-bar.js +150 -0
  81. package/dist/ui/progress-bar.js.map +1 -0
  82. package/dist/ui/quota-table.d.ts +15 -0
  83. package/dist/ui/quota-table.d.ts.map +1 -0
  84. package/dist/ui/quota-table.js +137 -0
  85. package/dist/ui/quota-table.js.map +1 -0
  86. package/dist/utils/paths.d.ts +7 -0
  87. package/dist/utils/paths.d.ts.map +1 -0
  88. package/dist/utils/paths.js +37 -0
  89. package/dist/utils/paths.js.map +1 -0
  90. package/dist/utils/time.d.ts +3 -0
  91. package/dist/utils/time.d.ts.map +1 -0
  92. package/dist/utils/time.js +38 -0
  93. package/dist/utils/time.js.map +1 -0
  94. package/dist/utils/validation.d.ts +6 -0
  95. package/dist/utils/validation.d.ts.map +1 -0
  96. package/dist/utils/validation.js +66 -0
  97. package/dist/utils/validation.js.map +1 -0
  98. package/package.json +42 -0
@@ -0,0 +1,26 @@
1
+ import { type IQuotaProvider, type QuotaData, type IHistoryService } from "./interfaces";
2
+ type CachedQuotas = {
3
+ data: QuotaData[];
4
+ fetchedAt: Date | null;
5
+ lastError: unknown;
6
+ };
7
+ type QuotaCacheOptions = {
8
+ refreshIntervalMs: number;
9
+ historyService?: IHistoryService;
10
+ debug?: boolean;
11
+ };
12
+ export declare class QuotaCache {
13
+ private readonly providers;
14
+ private readonly options;
15
+ private state;
16
+ private timer;
17
+ private inFlight;
18
+ constructor(providers: IQuotaProvider[], options?: Partial<QuotaCacheOptions>);
19
+ start(): void;
20
+ stop(): void;
21
+ getSnapshot(): CachedQuotas;
22
+ refresh(): Promise<void>;
23
+ private doRefresh;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=quota-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-cache.d.ts","sourceRoot":"","sources":["../src/quota-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAKzF,KAAK,YAAY,GAAG;IAChB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAMF,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,QAAQ,CAAuB;gBAEpB,SAAS,EAAE,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAQ7E,KAAK,IAAI,IAAI;IAcb,IAAI,IAAI,IAAI;IAOZ,WAAW,IAAI,YAAY;IAIrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBvB,SAAS;CA0E1B"}
@@ -0,0 +1,107 @@
1
+ import { validateQuotaData } from "./utils/validation";
2
+ import { logger } from "./logger";
3
+ const DEFAULT_OPTIONS = {
4
+ refreshIntervalMs: 60_000,
5
+ };
6
+ export class QuotaCache {
7
+ providers;
8
+ options;
9
+ state;
10
+ timer;
11
+ inFlight;
12
+ constructor(providers, options) {
13
+ this.providers = providers;
14
+ this.options = { ...DEFAULT_OPTIONS, ...(options ?? {}) };
15
+ this.state = { data: [], fetchedAt: null, lastError: null };
16
+ this.timer = null;
17
+ this.inFlight = null;
18
+ }
19
+ start() {
20
+ if (this.timer)
21
+ return;
22
+ // Kick off an initial refresh without blocking startup.
23
+ void this.refresh();
24
+ this.timer = setInterval(() => {
25
+ void this.refresh();
26
+ }, this.options.refreshIntervalMs);
27
+ // Avoid keeping the process alive just for quota polling.
28
+ this.timer.unref?.();
29
+ }
30
+ stop() {
31
+ if (this.timer) {
32
+ clearInterval(this.timer);
33
+ this.timer = null;
34
+ }
35
+ }
36
+ getSnapshot() {
37
+ return this.state;
38
+ }
39
+ async refresh() {
40
+ logger.debug("cache:refresh_start", {
41
+ providerCount: this.providers.length,
42
+ refreshIntervalMs: this.options.refreshIntervalMs,
43
+ inFlight: !!this.inFlight,
44
+ });
45
+ if (this.inFlight) {
46
+ logger.debug("cache:refresh_coalesced", { inFlight: true });
47
+ return this.inFlight;
48
+ }
49
+ const refreshPromise = this.doRefresh();
50
+ this.inFlight = refreshPromise;
51
+ return refreshPromise;
52
+ }
53
+ async doRefresh() {
54
+ try {
55
+ const results = await Promise.all(this.providers.map(async (p) => {
56
+ const startedAt = Date.now();
57
+ try {
58
+ logger.debug("cache:provider_fetch_start", { id: p.id });
59
+ const result = await p.fetchQuota();
60
+ logger.debug("cache:provider_fetch_ok", {
61
+ id: p.id,
62
+ count: result.length,
63
+ durationMs: Date.now() - startedAt,
64
+ });
65
+ return result;
66
+ }
67
+ catch (e) {
68
+ logger.error("cache:provider_fetch_error", {
69
+ id: p.id,
70
+ durationMs: Date.now() - startedAt,
71
+ error: e,
72
+ });
73
+ return [];
74
+ }
75
+ }));
76
+ // Validate and normalize provider responses before storing
77
+ const flattened = results.flat();
78
+ const validatedData = flattened
79
+ .map(d => validateQuotaData(d))
80
+ .filter((v) => v !== null);
81
+ this.state = {
82
+ data: validatedData,
83
+ fetchedAt: new Date(),
84
+ lastError: null,
85
+ };
86
+ logger.debug("cache:refresh_ok", {
87
+ totalCount: this.state.data.length,
88
+ fetchedAt: this.state.fetchedAt?.toISOString(),
89
+ });
90
+ if (this.options.historyService) {
91
+ void this.options.historyService.append(this.state.data);
92
+ }
93
+ }
94
+ catch (e) {
95
+ this.state = {
96
+ ...this.state,
97
+ lastError: e,
98
+ };
99
+ logger.error("cache:refresh_error", { error: e });
100
+ }
101
+ finally {
102
+ logger.debug("cache:refresh_end", { inFlightCleared: true });
103
+ this.inFlight = null;
104
+ }
105
+ }
106
+ }
107
+ //# sourceMappingURL=quota-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota-cache.js","sourceRoot":"","sources":["../src/quota-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type IQuotaRegistry } from "./interfaces";
2
+ export declare function getQuotaRegistry(): IQuotaRegistry;
3
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,cAAc,CAAC;AAuBxE,wBAAgB,gBAAgB,IAAI,cAAc,CAMjD"}
@@ -0,0 +1,23 @@
1
+ const REGISTRY_KEY = "__OPENCODE_QUOTA_REGISTRY__";
2
+ function createRegistry() {
3
+ const providers = [];
4
+ return {
5
+ register(provider) {
6
+ if (providers.some((p) => p.id === provider.id)) {
7
+ return;
8
+ }
9
+ providers.push(provider);
10
+ },
11
+ getAll() {
12
+ return [...providers];
13
+ },
14
+ };
15
+ }
16
+ export function getQuotaRegistry() {
17
+ const globalRef = globalThis;
18
+ if (!globalRef[REGISTRY_KEY]) {
19
+ globalRef[REGISTRY_KEY] = createRegistry();
20
+ }
21
+ return globalRef[REGISTRY_KEY];
22
+ }
23
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,6BAA6B,CAAC;AAMnD,SAAS,cAAc;IACrB,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,OAAO;QACL,QAAQ,CAAC,QAAwB;YAC/B,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAChD,OAAO;YACT,CAAC;YACD,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM;YACJ,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,SAAS,GAAG,UAA4B,CAAC;IAC/C,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,SAAS,CAAC,YAAY,CAAC,GAAG,cAAc,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,SAAS,CAAC,YAAY,CAAmB,CAAC;AACnD,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type QuotaData, type IPredictionEngine, type IAggregationService } from "../interfaces";
2
+ /**
3
+ * Service for aggregating multiple quota sources into a single representative quota.
4
+ *
5
+ * Supports multiple aggregation strategies:
6
+ * - most_critical: Selects the quota with shortest predicted time-to-limit
7
+ * - max: Selects the quota with highest usage ratio
8
+ * - min: Selects the quota with lowest usage ratio
9
+ * - mean: Creates a synthetic quota with average usage ratio
10
+ * - median: Creates a synthetic quota with median usage ratio
11
+ */
12
+ export declare class AggregationService implements IAggregationService {
13
+ private readonly predictionEngine;
14
+ constructor(predictionEngine: IPredictionEngine);
15
+ /**
16
+ * Aggregates quotas using the most critical (shortest time-to-limit) strategy.
17
+ * Falls back to max usage ratio if no predictions are available.
18
+ */
19
+ aggregateMostCritical(quotas: QuotaData[], windowMinutes?: number, shortWindowMinutes?: number): QuotaData | null;
20
+ /**
21
+ * Aggregates quotas by selecting the one with highest usage ratio.
22
+ */
23
+ aggregateMax(quotas: QuotaData[]): QuotaData;
24
+ /**
25
+ * Aggregates quotas by selecting the one with lowest usage ratio.
26
+ */
27
+ aggregateMin(quotas: QuotaData[]): QuotaData;
28
+ /**
29
+ * Aggregates quotas by averaging their usage ratios.
30
+ * Creates a synthetic quota with percentage-based representation.
31
+ */
32
+ aggregateAverage(quotas: QuotaData[], name: string, id: string, strategy: "mean" | "median"): QuotaData;
33
+ }
34
+ //# sourceMappingURL=aggregation-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-service.d.ts","sourceRoot":"","sources":["../../src/services/aggregation-service.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAC3B,MAAM,eAAe,CAAC;AAGvB;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB;IAC1D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAoB;gBAEzC,gBAAgB,EAAE,iBAAiB;IAI/C;;;OAGG;IACH,qBAAqB,CACjB,MAAM,EAAE,SAAS,EAAE,EACnB,aAAa,GAAE,MAAW,EAC1B,kBAAkB,CAAC,EAAE,MAAM,GAC5B,SAAS,GAAG,IAAI;IAiCnB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS;IAQ5C;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS;IAQ5C;;;OAGG;IACH,gBAAgB,CACZ,MAAM,EAAE,SAAS,EAAE,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAC5B,SAAS;CAoBf"}
@@ -0,0 +1,89 @@
1
+ import { formatDurationMs } from "../utils/time";
2
+ /**
3
+ * Service for aggregating multiple quota sources into a single representative quota.
4
+ *
5
+ * Supports multiple aggregation strategies:
6
+ * - most_critical: Selects the quota with shortest predicted time-to-limit
7
+ * - max: Selects the quota with highest usage ratio
8
+ * - min: Selects the quota with lowest usage ratio
9
+ * - mean: Creates a synthetic quota with average usage ratio
10
+ * - median: Creates a synthetic quota with median usage ratio
11
+ */
12
+ export class AggregationService {
13
+ predictionEngine;
14
+ constructor(predictionEngine) {
15
+ this.predictionEngine = predictionEngine;
16
+ }
17
+ /**
18
+ * Aggregates quotas using the most critical (shortest time-to-limit) strategy.
19
+ * Falls back to max usage ratio if no predictions are available.
20
+ */
21
+ aggregateMostCritical(quotas, windowMinutes = 60, shortWindowMinutes) {
22
+ if (quotas.length === 0)
23
+ return null;
24
+ let minTime = Infinity;
25
+ let representative = null;
26
+ for (const q of quotas) {
27
+ const time = this.predictionEngine.predictTimeToLimit(q.id, windowMinutes, shortWindowMinutes, { windowInfo: q.window });
28
+ if (time < minTime) {
29
+ minTime = time;
30
+ representative = q;
31
+ }
32
+ }
33
+ // Fallback to max usage if no prediction is possible
34
+ if (!representative) {
35
+ return this.aggregateMax(quotas);
36
+ }
37
+ if (minTime !== Infinity) {
38
+ return {
39
+ ...representative,
40
+ predictedReset: `in ${formatDurationMs(minTime)} (predicted)`
41
+ };
42
+ }
43
+ return representative;
44
+ }
45
+ /**
46
+ * Aggregates quotas by selecting the one with highest usage ratio.
47
+ */
48
+ aggregateMax(quotas) {
49
+ return quotas.reduce((a, b) => {
50
+ const aRatio = a.limit !== null && a.limit > 0 ? a.used / a.limit : 0;
51
+ const bRatio = b.limit !== null && b.limit > 0 ? b.used / b.limit : 0;
52
+ return aRatio > bRatio ? a : b;
53
+ });
54
+ }
55
+ /**
56
+ * Aggregates quotas by selecting the one with lowest usage ratio.
57
+ */
58
+ aggregateMin(quotas) {
59
+ return quotas.reduce((a, b) => {
60
+ const aRatio = a.limit !== null && a.limit > 0 ? a.used / a.limit : 0;
61
+ const bRatio = b.limit !== null && b.limit > 0 ? b.used / b.limit : 0;
62
+ return aRatio < bRatio ? a : b;
63
+ });
64
+ }
65
+ /**
66
+ * Aggregates quotas by averaging their usage ratios.
67
+ * Creates a synthetic quota with percentage-based representation.
68
+ */
69
+ aggregateAverage(quotas, name, id, strategy) {
70
+ const ratios = quotas.map(q => q.limit !== null && q.limit > 0 ? q.used / q.limit : 0);
71
+ let avgRatio = 0;
72
+ if (strategy === "mean") {
73
+ avgRatio = ratios.reduce((a, b) => a + b, 0) / ratios.length;
74
+ }
75
+ else {
76
+ ratios.sort((a, b) => a - b);
77
+ avgRatio = ratios[Math.floor(ratios.length / 2)];
78
+ }
79
+ return {
80
+ id: id,
81
+ providerName: name,
82
+ used: Math.round(avgRatio * 100),
83
+ limit: 100,
84
+ unit: "%",
85
+ info: "Aggregated"
86
+ };
87
+ }
88
+ }
89
+ //# sourceMappingURL=aggregation-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-service.js","sourceRoot":"","sources":["../../src/services/aggregation-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type QuotaConfig } from "../interfaces";
2
+ /**
3
+ * Configuration loading and merging service.
4
+ * Handles reading config from disk and merging with defaults.
5
+ */
6
+ export declare class ConfigLoader {
7
+ /**
8
+ * Creates a new configuration by merging defaults with initial config.
9
+ */
10
+ static createConfig(initialConfig?: Partial<QuotaConfig>): QuotaConfig;
11
+ /**
12
+ * Loads and merges user configuration from disk into the provided config.
13
+ * Searches for config in multiple locations:
14
+ * 1. OPENCODE_QUOTAS_CONFIG_PATH environment variable
15
+ * 2. Project-level: <directory>/.opencode/quotas.json
16
+ * 3. Global: ~/.opencode/quotas.json
17
+ *
18
+ * Missing config files are silently ignored (expected behavior).
19
+ * Parse errors are logged as warnings.
20
+ * Returns the updated config.
21
+ */
22
+ static loadFromDisk(directory: string, config: QuotaConfig): Promise<QuotaConfig>;
23
+ /**
24
+ * Merges user configuration into the target config.
25
+ */
26
+ private static mergeUserConfig;
27
+ /**
28
+ * Validates and normalizes configuration values.
29
+ */
30
+ private static validateConfig;
31
+ }
32
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/services/config-loader.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAKjD;;;GAGG;AACH,qBAAa,YAAY;IACrB;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW;IAkBtE;;;;;;;;;;OAUG;WACU,YAAY,CACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,WAAW,CAAC;IAoEvB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAkD9B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;CAahC"}
@@ -0,0 +1,168 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { DEFAULT_CONFIG } from "../defaults";
6
+ import { logger } from "../logger";
7
+ import { validatePollingInterval } from "../utils/validation";
8
+ /**
9
+ * Configuration loading and merging service.
10
+ * Handles reading config from disk and merging with defaults.
11
+ */
12
+ export class ConfigLoader {
13
+ /**
14
+ * Creates a new configuration by merging defaults with initial config.
15
+ */
16
+ static createConfig(initialConfig) {
17
+ const config = { ...DEFAULT_CONFIG, ...initialConfig };
18
+ // Deep clone specific nested objects to avoid mutation of the constant
19
+ if (DEFAULT_CONFIG.progressBar) {
20
+ config.progressBar = { ...DEFAULT_CONFIG.progressBar, ...initialConfig?.progressBar };
21
+ }
22
+ // Aggregated groups: if provided in the initial config, replace defaults;
23
+ // otherwise clone the defaults to avoid mutating the constant.
24
+ if (initialConfig?.aggregatedGroups !== undefined) {
25
+ config.aggregatedGroups = initialConfig.aggregatedGroups.map(g => ({ ...g }));
26
+ }
27
+ else if (DEFAULT_CONFIG.aggregatedGroups) {
28
+ config.aggregatedGroups = DEFAULT_CONFIG.aggregatedGroups.map(g => ({ ...g }));
29
+ }
30
+ return config;
31
+ }
32
+ /**
33
+ * Loads and merges user configuration from disk into the provided config.
34
+ * Searches for config in multiple locations:
35
+ * 1. OPENCODE_QUOTAS_CONFIG_PATH environment variable
36
+ * 2. Project-level: <directory>/.opencode/quotas.json
37
+ * 3. Global: ~/.opencode/quotas.json
38
+ *
39
+ * Missing config files are silently ignored (expected behavior).
40
+ * Parse errors are logged as warnings.
41
+ * Returns the updated config.
42
+ */
43
+ static async loadFromDisk(directory, config) {
44
+ const result = { ...config };
45
+ // Build list of config paths to try (in priority order)
46
+ const configPaths = [];
47
+ // 1. Environment variable (highest priority)
48
+ const envConfigPath = process.env.OPENCODE_QUOTAS_CONFIG_PATH;
49
+ if (envConfigPath) {
50
+ configPaths.push(envConfigPath);
51
+ }
52
+ // 2. Project-level config
53
+ configPaths.push(join(directory, ".opencode", "quotas.json"));
54
+ // 3. Global config in home directory
55
+ const homeDir = homedir();
56
+ if (homeDir) {
57
+ configPaths.push(join(homeDir, ".opencode", "quotas.json"));
58
+ }
59
+ // Try each config path until one succeeds
60
+ for (const configPath of configPaths) {
61
+ // Skip if file doesn't exist (expected, not an error)
62
+ if (!existsSync(configPath)) {
63
+ logger.debug("init:config_not_found", { configPath });
64
+ continue;
65
+ }
66
+ try {
67
+ const rawConfig = await readFile(configPath, "utf-8");
68
+ const userConfig = JSON.parse(rawConfig);
69
+ ConfigLoader.mergeUserConfig(result, userConfig);
70
+ logger.debug("init:config_loaded", { configPath, debug: result.debug });
71
+ // Successfully loaded config, stop searching
72
+ break;
73
+ }
74
+ catch (e) {
75
+ // File exists but failed to parse - this is a real error
76
+ const isParseError = e instanceof SyntaxError;
77
+ if (isParseError) {
78
+ logger.warn("init:config_parse_failed", {
79
+ configPath,
80
+ error: e instanceof Error ? e.message : String(e)
81
+ });
82
+ }
83
+ else {
84
+ // Other read errors (permissions, etc.)
85
+ logger.warn("init:config_read_failed", {
86
+ configPath,
87
+ error: e instanceof Error ? e.message : String(e)
88
+ });
89
+ }
90
+ // Continue to next config path
91
+ }
92
+ }
93
+ // Validate and normalize config values
94
+ ConfigLoader.validateConfig(result);
95
+ return result;
96
+ }
97
+ /**
98
+ * Merges user configuration into the target config.
99
+ */
100
+ static mergeUserConfig(target, userConfig) {
101
+ if (userConfig.debug !== undefined) {
102
+ target.debug = userConfig.debug;
103
+ logger.setDebug(!!target.debug);
104
+ }
105
+ if (userConfig.enableExperimentalGithub !== undefined) {
106
+ target.enableExperimentalGithub = userConfig.enableExperimentalGithub;
107
+ }
108
+ if (userConfig.footer !== undefined) {
109
+ target.footer = userConfig.footer;
110
+ }
111
+ if (userConfig.showFooterTitle !== undefined) {
112
+ target.showFooterTitle = userConfig.showFooterTitle;
113
+ }
114
+ if (userConfig.progressBar) {
115
+ target.progressBar = { ...target.progressBar, ...userConfig.progressBar };
116
+ }
117
+ if (userConfig.table) {
118
+ target.table = { ...target.table, ...userConfig.table };
119
+ }
120
+ if (userConfig.disabled) {
121
+ target.disabled = [...userConfig.disabled];
122
+ }
123
+ if (userConfig.filterByCurrentModel !== undefined) {
124
+ target.filterByCurrentModel = userConfig.filterByCurrentModel;
125
+ }
126
+ if (userConfig.showUnaggregated !== undefined) {
127
+ target.showUnaggregated = userConfig.showUnaggregated;
128
+ }
129
+ if (userConfig.aggregatedGroups) {
130
+ target.aggregatedGroups = userConfig.aggregatedGroups.map(g => ({
131
+ ...g,
132
+ sources: g.sources ? [...g.sources] : undefined,
133
+ patterns: g.patterns ? [...g.patterns] : undefined
134
+ }));
135
+ }
136
+ if (userConfig.historyMaxAgeHours !== undefined) {
137
+ target.historyMaxAgeHours = userConfig.historyMaxAgeHours;
138
+ }
139
+ if (userConfig.historyResetThreshold !== undefined) {
140
+ target.historyResetThreshold = userConfig.historyResetThreshold;
141
+ }
142
+ if (userConfig.predictionShortWindowMinutes !== undefined) {
143
+ target.predictionShortWindowMinutes = userConfig.predictionShortWindowMinutes;
144
+ }
145
+ if (userConfig.pollingInterval !== undefined) {
146
+ target.pollingInterval = userConfig.pollingInterval;
147
+ }
148
+ }
149
+ /**
150
+ * Validates and normalizes configuration values.
151
+ */
152
+ static validateConfig(config) {
153
+ // Handle pollingInterval from user config
154
+ const validated = validatePollingInterval(config.pollingInterval);
155
+ if (validated === null) {
156
+ console.warn('[QuotaService] pollingInterval is invalid, using default');
157
+ config.pollingInterval = DEFAULT_CONFIG.pollingInterval;
158
+ }
159
+ else if (validated < 10_000) {
160
+ console.warn('[QuotaService] pollingInterval below 10s is not recommended');
161
+ config.pollingInterval = Math.max(validated, 1_000);
162
+ }
163
+ else {
164
+ config.pollingInterval = validated;
165
+ }
166
+ }
167
+ }
168
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/services/config-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type IHistoryService, type HistoryPoint, type QuotaData } from "../interfaces";
2
+ export declare class HistoryService implements IHistoryService {
3
+ private static readonly CURRENT_VERSION;
4
+ /**
5
+ * Threshold for detecting a quota reset.
6
+ * If current usage drops by more than this percentage of the limit compared to
7
+ * the last recorded usage, we consider it a reset.
8
+ *
9
+ * Example: If limit is 100 and last usage was 80, a drop to below 60 (80 - 20)
10
+ * would trigger a reset detection.
11
+ */
12
+ private static readonly RESET_THRESHOLD_PERCENT;
13
+ private historyPath;
14
+ private data;
15
+ private maxWindowMs;
16
+ private resetThresholdPercent;
17
+ private saveTimeout;
18
+ constructor(customPath?: string);
19
+ init(): Promise<void>;
20
+ append(snapshot: QuotaData[]): Promise<void>;
21
+ /**
22
+ * Detects if a quota has reset by checking if usage dropped significantly.
23
+ * A reset is detected when:
24
+ * 1. The quota has a valid limit (not unlimited)
25
+ * 2. Current usage is significantly lower than the last recorded usage
26
+ * 3. The drop exceeds the threshold (default: 20% of the limit)
27
+ */
28
+ private detectReset;
29
+ getHistory(quotaId: string, windowMs: number): HistoryPoint[];
30
+ setMaxAge(hours: number): void;
31
+ setResetThreshold(percent: number): void;
32
+ pruneAll(): Promise<void>;
33
+ private save;
34
+ /**
35
+ * Immediately write the current data payload to disk (used in tests and migrations).
36
+ */
37
+ private flushSave;
38
+ }
39
+ //# sourceMappingURL=history-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-service.d.ts","sourceRoot":"","sources":["../../src/services/history-service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAGxF,qBAAa,cAAe,YAAW,eAAe;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAK;IAE5C;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAM;IAErD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,IAAI,CAAsC;IAClD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,qBAAqB,CAAkD;IAC/E,OAAO,CAAC,WAAW,CAA8C;gBAErD,UAAU,CAAC,EAAE,MAAM;IAIzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDrB,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDlD;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAwBnB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE;IAO7D,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI9B,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB/B,OAAO,CAAC,IAAI;IAiBZ;;OAEG;YACW,SAAS;CAc1B"}