@mdrv/opencode-quota 262.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +189 -0
  3. package/bin/copilot-quota.ts +374 -0
  4. package/bin/glm-quota.ts +467 -0
  5. package/bin/install.js +439 -0
  6. package/bin/kimi-quota.ts +314 -0
  7. package/dist/bin/copilot-quota.d.ts +8 -0
  8. package/dist/bin/copilot-quota.d.ts.map +1 -0
  9. package/dist/bin/copilot-quota.js +298 -0
  10. package/dist/bin/copilot-quota.js.map +1 -0
  11. package/dist/bin/glm-quota.d.ts +8 -0
  12. package/dist/bin/glm-quota.d.ts.map +1 -0
  13. package/dist/bin/glm-quota.js +367 -0
  14. package/dist/bin/glm-quota.js.map +1 -0
  15. package/dist/bin/kimi-quota.d.ts +3 -0
  16. package/dist/bin/kimi-quota.d.ts.map +1 -0
  17. package/dist/bin/kimi-quota.js +241 -0
  18. package/dist/bin/kimi-quota.js.map +1 -0
  19. package/dist/src/api/client.d.ts +76 -0
  20. package/dist/src/api/client.d.ts.map +1 -0
  21. package/dist/src/api/client.js +203 -0
  22. package/dist/src/api/client.js.map +1 -0
  23. package/dist/src/api/endpoints.d.ts +22 -0
  24. package/dist/src/api/endpoints.d.ts.map +1 -0
  25. package/dist/src/api/endpoints.js +41 -0
  26. package/dist/src/api/endpoints.js.map +1 -0
  27. package/dist/src/api/platforms.d.ts +20 -0
  28. package/dist/src/api/platforms.d.ts.map +1 -0
  29. package/dist/src/api/platforms.js +38 -0
  30. package/dist/src/api/platforms.js.map +1 -0
  31. package/dist/src/index.d.ts +10 -0
  32. package/dist/src/index.d.ts.map +1 -0
  33. package/dist/src/index.js +723 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/shared/logging.d.ts +7 -0
  36. package/dist/src/shared/logging.d.ts.map +1 -0
  37. package/dist/src/shared/logging.js +29 -0
  38. package/dist/src/shared/logging.js.map +1 -0
  39. package/dist/src/utils/box-constants.d.ts +43 -0
  40. package/dist/src/utils/box-constants.d.ts.map +1 -0
  41. package/dist/src/utils/box-constants.js +43 -0
  42. package/dist/src/utils/box-constants.js.map +1 -0
  43. package/dist/src/utils/date-formatter.d.ts +17 -0
  44. package/dist/src/utils/date-formatter.d.ts.map +1 -0
  45. package/dist/src/utils/date-formatter.js +33 -0
  46. package/dist/src/utils/date-formatter.js.map +1 -0
  47. package/dist/src/utils/error-formatter.d.ts +17 -0
  48. package/dist/src/utils/error-formatter.d.ts.map +1 -0
  49. package/dist/src/utils/error-formatter.js +60 -0
  50. package/dist/src/utils/error-formatter.js.map +1 -0
  51. package/dist/src/utils/progress-bar.d.ts +35 -0
  52. package/dist/src/utils/progress-bar.d.ts.map +1 -0
  53. package/dist/src/utils/progress-bar.js +43 -0
  54. package/dist/src/utils/progress-bar.js.map +1 -0
  55. package/dist/src/utils/reset-timer.d.ts +11 -0
  56. package/dist/src/utils/reset-timer.d.ts.map +1 -0
  57. package/dist/src/utils/reset-timer.js +32 -0
  58. package/dist/src/utils/reset-timer.js.map +1 -0
  59. package/dist/src/utils/time-window.d.ts +30 -0
  60. package/dist/src/utils/time-window.d.ts.map +1 -0
  61. package/dist/src/utils/time-window.js +34 -0
  62. package/dist/src/utils/time-window.js.map +1 -0
  63. package/dist/tests/error-handling/api-errors.test.d.ts +7 -0
  64. package/dist/tests/error-handling/api-errors.test.d.ts.map +1 -0
  65. package/dist/tests/error-handling/api-errors.test.js +110 -0
  66. package/dist/tests/error-handling/api-errors.test.js.map +1 -0
  67. package/dist/tests/error-handling/auth-errors.test.d.ts +7 -0
  68. package/dist/tests/error-handling/auth-errors.test.d.ts.map +1 -0
  69. package/dist/tests/error-handling/auth-errors.test.js +110 -0
  70. package/dist/tests/error-handling/auth-errors.test.js.map +1 -0
  71. package/dist/tests/error-handling/network-errors.test.d.ts +7 -0
  72. package/dist/tests/error-handling/network-errors.test.d.ts.map +1 -0
  73. package/dist/tests/error-handling/network-errors.test.js +94 -0
  74. package/dist/tests/error-handling/network-errors.test.js.map +1 -0
  75. package/dist/tests/error-handling/parse-errors.test.d.ts +7 -0
  76. package/dist/tests/error-handling/parse-errors.test.d.ts.map +1 -0
  77. package/dist/tests/error-handling/parse-errors.test.js +87 -0
  78. package/dist/tests/error-handling/parse-errors.test.js.map +1 -0
  79. package/dist/tests/error-handling/token-sanitization.test.d.ts +2 -0
  80. package/dist/tests/error-handling/token-sanitization.test.d.ts.map +1 -0
  81. package/dist/tests/error-handling/token-sanitization.test.js +59 -0
  82. package/dist/tests/error-handling/token-sanitization.test.js.map +1 -0
  83. package/dist/tests/functional/date-formatter.test.d.ts +5 -0
  84. package/dist/tests/functional/date-formatter.test.d.ts.map +1 -0
  85. package/dist/tests/functional/date-formatter.test.js +46 -0
  86. package/dist/tests/functional/date-formatter.test.js.map +1 -0
  87. package/dist/tests/functional/progress-bar.test.d.ts +5 -0
  88. package/dist/tests/functional/progress-bar.test.d.ts.map +1 -0
  89. package/dist/tests/functional/progress-bar.test.js +82 -0
  90. package/dist/tests/functional/progress-bar.test.js.map +1 -0
  91. package/dist/tests/functional/reset-timer.test.d.ts +6 -0
  92. package/dist/tests/functional/reset-timer.test.d.ts.map +1 -0
  93. package/dist/tests/functional/reset-timer.test.js +67 -0
  94. package/dist/tests/functional/reset-timer.test.js.map +1 -0
  95. package/dist/tests/functional/time-window.test.d.ts +5 -0
  96. package/dist/tests/functional/time-window.test.d.ts.map +1 -0
  97. package/dist/tests/functional/time-window.test.js +46 -0
  98. package/dist/tests/functional/time-window.test.js.map +1 -0
  99. package/dist/tests/integration/box-alignment.test.d.ts +8 -0
  100. package/dist/tests/integration/box-alignment.test.d.ts.map +1 -0
  101. package/dist/tests/integration/box-alignment.test.js +238 -0
  102. package/dist/tests/integration/box-alignment.test.js.map +1 -0
  103. package/dist/tests/integration/error-handling.test.d.ts +2 -0
  104. package/dist/tests/integration/error-handling.test.d.ts.map +1 -0
  105. package/dist/tests/integration/error-handling.test.js +36 -0
  106. package/dist/tests/integration/error-handling.test.js.map +1 -0
  107. package/dist/tests/integration/installer-config.test.d.ts +2 -0
  108. package/dist/tests/integration/installer-config.test.d.ts.map +1 -0
  109. package/dist/tests/integration/installer-config.test.js +65 -0
  110. package/dist/tests/integration/installer-config.test.js.map +1 -0
  111. package/dist/tests/integration/plugin-catch-block.test.d.ts +2 -0
  112. package/dist/tests/integration/plugin-catch-block.test.d.ts.map +1 -0
  113. package/dist/tests/integration/plugin-catch-block.test.js +134 -0
  114. package/dist/tests/integration/plugin-catch-block.test.js.map +1 -0
  115. package/dist/tests/integration/reset-time-display.test.d.ts +6 -0
  116. package/dist/tests/integration/reset-time-display.test.d.ts.map +1 -0
  117. package/dist/tests/integration/reset-time-display.test.js +138 -0
  118. package/dist/tests/integration/reset-time-display.test.js.map +1 -0
  119. package/dist/tests/module/http-client.test.d.ts +2 -0
  120. package/dist/tests/module/http-client.test.d.ts.map +1 -0
  121. package/dist/tests/module/http-client.test.js +49 -0
  122. package/dist/tests/module/http-client.test.js.map +1 -0
  123. package/dist/tests/module/platform-detection.test.d.ts +5 -0
  124. package/dist/tests/module/platform-detection.test.d.ts.map +1 -0
  125. package/dist/tests/module/platform-detection.test.js +48 -0
  126. package/dist/tests/module/platform-detection.test.js.map +1 -0
  127. package/integration/agents/copilot-quota-exec.md +20 -0
  128. package/integration/agents/glm-quota-exec.md +20 -0
  129. package/integration/command/copilot_quota.md +6 -0
  130. package/integration/command/glm_quota.md +6 -0
  131. package/integration/skills/copilot-quota/SKILL.md +11 -0
  132. package/integration/skills/glm-quota/SKILL.md +11 -0
  133. package/package.json +69 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Functional tests for progress bar module
