claude-dev-env 8.35.0 → 8.35.1
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/package.json +1 -1
- package/scripts/claude_account_profile.py +18 -18
- package/scripts/codex_account_choice.py +3 -7
- package/scripts/dev_env_scripts_constants/claude_account_constants.py +31 -11
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +1 -2
- package/scripts/dev_env_scripts_constants/codex_account_constants.py +9 -17
- package/scripts/dev_env_scripts_constants/test_claude_account_constants.py +0 -6
- package/scripts/test_claude_account_profile.py +85 -7
- package/scripts/test_codex_account_choice.py +0 -35
package/package.json
CHANGED
|
@@ -28,14 +28,15 @@ from dev_env_scripts_constants.claude_account_constants import (
|
|
|
28
28
|
ALL_WINDOWS_JUNCTION_COMMAND_PREFIX,
|
|
29
29
|
CHOICE_MAIN,
|
|
30
30
|
CHOICE_WAIT,
|
|
31
|
+
CLAUDE_LAUNCHER_PROGRAM,
|
|
31
32
|
JSON_LAUNCHER_KEY,
|
|
32
33
|
JSON_LINKED_KEY,
|
|
33
34
|
JSON_MOVED_ASIDE_KEY,
|
|
34
35
|
JSON_UNLINKED_KEY,
|
|
35
36
|
ALL_LAUNCHER_DIRECTORY_RELATIVE_PARTS,
|
|
36
|
-
|
|
37
|
+
LAUNCHER_BODY_TEMPLATE,
|
|
37
38
|
LAUNCHER_REPLACED_SUFFIX,
|
|
38
|
-
|
|
39
|
+
LauncherProgram,
|
|
39
40
|
MAIN_CLAUDE_HOME_DIRECTORY_NAME,
|
|
40
41
|
PROFILES_ROOT_DIRECTORY_NAME,
|
|
41
42
|
PROFILES_ROOT_ENVIRONMENT_VARIABLE,
|
|
@@ -343,16 +344,14 @@ def move_launcher_aside(launcher_path: Path, now: datetime) -> Path:
|
|
|
343
344
|
|
|
344
345
|
|
|
345
346
|
def launcher_path(
|
|
346
|
-
launcher_directory: Path,
|
|
347
|
-
profile_name: str,
|
|
348
|
-
launcher_file_name_template: str = LAUNCHER_FILE_NAME_TEMPLATE,
|
|
347
|
+
launcher_directory: Path, profile_name: str, launcher_program: LauncherProgram
|
|
349
348
|
) -> Path:
|
|
350
349
|
"""Name the launcher file for a profile.
|
|
351
350
|
|
|
352
351
|
Args:
|
|
353
352
|
launcher_directory: The directory on PATH that holds the launcher.
|
|
354
353
|
profile_name: The name used in the launcher file name.
|
|
355
|
-
|
|
354
|
+
launcher_program: The program the launcher runs, which names the file.
|
|
356
355
|
|
|
357
356
|
Returns:
|
|
358
357
|
The launcher path.
|
|
@@ -360,7 +359,7 @@ def launcher_path(
|
|
|
360
359
|
Raises:
|
|
361
360
|
ValueError: When the profile name is not a valid profile name.
|
|
362
361
|
"""
|
|
363
|
-
return launcher_directory /
|
|
362
|
+
return launcher_directory / launcher_program.file_name_template.format(
|
|
364
363
|
profile_name=validate_profile_name(profile_name)
|
|
365
364
|
)
|
|
366
365
|
|
|
@@ -370,33 +369,33 @@ def write_launcher(
|
|
|
370
369
|
launcher_directory: Path,
|
|
371
370
|
profile_home: Path,
|
|
372
371
|
now: datetime,
|
|
373
|
-
profile_name: str
|
|
374
|
-
|
|
375
|
-
launcher_text_template: str = LAUNCHER_TEXT_TEMPLATE,
|
|
372
|
+
profile_name: str,
|
|
373
|
+
launcher_program: LauncherProgram,
|
|
376
374
|
) -> Path:
|
|
377
|
-
"""Write the launcher that runs
|
|
375
|
+
"""Write the launcher that runs a program under a named profile.
|
|
378
376
|
|
|
379
377
|
::
|
|
380
378
|
|
|
381
379
|
claude-NAME -p "fix the test"
|
|
382
|
-
-> CLAUDE_CONFIG_DIR=<profile home>, then claude -p "fix the test"
|
|
380
|
+
-> CLAUDE_CONFIG_DIR=<profile home>, then call claude -p "fix the test"
|
|
383
381
|
an older claude-NAME.cmd -> claude-NAME.cmd.replaced-<time>
|
|
384
382
|
|
|
385
383
|
Args:
|
|
386
384
|
launcher_directory: The directory on PATH that holds the launcher.
|
|
387
|
-
profile_home: The named account's
|
|
385
|
+
profile_home: The named account's home for the program.
|
|
388
386
|
now: The run time that names a moved older launcher.
|
|
389
387
|
profile_name: The name used in the launcher file name.
|
|
390
|
-
|
|
391
|
-
launcher_text_template: The launcher body, with ``{profile_home}``.
|
|
388
|
+
launcher_program: The program to run and the variable naming its home.
|
|
392
389
|
|
|
393
390
|
Returns:
|
|
394
391
|
The launcher path.
|
|
395
392
|
"""
|
|
396
|
-
target_path = launcher_path(
|
|
397
|
-
|
|
393
|
+
target_path = launcher_path(launcher_directory, profile_name, launcher_program)
|
|
394
|
+
launcher_text = LAUNCHER_BODY_TEMPLATE.format(
|
|
395
|
+
environment_variable=launcher_program.environment_variable,
|
|
396
|
+
profile_home=profile_home,
|
|
397
|
+
program=launcher_program.program,
|
|
398
398
|
)
|
|
399
|
-
launcher_text = launcher_text_template.format(profile_home=profile_home)
|
|
400
399
|
launcher_bytes = launcher_text.encode(TEXT_ENCODING)
|
|
401
400
|
if target_path.is_file() and target_path.read_bytes() == launcher_bytes:
|
|
402
401
|
return target_path
|
|
@@ -454,6 +453,7 @@ def _sync_named_profile(arguments: argparse.Namespace) -> dict[str, object]:
|
|
|
454
453
|
profile_home=profile_home,
|
|
455
454
|
now=now,
|
|
456
455
|
profile_name=arguments.profile_name,
|
|
456
|
+
launcher_program=CLAUDE_LAUNCHER_PROGRAM,
|
|
457
457
|
)
|
|
458
458
|
return {**sync_report_payload(report), JSON_LAUNCHER_KEY: str(launcher_path)}
|
|
459
459
|
|
|
@@ -71,8 +71,7 @@ from dev_env_scripts_constants.codex_account_constants import (
|
|
|
71
71
|
CODEX_ACCOUNT_NAME_SEPARATOR,
|
|
72
72
|
CODEX_ACCOUNT_PROFILES_ENVIRONMENT_VARIABLE,
|
|
73
73
|
CODEX_AUTH_FILE_NAME,
|
|
74
|
-
|
|
75
|
-
CODEX_LAUNCHER_TEXT_TEMPLATE,
|
|
74
|
+
CODEX_LAUNCHER_PROGRAM,
|
|
76
75
|
CODEX_PROFILES_ROOT_DIRECTORY_NAME,
|
|
77
76
|
CODEX_PROFILES_ROOT_ENVIRONMENT_VARIABLE,
|
|
78
77
|
COMMAND_INSTALL,
|
|
@@ -285,8 +284,7 @@ def _install_account(
|
|
|
285
284
|
profile_home=profiles_root / name,
|
|
286
285
|
now=now,
|
|
287
286
|
profile_name=name,
|
|
288
|
-
|
|
289
|
-
launcher_text_template=CODEX_LAUNCHER_TEXT_TEMPLATE,
|
|
287
|
+
launcher_program=CODEX_LAUNCHER_PROGRAM,
|
|
290
288
|
)
|
|
291
289
|
return {**sync_payload, JSON_LAUNCHER_KEY: str(launcher_path)}
|
|
292
290
|
|
|
@@ -338,9 +336,7 @@ def _retire_codex_account_launchers(
|
|
|
338
336
|
"""
|
|
339
337
|
all_retired: dict[str, str] = {}
|
|
340
338
|
for each_name in all_names:
|
|
341
|
-
retired_path = launcher_path(
|
|
342
|
-
launcher_directory, each_name, CODEX_LAUNCHER_FILE_NAME_TEMPLATE
|
|
343
|
-
)
|
|
339
|
+
retired_path = launcher_path(launcher_directory, each_name, CODEX_LAUNCHER_PROGRAM)
|
|
344
340
|
if retired_path.is_file():
|
|
345
341
|
all_retired[each_name] = str(move_launcher_aside(retired_path, now))
|
|
346
342
|
return all_retired
|
|
@@ -8,8 +8,11 @@ main account so its owner never runs out.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
from dataclasses import dataclass
|
|
11
12
|
from datetime import timedelta
|
|
12
13
|
|
|
14
|
+
from dev_env_scripts_constants.shared_tree_constants import CLAUDE_CONFIG_DIR_ENV_VAR
|
|
15
|
+
|
|
13
16
|
MAIN_CLAUDE_HOME_DIRECTORY_NAME: str = ".claude"
|
|
14
17
|
"""Directory under the user home that holds the main account's Claude home."""
|
|
15
18
|
|
|
@@ -38,22 +41,39 @@ ALL_WINDOWS_RESERVED_PROFILE_NAMES: frozenset[str] = frozenset(
|
|
|
38
41
|
ALL_LAUNCHER_DIRECTORY_RELATIVE_PARTS: tuple[str, ...] = (".local", "bin")
|
|
39
42
|
"""Path parts under the user home of the directory on PATH that holds the launcher."""
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
LAUNCHER_FILE_NAME: str = LAUNCHER_FILE_NAME_TEMPLATE.format(
|
|
45
|
-
profile_name=SECOND_ACCOUNT_PROFILE_NAME
|
|
46
|
-
)
|
|
47
|
-
"""File name of the launcher that runs Claude under the second account."""
|
|
44
|
+
CLAUDE_BINARY_NAME: str = "claude"
|
|
45
|
+
"""Claude command name looked up on PATH."""
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
LAUNCHER_BODY_TEMPLATE: str = (
|
|
50
48
|
"@echo off\r\n"
|
|
51
49
|
"setlocal\r\n"
|
|
52
|
-
'set "
|
|
53
|
-
"
|
|
50
|
+
'set "{environment_variable}={profile_home}"\r\n'
|
|
51
|
+
"call {program} %*\r\n"
|
|
54
52
|
"exit /b %ERRORLEVEL%\r\n"
|
|
55
53
|
)
|
|
56
|
-
"""Launcher body: point
|
|
54
|
+
"""Launcher body: point a program at the profile home and pass every argument through.
|
|
55
|
+
|
|
56
|
+
The program resolves to npm's ``<program>.cmd``, whose last line runs ``endLocal``
|
|
57
|
+
and a ``goto`` to a missing label. Chained into without ``call``, that line ends
|
|
58
|
+
this launcher's ``setlocal`` too, and the program starts without the variable.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class LauncherProgram:
|
|
64
|
+
"""The program a profile launcher runs and the variable naming its home."""
|
|
65
|
+
|
|
66
|
+
program: str
|
|
67
|
+
environment_variable: str
|
|
68
|
+
file_name_template: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
CLAUDE_LAUNCHER_PROGRAM: LauncherProgram = LauncherProgram(
|
|
72
|
+
program=CLAUDE_BINARY_NAME,
|
|
73
|
+
environment_variable=CLAUDE_CONFIG_DIR_ENV_VAR,
|
|
74
|
+
file_name_template="claude-{profile_name}.cmd",
|
|
75
|
+
)
|
|
76
|
+
"""Launcher that runs Claude with ``CLAUDE_CONFIG_DIR`` set to the profile home."""
|
|
57
77
|
|
|
58
78
|
LAUNCHER_REPLACED_SUFFIX: str = ".replaced-"
|
|
59
79
|
"""Suffix, before the run time, of an older launcher the sync moved aside."""
|
|
@@ -6,6 +6,7 @@ from dev_env_scripts_constants.claude_account_constants import (
|
|
|
6
6
|
CHOICE_MAIN,
|
|
7
7
|
CHOICE_SECOND,
|
|
8
8
|
CHOICE_WAIT,
|
|
9
|
+
CLAUDE_BINARY_NAME,
|
|
9
10
|
CREDENTIALS_FILE_NAME,
|
|
10
11
|
EXTRA_PROFILES_FILE_NAME,
|
|
11
12
|
JSON_ACCOUNT_KEY,
|
|
@@ -15,8 +16,6 @@ from dev_env_scripts_constants.claude_account_constants import (
|
|
|
15
16
|
)
|
|
16
17
|
from dev_env_scripts_constants.shared_tree_constants import CLAUDE_CONFIG_DIR_ENV_VAR
|
|
17
18
|
|
|
18
|
-
CLAUDE_BINARY_NAME: str = "claude"
|
|
19
|
-
|
|
20
19
|
DEFAULT_PERMISSION_MODE: str = "auto"
|
|
21
20
|
|
|
22
21
|
DEFAULT_TIMEOUT_MINUTES: int = 60
|
|
@@ -7,6 +7,8 @@ a job runs on, trying the accounts in roster order.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
from dev_env_scripts_constants.claude_account_constants import LauncherProgram
|
|
11
|
+
|
|
10
12
|
ALL_CODEX_ACCOUNT_NAMES: tuple[str, ...] = ("codex-1", "codex-2", "codex-3", "codex-4")
|
|
11
13
|
"""Fallback Codex accounts, in try order, when no account roster is saved or set."""
|
|
12
14
|
|
|
@@ -19,23 +21,6 @@ CODEX_ACCOUNT_LAUNCHERS_FILE_NAME: str = "account-launchers.json"
|
|
|
19
21
|
CODEX_ACCOUNT_NAME_SEPARATOR: str = ","
|
|
20
22
|
"""Separator between account names in the roster environment variable."""
|
|
21
23
|
|
|
22
|
-
CODEX_LAUNCHER_FILE_NAME_TEMPLATE: str = "codex-{profile_name}.cmd"
|
|
23
|
-
"""File name pattern for one account's Codex launcher."""
|
|
24
|
-
|
|
25
|
-
CODEX_LAUNCHER_TEXT_TEMPLATE: str = (
|
|
26
|
-
"@echo off\r\n"
|
|
27
|
-
"setlocal\r\n"
|
|
28
|
-
'set "CODEX_HOME={profile_home}"\r\n'
|
|
29
|
-
"call codex %*\r\n"
|
|
30
|
-
"exit /b %ERRORLEVEL%\r\n"
|
|
31
|
-
)
|
|
32
|
-
"""Launcher body: point Codex at the account's home and pass every argument through.
|
|
33
|
-
|
|
34
|
-
``codex`` resolves to npm's ``codex.cmd``, whose last line runs ``endLocal`` and a
|
|
35
|
-
``goto`` to a missing label. Chained into without ``call``, that line ends this
|
|
36
|
-
launcher's ``setlocal`` too, and Codex starts without ``CODEX_HOME``.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
24
|
SETUP_PROMPT_TEXT: str = "Name for this Codex account launcher (blank to finish): "
|
|
40
25
|
"""Prompt the setup command shows for each account name."""
|
|
41
26
|
|
|
@@ -134,6 +119,13 @@ ALL_CODEX_BINARY_CANDIDATE_RELATIVE_PARTS: tuple[tuple[str, ...], ...] = (
|
|
|
134
119
|
CODEX_BINARY_NAME: str = "codex"
|
|
135
120
|
"""Codex command name looked up on PATH."""
|
|
136
121
|
|
|
122
|
+
CODEX_LAUNCHER_PROGRAM: LauncherProgram = LauncherProgram(
|
|
123
|
+
program=CODEX_BINARY_NAME,
|
|
124
|
+
environment_variable=CODEX_HOME_ENVIRONMENT_VARIABLE,
|
|
125
|
+
file_name_template="codex-{profile_name}.cmd",
|
|
126
|
+
)
|
|
127
|
+
"""Launcher that runs Codex with ``CODEX_HOME`` set to the account's home."""
|
|
128
|
+
|
|
137
129
|
ALL_APP_SERVER_ARGUMENTS: tuple[str, ...] = ("app-server", "--listen", "stdio://")
|
|
138
130
|
"""Arguments that start Codex as a JSON-RPC server on standard input and output."""
|
|
139
131
|
|
|
@@ -11,7 +11,6 @@ from dev_env_scripts_constants.claude_account_constants import (
|
|
|
11
11
|
JSON_SESSION_USED_PERCENT_KEY,
|
|
12
12
|
JSON_WEEKLY_RESETS_AT_KEY,
|
|
13
13
|
JSON_WEEKLY_USED_PERCENT_KEY,
|
|
14
|
-
LAUNCHER_TEXT_TEMPLATE,
|
|
15
14
|
MAIN_SESSION_USED_CEILING_PERCENT,
|
|
16
15
|
MAIN_WEEKLY_USED_CEILING_PERCENT,
|
|
17
16
|
SECOND_SESSION_USED_CEILING_PERCENT,
|
|
@@ -24,11 +23,6 @@ def test_main_ceilings_sit_below_the_second_account_ceilings() -> None:
|
|
|
24
23
|
assert MAIN_SESSION_USED_CEILING_PERCENT < SECOND_SESSION_USED_CEILING_PERCENT
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def test_launcher_names_the_profile_and_passes_every_argument() -> None:
|
|
28
|
-
assert "{profile_home}" in LAUNCHER_TEXT_TEMPLATE
|
|
29
|
-
assert "claude %*" in LAUNCHER_TEXT_TEMPLATE
|
|
30
|
-
|
|
31
|
-
|
|
32
26
|
def test_picker_report_keys_never_collide() -> None:
|
|
33
27
|
all_report_keys = [
|
|
34
28
|
JSON_ACCOUNT_KEY,
|
|
@@ -4,12 +4,18 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import subprocess
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
import pytest
|
|
11
12
|
|
|
12
13
|
import claude_account_profile as profile
|
|
14
|
+
from dev_env_scripts_constants.claude_account_constants import (
|
|
15
|
+
CLAUDE_LAUNCHER_PROGRAM,
|
|
16
|
+
LauncherProgram,
|
|
17
|
+
)
|
|
18
|
+
from dev_env_scripts_constants.codex_account_constants import CODEX_LAUNCHER_PROGRAM
|
|
13
19
|
|
|
14
20
|
NOW = datetime(2026, 9, 22, 21, 0, tzinfo=timezone.utc)
|
|
15
21
|
|
|
@@ -201,11 +207,15 @@ class TestWriteLauncher:
|
|
|
201
207
|
) -> None:
|
|
202
208
|
profile_home = tmp_path / "ev"
|
|
203
209
|
launcher_path = profile.write_launcher(
|
|
204
|
-
launcher_directory=tmp_path / "bin",
|
|
210
|
+
launcher_directory=tmp_path / "bin",
|
|
211
|
+
profile_home=profile_home,
|
|
212
|
+
now=NOW,
|
|
213
|
+
profile_name="ev",
|
|
214
|
+
launcher_program=CLAUDE_LAUNCHER_PROGRAM,
|
|
205
215
|
)
|
|
206
216
|
launcher_text = launcher_path.read_text(encoding="utf-8")
|
|
207
217
|
assert f'set "CLAUDE_CONFIG_DIR={profile_home}"' in launcher_text
|
|
208
|
-
assert "claude %*" in launcher_text
|
|
218
|
+
assert "call claude %*" in launcher_text
|
|
209
219
|
|
|
210
220
|
def test_should_move_an_older_launcher_aside(self, tmp_path: Path) -> None:
|
|
211
221
|
launcher_directory = tmp_path / "bin"
|
|
@@ -214,14 +224,18 @@ class TestWriteLauncher:
|
|
|
214
224
|
"old launcher", encoding="utf-8"
|
|
215
225
|
)
|
|
216
226
|
profile.write_launcher(
|
|
217
|
-
launcher_directory=launcher_directory,
|
|
227
|
+
launcher_directory=launcher_directory,
|
|
228
|
+
profile_home=tmp_path / "ev",
|
|
229
|
+
now=NOW,
|
|
230
|
+
profile_name="ev",
|
|
231
|
+
launcher_program=CLAUDE_LAUNCHER_PROGRAM,
|
|
218
232
|
)
|
|
219
233
|
moved_launcher = launcher_directory / (
|
|
220
234
|
"claude-ev.cmd.replaced-" + NOW.strftime("%Y%m%dT%H%M%SZ")
|
|
221
235
|
)
|
|
222
236
|
assert moved_launcher.read_text(encoding="utf-8") == "old launcher"
|
|
223
237
|
|
|
224
|
-
def
|
|
238
|
+
def test_should_write_the_launcher_for_the_callers_program(
|
|
225
239
|
self, tmp_path: Path
|
|
226
240
|
) -> None:
|
|
227
241
|
launcher_path = profile.write_launcher(
|
|
@@ -229,11 +243,75 @@ class TestWriteLauncher:
|
|
|
229
243
|
profile_home=tmp_path / "alpha",
|
|
230
244
|
now=NOW,
|
|
231
245
|
profile_name="alpha",
|
|
232
|
-
|
|
233
|
-
|
|
246
|
+
launcher_program=LauncherProgram(
|
|
247
|
+
program="tool",
|
|
248
|
+
environment_variable="TOOL_HOME",
|
|
249
|
+
file_name_template="tool-{profile_name}.cmd",
|
|
250
|
+
),
|
|
234
251
|
)
|
|
235
252
|
assert launcher_path == tmp_path / "bin" / "tool-alpha.cmd"
|
|
236
|
-
assert launcher_path.
|
|
253
|
+
assert launcher_path.read_bytes() == (
|
|
254
|
+
"@echo off\r\n"
|
|
255
|
+
"setlocal\r\n"
|
|
256
|
+
f'set "TOOL_HOME={tmp_path / "alpha"}"\r\n'
|
|
257
|
+
"call tool %*\r\n"
|
|
258
|
+
"exit /b %ERRORLEVEL%\r\n"
|
|
259
|
+
).encode()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
NPM_SHIM_TEXT_TEMPLATE = (
|
|
263
|
+
"@ECHO off\r\n"
|
|
264
|
+
"GOTO start\r\n"
|
|
265
|
+
":find_dp0\r\n"
|
|
266
|
+
"SET dp0=%~dp0\r\n"
|
|
267
|
+
"EXIT /b\r\n"
|
|
268
|
+
":start\r\n"
|
|
269
|
+
"SETLOCAL\r\n"
|
|
270
|
+
"CALL :find_dp0\r\n"
|
|
271
|
+
'SET "_prog=cmd"\r\n'
|
|
272
|
+
"endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% &"
|
|
273
|
+
' "%_prog%" /d /c "echo %%{environment_variable}%%"\r\n'
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class TestNpmShimLauncher:
|
|
278
|
+
@pytest.mark.skipif(os.name != "nt", reason="runs the launcher through cmd.exe")
|
|
279
|
+
@pytest.mark.parametrize(
|
|
280
|
+
"launcher_program",
|
|
281
|
+
[
|
|
282
|
+
pytest.param(CLAUDE_LAUNCHER_PROGRAM, id="claude"),
|
|
283
|
+
pytest.param(CODEX_LAUNCHER_PROGRAM, id="codex"),
|
|
284
|
+
],
|
|
285
|
+
)
|
|
286
|
+
def should_hand_the_profile_home_to_the_npm_shim(
|
|
287
|
+
self, tmp_path: Path, launcher_program: LauncherProgram
|
|
288
|
+
) -> None:
|
|
289
|
+
profile_home = tmp_path / "profiles" / "alpha"
|
|
290
|
+
launcher_path = profile.write_launcher(
|
|
291
|
+
launcher_directory=tmp_path / "bin",
|
|
292
|
+
profile_home=profile_home,
|
|
293
|
+
now=NOW,
|
|
294
|
+
profile_name="alpha",
|
|
295
|
+
launcher_program=launcher_program,
|
|
296
|
+
)
|
|
297
|
+
fake_directory = tmp_path / "fake"
|
|
298
|
+
fake_directory.mkdir()
|
|
299
|
+
(fake_directory / f"{launcher_program.program}.cmd").write_bytes(
|
|
300
|
+
NPM_SHIM_TEXT_TEMPLATE.format(
|
|
301
|
+
environment_variable=launcher_program.environment_variable
|
|
302
|
+
).encode("utf-8")
|
|
303
|
+
)
|
|
304
|
+
environment = dict(os.environ)
|
|
305
|
+
environment["PATH"] = str(fake_directory) + os.pathsep + environment["PATH"]
|
|
306
|
+
environment[launcher_program.environment_variable] = str(tmp_path / "decoy")
|
|
307
|
+
completed = subprocess.run(
|
|
308
|
+
["cmd", "/c", str(launcher_path)],
|
|
309
|
+
env=environment,
|
|
310
|
+
capture_output=True,
|
|
311
|
+
text=True,
|
|
312
|
+
check=False,
|
|
313
|
+
)
|
|
314
|
+
assert completed.stdout.strip() == str(profile_home)
|
|
237
315
|
|
|
238
316
|
|
|
239
317
|
class TestMoveLauncherAside:
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
-
import subprocess
|
|
8
7
|
from collections.abc import Callable, Iterator
|
|
9
8
|
from datetime import datetime, timedelta, timezone
|
|
10
9
|
from pathlib import Path
|
|
@@ -26,19 +25,6 @@ ALL_SHARED_ENTRY_NAMES = (
|
|
|
26
25
|
"rules",
|
|
27
26
|
"skills",
|
|
28
27
|
)
|
|
29
|
-
NPM_CODEX_SHIM_TEXT = (
|
|
30
|
-
"@ECHO off\r\n"
|
|
31
|
-
"GOTO start\r\n"
|
|
32
|
-
":find_dp0\r\n"
|
|
33
|
-
"SET dp0=%~dp0\r\n"
|
|
34
|
-
"EXIT /b\r\n"
|
|
35
|
-
":start\r\n"
|
|
36
|
-
"SETLOCAL\r\n"
|
|
37
|
-
"CALL :find_dp0\r\n"
|
|
38
|
-
'SET "_prog=cmd"\r\n'
|
|
39
|
-
"endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% &"
|
|
40
|
-
' "%_prog%" /d /c "echo %%CODEX_HOME%%"\r\n'
|
|
41
|
-
)
|
|
42
28
|
|
|
43
29
|
|
|
44
30
|
@pytest.fixture(autouse=True)
|
|
@@ -621,24 +607,3 @@ class TestSetupRoster:
|
|
|
621
607
|
"beta sign-in"
|
|
622
608
|
)
|
|
623
609
|
assert choice.saved_codex_account_names(profiles_root) == ("alpha",)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
class TestCodexLauncher:
|
|
627
|
-
@pytest.mark.skipif(os.name != "nt", reason="runs the launcher through cmd.exe")
|
|
628
|
-
def should_hand_codex_home_to_the_npm_codex_shim(self, tmp_path: Path) -> None:
|
|
629
|
-
install(build_codex_main_home(tmp_path), tmp_path, "alpha")
|
|
630
|
-
launcher_path = tmp_path / "bin" / "codex-alpha.cmd"
|
|
631
|
-
fake_directory = tmp_path / "fake"
|
|
632
|
-
fake_directory.mkdir()
|
|
633
|
-
(fake_directory / "codex.cmd").write_bytes(NPM_CODEX_SHIM_TEXT.encode("utf-8"))
|
|
634
|
-
environment = dict(os.environ)
|
|
635
|
-
environment["PATH"] = str(fake_directory) + os.pathsep + environment["PATH"]
|
|
636
|
-
environment["CODEX_HOME"] = str(tmp_path / "decoy")
|
|
637
|
-
completed = subprocess.run(
|
|
638
|
-
["cmd", "/c", str(launcher_path)],
|
|
639
|
-
env=environment,
|
|
640
|
-
capture_output=True,
|
|
641
|
-
text=True,
|
|
642
|
-
check=False,
|
|
643
|
-
)
|
|
644
|
-
assert completed.stdout.strip() == str(tmp_path / "profiles" / "alpha")
|