github-issue-tower-defence-management 1.55.0 → 1.56.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 (71) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +23 -0
  3. package/bin/adapter/entry-points/cli/index.js +3 -1
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +5 -0
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +3 -1
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/proxy/RateLimitCache.js +123 -0
  10. package/bin/adapter/proxy/RateLimitCache.js.map +1 -0
  11. package/bin/adapter/proxy/TokenListLoader.js +72 -0
  12. package/bin/adapter/proxy/TokenListLoader.js.map +1 -0
  13. package/bin/adapter/proxy/ensureProxyRunning.js +73 -0
  14. package/bin/adapter/proxy/ensureProxyRunning.js.map +1 -0
  15. package/bin/adapter/proxy/proxyEntry.js +96 -0
  16. package/bin/adapter/proxy/proxyEntry.js.map +1 -0
  17. package/bin/adapter/repositories/NodeLocalCommandRunner.js +10 -4
  18. package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -1
  19. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +35 -0
  20. package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -0
  21. package/bin/domain/entities/ClaudeTokenUsage.js +3 -0
  22. package/bin/domain/entities/ClaudeTokenUsage.js.map +1 -0
  23. package/bin/domain/usecases/StartPreparationUseCase.js +26 -2
  24. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  25. package/bin/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.js +3 -0
  26. package/bin/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.js.map +1 -0
  27. package/package.json +1 -1
  28. package/src/adapter/entry-points/cli/index.ts +5 -0
  29. package/src/adapter/entry-points/cli/projectConfig.ts +13 -0
  30. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +5 -0
  31. package/src/adapter/proxy/RateLimitCache.test.ts +131 -0
  32. package/src/adapter/proxy/RateLimitCache.ts +112 -0
  33. package/src/adapter/proxy/TokenListLoader.test.ts +82 -0
  34. package/src/adapter/proxy/TokenListLoader.ts +35 -0
  35. package/src/adapter/proxy/ensureProxyRunning.test.ts +85 -0
  36. package/src/adapter/proxy/ensureProxyRunning.ts +41 -0
  37. package/src/adapter/proxy/proxyEntry.test.ts +48 -0
  38. package/src/adapter/proxy/proxyEntry.ts +69 -0
  39. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +3 -1
  40. package/src/adapter/repositories/NodeLocalCommandRunner.ts +18 -4
  41. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +127 -0
  42. package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +36 -0
  43. package/src/domain/entities/ClaudeTokenUsage.ts +5 -0
  44. package/src/domain/usecases/StartPreparationUseCase.test.ts +308 -0
  45. package/src/domain/usecases/StartPreparationUseCase.ts +37 -1
  46. package/src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts +7 -0
  47. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +5 -0
  48. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  49. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  50. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  51. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  52. package/types/adapter/proxy/RateLimitCache.d.ts +14 -0
  53. package/types/adapter/proxy/RateLimitCache.d.ts.map +1 -0
  54. package/types/adapter/proxy/TokenListLoader.d.ts +2 -0
  55. package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -0
  56. package/types/adapter/proxy/ensureProxyRunning.d.ts +2 -0
  57. package/types/adapter/proxy/ensureProxyRunning.d.ts.map +1 -0
  58. package/types/adapter/proxy/proxyEntry.d.ts +4 -0
  59. package/types/adapter/proxy/proxyEntry.d.ts.map +1 -0
  60. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +2 -2
  61. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -1
  62. package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts +11 -0
  63. package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -0
  64. package/types/domain/entities/ClaudeTokenUsage.d.ts +6 -0
  65. package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -0
  66. package/types/domain/usecases/StartPreparationUseCase.d.ts +4 -1
  67. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  68. package/types/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.d.ts +7 -0
  69. package/types/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.d.ts.map +1 -0
  70. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +4 -1
  71. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -1
@@ -5,12 +5,18 @@ const child_process_1 = require("child_process");
5
5
  const util_1 = require("util");
