github-issue-tower-defence-management 1.142.2 → 1.142.3
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/umino-project.yml +2 -8
- package/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +2 -2
- package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +2 -2
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +33 -11
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +2 -2
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +2 -2
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
|
@@ -13,16 +13,12 @@ on:
|
|
|
13
13
|
types:
|
|
14
14
|
- opened
|
|
15
15
|
- reopened
|
|
16
|
-
- assigned
|
|
17
|
-
- unassigned
|
|
18
16
|
- labeled
|
|
19
17
|
pull_request:
|
|
20
18
|
types:
|
|
21
19
|
- opened
|
|
22
20
|
- reopened
|
|
23
21
|
- review_requested
|
|
24
|
-
- assigned
|
|
25
|
-
- unassigned
|
|
26
22
|
- labeled
|
|
27
23
|
- opened
|
|
28
24
|
- edited
|
|
@@ -116,8 +112,7 @@ jobs:
|
|
|
116
112
|
if: >-
|
|
117
113
|
((github.event_name == 'issues' && github.event.issue.state == 'open') ||
|
|
118
114
|
(github.event_name == 'pull_request' && github.event.pull_request.state == 'open')) &&
|
|
119
|
-
(github.event.action == 'opened' || github.event.action == 'reopened'
|
|
120
|
-
github.event.action == 'assigned' || github.event.action == 'unassigned')
|
|
115
|
+
(github.event.action == 'opened' || github.event.action == 'reopened')
|
|
121
116
|
- run: |
|
|
122
117
|
OWNER=$(echo ${{ github.repository }} | cut -d '/' -f 1)
|
|
123
118
|
REPO=$(echo ${{ github.repository }} | cut -d '/' -f 2)
|
|
@@ -161,8 +156,7 @@ jobs:
|
|
|
161
156
|
if: >-
|
|
162
157
|
((github.event_name == 'issues' && github.event.issue.state == 'open') ||
|
|
163
158
|
(github.event_name == 'pull_request' && github.event.pull_request.state == 'open')) &&
|
|
164
|
-
(github.event.action == 'opened' || github.event.action == 'reopened'
|
|
165
|
-
github.event.action == 'assigned' || github.event.action == 'unassigned')
|
|
159
|
+
(github.event.action == 'opened' || github.event.action == 'reopened')
|
|
166
160
|
|
|
167
161
|
- name: Create Issue
|
|
168
162
|
if: ${{ github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.comment.user.login == 'HiromiShikata' && contains(github.event.comment.body, '/createissue') }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.142.3](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.142.2...v1.142.3) (2026-07-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **OauthTokenSelectUseCase:** relax eligibility thresholds to 5h>=25% free / 7d>=3% free ([#1334](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1334)) ([fd11dbb](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/fd11dbb06f7547f124cc61d46a219ac99ba48ef2)), closes [#1333](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/1333)
|
|
7
|
+
|
|
1
8
|
## [1.142.2](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.142.1...v1.142.2) (2026-07-29)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -116,9 +116,9 @@ Each project's status and story options are loaded lazily the first time a `pjco
|
|
|
116
116
|
|
|
117
117
|
The `checkIssueReviewReadiness` sub-command lets an agent self-check whether an issue is currently review-ready. It does NOT change the issue Status field and does NOT post any comment. It writes a single JSON line to stdout of the shape `{ "reviewReady": boolean, "rejections": [{ "type": string, "detail": string }] }` and exits 0 on a successful evaluation regardless of readiness; a non-zero exit indicates an operational error (auth failure, network error). The rejection types include: `ISSUE_NOT_FOUND`, `NO_REPORT_FROM_AGENT_BOT`, `REPORT_HAS_NEXT_STEP`, `PULL_REQUEST_NOT_FOUND`, `PULL_REQUEST_IS_DRAFT`, `PULL_REQUEST_CONFLICTED`, `ANY_CI_JOB_FAILED_OR_IN_PROGRESS`, `REQUIRED_CI_JOB_NEVER_STARTED`, `ANY_REVIEW_COMMENT_NOT_RESOLVED`, and `MULTIPLE_PULL_REQUESTS_FOUND`. The `--projectUrl` option is optional; when omitted the command still runs using only the issue URL.
|
|
118
118
|
|
|
119
|
-
The `selectOauthToken` sub-command reads the same per-token rate-limit cache that the `startDaemon` proxy writes (see "Claude OAuth Token Rotation" below) and prints exactly one token string to stdout so a caller can choose an appropriate token before launching Claude Code. It is read-only: it never starts the proxy, never mutates any cache file, and never writes the token anywhere. Selection runs in two stages. First, a candidate filter keeps tokens whose 5-hour window is at least
|
|
119
|
+
The `selectOauthToken` sub-command reads the same per-token rate-limit cache that the `startDaemon` proxy writes (see "Claude OAuth Token Rotation" below) and prints exactly one token string to stdout so a caller can choose an appropriate token before launching Claude Code. It is read-only: it never starts the proxy, never mutates any cache file, and never writes the token anywhere. Selection runs in two stages. First, a candidate filter keeps tokens whose 5-hour window is at least 25% free (5-hour utilization at most 0.75) AND whose 7-day window is at least 3% free (7-day utilization at most 0.97), where "% free" is `1 - utilization`. A token with no cache file, or whose window reset epoch has already passed, is treated as fully free (utilization 0) for these checks. The filter additionally excludes any token carrying a non-expired reactive `seven_day_fable` rejection marker — set when a `fable`-model request on that token was rejected with HTTP 429 (see "Claude OAuth Token Rotation" below) — because these interactive sessions run on the `fable` model; a token without the marker is treated as `fable`-usable, and the marker is ignored once its stored reset epoch has passed. Second, among the surviving candidates it selects the single token whose 7-day window reset epoch is nearest in the future (soonest reset), so weekly quota that would otherwise reset unused is consumed first; a candidate with no active 7-day window is treated as having the farthest reset (now + 7 days) and therefore sorts last. Each token entry may carry an optional `selectionWeight` (a positive number, default `1`); when the eligible candidates carry differing weights the selection among them becomes weighted-random by that weight, so a token with a smaller weight is chosen proportionally less often, while a token that is the only eligible candidate is always chosen regardless of its weight. When every eligible candidate shares the same weight (the default), the deterministic soonest-reset selection above is used unchanged. The selected token string is written to stdout (pipeable) and the per-candidate decision trace is written to stderr. When no token passes the filter, nothing is written to stdout and the command exits non-zero with an explanatory message on stderr. The token-list path comes from `--tokenListJsonPath` or the `CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH` environment variable; the cache directory comes from `--cacheDir` or the `TDPM_RATELIMIT_CACHE_DIR` environment variable, defaulting to `${XDG_CACHE_HOME:-~/.cache}/tdpm/ratelimit`.
|
|
120
120
|
|
|
121
|
-
The `selectLiveSessionOauthToken` sub-command picks a token for a new live interactive Claude Code session a human is about to start, so that concurrent interactive sessions spread across distinct tokens instead of stacking onto the same one. It loads the token list and reads the same per-token rate-limit cache as `selectOauthToken`, and it is read-only in exactly the same way: it never starts the proxy, never mutates any cache file, and never writes the token anywhere. It applies the identical rate-limit eligibility filter as `selectOauthToken` (5-hour window at least
|
|
121
|
+
The `selectLiveSessionOauthToken` sub-command picks a token for a new live interactive Claude Code session a human is about to start, so that concurrent interactive sessions spread across distinct tokens instead of stacking onto the same one. It loads the token list and reads the same per-token rate-limit cache as `selectOauthToken`, and it is read-only in exactly the same way: it never starts the proxy, never mutates any cache file, and never writes the token anywhere. It applies the identical rate-limit eligibility filter as `selectOauthToken` (5-hour window at least 25% free AND 7-day window at least 3% free, with a missing cache file or an expired window treated as fully free, and any token carrying a non-expired reactive `seven_day_fable` rejection marker excluded). It additionally measures current live occupancy per token by scanning running Claude Code processes on the local Linux host: for each process under `/proc` it reads the NUL-separated `/proc/<pid>/environ` and, when the process is a Claude Code process, takes its `CLAUDE_CODE_OAUTH_TOKEN` and `CLAUDE_CODE_SESSION_ID`. A token's occupancy is the number of distinct `CLAUDE_CODE_SESSION_ID` values bound to it, so child processes that inherit one session id count once. Processes without `CLAUDE_CODE_OAUTH_TOKEN` (for example API-key sessions) are ignored, and a process whose environ cannot be read is skipped. Among the eligible tokens it selects the one with the fewest live sessions (ideally zero), breaking ties by the soonest 7-day reset. As with `selectOauthToken`, each token entry may carry an optional `selectionWeight` (a positive number, default `1`); when the eligible candidates carry differing weights the selection among them becomes weighted-random by that weight, so a token with a smaller weight is chosen proportionally less often, while a token that is the only eligible candidate is always chosen regardless of its weight, and uniform weights preserve the deterministic fewest-live-sessions-then-soonest-reset selection above. The selected token string is written to stdout (pipeable) and the per-candidate decision trace is written to stderr; when no token passes the filter, nothing is written to stdout and the command exits non-zero with an explanatory message on stderr. The token-list path and cache directory are resolved exactly as for `selectOauthToken`. Because occupancy is read from `/proc/<pid>/environ`, this sub-command is Linux-specific.
|
|
122
122
|
|
|
123
123
|
The `countInTmuxByHumanSessionsPerToken` sub-command reports, per Claude Code OAuth token, how many live interactive sessions running under that token belong to an issue that is currently in the GitHub Project Status `In Tmux by human`. It is read-only: it never starts the proxy, never mutates any cache file, and never writes any token anywhere. It enumerates live interactive sessions by scanning running processes on the local Linux host: for each process under `/proc` it reads the NUL-separated `/proc/<pid>/cmdline` and selects only processes launched for an interactive session, identified by a `--name <issue-url>` argument whose value is an HTTP or HTTPS URL; `Take ownership` background spawns (which have no `--name` argument) are excluded. For each selected process it reads `/proc/<pid>/environ` and takes its `CLAUDE_CODE_OAUTH_TOKEN` and `CLAUDE_CODE_SESSION_ID`; a process missing either is skipped, and a process whose files cannot be read is skipped. It then loads the project's issues with their Status (reusing the same on-disk issue cache the daemon uses), keeps only the sessions whose `--name` issue URL maps to an open issue in Status `In Tmux by human`, and counts the distinct `CLAUDE_CODE_SESSION_ID` values per token so child processes that inherit one session id count once. It writes one tab-separated line per token to stdout in the form `<tokenName>\t<count>` (the token name comes from the token list; the raw token value is never printed), and writes the per-run decision trace to stderr. The token-list path comes from `--tokenListJsonPath`, the `claudeCodeOauthTokenListJsonPath` config value, or the `CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH` environment variable. Because occupancy is read from `/proc/<pid>/cmdline` and `/proc/<pid>/environ`, this sub-command is Linux-specific.
|
|
124
124
|
|
|
@@ -9,8 +9,8 @@ const selectionWeightOf = (candidate) => {
|
|
|
9
9
|
exports.selectionWeightOf = selectionWeightOf;
|
|
10
10
|
const SECONDS_PER_DAY = 86400;
|
|
11
11
|
const SEVEN_DAYS_IN_SECONDS = 7 * SECONDS_PER_DAY;
|
|
12
|
-
exports.FIVE_HOUR_MIN_FREE_RATIO = 0.
|
|
13
|
-
exports.SEVEN_DAY_MIN_FREE_RATIO = 0.
|
|
12
|
+
exports.FIVE_HOUR_MIN_FREE_RATIO = 0.25;
|
|
13
|
+
exports.SEVEN_DAY_MIN_FREE_RATIO = 0.03;
|
|
14
14
|
const selectWeightedCandidate = (eligible, candidateOf, deterministicBest, random) => {
|
|
15
15
|
if (eligible.length <= 1) {
|
|
16
16
|
return deterministicBest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OauthTokenSelectUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":";;;AAmBa,QAAA,wBAAwB,GAAG,CAAC,CAAC;AAEnC,MAAM,iBAAiB,GAAG,CAAC,SAA8B,EAAU,EAAE;IAC1E,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,IAAI,gCAAwB,CAAC;IACrE,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC;AAHW,QAAA,iBAAiB,qBAG5B;AAgBF,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,qBAAqB,GAAG,CAAC,GAAG,eAAe,CAAC;AAErC,QAAA,wBAAwB,GAAG,
|
|
1
|
+
{"version":3,"file":"OauthTokenSelectUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -308,8 +308,8 @@ describe('LiveSessionOauthTokenSelectHandler', () => {
|
|
|
308
308
|
expect(output.selectedName).toBeNull();
|
|
309
309
|
const diagnostics = output.diagnostics.join('\n');
|
|
310
310
|
expect(diagnostics).toContain('No eligible token');
|
|
311
|
-
expect(diagnostics).toContain('5h >=
|
|
312
|
-
expect(diagnostics).toContain('7d >=
|
|
311
|
+
expect(diagnostics).toContain('5h >= 25% free');
|
|
312
|
+
expect(diagnostics).toContain('7d >= 3% free');
|
|
313
313
|
expect(diagnostics).not.toContain('fake-busy');
|
|
314
314
|
});
|
|
315
315
|
|
|
@@ -59,11 +59,11 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
59
59
|
expect(result.selected?.token).toBe('fake-token-soon');
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
it('excludes a token whose 5h window is less than
|
|
62
|
+
it('excludes a token whose 5h window is less than 25% free', () => {
|
|
63
63
|
const result = useCase.run(
|
|
64
64
|
[
|
|
65
|
-
candidate('busy5h', snapshot({ fiveHourUtilization: 0.
|
|
66
|
-
candidate('ok', snapshot({ fiveHourUtilization: 0.
|
|
65
|
+
candidate('busy5h', snapshot({ fiveHourUtilization: 0.76 })),
|
|
66
|
+
candidate('ok', snapshot({ fiveHourUtilization: 0.75 })),
|
|
67
67
|
],
|
|
68
68
|
NOW,
|
|
69
69
|
);
|
|
@@ -74,20 +74,22 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
74
74
|
expect(busy?.exclusionReason).toContain('5h window');
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
it('treats exactly
|
|
77
|
+
it('treats exactly 75% used 5h utilization as eligible (boundary)', () => {
|
|
78
78
|
const result = useCase.run(
|
|
79
|
-
[candidate('boundary', snapshot({ fiveHourUtilization: 0.
|
|
79
|
+
[candidate('boundary', snapshot({ fiveHourUtilization: 0.75 }))],
|
|
80
80
|
NOW,
|
|
81
81
|
);
|
|
82
82
|
|
|
83
83
|
expect(result.selected?.name).toBe('boundary');
|
|
84
|
+
const boundary = result.metrics.find((m) => m.name === 'boundary');
|
|
85
|
+
expect(boundary?.fiveHourFreeRatio).toBe(0.25);
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
it('excludes a token whose 7d window is less than
|
|
88
|
+
it('excludes a token whose 7d window is less than 3% free', () => {
|
|
87
89
|
const result = useCase.run(
|
|
88
90
|
[
|
|
89
|
-
candidate('busy7d', snapshot({ sevenDayUtilization: 0.
|
|
90
|
-
candidate('ok', snapshot({ sevenDayUtilization: 0.
|
|
91
|
+
candidate('busy7d', snapshot({ sevenDayUtilization: 0.98 })),
|
|
92
|
+
candidate('ok', snapshot({ sevenDayUtilization: 0.97 })),
|
|
91
93
|
],
|
|
92
94
|
NOW,
|
|
93
95
|
);
|
|
@@ -98,13 +100,33 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
98
100
|
expect(busy?.exclusionReason).toContain('7d window');
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
it('treats
|
|
103
|
+
it('treats exactly 97% used 7d utilization as eligible (boundary)', () => {
|
|
102
104
|
const result = useCase.run(
|
|
103
|
-
[candidate('boundary', snapshot({ sevenDayUtilization: 0.
|
|
105
|
+
[candidate('boundary', snapshot({ sevenDayUtilization: 0.97 }))],
|
|
104
106
|
NOW,
|
|
105
107
|
);
|
|
106
108
|
|
|
107
109
|
expect(result.selected?.name).toBe('boundary');
|
|
110
|
+
const boundary = result.metrics.find((m) => m.name === 'boundary');
|
|
111
|
+
expect(boundary?.sevenDayFreeRatio).toBeCloseTo(0.03, 9);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('proceeds when 5h is exactly 25% free and 7d is exactly 3% free (combined boundary)', () => {
|
|
115
|
+
const result = useCase.run(
|
|
116
|
+
[
|
|
117
|
+
candidate(
|
|
118
|
+
'boundary',
|
|
119
|
+
snapshot({ fiveHourUtilization: 0.75, sevenDayUtilization: 0.97 }),
|
|
120
|
+
),
|
|
121
|
+
],
|
|
122
|
+
NOW,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
expect(result.selected?.name).toBe('boundary');
|
|
126
|
+
const boundary = result.metrics.find((m) => m.name === 'boundary');
|
|
127
|
+
expect(boundary?.eligible).toBe(true);
|
|
128
|
+
expect(boundary?.fiveHourFreeRatio).toBe(0.25);
|
|
129
|
+
expect(boundary?.sevenDayFreeRatio).toBeCloseTo(0.03, 9);
|
|
108
130
|
});
|
|
109
131
|
|
|
110
132
|
it('treats a token with no snapshot as fully free', () => {
|
|
@@ -165,7 +187,7 @@ describe('OauthTokenSelectUseCase', () => {
|
|
|
165
187
|
const result = useCase.run(
|
|
166
188
|
[
|
|
167
189
|
candidate('busy', snapshot({ fiveHourUtilization: 0.9 })),
|
|
168
|
-
candidate('alsoBusy', snapshot({ sevenDayUtilization: 0.
|
|
190
|
+
candidate('alsoBusy', snapshot({ sevenDayUtilization: 0.98 })),
|
|
169
191
|
],
|
|
170
192
|
NOW,
|
|
171
193
|
);
|
|
@@ -41,8 +41,8 @@ export type OauthTokenSelectResult = {
|
|
|
41
41
|
const SECONDS_PER_DAY = 86400;
|
|
42
42
|
const SEVEN_DAYS_IN_SECONDS = 7 * SECONDS_PER_DAY;
|
|
43
43
|
|
|
44
|
-
export const FIVE_HOUR_MIN_FREE_RATIO = 0.
|
|
45
|
-
export const SEVEN_DAY_MIN_FREE_RATIO = 0.
|
|
44
|
+
export const FIVE_HOUR_MIN_FREE_RATIO = 0.25;
|
|
45
|
+
export const SEVEN_DAY_MIN_FREE_RATIO = 0.03;
|
|
46
46
|
|
|
47
47
|
export const selectWeightedCandidate = <Entry>(
|
|
48
48
|
eligible: Entry[],
|
|
@@ -28,8 +28,8 @@ export type OauthTokenSelectResult = {
|
|
|
28
28
|
selected: OauthTokenCandidate | null;
|
|
29
29
|
metrics: OauthTokenCandidateMetrics[];
|
|
30
30
|
};
|
|
31
|
-
export declare const FIVE_HOUR_MIN_FREE_RATIO = 0.
|
|
32
|
-
export declare const SEVEN_DAY_MIN_FREE_RATIO = 0.
|
|
31
|
+
export declare const FIVE_HOUR_MIN_FREE_RATIO = 0.25;
|
|
32
|
+
export declare const SEVEN_DAY_MIN_FREE_RATIO = 0.03;
|
|
33
33
|
export declare const selectWeightedCandidate: <Entry>(eligible: Entry[], candidateOf: (entry: Entry) => OauthTokenCandidate, deterministicBest: Entry, random: SelectionRandom) => Entry;
|
|
34
34
|
export declare class OauthTokenSelectUseCase {
|
|
35
35
|
run: (candidates: OauthTokenCandidate[], nowEpochSeconds: number, random?: SelectionRandom) => OauthTokenSelectResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC;AAE3C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,iBAAiB,GAAI,WAAW,mBAAmB,KAAG,MAGlE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAKF,eAAO,MAAM,wBAAwB,
|
|
1
|
+
{"version":3,"file":"OauthTokenSelectUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC1C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC;AAE3C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,iBAAiB,GAAI,WAAW,mBAAmB,KAAG,MAGlE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,OAAO,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAKF,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,eAAO,MAAM,uBAAuB,GAAI,KAAK,EAC3C,UAAU,KAAK,EAAE,EACjB,aAAa,CAAC,KAAK,EAAE,KAAK,KAAK,mBAAmB,EAClD,mBAAmB,KAAK,EACxB,QAAQ,eAAe,KACtB,KAoBF,CAAC;AAEF,qBAAa,uBAAuB;IAClC,GAAG,GACD,YAAY,mBAAmB,EAAE,EACjC,iBAAiB,MAAM,EACvB,SAAQ,eAA6B,KACpC,sBAAsB,CA2BvB;IAEF,OAAO,CAAC,QAAQ,CAiCd;IAEF,OAAO,CAAC,eAAe,CAuBrB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,iBAAiB,CAWvB;IAEF,OAAO,CAAC,gBAAgB,CActB;IAEF,OAAO,CAAC,aAAa,CAGwC;IAE7D,OAAO,CAAC,wBAAwB,CAG9B;IAEF,OAAO,CAAC,SAAS,CAAsD;CACxE"}
|