claude-dev-env 8.43.5 → 8.43.6
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/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/cli-chain.md +9 -12
- package/_shared/advisor/reference/lifecycle.md +1 -1
- package/_shared/advisor/reference/third-party-bind.md +3 -3
- package/bin/install.test.mjs +1 -1
- package/docs/second-claude-account.md +11 -13
- package/package.json +1 -1
- package/scripts/_code_review_test_support.py +29 -39
- package/scripts/claude_account_worker.py +46 -297
- package/scripts/claude_account_worker_process.py +18 -154
- package/scripts/claude_account_worker_report.py +8 -4
- package/scripts/claude_chain_usage.py +7 -271
- package/scripts/codec_forwarding_test_support.py +11 -65
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +6 -24
- package/scripts/invoke_code_review.py +34 -56
- package/scripts/resolve_worker_spawn.py +40 -72
- package/scripts/test_account_broker_guard.py +0 -4
- package/scripts/test_claude_account_worker.py +105 -350
- package/scripts/test_claude_chain_usage.py +50 -510
- package/scripts/test_dispatcher_profile_import.py +5 -9
- package/scripts/test_invoke_code_review.py +7 -6
- package/scripts/test_invoke_code_review_chain.py +26 -0
- package/scripts/test_invoke_code_review_cli.py +8 -8
- package/scripts/test_invoke_code_review_codec.py +3 -11
- package/scripts/test_invoke_code_review_contract.py +5 -1
- package/scripts/test_resolve_worker_spawn.py +118 -265
- package/scripts/test_resolve_worker_spawn_codec.py +9 -21
- package/scripts/tests/test_code_review_constants.py +2 -2
- package/scripts/claude_account_choice.py +0 -422
- package/scripts/claude_chain_runner.py +0 -1289
- package/scripts/test_claude_account_choice.py +0 -355
- package/scripts/test_claude_chain_runner.py +0 -1839
|
@@ -1,42 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
"""Report remaining weekly usage for every account in the Claude chain.
|
|
3
|
-
|
|
4
|
-
Loads ``~/.claude/claude-chain.json`` (or a path the caller names), probes each
|
|
5
|
-
entry's weekly utilization through the usage-pause OAuth endpoint, and prints a
|
|
6
|
-
JSON report. Ranking is available as an importable helper; ``run_claude``
|
|
7
|
-
consumes that ranking to choose try order.
|
|
8
|
-
|
|
9
|
-
::
|
|
10
|
-
|
|
11
|
-
python claude_chain_usage.py
|
|
12
|
-
{"accounts": [
|
|
13
|
-
{"command": "claude", "weekly_remaining_percent": 58.0},
|
|
14
|
-
{"command": "claude-profile-c", "weekly_remaining_percent": null, "error": "..."}
|
|
15
|
-
]}
|
|
16
|
-
"""
|
|
1
|
+
"""Probe Claude account usage meters for the account broker."""
|
|
17
2
|
|
|
18
3
|
from __future__ import annotations
|
|
19
4
|
|
|
20
|
-
import argparse
|
|
21
5
|
import http.client
|
|
22
6
|
import importlib.util
|
|
23
|
-
import json
|
|
24
7
|
import sys
|
|
25
8
|
import urllib.error
|
|
26
|
-
from collections.abc import Callable
|
|
27
9
|
from dataclasses import dataclass
|
|
28
10
|
from datetime import datetime
|
|
29
11
|
from pathlib import Path
|
|
30
12
|
from types import ModuleType
|
|
31
13
|
|
|
32
|
-
from dev_env_scripts_constants.claude_chain_constants import CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
33
14
|
from dev_env_scripts_constants.claude_chain_usage_constants import (
|
|
34
|
-
CLI_CONFIG_PATH_FLAG,
|
|
35
|
-
FULL_WEEKLY_PERCENT,
|
|
36
|
-
JSON_ACCOUNTS_KEY,
|
|
37
|
-
JSON_COMMAND_KEY,
|
|
38
|
-
JSON_ERROR_KEY,
|
|
39
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY,
|
|
40
15
|
NO_ACCESS_TOKEN_ERROR_TEMPLATE,
|
|
41
16
|
RESOLVE_USAGE_WINDOW_FILENAME,
|
|
42
17
|
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE,
|
|
@@ -46,38 +21,11 @@ from dev_env_scripts_constants.claude_chain_usage_constants import (
|
|
|
46
21
|
USAGE_PAUSE_SKILL_DIRECTORY_NAME,
|
|
47
22
|
USAGE_PAUSE_SKILL_NAME,
|
|
48
23
|
USAGE_PROBE_FAILED_ERROR_TEMPLATE,
|
|
49
|
-
WEEKLY_UTILIZATION_MISSING_ERROR,
|
|
50
24
|
)
|
|
51
25
|
|
|
52
|
-
import claude_chain_runner as chain_runner
|
|
53
|
-
|
|
54
|
-
WeeklyUtilizationProbe = Callable[[Path], float]
|
|
55
|
-
|
|
56
26
|
|
|
57
27
|
class WeeklyUtilizationProbeError(Exception):
|
|
58
|
-
"""Raised when
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@dataclass(frozen=True)
|
|
62
|
-
class AccountUsageReport:
|
|
63
|
-
"""One chain account's remaining weekly usage, or the reason it is unknown."""
|
|
64
|
-
|
|
65
|
-
command: str
|
|
66
|
-
weekly_remaining_percent: float | None
|
|
67
|
-
error: str | None = None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _default_credentials_path() -> Path:
|
|
71
|
-
usage_window_resolver = _load_resolve_usage_window_module()
|
|
72
|
-
return Path.home().joinpath(
|
|
73
|
-
*usage_window_resolver.ALL_CREDENTIALS_RELATIVE_PATH_PARTS
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _credentials_path_for_entry(entry: chain_runner.ChainEntry) -> Path:
|
|
78
|
-
if entry.credentials_path is None:
|
|
79
|
-
return _default_credentials_path()
|
|
80
|
-
return Path(entry.credentials_path).expanduser()
|
|
28
|
+
"""Raised when an account's usage meters cannot be measured."""
|
|
81
29
|
|
|
82
30
|
|
|
83
31
|
def _usage_pause_scripts_directory() -> Path:
|
|
@@ -104,9 +52,7 @@ def _load_resolve_usage_window_module() -> ModuleType:
|
|
|
104
52
|
module_path = scripts_directory / RESOLVE_USAGE_WINDOW_FILENAME
|
|
105
53
|
if not module_path.is_file():
|
|
106
54
|
raise WeeklyUtilizationProbeError(
|
|
107
|
-
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(
|
|
108
|
-
module_path=module_path
|
|
109
|
-
)
|
|
55
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(module_path=module_path)
|
|
110
56
|
)
|
|
111
57
|
scripts_directory_text = str(scripts_directory)
|
|
112
58
|
was_path_inserted = False
|
|
@@ -120,9 +66,7 @@ def _load_resolve_usage_window_module() -> ModuleType:
|
|
|
120
66
|
)
|
|
121
67
|
if module_specification is None or module_specification.loader is None:
|
|
122
68
|
raise WeeklyUtilizationProbeError(
|
|
123
|
-
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(
|
|
124
|
-
module_path=module_path
|
|
125
|
-
)
|
|
69
|
+
RESOLVE_USAGE_WINDOW_MISSING_ERROR_TEMPLATE.format(module_path=module_path)
|
|
126
70
|
)
|
|
127
71
|
loaded_module = importlib.util.module_from_spec(module_specification)
|
|
128
72
|
sys.modules[RESOLVE_USAGE_WINDOW_MODULE_NAME] = loaded_module
|
|
@@ -138,13 +82,7 @@ def _load_resolve_usage_window_module() -> ModuleType:
|
|
|
138
82
|
|
|
139
83
|
@dataclass(frozen=True)
|
|
140
84
|
class AccountUsageMeters:
|
|
141
|
-
"""
|
|
142
|
-
|
|
143
|
-
::
|
|
144
|
-
|
|
145
|
-
{"five_hour": {"utilization": 42, ...}, "seven_day": {...}}
|
|
146
|
-
-> AccountUsageMeters(42.0, <5-hour reset>, 63.0, <weekly reset>)
|
|
147
|
-
"""
|
|
85
|
+
"""An account's short and weekly utilization and reset times."""
|
|
148
86
|
|
|
149
87
|
session_utilization: float | None
|
|
150
88
|
session_resets_at: datetime | None
|
|
@@ -157,9 +95,7 @@ def _fetch_account_usage_payload(
|
|
|
157
95
|
) -> dict[str, object]:
|
|
158
96
|
now = datetime.now().astimezone()
|
|
159
97
|
try:
|
|
160
|
-
access_token = usage_window_resolver.read_oauth_access_token(
|
|
161
|
-
credentials_path, now
|
|
162
|
-
)
|
|
98
|
+
access_token = usage_window_resolver.read_oauth_access_token(credentials_path, now)
|
|
163
99
|
if access_token is None:
|
|
164
100
|
raise WeeklyUtilizationProbeError(
|
|
165
101
|
NO_ACCESS_TOKEN_ERROR_TEMPLATE.format(credentials_path=credentials_path)
|
|
@@ -180,18 +116,7 @@ def _fetch_account_usage_payload(
|
|
|
180
116
|
|
|
181
117
|
|
|
182
118
|
def probe_account_meters(credentials_path: Path) -> AccountUsageMeters:
|
|
183
|
-
"""Read
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
credentials_path: The account's CLI credential file.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
The account's 5-hour and weekly meters.
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
WeeklyUtilizationProbeError: When the credential yields no usable token
|
|
193
|
-
or the probe request fails.
|
|
194
|
-
"""
|
|
119
|
+
"""Read short and weekly usage meters for one account."""
|
|
195
120
|
usage_window_resolver = _load_resolve_usage_window_module()
|
|
196
121
|
usage_payload = _fetch_account_usage_payload(usage_window_resolver, credentials_path)
|
|
197
122
|
usage_windows = usage_window_resolver.extract_usage_windows(usage_payload)
|
|
@@ -201,192 +126,3 @@ def probe_account_meters(credentials_path: Path) -> AccountUsageMeters:
|
|
|
201
126
|
weekly_utilization=usage_windows.weekly_utilization,
|
|
202
127
|
weekly_resets_at=usage_windows.weekly_resets_at,
|
|
203
128
|
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _probe_weekly_utilization(credentials_path: Path) -> float:
|
|
207
|
-
weekly_utilization = probe_account_meters(credentials_path).weekly_utilization
|
|
208
|
-
if weekly_utilization is None:
|
|
209
|
-
raise WeeklyUtilizationProbeError(WEEKLY_UTILIZATION_MISSING_ERROR)
|
|
210
|
-
return float(weekly_utilization)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
weekly_utilization_probe: WeeklyUtilizationProbe = _probe_weekly_utilization
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def weekly_remaining_from_utilization(weekly_utilization: float) -> float:
|
|
217
|
-
"""Convert weekly utilization percent into remaining weekly percent.
|
|
218
|
-
|
|
219
|
-
::
|
|
220
|
-
|
|
221
|
-
weekly_remaining_from_utilization(42.0) -> 58.0
|
|
222
|
-
weekly_remaining_from_utilization(9.0) -> 91.0
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
weekly_utilization: Used weekly capacity on the 0–100 scale.
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
Remaining weekly capacity on the same 0–100 scale.
|
|
229
|
-
"""
|
|
230
|
-
return FULL_WEEKLY_PERCENT - weekly_utilization
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _report_for_entry(
|
|
234
|
-
entry: chain_runner.ChainEntry,
|
|
235
|
-
active_probe: WeeklyUtilizationProbe,
|
|
236
|
-
) -> AccountUsageReport:
|
|
237
|
-
try:
|
|
238
|
-
credentials_path = _credentials_path_for_entry(entry)
|
|
239
|
-
weekly_utilization = active_probe(credentials_path)
|
|
240
|
-
except (WeeklyUtilizationProbeError, ImportError, AttributeError) as probe_error:
|
|
241
|
-
return AccountUsageReport(
|
|
242
|
-
command=entry.command,
|
|
243
|
-
weekly_remaining_percent=None,
|
|
244
|
-
error=str(probe_error),
|
|
245
|
-
)
|
|
246
|
-
return AccountUsageReport(
|
|
247
|
-
command=entry.command,
|
|
248
|
-
weekly_remaining_percent=weekly_remaining_from_utilization(
|
|
249
|
-
weekly_utilization
|
|
250
|
-
),
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def report_chain_weekly_usage(*, config_path: Path) -> list[AccountUsageReport]:
|
|
255
|
-
"""Report remaining weekly usage for every account in the chain config.
|
|
256
|
-
|
|
257
|
-
::
|
|
258
|
-
|
|
259
|
-
report_chain_weekly_usage(config_path=Path("chain.json"))
|
|
260
|
-
-> [AccountUsageReport("claude", 58.0), ...]
|
|
261
|
-
|
|
262
|
-
Uses the module-level ``weekly_utilization_probe`` (tests rebind it).
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
config_path: Chain configuration file to load.
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
One report per chain entry, in config order.
|
|
269
|
-
|
|
270
|
-
Raises:
|
|
271
|
-
ChainConfigurationError: When the chain configuration cannot be loaded.
|
|
272
|
-
"""
|
|
273
|
-
all_entries = chain_runner.load_chain(config_path)
|
|
274
|
-
return [
|
|
275
|
-
_report_for_entry(each_entry, weekly_utilization_probe)
|
|
276
|
-
for each_entry in all_entries
|
|
277
|
-
]
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def _nulls_last_remaining_rank(report: AccountUsageReport) -> tuple[int, float]:
|
|
281
|
-
if report.weekly_remaining_percent is None:
|
|
282
|
-
return (1, 0.0)
|
|
283
|
-
return (0, -report.weekly_remaining_percent)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def rank_accounts_by_weekly_remaining(
|
|
287
|
-
all_reports: list[AccountUsageReport],
|
|
288
|
-
) -> list[AccountUsageReport]:
|
|
289
|
-
"""Order reports by remaining weekly usage, highest first.
|
|
290
|
-
|
|
291
|
-
::
|
|
292
|
-
|
|
293
|
-
[40%, 70%, null, 70%] -> [70% (earlier), 70% (later), 40%, null]
|
|
294
|
-
|
|
295
|
-
Ties keep config order. Unmeasurable accounts (null remaining) sort last.
|
|
296
|
-
|
|
297
|
-
Args:
|
|
298
|
-
all_reports: Reports in chain-config order.
|
|
299
|
-
|
|
300
|
-
Returns:
|
|
301
|
-
A new list ordered by remaining weekly percent descending.
|
|
302
|
-
"""
|
|
303
|
-
return sorted(all_reports, key=_nulls_last_remaining_rank)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def _account_report_to_json_mapping(
|
|
307
|
-
report: AccountUsageReport,
|
|
308
|
-
) -> dict[str, str | float | None]:
|
|
309
|
-
if report.weekly_remaining_percent is None:
|
|
310
|
-
return {
|
|
311
|
-
JSON_COMMAND_KEY: report.command,
|
|
312
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: None,
|
|
313
|
-
JSON_ERROR_KEY: report.error,
|
|
314
|
-
}
|
|
315
|
-
return {
|
|
316
|
-
JSON_COMMAND_KEY: report.command,
|
|
317
|
-
JSON_WEEKLY_REMAINING_PERCENT_KEY: report.weekly_remaining_percent,
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def reports_to_json_payload(
|
|
322
|
-
all_reports: list[AccountUsageReport],
|
|
323
|
-
) -> dict[str, list[dict[str, str | float | None]]]:
|
|
324
|
-
"""Build the CLI JSON payload for a list of account reports.
|
|
325
|
-
|
|
326
|
-
::
|
|
327
|
-
|
|
328
|
-
reports_to_json_payload([AccountUsageReport("claude", 58.0)])
|
|
329
|
-
-> {"accounts": [{"command": "claude", "weekly_remaining_percent": 58.0}]}
|
|
330
|
-
|
|
331
|
-
Args:
|
|
332
|
-
all_reports: Reports in the order they should appear in the payload.
|
|
333
|
-
|
|
334
|
-
Returns:
|
|
335
|
-
The ``{"accounts": [...]}`` envelope.
|
|
336
|
-
"""
|
|
337
|
-
return {
|
|
338
|
-
JSON_ACCOUNTS_KEY: [
|
|
339
|
-
_account_report_to_json_mapping(each_report)
|
|
340
|
-
for each_report in all_reports
|
|
341
|
-
]
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
346
|
-
parser = argparse.ArgumentParser(
|
|
347
|
-
description=(
|
|
348
|
-
"Report remaining weekly usage for every account in the Claude chain."
|
|
349
|
-
)
|
|
350
|
-
)
|
|
351
|
-
parser.add_argument(
|
|
352
|
-
CLI_CONFIG_PATH_FLAG,
|
|
353
|
-
dest="config_path",
|
|
354
|
-
default=None,
|
|
355
|
-
help="Path to claude-chain.json; defaults to the per-user chain config.",
|
|
356
|
-
)
|
|
357
|
-
return parser
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def main(all_command_arguments: list[str]) -> int:
|
|
361
|
-
"""Probe the chain and print the weekly-usage JSON report on stdout.
|
|
362
|
-
|
|
363
|
-
::
|
|
364
|
-
|
|
365
|
-
main(["--config-path", "chain.json"]) -> 0 # JSON on stdout
|
|
366
|
-
main(["--config-path", "missing.json"]) -> 3 # error on stderr
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
all_command_arguments: The argument vector after the program name.
|
|
370
|
-
|
|
371
|
-
Returns:
|
|
372
|
-
Zero on success, or the chain-config error exit code when the config
|
|
373
|
-
cannot be loaded.
|
|
374
|
-
"""
|
|
375
|
-
parser = _build_argument_parser()
|
|
376
|
-
parsed_arguments = parser.parse_args(all_command_arguments)
|
|
377
|
-
resolved_config_path = (
|
|
378
|
-
Path(parsed_arguments.config_path)
|
|
379
|
-
if parsed_arguments.config_path is not None
|
|
380
|
-
else chain_runner.chain_config_path()
|
|
381
|
-
)
|
|
382
|
-
try:
|
|
383
|
-
all_reports = report_chain_weekly_usage(config_path=resolved_config_path)
|
|
384
|
-
except chain_runner.ChainConfigurationError as configuration_error:
|
|
385
|
-
print(str(configuration_error), file=sys.stderr)
|
|
386
|
-
return CHAIN_CONFIG_ERROR_EXIT_CODE
|
|
387
|
-
print(json.dumps(reports_to_json_payload(all_reports), ensure_ascii=True))
|
|
388
|
-
return 0
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if __name__ == "__main__":
|
|
392
|
-
sys.exit(main(sys.argv[1:]))
|
|
@@ -1,85 +1,31 @@
|
|
|
1
|
-
"""Shared fixtures for
|
|
1
|
+
"""Shared fixtures for broker keyword forwarding tests."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import subprocess
|
|
6
|
-
from collections.abc import Sequence
|
|
7
|
-
|
|
8
|
-
import claude_chain_runner as chain_runner
|
|
9
5
|
import pytest
|
|
10
|
-
|
|
11
|
-
from dev_env_scripts_constants.
|
|
6
|
+
|
|
7
|
+
from dev_env_scripts_constants.account_broker_constants import JobOutcome, Product
|
|
12
8
|
|
|
13
9
|
FIXTURE_ENCODING_KEYWORD_NAME = "encoding"
|
|
14
10
|
FIXTURE_ERRORS_KEYWORD_NAME = "errors"
|
|
15
11
|
FIXTURE_CHAIN_ENCODING = "utf-8"
|
|
16
12
|
FIXTURE_CHAIN_ERRORS = "replace"
|
|
17
|
-
FIXTURE_SERVED_COMMAND = "claude"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def build_served_outcome(*, chain_stdout: str) -> ChainInvocationOutcome:
|
|
21
|
-
return ChainInvocationOutcome(
|
|
22
|
-
served_command=FIXTURE_SERVED_COMMAND,
|
|
23
|
-
returncode=0,
|
|
24
|
-
stdout=chain_stdout,
|
|
25
|
-
stderr="",
|
|
26
|
-
attempts=(ChainAttempt(command=FIXTURE_SERVED_COMMAND, status="served"),),
|
|
27
|
-
terminal_status=TERMINAL_STATUS_SERVED,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def build_recording_runner(
|
|
32
|
-
all_observed_runner_keywords: dict[str, object],
|
|
33
|
-
*,
|
|
34
|
-
chain_stdout: str,
|
|
35
|
-
) -> object:
|
|
36
|
-
def _recording_runner(
|
|
37
|
-
all_invocation_tokens: Sequence[str],
|
|
38
|
-
*all_positionals: object,
|
|
39
|
-
**all_keywords: object,
|
|
40
|
-
) -> subprocess.CompletedProcess[str]:
|
|
41
|
-
del all_positionals
|
|
42
|
-
all_observed_runner_keywords.update(all_keywords)
|
|
43
|
-
return subprocess.CompletedProcess(
|
|
44
|
-
args=list(all_invocation_tokens),
|
|
45
|
-
returncode=0,
|
|
46
|
-
stdout=chain_stdout,
|
|
47
|
-
stderr="",
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
return _recording_runner
|
|
51
13
|
|
|
52
14
|
|
|
53
15
|
def install_codec_seams(
|
|
54
16
|
monkeypatch: pytest.MonkeyPatch,
|
|
55
17
|
*,
|
|
56
|
-
all_chain_codec_keywords: dict[str, str],
|
|
57
18
|
chain_stdout: str,
|
|
58
19
|
runner_host: object,
|
|
59
20
|
runner_attribute_name: str,
|
|
60
21
|
) -> dict[str, object]:
|
|
61
|
-
|
|
22
|
+
observed_options: dict[str, object] = {}
|
|
62
23
|
|
|
63
|
-
def
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
capture_output=True,
|
|
69
|
-
text=True,
|
|
70
|
-
timeout=timeout_seconds,
|
|
71
|
-
check=False,
|
|
72
|
-
**all_chain_codec_keywords,
|
|
73
|
-
)
|
|
74
|
-
return build_served_outcome(chain_stdout=chain_stdout)
|
|
24
|
+
def recording_runner(product: Product, argv: list[str], **options: object) -> JobOutcome:
|
|
25
|
+
assert product is Product.CLAUDE
|
|
26
|
+
assert argv[0] == "claude"
|
|
27
|
+
observed_options.update(options)
|
|
28
|
+
return JobOutcome(0, chain_stdout, "", "main", (("main", "served"),), "served", None, None)
|
|
75
29
|
|
|
76
|
-
monkeypatch.setattr(
|
|
77
|
-
|
|
78
|
-
"chain_subprocess_runner",
|
|
79
|
-
build_recording_runner(
|
|
80
|
-
all_observed_runner_keywords,
|
|
81
|
-
chain_stdout=chain_stdout,
|
|
82
|
-
),
|
|
83
|
-
)
|
|
84
|
-
monkeypatch.setattr(runner_host, runner_attribute_name, _fake_claude)
|
|
85
|
-
return all_observed_runner_keywords
|
|
30
|
+
monkeypatch.setattr(runner_host, runner_attribute_name, recording_runner)
|
|
31
|
+
return observed_options
|
|
@@ -2,19 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from dev_env_scripts_constants.
|
|
6
|
-
|
|
7
|
-
CHOICE_SECOND,
|
|
8
|
-
CHOICE_WAIT,
|
|
9
|
-
CLAUDE_BINARY_NAME,
|
|
10
|
-
CREDENTIALS_FILE_NAME,
|
|
11
|
-
EXTRA_PROFILES_FILE_NAME,
|
|
12
|
-
JSON_ACCOUNT_KEY,
|
|
13
|
-
JSON_CONFIG_DIRECTORY_KEY,
|
|
14
|
-
JSON_REASON_KEY,
|
|
15
|
-
MAIN_CLAUDE_HOME_DIRECTORY_NAME,
|
|
16
|
-
)
|
|
17
|
-
from dev_env_scripts_constants.shared_tree_constants import CLAUDE_CONFIG_DIR_ENV_VAR
|
|
5
|
+
from dev_env_scripts_constants.account_broker_constants import WAIT_EXIT_CODE
|
|
6
|
+
from dev_env_scripts_constants.claude_account_constants import CLAUDE_BINARY_NAME, JSON_ACCOUNT_KEY, JSON_REASON_KEY
|
|
18
7
|
|
|
19
8
|
DEFAULT_PERMISSION_MODE: str = "auto"
|
|
20
9
|
|
|
@@ -22,14 +11,14 @@ DEFAULT_TIMEOUT_MINUTES: int = 60
|
|
|
22
11
|
|
|
23
12
|
MINIMUM_TIMEOUT_MINUTES: int = 1
|
|
24
13
|
|
|
25
|
-
WAIT_EXIT_CODE: int = 3
|
|
26
|
-
|
|
27
14
|
TIMEOUT_EXIT_CODE: int = 124
|
|
28
15
|
|
|
29
16
|
MISSING_BINARY_EXIT_CODE: int = 127
|
|
30
17
|
|
|
31
18
|
LAUNCH_FAILURE_EXIT_CODE: int = 1
|
|
32
19
|
|
|
20
|
+
CONFIGURATION_FAILURE_EXIT_CODE: int = 2
|
|
21
|
+
|
|
33
22
|
WAIT_DURATION_SECONDS: float = 0.0
|
|
34
23
|
|
|
35
24
|
STDOUT_TAIL_CHARACTER_LIMIT: int = 4000
|
|
@@ -46,7 +35,7 @@ UTF8_ENCODING: str = "utf-8"
|
|
|
46
35
|
|
|
47
36
|
UTF8_DECODE_ERRORS: str = "replace"
|
|
48
37
|
|
|
49
|
-
CLI_DESCRIPTION: str = "Run one Claude worker
|
|
38
|
+
CLI_DESCRIPTION: str = "Run one Claude worker through the account broker."
|
|
50
39
|
|
|
51
40
|
PROMPT_FILE_FLAG: str = "--prompt-file"
|
|
52
41
|
|
|
@@ -90,28 +79,21 @@ INVALID_TIMEOUT_MESSAGE: str = "--timeout-minutes must be at least 1"
|
|
|
90
79
|
|
|
91
80
|
|
|
92
81
|
__all__ = [
|
|
93
|
-
"CHOICE_MAIN",
|
|
94
|
-
"CHOICE_SECOND",
|
|
95
|
-
"CHOICE_WAIT",
|
|
96
82
|
"CLAUDE_BINARY_NAME",
|
|
97
|
-
"CLAUDE_CONFIG_DIR_ENV_VAR",
|
|
98
83
|
"CLI_DESCRIPTION",
|
|
99
|
-
"
|
|
84
|
+
"CONFIGURATION_FAILURE_EXIT_CODE",
|
|
100
85
|
"CWD_FLAG",
|
|
101
86
|
"DEFAULT_PERMISSION_MODE",
|
|
102
87
|
"DEFAULT_TIMEOUT_MINUTES",
|
|
103
88
|
"DRAIN_ATTEMPT_LIMIT",
|
|
104
89
|
"DRAIN_GRACE_TIMEOUT_SECONDS",
|
|
105
90
|
"DURATION_DECIMAL_PLACES",
|
|
106
|
-
"EXTRA_PROFILES_FILE_NAME",
|
|
107
91
|
"INVALID_TIMEOUT_MESSAGE",
|
|
108
92
|
"JSON_ACCOUNT_KEY",
|
|
109
|
-
"JSON_CONFIG_DIRECTORY_KEY",
|
|
110
93
|
"JSON_IS_ERROR_KEY",
|
|
111
94
|
"JSON_REASON_KEY",
|
|
112
95
|
"JSON_RESULT_KEY",
|
|
113
96
|
"LAUNCH_FAILURE_EXIT_CODE",
|
|
114
|
-
"MAIN_CLAUDE_HOME_DIRECTORY_NAME",
|
|
115
97
|
"MISSING_BINARY_EXIT_CODE",
|
|
116
98
|
"MINIMUM_TIMEOUT_MINUTES",
|
|
117
99
|
"MODEL_FLAG",
|