6
6
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
7
7
  class NodeLocalCommandRunner {
8
- async runCommand(program, args) {
8
+ async runCommand(program, args, options) {
9
+ const execOptions = {
10
+ encoding: 'utf8',
11
+ };
12
+ if (options?.env) {
13
+ execOptions.env = { ...process.env, ...options.env };
14
+ }
9
15
  try {
10
- const { stdout, stderr } = await execFileAsync(program, args);
16
+ const { stdout, stderr } = await execFileAsync(program, args, execOptions);
11
17
  return {
12
- stdout,
13
- stderr,
18
+ stdout: String(stdout),
19
+ stderr: String(stderr),
14
20
  exitCode: 0,
15
21
  };
16
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NodeLocalCommandRunner.js","sourceRoot":"","sources":["../../../src/adapter/repositories/NodeLocalCommandRunner.ts"],"names":[],"mappings":";;;AACA,iDAAyC;AACzC,+BAAiC;AAEjC,MAAM,aAAa,GAAG,IAAA,gBAAS,EAAC,wBAAQ,CAAC,CAAC;AAE1C,MAAa,sBAAsB;IACjC,KAAK,CAAC,UAAU,CACd,OAAe,EACf,IAAc;QAMd,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC9D,OAAO;gBACL,MAAM;gBACN,MAAM;gBACN,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK;gBACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,QAAQ,IAAI,KAAK;gBACjB,QAAQ,IAAI,KAAK;gBACjB,MAAM,IAAI,KAAK,EACf,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,QAAQ,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBAC1D,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAjCD,wDAiCC"}
1
+ {"version":3,"file":"NodeLocalCommandRunner.js","sourceRoot":"","sources":["../../../src/adapter/repositories/NodeLocalCommandRunner.ts"],"names":[],"mappings":";;;AAIA,iDAAyC;AACzC,+BAAiC;AAEjC,MAAM,aAAa,GAAG,IAAA,gBAAS,EAAC,wBAAQ,CAAC,CAAC;AAE1C,MAAa,sBAAsB;IACjC,KAAK,CAAC,UAAU,CACd,OAAe,EACf,IAAc,EACd,OAAmC;QAMnC,MAAM,WAAW,GAAwC;YACvD,QAAQ,EAAE,MAAM;SACjB,CAAC;QACF,IAAI,OAAO,EAAE,GAAG,EAAE,CAAC;YACjB,WAAW,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAC5C,OAAO,EACP,IAAI,EACJ,WAAW,CACZ,CAAC;YACF,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;gBACtB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;gBACtB,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK;gBACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,QAAQ,IAAI,KAAK;gBACjB,QAAQ,IAAI,KAAK;gBACjB,MAAM,IAAI,KAAK,EACf,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,QAAQ,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBAC1D,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AA5CD,wDA4CC"}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProxyClaudeTokenUsageRepository = void 0;
4
+ const ensureProxyRunning_1 = require("../proxy/ensureProxyRunning");
5
+ const RateLimitCache_1 = require("../proxy/RateLimitCache");
6
+ const TokenListLoader_1 = require("../proxy/TokenListLoader");
7
+ class ProxyClaudeTokenUsageRepository {
8
+ constructor(tokenListJsonPath, port = RateLimitCache_1.PROXY_PORT) {
9
+ this.tokenListJsonPath = tokenListJsonPath;
10
+ this.port = port;
11
+ this.ensureObservable = async () => {
12
+ await (0, ensureProxyRunning_1.ensureProxyRunning)(this.port);
13
+ };
14
+ this.getAvailableTokenUsages = async () => {
15
+ if (this.tokenListJsonPath === null) {
16
+ return [];
17
+ }
18
+ const tokens = (0, TokenListLoader_1.loadTokens)(this.tokenListJsonPath);
19
+ if (tokens === null) {
20
+ return [];
21
+ }
22
+ return tokens.map((token) => {
23
+ const snapshot = (0, RateLimitCache_1.readRateLimit)(token);
24
+ return {
25
+ token,
26
+ fiveHourUtilization: snapshot ? snapshot.fiveHourUtilization : 0,
27
+ blocked: snapshot?.blocked ?? false,
28
+ };
29
+ });
30
+ };
31
+ this.proxyBaseUrl = () => `http://127.0.0.1:${this.port}`;
32
+ }
33
+ }
34
+ exports.ProxyClaudeTokenUsageRepository = ProxyClaudeTokenUsageRepository;
35
+ //# sourceMappingURL=ProxyClaudeTokenUsageRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProxyClaudeTokenUsageRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts"],"names":[],"mappings":";;;AAEA,oEAAiE;AACjE,4DAAoE;AACpE,8DAAsD;AAEtD,MAAa,+BAA+B;IAC1C,YACmB,iBAAgC,EAChC,OAAe,2BAAU;QADzB,sBAAiB,GAAjB,iBAAiB,CAAe;QAChC,SAAI,GAAJ,IAAI,CAAqB;QAG5C,qBAAgB,GAAG,KAAK,IAAmB,EAAE;YAC3C,MAAM,IAAA,uCAAkB,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC,CAAC;QAEF,4BAAuB,GAAG,KAAK,IAAiC,EAAE;YAChE,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBACpC,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,MAAM,GAAG,IAAA,4BAAU,EAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YAClD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC1B,MAAM,QAAQ,GAAG,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;gBACtC,OAAO;oBACL,KAAK;oBACL,mBAAmB,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;oBAChE,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,KAAK;iBACpC,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,iBAAY,GAAG,GAAW,EAAE,CAAC,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC;IAxB1D,CAAC;CAyBL;AA7BD,0EA6BC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ClaudeTokenUsage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClaudeTokenUsage.js","sourceRoot":"","sources":["../../../src/domain/entities/ClaudeTokenUsage.ts"],"names":[],"mappings":""}
@@ -3,11 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StartPreparationUseCase = void 0;
4
4
  const WorkflowStatus_1 = require("../entities/WorkflowStatus");
5
5
  class StartPreparationUseCase {
6
- constructor(projectRepository, issueRepository, claudeRepository, localCommandRunner) {
6
+ constructor(projectRepository, issueRepository, claudeRepository, localCommandRunner, claudeTokenUsageRepository) {
7
7
  this.projectRepository = projectRepository;
8
8
  this.issueRepository = issueRepository;
9
9
  this.claudeRepository = claudeRepository;
10
10
  this.localCommandRunner = localCommandRunner;
11
+ this.claudeTokenUsageRepository = claudeTokenUsageRepository;
12
+ this.selectRotationTokens = (tokenUsages) => tokenUsages
13
+ .filter((usage) => !usage.blocked)
14
+ .sort((a, b) => a.fiveHourUtilization - b.fiveHourUtilization)
15
+ .map((usage) => usage.token);
11
16
  this.run = async (params) => {
12
17
  const claudeUsages = await this.claudeRepository.getUsage();
13
18
  const weeklyWindowHours = 168;
@@ -33,6 +38,17 @@ class StartPreparationUseCase {
33
38
  console.warn(`Weekly Claude usage (${maxWeeklyUtilization}%) exceeds threshold (${params.utilizationPercentageThreshold}%). Reducing maximumPreparingIssuesCount to ${maximumPreparingIssuesCount}.`);
34
39
  }
35
40
  }
41
+ const tokenUsages = await this.claudeTokenUsageRepository.getAvailableTokenUsages();
42
+ let rotationTokens = null;
43
+ let proxyBaseUrl = null;
44
+ if (tokenUsages.length > 0) {
45
+ const ranked = this.selectRotationTokens(tokenUsages);
46
+ if (ranked.length > 0) {
47
+ await this.claudeTokenUsageRepository.ensureObservable();
48
+ rotationTokens = ranked;
49
+ proxyBaseUrl = this.claudeTokenUsageRepository.proxyBaseUrl();
50
+ }
51
+ }
36
52
  const project = await this.projectRepository.getByUrl(params.projectUrl);
37
53
  const storyObjectMap = await this.issueRepository.getStoryObjectMap(project, params.allowIssueCacheMinutes);
38
54
  const allOpenedIssues = Array.from(storyObjectMap.values()).flatMap((storyObject) => storyObject.issues);
@@ -152,7 +168,15 @@ class StartPreparationUseCase {
152
168
  const codexHome = params.codexHomeCandidates[startedInThisRunCount % params.codexHomeCandidates.length];
153
169
  awArgs.push('--codexHome', codexHome);
154
170
  }
155
- await this.localCommandRunner.runCommand('aw', awArgs);
171
+ let spawnEnv;
172
+ if (rotationTokens !== null && proxyBaseUrl !== null) {
173
+ const selected = rotationTokens[startedInThisRunCount % rotationTokens.length];
174
+ spawnEnv = {
175
+ CLAUDE_CODE_OAUTH_TOKEN: selected,
176
+ ANTHROPIC_BASE_URL: proxyBaseUrl,
177
+ };
178
+ }
179
+ await this.localCommandRunner.runCommand('aw', awArgs, spawnEnv ? { env: spawnEnv } : undefined);
156
180
  startedInThisRunCount++;
157
181
  updatedCurrentPreparationIssueCount++;
158
182
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ClaudeTokenUsageRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClaudeTokenUsageRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ClaudeTokenUsageRepository.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -16,6 +16,7 @@ import {
16
16
  fetchProjectReadme,
17
17
  } from './projectConfig';
18
18
  import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparationUseCase';
19
+ import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
19
20
  import { NotifyFinishedIssuePreparationUseCase } from '../../../domain/usecases/NotifyFinishedIssuePreparationUseCase';
20
21
  import { LocalStorageRepository } from '../../repositories/LocalStorageRepository';
21
22
  import { GraphqlProjectRepository } from '../../repositories/GraphqlProjectRepository';
@@ -269,11 +270,15 @@ program
269
270
  });
270
271
  }
271
272
 
273
+ const claudeTokenUsageRepository = new ProxyClaudeTokenUsageRepository(
274
+ config.claudeCodeOauthTokenListJsonPath ?? null,
275
+ );
272
276
  const useCase = new StartPreparationUseCase(
273
277
  projectRepository,
274
278
  issueRepository,
275
279
  claudeRepository,
276
280
  localCommandRunner,
281
+ claudeTokenUsageRepository,
277
282
  );
278
283
 
279
284
  const rawAllowedIssueAuthors = config.allowedIssueAuthors;
@@ -15,6 +15,7 @@ export type ConfigFile = {
15
15
  projectName?: string;
16
16
  preparationProcessCheckCommand?: string;
17
17
  codexHomeCandidates?: string[];
18
+ claudeCodeOauthTokenListJsonPath?: string;
18
19
  awLogDirectoryPath?: string;
19
20
  awLogStaleThresholdMinutes?: number;
20
21
  };
@@ -89,6 +90,10 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
89
90
  'preparationProcessCheckCommand',
90
91
  ),
91
92
  codexHomeCandidates: getStringArrayValue(parsed, 'codexHomeCandidates'),
93
+ claudeCodeOauthTokenListJsonPath: getStringValue(
94
+ parsed,
95
+ 'claudeCodeOauthTokenListJsonPath',
96
+ ),
92
97
  awLogDirectoryPath: getStringValue(parsed, 'awLogDirectoryPath'),
93
98
  awLogStaleThresholdMinutes: getNumberValue(
94
99
  parsed,
@@ -144,6 +149,10 @@ export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
144
149
  'preparationProcessCheckCommand',
145
150
  ),
146
151
  codexHomeCandidates: getStringArrayValue(parsed, 'codexHomeCandidates'),
152
+ claudeCodeOauthTokenListJsonPath: getStringValue(
153
+ parsed,
154
+ 'claudeCodeOauthTokenListJsonPath',
155
+ ),
147
156
  awLogDirectoryPath: getStringValue(parsed, 'awLogDirectoryPath'),
148
157
  awLogStaleThresholdMinutes: getNumberValue(
149
158
  parsed,
@@ -207,6 +216,10 @@ export const mergeConfigs = (
207
216
  readmeOverrides.codexHomeCandidates ??
208
217
  cliOverrides.codexHomeCandidates ??
209
218
  configFile.codexHomeCandidates,
219
+ claudeCodeOauthTokenListJsonPath:
220
+ readmeOverrides.claudeCodeOauthTokenListJsonPath ??
221
+ cliOverrides.claudeCodeOauthTokenListJsonPath ??
222
+ configFile.claudeCodeOauthTokenListJsonPath,
210
223
  awLogDirectoryPath:
211
224
  readmeOverrides.awLogDirectoryPath ??
212
225
  cliOverrides.awLogDirectoryPath ??
@@ -35,6 +35,7 @@ import { UpdateIssueStatusByLabelUseCase } from '../../../domain/usecases/Update
35
35
  import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparationUseCase';
36
36
  import { NodeLocalCommandRunner } from '../../repositories/NodeLocalCommandRunner';
37
37
  import { OauthAPIProxyClaudeRepository } from '../../repositories/OauthAPIProxyClaudeRepository';
38
+ import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
38
39
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
39
40
  import { RevertNotReadyAwaitingQualityCheckUseCase } from '../../../domain/usecases/RevertNotReadyAwaitingQualityCheckUseCase';
40
41
  import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
@@ -217,11 +218,15 @@ export class HandleScheduledEventUseCaseHandler {
217
218
  );
218
219
  const nodeLocalCommandRunner = new NodeLocalCommandRunner();
219
220
  const claudeRepository = new OauthAPIProxyClaudeRepository();
221
+ const claudeTokenUsageRepository = new ProxyClaudeTokenUsageRepository(
222
+ null,
223
+ );
220
224
  const startPreparationUseCase = new StartPreparationUseCase(
221
225
  projectRepository,
222
226
  issueRepository,
223
227
  claudeRepository,
224
228
  nodeLocalCommandRunner,
229
+ claudeTokenUsageRepository,
225
230
  );
226
231
  const issueCommentRepository = new GitHubIssueCommentRepository(
227
232
  input.credentials.bot.github.token,
@@ -0,0 +1,131 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import {
5
+ cacheDir,
6
+ cachePathForToken,
7
+ hashToken,
8
+ readRateLimit,
9
+ writeRateLimit,
10
+ } from './RateLimitCache';
11
+
12
+ describe('RateLimitCache', () => {
13
+ let tempDir: string;
14
+ const originalXdg = process.env.XDG_CACHE_HOME;
15
+
16
+ beforeEach(() => {
17
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ratelimit-cache-'));
18
+ process.env.XDG_CACHE_HOME = tempDir;
19
+ });
20
+
21
+ afterEach(() => {
22
+ if (originalXdg === undefined) {
23
+ delete process.env.XDG_CACHE_HOME;
24
+ } else {
25
+ process.env.XDG_CACHE_HOME = originalXdg;
26
+ }
27
+ fs.rmSync(tempDir, { recursive: true, force: true });
28
+ });
29
+
30
+ describe('hashToken', () => {
31
+ it('should be deterministic for the same input', () => {
32
+ expect(hashToken('token-a')).toBe(hashToken('token-a'));
33
+ });
34
+
35
+ it('should differ for different inputs', () => {
36
+ expect(hashToken('token-a')).not.toBe(hashToken('token-b'));
37
+ });
38
+
39
+ it('should return a 64-character hex string for sha256', () => {
40
+ const hash = hashToken('token-a');
41
+ expect(hash).toMatch(/^[0-9a-f]{64}$/);
42
+ });
43
+ });
44
+
45
+ describe('cacheDir', () => {
46
+ it('should honor XDG_CACHE_HOME', () => {
47
+ expect(cacheDir()).toBe(path.join(tempDir, 'tdpm', 'ratelimit'));
48
+ });
49
+
50
+ it('should default to ~/.cache when XDG_CACHE_HOME is unset', () => {
51
+ delete process.env.XDG_CACHE_HOME;
52
+ expect(cacheDir()).toBe(
53
+ path.join(os.homedir(), '.cache', 'tdpm', 'ratelimit'),
54
+ );
55
+ });
56
+ });
57
+
58
+ describe('cachePathForToken', () => {
59
+ it('should place the file under cacheDir with the token hash as filename', () => {
60
+ const token = 'token-x';
61
+ expect(cachePathForToken(token)).toBe(
62
+ path.join(cacheDir(), `${hashToken(token)}.json`),
63
+ );
64
+ });
65
+ });
66
+
67
+ describe('writeRateLimit and readRateLimit', () => {
68
+ it('should round-trip rate limit headers', () => {
69
+ const token = 'roundtrip-token';
70
+ writeRateLimit(token, {
71
+ 'anthropic-ratelimit-unified-status': 'allowed',
72
+ 'anthropic-ratelimit-unified-5h-status': 'allowed',
73
+ 'anthropic-ratelimit-unified-5h-reset': '1700000000',
74
+ 'anthropic-ratelimit-unified-5h-utilization': '42',
75
+ 'anthropic-ratelimit-unified-7d-status': 'allowed',
76
+ 'anthropic-ratelimit-unified-7d-reset': '1700100000',
77
+ 'anthropic-ratelimit-unified-7d-utilization': '17',
78
+ });
79
+ const snapshot = readRateLimit(token);
80
+ expect(snapshot).not.toBeNull();
81
+ if (snapshot === null) return;
82
+ expect(snapshot.fiveHourUtilization).toBe(42);
83
+ expect(snapshot.fiveHourReset).toBe(1700000000);
84
+ expect(snapshot.sevenDayUtilization).toBe(17);
85
+ expect(snapshot.sevenDayReset).toBe(1700100000);
86
+ expect(snapshot.blocked).toBe(false);
87
+ });
88
+
89
+ it('should mark snapshot as blocked when status header is blocked', () => {
90
+ const token = 'blocked-token';
91
+ writeRateLimit(token, {
92
+ 'anthropic-ratelimit-unified-status': 'blocked',
93
+ 'anthropic-ratelimit-unified-5h-status': 'allowed',
94
+ 'anthropic-ratelimit-unified-5h-reset': '1700000000',
95
+ 'anthropic-ratelimit-unified-5h-utilization': '100',
96
+ 'anthropic-ratelimit-unified-7d-status': 'allowed',
97
+ 'anthropic-ratelimit-unified-7d-reset': '1700100000',
98
+ 'anthropic-ratelimit-unified-7d-utilization': '99',
99
+ });
100
+ const snapshot = readRateLimit(token);
101
+ expect(snapshot?.blocked).toBe(true);
102
+ });
103
+
104
+ it('should return null when file does not exist', () => {
105
+ expect(readRateLimit('never-written-token')).toBeNull();
106
+ });
107
+
108
+ it('should return null when file content is malformed JSON', () => {
109
+ const token = 'malformed-token';
110
+ fs.mkdirSync(cacheDir(), { recursive: true });
111
+ fs.writeFileSync(cachePathForToken(token), '{not json');
112
+ expect(readRateLimit(token)).toBeNull();
113
+ });
114
+
115
+ it('should treat array header values as the first element', () => {
116
+ const token = 'array-token';
117
+ writeRateLimit(token, {
118
+ 'anthropic-ratelimit-unified-status': 'allowed',
119
+ 'anthropic-ratelimit-unified-5h-status': 'allowed',
120
+ 'anthropic-ratelimit-unified-5h-reset': ['1700000000', 'ignored'],
121
+ 'anthropic-ratelimit-unified-5h-utilization': ['55', 'ignored'],
122
+ 'anthropic-ratelimit-unified-7d-status': 'allowed',
123
+ 'anthropic-ratelimit-unified-7d-reset': '1700100000',
124
+ 'anthropic-ratelimit-unified-7d-utilization': '10',
125
+ });
126
+ const snapshot = readRateLimit(token);
127
+ expect(snapshot?.fiveHourUtilization).toBe(55);
128
+ expect(snapshot?.fiveHourReset).toBe(1700000000);
129
+ });
130
+ });
131
+ });
@@ -0,0 +1,112 @@
1
+ import * as crypto from 'crypto';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+
6
+ export interface RateLimitSnapshot {
7
+ fiveHourUtilization: number;
8
+ fiveHourReset: number;
9
+ sevenDayUtilization: number;
10
+ sevenDayReset: number;
11
+ blocked: boolean;
12
+ }
13
+
14
+ export const PROXY_PORT = 8787;
15
+
16
+ const HASH_ALGORITHM = 'sha256';
17
+
18
+ export const cacheDir = (): string => {
19
+ const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
20
+ return path.join(base, 'tdpm', 'ratelimit');
21
+ };
22
+
23
+ export const hashToken = (token: string): string =>
24
+ crypto.createHash(HASH_ALGORITHM).update(token).digest('hex');
25
+
26
+ export const cachePathForToken = (token: string): string =>
27
+ path.join(cacheDir(), `${hashToken(token)}.json`);
28
+
29
+ export const writeRateLimit = (
30
+ token: string,
31
+ headers: Record<string, string | string[] | undefined>,
32
+ ): void => {
33
+ const dir = cacheDir();
34
+ if (!fs.existsSync(dir)) {
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ }
37
+ const pick = (key: string): string | undefined => {
38
+ const value = headers[key];
39
+ if (Array.isArray(value)) return value[0];
40
+ return value;
41
+ };
42
+ const payload = {
43
+ ts: Date.now() / 1000,
44
+ headers: {
45
+ 'anthropic-ratelimit-unified-status': pick(
46
+ 'anthropic-ratelimit-unified-status',
47
+ ),
48
+ 'anthropic-ratelimit-unified-5h-status': pick(
49
+ 'anthropic-ratelimit-unified-5h-status',
50
+ ),
51
+ 'anthropic-ratelimit-unified-5h-reset': pick(
52
+ 'anthropic-ratelimit-unified-5h-reset',
53
+ ),
54
+ 'anthropic-ratelimit-unified-5h-utilization': pick(
55
+ 'anthropic-ratelimit-unified-5h-utilization',
56
+ ),
57
+ 'anthropic-ratelimit-unified-7d-status': pick(
58
+ 'anthropic-ratelimit-unified-7d-status',
59
+ ),
60
+ 'anthropic-ratelimit-unified-7d-reset': pick(
61
+ 'anthropic-ratelimit-unified-7d-reset',
62
+ ),
63
+ 'anthropic-ratelimit-unified-7d-utilization': pick(
64
+ 'anthropic-ratelimit-unified-7d-utilization',
65
+ ),
66
+ },
67
+ };
68
+ const filePath = path.join(dir, `${hashToken(token)}.json`);
69
+ fs.writeFileSync(filePath, JSON.stringify(payload));
70
+ };
71
+
72
+ export const readRateLimit = (token: string): RateLimitSnapshot | null => {
73
+ const filePath = cachePathForToken(token);
74
+ if (!fs.existsSync(filePath)) return null;
75
+ try {
76
+ const raw = fs.readFileSync(filePath, 'utf8');
77
+ const parsed: unknown = JSON.parse(raw);
78
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
79
+ value !== null && typeof value === 'object' && !Array.isArray(value);
80
+ if (!isRecord(parsed)) return null;
81
+ const headersUnknown = parsed.headers;
82
+ const headers: Record<string, string> = {};
83
+ if (isRecord(headersUnknown)) {
84
+ for (const [key, value] of Object.entries(headersUnknown)) {
85
+ if (typeof value === 'string') {
86
+ headers[key] = value;
87
+ }
88
+ }
89
+ }
90
+ const num = (key: string): number => {
91
+ const value = headers[key];
92
+ if (typeof value !== 'string') return 0;
93
+ const parsedValue = Number(value);
94
+ return Number.isFinite(parsedValue) ? parsedValue : 0;
95
+ };
96
+ const status = headers['anthropic-ratelimit-unified-status'];
97
+ const fiveHourStatus = headers['anthropic-ratelimit-unified-5h-status'];
98
+ const sevenDayStatus = headers['anthropic-ratelimit-unified-7d-status'];
99
+ return {
100
+ fiveHourUtilization: num('anthropic-ratelimit-unified-5h-utilization'),
101
+ fiveHourReset: num('anthropic-ratelimit-unified-5h-reset'),
102
+ sevenDayUtilization: num('anthropic-ratelimit-unified-7d-utilization'),
103
+ sevenDayReset: num('anthropic-ratelimit-unified-7d-reset'),
104
+ blocked:
105
+ status === 'blocked' ||
106
+ fiveHourStatus === 'blocked' ||
107
+ sevenDayStatus === 'blocked',
108
+ };
109
+ } catch {
110
+ return null;
111
+ }
112
+ };
@@ -0,0 +1,82 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { loadTokens } from './TokenListLoader';
5
+
6
+ describe('TokenListLoader', () => {
7
+ let tempDir: string;
8
+
9
+ beforeEach(() => {
10
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'token-list-loader-'));
11
+ });
12
+
13
+ afterEach(() => {
14
+ fs.rmSync(tempDir, { recursive: true, force: true });
15
+ });
16
+
17
+ it('should return tokens in the order they appear in the JSON file', () => {
18
+ const filePath = path.join(tempDir, 'tokens.json');
19
+ fs.writeFileSync(
20
+ filePath,
21
+ JSON.stringify([
22
+ { name: 'first', token: 'token-1' },
23
+ { name: 'second', token: 'token-2' },
24
+ ]),
25
+ );
26
+ expect(loadTokens(filePath)).toEqual(['token-1', 'token-2']);
27
+ });
28
+
29
+ it('should return null when the file does not exist', () => {
30
+ expect(loadTokens(path.join(tempDir, 'missing.json'))).toBeNull();
31
+ });
32
+
33
+ it('should return null when the JSON root is not an array', () => {
34
+ const filePath = path.join(tempDir, 'object.json');
35
+ fs.writeFileSync(filePath, JSON.stringify({ token: 'token-1' }));
36
+ expect(loadTokens(filePath)).toBeNull();
37
+ });
38
+
39
+ it('should return null when JSON parsing fails', () => {
40
+ const filePath = path.join(tempDir, 'malformed.json');
41
+ fs.writeFileSync(filePath, '{not json');
42
+ expect(loadTokens(filePath)).toBeNull();
43
+ });
44
+
45
+ it('should skip entries that do not contain a string token', () => {
46
+ const filePath = path.join(tempDir, 'mixed.json');
47
+ fs.writeFileSync(
48
+ filePath,
49
+ JSON.stringify([
50
+ { name: 'no-token' },
51
+ { name: 'first', token: 'token-1' },
52
+ { name: 'numeric', token: 123 },
53
+ { name: 'second', token: 'token-2' },
54
+ ]),
55
+ );
56
+ expect(loadTokens(filePath)).toEqual(['token-1', 'token-2']);
57
+ });
58
+
59
+ it('should return null when every entry is invalid', () => {
60
+ const filePath = path.join(tempDir, 'no-valid-entries.json');
61
+ fs.writeFileSync(
62
+ filePath,
63
+ JSON.stringify([{ name: 'no-token' }, { token: 42 }]),
64
+ );
65
+ expect(loadTokens(filePath)).toBeNull();
66
+ });
67
+
68
+ it('should expand a leading tilde to the home directory', () => {
69
+ const home = os.homedir();
70
+ const fileName = `tdpm-loader-${process.pid}-${Date.now()}.json`;
71
+ const targetPath = path.join(home, fileName);
72
+ fs.writeFileSync(
73
+ targetPath,
74
+ JSON.stringify([{ name: 'first', token: 'home-token' }]),
75
+ );
76
+ try {
77
+ expect(loadTokens(`~/${fileName}`)).toEqual(['home-token']);
78
+ } finally {
79
+ fs.unlinkSync(targetPath);
80
+ }
81
+ });
82
+ });
@@ -0,0 +1,35 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+
5
+ const expandHome = (filePath: string): string => {
6
+ if (filePath.startsWith('~/')) {
7
+ return path.join(os.homedir(), filePath.slice(2));
8
+ }
9
+ if (filePath === '~') {
10
+ return os.homedir();
11
+ }
12
+ return filePath;
13
+ };
14
+
15
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
16
+ value !== null && typeof value === 'object' && !Array.isArray(value);
17
+
18
+ export const loadTokens = (jsonPath: string): string[] | null => {
19
+ const resolved = expandHome(jsonPath);
20
+ if (!fs.existsSync(resolved)) return null;
21
+ try {
22
+ const raw = fs.readFileSync(resolved, 'utf8');
23
+ const parsed: unknown = JSON.parse(raw);
24
+ if (!Array.isArray(parsed)) return null;
25
+ const tokens: string[] = [];
26
+ for (const entry of parsed) {
27
+ if (isRecord(entry) && typeof entry.token === 'string') {
28
+ tokens.push(entry.token);
29
+ }
30
+ }
31
+ return tokens.length > 0 ? tokens : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ };