github-issue-tower-defence-management 1.94.4 → 1.96.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.
- package/.github/workflows/console-ui.yml +17 -0
- package/CHANGELOG.md +14 -0
- package/README.md +11 -0
- package/bin/adapter/entry-points/cli/index.js +21 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-B3F4E8LD.js +101 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +77 -0
- package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -0
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js +139 -0
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js.map +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +59 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -0
- package/bin/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.js.map +1 -0
- package/package.json +4 -1
- package/src/adapter/entry-points/cli/index.ts +38 -0
- package/src/adapter/entry-points/console/ui/biome.json +7 -1
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +93 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +380 -0
- package/src/adapter/entry-points/console/ui/e2e/playwright.config.ts +30 -0
- package/src/adapter/entry-points/console/ui/e2e/tsconfig.json +18 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +48 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.ts +19 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +39 -6
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +31 -4
- package/src/adapter/entry-points/console/ui-dist/assets/index-B3F4E8LD.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +268 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +116 -0
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +146 -0
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +119 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +147 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +100 -0
- package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +8 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +20 -0
- package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -0
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts +11 -0
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts.map +1 -0
- package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts +23 -0
- package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -0
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts +8 -0
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts.map +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +0 -101
- package/src/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +0 -101
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-B3F4E8LD.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CO3Vvzqr.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LiveSessionOauthTokenSelectHandler = void 0;
|
|
4
|
+
const LiveSessionOauthTokenSelectUseCase_1 = require("../../../domain/usecases/LiveSessionOauthTokenSelectUseCase");
|
|
5
|
+
const ProcClaudeLiveSessionRepository_1 = require("../../repositories/ProcClaudeLiveSessionRepository");
|
|
6
|
+
const RateLimitCache_1 = require("../../proxy/RateLimitCache");
|
|
7
|
+
const TokenListLoader_1 = require("../../proxy/TokenListLoader");
|
|
8
|
+
const OauthTokenSelectHandler_1 = require("./OauthTokenSelectHandler");
|
|
9
|
+
class LiveSessionOauthTokenSelectHandler {
|
|
10
|
+
constructor(useCase = new LiveSessionOauthTokenSelectUseCase_1.LiveSessionOauthTokenSelectUseCase(), liveSessionRepository = new ProcClaudeLiveSessionRepository_1.ProcClaudeLiveSessionRepository()) {
|
|
11
|
+
this.useCase = useCase;
|
|
12
|
+
this.liveSessionRepository = liveSessionRepository;
|
|
13
|
+
this.handle = (input) => {
|
|
14
|
+
const tokenListJsonPath = (0, OauthTokenSelectHandler_1.resolveTokenListJsonPath)(input.tokenListJsonPath);
|
|
15
|
+
if (tokenListJsonPath === null) {
|
|
16
|
+
return {
|
|
17
|
+
selectedToken: null,
|
|
18
|
+
selectedName: null,
|
|
19
|
+
diagnostics: [
|
|
20
|
+
'No token list path provided. Pass --tokenListJsonPath or set CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH.',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const entries = (0, TokenListLoader_1.loadTokenEntries)(tokenListJsonPath);
|
|
25
|
+
if (entries === null) {
|
|
26
|
+
return {
|
|
27
|
+
selectedToken: null,
|
|
28
|
+
selectedName: null,
|
|
29
|
+
diagnostics: [
|
|
30
|
+
`No usable token entries loaded from ${tokenListJsonPath}.`,
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const cacheDirectory = (0, OauthTokenSelectHandler_1.resolveCacheDirectory)(input.cacheDirectory);
|
|
35
|
+
const candidates = entries.map(({ name, token }) => {
|
|
36
|
+
const snapshot = (0, RateLimitCache_1.readRateLimit)(token, cacheDirectory);
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
token,
|
|
40
|
+
snapshot: snapshot === null
|
|
41
|
+
? null
|
|
42
|
+
: {
|
|
43
|
+
fiveHourUtilization: snapshot.fiveHourUtilization,
|
|
44
|
+
fiveHourReset: snapshot.fiveHourReset,
|
|
45
|
+
sevenDayUtilization: snapshot.sevenDayUtilization,
|
|
46
|
+
sevenDayReset: snapshot.sevenDayReset,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
const liveSessions = this.liveSessionRepository.listLiveSessions();
|
|
51
|
+
const result = this.useCase.run(candidates, liveSessions, input.nowEpochSeconds);
|
|
52
|
+
return {
|
|
53
|
+
selectedToken: result.selected?.token ?? null,
|
|
54
|
+
selectedName: result.selected?.name ?? null,
|
|
55
|
+
diagnostics: this.formatDiagnostics(result, input.nowEpochSeconds),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
this.formatDiagnostics = (result, nowEpochSeconds) => {
|
|
59
|
+
const lines = result.metrics.map((metric) => {
|
|
60
|
+
const secondsUntilSevenDayEnd = Math.round(metric.sevenDayEndEpoch - nowEpochSeconds);
|
|
61
|
+
const status = metric.eligible
|
|
62
|
+
? 'eligible'
|
|
63
|
+
: `excluded (${metric.exclusionReason})`;
|
|
64
|
+
return `${metric.name}: ${metric.liveSessionCount} live session(s), 5h ${Math.round(metric.fiveHourFreeRatio * 100)}% free, 7d ${Math.round(metric.sevenDayFreeRatio * 100)}% free, 7d-end in ${secondsUntilSevenDayEnd}s -> ${status}`;
|
|
65
|
+
});
|
|
66
|
+
if (result.selected === null) {
|
|
67
|
+
lines.push('No eligible token passed the rate-limit filter.');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
lines.push(`Selected ${result.selected.name} (fewest live sessions, then soonest 7d reset among eligible tokens).`);
|
|
71
|
+
}
|
|
72
|
+
return lines;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.LiveSessionOauthTokenSelectHandler = LiveSessionOauthTokenSelectHandler;
|
|
77
|
+
//# sourceMappingURL=LiveSessionOauthTokenSelectHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveSessionOauthTokenSelectHandler.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ProcClaudeLiveSessionRepository = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const DEFAULT_PROC_DIRECTORY = '/proc';
|
|
40
|
+
const OAUTH_TOKEN_ENVIRON_KEY = 'CLAUDE_CODE_OAUTH_TOKEN';
|
|
41
|
+
const SESSION_ID_ENVIRON_KEY = 'CLAUDE_CODE_SESSION_ID';
|
|
42
|
+
const isClaudeProcessCommand = (command) => {
|
|
43
|
+
if (command.length === 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (command.includes('.local/share/claude')) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const executableName = path.basename(command.split('\0')[0] ?? '');
|
|
50
|
+
return executableName === 'claude';
|
|
51
|
+
};
|
|
52
|
+
class ProcClaudeLiveSessionRepository {
|
|
53
|
+
constructor(procDirectory = DEFAULT_PROC_DIRECTORY) {
|
|
54
|
+
this.procDirectory = procDirectory;
|
|
55
|
+
this.listLiveSessions = () => {
|
|
56
|
+
const liveSessions = [];
|
|
57
|
+
for (const pidDirectory of this.listProcessIdDirectories()) {
|
|
58
|
+
const liveSession = this.readLiveSession(pidDirectory);
|
|
59
|
+
if (liveSession !== null) {
|
|
60
|
+
liveSessions.push(liveSession);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return liveSessions;
|
|
64
|
+
};
|
|
65
|
+
this.listProcessIdDirectories = () => {
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = fs.readdirSync(this.procDirectory);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return entries.filter((entry) => /^\d+$/.test(entry));
|
|
74
|
+
};
|
|
75
|
+
this.readLiveSession = (processIdDirectory) => {
|
|
76
|
+
const environ = this.readEnviron(processIdDirectory);
|
|
77
|
+
if (environ === null) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const token = environ.get(OAUTH_TOKEN_ENVIRON_KEY);
|
|
81
|
+
const sessionId = environ.get(SESSION_ID_ENVIRON_KEY);
|
|
82
|
+
if (token === undefined ||
|
|
83
|
+
token.length === 0 ||
|
|
84
|
+
sessionId === undefined ||
|
|
85
|
+
sessionId.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (!this.isClaudeProcess(processIdDirectory)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return { token, sessionId };
|
|
92
|
+
};
|
|
93
|
+
this.isClaudeProcess = (processIdDirectory) => {
|
|
94
|
+
const basePath = path.join(this.procDirectory, processIdDirectory);
|
|
95
|
+
try {
|
|
96
|
+
const cmdline = fs.readFileSync(path.join(basePath, 'cmdline'), 'utf8');
|
|
97
|
+
if (isClaudeProcessCommand(cmdline)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const exe = fs.readlinkSync(path.join(basePath, 'exe'));
|
|
106
|
+
return isClaudeProcessCommand(exe);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
this.readEnviron = (processIdDirectory) => {
|
|
113
|
+
const environPath = path.join(this.procDirectory, processIdDirectory, 'environ');
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = fs.readFileSync(environPath, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const environ = new Map();
|
|
122
|
+
for (const entry of raw.split('\0')) {
|
|
123
|
+
if (entry.length === 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const separatorIndex = entry.indexOf('=');
|
|
127
|
+
if (separatorIndex <= 0) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const key = entry.slice(0, separatorIndex);
|
|
131
|
+
const value = entry.slice(separatorIndex + 1);
|
|
132
|
+
environ.set(key, value);
|
|
133
|
+
}
|
|
134
|
+
return environ;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.ProcClaudeLiveSessionRepository = ProcClaudeLiveSessionRepository;
|
|
139
|
+
//# sourceMappingURL=ProcClaudeLiveSessionRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcClaudeLiveSessionRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/ProcClaudeLiveSessionRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LiveSessionOauthTokenSelectUseCase = void 0;
|
|
4
|
+
const OauthTokenSelectUseCase_1 = require("./OauthTokenSelectUseCase");
|
|
5
|
+
class LiveSessionOauthTokenSelectUseCase {
|
|
6
|
+
constructor(rateLimitSelectUseCase = new OauthTokenSelectUseCase_1.OauthTokenSelectUseCase()) {
|
|
7
|
+
this.rateLimitSelectUseCase = rateLimitSelectUseCase;
|
|
8
|
+
this.run = (candidates, liveSessions, nowEpochSeconds) => {
|
|
9
|
+
const rateLimitResult = this.rateLimitSelectUseCase.run(candidates, nowEpochSeconds);
|
|
10
|
+
const liveSessionCountByToken = this.liveSessionCountByToken(liveSessions);
|
|
11
|
+
const evaluated = candidates.map((candidate, index) => {
|
|
12
|
+
const rateLimitMetric = rateLimitResult.metrics[index];
|
|
13
|
+
const liveSessionCount = liveSessionCountByToken.get(candidate.token) ?? 0;
|
|
14
|
+
return {
|
|
15
|
+
candidate,
|
|
16
|
+
metric: {
|
|
17
|
+
name: rateLimitMetric.name,
|
|
18
|
+
fiveHourFreeRatio: rateLimitMetric.fiveHourFreeRatio,
|
|
19
|
+
sevenDayFreeRatio: rateLimitMetric.sevenDayFreeRatio,
|
|
20
|
+
sevenDayEndEpoch: rateLimitMetric.sevenDayEndEpoch,
|
|
21
|
+
liveSessionCount,
|
|
22
|
+
eligible: rateLimitMetric.eligible,
|
|
23
|
+
exclusionReason: rateLimitMetric.exclusionReason,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const metrics = evaluated.map((entry) => entry.metric);
|
|
28
|
+
const eligible = evaluated.filter((entry) => entry.metric.eligible);
|
|
29
|
+
if (eligible.length === 0) {
|
|
30
|
+
return { selected: null, metrics };
|
|
31
|
+
}
|
|
32
|
+
const best = eligible.reduce((bestEntry, currentEntry) => this.preferred(currentEntry.metric, bestEntry.metric)
|
|
33
|
+
? currentEntry
|
|
34
|
+
: bestEntry);
|
|
35
|
+
return { selected: best.candidate, metrics };
|
|
36
|
+
};
|
|
37
|
+
this.preferred = (candidateMetric, incumbentMetric) => {
|
|
38
|
+
if (candidateMetric.liveSessionCount !== incumbentMetric.liveSessionCount) {
|
|
39
|
+
return (candidateMetric.liveSessionCount < incumbentMetric.liveSessionCount);
|
|
40
|
+
}
|
|
41
|
+
return candidateMetric.sevenDayEndEpoch < incumbentMetric.sevenDayEndEpoch;
|
|
42
|
+
};
|
|
43
|
+
this.liveSessionCountByToken = (liveSessions) => {
|
|
44
|
+
const sessionIdsByToken = new Map();
|
|
45
|
+
for (const liveSession of liveSessions) {
|
|
46
|
+
const sessionIds = sessionIdsByToken.get(liveSession.token) ?? new Set();
|
|
47
|
+
sessionIds.add(liveSession.sessionId);
|
|
48
|
+
sessionIdsByToken.set(liveSession.token, sessionIds);
|
|
49
|
+
}
|
|
50
|
+
const countByToken = new Map();
|
|
51
|
+
for (const [token, sessionIds] of sessionIdsByToken.entries()) {
|
|
52
|
+
countByToken.set(token, sessionIds.size);
|
|
53
|
+
}
|
|
54
|
+
return countByToken;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.LiveSessionOauthTokenSelectUseCase = LiveSessionOauthTokenSelectUseCase;
|
|
59
|
+
//# sourceMappingURL=LiveSessionOauthTokenSelectUseCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveSessionOauthTokenSelectUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeLiveSessionRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-issue-tower-defence-management",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"build": "tsc -p ./tsconfig.build.json",
|
|
16
16
|
"postbuild": "node scripts/copyConsoleUiDist.mjs",
|
|
17
17
|
"console-ui:lint": "biome check src/adapter/entry-points/console/ui",
|
|
18
|
+
"console-ui:e2e:typecheck": "tsc --noEmit -p src/adapter/entry-points/console/ui/e2e/tsconfig.json",
|
|
19
|
+
"console-ui:e2e": "npm run build:console-ui && playwright test --config src/adapter/entry-points/console/ui/e2e/playwright.config.ts",
|
|
18
20
|
"console-ui:storybook": "storybook dev -p 6006 -c src/adapter/entry-points/console/ui/.storybook",
|
|
19
21
|
"console-ui:build-storybook": "storybook build -c src/adapter/entry-points/console/ui/.storybook -o src/adapter/entry-points/console/ui/storybook-static",
|
|
20
22
|
"test": "jest",
|
|
@@ -54,6 +56,7 @@
|
|
|
54
56
|
"homepage": "https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management#readme",
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"@biomejs/biome": "^2.2.0",
|
|
59
|
+
"@playwright/test": "1.60.0",
|
|
57
60
|
"@radix-ui/react-slot": "^1.2.0",
|
|
58
61
|
"@semantic-release/changelog": "^6.0.3",
|
|
59
62
|
"@semantic-release/commit-analyzer": "^13.0.0",
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
createConsoleProjectResolver,
|
|
43
43
|
} from '../console/consoleProjectResolver';
|
|
44
44
|
import { OauthTokenSelectHandler } from '../handlers/OauthTokenSelectHandler';
|
|
45
|
+
import { LiveSessionOauthTokenSelectHandler } from '../handlers/LiveSessionOauthTokenSelectHandler';
|
|
45
46
|
|
|
46
47
|
type StartDaemonOptions = {
|
|
47
48
|
projectUrl?: string;
|
|
@@ -89,6 +90,11 @@ type SelectOauthTokenOptions = {
|
|
|
89
90
|
cacheDir?: string;
|
|
90
91
|
};
|
|
91
92
|
|
|
93
|
+
type SelectLiveSessionOauthTokenOptions = {
|
|
94
|
+
tokenListJsonPath?: string;
|
|
95
|
+
cacheDir?: string;
|
|
96
|
+
};
|
|
97
|
+
|
|
92
98
|
const buildGithubRepositoryParams = (
|
|
93
99
|
localStorageRepository: LocalStorageRepository,
|
|
94
100
|
token: string,
|
|
@@ -757,6 +763,38 @@ program
|
|
|
757
763
|
process.stdout.write(`${output.selectedToken}\n`);
|
|
758
764
|
});
|
|
759
765
|
|
|
766
|
+
program
|
|
767
|
+
.command('selectLiveSessionOauthToken')
|
|
768
|
+
.description(
|
|
769
|
+
'Print exactly one Claude Code OAuth token chosen for a new live interactive session. Among rate-limit-eligible tokens it prefers the one with the fewest current live sessions (by distinct CLAUDE_CODE_SESSION_ID found in running Claude Code processes), tiebreaking on the soonest 7d reset. The token string is written to stdout (pipeable); the per-candidate decision trace is written to stderr. Exits non-zero when no token passes the filter.',
|
|
770
|
+
)
|
|
771
|
+
.option(
|
|
772
|
+
'--tokenListJsonPath <path>',
|
|
773
|
+
'Path to the JSON array of { name, token } records. Falls back to the CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH environment variable.',
|
|
774
|
+
)
|
|
775
|
+
.option(
|
|
776
|
+
'--cacheDir <path>',
|
|
777
|
+
'Directory holding per-token rate-limit cache files. Falls back to the TDPM_RATELIMIT_CACHE_DIR environment variable, then to ${XDG_CACHE_HOME:-~/.cache}/tdpm/ratelimit.',
|
|
778
|
+
)
|
|
779
|
+
.action((options: SelectLiveSessionOauthTokenOptions) => {
|
|
780
|
+
const handler = new LiveSessionOauthTokenSelectHandler();
|
|
781
|
+
const output = handler.handle({
|
|
782
|
+
tokenListJsonPath: options.tokenListJsonPath ?? null,
|
|
783
|
+
cacheDirectory: options.cacheDir ?? null,
|
|
784
|
+
nowEpochSeconds: Date.now() / 1000,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
for (const line of output.diagnostics) {
|
|
788
|
+
console.error(line);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (output.selectedToken === null) {
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
process.stdout.write(`${output.selectedToken}\n`);
|
|
796
|
+
});
|
|
797
|
+
|
|
760
798
|
/* istanbul ignore next */
|
|
761
799
|
if (process.argv && require.main === module) {
|
|
762
800
|
program.parse(process.argv);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { expect, type Page, test } from '@playwright/test';
|
|
2
|
+
import {
|
|
3
|
+
type ConsoleE2eHarness,
|
|
4
|
+
startConsoleE2eHarness,
|
|
5
|
+
} from './consoleTestHarness';
|
|
6
|
+
|
|
7
|
+
let harness: ConsoleE2eHarness;
|
|
8
|
+
|
|
9
|
+
test.beforeAll(async () => {
|
|
10
|
+
harness = await startConsoleE2eHarness();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test.afterAll(async () => {
|
|
14
|
+
if (harness !== undefined) {
|
|
15
|
+
await harness.stop();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const activeTabLabel = (page: Page) =>
|
|
20
|
+
page.locator('.console-tab[data-active="true"] .console-tab-label');
|
|
21
|
+
|
|
22
|
+
const tabByLabel = (page: Page, label: string) =>
|
|
23
|
+
page.locator('.console-tab', { hasText: label });
|
|
24
|
+
|
|
25
|
+
const tabBadge = (page: Page, label: string) =>
|
|
26
|
+
tabByLabel(page, label).locator('.console-tab-badge');
|
|
27
|
+
|
|
28
|
+
const itemRowByText = (page: Page, text: string) =>
|
|
29
|
+
page.locator('.console-item-row', { hasText: text });
|
|
30
|
+
|
|
31
|
+
const processSelectedItemViaStatus = async (page: Page): Promise<void> => {
|
|
32
|
+
const statusButton = page
|
|
33
|
+
.locator('.console-op-button', { hasText: 'Awaiting Workspace' })
|
|
34
|
+
.first();
|
|
35
|
+
await expect(statusButton).toBeVisible();
|
|
36
|
+
await statusButton.click();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
test('processing tabs drives auto-advance and keeps emptied badges at zero', async ({
|
|
40
|
+
page,
|
|
41
|
+
}) => {
|
|
42
|
+
await page.goto(harness.appUrl);
|
|
43
|
+
|
|
44
|
+
await expect(activeTabLabel(page)).toHaveText('Awaiting Quality Check');
|
|
45
|
+
await expect(tabBadge(page, 'Awaiting Quality Check')).toHaveText('1');
|
|
46
|
+
await expect(tabBadge(page, 'Unread')).toHaveText('2');
|
|
47
|
+
await expect(tabBadge(page, 'Triage')).toHaveText('2');
|
|
48
|
+
await expect(tabBadge(page, 'Todo by human')).toHaveText('1');
|
|
49
|
+
|
|
50
|
+
await itemRowByText(
|
|
51
|
+
page,
|
|
52
|
+
'Serve the committed console UI bundle from serveConsole',
|
|
53
|
+
).click();
|
|
54
|
+
const approveButton = page
|
|
55
|
+
.locator('.console-op-button', { hasText: 'Approve' })
|
|
56
|
+
.first();
|
|
57
|
+
await expect(approveButton).toBeVisible();
|
|
58
|
+
await approveButton.click();
|
|
59
|
+
|
|
60
|
+
await expect(activeTabLabel(page)).toHaveText('Triage', { timeout: 8000 });
|
|
61
|
+
await expect(tabByLabel(page, 'Awaiting Quality Check')).toHaveCount(0, {
|
|
62
|
+
timeout: 8000,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await itemRowByText(
|
|
66
|
+
page,
|
|
67
|
+
'Add Sonnet to Opus weekly-limit fallback routing per token',
|
|
68
|
+
).click();
|
|
69
|
+
await processSelectedItemViaStatus(page);
|
|
70
|
+
await expect(tabBadge(page, 'Triage')).toHaveText('1', { timeout: 8000 });
|
|
71
|
+
await page.locator('.console-back-button').click();
|
|
72
|
+
|
|
73
|
+
await itemRowByText(
|
|
74
|
+
page,
|
|
75
|
+
'Publish the generated documentation site to GitHub Pages',
|
|
76
|
+
).click();
|
|
77
|
+
await processSelectedItemViaStatus(page);
|
|
78
|
+
|
|
79
|
+
await expect(activeTabLabel(page)).toHaveText('Unread', { timeout: 8000 });
|
|
80
|
+
await expect(tabByLabel(page, 'Triage')).toHaveCount(0, {
|
|
81
|
+
timeout: 8000,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await tabByLabel(page, 'Todo by human').click();
|
|
85
|
+
await expect(activeTabLabel(page)).toHaveText('Todo by human');
|
|
86
|
+
await expect(tabByLabel(page, 'Triage')).toHaveCount(0);
|
|
87
|
+
await expect(tabBadge(page, 'Todo by human')).toHaveText('1');
|
|
88
|
+
|
|
89
|
+
await tabByLabel(page, 'Unread').click();
|
|
90
|
+
await expect(activeTabLabel(page)).toHaveText('Unread');
|
|
91
|
+
await expect(tabByLabel(page, 'Triage')).toHaveCount(0);
|
|
92
|
+
await expect(tabBadge(page, 'Unread')).toHaveText('2');
|
|
93
|
+
});
|