3
+ */
4
+ import assert from 'node:assert';
5
+ import { describe, test } from 'node:test';
6
+ import { createProgressBar, formatPercentage, formatProgressLine } from '../../src/utils/progress-bar.js';
7
+ describe('createProgressBar', () => {
8
+ test('creates full bar at 100%', () => {
9
+ const result = createProgressBar(100);
10
+ assert.strictEqual(result.length, 30);
11
+ assert.ok(result.includes('#'));
12
+ assert.ok(!result.includes('-'));
13
+ });
14
+ test('creates empty bar at 0%', () => {
15
+ const result = createProgressBar(0);
16
+ assert.strictEqual(result.length, 30);
17
+ assert.ok(!result.includes('#'));
18
+ assert.ok(result.includes('-'));
19
+ });
20
+ test('creates half-filled bar at 50%', () => {
21
+ const result = createProgressBar(50);
22
+ assert.strictEqual(result.length, 30);
23
+ const filledCount = (result.match(/#/g) || []).length;
24
+ const emptyCount = (result.match(/-/g) || []).length;
25
+ assert.strictEqual(filledCount, 15);
26
+ assert.strictEqual(emptyCount, 15);
27
+ });
28
+ test('clamps percentage to 100', () => {
29
+ const result = createProgressBar(150);
30
+ assert.strictEqual(result.length, 30);
31
+ assert.ok(result.includes('#'));
32
+ assert.ok(!result.includes('-'));
33
+ });
34
+ test('clamps percentage to 0', () => {
35
+ const result = createProgressBar(-50);
36
+ assert.strictEqual(result.length, 30);
37
+ assert.ok(!result.includes('#'));
38
+ assert.ok(result.includes('-'));
39
+ });
40
+ test('uses custom width', () => {
41
+ const result = createProgressBar(50, { width: 10 });
42
+ assert.strictEqual(result.length, 10);
43
+ });
44
+ test('uses custom characters', () => {
45
+ const result = createProgressBar(50, {
46
+ filledChar: '■',
47
+ emptyChar: '□',
48
+ });
49
+ assert.ok(result.includes('■'));
50
+ assert.ok(result.includes('□'));
51
+ assert.ok(!result.includes('#'));
52
+ assert.ok(!result.includes('-'));
53
+ });
54
+ });
55
+ describe('formatPercentage', () => {
56
+ test('formats with one decimal', () => {
57
+ assert.strictEqual(formatPercentage(40.5), '40.5%');
58
+ });
59
+ test('formats whole number', () => {
60
+ assert.strictEqual(formatPercentage(100), '100.0%');
61
+ });
62
+ test('formats zero', () => {
63
+ assert.strictEqual(formatPercentage(0), '0.0%');
64
+ });
65
+ test('formats with custom decimals', () => {
66
+ assert.strictEqual(formatPercentage(33.333, 2), '33.33%');
67
+ });
68
+ });
69
+ describe('formatProgressLine', () => {
70
+ test('formats complete line', () => {
71
+ const result = formatProgressLine('Test Label', 75);
72
+ assert.ok(result.includes('Test Label'));
73
+ assert.ok(result.includes('75.0%'));
74
+ assert.ok(result.includes('#'));
75
+ assert.ok(result.includes('-'));
76
+ });
77
+ test('pads label to 20 characters', () => {
78
+ const result = formatProgressLine('Short', 50);
79
+ assert.ok(result.startsWith('Short'));
80
+ });
81
+ });
82
+ //# sourceMappingURL=progress-bar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress-bar.test.js","sourceRoot":"","sources":["../../../tests/functional/progress-bar.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for reset timer utility
3
+ * Test formatTimeUntilReset() function following TDD
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=reset-timer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-timer.test.d.ts","sourceRoot":"","sources":["../../../tests/functional/reset-timer.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Tests for reset timer utility
3
+ * Test formatTimeUntilReset() function following TDD
4
+ */
5
+ import assert from 'node:assert';
6
+ import { afterEach, beforeEach, describe, test } from 'node:test';
7
+ import { formatTimeUntilReset } from '../../src/utils/reset-timer.js';
8
+ // ============================================================================
9
+ // TESTS FOR formatTimeUntilReset()
10
+ // ============================================================================
11
+ describe('formatTimeUntilReset', () => {
12
+ const fixedNow = 1737763200000;
13
+ const originalNow = Date.now;
14
+ beforeEach(() => {
15
+ Date.now = () => fixedNow;
16
+ });
17
+ afterEach(() => {
18
+ Date.now = originalNow;
19
+ });
20
+ test('should return empty string for null timestamp', () => {
21
+ const result = formatTimeUntilReset(null);
22
+ assert.strictEqual(result, '');
23
+ });
24
+ test('should return empty string for undefined timestamp', () => {
25
+ const result = formatTimeUntilReset(undefined);
26
+ assert.strictEqual(result, '');
27
+ });
28
+ test('should format 1 hour 30 minutes', () => {
29
+ // Current time + 1.5 hours
30
+ const resetTime = fixedNow + (90 * 60 * 1000);
31
+ const result = formatTimeUntilReset(resetTime);
32
+ assert.strictEqual(result, 'Resets in 1 hours 30 minutes');
33
+ });
34
+ test('should format 4 hours 42 minutes', () => {
35
+ // Current time + 4h 42m
36
+ const resetTime = fixedNow + (4 * 60 * 60 * 1000) + (42 * 60 * 1000);
37
+ const result = formatTimeUntilReset(resetTime);
38
+ assert.strictEqual(result, 'Resets in 4 hours 42 minutes');
39
+ });
40
+ test('should format only hours when minutes is 0', () => {
41
+ // Current time + 5 hours
42
+ const resetTime = fixedNow + (5 * 60 * 60 * 1000);
43
+ const result = formatTimeUntilReset(resetTime);
44
+ assert.strictEqual(result, 'Resets in 5 hours 0 minutes');
45
+ });
46
+ test('should format only minutes when hours is 0', () => {
47
+ // Current time + 45 minutes
48
+ const resetTime = fixedNow + (45 * 60 * 1000);
49
+ const result = formatTimeUntilReset(resetTime);
50
+ assert.strictEqual(result, 'Resets in 0 hours 45 minutes');
51
+ });
52
+ test('should return empty string for past timestamp', () => {
53
+ // Current time - 1 hour (already passed)
54
+ const resetTime = fixedNow - (60 * 60 * 1000);
55
+ const result = formatTimeUntilReset(resetTime);
56
+ assert.strictEqual(result, '');
57
+ });
58
+ test('should return empty string for invalid timestamp', () => {
59
+ const result = formatTimeUntilReset(-1);
60
+ assert.strictEqual(result, '');
61
+ });
62
+ test('should return empty string for non-numeric timestamp', () => {
63
+ const result = formatTimeUntilReset('invalid');
64
+ assert.strictEqual(result, '');
65
+ });
66
+ });
67
+ //# sourceMappingURL=reset-timer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-timer.test.js","sourceRoot":"","sources":["../../../tests/functional/reset-timer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Functional tests for time window module
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=time-window.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.test.d.ts","sourceRoot":"","sources":["../../../tests/functional/time-window.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Functional tests for time window module
3
+ */
4
+ import assert from 'node:assert';
5
+ import { describe, test } from 'node:test';
6
+ import { getTimeWindow, getTimeWindowQueryParams } from '../../src/utils/time-window.js';
7
+ describe('getTimeWindow', () => {
8
+ test('returns 24-hour rolling window', () => {
9
+ // Mock Date to 2026-01-18 14:30:00
10
+ const mockNow = new Date(2026, 0, 18, 14, 30, 0, 0);
11
+ const { startTime, endTime } = getTimeWindow(mockNow);
12
+ assert.strictEqual(startTime, '2026-01-17 14:00:00');
13
+ assert.strictEqual(endTime, '2026-01-18 14:59:59');
14
+ });
15
+ test('start is yesterday at same hour', () => {
16
+ const mockNow = new Date(2026, 5, 15, 10, 0, 0, 0);
17
+ const { startTime, endTime } = getTimeWindow(mockNow);
18
+ assert.ok(startTime.endsWith('10:00:00'));
19
+ assert.ok(startTime.startsWith('2026-06-14'));
20
+ });
21
+ test('end is today at 59:59:999', () => {
22
+ const mockNow = new Date(2026, 5, 15, 10, 0, 0, 0);
23
+ const { startTime, endTime } = getTimeWindow(mockNow);
24
+ assert.ok(endTime.endsWith('10:59:59'));
25
+ assert.ok(endTime.startsWith('2026-06-15'));
26
+ });
27
+ });
28
+ describe('getTimeWindowQueryParams', () => {
29
+ test('returns URL-encoded query string', () => {
30
+ const mockNow = new Date(2026, 0, 18, 14, 30, 0, 0);
31
+ const result = getTimeWindowQueryParams(mockNow);
32
+ assert.ok(result.startsWith('startTime='));
33
+ assert.ok(result.includes('&endTime='));
34
+ assert.ok(result.includes('2026-01-17'));
35
+ assert.ok(result.includes('2026-01-18'));
36
+ });
37
+ test('contains encoded spaces and colons', () => {
38
+ const mockNow = new Date(2026, 0, 18, 14, 30, 0, 0);
39
+ const result = getTimeWindowQueryParams(mockNow);
40
+ // Spaces should be encoded as %20
41
+ assert.ok(result.includes('%20'));
42
+ // Colons should be encoded as %3A
43
+ assert.ok(result.includes('%3A'));
44
+ });
45
+ });
46
+ //# sourceMappingURL=time-window.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.test.js","sourceRoot":"","sources":["../../../tests/functional/time-window.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Box Alignment Validation Tests
3
+ *
4
+ * Ensures all output lines maintain consistent 60-character width
5
+ * and proper box drawing alignment.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=box-alignment.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box-alignment.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/box-alignment.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Box Alignment Validation Tests
3
+ *
4
+ * Ensures all output lines maintain consistent 60-character width
5
+ * and proper box drawing alignment.
6
+ */
7
+ import * as assert from 'node:assert';
8
+ import { describe, it } from 'node:test';
9
+ // Mock data for testing
10
+ const mockQuotaData = {
11
+ limits: [
12
+ {
13
+ type: 'MCP usage(1 Month)',
14
+ percentage: 1.0,
15
+ currentValue: 4,
16
+ total: 1000,
17
+ usageDetails: [
18
+ { modelCode: 'search-prime', usage: 0 },
19
+ { modelCode: 'web-reader', usage: 0 },
20
+ { modelCode: 'zread', usage: 4 },
21
+ ],
22
+ },
23
+ {
24
+ type: 'Token usage(5 Hour)',
25
+ percentage: 1.0,
26
+ nextResetTime: Date.now() + 5 * 60 * 60 * 1000, // 5 hours from now
27
+ },
28
+ ],
29
+ };
30
+ const mockModelData = {
31
+ totalUsage: {
32
+ totalModelCallCount: 0,
33
+ totalTokensUsage: 0,
34
+ },
35
+ };
36
+ const mockToolData = {
37
+ totalUsage: {
38
+ totalNetworkSearchCount: 0,
39
+ totalWebReadMcpCount: 0,
40
+ totalZreadMcpCount: 4,
41
+ },
42
+ };
43
+ /**
44
+ * Get display width of a string (handles Unicode)
45
+ */
46
+ function getDisplayWidth(text) {
47
+ let width = 0;
48
+ for (let i = 0; i < text.length; i += 1) {
49
+ const codePoint = text.codePointAt(i);
50
+ if (codePoint === undefined) {
51
+ continue;
52
+ }
53
+ if (codePoint > 0xffff) {
54
+ i += 1;
55
+ }
56
+ if (isControlCodePoint(codePoint) || isZeroWidthCodePoint(codePoint)) {
57
+ continue;
58
+ }
59
+ width += isEmojiCodePoint(codePoint) || isFullWidthCodePoint(codePoint) ? 2 : 1;
60
+ }
61
+ return width;
62
+ }
63
+ function isControlCodePoint(codePoint) {
64
+ return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
65
+ }
66
+ function isZeroWidthCodePoint(codePoint) {
67
+ return (codePoint === 0x200d
68
+ || codePoint === 0xfe0f
69
+ || (codePoint >= 0xfe00 && codePoint <= 0xfe0f));
70
+ }
71
+ function isEmojiCodePoint(codePoint) {
72
+ return ((codePoint >= 0x1f300 && codePoint <= 0x1f5ff)
73
+ || (codePoint >= 0x1f600 && codePoint <= 0x1f64f)
74
+ || (codePoint >= 0x1f680 && codePoint <= 0x1f6ff)
75
+ || (codePoint >= 0x1f700 && codePoint <= 0x1f77f)
76
+ || (codePoint >= 0x1f780 && codePoint <= 0x1f7ff)
77
+ || (codePoint >= 0x1f800 && codePoint <= 0x1f8ff)
78
+ || (codePoint >= 0x1f900 && codePoint <= 0x1f9ff)
79
+ || (codePoint >= 0x1fa00 && codePoint <= 0x1faff)
80
+ || (codePoint >= 0x2600 && codePoint <= 0x26ff)
81
+ || (codePoint >= 0x2700 && codePoint <= 0x27bf));
82
+ }
83
+ function isFullWidthCodePoint(codePoint) {
84
+ return (codePoint >= 0x1100 && (codePoint <= 0x115f
85
+ || codePoint === 0x2329
86
+ || codePoint === 0x232a
87
+ || (codePoint >= 0x2e80 && codePoint <= 0x3247 && codePoint !== 0x303f)
88
+ || (codePoint >= 0x3250 && codePoint <= 0x4dbf)
89
+ || (codePoint >= 0x4e00 && codePoint <= 0xa4c6)
90
+ || (codePoint >= 0xa960 && codePoint <= 0xa97c)
91
+ || (codePoint >= 0xac00 && codePoint <= 0xd7a3)
92
+ || (codePoint >= 0xf900 && codePoint <= 0xfaff)
93
+ || (codePoint >= 0xfe10 && codePoint <= 0xfe19)
94
+ || (codePoint >= 0xfe30 && codePoint <= 0xfe6b)
95
+ || (codePoint >= 0xff01 && codePoint <= 0xff60)
96
+ || (codePoint >= 0xffe0 && codePoint <= 0xffe6)
97
+ || (codePoint >= 0x1b000 && codePoint <= 0x1b001)
98
+ || (codePoint >= 0x1f200 && codePoint <= 0x1f251)
99
+ || (codePoint >= 0x20000 && codePoint <= 0x3fffd)));
100
+ }
101
+ /**
102
+ * Generate test output using the plugin
103
+ */
104
+ async function generateTestOutput() {
105
+ // Import the plugin dynamically
106
+ const plugin = await import('../../src/index.js');
107
+ const instance = await plugin.GlmQuotaPlugin(null);
108
+ // The tool.execute function signature requires args and context
109
+ // We pass empty objects since our tool doesn't use them
110
+ const execute = instance.tool.glm_quota.execute;
111
+ const result = await execute({}, {});
112
+ return result;
113
+ }
114
+ describe('Box Alignment Validation', () => {
115
+ it('all output lines are exactly 60 characters wide', async () => {
116
+ const output = await generateTestOutput();
117
+ const lines = output.split('\n').filter(line => line.length > 0);
118
+ for (const line of lines) {
119
+ const displayWidth = getDisplayWidth(line);
120
+ assert.strictEqual(displayWidth, 60, `Line has incorrect width (${displayWidth}): "${line}"`);
121
+ }
122
+ // Ensure we have output
123
+ assert.ok(lines.length > 0, 'Output should contain lines');
124
+ });
125
+ it('top border matches bottom border width', async () => {
126
+ const output = await generateTestOutput();
127
+ const lines = output.split('\n').filter(line => line.length > 0);
128
+ const topBorder = lines[0];
129
+ const bottomBorder = lines[lines.length - 1];
130
+ assert.ok(topBorder.startsWith('╔'), 'First line should be top border');
131
+ assert.ok(bottomBorder.startsWith('╚'), 'Last line should be bottom border');
132
+ assert.strictEqual(getDisplayWidth(topBorder), getDisplayWidth(bottomBorder), 'Top and bottom borders should have same width');
133
+ });
134
+ it('all section dividers are 60 characters', async () => {
135
+ const output = await generateTestOutput();
136
+ const lines = output.split('\n').filter(line => line.length > 0);
137
+ const dividers = lines.filter(line => line.startsWith('╠') || line.startsWith('╟'));
138
+ for (const divider of dividers) {
139
+ const displayWidth = getDisplayWidth(divider);
140
+ assert.strictEqual(displayWidth, 60, `Divider has incorrect width: "${divider}"`);
141
+ }
142
+ // Dividers are optional (may not exist in error message output)
143
+ // Just ensure if they exist, they're correctly sized
144
+ });
145
+ it('content lines maintain consistent padding', async () => {
146
+ const output = await generateTestOutput();
147
+ const lines = output.split('\n').filter(line => line.length > 0);
148
+ const contentLines = lines.filter(line => line.startsWith('║') && !line.startsWith('╠') && !line.startsWith('╟'));
149
+ for (const line of contentLines) {
150
+ assert.ok(line.startsWith('║'), 'Content line should start with ║');
151
+ assert.ok(line.endsWith('║'), 'Content line should end with ║');
152
+ const displayWidth = getDisplayWidth(line);
153
+ assert.strictEqual(displayWidth, 60, `Content line has incorrect width: "${line}"`);
154
+ }
155
+ // Ensure we have content lines
156
+ assert.ok(contentLines.length > 0, 'Output should contain content lines');
157
+ });
158
+ it('progress bar lines fit within box width', async () => {
159
+ const output = await generateTestOutput();
160
+ const lines = output.split('\n').filter(line => line.length > 0);
161
+ const progressLines = lines.filter(line => line.includes('[') && line.includes(']'));
162
+ for (const line of progressLines) {
163
+ const displayWidth = getDisplayWidth(line);
164
+ assert.strictEqual(displayWidth, 60, `Progress bar line has incorrect width: "${line}"`);
165
+ }
166
+ // Progress bars are optional (may not exist in error message output)
167
+ // Just ensure if they exist, they're correctly sized
168
+ });
169
+ it('handles empty sections with proper alignment', async () => {
170
+ // This test uses actual output which might have data
171
+ // We're verifying that even with varying data, alignment is maintained
172
+ const output = await generateTestOutput();
173
+ const lines = output.split('\n').filter(line => line.length > 0);
174
+ // All lines should be exactly 60 characters regardless of content
175
+ for (const line of lines) {
176
+ const displayWidth = getDisplayWidth(line);
177
+ assert.strictEqual(displayWidth, 60, `Line with varying content has incorrect width: "${line}"`);
178
+ }
179
+ });
180
+ it('box structure is complete and valid', async () => {
181
+ const output = await generateTestOutput();
182
+ const lines = output.split('\n').filter(line => line.length > 0);
183
+ // Check box structure
184
+ assert.ok(lines[0].startsWith('╔'), 'Should start with top border');
185
+ assert.ok(lines[lines.length - 1].startsWith('╚'), 'Should end with bottom border');
186
+ // Count section markers (only check if they exist - error messages have fewer/no dividers)
187
+ const sectionDividers = lines.filter(line => line.startsWith('╠')).length;
188
+ const subsectionDividers = lines.filter(line => line.startsWith('╟')).length;
189
+ // If we have dividers, they should be valid (but they're optional for error messages)
190
+ // Just ensure the box is complete with top and bottom borders
191
+ assert.ok(lines.length >= 3, 'Should have at least top border, content, and bottom border');
192
+ });
193
+ it('unicode box-drawing characters render correctly', async () => {
194
+ const output = await generateTestOutput();
195
+ // Ensure all expected box-drawing characters are present
196
+ assert.ok(output.includes('╔'), 'Should contain top-left corner');
197
+ assert.ok(output.includes('╗'), 'Should contain top-right corner');
198
+ assert.ok(output.includes('╚'), 'Should contain bottom-left corner');
199
+ assert.ok(output.includes('╝'), 'Should contain bottom-right corner');
200
+ assert.ok(output.includes('║'), 'Should contain vertical lines');
201
+ assert.ok(output.includes('═'), 'Should contain horizontal lines');
202
+ // T-junctions are optional in error output
203
+ if (output.includes('╠') || output.includes('╟')) {
204
+ assert.ok(output.includes('╠'), 'Should contain left T-junction');
205
+ assert.ok(output.includes('╣'), 'Should contain right T-junction');
206
+ assert.ok(output.includes('╟'), 'Should contain left T-junction (dashed)');
207
+ assert.ok(output.includes('╢'), 'Should contain right T-junction (dashed)');
208
+ }
209
+ });
210
+ it('progress bar characters do not break alignment', async () => {
211
+ const output = await generateTestOutput();
212
+ const lines = output.split('\n').filter(line => line.length > 0);
213
+ // Find lines with progress bars (contain █ or ░)
214
+ const progressLines = lines.filter(line => line.includes('█') || line.includes('░'));
215
+ for (const line of progressLines) {
216
+ const displayWidth = getDisplayWidth(line);
217
+ assert.strictEqual(displayWidth, 60, `Progress bar with special chars has incorrect width: "${line}"`);
218
+ }
219
+ // Progress bars are optional in error output
220
+ // Just ensure if they exist, they're correctly aligned
221
+ });
222
+ it('all sections are present in output', async () => {
223
+ const output = await generateTestOutput();
224
+ // Check if this is quota data or error message
225
+ // Both are valid outputs with correct alignment
226
+ const isQuotaData = output.includes('QUOTA LIMITS');
227
+ const isErrorMessage = output.includes('Credentials Not Found');
228
+ assert.ok(isQuotaData || isErrorMessage, 'Output should be either quota data or error message');
229
+ if (isQuotaData) {
230
+ // If we have quota data, verify expected sections
231
+ assert.ok(output.includes('MODEL USAGE'), 'Quota output should contain MODEL USAGE section');
232
+ assert.ok(output.includes('TOOL/MCP USAGE'), 'Quota output should contain TOOL/MCP USAGE section');
233
+ assert.ok(output.includes('Platform:'), 'Quota output should contain platform information');
234
+ assert.ok(output.includes('Period:'), 'Quota output should contain period information');
235
+ }
236
+ });
237
+ });
238
+ //# sourceMappingURL=box-alignment.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"box-alignment.test.js","sourceRoot":"","sources":["../../../tests/integration/box-alignment.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=error-handling.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handling.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/error-handling.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import * as assert from 'node:assert';
2
+ import { describe, test } from 'node:test';
3
+ import { GlmQuotaPlugin } from '../../src/index.js';
4
+ import { BOX_WIDTH } from '../../src/utils/box-constants.js';
5
+ describe('Integration: Error Handling in src/index.ts', () => {
6
+ test('should return boxed error message when Date constructor throws', async () => {
7
+ // Setup credentials to reach queryAllUsage
8
+ process.env.ZAI_API_KEY = 'test-token';
9
+ // Mock Date to throw
10
+ const originalDate = global.Date;
11
+ try {
12
+ class ThrowingDate extends Date {
13
+ constructor(...args) {
14
+ super(...args);
15
+ throw new Error('Date failure');
16
+ }
17
+ }
18
+ global.Date = ThrowingDate;
19
+ // Create plugin instance
20
+ const plugin = await GlmQuotaPlugin({});
21
+ const tool = plugin.tool.glm_quota;
22
+ const result = await tool.execute();
23
+ const lines = result.split('\n');
24
+ assert.ok(result.includes('╔'), 'Output should contain top border');
25
+ assert.ok(result.includes('╚'), 'Output should contain bottom border');
26
+ assert.ok(result.includes('Date failure'), `Output should contain error message. Got: ${result}`);
27
+ assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL, 'Box width should match');
28
+ }
29
+ finally {
30
+ // Cleanup
31
+ global.Date = originalDate;
32
+ delete process.env.ZAI_API_KEY;
33
+ }
34
+ });
35
+ });
36
+ //# sourceMappingURL=error-handling.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handling.test.js","sourceRoot":"","sources":["../../../tests/integration/error-handling.test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAO5D,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC5D,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QACjF,2CAA2C;QAC3C,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,YAAY,CAAA;QAEtC,qBAAqB;QACrB,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAA;QAEhC,IAAI,CAAC;YACJ,MAAM,YAAa,SAAQ,IAAI;gBAC9B,YAAY,GAAG,IAAwC;oBACtD,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;oBACd,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAA;gBAChC,CAAC;aACD;YAED,MAAM,CAAC,IAAI,GAAG,YAA+B,CAAA;YAE7C,yBAAyB;YACzB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAA8B,CAAC,CAAA;YACnE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAK,CAAC,SAAS,CAAA;YAEnC,MAAM,MAAM,GAAG,MAAO,IAAgC,CAAC,OAAO,EAAE,CAAA;YAEhE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAChC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,kCAAkC,CAAC,CAAA;YACnE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,qCAAqC,CAAC,CAAA;YACtE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,6CAA6C,MAAM,EAAE,CAAC,CAAA;YACjG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAA;QAC/E,CAAC;gBAAS,CAAC;YACV,UAAU;YACV,MAAM,CAAC,IAAI,GAAG,YAAY,CAAA;YAC1B,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAA;QAC/B,CAAC;IACF,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=installer-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer-config.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/installer-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,65 @@
1
+ import assert from 'node:assert';
2
+ import { spawnSync } from 'node:child_process';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import { afterEach, describe, test } from 'node:test';
7
+ const PLUGIN_NAME = 'opencode-glm-quota';
8
+ const createdHomes = [];
9
+ function createTempHome() {
10
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'glm-quota-installer-'));
11
+ createdHomes.push(tempHome);
12
+ return tempHome;
13
+ }
14
+ function runInstaller(tempHome) {
15
+ const result = spawnSync('node', ['bin/install.js', '--force'], {
16
+ cwd: process.cwd(),
17
+ encoding: 'utf-8',
18
+ env: {
19
+ ...process.env,
20
+ HOME: tempHome,
21
+ USERPROFILE: tempHome,
22
+ },
23
+ });
24
+ assert.strictEqual(result.status, 0, `Installer failed.\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
25
+ }
26
+ function readConfig(tempHome) {
27
+ const configPath = path.join(tempHome, '.config', 'opencode', 'opencode.json');
28
+ assert.ok(fs.existsSync(configPath), `Expected config file to exist: ${configPath}`);
29
+ const content = fs.readFileSync(configPath, 'utf-8');
30
+ return JSON.parse(content);
31
+ }
32
+ afterEach(() => {
33
+ while (createdHomes.length > 0) {
34
+ const tempHome = createdHomes.pop();
35
+ if (!tempHome) {
36
+ continue;
37
+ }
38
+ fs.rmSync(tempHome, { recursive: true, force: true });
39
+ }
40
+ });
41
+ describe('Installer config key handling', () => {
42
+ test('fresh install writes plugin key (not plugins)', () => {
43
+ const tempHome = createTempHome();
44
+ runInstaller(tempHome);
45
+ const config = readConfig(tempHome);
46
+ const pluginArray = config.plugin;
47
+ assert.ok(Array.isArray(pluginArray), 'Expected "plugin" to be an array');
48
+ assert.ok(pluginArray.includes(PLUGIN_NAME), `Expected plugin array to include ${PLUGIN_NAME}`);
49
+ assert.strictEqual(Object.prototype.hasOwnProperty.call(config, 'plugins'), false, 'Expected "plugins" key to be absent');
50
+ });
51
+ test('migrates legacy plugins key into plugin key', () => {
52
+ const tempHome = createTempHome();
53
+ const configDir = path.join(tempHome, '.config', 'opencode');
54
+ fs.mkdirSync(configDir, { recursive: true });
55
+ fs.writeFileSync(path.join(configDir, 'opencode.json'), JSON.stringify({ plugins: ['existing-plugin'] }, null, 2) + '\n');
56
+ runInstaller(tempHome);
57
+ const config = readConfig(tempHome);
58
+ const pluginArray = config.plugin;
59
+ assert.ok(Array.isArray(pluginArray), 'Expected "plugin" to be an array');
60
+ assert.ok(pluginArray.includes('existing-plugin'), 'Expected legacy plugin value to be preserved');
61
+ assert.ok(pluginArray.includes(PLUGIN_NAME), `Expected plugin array to include ${PLUGIN_NAME}`);
62
+ assert.strictEqual(Object.prototype.hasOwnProperty.call(config, 'plugins'), false, 'Expected legacy "plugins" key to be removed');
63
+ });
64
+ });
65
+ //# sourceMappingURL=installer-config.test.js.map