cdx-manager 0.21.2 → 0.21.4
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/README.md +1 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_21_3.md +5 -0
- package/changelogs/CHANGELOGS_0_21_4.md +10 -0
- package/checksums/release-archives.json +22 -0
- package/docs/cli.md +1 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/claude_usage.py +41 -25
- package/src/cli.py +13 -3
- package/src/commands/launch.py +32 -16
- package/src/provider_runtime.py +27 -5
- package/src/session_service.py +4 -3
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
8
8
|
|
|
9
9
|
**The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
|
|
10
10
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.21.
|
|
1
|
+
0.21.4
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# CDX Manager 0.21.3
|
|
2
|
+
|
|
3
|
+
## Fixes
|
|
4
|
+
|
|
5
|
+
- Claude in Chrome now works from CDX Claude profiles (`claude --chrome`, `/chrome`, or `cdx set <name> --extra-args '--chrome'`). Profile isolation redirects HOME, so Claude Code looked for Chrome and its Claude extension inside the profile home, found neither, and never installed its native-messaging host where Chrome reads it. CDX now links the real Chrome directory into each Claude profile home, as it already did for the macOS keychain: `~/Library/Application Support/Google/Chrome` on macOS, `~/.config/google-chrome` on Linux. Windows needed no change: Chrome lives under `%LOCALAPPDATA%` and the host is registered in the registry, neither of which CDX redirects. Profile copies keep the link as a link and never copy the Chrome profile itself.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# CDX Manager 0.21.4
|
|
2
|
+
|
|
3
|
+
## Start a session on a first message
|
|
4
|
+
|
|
5
|
+
- `cdx <name> --prompt TEXT` and `cdx <name> --prompt-file PATH` launch an interactive session that starts on that message, for launcher scripts that open a session already pointed at a task. Both spellings `--flag value` and `--flag=value` work. They are refused before the provider starts when combined with each other, with `--resume` or `cdx resume`, with `--prompt-file -` (the session needs standard input), and for Ollama sessions (`ollama run` answers once and exits). A relative `--prompt-file` path is read from the current directory, not `--dir`. Contributed by Nicolas Rigaudy (#13).
|
|
6
|
+
|
|
7
|
+
## Fixes
|
|
8
|
+
|
|
9
|
+
- Claude sessions started from inside a running Claude Code session (a hook, or a terminal or script it spawned) saved no transcript: they inherited the parent's session markers (`CLAUDECODE`, `CLAUDE_CODE_CHILD_SESSION`, ...) and ran as a child session, which broke resume and interactive usage backfill. CDX now drops those markers for every Claude spawn; user settings such as `CLAUDE_CODE_DISABLE_*` still pass through. Contributed by Nicolas Rigaudy (#14).
|
|
10
|
+
- A prompt starting with `-` (from `--prompt`, `cdx handoff` or `cdx run`) is now passed after `--`, so Claude, Codex and Ollama no longer read it as an option (for example `-h` printed the provider's help instead of starting).
|
|
@@ -496,6 +496,28 @@
|
|
|
496
496
|
"x86_64-pc-windows-msvc": "c316d753c055be48d74e23537ca66093a1a7c36df8fb8518d6b909484ccb9415",
|
|
497
497
|
"x86_64-unknown-linux-musl": "f5a034ba301ecf10de33498cd99f00fa3aa6afda94d7c743390af05f4f718790"
|
|
498
498
|
}
|
|
499
|
+
},
|
|
500
|
+
"v0.21.2": {
|
|
501
|
+
"github_tarball_sha256": "b6225a95b590cc931988260597b870edbff00b3175f5c8091a0e23503d408e5e",
|
|
502
|
+
"github_zip_sha256": "b0714ed222e17ae9bdd658c37b6ba32b9026ddddfb4d20794783b2203f4ccc88",
|
|
503
|
+
"tray_assets": {
|
|
504
|
+
"aarch64-apple-darwin": "c4b8938b62fad445351938f79e0cd42d5342a530e88eeb0f52c51e2fe77e3391",
|
|
505
|
+
"aarch64-unknown-linux-musl": "6f9c959e4a17a39c3d548b48afe326ca2d68bee0e298f532c4880d080d41eb20",
|
|
506
|
+
"x86_64-apple-darwin": "f41652aa24d27e2e4deef566cd66c11fc7cb3b5155dc8d5a917433a3e172d33b",
|
|
507
|
+
"x86_64-pc-windows-msvc": "856f9dc459182adb10f66f2836abc862be82feff1cc3f1cf859764336487d781",
|
|
508
|
+
"x86_64-unknown-linux-musl": "f1832fdbe7114b4625da21d6a3a0f0c100f18d3a441796e809e568620e4cd345"
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
"v0.21.3": {
|
|
512
|
+
"github_tarball_sha256": "4f38562539953131cd2d8aedffea71ba1e7b2d8ccbc899b6521a1ed8c7507cf2",
|
|
513
|
+
"github_zip_sha256": "06e95cf08fcdec0e978c16980f58f282b01daee0513ab8db1ea659b423e58dc6",
|
|
514
|
+
"tray_assets": {
|
|
515
|
+
"aarch64-apple-darwin": "086ec24d2d1ad2820394e74a5f100cec62da1ffe2df91ee470c247f5b1cb9bb0",
|
|
516
|
+
"aarch64-unknown-linux-musl": "0f32a168e097a4d18da86fe0ae028465d5fb43afae6344da39a76463ba99c0ea",
|
|
517
|
+
"x86_64-apple-darwin": "348ec8e5ed89c1181a800ab65b5d736a46b8118d7b83445847049cae95735af8",
|
|
518
|
+
"x86_64-pc-windows-msvc": "9729f245b46420ae4b11c1a50d3ba9cba682854866ef37a3f334dccbdd70ac89",
|
|
519
|
+
"x86_64-unknown-linux-musl": "182fb651dab29a6a21b3b8ef56c196e954fd75230891f8532c21b09ff5fbacee"
|
|
520
|
+
}
|
|
499
521
|
}
|
|
500
522
|
}
|
|
501
523
|
}
|
package/docs/cli.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| `cdx --json` | List all sessions as a machine-readable JSON payload |
|
|
9
9
|
| `cdx <name>` | Launch a session (checks auth first) |
|
|
10
10
|
| `cdx <name> [--json]` | Launch a session; `--json` returns a structured success payload after the interactive run ends |
|
|
11
|
+
| `cdx <name> --prompt TEXT` / `cdx <name> --prompt-file PATH` | Launch a session that starts on this first message (not with `--resume` or Ollama sessions; `--prompt-file -` is refused because the session needs standard input; a relative `--prompt-file` path is read from the current directory, not `--dir`) |
|
|
11
12
|
| `cdx <name> -r` / `cdx <name> --resume` | Resume the provider-native conversation for a session when supported |
|
|
12
13
|
| `cdx add [provider] <name> [--model MODEL] [--json]` | Register a new session (`provider`: `codex`, `claude`, `antigravity`, or `ollama`; Ollama requires `--model`) |
|
|
13
14
|
| `cdx cp <source> <dest> [--json]` | Copy a session into another session name, overwriting the destination if it exists |
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/claude_usage.py
CHANGED
|
@@ -117,36 +117,52 @@ def claude_profile_store_dir(auth_home):
|
|
|
117
117
|
return os.path.join(auth_home, "credentials")
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
# Real-home directories a redirected profile home must still reach on macOS.
|
|
121
|
+
# Keychains: the login keychain (else a "Keychain Not Found" dialog on every launch).
|
|
122
|
+
# Chrome: Claude in Chrome looks for its extension and installs its native-messaging
|
|
123
|
+
# host under $HOME/Library/Application Support/Google/Chrome; without the link,
|
|
124
|
+
# `claude --chrome` sees no extension and Chrome never finds the host.
|
|
125
|
+
# Linux keeps Chrome under ~/.config (XDG), also resolved from HOME. Windows needs
|
|
126
|
+
# nothing: the host is registered in the registry and Chrome lives in
|
|
127
|
+
# %LOCALAPPDATA%, which profile isolation does not redirect.
|
|
128
|
+
_MACOS_SHARED_DIRS = (
|
|
129
|
+
("Library", "Keychains"),
|
|
130
|
+
("Library", "Application Support", "Google", "Chrome"),
|
|
131
|
+
)
|
|
132
|
+
_LINUX_SHARED_DIRS = ((".config", "google-chrome"),)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _link_real_home_dirs(auth_home):
|
|
136
|
+
"""Link the real-home directories in `_MACOS_SHARED_DIRS` (or `_LINUX_SHARED_DIRS`) into a redirected home.
|
|
137
|
+
|
|
138
|
+
Profile isolation redirects HOME (see `_home_env_overrides`), so these
|
|
139
|
+
paths do not exist in the profile home. A real directory already there is
|
|
140
|
+
left alone. Failure is non-fatal, and deliberately quiet: the profile
|
|
141
|
+
simply keeps working without that integration.
|
|
130
142
|
"""
|
|
131
|
-
if sys.platform
|
|
143
|
+
shared = _MACOS_SHARED_DIRS if sys.platform == "darwin" else _LINUX_SHARED_DIRS if sys.platform.startswith("linux") else ()
|
|
144
|
+
if not shared or pwd is None:
|
|
132
145
|
return
|
|
133
146
|
try:
|
|
134
147
|
real_home = pwd.getpwuid(os.getuid()).pw_dir
|
|
135
148
|
except (KeyError, OSError):
|
|
136
149
|
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
os.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
for parts in shared:
|
|
151
|
+
source = os.path.join(real_home, *parts)
|
|
152
|
+
if not os.path.isdir(source):
|
|
153
|
+
continue
|
|
154
|
+
link = os.path.join(auth_home, *parts)
|
|
155
|
+
if os.path.realpath(link) == os.path.realpath(source):
|
|
156
|
+
continue
|
|
157
|
+
if os.path.exists(link) and not os.path.islink(link):
|
|
158
|
+
continue
|
|
159
|
+
try:
|
|
160
|
+
os.makedirs(os.path.dirname(link), exist_ok=True)
|
|
161
|
+
if os.path.islink(link):
|
|
162
|
+
os.unlink(link)
|
|
163
|
+
os.symlink(source, link)
|
|
164
|
+
except OSError:
|
|
165
|
+
continue
|
|
150
166
|
|
|
151
167
|
|
|
152
168
|
def _secure_storage_overrides(auth_home):
|
|
@@ -167,7 +183,7 @@ def _secure_storage_overrides(auth_home):
|
|
|
167
183
|
the variable overrides that directory as well as naming the entry. Any
|
|
168
184
|
other value would also move where credentials are read from.
|
|
169
185
|
"""
|
|
170
|
-
|
|
186
|
+
_link_real_home_dirs(auth_home)
|
|
171
187
|
return {"CLAUDE_SECURESTORAGE_CONFIG_DIR": os.path.join(auth_home, ".claude")}
|
|
172
188
|
|
|
173
189
|
|
package/src/cli.py
CHANGED
|
@@ -70,6 +70,7 @@ from .cli_render import (
|
|
|
70
70
|
_visible_len,
|
|
71
71
|
format_error,
|
|
72
72
|
)
|
|
73
|
+
from .config import PROVIDER_OLLAMA
|
|
73
74
|
from .errors import CdxError
|
|
74
75
|
from .provider_runtime import (
|
|
75
76
|
LOG_ROTATE_BYTES,
|
|
@@ -288,6 +289,7 @@ def _print_help(use_color=False):
|
|
|
288
289
|
f" {_style('cdx set <name> --notify on', '36', use_color)} # agent finishes or needs attention",
|
|
289
290
|
f" {_style('cdx ready', '36', use_color)} # next session to become ready",
|
|
290
291
|
f" {_style('cdx <name> [--json]', '36', use_color)}",
|
|
292
|
+
f" {_style('cdx <name> --prompt TEXT|--prompt-file PATH', '36', use_color)} # start with a first message",
|
|
291
293
|
f" {_style('cdx --help', '36', use_color)}",
|
|
292
294
|
f" {_style('cdx --version', '36', use_color)}",
|
|
293
295
|
])
|
|
@@ -623,10 +625,18 @@ def main(argv, options=None):
|
|
|
623
625
|
if handler:
|
|
624
626
|
return handler(rest, ctx)
|
|
625
627
|
|
|
626
|
-
from .commands.launch import _parse_launch_args
|
|
627
|
-
directory, launch_args = _parse_launch_args(rest)
|
|
628
|
+
from .commands.launch import _parse_launch_args, _read_launch_prompt
|
|
629
|
+
directory, launch_args, prompt = _parse_launch_args(rest)
|
|
628
630
|
if all(arg in ("--json", "-r", "--resume") for arg in launch_args):
|
|
629
|
-
|
|
631
|
+
resume = "-r" in launch_args or "--resume" in launch_args
|
|
632
|
+
has_prompt = prompt["prompt"] is not None or prompt["prompt_file"] is not None
|
|
633
|
+
if resume and has_prompt:
|
|
634
|
+
raise CdxError("cdx <name>: --prompt and --prompt-file are not supported with --resume.")
|
|
635
|
+
# `ollama run MODEL PROMPT` answers once and exits instead of opening a session.
|
|
636
|
+
if has_prompt and (ctx["service"]["get_session"](command) or {}).get("provider") == PROVIDER_OLLAMA:
|
|
637
|
+
raise CdxError("cdx <name>: --prompt and --prompt-file are not supported for Ollama sessions.")
|
|
638
|
+
return handle_launch(command, ctx, initial_prompt=_read_launch_prompt(prompt), resume=resume,
|
|
639
|
+
directory=directory)
|
|
630
640
|
|
|
631
641
|
raise CdxError(f"Unknown command: {command}. Use cdx --help.")
|
|
632
642
|
|
package/src/commands/launch.py
CHANGED
|
@@ -30,31 +30,45 @@ from ..provider_runtime import (
|
|
|
30
30
|
_get_auth_home,
|
|
31
31
|
_run_interactive_provider_command,
|
|
32
32
|
)
|
|
33
|
+
from ..run_command import PROMPT_STDIN, read_run_prompt
|
|
33
34
|
from ..run_usage import estimate_cost
|
|
34
35
|
|
|
36
|
+
LAUNCH_USAGE = "Usage: cdx <name> [--dir PATH] [--prompt TEXT|--prompt-file PATH] [--resume] [--json]"
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
def _parse_launch_args(args):
|
|
37
|
-
"""Return the per-launch directory and ordinary launch flags."""
|
|
38
|
-
|
|
40
|
+
"""Return the per-launch directory, initial prompt source and ordinary launch flags."""
|
|
41
|
+
values = {"--dir": None, "--prompt": None, "--prompt-file": None}
|
|
39
42
|
cleaned = []
|
|
40
43
|
index = 0
|
|
41
44
|
while index < len(args):
|
|
42
45
|
arg = args[index]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
directory = args[index + 1]
|
|
47
|
-
index += 2
|
|
48
|
-
continue
|
|
49
|
-
if arg.startswith("--dir="):
|
|
50
|
-
if directory is not None or not arg.split("=", 1)[1]:
|
|
51
|
-
raise CdxError("Usage: cdx <name> [--dir PATH] [--resume] [--json]")
|
|
52
|
-
directory = arg.split("=", 1)[1]
|
|
46
|
+
flag, sep, inline = arg.partition("=")
|
|
47
|
+
if flag not in values:
|
|
48
|
+
cleaned.append(arg)
|
|
53
49
|
index += 1
|
|
54
50
|
continue
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
if sep:
|
|
52
|
+
value, index = inline, index + 1
|
|
53
|
+
elif index + 1 < len(args):
|
|
54
|
+
value, index = args[index + 1], index + 2
|
|
55
|
+
else:
|
|
56
|
+
raise CdxError(LAUNCH_USAGE)
|
|
57
|
+
if values[flag] is not None or not value:
|
|
58
|
+
raise CdxError(LAUNCH_USAGE)
|
|
59
|
+
values[flag] = value
|
|
60
|
+
if values["--prompt"] is not None and values["--prompt-file"] is not None:
|
|
61
|
+
raise CdxError("cdx <name>: --prompt and --prompt-file are mutually exclusive.")
|
|
62
|
+
if values["--prompt-file"] == PROMPT_STDIN:
|
|
63
|
+
raise CdxError("cdx <name>: --prompt-file - is not supported; the interactive session needs standard input.")
|
|
64
|
+
prompt = {"prompt": values["--prompt"], "prompt_file": values["--prompt-file"]}
|
|
65
|
+
return values["--dir"], cleaned, prompt
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _read_launch_prompt(prompt):
|
|
69
|
+
if prompt["prompt"] is None and prompt["prompt_file"] is None:
|
|
70
|
+
return None
|
|
71
|
+
return read_run_prompt(prompt)
|
|
58
72
|
|
|
59
73
|
|
|
60
74
|
def _launch_directory(session, ctx, explicit, json_flag):
|
|
@@ -154,7 +168,9 @@ def handle_can_resume(rest, ctx):
|
|
|
154
168
|
return 0
|
|
155
169
|
|
|
156
170
|
def handle_resume(rest, ctx):
|
|
157
|
-
directory, rest = _parse_launch_args(rest)
|
|
171
|
+
directory, rest, prompt = _parse_launch_args(rest)
|
|
172
|
+
if prompt["prompt"] is not None or prompt["prompt_file"] is not None:
|
|
173
|
+
raise CdxError("cdx resume: --prompt and --prompt-file are not supported when resuming.")
|
|
158
174
|
json_flag, args = _parse_json_flag(rest)
|
|
159
175
|
if len(args) != 1:
|
|
160
176
|
raise CdxError(RESUME_USAGE)
|
package/src/provider_runtime.py
CHANGED
|
@@ -137,6 +137,21 @@ REDACTED_PROMPT_ARG = "[prompt redacted]"
|
|
|
137
137
|
# Documented Claude Code switch that stops it from setting and clearing the
|
|
138
138
|
# terminal title, leaving cdx's shared title keeper as the only writer.
|
|
139
139
|
CLAUDE_DISABLE_TERMINAL_TITLE_ENV = "CLAUDE_CODE_DISABLE_TERMINAL_TITLE"
|
|
140
|
+
# Set by a running Claude Code session for its own child processes. When cdx is
|
|
141
|
+
# started from inside one (a hook, or a terminal or script it spawned), the new
|
|
142
|
+
# claude inherits them, treats itself as a child session and turns transcript
|
|
143
|
+
# saving off, which breaks resume and usage backfill.
|
|
144
|
+
CLAUDE_PARENT_SESSION_ENV = (
|
|
145
|
+
"CLAUDECODE",
|
|
146
|
+
"CLAUDE_CODE_CHILD_SESSION",
|
|
147
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
148
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
149
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
150
|
+
"CLAUDE_CODE_EXECPATH",
|
|
151
|
+
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
152
|
+
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
153
|
+
"CLAUDE_PID",
|
|
154
|
+
)
|
|
140
155
|
CLAUDE_CLI_MODEL_ALIASES = {
|
|
141
156
|
"claude-sonnet": "sonnet",
|
|
142
157
|
"claude-opus": "opus",
|
|
@@ -216,6 +231,8 @@ def _claude_env(base_env, auth_home, own_terminal_title=False):
|
|
|
216
231
|
env = {**base_env, **_home_env_overrides(auth_home)}
|
|
217
232
|
env.pop("CLAUDE_CONFIG_DIR", None)
|
|
218
233
|
env.pop("CODEX_HOME", None)
|
|
234
|
+
for key in CLAUDE_PARENT_SESSION_ENV:
|
|
235
|
+
env.pop(key, None)
|
|
219
236
|
env.setdefault("ANTHROPIC_PROFILE", _anthropic_profile_name())
|
|
220
237
|
if own_terminal_title:
|
|
221
238
|
env[CLAUDE_DISABLE_TERMINAL_TITLE_ENV] = "1"
|
|
@@ -672,7 +689,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
672
689
|
args += preference_args
|
|
673
690
|
args += _extra_args(session)
|
|
674
691
|
if initial_prompt:
|
|
675
|
-
|
|
692
|
+
# `--` keeps a prompt that starts with `-` from being read as an option.
|
|
693
|
+
args += ["--", initial_prompt]
|
|
676
694
|
auth_home = _get_auth_home(session)
|
|
677
695
|
claude_env = _claude_env(env, auth_home, own_terminal_title=True)
|
|
678
696
|
oauth_token = _read_claude_launch_oauth_token(auth_home)
|
|
@@ -711,7 +729,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
711
729
|
# failed only on the model pull.
|
|
712
730
|
args = ["run", model] + _launch_config_args(session)
|
|
713
731
|
if initial_prompt:
|
|
714
|
-
|
|
732
|
+
# `--` keeps a prompt that starts with `-` from being read as an option.
|
|
733
|
+
args += ["--", initial_prompt]
|
|
715
734
|
return _wrap_launch_with_transcript(session, {
|
|
716
735
|
"command": "ollama",
|
|
717
736
|
"args": args,
|
|
@@ -728,7 +747,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
|
|
|
728
747
|
args += _launch_config_args(session)
|
|
729
748
|
args += preference_args
|
|
730
749
|
if initial_prompt:
|
|
731
|
-
|
|
750
|
+
# `--` keeps a prompt that starts with `-` from being read as an option.
|
|
751
|
+
args += ["--", initial_prompt]
|
|
732
752
|
return _wrap_launch_with_transcript(session, {
|
|
733
753
|
"command": "codex",
|
|
734
754
|
"args": args,
|
|
@@ -933,7 +953,8 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
933
953
|
args += preference_args
|
|
934
954
|
args += _extra_args(session)
|
|
935
955
|
if initial_prompt:
|
|
936
|
-
|
|
956
|
+
# `--` keeps a prompt that starts with `-` from being read as an option.
|
|
957
|
+
args += ["--", initial_prompt]
|
|
937
958
|
auth_home = _get_auth_home(session)
|
|
938
959
|
claude_env = _claude_env(env, auth_home)
|
|
939
960
|
oauth_token = _read_claude_launch_oauth_token(auth_home)
|
|
@@ -958,7 +979,8 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
|
|
|
958
979
|
args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
|
|
959
980
|
args += preference_args
|
|
960
981
|
if initial_prompt:
|
|
961
|
-
|
|
982
|
+
# `--` keeps a prompt that starts with `-` from being read as an option.
|
|
983
|
+
args += ["--", initial_prompt]
|
|
962
984
|
return {
|
|
963
985
|
"command": "codex",
|
|
964
986
|
"args": args,
|
package/src/session_service.py
CHANGED
|
@@ -35,7 +35,7 @@ from contextlib import nullcontext
|
|
|
35
35
|
from functools import partial
|
|
36
36
|
|
|
37
37
|
from .claude_credentials import copy_keychain_credentials, delete_keychain_credentials
|
|
38
|
-
from .claude_usage import
|
|
38
|
+
from .claude_usage import _link_real_home_dirs
|
|
39
39
|
from .codex_usage import fetch_codex_rate_limits
|
|
40
40
|
from .config import (
|
|
41
41
|
MAX_LAUNCH_BUDGET_USD,
|
|
@@ -573,8 +573,9 @@ def copy_session(base_dir, store, source_name, dest_name):
|
|
|
573
573
|
with credential_copy:
|
|
574
574
|
if source["provider"] == PROVIDER_CLAUDE:
|
|
575
575
|
shutil.copytree(source_root, temp_root, symlinks=True,
|
|
576
|
-
ignore=lambda
|
|
577
|
-
|
|
576
|
+
ignore=lambda directory, names: [n for n in names if n == "Keychains" or (
|
|
577
|
+
n == "Chrome" and os.path.islink(os.path.join(directory, n)))])
|
|
578
|
+
_link_real_home_dirs(os.path.join(temp_root, "claude-home"))
|
|
578
579
|
else:
|
|
579
580
|
shutil.copytree(source_root, temp_root)
|
|
580
581
|
if existing:
|